From 96a788a3499bc5d709d136418f026526272fe94c Mon Sep 17 00:00:00 2001 From: Sergii Tkachenko Date: Mon, 29 Jul 2024 12:18:18 -0400 Subject: [PATCH 001/103] xds: Envoy proto sync to 2024-07-06 (#11401) `envoyproxy/envoy`: Sync protos to the latest imported version https://github.com/envoyproxy/envoy/commit/ab911ac2ff971f805ec822ad4d4ff6b42a61cc7c (commit 2024-07-06, cl/651956889). Should be a noop, just a routine xDS proto update to make upcoming RLQS-related imports simpler. --- buildscripts/data-plane-api-no-envoy.patch | 50 -------- repositories.bzl | 9 +- xds/third_party/envoy/import.sh | 2 +- .../envoy/config/accesslog/v3/accesslog.proto | 1 + .../envoy/config/bootstrap/v3/bootstrap.proto | 17 ++- .../envoy/config/cluster/v3/cluster.proto | 46 ++++++-- .../config/cluster/v3/outlier_detection.proto | 16 ++- .../proto/envoy/config/core/v3/base.proto | 107 +++++++++++++++++- .../envoy/config/core/v3/config_source.proto | 8 +- .../envoy/config/core/v3/grpc_service.proto | 19 +++- .../envoy/config/core/v3/health_check.proto | 20 +++- .../proto/envoy/config/core/v3/protocol.proto | 38 +++++-- .../envoy/config/endpoint/v3/endpoint.proto | 6 + .../endpoint/v3/endpoint_components.proto | 5 +- .../config/endpoint/v3/load_report.proto | 29 ++++- .../envoy/config/listener/v3/listener.proto | 5 +- .../config/listener/v3/quic_config.proto | 13 ++- .../proto/envoy/config/rbac/v3/rbac.proto | 6 +- .../config/route/v3/route_components.proto | 15 ++- .../envoy/config/trace/v3/dynamic_ot.proto | 10 +- .../proto/envoy/config/trace/v3/zipkin.proto | 7 +- .../envoy/data/accesslog/v3/accesslog.proto | 5 +- .../filters/http/rbac/v3/rbac.proto | 10 +- .../v3/http_connection_manager.proto | 6 +- .../least_request/v3/least_request.proto | 39 ++++++- .../transport_sockets/tls/v3/common.proto | 34 +++++- .../transport_sockets/tls/v3/tls.proto | 18 +-- .../proto/envoy/type/matcher/v3/string.proto | 8 +- 28 files changed, 419 insertions(+), 130 deletions(-) delete mode 100644 buildscripts/data-plane-api-no-envoy.patch diff --git a/buildscripts/data-plane-api-no-envoy.patch b/buildscripts/data-plane-api-no-envoy.patch deleted file mode 100644 index 0c1eec60f1c..00000000000 --- a/buildscripts/data-plane-api-no-envoy.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 786c93ccaae9891338f098a5aba60e9987d78bd3 Mon Sep 17 00:00:00 2001 -From: "update-envoy[bot]" - <135279899+update-envoy[bot]@users.noreply.github.com> -Date: Mon, 17 Jun 2024 02:25:24 +0000 -Subject: [PATCH] bazel: `@envoy_api` should not depend on `@envoy` (#34759) - -The extra dependency was introduced in 65273b2a9b. pgv.patch is only -used by envoy_api, so just moving the file avoids the dependency. - -Signed-off-by: Eric Anderson - -Mirrored from https://github.com/envoyproxy/envoy @ 9fde867399cc7fcf97815995f8466f62172b26f6 ---- - bazel/pgv.patch | 13 +++++++++++++ - bazel/repositories.bzl | 2 +- - 2 files changed, 14 insertions(+), 1 deletion(-) - create mode 100644 bazel/pgv.patch - -diff --git a/bazel/pgv.patch b/bazel/pgv.patch -new file mode 100644 -index 000000000..81e25abfe ---- /dev/null -+++ b/bazel/pgv.patch -@@ -0,0 +1,13 @@ -+--- a/templates/cc/register.go 2023-06-22 14:25:05.776175085 +0000 -++++ b/templates/cc/register.go 2023-06-22 14:26:33.008090583 +0000 -+@@ -116,6 +116,10 @@ -+ func (fns CCFuncs) methodName(name interface{}) string { -+ nameStr := fmt.Sprintf("%s", name) -+ switch nameStr { -++ case "concept": -++ return "concept_" -++ case "requires": -++ return "requires_" -+ case "const": -+ return "const_" -+ case "inline": -diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl -index 3e24566a9..7813b0abd 100644 ---- a/bazel/repositories.bzl -+++ b/bazel/repositories.bzl -@@ -19,7 +19,7 @@ def api_dependencies(): - external_http_archive( - name = "com_envoyproxy_protoc_gen_validate", - patch_args = ["-p1"], -- patches = ["@envoy//bazel:pgv.patch"], -+ patches = ["@envoy_api//bazel:pgv.patch"], - ) - external_http_archive( - name = "com_google_googleapis", diff --git a/repositories.bzl b/repositories.bzl index 12dca04862b..c2be72c476f 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -130,14 +130,11 @@ def grpc_java_repositories(bzlmod = False): if not native.existing_rule("envoy_api"): http_archive( name = "envoy_api", - sha256 = "c4c9c43903e413924b0cb08e9747f3c3a0727ad221a3c446a326db32def18c60", - strip_prefix = "data-plane-api-1611a7304794e13efe2d26f8480a2d2473a528c5", + sha256 = "cb7cd388eaa297320d392c872ceb82571dee71f4b6f1c4546b0c0a399636f523", + strip_prefix = "data-plane-api-874e3aa8c3aa5086b6bffa2166e0e0077bb32f71", urls = [ - "https://storage.googleapis.com/grpc-bazel-mirror/github.com/envoyproxy/data-plane-api/archive/1611a7304794e13efe2d26f8480a2d2473a528c5.tar.gz", - "https://github.com/envoyproxy/data-plane-api/archive/1611a7304794e13efe2d26f8480a2d2473a528c5.tar.gz", + "https://github.com/envoyproxy/data-plane-api/archive/874e3aa8c3aa5086b6bffa2166e0e0077bb32f71.tar.gz", ], - patch_args = ["-p1"], - patches = ["@io_grpc_grpc_java//:buildscripts/data-plane-api-no-envoy.patch"], ) def com_google_protobuf(): diff --git a/xds/third_party/envoy/import.sh b/xds/third_party/envoy/import.sh index adc1e5e9e65..3eeb46cf664 100755 --- a/xds/third_party/envoy/import.sh +++ b/xds/third_party/envoy/import.sh @@ -17,7 +17,7 @@ set -e # import VERSION from the google internal copybara_version.txt for Envoy -VERSION=147e6b9523d8d2ae0d9d2205254d6e633644c6fe +VERSION=ab911ac2ff971f805ec822ad4d4ff6b42a61cc7c DOWNLOAD_URL="https://github.com/envoyproxy/envoy/archive/${VERSION}.tar.gz" DOWNLOAD_BASE_DIR="envoy-${VERSION}" SOURCE_PROTO_BASE_DIR="${DOWNLOAD_BASE_DIR}/api" diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/accesslog/v3/accesslog.proto b/xds/third_party/envoy/src/main/proto/envoy/config/accesslog/v3/accesslog.proto index fe3ba2bc97c..5599f8082d3 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/accesslog/v3/accesslog.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/accesslog/v3/accesslog.proto @@ -256,6 +256,7 @@ message ResponseFlagFilter { in: "OM" in: "DF" in: "DO" + in: "DR" } } }]; diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/bootstrap/v3/bootstrap.proto b/xds/third_party/envoy/src/main/proto/envoy/config/bootstrap/v3/bootstrap.proto index b5f36f273bc..94868f13432 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/bootstrap/v3/bootstrap.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/bootstrap/v3/bootstrap.proto @@ -41,7 +41,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // ` for more detail. // Bootstrap :ref:`configuration overview `. -// [#next-free-field: 41] +// [#next-free-field: 42] message Bootstrap { option (udpa.annotations.versioning).previous_message_type = "envoy.config.bootstrap.v2.Bootstrap"; @@ -411,6 +411,10 @@ message Bootstrap { // Optional gRPC async manager config. GrpcAsyncClientManagerConfig grpc_async_client_manager_config = 40; + + // Optional configuration for memory allocation manager. + // Memory releasing is only supported for `tcmalloc allocator `_. + MemoryAllocatorManager memory_allocator_manager = 41; } // Administration interface :ref:`operations documentation @@ -734,3 +738,14 @@ message CustomInlineHeader { // The type of the header that is expected to be set as the inline header. InlineHeaderType inline_header_type = 2 [(validate.rules).enum = {defined_only: true}]; } + +message MemoryAllocatorManager { + // Configures tcmalloc to perform background release of free memory in amount of bytes per ``memory_release_interval`` interval. + // If equals to ``0``, no memory release will occur. Defaults to ``0``. + uint64 bytes_to_release = 1; + + // Interval in milliseconds for memory releasing. If specified, during every + // interval Envoy will try to release ``bytes_to_release`` of free memory back to operating system for reuse. + // Defaults to 1000 milliseconds. + google.protobuf.Duration memory_release_interval = 2; +} diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/cluster/v3/cluster.proto b/xds/third_party/envoy/src/main/proto/envoy/config/cluster/v3/cluster.proto index 9b847a33126..0074e63dff6 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/cluster/v3/cluster.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/cluster/v3/cluster.proto @@ -168,7 +168,7 @@ message Cluster { // The name of the match, used in stats generation. string name = 1 [(validate.rules).string = {min_len: 1}]; - // Optional endpoint metadata match criteria. + // Optional 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 @@ -754,12 +754,14 @@ message Cluster { reserved "hosts", "tls_context", "extension_protocol_options"; - // Configuration to use different transport sockets for different endpoints. - // 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. - // For example, with the following match + // Configuration to use different transport sockets for different endpoints. 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. If a match is not found, the search continues in + // :ref:`LocalityLbEndpoints.Metadata + // `. The first :ref:`match + // ` is used. For example, with + // the following match // // .. code-block:: yaml // @@ -783,8 +785,9 @@ message Cluster { // 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. + // ``TransportSocketMatch``, the locality metadata is then checked for a match. Barring any + // matches in the endpoint or locality metadata, the 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. // @@ -1236,6 +1239,26 @@ message UpstreamConnectionOptions { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.UpstreamConnectionOptions"; + enum FirstAddressFamilyVersion { + // respect the native ranking of destination ip addresses returned from dns + // resolution + DEFAULT = 0; + + V4 = 1; + + V6 = 2; + } + + message HappyEyeballsConfig { + // Specify the IP address family to attempt connection first in happy + // eyeballs algorithm according to RFC8305#section-4. + FirstAddressFamilyVersion first_address_family_version = 1; + + // Specify the number of addresses of the first_address_family_version being + // attempted for connection before the other address family. + google.protobuf.UInt32Value first_address_family_count = 2 [(validate.rules).uint32 = {gte: 1}]; + } + // If set then set SO_KEEPALIVE on the socket to enable TCP Keepalives. core.v3.TcpKeepalive tcp_keepalive = 1; @@ -1243,6 +1266,11 @@ message UpstreamConnectionOptions { // This can be used by extensions during processing of requests. The association mechanism is // implementation specific. Defaults to false due to performance concerns. bool set_local_interface_name_on_upstream_connections = 2; + + // Configurations for happy eyeballs algorithm. + // Add configs for first_address_family_version and first_address_family_count + // when sorting destination ip addresses. + HappyEyeballsConfig happy_eyeballs_config = 3; } message TrackClusterStats { diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/cluster/v3/outlier_detection.proto b/xds/third_party/envoy/src/main/proto/envoy/config/cluster/v3/outlier_detection.proto index 11289e26b4f..822d81da850 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/cluster/v3/outlier_detection.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/cluster/v3/outlier_detection.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package envoy.config.cluster.v3; +import "envoy/config/core/v3/extension.proto"; + import "google/protobuf/duration.proto"; import "google/protobuf/wrappers.proto"; @@ -19,7 +21,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // See the :ref:`architecture overview ` for // more information on outlier detection. -// [#next-free-field: 24] +// [#next-free-field: 26] message OutlierDetection { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.cluster.OutlierDetection"; @@ -40,8 +42,8 @@ message OutlierDetection { // Defaults to 30000ms or 30s. google.protobuf.Duration base_ejection_time = 3 [(validate.rules).duration = {gt {}}]; - // 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. + // The maximum % of an upstream cluster that can be ejected due to outlier detection. Defaults to 10% . + // Will eject at least one host regardless of the value if :ref:`always_eject_one_host` is enabled. google.protobuf.UInt32Value max_ejection_percent = 4 [(validate.rules).uint32 = {lte: 100}]; // The % chance that a host will be actually ejected when an outlier status @@ -167,4 +169,12 @@ message OutlierDetection { // To change this default behavior set this config to ``false`` where active health checking will not uneject the host. // Defaults to true. google.protobuf.BoolValue successful_active_health_check_uneject_host = 23; + + // Set of host's passive monitors. + // [#not-implemented-hide:] + repeated core.v3.TypedExtensionConfig monitors = 24; + + // If enabled, at least one host is ejected regardless of the value of :ref:`max_ejection_percent`. + // Defaults to false. + google.protobuf.BoolValue always_eject_one_host = 25; } diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/base.proto b/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/base.proto index 97131e4b8c6..df91565d0a7 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/base.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/base.proto @@ -245,7 +245,8 @@ message Metadata { // :ref:`typed_filter_metadata ` // fields are present in the metadata with same keys, // only ``typed_filter_metadata`` field will be parsed. - map filter_metadata = 1; + map filter_metadata = 1 + [(validate.rules).map = {keys {string {min_len: 1}}}]; // Key is the reverse DNS filter name, e.g. com.acme.widget. The ``envoy.*`` // namespace is reserved for Envoy's built-in filters. @@ -253,7 +254,8 @@ message Metadata { // 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. - map typed_filter_metadata = 2; + map typed_filter_metadata = 2 + [(validate.rules).map = {keys {string {min_len: 1}}}]; } // Runtime derived uint32 with a default when not specified. @@ -301,6 +303,59 @@ message RuntimeFeatureFlag { string runtime_key = 2 [(validate.rules).string = {min_len: 1}]; } +message KeyValue { + // The key of the key/value pair. + string key = 1 [(validate.rules).string = {min_len: 1 max_bytes: 16384}]; + + // The value of the key/value pair. + bytes value = 2; +} + +// Key/value pair plus option to control append behavior. This is used to specify +// key/value pairs that should be appended to a set of existing key/value pairs. +message KeyValueAppend { + // Describes the supported actions types for key/value pair append action. + enum KeyValueAppendAction { + // If the key already exists, this action will result in the following behavior: + // + // - Comma-concatenated value if multiple values are not allowed. + // - New value added to the list of values if multiple values are allowed. + // + // If the key doesn't exist then this will add pair with specified key and value. + APPEND_IF_EXISTS_OR_ADD = 0; + + // This action will add the key/value pair if it doesn't already exist. If the + // key 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 key already exists. If the key doesn't exist then this will add + // the pair with specified key and value. + OVERWRITE_IF_EXISTS_OR_ADD = 2; + + // This action will overwrite the specified value by discarding any existing + // values if the key already exists. If the key doesn't exist then this will + // be no-op. + OVERWRITE_IF_EXISTS = 3; + } + + // Key/value pair entry that this option to append or overwrite. + KeyValue entry = 1 [(validate.rules).message = {required: true}]; + + // Describes the action taken to append/overwrite the given value for an existing + // key or to only add this key if it's absent. + KeyValueAppendAction action = 2 [(validate.rules).enum = {defined_only: true}]; +} + +// Key/value pair to append or remove. +message KeyValueMutation { + // Key/value pair to append or overwrite. Only one of ``append`` or ``remove`` can be set. + KeyValueAppend append = 1; + + // Key to remove. Only one of ``append`` or ``remove`` can be set. + string remove = 2 [(validate.rules).string = {max_bytes: 16384}]; +} + // Query parameter name/value pair. message QueryParameter { // The key of the query parameter. Case sensitive. @@ -409,6 +464,7 @@ message WatchedDirectory { } // Data source consisting of a file, an inline value, or an environment variable. +// [#next-free-field: 6] message DataSource { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.DataSource"; @@ -427,12 +483,47 @@ message DataSource { // Environment variable data source. string environment_variable = 4 [(validate.rules).string = {min_len: 1}]; } + + // Watched directory that is watched for file changes. If this is set explicitly, the file + // specified in the ``filename`` field will be reloaded when relevant file move events occur. + // + // .. note:: + // This field only makes sense when the ``filename`` field is set. + // + // .. note:: + // Envoy only updates when the file is replaced by a file move, and not when the file is + // edited in place. + // + // .. note:: + // Not all use cases of ``DataSource`` support watching directories. It depends on the + // specific usage of the ``DataSource``. See the documentation of the parent message for + // details. + WatchedDirectory watched_directory = 5; } // The message specifies the retry policy of remote data source when fetching fails. +// [#next-free-field: 7] message RetryPolicy { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.RetryPolicy"; + // See :ref:`RetryPriority `. + message RetryPriority { + string name = 1 [(validate.rules).string = {min_len: 1}]; + + oneof config_type { + google.protobuf.Any typed_config = 2; + } + } + + // See :ref:`RetryHostPredicate `. + message RetryHostPredicate { + string name = 1 [(validate.rules).string = {min_len: 1}]; + + oneof config_type { + google.protobuf.Any typed_config = 2; + } + } + // 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. @@ -442,6 +533,18 @@ message RetryPolicy { // defaults to 1. google.protobuf.UInt32Value num_retries = 2 [(udpa.annotations.field_migrate).rename = "max_retries"]; + + // For details, see :ref:`retry_on `. + string retry_on = 3; + + // For details, see :ref:`retry_priority `. + RetryPriority retry_priority = 4; + + // For details, see :ref:`RetryHostPredicate `. + repeated RetryHostPredicate retry_host_predicate = 5; + + // For details, see :ref:`host_selection_retry_max_attempts `. + int64 host_selection_retry_max_attempts = 6; } // The message specifies how to fetch data from remote and how to verify it. diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/config_source.proto b/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/config_source.proto index 70204bad9eb..f0effd99e45 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/config_source.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/config_source.proto @@ -28,12 +28,10 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // xDS API and non-xDS services version. This is used to describe both resource and transport // protocol versions (in distinct configuration fields). enum 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 [deprecated = true, (envoy.annotations.deprecated_at_minor_version_enum) = "3.0"]; + // When not specified, we assume v3; it is the only supported version. + AUTO = 0; - // Use xDS v2 API. + // Use xDS v2 API. This is no longer supported. V2 = 1 [deprecated = true, (envoy.annotations.deprecated_at_minor_version_enum) = "3.0"]; // Use xDS v3 API. diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/grpc_service.proto b/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/grpc_service.proto index f266c7bce5b..5fd7921a806 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/grpc_service.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/grpc_service.proto @@ -25,10 +25,11 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // gRPC service configuration. This is used by :ref:`ApiConfigSource // ` and filter configurations. -// [#next-free-field: 6] +// [#next-free-field: 7] message GrpcService { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.GrpcService"; + // [#next-free-field: 6] message EnvoyGrpc { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.GrpcService.EnvoyGrpc"; @@ -49,6 +50,18 @@ message GrpcService { // Currently only supported for xDS gRPC streams. // If not set, xDS gRPC streams default base interval:500ms, maximum interval:30s will be applied. RetryPolicy retry_policy = 3; + + // Maximum gRPC message size that is allowed to be received. + // If a message over this limit is received, the gRPC stream is terminated with the RESOURCE_EXHAUSTED error. + // This limit is applied to individual messages in the streaming response and not the total size of streaming response. + // Defaults to 0, which means unlimited. + google.protobuf.UInt32Value max_receive_message_length = 4; + + // This provides gRPC client level control over envoy generated headers. + // If false, the header will be sent but it can be overridden by per stream option. + // If true, the header will be removed and can not be overridden by per stream option. + // Default to false. + bool skip_envoy_headers = 5; } // [#next-free-field: 9] @@ -300,4 +313,8 @@ message GrpcService { // documentation on :ref:`custom request headers // `. repeated HeaderValue initial_metadata = 5; + + // Optional default retry policy for streams toward the service. + // If an async stream doesn't have retry policy configured in its stream options, this retry policy is used. + RetryPolicy retry_policy = 6; } diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/health_check.proto b/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/health_check.proto index 2ec258d8ac0..821f042bbe6 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/health_check.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/health_check.proto @@ -5,6 +5,7 @@ package envoy.config.core.v3; import "envoy/config/core/v3/base.proto"; import "envoy/config/core/v3/event_service_config.proto"; import "envoy/config/core/v3/extension.proto"; +import "envoy/config/core/v3/proxy_protocol.proto"; import "envoy/type/matcher/v3/string.proto"; import "envoy/type/v3/http.proto"; import "envoy/type/v3/range.proto"; @@ -62,7 +63,7 @@ message HealthStatusSet { [(validate.rules).repeated = {items {enum {defined_only: true}}}]; } -// [#next-free-field: 26] +// [#next-free-field: 27] message HealthCheck { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.HealthCheck"; @@ -95,12 +96,11 @@ message HealthCheck { // 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. - string host = 1 [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}]; + string host = 1 [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE}]; // Specifies the HTTP path that will be requested during health checking. For example // ``/healthcheck``. - string path = 2 - [(validate.rules).string = {min_len: 1 well_known_regex: HTTP_HEADER_VALUE strict: false}]; + string path = 2 [(validate.rules).string = {min_len: 1 well_known_regex: HTTP_HEADER_VALUE}]; // [#not-implemented-hide:] HTTP specific payload. Payload send = 3; @@ -178,6 +178,13 @@ message HealthCheck { // payload block must be found, and in the order specified, but not // necessarily contiguous. repeated Payload receive = 2; + + // When setting this value, it tries to attempt health check request with ProxyProtocol. + // When ``send`` is presented, they are sent after preceding ProxyProtocol header. + // Only ProxyProtocol header is sent when ``send`` is not presented. + // It allows to use both ProxyProtocol V1 and V2. In V1, it presents L3/L4. In V2, it includes + // LOCAL command and doesn't include L3/L4. + ProxyProtocolConfig proxy_protocol_config = 3; } message RedisHealthCheck { @@ -392,6 +399,11 @@ message HealthCheck { // The default value is false. bool always_log_health_check_failures = 19; + // If set to true, health check success events will always be logged. If set to false, only host addition event will be logged + // if it is the first successful health check, or if the healthy threshold is reached. + // The default value is false. + bool always_log_health_check_success = 26; + // This allows overriding the cluster TLS settings, just for health check connections. TlsOptions tls_options = 21; diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/protocol.proto b/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/protocol.proto index d128dc6d93d..e2c5863d784 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/protocol.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/protocol.proto @@ -56,7 +56,7 @@ message QuicKeepAliveSettings { } // QUIC protocol options which apply to both downstream and upstream connections. -// [#next-free-field: 8] +// [#next-free-field: 9] message QuicProtocolOptions { // Maximum number of streams that the client can negotiate per connection. 100 // if not specified. @@ -64,7 +64,7 @@ message QuicProtocolOptions { // `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). + // 1 to 16777216 (2^24, maximum supported by QUICHE) and defaults to 16777216 (16 * 1024 * 1024). // // 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. @@ -76,8 +76,8 @@ message QuicProtocolOptions { [(validate.rules).uint32 = {lte: 16777216 gte: 1}]; // 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``. + // flow-control. Valid values rage from 1 to 25165824 (24MB, maximum supported by QUICHE) and defaults + // to 25165824 (24 * 1024 * 1024). // // 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. @@ -102,6 +102,15 @@ message QuicProtocolOptions { // A comma-separated list of strings representing QUIC client connection options defined in // `QUICHE `_ and to be sent by upstream connections. string client_connection_options = 7; + + // The duration that a QUIC connection stays idle before it closes itself. If this field is not present, QUICHE + // default 600s will be applied. + // For internal corporate network, a long timeout is often fine. + // But for client facing network, 30s is usually a good choice. + google.protobuf.Duration idle_network_timeout = 8 [(validate.rules).duration = { + lte {seconds: 600} + gte {seconds: 1} + }]; } message UpstreamHttpProtocolOptions { @@ -477,10 +486,10 @@ message Http2ProtocolOptions { // Allows proxying Websocket and other upgrades over H2 connect. bool allow_connect = 5; - // [#not-implemented-hide:] Hiding until envoy has full metadata support. + // [#not-implemented-hide:] Hiding until Envoy has full metadata support. // Still under implementation. DO NOT USE. // - // Allows metadata. See [metadata + // Allows sending and receiving HTTP/2 METADATA frames. See [metadata // docs](https://github.com/envoyproxy/envoy/blob/main/source/docs/h2_metadata.md) for more // information. bool allow_metadata = 6; @@ -609,7 +618,7 @@ message GrpcProtocolOptions { } // A message which allows using HTTP/3. -// [#next-free-field: 6] +// [#next-free-field: 7] message Http3ProtocolOptions { QuicProtocolOptions quic_protocol_options = 1; @@ -628,12 +637,27 @@ message Http3ProtocolOptions { // `_ // Note that HTTP/3 CONNECT is not yet an RFC. bool allow_extended_connect = 5 [(xds.annotations.v3.field_status).work_in_progress = true]; + + // [#not-implemented-hide:] Hiding until Envoy has full metadata support. + // Still under implementation. DO NOT USE. + // + // Allows sending and receiving HTTP/3 METADATA frames. See [metadata + // docs](https://github.com/envoyproxy/envoy/blob/main/source/docs/h2_metadata.md) for more + // information. + bool allow_metadata = 6; } // A message to control transformations to the :scheme header message SchemeHeaderTransformation { oneof transformation { // Overwrite any Scheme header with the contents of this string. + // If set, takes precedence over match_upstream. string scheme_to_overwrite = 1 [(validate.rules).string = {in: "http" in: "https"}]; } + + // Set the Scheme header to match the upstream transport protocol. For example, should a + // request be sent to the upstream over TLS, the scheme header will be set to "https". Should the + // request be sent over plaintext, the scheme header will be set to "http". + // If scheme_to_overwrite is set, this field is not used. + bool match_upstream = 2; } diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/endpoint/v3/endpoint.proto b/xds/third_party/envoy/src/main/proto/envoy/config/endpoint/v3/endpoint.proto index 20939526eb5..894f68310a4 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/endpoint/v3/endpoint.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/endpoint/v3/endpoint.proto @@ -77,6 +77,12 @@ message ClusterLoadAssignment { // // Envoy supports only one element and will NACK if more than one element is present. // Other xDS-capable data planes will not necessarily have this limitation. + // + // In Envoy, this ``drop_overloads`` config can be overridden by a runtime key + // "load_balancing_policy.drop_overload_limit" setting. This runtime key can be set to + // any integer number between 0 and 100. 0 means drop 0%. 100 means drop 100%. + // When both ``drop_overloads`` config and "load_balancing_policy.drop_overload_limit" + // setting are in place, the min of these two wins. repeated DropOverload drop_overloads = 2; // Priority levels and localities are considered overprovisioned with this diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/endpoint/v3/endpoint_components.proto b/xds/third_party/envoy/src/main/proto/envoy/config/endpoint/v3/endpoint_components.proto index ebd2bb4c332..6673691105e 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/endpoint/v3/endpoint_components.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/endpoint/v3/endpoint_components.proto @@ -147,7 +147,7 @@ message LedsClusterLocalityConfig { // A group of endpoints belonging to a Locality. // One can have multiple LocalityLbEndpoints for a locality, but only if // they have different priorities. -// [#next-free-field: 9] +// [#next-free-field: 10] message LocalityLbEndpoints { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.endpoint.LocalityLbEndpoints"; @@ -161,6 +161,9 @@ message LocalityLbEndpoints { // Identifies location of where the upstream hosts run. core.v3.Locality locality = 1; + // Metadata to provide additional information about the locality endpoints in aggregate. + core.v3.Metadata metadata = 9; + // 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``.] diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/endpoint/v3/load_report.proto b/xds/third_party/envoy/src/main/proto/envoy/config/endpoint/v3/load_report.proto index 832fe83dbb0..fbd1d36d5d0 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/endpoint/v3/load_report.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/endpoint/v3/load_report.proto @@ -8,6 +8,8 @@ import "envoy/config/core/v3/base.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/struct.proto"; +import "xds/annotations/v3/status.proto"; + import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; @@ -23,7 +25,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // 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] +// [#next-free-field: 12] message UpstreamLocalityStats { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.endpoint.UpstreamLocalityStats"; @@ -48,6 +50,31 @@ message UpstreamLocalityStats { // upstream endpoints in the locality. uint64 total_issued_requests = 8; + // The total number of connections in an established state at the time of the + // report. This field is aggregated over all the upstream endpoints in the + // locality. + // In Envoy, this information may be based on ``upstream_cx_active metric``. + // [#not-implemented-hide:] + uint64 total_active_connections = 9 [(xds.annotations.v3.field_status).work_in_progress = true]; + + // The total number of connections opened since the last report. + // This field is aggregated over all the upstream endpoints in the locality. + // In Envoy, this information may be based on ``upstream_cx_total`` metric + // compared to itself between start and end of an interval, i.e. + // ``upstream_cx_total``(now) - ``upstream_cx_total``(now - + // load_report_interval). + // [#not-implemented-hide:] + uint64 total_new_connections = 10 [(xds.annotations.v3.field_status).work_in_progress = true]; + + // The total number of connection failures since the last report. + // This field is aggregated over all the upstream endpoints in the locality. + // In Envoy, this information may be based on ``upstream_cx_connect_fail`` + // metric compared to itself between start and end of an interval, i.e. + // ``upstream_cx_connect_fail``(now) - ``upstream_cx_connect_fail``(now - + // load_report_interval). + // [#not-implemented-hide:] + uint64 total_fail_connections = 11 [(xds.annotations.v3.field_status).work_in_progress = true]; + // Stats for multi-dimensional load balancing. repeated EndpointLoadMetricStats load_metric_stats = 5; diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/listener/v3/listener.proto b/xds/third_party/envoy/src/main/proto/envoy/config/listener/v3/listener.proto index a1a3d82c1c8..9381d4eb7ac 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/listener/v3/listener.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/listener/v3/listener.proto @@ -53,7 +53,7 @@ message ListenerCollection { repeated xds.core.v3.CollectionEntry entries = 1; } -// [#next-free-field: 35] +// [#next-free-field: 36] message Listener { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.Listener"; @@ -387,6 +387,9 @@ message Listener { // Whether the listener should limit connections based upon the value of // :ref:`global_downstream_max_connections `. bool ignore_global_conn_limit = 31; + + // Whether the listener bypasses configured overload manager actions. + bool bypass_overload_manager = 35; } // A placeholder proto so that users can explicitly configure the standard diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/listener/v3/quic_config.proto b/xds/third_party/envoy/src/main/proto/envoy/config/listener/v3/quic_config.proto index 3a8ce2cd0a6..3ddebe900ef 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/listener/v3/quic_config.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/listener/v3/quic_config.proto @@ -24,7 +24,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: QUIC listener config] // Configuration specific to the UDP QUIC listener. -// [#next-free-field: 10] +// [#next-free-field: 12] message QuicProtocolOptions { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.listener.QuicProtocolOptions"; @@ -72,9 +72,18 @@ message QuicProtocolOptions { core.v3.TypedExtensionConfig connection_id_generator_config = 8; // 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. + // 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. // If not specified, Envoy will not advertise any server's preferred address. // [#extension-category: envoy.quic.server_preferred_address] core.v3.TypedExtensionConfig server_preferred_address_config = 9 [(xds.annotations.v3.field_status).work_in_progress = true]; + + // Configure the server to send transport parameter `disable_active_migration `_. + // Defaults to false (do not send this transport parameter). + google.protobuf.BoolValue send_disable_active_migration = 10; + + // Configure which implementation of ``quic::QuicConnectionDebugVisitor`` to be used for this listener. + // If not specified, no debug visitor will be attached to connections. + // [#extension-category: envoy.quic.connection_debug_visitor] + core.v3.TypedExtensionConfig connection_debug_visitor_config = 11; } diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/rbac/v3/rbac.proto b/xds/third_party/envoy/src/main/proto/envoy/config/rbac/v3/rbac.proto index 3a9271c0015..8d98fd7155d 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/rbac/v3/rbac.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/rbac/v3/rbac.proto @@ -194,7 +194,7 @@ message Policy { } // Permission defines an action (or actions) that a principal can take. -// [#next-free-field: 13] +// [#next-free-field: 14] message Permission { option (udpa.annotations.versioning).previous_message_type = "envoy.config.rbac.v2.Permission"; @@ -270,6 +270,10 @@ message Permission { // Extension for configuring custom matchers for RBAC. // [#extension-category: envoy.rbac.matchers] core.v3.TypedExtensionConfig matcher = 12; + + // URI template path matching. + // [#extension-category: envoy.path.match] + core.v3.TypedExtensionConfig uri_template = 13; } } diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/route/v3/route_components.proto b/xds/third_party/envoy/src/main/proto/envoy/config/route/v3/route_components.proto index 1e2b486d288..7e2ff33da5c 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/route/v3/route_components.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/route/v3/route_components.proto @@ -673,7 +673,7 @@ message RouteMatch { // :ref:`CorsPolicy in filter extension ` // as as alternative. // -// [#next-free-field: 13] +// [#next-free-field: 14] message CorsPolicy { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.CorsPolicy"; @@ -727,6 +727,10 @@ message CorsPolicy { // // More details refer to https://developer.chrome.com/blog/private-network-access-preflight. google.protobuf.BoolValue allow_private_network_access = 12; + + // Specifies if preflight requests not matching the configured allowed origin should be forwarded + // to the upstream. Default is true. + google.protobuf.BoolValue forward_not_matching_preflights = 13; } // [#next-free-field: 42] @@ -759,7 +763,8 @@ message RouteAction { // 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``. + // useful for logging. For example, ``cluster1`` becomes ``cluster1-shadow``. This behavior can be + // disabled by setting ``disable_shadow_host_suffix_append`` to ``true``. // // .. note:: // @@ -768,7 +773,7 @@ message RouteAction { // .. note:: // // Shadowing doesn't support Http CONNECT and upgrades. - // [#next-free-field: 6] + // [#next-free-field: 7] message RequestMirrorPolicy { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.RouteAction.RequestMirrorPolicy"; @@ -814,6 +819,9 @@ message RouteAction { // Determines if the trace span should be sampled. Defaults to true. google.protobuf.BoolValue trace_sampled = 4; + + // Disables appending the ``-shadow`` suffix to the shadowed ``Host`` header. Defaults to ``false``. + bool disable_shadow_host_suffix_append = 6; } // Specifies the route's hashing policy if the upstream cluster uses a hashing :ref:`load balancer @@ -1211,7 +1219,6 @@ message RouteAction { // :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. bool append_x_forwarded_host = 38; // Specifies the upstream timeout for the route. If not specified, the default is 15s. This diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/trace/v3/dynamic_ot.proto b/xds/third_party/envoy/src/main/proto/envoy/config/trace/v3/dynamic_ot.proto index 35971f30dfb..d2664ef717e 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/trace/v3/dynamic_ot.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/trace/v3/dynamic_ot.proto @@ -33,11 +33,15 @@ message DynamicOtConfig { string library = 1 [ deprecated = true, (validate.rules).string = {min_len: 1}, - (envoy.annotations.deprecated_at_minor_version) = "3.0" + (envoy.annotations.deprecated_at_minor_version) = "3.0", + (envoy.annotations.disallowed_by_default) = true ]; // The configuration to use when creating a tracer from the given dynamic // library. - google.protobuf.Struct config = 2 - [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; + google.protobuf.Struct config = 2 [ + deprecated = true, + (envoy.annotations.deprecated_at_minor_version) = "3.0", + (envoy.annotations.disallowed_by_default) = true + ]; } diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/trace/v3/zipkin.proto b/xds/third_party/envoy/src/main/proto/envoy/config/trace/v3/zipkin.proto index a9aefef0c6d..2d8f3195c31 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/trace/v3/zipkin.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/trace/v3/zipkin.proto @@ -82,5 +82,10 @@ message ZipkinConfig { // If this is set to true, then the // :ref:`start_child_span of router ` // SHOULD be set to true also to ensure the correctness of trace chain. - bool split_spans_for_request = 7; + // + // Both this field and ``start_child_span`` are deprecated by the + // :ref:`spawn_upstream_span `. + // Please use that ``spawn_upstream_span`` field to control the span creation. + bool split_spans_for_request = 7 + [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; } diff --git a/xds/third_party/envoy/src/main/proto/envoy/data/accesslog/v3/accesslog.proto b/xds/third_party/envoy/src/main/proto/envoy/data/accesslog/v3/accesslog.proto index a247c08df30..2e02f1eb455 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/data/accesslog/v3/accesslog.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/data/accesslog/v3/accesslog.proto @@ -271,7 +271,7 @@ message AccessLogCommon { } // Flags indicating occurrences during request/response processing. -// [#next-free-field: 28] +// [#next-free-field: 29] message ResponseFlags { option (udpa.annotations.versioning).previous_message_type = "envoy.data.accesslog.v2.ResponseFlags"; @@ -372,6 +372,9 @@ message ResponseFlags { // Indicates a DNS resolution failed. bool dns_resolution_failure = 27; + + // Indicates a downstream remote codec level reset was received on the stream + bool downstream_remote_reset = 28; } // Properties of a negotiated TLS connection. diff --git a/xds/third_party/envoy/src/main/proto/envoy/extensions/filters/http/rbac/v3/rbac.proto b/xds/third_party/envoy/src/main/proto/envoy/extensions/filters/http/rbac/v3/rbac.proto index eeb505a17fb..649869a255d 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/extensions/filters/http/rbac/v3/rbac.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/extensions/filters/http/rbac/v3/rbac.proto @@ -22,7 +22,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#extension: envoy.filters.http.rbac] // RBAC filter config. -// [#next-free-field: 6] +// [#next-free-field: 8] message RBAC { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.rbac.v2.RBAC"; @@ -34,6 +34,11 @@ message RBAC { config.rbac.v3.RBAC rules = 1 [(udpa.annotations.field_migrate).oneof_promotion = "rules_specifier"]; + // If specified, rules will emit stats with the given prefix. + // This is useful to distinguish the stat when there are more than 1 RBAC filter configured with + // rules. + string rules_stat_prefix = 6; + // The match tree to use when resolving RBAC action for incoming requests. Requests do not // match any matcher will be denied. // If absent, no enforcing RBAC matcher will be applied. @@ -62,6 +67,9 @@ message RBAC { // This is useful to distinguish the stat when there are more than 1 RBAC filter configured with // shadow rules. string shadow_rules_stat_prefix = 3; + + // If track_per_rule_stats is true, counters will be published for each rule and shadow rule. + bool track_per_rule_stats = 7; } message RBACPerRoute { diff --git a/xds/third_party/envoy/src/main/proto/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto b/xds/third_party/envoy/src/main/proto/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto index 7a92259eb43..9e7274daa53 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -37,7 +37,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // HTTP connection manager :ref:`configuration overview `. // [#extension: envoy.filters.network.http_connection_manager] -// [#next-free-field: 57] +// [#next-free-field: 58] message HttpConnectionManager { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager"; @@ -887,6 +887,10 @@ message HttpConnectionManager { // will be ignored if the ``x-forwarded-port`` header has been set by any trusted proxy in front of Envoy. bool append_x_forwarded_port = 51; + // Append the :ref:`config_http_conn_man_headers_x-envoy-local-overloaded` HTTP header in the scenario where + // the Overload Manager has been triggered. + bool append_local_overload = 57; + // 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. diff --git a/xds/third_party/envoy/src/main/proto/envoy/extensions/load_balancing_policies/least_request/v3/least_request.proto b/xds/third_party/envoy/src/main/proto/envoy/extensions/load_balancing_policies/least_request/v3/least_request.proto index ebef61852e2..095f6075286 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/extensions/load_balancing_policies/least_request/v3/least_request.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/extensions/load_balancing_policies/least_request/v3/least_request.proto @@ -7,6 +7,7 @@ import "envoy/extensions/load_balancing_policies/common/v3/common.proto"; import "google/protobuf/wrappers.proto"; +import "envoy/annotations/deprecation.proto"; import "udpa/annotations/status.proto"; import "validate/validate.proto"; @@ -22,10 +23,34 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // This configuration allows the built-in LEAST_REQUEST LB policy to be configured via the LB policy // extension point. See the :ref:`load balancing architecture overview // ` for more information. -// [#next-free-field: 6] +// [#next-free-field: 7] message LeastRequest { + // Available methods for selecting the host set from which to return the host with the + // fewest active requests. + enum SelectionMethod { + // Return host with fewest requests from a set of ``choice_count`` randomly selected hosts. + // Best selection method for most scenarios. + N_CHOICES = 0; + + // Return host with fewest requests from all hosts. + // Useful in some niche use cases involving low request rates and one of: + // (example 1) low request limits on workloads, or (example 2) few hosts. + // + // Example 1: Consider a workload type that can only accept one connection at a time. + // If such workloads are deployed across many hosts, only a small percentage of those + // workloads have zero connections at any given time, and the rate of new connections is low, + // the ``FULL_SCAN`` method is more likely to select a suitable host than ``N_CHOICES``. + // + // Example 2: Consider a workload type that is only deployed on 2 hosts. With default settings, + // the ``N_CHOICES`` method will return the host with more active requests 25% of the time. + // If the request rate is sufficiently low, the behavior of always selecting the host with least + // requests as of the last metrics refresh may be preferable. + FULL_SCAN = 1; + } + // 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. + // Only applies to the ``N_CHOICES`` selection method. google.protobuf.UInt32Value choice_count = 1 [(validate.rules).uint32 = {gte: 2}]; // The following formula is used to calculate the dynamic weights when hosts have different load @@ -61,8 +86,12 @@ message LeastRequest { common.v3.LocalityLbConfig locality_lb_config = 4; // [#not-implemented-hide:] - // Configuration for performing full scan on the list of hosts. - // If this configuration is set, when selecting the host a full scan on the list hosts will be - // used to select the one with least requests instead of using random choices. - google.protobuf.BoolValue enable_full_scan = 5; + // Unused. Replaced by the `selection_method` enum for better extensibility. + google.protobuf.BoolValue enable_full_scan = 5 + [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; + + // Method for selecting the host set from which to return the host with the fewest active requests. + // + // Defaults to ``N_CHOICES``. + SelectionMethod selection_method = 6 [(validate.rules).enum = {defined_only: true}]; } diff --git a/xds/third_party/envoy/src/main/proto/envoy/extensions/transport_sockets/tls/v3/common.proto b/xds/third_party/envoy/src/main/proto/envoy/extensions/transport_sockets/tls/v3/common.proto index d244adcdf54..c1a3f5b33b3 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/extensions/transport_sockets/tls/v3/common.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/extensions/transport_sockets/tls/v3/common.proto @@ -314,16 +314,32 @@ message SubjectAltNameMatcher { DNS = 2; URI = 3; IP_ADDRESS = 4; + OTHER_NAME = 5; } // Specification of type of SAN. Note that the default enum value is an invalid choice. SanType san_type = 1 [(validate.rules).enum = {defined_only: true not_in: 0}]; // Matcher for SAN value. + // + // The string matching for OTHER_NAME SAN values depends on their ASN.1 type: + // + // * OBJECT: Validated against its dotted numeric notation (e.g., "1.2.3.4") + // * BOOLEAN: Validated against strings "true" or "false" + // * INTEGER/ENUMERATED: Validated against a string containing the integer value + // * NULL: Validated against an empty string + // * Other types: Validated directly against the string value type.matcher.v3.StringMatcher matcher = 2 [(validate.rules).message = {required: true}]; + + // OID Value which is required if OTHER_NAME SAN type is used. + // For example, UPN OID is 1.3.6.1.4.1.311.20.2.3 + // (Reference: http://oid-info.com/get/1.3.6.1.4.1.311.20.2.3). + // + // If set for SAN types other than OTHER_NAME, it will be ignored. + string oid = 3; } -// [#next-free-field: 17] +// [#next-free-field: 18] message CertificateValidationContext { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.auth.CertificateValidationContext"; @@ -339,6 +355,9 @@ message CertificateValidationContext { ACCEPT_UNTRUSTED = 1; } + message SystemRootCerts { + } + reserved 4, 5; reserved "verify_subject_alt_name"; @@ -378,20 +397,23 @@ message CertificateValidationContext { // can be treated as trust anchor as well. It allows verification with building valid partial chain instead // of a full chain. // - // 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.] + // If ``ca_certificate_provider_instance`` is set, it takes precedence over ``trusted_ca``. config.core.v3.DataSource trusted_ca = 1 [(udpa.annotations.field_migrate).oneof_promotion = "ca_cert_source"]; // Certificate provider instance for fetching TLS certificates. // - // Only one of ``trusted_ca`` and ``ca_certificate_provider_instance`` may be specified. + // If set, takes precedence over ``trusted_ca``. // [#not-implemented-hide:] CertificateProviderPluginInstance ca_certificate_provider_instance = 13 [(udpa.annotations.field_migrate).oneof_promotion = "ca_cert_source"]; + // Use system root certs for validation. + // If present, system root certs are used only if neither of the ``trusted_ca`` + // or ``ca_certificate_provider_instance`` fields are set. + // [#not-implemented-hide:] + SystemRootCerts system_root_certs = 17; + // 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 diff --git a/xds/third_party/envoy/src/main/proto/envoy/extensions/transport_sockets/tls/v3/tls.proto b/xds/third_party/envoy/src/main/proto/envoy/extensions/transport_sockets/tls/v3/tls.proto index f94889cfad0..9d465c97321 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/extensions/transport_sockets/tls/v3/tls.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/extensions/transport_sockets/tls/v3/tls.proto @@ -248,11 +248,8 @@ message CommonTlsContext { // :ref:`Multiple TLS certificates ` can be associated with the // same context to allow both RSA and ECDSA certificates and support SNI-based selection. // - // Only one of ``tls_certificates``, ``tls_certificate_sds_secret_configs``, - // and ``tls_certificate_provider_instance`` may be used. - // [#next-major-version: These mutually exclusive fields should ideally be in a oneof, but it's - // not legal to put a repeated field in a oneof. In the next major version, we should rework - // this to avoid this problem.] + // If ``tls_certificate_provider_instance`` is set, this field is ignored. + // If this field is set, ``tls_certificate_sds_secret_configs`` is ignored. repeated TlsCertificate tls_certificates = 2; // Configs for fetching TLS certificates via SDS API. Note SDS API allows certificates to be @@ -261,17 +258,14 @@ message CommonTlsContext { // The same number and types of certificates as :ref:`tls_certificates ` // are valid in the the certificates fetched through this setting. // - // Only one of ``tls_certificates``, ``tls_certificate_sds_secret_configs``, - // and ``tls_certificate_provider_instance`` may be used. - // [#next-major-version: These mutually exclusive fields should ideally be in a oneof, but it's - // not legal to put a repeated field in a oneof. In the next major version, we should rework - // this to avoid this problem.] + // If ``tls_certificates`` or ``tls_certificate_provider_instance`` are set, this field + // is ignored. repeated SdsSecretConfig tls_certificate_sds_secret_configs = 6; // Certificate provider instance for fetching TLS certs. // - // Only one of ``tls_certificates``, ``tls_certificate_sds_secret_configs``, - // and ``tls_certificate_provider_instance`` may be used. + // If this field is set, ``tls_certificates`` and ``tls_certificate_provider_instance`` + // are ignored. // [#not-implemented-hide:] CertificateProviderPluginInstance tls_certificate_provider_instance = 14; diff --git a/xds/third_party/envoy/src/main/proto/envoy/type/matcher/v3/string.proto b/xds/third_party/envoy/src/main/proto/envoy/type/matcher/v3/string.proto index 2df1bd37a6a..10033749acd 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/type/matcher/v3/string.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/type/matcher/v3/string.proto @@ -4,6 +4,8 @@ package envoy.type.matcher.v3; import "envoy/type/matcher/v3/regex.proto"; +import "xds/core/v3/extension.proto"; + import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; @@ -17,7 +19,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: String matcher] // Specifies the way to match a string. -// [#next-free-field: 8] +// [#next-free-field: 9] message StringMatcher { option (udpa.annotations.versioning).previous_message_type = "envoy.type.matcher.StringMatcher"; @@ -61,6 +63,10 @@ message StringMatcher { // // * ``abc`` matches the value ``xyz.abc.def`` string contains = 7 [(validate.rules).string = {min_len: 1}]; + + // Use an extension as the matcher type. + // [#extension-category: envoy.string_matcher] + xds.core.v3.TypedExtensionConfig custom = 8; } // If true, indicates the exact/prefix/suffix/contains matching should be case insensitive. This From 448ec4f37e6ade0b9e547fd50bece6a22a317bf3 Mon Sep 17 00:00:00 2001 From: Jiajing LU Date: Tue, 30 Jul 2024 23:46:01 +0800 Subject: [PATCH 002/103] xds: XdsClient should unsubscribe on last resource (#11264) Otherwise, the server will continue sending updates and if we re-subscribe to the last resource, the server won't re-send it. Also completely remove the per-type state, as it could only add confusion. --- .../grpc/xds/client/ControlPlaneClient.java | 10 +++- .../io/grpc/xds/client/XdsClientImpl.java | 2 +- .../grpc/xds/GrpcXdsClientImplTestBase.java | 46 ++++++++++++++++++- .../io/grpc/xds/GrpcXdsClientImplV3Test.java | 5 +- 4 files changed, 57 insertions(+), 6 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/client/ControlPlaneClient.java b/xds/src/main/java/io/grpc/xds/client/ControlPlaneClient.java index 761c10ede6a..3074d1120ad 100644 --- a/xds/src/main/java/io/grpc/xds/client/ControlPlaneClient.java +++ b/xds/src/main/java/io/grpc/xds/client/ControlPlaneClient.java @@ -152,8 +152,14 @@ void adjustResourceSubscription(XdsResourceType resourceType) { startRpcStream(); } Collection resources = resourceStore.getSubscribedResources(serverInfo, resourceType); - if (resources != null) { - adsStream.sendDiscoveryRequest(resourceType, resources); + if (resources == null) { + resources = Collections.emptyList(); + } + adsStream.sendDiscoveryRequest(resourceType, resources); + if (resources.isEmpty()) { + // The resource type no longer has subscribing resources; clean up references to it + versions.remove(resourceType); + adsStream.respNonces.remove(resourceType); } } diff --git a/xds/src/main/java/io/grpc/xds/client/XdsClientImpl.java b/xds/src/main/java/io/grpc/xds/client/XdsClientImpl.java index 969660bf7d4..79147cd9862 100644 --- a/xds/src/main/java/io/grpc/xds/client/XdsClientImpl.java +++ b/xds/src/main/java/io/grpc/xds/client/XdsClientImpl.java @@ -281,7 +281,7 @@ public void cancelXdsResourceWatch(XdsResourceType @SuppressWarnings("unchecked") public void run() { ResourceSubscriber subscriber = - (ResourceSubscriber) resourceSubscribers.get(type).get(resourceName);; + (ResourceSubscriber) resourceSubscribers.get(type).get(resourceName); subscriber.removeWatcher(watcher); if (!subscriber.isWatched()) { subscriber.cancelResourceWatch(); diff --git a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java index fd276a849ce..6b04edcb9b8 100644 --- a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java +++ b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java @@ -133,6 +133,7 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.mockito.stubbing.Answer; +import org.mockito.verification.VerificationMode; /** * Tests for {@link XdsClientImpl}. @@ -2757,6 +2758,37 @@ public void edsResourceNotFound() { verifySubscribedResourcesMetadataSizes(0, 0, 0, 1); } + @Test + public void edsCleanupNonceAfterUnsubscription() { + Assume.assumeFalse(ignoreResourceDeletion()); + + // Suppose we have an EDS subscription A.1 + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(), "A.1", edsResourceWatcher); + DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); + assertThat(call).isNotNull(); + call.verifyRequest(EDS, "A.1", "", "", NODE); + + // EDS -> {A.1}, version 1 + List dropOverloads = ImmutableList.of(); + List endpointsV1 = ImmutableList.of(lbEndpointHealthy); + ImmutableMap resourcesV1 = ImmutableMap.of( + "A.1", Any.pack(mf.buildClusterLoadAssignment("A.1", endpointsV1, dropOverloads))); + call.sendResponse(EDS, resourcesV1.values().asList(), VERSION_1, "0000"); + // {A.1} -> ACK, version 1 + call.verifyRequest(EDS, "A.1", VERSION_1, "0000", NODE); + verify(edsResourceWatcher, times(1)).onChanged(any()); + + // trigger an EDS resource unsubscription. + xdsClient.cancelXdsResourceWatch(XdsEndpointResource.getInstance(), "A.1", edsResourceWatcher); + verifySubscribedResourcesMetadataSizes(0, 0, 0, 0); + call.verifyRequest(EDS, Arrays.asList(), VERSION_1, "0000", NODE); + + // When re-subscribing, the version and nonce were properly forgotten, so the request is the + // same as the initial request + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(), "A.1", edsResourceWatcher); + call.verifyRequest(EDS, "A.1", "", "", NODE, Mockito.timeout(2000).times(2)); + } + @Test public void edsResponseErrorHandling_allResourcesFailedUnpack() { DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), EDS_RESOURCE, @@ -3787,10 +3819,22 @@ protected abstract static class DiscoveryRpcCall { protected void verifyRequest( XdsResourceType type, List resources, String versionInfo, String nonce, - Node node) { + Node node, VerificationMode verificationMode) { throw new UnsupportedOperationException(); } + protected void verifyRequest( + XdsResourceType type, List resources, String versionInfo, String nonce, + Node node) { + verifyRequest(type, resources, versionInfo, nonce, node, Mockito.timeout(2000)); + } + + protected void verifyRequest( + XdsResourceType type, String resource, String versionInfo, String nonce, + Node node, VerificationMode verificationMode) { + verifyRequest(type, ImmutableList.of(resource), versionInfo, nonce, node, verificationMode); + } + protected void verifyRequest( XdsResourceType type, String resource, String versionInfo, String nonce, Node node) { verifyRequest(type, ImmutableList.of(resource), versionInfo, nonce, node); diff --git a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplV3Test.java b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplV3Test.java index 71d0895a252..2b2ce5cbd72 100644 --- a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplV3Test.java +++ b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplV3Test.java @@ -118,6 +118,7 @@ import org.mockito.ArgumentMatcher; import org.mockito.InOrder; import org.mockito.Mockito; +import org.mockito.verification.VerificationMode; /** * Tests for {@link XdsClientImpl} with protocol version v3. @@ -205,8 +206,8 @@ private DiscoveryRpcCallV3(StreamObserver requestObserver, @Override protected void verifyRequest( XdsResourceType type, List resources, String versionInfo, String nonce, - EnvoyProtoData.Node node) { - verify(requestObserver, Mockito.timeout(2000)).onNext(argThat(new DiscoveryRequestMatcher( + EnvoyProtoData.Node node, VerificationMode verificationMode) { + verify(requestObserver, verificationMode).onNext(argThat(new DiscoveryRequestMatcher( node.toEnvoyProtoNode(), versionInfo, resources, type.typeUrl(), nonce, null, null))); } From 0017c98f6b6b800b8515aa7799567b54830f8aea Mon Sep 17 00:00:00 2001 From: Sergii Tkachenko Date: Tue, 30 Jul 2024 15:17:49 -0400 Subject: [PATCH 003/103] xds: cncf/xds proto sync to 2024-07-24 (#11417) `cncf/xds`: Sync protos to the latest imported version cncf/xds@024c85f (commit 2024-07-23, cl/655545156). Should be a noop, just a routine xDS proto update to make upcoming RLQS-related imports simpler, see related #11401. Note that CEL is only added as a bazel dependency as now it's required to build cncf/xds. Actual third-party source import will be done in the follow up PR, where RLQS dependencies are added to the import scripts. --- MODULE.bazel | 2 ++ repositories.bzl | 15 ++++++++++++--- xds/third_party/xds/import.sh | 2 +- .../src/main/proto/udpa/annotations/migrate.proto | 2 +- .../main/proto/udpa/annotations/security.proto | 2 +- .../main/proto/udpa/annotations/sensitive.proto | 2 +- .../src/main/proto/udpa/annotations/status.proto | 2 +- .../main/proto/udpa/annotations/versioning.proto | 2 +- .../main/proto/xds/type/matcher/v3/string.proto | 7 ++++++- 9 files changed, 26 insertions(+), 10 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index b0c923c0c35..89ce334d270 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -47,6 +47,8 @@ IO_GRPC_GRPC_JAVA_ARTIFACTS = [ bazel_dep(name = "bazel_skylib", version = "1.7.1") bazel_dep(name = "googleapis", repo_name = "com_google_googleapis", version = "0.0.0-20240326-1c8d509c5") +# CEL Spec may be removed when cncf/xds MODULE is no longer using protobuf 27.x +bazel_dep(name = "cel-spec", repo_name = "dev_cel", version = "0.15.0") bazel_dep(name = "grpc", repo_name = "com_github_grpc_grpc", version = "1.56.3.bcr.1") bazel_dep(name = "grpc-proto", repo_name = "io_grpc_grpc_proto", version = "0.0.0-20240627-ec30f58") bazel_dep(name = "protobuf", repo_name = "com_google_protobuf", version = "23.1") diff --git a/repositories.bzl b/repositories.bzl index c2be72c476f..af3acc8ddcf 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -87,13 +87,22 @@ IO_GRPC_GRPC_JAVA_OVERRIDE_TARGETS = { def grpc_java_repositories(bzlmod = False): """Imports dependencies for grpc-java.""" + if not bzlmod and not native.existing_rule("dev_cel"): + http_archive( + name = "dev_cel", + strip_prefix = "cel-spec-0.15.0", + sha256 = "3ee09eb69dbe77722e9dee23dc48dc2cd9f765869fcf5ffb1226587c81791a0b", + urls = [ + "https://github.com/google/cel-spec/archive/refs/tags/v0.15.0.tar.gz", + ], + ) if not native.existing_rule("com_github_cncf_xds"): http_archive( name = "com_github_cncf_xds", - strip_prefix = "xds-e9ce68804cb4e64cab5a52e3c8baf840d4ff87b7", - sha256 = "0d33b83f8c6368954e72e7785539f0d272a8aba2f6e2e336ed15fd1514bc9899", + strip_prefix = "xds-024c85f92f20cab567a83acc50934c7f9711d124", + sha256 = "5f403aa681711500ca8e62387be3e37d971977db6e88616fc21862a406430649", urls = [ - "https://github.com/cncf/xds/archive/e9ce68804cb4e64cab5a52e3c8baf840d4ff87b7.tar.gz", + "https://github.com/cncf/xds/archive/024c85f92f20cab567a83acc50934c7f9711d124.tar.gz", ], ) if not bzlmod and not native.existing_rule("com_github_grpc_grpc"): diff --git a/xds/third_party/xds/import.sh b/xds/third_party/xds/import.sh index cda86d0368f..44f9ad12ed4 100755 --- a/xds/third_party/xds/import.sh +++ b/xds/third_party/xds/import.sh @@ -17,7 +17,7 @@ set -e # import VERSION from one of the google internal CLs -VERSION=e9ce68804cb4e64cab5a52e3c8baf840d4ff87b7 +VERSION=024c85f92f20cab567a83acc50934c7f9711d124 DOWNLOAD_URL="https://github.com/cncf/xds/archive/${VERSION}.tar.gz" DOWNLOAD_BASE_DIR="xds-${VERSION}" SOURCE_PROTO_BASE_DIR="${DOWNLOAD_BASE_DIR}" diff --git a/xds/third_party/xds/src/main/proto/udpa/annotations/migrate.proto b/xds/third_party/xds/src/main/proto/udpa/annotations/migrate.proto index 5289cb8a742..5f5f389b7d2 100644 --- a/xds/third_party/xds/src/main/proto/udpa/annotations/migrate.proto +++ b/xds/third_party/xds/src/main/proto/udpa/annotations/migrate.proto @@ -8,7 +8,7 @@ package udpa.annotations; import "google/protobuf/descriptor.proto"; -option go_package = "github.com/cncf/xds/go/annotations"; +option go_package = "github.com/cncf/xds/go/udpa/annotations"; // Magic number in this file derived from top 28bit of SHA256 digest of // "udpa.annotation.migrate". diff --git a/xds/third_party/xds/src/main/proto/udpa/annotations/security.proto b/xds/third_party/xds/src/main/proto/udpa/annotations/security.proto index 52801d30d1e..0ef919716da 100644 --- a/xds/third_party/xds/src/main/proto/udpa/annotations/security.proto +++ b/xds/third_party/xds/src/main/proto/udpa/annotations/security.proto @@ -10,7 +10,7 @@ import "udpa/annotations/status.proto"; import "google/protobuf/descriptor.proto"; -option go_package = "github.com/cncf/xds/go/annotations"; +option go_package = "github.com/cncf/xds/go/udpa/annotations"; // All annotations in this file are experimental and subject to change. Their // only consumer today is the Envoy APIs and SecuritAnnotationValidator protoc diff --git a/xds/third_party/xds/src/main/proto/udpa/annotations/sensitive.proto b/xds/third_party/xds/src/main/proto/udpa/annotations/sensitive.proto index ab822fb4884..c7d8af608be 100644 --- a/xds/third_party/xds/src/main/proto/udpa/annotations/sensitive.proto +++ b/xds/third_party/xds/src/main/proto/udpa/annotations/sensitive.proto @@ -8,7 +8,7 @@ package udpa.annotations; import "google/protobuf/descriptor.proto"; -option go_package = "github.com/cncf/xds/go/annotations"; +option go_package = "github.com/cncf/xds/go/udpa/annotations"; extend google.protobuf.FieldOptions { // Magic number is the 28 most significant bits in the sha256sum of "udpa.annotations.sensitive". diff --git a/xds/third_party/xds/src/main/proto/udpa/annotations/status.proto b/xds/third_party/xds/src/main/proto/udpa/annotations/status.proto index 76cfd4dcfef..5a90bde29c7 100644 --- a/xds/third_party/xds/src/main/proto/udpa/annotations/status.proto +++ b/xds/third_party/xds/src/main/proto/udpa/annotations/status.proto @@ -8,7 +8,7 @@ package udpa.annotations; import "google/protobuf/descriptor.proto"; -option go_package = "github.com/cncf/xds/go/annotations"; +option go_package = "github.com/cncf/xds/go/udpa/annotations"; // Magic number in this file derived from top 28bit of SHA256 digest of // "udpa.annotation.status". diff --git a/xds/third_party/xds/src/main/proto/udpa/annotations/versioning.proto b/xds/third_party/xds/src/main/proto/udpa/annotations/versioning.proto index dcb7c85fd4f..06df78d818f 100644 --- a/xds/third_party/xds/src/main/proto/udpa/annotations/versioning.proto +++ b/xds/third_party/xds/src/main/proto/udpa/annotations/versioning.proto @@ -8,7 +8,7 @@ package udpa.annotations; import "google/protobuf/descriptor.proto"; -option go_package = "github.com/cncf/xds/go/annotations"; +option go_package = "github.com/cncf/xds/go/udpa/annotations"; extend google.protobuf.MessageOptions { // Magic number derived from 0x78 ('x') 0x44 ('D') 0x53 ('S') diff --git a/xds/third_party/xds/src/main/proto/xds/type/matcher/v3/string.proto b/xds/third_party/xds/src/main/proto/xds/type/matcher/v3/string.proto index fdc598e174a..e58cb413e96 100644 --- a/xds/third_party/xds/src/main/proto/xds/type/matcher/v3/string.proto +++ b/xds/third_party/xds/src/main/proto/xds/type/matcher/v3/string.proto @@ -2,6 +2,7 @@ syntax = "proto3"; package xds.type.matcher.v3; +import "xds/core/v3/extension.proto"; import "xds/type/matcher/v3/regex.proto"; import "validate/validate.proto"; @@ -14,7 +15,7 @@ option go_package = "github.com/cncf/xds/go/xds/type/matcher/v3"; // [#protodoc-title: String matcher] // Specifies the way to match a string. -// [#next-free-field: 8] +// [#next-free-field: 9] message StringMatcher { oneof match_pattern { option (validate.required) = true; @@ -52,6 +53,10 @@ message StringMatcher { // // * *abc* matches the value *xyz.abc.def* string contains = 7 [(validate.rules).string = {min_len: 1}]; + + // Use an extension as the matcher type. + // [#extension-category: envoy.string_matcher] + xds.core.v3.TypedExtensionConfig custom = 8; } // If true, indicates the exact/prefix/suffix matching should be case insensitive. This has no From 0090a526d7eafd493005bf579934a4689979d6e9 Mon Sep 17 00:00:00 2001 From: Kannan J Date: Wed, 31 Jul 2024 11:13:59 +0530 Subject: [PATCH 004/103] Start 1.67.0 development cycle (#11416) --- MODULE.bazel | 2 +- build.gradle | 2 +- .../src/test/golden/TestDeprecatedService.java.txt | 2 +- compiler/src/test/golden/TestService.java.txt | 2 +- core/src/main/java/io/grpc/internal/GrpcUtil.java | 2 +- examples/android/clientcache/app/build.gradle | 10 +++++----- examples/android/helloworld/app/build.gradle | 8 ++++---- examples/android/routeguide/app/build.gradle | 8 ++++---- examples/android/strictmode/app/build.gradle | 8 ++++---- examples/build.gradle | 2 +- examples/example-alts/build.gradle | 2 +- examples/example-debug/build.gradle | 2 +- examples/example-debug/pom.xml | 4 ++-- examples/example-gauth/build.gradle | 2 +- examples/example-gauth/pom.xml | 4 ++-- examples/example-gcp-csm-observability/build.gradle | 2 +- examples/example-gcp-observability/build.gradle | 2 +- examples/example-hostname/build.gradle | 2 +- examples/example-hostname/pom.xml | 4 ++-- examples/example-jwt-auth/build.gradle | 2 +- examples/example-jwt-auth/pom.xml | 4 ++-- examples/example-oauth/build.gradle | 2 +- examples/example-oauth/pom.xml | 4 ++-- examples/example-opentelemetry/build.gradle | 2 +- examples/example-orca/build.gradle | 2 +- examples/example-reflection/build.gradle | 2 +- examples/example-servlet/build.gradle | 2 +- examples/example-tls/build.gradle | 2 +- examples/example-tls/pom.xml | 4 ++-- examples/example-xds/build.gradle | 2 +- examples/pom.xml | 4 ++-- 31 files changed, 51 insertions(+), 51 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index 89ce334d270..2b5d85490f3 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -2,7 +2,7 @@ module( name = "grpc-java", compatibility_level = 0, repo_name = "io_grpc_grpc_java", - version = "1.66.0-SNAPSHOT", # CURRENT_GRPC_VERSION + version = "1.67.0-SNAPSHOT", # CURRENT_GRPC_VERSION ) # GRPC_DEPS_START diff --git a/build.gradle b/build.gradle index 76449ec0107..74cfacb800a 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ subprojects { apply plugin: "net.ltgt.errorprone" group = "io.grpc" - version = "1.66.0-SNAPSHOT" // CURRENT_GRPC_VERSION + version = "1.67.0-SNAPSHOT" // CURRENT_GRPC_VERSION repositories { maven { // The google mirror is less flaky than mavenCentral() diff --git a/compiler/src/test/golden/TestDeprecatedService.java.txt b/compiler/src/test/golden/TestDeprecatedService.java.txt index 5666abe8fda..75e9e0b47e0 100644 --- a/compiler/src/test/golden/TestDeprecatedService.java.txt +++ b/compiler/src/test/golden/TestDeprecatedService.java.txt @@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.66.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.67.0-SNAPSHOT)", comments = "Source: grpc/testing/compiler/test.proto") @io.grpc.stub.annotations.GrpcGenerated @java.lang.Deprecated diff --git a/compiler/src/test/golden/TestService.java.txt b/compiler/src/test/golden/TestService.java.txt index 52e2a772414..3852b6ee547 100644 --- a/compiler/src/test/golden/TestService.java.txt +++ b/compiler/src/test/golden/TestService.java.txt @@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.66.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.67.0-SNAPSHOT)", comments = "Source: grpc/testing/compiler/test.proto") @io.grpc.stub.annotations.GrpcGenerated public final class TestServiceGrpc { diff --git a/core/src/main/java/io/grpc/internal/GrpcUtil.java b/core/src/main/java/io/grpc/internal/GrpcUtil.java index fa488f30ef8..593bdbce13f 100644 --- a/core/src/main/java/io/grpc/internal/GrpcUtil.java +++ b/core/src/main/java/io/grpc/internal/GrpcUtil.java @@ -219,7 +219,7 @@ public byte[] parseAsciiString(byte[] serialized) { public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults(); - public static final String IMPLEMENTATION_VERSION = "1.66.0-SNAPSHOT"; // CURRENT_GRPC_VERSION + public static final String IMPLEMENTATION_VERSION = "1.67.0-SNAPSHOT"; // CURRENT_GRPC_VERSION /** * The default timeout in nanos for a keepalive ping request. diff --git a/examples/android/clientcache/app/build.gradle b/examples/android/clientcache/app/build.gradle index 64e95de4738..0ca032fb0e4 100644 --- a/examples/android/clientcache/app/build.gradle +++ b/examples/android/clientcache/app/build.gradle @@ -34,7 +34,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.25.1' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -54,12 +54,12 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.0.0' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' testImplementation 'junit:junit:4.13.2' testImplementation 'com.google.truth:truth:1.1.5' - testImplementation 'io.grpc:grpc-testing:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION + testImplementation 'io.grpc:grpc-testing:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION } diff --git a/examples/android/helloworld/app/build.gradle b/examples/android/helloworld/app/build.gradle index f9433f14010..0f1e8b4047b 100644 --- a/examples/android/helloworld/app/build.gradle +++ b/examples/android/helloworld/app/build.gradle @@ -32,7 +32,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.25.1' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -52,8 +52,8 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.0.0' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/android/routeguide/app/build.gradle b/examples/android/routeguide/app/build.gradle index 2431b473f29..c33135233ea 100644 --- a/examples/android/routeguide/app/build.gradle +++ b/examples/android/routeguide/app/build.gradle @@ -32,7 +32,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.25.1' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -52,8 +52,8 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.0.0' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/android/strictmode/app/build.gradle b/examples/android/strictmode/app/build.gradle index 699c8dd9d68..e8e2e8cac29 100644 --- a/examples/android/strictmode/app/build.gradle +++ b/examples/android/strictmode/app/build.gradle @@ -33,7 +33,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.25.1' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -53,8 +53,8 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.0.0' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/build.gradle b/examples/build.gradle index c9213cc6a21..076e0c4a25b 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -23,7 +23,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.25.3' def protocVersion = protobufVersion diff --git a/examples/example-alts/build.gradle b/examples/example-alts/build.gradle index 06b7ac501d0..3c998586bb6 100644 --- a/examples/example-alts/build.gradle +++ b/examples/example-alts/build.gradle @@ -24,7 +24,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/example-debug/build.gradle b/examples/example-debug/build.gradle index 624483f663e..ca151a13c1a 100644 --- a/examples/example-debug/build.gradle +++ b/examples/example-debug/build.gradle @@ -25,7 +25,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.25.3' dependencies { diff --git a/examples/example-debug/pom.xml b/examples/example-debug/pom.xml index 5aa8065ad31..10ccf834d86 100644 --- a/examples/example-debug/pom.xml +++ b/examples/example-debug/pom.xml @@ -6,13 +6,13 @@ jar - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT example-debug https://github.com/grpc/grpc-java UTF-8 - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT 3.25.3 1.8 diff --git a/examples/example-gauth/build.gradle b/examples/example-gauth/build.gradle index c43443c3860..40e72afad82 100644 --- a/examples/example-gauth/build.gradle +++ b/examples/example-gauth/build.gradle @@ -24,7 +24,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.25.3' def protocVersion = protobufVersion diff --git a/examples/example-gauth/pom.xml b/examples/example-gauth/pom.xml index d91eeb15ded..1e58e21e975 100644 --- a/examples/example-gauth/pom.xml +++ b/examples/example-gauth/pom.xml @@ -6,13 +6,13 @@ jar - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT example-gauth https://github.com/grpc/grpc-java UTF-8 - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT 3.25.3 1.8 diff --git a/examples/example-gcp-csm-observability/build.gradle b/examples/example-gcp-csm-observability/build.gradle index a24490918b5..5de2b1995e2 100644 --- a/examples/example-gcp-csm-observability/build.gradle +++ b/examples/example-gcp-csm-observability/build.gradle @@ -25,7 +25,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' def openTelemetryVersion = '1.40.0' def openTelemetryPrometheusVersion = '1.40.0-alpha' diff --git a/examples/example-gcp-observability/build.gradle b/examples/example-gcp-observability/build.gradle index d6dd1aedc6e..0462c987f52 100644 --- a/examples/example-gcp-observability/build.gradle +++ b/examples/example-gcp-observability/build.gradle @@ -25,7 +25,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/example-hostname/build.gradle b/examples/example-hostname/build.gradle index ee5e5cf5c70..ab45ee2dc5b 100644 --- a/examples/example-hostname/build.gradle +++ b/examples/example-hostname/build.gradle @@ -23,7 +23,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.25.3' dependencies { diff --git a/examples/example-hostname/pom.xml b/examples/example-hostname/pom.xml index 05131b89978..19b5f8b3c20 100644 --- a/examples/example-hostname/pom.xml +++ b/examples/example-hostname/pom.xml @@ -6,13 +6,13 @@ jar - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT example-hostname https://github.com/grpc/grpc-java UTF-8 - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT 3.25.3 1.8 diff --git a/examples/example-jwt-auth/build.gradle b/examples/example-jwt-auth/build.gradle index 2ad3c91f190..6fdd4498c7d 100644 --- a/examples/example-jwt-auth/build.gradle +++ b/examples/example-jwt-auth/build.gradle @@ -23,7 +23,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.25.3' def protocVersion = protobufVersion diff --git a/examples/example-jwt-auth/pom.xml b/examples/example-jwt-auth/pom.xml index 01cf0edce28..ad530e33aa7 100644 --- a/examples/example-jwt-auth/pom.xml +++ b/examples/example-jwt-auth/pom.xml @@ -7,13 +7,13 @@ jar - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT example-jwt-auth https://github.com/grpc/grpc-java UTF-8 - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT 3.25.3 3.25.3 diff --git a/examples/example-oauth/build.gradle b/examples/example-oauth/build.gradle index 23a6633e264..255633b4f9f 100644 --- a/examples/example-oauth/build.gradle +++ b/examples/example-oauth/build.gradle @@ -23,7 +23,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.25.3' def protocVersion = protobufVersion diff --git a/examples/example-oauth/pom.xml b/examples/example-oauth/pom.xml index afd45aecd39..2c38a05b3e4 100644 --- a/examples/example-oauth/pom.xml +++ b/examples/example-oauth/pom.xml @@ -7,13 +7,13 @@ jar - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT example-oauth https://github.com/grpc/grpc-java UTF-8 - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT 3.25.3 3.25.3 diff --git a/examples/example-opentelemetry/build.gradle b/examples/example-opentelemetry/build.gradle index 55d6685d771..00f7dc101bf 100644 --- a/examples/example-opentelemetry/build.gradle +++ b/examples/example-opentelemetry/build.gradle @@ -24,7 +24,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' def openTelemetryVersion = '1.40.0' def openTelemetryPrometheusVersion = '1.40.0-alpha' diff --git a/examples/example-orca/build.gradle b/examples/example-orca/build.gradle index f3eae10ace4..22feb8cae42 100644 --- a/examples/example-orca/build.gradle +++ b/examples/example-orca/build.gradle @@ -18,7 +18,7 @@ java { targetCompatibility = JavaVersion.VERSION_1_8 } -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/example-reflection/build.gradle b/examples/example-reflection/build.gradle index 0b5c99898ed..78821391911 100644 --- a/examples/example-reflection/build.gradle +++ b/examples/example-reflection/build.gradle @@ -18,7 +18,7 @@ java { targetCompatibility = JavaVersion.VERSION_1_8 } -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/example-servlet/build.gradle b/examples/example-servlet/build.gradle index b73d21fbc4c..9542ba0277f 100644 --- a/examples/example-servlet/build.gradle +++ b/examples/example-servlet/build.gradle @@ -16,7 +16,7 @@ java { targetCompatibility = JavaVersion.VERSION_1_8 } -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index 3791cc03271..94257af4758 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -24,7 +24,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/example-tls/pom.xml b/examples/example-tls/pom.xml index 1263b347030..bc9c0a7a8ee 100644 --- a/examples/example-tls/pom.xml +++ b/examples/example-tls/pom.xml @@ -6,13 +6,13 @@ jar - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT example-tls https://github.com/grpc/grpc-java UTF-8 - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT 3.25.3 1.8 diff --git a/examples/example-xds/build.gradle b/examples/example-xds/build.gradle index 9807b1f8b74..2554adb0033 100644 --- a/examples/example-xds/build.gradle +++ b/examples/example-xds/build.gradle @@ -23,7 +23,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/pom.xml b/examples/pom.xml index a71e9d449c3..2b25d13b50c 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -6,13 +6,13 @@ jar - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT examples https://github.com/grpc/grpc-java UTF-8 - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT 3.25.3 3.25.3 From dc83446d982b53f3f64ad885db23571729cd7c9b Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 25 Jul 2024 13:09:13 -0700 Subject: [PATCH 005/103] xds: Stop extending RR in WRR They share very little code, and we really don't want RoundRobinLb to be public and non-final. Originally, WRR was expected to share much more code with RR, and even delegated to RR at times. The delegation was removed in 111ff60e. After dca89b25, most of the sharing has been moved out into general-purpose tools that can be used by any LB policy. FixedResultPicker now has equals to makes it as a EmptyPicker replacement. RoundRobinLb still uses EmptyPicker because fixing its tests is a larger change. OutlierDetectionLbTest was changed because FixedResultPicker is used by PickFirstLeafLb, and now RoundRobinLb can squelch some of its updates for ready pickers. --- api/src/main/java/io/grpc/LoadBalancer.java | 14 ++++++ .../io/grpc/util/RoundRobinLoadBalancer.java | 8 ++-- .../OutlierDetectionLoadBalancerTest.java | 4 +- .../xds/WeightedRoundRobinLoadBalancer.java | 45 +++++++++++++++++-- .../WeightedRoundRobinLoadBalancerTest.java | 4 +- 5 files changed, 63 insertions(+), 12 deletions(-) diff --git a/api/src/main/java/io/grpc/LoadBalancer.java b/api/src/main/java/io/grpc/LoadBalancer.java index 80e3f8b89c7..15106a5ffc6 100644 --- a/api/src/main/java/io/grpc/LoadBalancer.java +++ b/api/src/main/java/io/grpc/LoadBalancer.java @@ -1526,5 +1526,19 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { public String toString() { return "FixedResultPicker(" + result + ")"; } + + @Override + public int hashCode() { + return result.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof FixedResultPicker)) { + return false; + } + FixedResultPicker that = (FixedResultPicker) o; + return this.result.equals(that.result); + } } } diff --git a/util/src/main/java/io/grpc/util/RoundRobinLoadBalancer.java b/util/src/main/java/io/grpc/util/RoundRobinLoadBalancer.java index a06bae545df..7c235bb3640 100644 --- a/util/src/main/java/io/grpc/util/RoundRobinLoadBalancer.java +++ b/util/src/main/java/io/grpc/util/RoundRobinLoadBalancer.java @@ -27,7 +27,6 @@ import com.google.common.base.Preconditions; import io.grpc.ConnectivityState; import io.grpc.EquivalentAddressGroup; -import io.grpc.Internal; import io.grpc.LoadBalancer; import io.grpc.NameResolver; import java.util.ArrayList; @@ -41,10 +40,9 @@ * A {@link LoadBalancer} that provides round-robin load-balancing over the {@link * EquivalentAddressGroup}s from the {@link NameResolver}. */ -@Internal -public class RoundRobinLoadBalancer extends MultiChildLoadBalancer { +final class RoundRobinLoadBalancer extends MultiChildLoadBalancer { private final AtomicInteger sequence = new AtomicInteger(new Random().nextInt()); - protected SubchannelPicker currentPicker = new EmptyPicker(); + private SubchannelPicker currentPicker = new EmptyPicker(); public RoundRobinLoadBalancer(Helper helper) { super(helper); @@ -87,7 +85,7 @@ private void updateBalancingState(ConnectivityState state, SubchannelPicker pick } } - protected SubchannelPicker createReadyPicker(Collection children) { + private SubchannelPicker createReadyPicker(Collection children) { List pickerList = new ArrayList<>(); for (ChildLbState child : children) { SubchannelPicker picker = child.getCurrentPicker(); diff --git a/util/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java b/util/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java index 8af935d8134..1b0139affef 100644 --- a/util/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java +++ b/util/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java @@ -569,7 +569,7 @@ public void successRateOneOutlier_configChange() { loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers)); // The PickFirstLeafLB has an extra level of indirection because of health - int expectedStateChanges = PickFirstLoadBalancerProvider.isEnabledNewPickFirst() ? 16 : 12; + int expectedStateChanges = PickFirstLoadBalancerProvider.isEnabledNewPickFirst() ? 8 : 12; generateLoad(ImmutableMap.of(subchannel2, Status.DEADLINE_EXCEEDED), expectedStateChanges); // Move forward in time to a point where the detection timer has fired. @@ -604,7 +604,7 @@ public void successRateOneOutlier_unejected() { assertEjectedSubchannels(ImmutableSet.of(ImmutableSet.copyOf(servers.get(0).getAddresses()))); // Now we produce more load, but the subchannel has started working and is no longer an outlier. - int expectedStateChanges = PickFirstLoadBalancerProvider.isEnabledNewPickFirst() ? 16 : 12; + int expectedStateChanges = PickFirstLoadBalancerProvider.isEnabledNewPickFirst() ? 8 : 12; generateLoad(ImmutableMap.of(), expectedStateChanges); // Move forward in time to a point where the detection timer has fired. diff --git a/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java b/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java index c3383148079..115857d43ff 100644 --- a/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java @@ -42,7 +42,7 @@ import io.grpc.services.MetricReport; import io.grpc.util.ForwardingLoadBalancerHelper; import io.grpc.util.ForwardingSubchannel; -import io.grpc.util.RoundRobinLoadBalancer; +import io.grpc.util.MultiChildLoadBalancer; import io.grpc.xds.orca.OrcaOobUtil; import io.grpc.xds.orca.OrcaOobUtil.OrcaOobReportListener; import io.grpc.xds.orca.OrcaPerRequestUtil; @@ -90,7 +90,7 @@ * See related documentation: https://cloud.google.com/service-mesh/legacy/load-balancing-apis/proxyless-configure-advanced-traffic-management#custom-lb-config */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/9885") -final class WeightedRoundRobinLoadBalancer extends RoundRobinLoadBalancer { +final class WeightedRoundRobinLoadBalancer extends MultiChildLoadBalancer { private static final LongCounterMetricInstrument RR_FALLBACK_COUNTER; private static final LongCounterMetricInstrument ENDPOINT_WEIGHT_NOT_YET_USEABLE_COUNTER; @@ -107,6 +107,7 @@ final class WeightedRoundRobinLoadBalancer extends RoundRobinLoadBalancer { private final long infTime; private final Ticker ticker; private String locality = ""; + private SubchannelPicker currentPicker = new FixedResultPicker(PickResult.withNoResult()); // The metric instruments are only registered once and shared by all instances of this LB. static { @@ -209,13 +210,51 @@ public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { return acceptRetVal.status; } + /** + * Updates picker with the list of active subchannels (state == READY). + */ @Override - public SubchannelPicker createReadyPicker(Collection activeList) { + protected void updateOverallBalancingState() { + List activeList = getReadyChildren(); + if (activeList.isEmpty()) { + // No READY subchannels + + // MultiChildLB will request connection immediately on subchannel IDLE. + boolean isConnecting = false; + for (ChildLbState childLbState : getChildLbStates()) { + ConnectivityState state = childLbState.getCurrentState(); + if (state == ConnectivityState.CONNECTING || state == ConnectivityState.IDLE) { + isConnecting = true; + break; + } + } + + if (isConnecting) { + updateBalancingState( + ConnectivityState.CONNECTING, new FixedResultPicker(PickResult.withNoResult())); + } else { + updateBalancingState( + ConnectivityState.TRANSIENT_FAILURE, createReadyPicker(getChildLbStates())); + } + } else { + updateBalancingState(ConnectivityState.READY, createReadyPicker(activeList)); + } + } + + private SubchannelPicker createReadyPicker(Collection activeList) { return new WeightedRoundRobinPicker(ImmutableList.copyOf(activeList), config.enableOobLoadReport, config.errorUtilizationPenalty, sequence, getHelper(), locality); } + private void updateBalancingState(ConnectivityState state, SubchannelPicker picker) { + if (state != currentConnectivityState || !picker.equals(currentPicker)) { + getHelper().updateBalancingState(state, picker); + currentConnectivityState = state; + currentPicker = picker; + } + } + @VisibleForTesting final class WeightedChildLbState extends ChildLbState { diff --git a/xds/src/test/java/io/grpc/xds/WeightedRoundRobinLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/WeightedRoundRobinLoadBalancerTest.java index a5b5651133b..dd98f1e1ae6 100644 --- a/xds/src/test/java/io/grpc/xds/WeightedRoundRobinLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/WeightedRoundRobinLoadBalancerTest.java @@ -536,8 +536,8 @@ public void emptyConfig() { verify(helper, times(3)).createSubchannel( any(CreateSubchannelArgs.class)); verify(helper).updateBalancingState(eq(CONNECTING), pickerCaptor.capture()); - assertThat(pickerCaptor.getValue().getClass().getName()) - .isEqualTo("io.grpc.util.RoundRobinLoadBalancer$EmptyPicker"); + assertThat(pickerCaptor.getValue().pickSubchannel(mockArgs)) + .isEqualTo(PickResult.withNoResult()); int expectedCount = isEnabledHappyEyeballs() ? servers.size() + 1 : 1; assertThat(fakeClock.forwardTime(11, TimeUnit.SECONDS)).isEqualTo( expectedCount); } From ebffb0a6b2b628f584ce97f943b2739642f58270 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Wed, 31 Jul 2024 13:22:04 -0700 Subject: [PATCH 006/103] Revert "Introduce onResult2 in NameResolver Listener2 that returns Status (#11313)" This reverts commit 9ba2f9dec5c71a5d0afbba0f196331a47844bc07. It causes a channel panic due to unimplemented onResult2(). ``` java.lang.UnsupportedOperationException: Not implemented. at io.grpc.NameResolver$Listener2.onResult2(NameResolver.java:257) at io.grpc.internal.DnsNameResolver$Resolve.lambda$run$0(DnsNameResolver.java:334) at io.grpc.SynchronizationContext.drain(SynchronizationContext.java:94) at io.grpc.SynchronizationContext.execute(SynchronizationContext.java:126) at io.grpc.internal.DnsNameResolver$Resolve.run(DnsNameResolver.java:333) ``` b/356669977 --- api/src/main/java/io/grpc/NameResolver.java | 10 - .../io/grpc/internal/DnsNameResolver.java | 4 +- .../io/grpc/internal/ManagedChannelImpl.java | 255 ++++++++------- .../grpc/internal/RetryingNameResolver.java | 12 - .../io/grpc/internal/DnsNameResolverTest.java | 50 +-- .../grpc/internal/ManagedChannelImplTest.java | 300 +----------------- .../internal/RetryingNameResolverTest.java | 28 +- .../grpc/grpclb/GrpclbNameResolverTest.java | 10 +- 8 files changed, 168 insertions(+), 501 deletions(-) diff --git a/api/src/main/java/io/grpc/NameResolver.java b/api/src/main/java/io/grpc/NameResolver.java index 8af8112ffdb..a74512eb7e3 100644 --- a/api/src/main/java/io/grpc/NameResolver.java +++ b/api/src/main/java/io/grpc/NameResolver.java @@ -246,16 +246,6 @@ public final void onAddresses( */ @Override public abstract void onError(Status error); - - /** - * Handles updates on resolved addresses and attributes. - * - * @param resolutionResult the resolved server addresses, attributes, and Service Config. - * @since 1.66 - */ - public Status onResult2(ResolutionResult resolutionResult) { - throw new UnsupportedOperationException("Not implemented."); - } } /** diff --git a/core/src/main/java/io/grpc/internal/DnsNameResolver.java b/core/src/main/java/io/grpc/internal/DnsNameResolver.java index df51d6f2c5c..5ef6dd863c2 100644 --- a/core/src/main/java/io/grpc/internal/DnsNameResolver.java +++ b/core/src/main/java/io/grpc/internal/DnsNameResolver.java @@ -330,9 +330,7 @@ public void run() { resolutionResultBuilder.setAttributes(result.attributes); } } - syncContext.execute(() -> { - savedListener.onResult2(resolutionResultBuilder.build()); - }); + savedListener.onResult(resolutionResultBuilder.build()); } catch (IOException e) { savedListener.onError( Status.UNAVAILABLE.withDescription("Unable to resolve host " + host).withCause(e)); diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java index 7f45ca967ea..c5c7b66e15d 100644 --- a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java +++ b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java @@ -1673,147 +1673,146 @@ final class NameResolverListener extends NameResolver.Listener2 { public void onResult(final ResolutionResult resolutionResult) { final class NamesResolved implements Runnable { + @SuppressWarnings("ReferenceEquality") @Override public void run() { - Status status = onResult2(resolutionResult); - ResolutionResultListener resolutionResultListener = resolutionResult.getAttributes() - .get(RetryingNameResolver.RESOLUTION_RESULT_LISTENER_KEY); - resolutionResultListener.resolutionAttempted(status); - } - } - - syncContext.execute(new NamesResolved()); - } + if (ManagedChannelImpl.this.nameResolver != resolver) { + return; + } - @SuppressWarnings("ReferenceEquality") - @Override - public Status onResult2(final ResolutionResult resolutionResult) { - syncContext.throwIfNotInThisSynchronizationContext(); - if (ManagedChannelImpl.this.nameResolver != resolver) { - return Status.OK; - } - - List servers = resolutionResult.getAddresses(); - channelLogger.log( - ChannelLogLevel.DEBUG, - "Resolved address: {0}, config={1}", - servers, - resolutionResult.getAttributes()); - - if (lastResolutionState != ResolutionState.SUCCESS) { - channelLogger.log(ChannelLogLevel.INFO, "Address resolved: {0}", servers); - lastResolutionState = ResolutionState.SUCCESS; - } - - ConfigOrError configOrError = resolutionResult.getServiceConfig(); - InternalConfigSelector resolvedConfigSelector = - resolutionResult.getAttributes().get(InternalConfigSelector.KEY); - ManagedChannelServiceConfig validServiceConfig = - configOrError != null && configOrError.getConfig() != null - ? (ManagedChannelServiceConfig) configOrError.getConfig() - : null; - Status serviceConfigError = configOrError != null ? configOrError.getError() : null; - - ManagedChannelServiceConfig effectiveServiceConfig; - if (!lookUpServiceConfig) { - if (validServiceConfig != null) { - channelLogger.log( - ChannelLogLevel.INFO, - "Service config from name resolver discarded by channel settings"); - } - effectiveServiceConfig = - defaultServiceConfig == null ? EMPTY_SERVICE_CONFIG : defaultServiceConfig; - if (resolvedConfigSelector != null) { + List servers = resolutionResult.getAddresses(); channelLogger.log( - ChannelLogLevel.INFO, - "Config selector from name resolver discarded by channel settings"); - } - realChannel.updateConfigSelector(effectiveServiceConfig.getDefaultConfigSelector()); - } else { - // Try to use config if returned from name resolver - // Otherwise, try to use the default config if available - if (validServiceConfig != null) { - effectiveServiceConfig = validServiceConfig; - if (resolvedConfigSelector != null) { - realChannel.updateConfigSelector(resolvedConfigSelector); - if (effectiveServiceConfig.getDefaultConfigSelector() != null) { + ChannelLogLevel.DEBUG, + "Resolved address: {0}, config={1}", + servers, + resolutionResult.getAttributes()); + + if (lastResolutionState != ResolutionState.SUCCESS) { + channelLogger.log(ChannelLogLevel.INFO, "Address resolved: {0}", servers); + lastResolutionState = ResolutionState.SUCCESS; + } + + ConfigOrError configOrError = resolutionResult.getServiceConfig(); + ResolutionResultListener resolutionResultListener = resolutionResult.getAttributes() + .get(RetryingNameResolver.RESOLUTION_RESULT_LISTENER_KEY); + InternalConfigSelector resolvedConfigSelector = + resolutionResult.getAttributes().get(InternalConfigSelector.KEY); + ManagedChannelServiceConfig validServiceConfig = + configOrError != null && configOrError.getConfig() != null + ? (ManagedChannelServiceConfig) configOrError.getConfig() + : null; + Status serviceConfigError = configOrError != null ? configOrError.getError() : null; + + ManagedChannelServiceConfig effectiveServiceConfig; + if (!lookUpServiceConfig) { + if (validServiceConfig != null) { channelLogger.log( - ChannelLogLevel.DEBUG, - "Method configs in service config will be discarded due to presence of" - + "config-selector"); + ChannelLogLevel.INFO, + "Service config from name resolver discarded by channel settings"); + } + effectiveServiceConfig = + defaultServiceConfig == null ? EMPTY_SERVICE_CONFIG : defaultServiceConfig; + if (resolvedConfigSelector != null) { + channelLogger.log( + ChannelLogLevel.INFO, + "Config selector from name resolver discarded by channel settings"); } - } else { realChannel.updateConfigSelector(effectiveServiceConfig.getDefaultConfigSelector()); - } - } else if (defaultServiceConfig != null) { - effectiveServiceConfig = defaultServiceConfig; - realChannel.updateConfigSelector(effectiveServiceConfig.getDefaultConfigSelector()); - channelLogger.log( - ChannelLogLevel.INFO, - "Received no service config, using default service config"); - } else if (serviceConfigError != null) { - if (!serviceConfigUpdated) { - // First DNS lookup has invalid service config, and cannot fall back to default - channelLogger.log( - ChannelLogLevel.INFO, - "Fallback to error due to invalid first service config without default config"); - // This error could be an "inappropriate" control plane error that should not bleed - // through to client code using gRPC. We let them flow through here to the LB as - // we later check for these error codes when investigating pick results in - // GrpcUtil.getTransportFromPickResult(). - onError(configOrError.getError()); - return configOrError.getError(); } else { - effectiveServiceConfig = lastServiceConfig; + // Try to use config if returned from name resolver + // Otherwise, try to use the default config if available + if (validServiceConfig != null) { + effectiveServiceConfig = validServiceConfig; + if (resolvedConfigSelector != null) { + realChannel.updateConfigSelector(resolvedConfigSelector); + if (effectiveServiceConfig.getDefaultConfigSelector() != null) { + channelLogger.log( + ChannelLogLevel.DEBUG, + "Method configs in service config will be discarded due to presence of" + + "config-selector"); + } + } else { + realChannel.updateConfigSelector(effectiveServiceConfig.getDefaultConfigSelector()); + } + } else if (defaultServiceConfig != null) { + effectiveServiceConfig = defaultServiceConfig; + realChannel.updateConfigSelector(effectiveServiceConfig.getDefaultConfigSelector()); + channelLogger.log( + ChannelLogLevel.INFO, + "Received no service config, using default service config"); + } else if (serviceConfigError != null) { + if (!serviceConfigUpdated) { + // First DNS lookup has invalid service config, and cannot fall back to default + channelLogger.log( + ChannelLogLevel.INFO, + "Fallback to error due to invalid first service config without default config"); + // This error could be an "inappropriate" control plane error that should not bleed + // through to client code using gRPC. We let them flow through here to the LB as + // we later check for these error codes when investigating pick results in + // GrpcUtil.getTransportFromPickResult(). + onError(configOrError.getError()); + if (resolutionResultListener != null) { + resolutionResultListener.resolutionAttempted(configOrError.getError()); + } + return; + } else { + effectiveServiceConfig = lastServiceConfig; + } + } else { + effectiveServiceConfig = EMPTY_SERVICE_CONFIG; + realChannel.updateConfigSelector(null); + } + if (!effectiveServiceConfig.equals(lastServiceConfig)) { + channelLogger.log( + ChannelLogLevel.INFO, + "Service config changed{0}", + effectiveServiceConfig == EMPTY_SERVICE_CONFIG ? " to empty" : ""); + lastServiceConfig = effectiveServiceConfig; + transportProvider.throttle = effectiveServiceConfig.getRetryThrottling(); + } + + try { + // TODO(creamsoup): when `servers` is empty and lastResolutionStateCopy == SUCCESS + // and lbNeedAddress, it shouldn't call the handleServiceConfigUpdate. But, + // lbNeedAddress is not deterministic + serviceConfigUpdated = true; + } catch (RuntimeException re) { + logger.log( + Level.WARNING, + "[" + getLogId() + "] Unexpected exception from parsing service config", + re); + } } - } else { - effectiveServiceConfig = EMPTY_SERVICE_CONFIG; - realChannel.updateConfigSelector(null); - } - if (!effectiveServiceConfig.equals(lastServiceConfig)) { - channelLogger.log( - ChannelLogLevel.INFO, - "Service config changed{0}", - effectiveServiceConfig == EMPTY_SERVICE_CONFIG ? " to empty" : ""); - lastServiceConfig = effectiveServiceConfig; - transportProvider.throttle = effectiveServiceConfig.getRetryThrottling(); - } - try { - // TODO(creamsoup): when `servers` is empty and lastResolutionStateCopy == SUCCESS - // and lbNeedAddress, it shouldn't call the handleServiceConfigUpdate. But, - // lbNeedAddress is not deterministic - serviceConfigUpdated = true; - } catch (RuntimeException re) { - logger.log( - Level.WARNING, - "[" + getLogId() + "] Unexpected exception from parsing service config", - re); + Attributes effectiveAttrs = resolutionResult.getAttributes(); + // Call LB only if it's not shutdown. If LB is shutdown, lbHelper won't match. + if (NameResolverListener.this.helper == ManagedChannelImpl.this.lbHelper) { + Attributes.Builder attrBuilder = + effectiveAttrs.toBuilder().discard(InternalConfigSelector.KEY); + Map healthCheckingConfig = + effectiveServiceConfig.getHealthCheckingConfig(); + if (healthCheckingConfig != null) { + attrBuilder + .set(LoadBalancer.ATTR_HEALTH_CHECKING_CONFIG, healthCheckingConfig) + .build(); + } + Attributes attributes = attrBuilder.build(); + + Status addressAcceptanceStatus = helper.lb.tryAcceptResolvedAddresses( + ResolvedAddresses.newBuilder() + .setAddresses(servers) + .setAttributes(attributes) + .setLoadBalancingPolicyConfig(effectiveServiceConfig.getLoadBalancingConfig()) + .build()); + // If a listener is provided, let it know if the addresses were accepted. + if (resolutionResultListener != null) { + resolutionResultListener.resolutionAttempted(addressAcceptanceStatus); + } + } } } - Attributes effectiveAttrs = resolutionResult.getAttributes(); - // Call LB only if it's not shutdown. If LB is shutdown, lbHelper won't match. - if (NameResolverListener.this.helper == ManagedChannelImpl.this.lbHelper) { - Attributes.Builder attrBuilder = - effectiveAttrs.toBuilder().discard(InternalConfigSelector.KEY); - Map healthCheckingConfig = - effectiveServiceConfig.getHealthCheckingConfig(); - if (healthCheckingConfig != null) { - attrBuilder - .set(LoadBalancer.ATTR_HEALTH_CHECKING_CONFIG, healthCheckingConfig) - .build(); - } - Attributes attributes = attrBuilder.build(); - - return helper.lb.tryAcceptResolvedAddresses( - ResolvedAddresses.newBuilder() - .setAddresses(servers) - .setAttributes(attributes) - .setLoadBalancingPolicyConfig(effectiveServiceConfig.getLoadBalancingConfig()) - .build()); - } - return Status.OK; + syncContext.execute(new NamesResolved()); } @Override diff --git a/core/src/main/java/io/grpc/internal/RetryingNameResolver.java b/core/src/main/java/io/grpc/internal/RetryingNameResolver.java index 6dcfcd3534a..6d806e95944 100644 --- a/core/src/main/java/io/grpc/internal/RetryingNameResolver.java +++ b/core/src/main/java/io/grpc/internal/RetryingNameResolver.java @@ -95,24 +95,12 @@ public void onResult(ResolutionResult resolutionResult) { "RetryingNameResolver can only be used once to wrap a NameResolver"); } - // To have retry behavior for name resolvers that haven't migrated to onResult2. delegateListener.onResult(resolutionResult.toBuilder().setAttributes( resolutionResult.getAttributes().toBuilder() .set(RESOLUTION_RESULT_LISTENER_KEY, new ResolutionResultListener()).build()) .build()); } - @Override - public Status onResult2(ResolutionResult resolutionResult) { - Status status = delegateListener.onResult2(resolutionResult); - if (status.isOk()) { - retryScheduler.reset(); - } else { - retryScheduler.schedule(new DelayedNameResolverRefresh()); - } - return status; - } - @Override public void onError(Status error) { delegateListener.onError(error); diff --git a/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java b/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java index 0512171f4e7..14d3fddd290 100644 --- a/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java +++ b/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java @@ -26,6 +26,7 @@ import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -225,7 +226,13 @@ public void setUp() { System.getProperty(DnsNameResolver.NETWORKADDRESS_CACHE_TTL_PROPERTY); // By default the mock listener processes the result successfully. - when(mockListener.onResult2(isA(ResolutionResult.class))).thenReturn(Status.OK); + doAnswer(invocation -> { + ResolutionResult result = invocation.getArgument(0); + syncContext.execute( + () -> result.getAttributes().get(RetryingNameResolver.RESOLUTION_RESULT_LISTENER_KEY) + .resolutionAttempted(Status.OK)); + return null; + }).when(mockListener).onResult(isA(ResolutionResult.class)); } @After @@ -312,13 +319,13 @@ private void resolveNeverCache(boolean isAndroid) throws Exception { resolver.start(mockListener); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener).onResult2(resultCaptor.capture()); + verify(mockListener).onResult(resultCaptor.capture()); assertAnswerMatches(answer1, 81, resultCaptor.getValue()); assertEquals(0, fakeClock.numPendingTasks()); resolver.refresh(); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener, times(2)).onResult2(resultCaptor.capture()); + verify(mockListener, times(2)).onResult(resultCaptor.capture()); assertAnswerMatches(answer2, 81, resultCaptor.getValue()); assertEquals(0, fakeClock.numPendingTasks()); assertEquals(0, fakeExecutor.numPendingTasks()); @@ -340,7 +347,7 @@ public void testExecutor_default() throws Exception { resolver.start(mockListener); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener).onResult2(resultCaptor.capture()); + verify(mockListener).onResult(resultCaptor.capture()); assertAnswerMatches(answer, 81, resultCaptor.getValue()); assertEquals(0, fakeClock.numPendingTasks()); assertEquals(0, fakeExecutor.numPendingTasks()); @@ -382,7 +389,7 @@ public void execute(Runnable command) { resolver.start(mockListener); assertEquals(0, fakeExecutor.runDueTasks()); - verify(mockListener).onResult2(resultCaptor.capture()); + verify(mockListener).onResult(resultCaptor.capture()); assertAnswerMatches(answer, 81, resultCaptor.getValue()); assertEquals(0, fakeClock.numPendingTasks()); assertEquals(0, fakeExecutor.numPendingTasks()); @@ -411,7 +418,7 @@ public void resolve_cacheForever() throws Exception { resolver.start(mockListener); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener).onResult2(resultCaptor.capture()); + verify(mockListener).onResult(resultCaptor.capture()); assertAnswerMatches(answer1, 81, resultCaptor.getValue()); assertEquals(0, fakeClock.numPendingTasks()); @@ -445,7 +452,7 @@ public void resolve_usingCache() throws Exception { resolver.start(mockListener); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener).onResult2(resultCaptor.capture()); + verify(mockListener).onResult(resultCaptor.capture()); assertAnswerMatches(answer, 81, resultCaptor.getValue()); assertEquals(0, fakeClock.numPendingTasks()); @@ -480,14 +487,14 @@ public void resolve_cacheExpired() throws Exception { resolver.start(mockListener); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener).onResult2(resultCaptor.capture()); + verify(mockListener).onResult(resultCaptor.capture()); assertAnswerMatches(answer1, 81, resultCaptor.getValue()); assertEquals(0, fakeClock.numPendingTasks()); fakeTicker.advance(ttl + 1, TimeUnit.SECONDS); resolver.refresh(); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener, times(2)).onResult2(resultCaptor.capture()); + verify(mockListener, times(2)).onResult(resultCaptor.capture()); assertAnswerMatches(answer2, 81, resultCaptor.getValue()); assertEquals(0, fakeClock.numPendingTasks()); assertEquals(0, fakeExecutor.numPendingTasks()); @@ -524,7 +531,7 @@ private void resolveDefaultValue() throws Exception { resolver.start(mockListener); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener).onResult2(resultCaptor.capture()); + verify(mockListener).onResult(resultCaptor.capture()); assertAnswerMatches(answer1, 81, resultCaptor.getValue()); assertEquals(0, fakeClock.numPendingTasks()); @@ -537,7 +544,7 @@ private void resolveDefaultValue() throws Exception { fakeTicker.advance(1, TimeUnit.SECONDS); resolver.refresh(); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener, times(2)).onResult2(resultCaptor.capture()); + verify(mockListener, times(2)).onResult(resultCaptor.capture()); assertAnswerMatches(answer2, 81, resultCaptor.getValue()); assertEquals(0, fakeClock.numPendingTasks()); assertEquals(0, fakeExecutor.numPendingTasks()); @@ -568,7 +575,7 @@ public List resolveAddress(String host) throws Exception { assertThat(fakeExecutor.runDueTasks()).isEqualTo(1); ArgumentCaptor ac = ArgumentCaptor.forClass(ResolutionResult.class); - verify(mockListener).onResult2(ac.capture()); + verify(mockListener).onResult(ac.capture()); verifyNoMoreInteractions(mockListener); assertThat(ac.getValue().getAddresses()).isEmpty(); assertThat(ac.getValue().getServiceConfig()).isNull(); @@ -581,7 +588,12 @@ public List resolveAddress(String host) throws Exception { // Load balancer rejects the empty addresses. @Test public void resolve_emptyResult_notAccepted() throws Exception { - when(mockListener.onResult2(isA(ResolutionResult.class))).thenReturn(Status.UNAVAILABLE); + doAnswer(invocation -> { + ResolutionResult result = invocation.getArgument(0); + result.getAttributes().get(RetryingNameResolver.RESOLUTION_RESULT_LISTENER_KEY) + .resolutionAttempted(Status.UNAVAILABLE); + return null; + }).when(mockListener).onResult(isA(ResolutionResult.class)); DnsNameResolver.enableTxt = true; RetryingNameResolver resolver = newResolver("dns:///addr.fake:1234", 443); @@ -602,7 +614,7 @@ public List resolveAddress(String host) throws Exception { syncContext.execute(() -> assertThat(fakeExecutor.runDueTasks()).isEqualTo(1)); ArgumentCaptor ac = ArgumentCaptor.forClass(ResolutionResult.class); - verify(mockListener).onResult2(ac.capture()); + verify(mockListener).onResult(ac.capture()); verifyNoMoreInteractions(mockListener); assertThat(ac.getValue().getAddresses()).isEmpty(); assertThat(ac.getValue().getServiceConfig()).isNull(); @@ -628,7 +640,7 @@ public void resolve_nullResourceResolver() throws Exception { dnsResolver.setResourceResolver(null); resolver.start(mockListener); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener).onResult2(resultCaptor.capture()); + verify(mockListener).onResult(resultCaptor.capture()); ResolutionResult result = resultCaptor.getValue(); InetSocketAddress resolvedBackendAddr = (InetSocketAddress) Iterables.getOnlyElement( @@ -700,7 +712,7 @@ public ConfigOrError parseServiceConfig(Map rawServiceConfig) { resolver.start(mockListener); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener).onResult2(resultCaptor.capture()); + verify(mockListener).onResult(resultCaptor.capture()); ResolutionResult result = resultCaptor.getValue(); InetSocketAddress resolvedBackendAddr = (InetSocketAddress) Iterables.getOnlyElement( @@ -758,7 +770,7 @@ public void resolve_serviceConfigLookupFails_nullServiceConfig() throws Exceptio dnsResolver.setResourceResolver(mockResourceResolver); resolver.start(mockListener); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener).onResult2(resultCaptor.capture()); + verify(mockListener).onResult(resultCaptor.capture()); ResolutionResult result = resultCaptor.getValue(); InetSocketAddress resolvedBackendAddr = (InetSocketAddress) Iterables.getOnlyElement( @@ -790,7 +802,7 @@ public void resolve_serviceConfigMalformed_serviceConfigError() throws Exception dnsResolver.setResourceResolver(mockResourceResolver); resolver.start(mockListener); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener).onResult2(resultCaptor.capture()); + verify(mockListener).onResult(resultCaptor.capture()); ResolutionResult result = resultCaptor.getValue(); InetSocketAddress resolvedBackendAddr = (InetSocketAddress) Iterables.getOnlyElement( @@ -858,7 +870,7 @@ public HttpConnectProxiedSocketAddress proxyFor(SocketAddress targetAddress) { resolver.start(mockListener); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener).onResult2(resultCaptor.capture()); + verify(mockListener).onResult(resultCaptor.capture()); List result = resultCaptor.getValue().getAddresses(); assertThat(result).hasSize(1); EquivalentAddressGroup eag = result.get(0); diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java index 4d42056b689..1d6492f791c 100644 --- a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java +++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java @@ -1054,79 +1054,6 @@ public void noMoreCallbackAfterLoadBalancerShutdown() { verifyNoMoreInteractions(mockLoadBalancer); } - @Test - public void noMoreCallbackAfterLoadBalancerShutdown_configError() throws InterruptedException { - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri) - .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress))) - .build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - Status resolutionError = Status.UNAVAILABLE.withDescription("Resolution failed"); - createChannel(); - - FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.get(0); - verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class)); - verify(mockLoadBalancer).acceptResolvedAddresses(resolvedAddressCaptor.capture()); - assertThat(resolvedAddressCaptor.getValue().getAddresses()).containsExactly(addressGroup); - - SubchannelStateListener stateListener1 = mock(SubchannelStateListener.class); - SubchannelStateListener stateListener2 = mock(SubchannelStateListener.class); - Subchannel subchannel1 = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, stateListener1); - Subchannel subchannel2 = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, stateListener2); - requestConnectionSafely(helper, subchannel1); - requestConnectionSafely(helper, subchannel2); - verify(mockTransportFactory, times(2)) - .newClientTransport( - any(SocketAddress.class), any(ClientTransportOptions.class), any(ChannelLogger.class)); - MockClientTransportInfo transportInfo1 = transports.poll(); - MockClientTransportInfo transportInfo2 = transports.poll(); - - // LoadBalancer receives all sorts of callbacks - transportInfo1.listener.transportReady(); - - verify(stateListener1, times(2)).onSubchannelState(stateInfoCaptor.capture()); - assertSame(CONNECTING, stateInfoCaptor.getAllValues().get(0).getState()); - assertSame(READY, stateInfoCaptor.getAllValues().get(1).getState()); - - verify(stateListener2).onSubchannelState(stateInfoCaptor.capture()); - assertSame(CONNECTING, stateInfoCaptor.getValue().getState()); - - resolver.listener.onError(resolutionError); - verify(mockLoadBalancer).handleNameResolutionError(resolutionError); - - verifyNoMoreInteractions(mockLoadBalancer); - - channel.shutdown(); - verify(mockLoadBalancer).shutdown(); - verifyNoMoreInteractions(stateListener1, stateListener2); - - // LoadBalancer will normally shutdown all subchannels - shutdownSafely(helper, subchannel1); - shutdownSafely(helper, subchannel2); - - // Since subchannels are shutdown, SubchannelStateListeners will only get SHUTDOWN regardless of - // the transport states. - transportInfo1.listener.transportShutdown(Status.UNAVAILABLE); - transportInfo2.listener.transportReady(); - verify(stateListener1).onSubchannelState(ConnectivityStateInfo.forNonError(SHUTDOWN)); - verify(stateListener2).onSubchannelState(ConnectivityStateInfo.forNonError(SHUTDOWN)); - verifyNoMoreInteractions(stateListener1, stateListener2); - - // No more callback should be delivered to LoadBalancer after it's shut down - resolver.listener.onResult( - ResolutionResult.newBuilder() - .setAddresses(new ArrayList<>()) - .setServiceConfig( - ConfigOrError.fromError(Status.UNAVAILABLE.withDescription("Resolution failed"))) - .build()); - Thread.sleep(1100); - assertThat(timer.getPendingTasks()).isEmpty(); - resolver.resolved(); - verifyNoMoreInteractions(mockLoadBalancer); - } - @Test public void interceptor() throws Exception { final AtomicLong atomic = new AtomicLong(); @@ -3211,48 +3138,6 @@ public void channelTracing_nameResolvedEvent_zeorAndNonzeroBackends() throws Exc assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1); } - @Test - public void channelTracing_nameResolvedEvent_zeorAndNonzeroBackends_usesListener2onResult2() - throws Exception { - timer.forwardNanos(1234); - channelBuilder.maxTraceEvents(10); - List servers = new ArrayList<>(); - servers.add(new EquivalentAddressGroup(socketAddress)); - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri).setServers(servers).build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - createChannel(); - - int prevSize = getStats(channel).channelTrace.events.size(); - ResolutionResult resolutionResult1 = ResolutionResult.newBuilder() - .setAddresses(Collections.singletonList( - new EquivalentAddressGroup( - Arrays.asList(new SocketAddress() {}, new SocketAddress() {})))) - .build(); - - channel.syncContext.execute( - () -> nameResolverFactory.resolvers.get(0).listener.onResult2(resolutionResult1)); - assertThat(getStats(channel).channelTrace.events).hasSize(prevSize); - - prevSize = getStats(channel).channelTrace.events.size(); - nameResolverFactory.resolvers.get(0).listener.onError(Status.INTERNAL); - assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1); - - prevSize = getStats(channel).channelTrace.events.size(); - nameResolverFactory.resolvers.get(0).listener.onError(Status.INTERNAL); - assertThat(getStats(channel).channelTrace.events).hasSize(prevSize); - - prevSize = getStats(channel).channelTrace.events.size(); - ResolutionResult resolutionResult2 = ResolutionResult.newBuilder() - .setAddresses(Collections.singletonList( - new EquivalentAddressGroup( - Arrays.asList(new SocketAddress() {}, new SocketAddress() {})))) - .build(); - channel.syncContext.execute( - () -> nameResolverFactory.resolvers.get(0).listener.onResult2(resolutionResult2)); - assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1); - } - @Test public void channelTracing_serviceConfigChange() throws Exception { timer.forwardNanos(1234); @@ -3312,69 +3197,6 @@ public void channelTracing_serviceConfigChange() throws Exception { .build()); } - @Test - public void channelTracing_serviceConfigChange_usesListener2OnResult2() throws Exception { - timer.forwardNanos(1234); - channelBuilder.maxTraceEvents(10); - List servers = new ArrayList<>(); - servers.add(new EquivalentAddressGroup(socketAddress)); - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri).setServers(servers).build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - createChannel(); - - int prevSize = getStats(channel).channelTrace.events.size(); - ManagedChannelServiceConfig mcsc1 = createManagedChannelServiceConfig( - ImmutableMap.of(), - new PolicySelection( - mockLoadBalancerProvider, null)); - ResolutionResult resolutionResult1 = ResolutionResult.newBuilder() - .setAddresses(Collections.singletonList( - new EquivalentAddressGroup( - Arrays.asList(new SocketAddress() {}, new SocketAddress() {})))) - .setServiceConfig(ConfigOrError.fromConfig(mcsc1)) - .build(); - - channel.syncContext.execute(() -> - nameResolverFactory.resolvers.get(0).listener.onResult2(resolutionResult1)); - assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1); - assertThat(getStats(channel).channelTrace.events.get(prevSize)) - .isEqualTo(new ChannelTrace.Event.Builder() - .setDescription("Service config changed") - .setSeverity(ChannelTrace.Event.Severity.CT_INFO) - .setTimestampNanos(timer.getTicker().read()) - .build()); - - prevSize = getStats(channel).channelTrace.events.size(); - ResolutionResult resolutionResult2 = ResolutionResult.newBuilder().setAddresses( - Collections.singletonList( - new EquivalentAddressGroup( - Arrays.asList(new SocketAddress() {}, new SocketAddress() {})))) - .setServiceConfig(ConfigOrError.fromConfig(mcsc1)) - .build(); - channel.syncContext.execute(() -> - nameResolverFactory.resolvers.get(0).listener.onResult(resolutionResult2)); - assertThat(getStats(channel).channelTrace.events).hasSize(prevSize); - - prevSize = getStats(channel).channelTrace.events.size(); - timer.forwardNanos(1234); - ResolutionResult resolutionResult3 = ResolutionResult.newBuilder() - .setAddresses(Collections.singletonList( - new EquivalentAddressGroup( - Arrays.asList(new SocketAddress() {}, new SocketAddress() {})))) - .setServiceConfig(ConfigOrError.fromConfig(ManagedChannelServiceConfig.empty())) - .build(); - channel.syncContext.execute(() -> - nameResolverFactory.resolvers.get(0).listener.onResult(resolutionResult3)); - assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1); - assertThat(getStats(channel).channelTrace.events.get(prevSize)) - .isEqualTo(new ChannelTrace.Event.Builder() - .setDescription("Service config changed") - .setSeverity(ChannelTrace.Event.Severity.CT_INFO) - .setTimestampNanos(timer.getTicker().read()) - .build()); - } - @Test public void channelTracing_stateChangeEvent() throws Exception { channelBuilder.maxTraceEvents(10); @@ -4035,120 +3857,6 @@ public ClientTransportFactory buildClientTransportFactory() { mychannel.shutdownNow(); } - @Test - public void badServiceConfigIsRecoverable_usesListener2OnResult2() throws Exception { - final List addresses = - ImmutableList.of(new EquivalentAddressGroup(new SocketAddress() {})); - final class FakeNameResolver extends NameResolver { - Listener2 listener; - private final SynchronizationContext syncContext; - - FakeNameResolver(Args args) { - this.syncContext = args.getSynchronizationContext(); - } - - @Override - public String getServiceAuthority() { - return "also fake"; - } - - @Override - public void start(Listener2 listener) { - this.listener = listener; - syncContext.execute(() -> - listener.onResult2( - ResolutionResult.newBuilder() - .setAddresses(addresses) - .setServiceConfig( - ConfigOrError.fromError( - Status.INTERNAL.withDescription("kaboom is invalid"))) - .build())); - } - - @Override - public void shutdown() {} - } - - final class FakeNameResolverFactory2 extends NameResolver.Factory { - FakeNameResolver resolver; - ManagedChannelImpl managedChannel; - SynchronizationContext syncContext; - - @Nullable - @Override - public NameResolver newNameResolver(URI targetUri, NameResolver.Args args) { - syncContext = args.getSynchronizationContext(); - return (resolver = new FakeNameResolver(args)); - } - - @Override - public String getDefaultScheme() { - return "fake"; - } - } - - FakeNameResolverFactory2 factory = new FakeNameResolverFactory2(); - - ManagedChannelImplBuilder customBuilder = new ManagedChannelImplBuilder(TARGET, - new ClientTransportFactoryBuilder() { - @Override - public ClientTransportFactory buildClientTransportFactory() { - return mockTransportFactory; - } - }, - null); - when(mockTransportFactory.getSupportedSocketAddressTypes()).thenReturn(Collections.singleton( - InetSocketAddress.class)); - customBuilder.executorPool = executorPool; - customBuilder.channelz = channelz; - ManagedChannel mychannel = customBuilder.nameResolverFactory(factory).build(); - - ClientCall call1 = - mychannel.newCall(TestMethodDescriptors.voidMethod(), CallOptions.DEFAULT); - ListenableFuture future1 = ClientCalls.futureUnaryCall(call1, null); - executor.runDueTasks(); - try { - future1.get(1, TimeUnit.SECONDS); - Assert.fail(); - } catch (ExecutionException e) { - assertThat(Throwables.getStackTraceAsString(e.getCause())).contains("kaboom"); - } - - // ok the service config is bad, let's fix it. - Map rawServiceConfig = - parseConfig("{\"loadBalancingConfig\": [{\"round_robin\": {}}]}"); - Object fakeLbConfig = new Object(); - PolicySelection lbConfigs = - new PolicySelection( - mockLoadBalancerProvider, fakeLbConfig); - mockLoadBalancerProvider.parseLoadBalancingPolicyConfig(rawServiceConfig); - ManagedChannelServiceConfig managedChannelServiceConfig = - createManagedChannelServiceConfig(rawServiceConfig, lbConfigs); - factory.syncContext.execute(() -> - factory.resolver.listener.onResult2( - ResolutionResult.newBuilder() - .setAddresses(addresses) - .setServiceConfig(ConfigOrError.fromConfig(managedChannelServiceConfig)) - .build())); - - ClientCall call2 = mychannel.newCall( - TestMethodDescriptors.voidMethod(), - CallOptions.DEFAULT.withDeadlineAfter(5, TimeUnit.SECONDS)); - ListenableFuture future2 = ClientCalls.futureUnaryCall(call2, null); - - timer.forwardTime(1234, TimeUnit.SECONDS); - - executor.runDueTasks(); - try { - future2.get(); - Assert.fail(); - } catch (ExecutionException e) { - assertThat(Throwables.getStackTraceAsString(e.getCause())).contains("deadline"); - } - - mychannel.shutdownNow(); - } - @Test public void nameResolverArgsPropagation() { final AtomicReference capturedArgs = new AtomicReference<>(); @@ -4810,7 +4518,7 @@ public NameResolver newNameResolver(final URI targetUri, NameResolver.Args args) } assertEquals(DEFAULT_PORT, args.getDefaultPort()); FakeNameResolverFactory.FakeNameResolver resolver = - new FakeNameResolverFactory.FakeNameResolver(targetUri, error, args); + new FakeNameResolverFactory.FakeNameResolver(targetUri, error); resolvers.add(resolver); return resolver; } @@ -4838,16 +4546,14 @@ void allResolved() { final class FakeNameResolver extends NameResolver { final URI targetUri; - final SynchronizationContext syncContext; Listener2 listener; boolean shutdown; int refreshCalled; Status error; - FakeNameResolver(URI targetUri, Status error, Args args) { + FakeNameResolver(URI targetUri, Status error) { this.targetUri = targetUri; this.error = error; - syncContext = args.getSynchronizationContext(); } @Override public String getServiceAuthority() { @@ -4879,7 +4585,7 @@ void resolved() { if (configOrError != null) { builder.setServiceConfig(configOrError); } - syncContext.execute(() -> listener.onResult(builder.build())); + listener.onResult(builder.build()); } @Override public void shutdown() { diff --git a/core/src/test/java/io/grpc/internal/RetryingNameResolverTest.java b/core/src/test/java/io/grpc/internal/RetryingNameResolverTest.java index 6347416f0ca..8801f540394 100644 --- a/core/src/test/java/io/grpc/internal/RetryingNameResolverTest.java +++ b/core/src/test/java/io/grpc/internal/RetryingNameResolverTest.java @@ -21,7 +21,6 @@ import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import io.grpc.NameResolver; import io.grpc.NameResolver.Listener2; @@ -80,7 +79,7 @@ public void startAndShutdown() { // Make sure the ResolutionResultListener callback is added to the ResolutionResult attributes, // and the retry scheduler is reset since the name resolution was successful. @Test - public void onResult_success() { + public void onResult_sucess() { retryingNameResolver.start(mockListener); verify(mockNameResolver).start(listenerCaptor.capture()); @@ -95,18 +94,6 @@ public void onResult_success() { verify(mockRetryScheduler).reset(); } - @Test - public void onResult2_sucesss() { - when(mockListener.onResult2(isA(ResolutionResult.class))).thenReturn(Status.OK); - retryingNameResolver.start(mockListener); - verify(mockNameResolver).start(listenerCaptor.capture()); - - assertThat(listenerCaptor.getValue().onResult2(ResolutionResult.newBuilder().build())) - .isEqualTo(Status.OK); - - verify(mockRetryScheduler).reset(); - } - // Make sure the ResolutionResultListener callback is added to the ResolutionResult attributes, // and that a retry gets scheduled when the resolution results are rejected. @Test @@ -125,19 +112,6 @@ public void onResult_failure() { verify(mockRetryScheduler).schedule(isA(Runnable.class)); } - // Make sure that a retry gets scheduled when the resolution results are rejected. - @Test - public void onResult2_failure() { - when(mockListener.onResult2(isA(ResolutionResult.class))).thenReturn(Status.UNAVAILABLE); - retryingNameResolver.start(mockListener); - verify(mockNameResolver).start(listenerCaptor.capture()); - - assertThat(listenerCaptor.getValue().onResult2(ResolutionResult.newBuilder().build())) - .isEqualTo(Status.UNAVAILABLE); - - verify(mockRetryScheduler).schedule(isA(Runnable.class)); - } - // Wrapping a NameResolver more than once is a misconfiguration. @Test public void onResult_failure_doubleWrapped() { diff --git a/grpclb/src/test/java/io/grpc/grpclb/GrpclbNameResolverTest.java b/grpclb/src/test/java/io/grpc/grpclb/GrpclbNameResolverTest.java index c195a78e6f4..3e2cf22605f 100644 --- a/grpclb/src/test/java/io/grpc/grpclb/GrpclbNameResolverTest.java +++ b/grpclb/src/test/java/io/grpc/grpclb/GrpclbNameResolverTest.java @@ -152,7 +152,7 @@ public List resolveSrv(String host) throws Exception { resolver.start(mockListener); assertThat(fakeClock.runDueTasks()).isEqualTo(1); - verify(mockListener).onResult2(resultCaptor.capture()); + verify(mockListener).onResult(resultCaptor.capture()); ResolutionResult result = resultCaptor.getValue(); assertThat(result.getAddresses()).isEmpty(); assertThat(result.getAttributes()).isEqualTo(Attributes.EMPTY); @@ -192,7 +192,7 @@ public ConfigOrError answer(InvocationOnMock invocation) { resolver.start(mockListener); assertThat(fakeClock.runDueTasks()).isEqualTo(1); - verify(mockListener).onResult2(resultCaptor.capture()); + verify(mockListener).onResult(resultCaptor.capture()); ResolutionResult result = resultCaptor.getValue(); InetSocketAddress resolvedBackendAddr = (InetSocketAddress) Iterables.getOnlyElement( @@ -225,7 +225,7 @@ public void resolve_nullResourceResolver() throws Exception { resolver.start(mockListener); assertThat(fakeClock.runDueTasks()).isEqualTo(1); - verify(mockListener).onResult2(resultCaptor.capture()); + verify(mockListener).onResult(resultCaptor.capture()); ResolutionResult result = resultCaptor.getValue(); assertThat(result.getAddresses()) .containsExactly( @@ -272,7 +272,7 @@ public void resolve_addressFailure_stillLookUpBalancersAndServiceConfig() throws resolver.start(mockListener); assertThat(fakeClock.runDueTasks()).isEqualTo(1); - verify(mockListener).onResult2(resultCaptor.capture()); + verify(mockListener).onResult(resultCaptor.capture()); ResolutionResult result = resultCaptor.getValue(); assertThat(result.getAddresses()).isEmpty(); EquivalentAddressGroup resolvedBalancerAddr = @@ -306,7 +306,7 @@ public void resolveAll_balancerLookupFails_stillLookUpServiceConfig() throws Exc resolver.start(mockListener); assertThat(fakeClock.runDueTasks()).isEqualTo(1); - verify(mockListener).onResult2(resultCaptor.capture()); + verify(mockListener).onResult(resultCaptor.capture()); ResolutionResult result = resultCaptor.getValue(); InetSocketAddress resolvedBackendAddr = From 941a1c37a3cf9078da633da38ceaa15009975ad4 Mon Sep 17 00:00:00 2001 From: Larry Safran Date: Wed, 31 Jul 2024 14:56:07 -0700 Subject: [PATCH 007/103] Add dualstack interop test kokoro config (#11422) --- buildscripts/kokoro/psm-dualstack.cfg | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 buildscripts/kokoro/psm-dualstack.cfg diff --git a/buildscripts/kokoro/psm-dualstack.cfg b/buildscripts/kokoro/psm-dualstack.cfg new file mode 100644 index 00000000000..55c906bc4ec --- /dev/null +++ b/buildscripts/kokoro/psm-dualstack.cfg @@ -0,0 +1,17 @@ +# Config file for internal CI + +# Location of the continuous shell script in repository. +build_file: "grpc-java/buildscripts/kokoro/psm-interop-test-java.sh" +timeout_mins: 120 + +action { + define_artifacts { + regex: "artifacts/**/*sponge_log.xml" + regex: "artifacts/**/*.log" + strip_prefix: "artifacts" + } +} +env_vars { + key: "PSM_TEST_SUITE" + value: "dualstack" +} From cc1cbe987191cf1e72e25cd3fd7bef392910a0d6 Mon Sep 17 00:00:00 2001 From: Larry Safran Date: Thu, 1 Aug 2024 11:30:36 -0700 Subject: [PATCH 008/103] Revert "Enable new PickFirst LB (#11348)" (#11425) This reverts commit ccfd351a2e9e3a1dc6d3d0130a6cfa561fba7321. --- .../java/io/grpc/internal/PickFirstLoadBalancerProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/io/grpc/internal/PickFirstLoadBalancerProvider.java b/core/src/main/java/io/grpc/internal/PickFirstLoadBalancerProvider.java index 6591a4a0a7d..92178ccae24 100644 --- a/core/src/main/java/io/grpc/internal/PickFirstLoadBalancerProvider.java +++ b/core/src/main/java/io/grpc/internal/PickFirstLoadBalancerProvider.java @@ -37,7 +37,7 @@ public final class PickFirstLoadBalancerProvider extends LoadBalancerProvider { private static final String SHUFFLE_ADDRESS_LIST_KEY = "shuffleAddressList"; private static boolean enableNewPickFirst = - GrpcUtil.getFlag("GRPC_EXPERIMENTAL_ENABLE_NEW_PICK_FIRST", true); + GrpcUtil.getFlag("GRPC_EXPERIMENTAL_ENABLE_NEW_PICK_FIRST", false); public static boolean isEnabledHappyEyeballs() { From 9bc1a93f6eddff76ad76e28f561d191d2eeb455c Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 1 Aug 2024 16:30:02 -0700 Subject: [PATCH 009/103] xds: Add test that uses real DnsNR with ClusterResolverLB This can detect failures like the UnsupportedOperationException from ebffb0a6. --- .../FakeControlPlaneXdsIntegrationTest.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/xds/src/test/java/io/grpc/xds/FakeControlPlaneXdsIntegrationTest.java b/xds/src/test/java/io/grpc/xds/FakeControlPlaneXdsIntegrationTest.java index 16e6d22631f..30c2403396e 100644 --- a/xds/src/test/java/io/grpc/xds/FakeControlPlaneXdsIntegrationTest.java +++ b/xds/src/test/java/io/grpc/xds/FakeControlPlaneXdsIntegrationTest.java @@ -24,10 +24,17 @@ import com.google.protobuf.Any; import com.google.protobuf.Struct; import com.google.protobuf.Value; +import io.envoyproxy.envoy.config.cluster.v3.Cluster; import io.envoyproxy.envoy.config.cluster.v3.Cluster.LbPolicy; import io.envoyproxy.envoy.config.cluster.v3.LoadBalancingPolicy; import io.envoyproxy.envoy.config.cluster.v3.LoadBalancingPolicy.Policy; +import io.envoyproxy.envoy.config.core.v3.Address; +import io.envoyproxy.envoy.config.core.v3.SocketAddress; import io.envoyproxy.envoy.config.core.v3.TypedExtensionConfig; +import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment; +import io.envoyproxy.envoy.config.endpoint.v3.Endpoint; +import io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint; +import io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints; import io.envoyproxy.envoy.extensions.load_balancing_policies.wrr_locality.v3.WrrLocality; import io.grpc.CallOptions; import io.grpc.Channel; @@ -42,6 +49,7 @@ import io.grpc.testing.protobuf.SimpleRequest; import io.grpc.testing.protobuf.SimpleResponse; import io.grpc.testing.protobuf.SimpleServiceGrpc; +import java.net.InetSocketAddress; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -179,4 +187,34 @@ public void pingPong_ringHash() { .build(); assertEquals(goldenResponse, blockingStub.unaryRpc(request)); } + + @Test + public void pingPong_logicalDns() { + InetSocketAddress serverAddress = + (InetSocketAddress) dataPlane.getServer().getListenSockets().get(0); + controlPlane.setCdsConfig( + ControlPlaneRule.buildCluster().toBuilder() + .setType(Cluster.DiscoveryType.LOGICAL_DNS) + .setLoadAssignment( + ClusterLoadAssignment.newBuilder().addEndpoints( + LocalityLbEndpoints.newBuilder().addLbEndpoints( + LbEndpoint.newBuilder().setEndpoint( + Endpoint.newBuilder().setAddress( + Address.newBuilder().setSocketAddress( + SocketAddress.newBuilder() + .setAddress("localhost") + .setPortValue(serverAddress.getPort())))))) + .build()) + .build()); + + ManagedChannel channel = dataPlane.getManagedChannel(); + SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub = SimpleServiceGrpc.newBlockingStub( + channel); + SimpleRequest request = SimpleRequest.newBuilder() + .build(); + SimpleResponse goldenResponse = SimpleResponse.newBuilder() + .setResponseMessage("Hi, xDS!") + .build(); + assertEquals(goldenResponse, blockingStub.unaryRpc(request)); + } } From 90d0fabb1f100231ab6544cf8e352c623771dee1 Mon Sep 17 00:00:00 2001 From: Kannan J Date: Fri, 2 Aug 2024 20:40:31 +0530 Subject: [PATCH 010/103] Introduce onResult2 in NameResolver Listener2 that returns Status Lets the Name Resolver receive the status of the acceptance of the name resolution by the load balancer. --- api/src/main/java/io/grpc/NameResolver.java | 11 + .../io/grpc/internal/DnsNameResolver.java | 4 +- .../io/grpc/internal/ManagedChannelImpl.java | 255 +++++++-------- .../grpc/internal/RetryingNameResolver.java | 12 + .../io/grpc/internal/DnsNameResolverTest.java | 50 ++- .../grpc/internal/ManagedChannelImplTest.java | 300 +++++++++++++++++- .../internal/RetryingNameResolverTest.java | 28 +- .../grpc/grpclb/GrpclbNameResolverTest.java | 10 +- 8 files changed, 502 insertions(+), 168 deletions(-) diff --git a/api/src/main/java/io/grpc/NameResolver.java b/api/src/main/java/io/grpc/NameResolver.java index a74512eb7e3..bfb9c2a43a1 100644 --- a/api/src/main/java/io/grpc/NameResolver.java +++ b/api/src/main/java/io/grpc/NameResolver.java @@ -246,6 +246,17 @@ public final void onAddresses( */ @Override public abstract void onError(Status error); + + /** + * Handles updates on resolved addresses and attributes. + * + * @param resolutionResult the resolved server addresses, attributes, and Service Config. + * @since 1.66 + */ + public Status onResult2(ResolutionResult resolutionResult) { + onResult(resolutionResult); + return Status.OK; + } } /** diff --git a/core/src/main/java/io/grpc/internal/DnsNameResolver.java b/core/src/main/java/io/grpc/internal/DnsNameResolver.java index 5ef6dd863c2..df51d6f2c5c 100644 --- a/core/src/main/java/io/grpc/internal/DnsNameResolver.java +++ b/core/src/main/java/io/grpc/internal/DnsNameResolver.java @@ -330,7 +330,9 @@ public void run() { resolutionResultBuilder.setAttributes(result.attributes); } } - savedListener.onResult(resolutionResultBuilder.build()); + syncContext.execute(() -> { + savedListener.onResult2(resolutionResultBuilder.build()); + }); } catch (IOException e) { savedListener.onError( Status.UNAVAILABLE.withDescription("Unable to resolve host " + host).withCause(e)); diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java index c5c7b66e15d..7f45ca967ea 100644 --- a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java +++ b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java @@ -1673,146 +1673,147 @@ final class NameResolverListener extends NameResolver.Listener2 { public void onResult(final ResolutionResult resolutionResult) { final class NamesResolved implements Runnable { - @SuppressWarnings("ReferenceEquality") @Override public void run() { - if (ManagedChannelImpl.this.nameResolver != resolver) { - return; - } - - List servers = resolutionResult.getAddresses(); - channelLogger.log( - ChannelLogLevel.DEBUG, - "Resolved address: {0}, config={1}", - servers, - resolutionResult.getAttributes()); - - if (lastResolutionState != ResolutionState.SUCCESS) { - channelLogger.log(ChannelLogLevel.INFO, "Address resolved: {0}", servers); - lastResolutionState = ResolutionState.SUCCESS; - } - - ConfigOrError configOrError = resolutionResult.getServiceConfig(); + Status status = onResult2(resolutionResult); ResolutionResultListener resolutionResultListener = resolutionResult.getAttributes() .get(RetryingNameResolver.RESOLUTION_RESULT_LISTENER_KEY); - InternalConfigSelector resolvedConfigSelector = - resolutionResult.getAttributes().get(InternalConfigSelector.KEY); - ManagedChannelServiceConfig validServiceConfig = - configOrError != null && configOrError.getConfig() != null - ? (ManagedChannelServiceConfig) configOrError.getConfig() - : null; - Status serviceConfigError = configOrError != null ? configOrError.getError() : null; - - ManagedChannelServiceConfig effectiveServiceConfig; - if (!lookUpServiceConfig) { - if (validServiceConfig != null) { - channelLogger.log( - ChannelLogLevel.INFO, - "Service config from name resolver discarded by channel settings"); - } - effectiveServiceConfig = - defaultServiceConfig == null ? EMPTY_SERVICE_CONFIG : defaultServiceConfig; - if (resolvedConfigSelector != null) { + resolutionResultListener.resolutionAttempted(status); + } + } + + syncContext.execute(new NamesResolved()); + } + + @SuppressWarnings("ReferenceEquality") + @Override + public Status onResult2(final ResolutionResult resolutionResult) { + syncContext.throwIfNotInThisSynchronizationContext(); + if (ManagedChannelImpl.this.nameResolver != resolver) { + return Status.OK; + } + + List servers = resolutionResult.getAddresses(); + channelLogger.log( + ChannelLogLevel.DEBUG, + "Resolved address: {0}, config={1}", + servers, + resolutionResult.getAttributes()); + + if (lastResolutionState != ResolutionState.SUCCESS) { + channelLogger.log(ChannelLogLevel.INFO, "Address resolved: {0}", servers); + lastResolutionState = ResolutionState.SUCCESS; + } + + ConfigOrError configOrError = resolutionResult.getServiceConfig(); + InternalConfigSelector resolvedConfigSelector = + resolutionResult.getAttributes().get(InternalConfigSelector.KEY); + ManagedChannelServiceConfig validServiceConfig = + configOrError != null && configOrError.getConfig() != null + ? (ManagedChannelServiceConfig) configOrError.getConfig() + : null; + Status serviceConfigError = configOrError != null ? configOrError.getError() : null; + + ManagedChannelServiceConfig effectiveServiceConfig; + if (!lookUpServiceConfig) { + if (validServiceConfig != null) { + channelLogger.log( + ChannelLogLevel.INFO, + "Service config from name resolver discarded by channel settings"); + } + effectiveServiceConfig = + defaultServiceConfig == null ? EMPTY_SERVICE_CONFIG : defaultServiceConfig; + if (resolvedConfigSelector != null) { + channelLogger.log( + ChannelLogLevel.INFO, + "Config selector from name resolver discarded by channel settings"); + } + realChannel.updateConfigSelector(effectiveServiceConfig.getDefaultConfigSelector()); + } else { + // Try to use config if returned from name resolver + // Otherwise, try to use the default config if available + if (validServiceConfig != null) { + effectiveServiceConfig = validServiceConfig; + if (resolvedConfigSelector != null) { + realChannel.updateConfigSelector(resolvedConfigSelector); + if (effectiveServiceConfig.getDefaultConfigSelector() != null) { channelLogger.log( - ChannelLogLevel.INFO, - "Config selector from name resolver discarded by channel settings"); + ChannelLogLevel.DEBUG, + "Method configs in service config will be discarded due to presence of" + + "config-selector"); } + } else { realChannel.updateConfigSelector(effectiveServiceConfig.getDefaultConfigSelector()); + } + } else if (defaultServiceConfig != null) { + effectiveServiceConfig = defaultServiceConfig; + realChannel.updateConfigSelector(effectiveServiceConfig.getDefaultConfigSelector()); + channelLogger.log( + ChannelLogLevel.INFO, + "Received no service config, using default service config"); + } else if (serviceConfigError != null) { + if (!serviceConfigUpdated) { + // First DNS lookup has invalid service config, and cannot fall back to default + channelLogger.log( + ChannelLogLevel.INFO, + "Fallback to error due to invalid first service config without default config"); + // This error could be an "inappropriate" control plane error that should not bleed + // through to client code using gRPC. We let them flow through here to the LB as + // we later check for these error codes when investigating pick results in + // GrpcUtil.getTransportFromPickResult(). + onError(configOrError.getError()); + return configOrError.getError(); } else { - // Try to use config if returned from name resolver - // Otherwise, try to use the default config if available - if (validServiceConfig != null) { - effectiveServiceConfig = validServiceConfig; - if (resolvedConfigSelector != null) { - realChannel.updateConfigSelector(resolvedConfigSelector); - if (effectiveServiceConfig.getDefaultConfigSelector() != null) { - channelLogger.log( - ChannelLogLevel.DEBUG, - "Method configs in service config will be discarded due to presence of" - + "config-selector"); - } - } else { - realChannel.updateConfigSelector(effectiveServiceConfig.getDefaultConfigSelector()); - } - } else if (defaultServiceConfig != null) { - effectiveServiceConfig = defaultServiceConfig; - realChannel.updateConfigSelector(effectiveServiceConfig.getDefaultConfigSelector()); - channelLogger.log( - ChannelLogLevel.INFO, - "Received no service config, using default service config"); - } else if (serviceConfigError != null) { - if (!serviceConfigUpdated) { - // First DNS lookup has invalid service config, and cannot fall back to default - channelLogger.log( - ChannelLogLevel.INFO, - "Fallback to error due to invalid first service config without default config"); - // This error could be an "inappropriate" control plane error that should not bleed - // through to client code using gRPC. We let them flow through here to the LB as - // we later check for these error codes when investigating pick results in - // GrpcUtil.getTransportFromPickResult(). - onError(configOrError.getError()); - if (resolutionResultListener != null) { - resolutionResultListener.resolutionAttempted(configOrError.getError()); - } - return; - } else { - effectiveServiceConfig = lastServiceConfig; - } - } else { - effectiveServiceConfig = EMPTY_SERVICE_CONFIG; - realChannel.updateConfigSelector(null); - } - if (!effectiveServiceConfig.equals(lastServiceConfig)) { - channelLogger.log( - ChannelLogLevel.INFO, - "Service config changed{0}", - effectiveServiceConfig == EMPTY_SERVICE_CONFIG ? " to empty" : ""); - lastServiceConfig = effectiveServiceConfig; - transportProvider.throttle = effectiveServiceConfig.getRetryThrottling(); - } - - try { - // TODO(creamsoup): when `servers` is empty and lastResolutionStateCopy == SUCCESS - // and lbNeedAddress, it shouldn't call the handleServiceConfigUpdate. But, - // lbNeedAddress is not deterministic - serviceConfigUpdated = true; - } catch (RuntimeException re) { - logger.log( - Level.WARNING, - "[" + getLogId() + "] Unexpected exception from parsing service config", - re); - } + effectiveServiceConfig = lastServiceConfig; } + } else { + effectiveServiceConfig = EMPTY_SERVICE_CONFIG; + realChannel.updateConfigSelector(null); + } + if (!effectiveServiceConfig.equals(lastServiceConfig)) { + channelLogger.log( + ChannelLogLevel.INFO, + "Service config changed{0}", + effectiveServiceConfig == EMPTY_SERVICE_CONFIG ? " to empty" : ""); + lastServiceConfig = effectiveServiceConfig; + transportProvider.throttle = effectiveServiceConfig.getRetryThrottling(); + } - Attributes effectiveAttrs = resolutionResult.getAttributes(); - // Call LB only if it's not shutdown. If LB is shutdown, lbHelper won't match. - if (NameResolverListener.this.helper == ManagedChannelImpl.this.lbHelper) { - Attributes.Builder attrBuilder = - effectiveAttrs.toBuilder().discard(InternalConfigSelector.KEY); - Map healthCheckingConfig = - effectiveServiceConfig.getHealthCheckingConfig(); - if (healthCheckingConfig != null) { - attrBuilder - .set(LoadBalancer.ATTR_HEALTH_CHECKING_CONFIG, healthCheckingConfig) - .build(); - } - Attributes attributes = attrBuilder.build(); - - Status addressAcceptanceStatus = helper.lb.tryAcceptResolvedAddresses( - ResolvedAddresses.newBuilder() - .setAddresses(servers) - .setAttributes(attributes) - .setLoadBalancingPolicyConfig(effectiveServiceConfig.getLoadBalancingConfig()) - .build()); - // If a listener is provided, let it know if the addresses were accepted. - if (resolutionResultListener != null) { - resolutionResultListener.resolutionAttempted(addressAcceptanceStatus); - } - } + try { + // TODO(creamsoup): when `servers` is empty and lastResolutionStateCopy == SUCCESS + // and lbNeedAddress, it shouldn't call the handleServiceConfigUpdate. But, + // lbNeedAddress is not deterministic + serviceConfigUpdated = true; + } catch (RuntimeException re) { + logger.log( + Level.WARNING, + "[" + getLogId() + "] Unexpected exception from parsing service config", + re); } } - syncContext.execute(new NamesResolved()); + Attributes effectiveAttrs = resolutionResult.getAttributes(); + // Call LB only if it's not shutdown. If LB is shutdown, lbHelper won't match. + if (NameResolverListener.this.helper == ManagedChannelImpl.this.lbHelper) { + Attributes.Builder attrBuilder = + effectiveAttrs.toBuilder().discard(InternalConfigSelector.KEY); + Map healthCheckingConfig = + effectiveServiceConfig.getHealthCheckingConfig(); + if (healthCheckingConfig != null) { + attrBuilder + .set(LoadBalancer.ATTR_HEALTH_CHECKING_CONFIG, healthCheckingConfig) + .build(); + } + Attributes attributes = attrBuilder.build(); + + return helper.lb.tryAcceptResolvedAddresses( + ResolvedAddresses.newBuilder() + .setAddresses(servers) + .setAttributes(attributes) + .setLoadBalancingPolicyConfig(effectiveServiceConfig.getLoadBalancingConfig()) + .build()); + } + return Status.OK; } @Override diff --git a/core/src/main/java/io/grpc/internal/RetryingNameResolver.java b/core/src/main/java/io/grpc/internal/RetryingNameResolver.java index 6d806e95944..6dcfcd3534a 100644 --- a/core/src/main/java/io/grpc/internal/RetryingNameResolver.java +++ b/core/src/main/java/io/grpc/internal/RetryingNameResolver.java @@ -95,12 +95,24 @@ public void onResult(ResolutionResult resolutionResult) { "RetryingNameResolver can only be used once to wrap a NameResolver"); } + // To have retry behavior for name resolvers that haven't migrated to onResult2. delegateListener.onResult(resolutionResult.toBuilder().setAttributes( resolutionResult.getAttributes().toBuilder() .set(RESOLUTION_RESULT_LISTENER_KEY, new ResolutionResultListener()).build()) .build()); } + @Override + public Status onResult2(ResolutionResult resolutionResult) { + Status status = delegateListener.onResult2(resolutionResult); + if (status.isOk()) { + retryScheduler.reset(); + } else { + retryScheduler.schedule(new DelayedNameResolverRefresh()); + } + return status; + } + @Override public void onError(Status error) { delegateListener.onError(error); diff --git a/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java b/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java index 14d3fddd290..0512171f4e7 100644 --- a/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java +++ b/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java @@ -26,7 +26,6 @@ import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.isA; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -226,13 +225,7 @@ public void setUp() { System.getProperty(DnsNameResolver.NETWORKADDRESS_CACHE_TTL_PROPERTY); // By default the mock listener processes the result successfully. - doAnswer(invocation -> { - ResolutionResult result = invocation.getArgument(0); - syncContext.execute( - () -> result.getAttributes().get(RetryingNameResolver.RESOLUTION_RESULT_LISTENER_KEY) - .resolutionAttempted(Status.OK)); - return null; - }).when(mockListener).onResult(isA(ResolutionResult.class)); + when(mockListener.onResult2(isA(ResolutionResult.class))).thenReturn(Status.OK); } @After @@ -319,13 +312,13 @@ private void resolveNeverCache(boolean isAndroid) throws Exception { resolver.start(mockListener); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener).onResult(resultCaptor.capture()); + verify(mockListener).onResult2(resultCaptor.capture()); assertAnswerMatches(answer1, 81, resultCaptor.getValue()); assertEquals(0, fakeClock.numPendingTasks()); resolver.refresh(); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener, times(2)).onResult(resultCaptor.capture()); + verify(mockListener, times(2)).onResult2(resultCaptor.capture()); assertAnswerMatches(answer2, 81, resultCaptor.getValue()); assertEquals(0, fakeClock.numPendingTasks()); assertEquals(0, fakeExecutor.numPendingTasks()); @@ -347,7 +340,7 @@ public void testExecutor_default() throws Exception { resolver.start(mockListener); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener).onResult(resultCaptor.capture()); + verify(mockListener).onResult2(resultCaptor.capture()); assertAnswerMatches(answer, 81, resultCaptor.getValue()); assertEquals(0, fakeClock.numPendingTasks()); assertEquals(0, fakeExecutor.numPendingTasks()); @@ -389,7 +382,7 @@ public void execute(Runnable command) { resolver.start(mockListener); assertEquals(0, fakeExecutor.runDueTasks()); - verify(mockListener).onResult(resultCaptor.capture()); + verify(mockListener).onResult2(resultCaptor.capture()); assertAnswerMatches(answer, 81, resultCaptor.getValue()); assertEquals(0, fakeClock.numPendingTasks()); assertEquals(0, fakeExecutor.numPendingTasks()); @@ -418,7 +411,7 @@ public void resolve_cacheForever() throws Exception { resolver.start(mockListener); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener).onResult(resultCaptor.capture()); + verify(mockListener).onResult2(resultCaptor.capture()); assertAnswerMatches(answer1, 81, resultCaptor.getValue()); assertEquals(0, fakeClock.numPendingTasks()); @@ -452,7 +445,7 @@ public void resolve_usingCache() throws Exception { resolver.start(mockListener); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener).onResult(resultCaptor.capture()); + verify(mockListener).onResult2(resultCaptor.capture()); assertAnswerMatches(answer, 81, resultCaptor.getValue()); assertEquals(0, fakeClock.numPendingTasks()); @@ -487,14 +480,14 @@ public void resolve_cacheExpired() throws Exception { resolver.start(mockListener); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener).onResult(resultCaptor.capture()); + verify(mockListener).onResult2(resultCaptor.capture()); assertAnswerMatches(answer1, 81, resultCaptor.getValue()); assertEquals(0, fakeClock.numPendingTasks()); fakeTicker.advance(ttl + 1, TimeUnit.SECONDS); resolver.refresh(); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener, times(2)).onResult(resultCaptor.capture()); + verify(mockListener, times(2)).onResult2(resultCaptor.capture()); assertAnswerMatches(answer2, 81, resultCaptor.getValue()); assertEquals(0, fakeClock.numPendingTasks()); assertEquals(0, fakeExecutor.numPendingTasks()); @@ -531,7 +524,7 @@ private void resolveDefaultValue() throws Exception { resolver.start(mockListener); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener).onResult(resultCaptor.capture()); + verify(mockListener).onResult2(resultCaptor.capture()); assertAnswerMatches(answer1, 81, resultCaptor.getValue()); assertEquals(0, fakeClock.numPendingTasks()); @@ -544,7 +537,7 @@ private void resolveDefaultValue() throws Exception { fakeTicker.advance(1, TimeUnit.SECONDS); resolver.refresh(); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener, times(2)).onResult(resultCaptor.capture()); + verify(mockListener, times(2)).onResult2(resultCaptor.capture()); assertAnswerMatches(answer2, 81, resultCaptor.getValue()); assertEquals(0, fakeClock.numPendingTasks()); assertEquals(0, fakeExecutor.numPendingTasks()); @@ -575,7 +568,7 @@ public List resolveAddress(String host) throws Exception { assertThat(fakeExecutor.runDueTasks()).isEqualTo(1); ArgumentCaptor ac = ArgumentCaptor.forClass(ResolutionResult.class); - verify(mockListener).onResult(ac.capture()); + verify(mockListener).onResult2(ac.capture()); verifyNoMoreInteractions(mockListener); assertThat(ac.getValue().getAddresses()).isEmpty(); assertThat(ac.getValue().getServiceConfig()).isNull(); @@ -588,12 +581,7 @@ public List resolveAddress(String host) throws Exception { // Load balancer rejects the empty addresses. @Test public void resolve_emptyResult_notAccepted() throws Exception { - doAnswer(invocation -> { - ResolutionResult result = invocation.getArgument(0); - result.getAttributes().get(RetryingNameResolver.RESOLUTION_RESULT_LISTENER_KEY) - .resolutionAttempted(Status.UNAVAILABLE); - return null; - }).when(mockListener).onResult(isA(ResolutionResult.class)); + when(mockListener.onResult2(isA(ResolutionResult.class))).thenReturn(Status.UNAVAILABLE); DnsNameResolver.enableTxt = true; RetryingNameResolver resolver = newResolver("dns:///addr.fake:1234", 443); @@ -614,7 +602,7 @@ public List resolveAddress(String host) throws Exception { syncContext.execute(() -> assertThat(fakeExecutor.runDueTasks()).isEqualTo(1)); ArgumentCaptor ac = ArgumentCaptor.forClass(ResolutionResult.class); - verify(mockListener).onResult(ac.capture()); + verify(mockListener).onResult2(ac.capture()); verifyNoMoreInteractions(mockListener); assertThat(ac.getValue().getAddresses()).isEmpty(); assertThat(ac.getValue().getServiceConfig()).isNull(); @@ -640,7 +628,7 @@ public void resolve_nullResourceResolver() throws Exception { dnsResolver.setResourceResolver(null); resolver.start(mockListener); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener).onResult(resultCaptor.capture()); + verify(mockListener).onResult2(resultCaptor.capture()); ResolutionResult result = resultCaptor.getValue(); InetSocketAddress resolvedBackendAddr = (InetSocketAddress) Iterables.getOnlyElement( @@ -712,7 +700,7 @@ public ConfigOrError parseServiceConfig(Map rawServiceConfig) { resolver.start(mockListener); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener).onResult(resultCaptor.capture()); + verify(mockListener).onResult2(resultCaptor.capture()); ResolutionResult result = resultCaptor.getValue(); InetSocketAddress resolvedBackendAddr = (InetSocketAddress) Iterables.getOnlyElement( @@ -770,7 +758,7 @@ public void resolve_serviceConfigLookupFails_nullServiceConfig() throws Exceptio dnsResolver.setResourceResolver(mockResourceResolver); resolver.start(mockListener); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener).onResult(resultCaptor.capture()); + verify(mockListener).onResult2(resultCaptor.capture()); ResolutionResult result = resultCaptor.getValue(); InetSocketAddress resolvedBackendAddr = (InetSocketAddress) Iterables.getOnlyElement( @@ -802,7 +790,7 @@ public void resolve_serviceConfigMalformed_serviceConfigError() throws Exception dnsResolver.setResourceResolver(mockResourceResolver); resolver.start(mockListener); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener).onResult(resultCaptor.capture()); + verify(mockListener).onResult2(resultCaptor.capture()); ResolutionResult result = resultCaptor.getValue(); InetSocketAddress resolvedBackendAddr = (InetSocketAddress) Iterables.getOnlyElement( @@ -870,7 +858,7 @@ public HttpConnectProxiedSocketAddress proxyFor(SocketAddress targetAddress) { resolver.start(mockListener); assertEquals(1, fakeExecutor.runDueTasks()); - verify(mockListener).onResult(resultCaptor.capture()); + verify(mockListener).onResult2(resultCaptor.capture()); List result = resultCaptor.getValue().getAddresses(); assertThat(result).hasSize(1); EquivalentAddressGroup eag = result.get(0); diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java index 1d6492f791c..4d42056b689 100644 --- a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java +++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java @@ -1054,6 +1054,79 @@ public void noMoreCallbackAfterLoadBalancerShutdown() { verifyNoMoreInteractions(mockLoadBalancer); } + @Test + public void noMoreCallbackAfterLoadBalancerShutdown_configError() throws InterruptedException { + FakeNameResolverFactory nameResolverFactory = + new FakeNameResolverFactory.Builder(expectedUri) + .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress))) + .build(); + channelBuilder.nameResolverFactory(nameResolverFactory); + Status resolutionError = Status.UNAVAILABLE.withDescription("Resolution failed"); + createChannel(); + + FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.get(0); + verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class)); + verify(mockLoadBalancer).acceptResolvedAddresses(resolvedAddressCaptor.capture()); + assertThat(resolvedAddressCaptor.getValue().getAddresses()).containsExactly(addressGroup); + + SubchannelStateListener stateListener1 = mock(SubchannelStateListener.class); + SubchannelStateListener stateListener2 = mock(SubchannelStateListener.class); + Subchannel subchannel1 = + createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, stateListener1); + Subchannel subchannel2 = + createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, stateListener2); + requestConnectionSafely(helper, subchannel1); + requestConnectionSafely(helper, subchannel2); + verify(mockTransportFactory, times(2)) + .newClientTransport( + any(SocketAddress.class), any(ClientTransportOptions.class), any(ChannelLogger.class)); + MockClientTransportInfo transportInfo1 = transports.poll(); + MockClientTransportInfo transportInfo2 = transports.poll(); + + // LoadBalancer receives all sorts of callbacks + transportInfo1.listener.transportReady(); + + verify(stateListener1, times(2)).onSubchannelState(stateInfoCaptor.capture()); + assertSame(CONNECTING, stateInfoCaptor.getAllValues().get(0).getState()); + assertSame(READY, stateInfoCaptor.getAllValues().get(1).getState()); + + verify(stateListener2).onSubchannelState(stateInfoCaptor.capture()); + assertSame(CONNECTING, stateInfoCaptor.getValue().getState()); + + resolver.listener.onError(resolutionError); + verify(mockLoadBalancer).handleNameResolutionError(resolutionError); + + verifyNoMoreInteractions(mockLoadBalancer); + + channel.shutdown(); + verify(mockLoadBalancer).shutdown(); + verifyNoMoreInteractions(stateListener1, stateListener2); + + // LoadBalancer will normally shutdown all subchannels + shutdownSafely(helper, subchannel1); + shutdownSafely(helper, subchannel2); + + // Since subchannels are shutdown, SubchannelStateListeners will only get SHUTDOWN regardless of + // the transport states. + transportInfo1.listener.transportShutdown(Status.UNAVAILABLE); + transportInfo2.listener.transportReady(); + verify(stateListener1).onSubchannelState(ConnectivityStateInfo.forNonError(SHUTDOWN)); + verify(stateListener2).onSubchannelState(ConnectivityStateInfo.forNonError(SHUTDOWN)); + verifyNoMoreInteractions(stateListener1, stateListener2); + + // No more callback should be delivered to LoadBalancer after it's shut down + resolver.listener.onResult( + ResolutionResult.newBuilder() + .setAddresses(new ArrayList<>()) + .setServiceConfig( + ConfigOrError.fromError(Status.UNAVAILABLE.withDescription("Resolution failed"))) + .build()); + Thread.sleep(1100); + assertThat(timer.getPendingTasks()).isEmpty(); + resolver.resolved(); + verifyNoMoreInteractions(mockLoadBalancer); + } + @Test public void interceptor() throws Exception { final AtomicLong atomic = new AtomicLong(); @@ -3138,6 +3211,48 @@ public void channelTracing_nameResolvedEvent_zeorAndNonzeroBackends() throws Exc assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1); } + @Test + public void channelTracing_nameResolvedEvent_zeorAndNonzeroBackends_usesListener2onResult2() + throws Exception { + timer.forwardNanos(1234); + channelBuilder.maxTraceEvents(10); + List servers = new ArrayList<>(); + servers.add(new EquivalentAddressGroup(socketAddress)); + FakeNameResolverFactory nameResolverFactory = + new FakeNameResolverFactory.Builder(expectedUri).setServers(servers).build(); + channelBuilder.nameResolverFactory(nameResolverFactory); + createChannel(); + + int prevSize = getStats(channel).channelTrace.events.size(); + ResolutionResult resolutionResult1 = ResolutionResult.newBuilder() + .setAddresses(Collections.singletonList( + new EquivalentAddressGroup( + Arrays.asList(new SocketAddress() {}, new SocketAddress() {})))) + .build(); + + channel.syncContext.execute( + () -> nameResolverFactory.resolvers.get(0).listener.onResult2(resolutionResult1)); + assertThat(getStats(channel).channelTrace.events).hasSize(prevSize); + + prevSize = getStats(channel).channelTrace.events.size(); + nameResolverFactory.resolvers.get(0).listener.onError(Status.INTERNAL); + assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1); + + prevSize = getStats(channel).channelTrace.events.size(); + nameResolverFactory.resolvers.get(0).listener.onError(Status.INTERNAL); + assertThat(getStats(channel).channelTrace.events).hasSize(prevSize); + + prevSize = getStats(channel).channelTrace.events.size(); + ResolutionResult resolutionResult2 = ResolutionResult.newBuilder() + .setAddresses(Collections.singletonList( + new EquivalentAddressGroup( + Arrays.asList(new SocketAddress() {}, new SocketAddress() {})))) + .build(); + channel.syncContext.execute( + () -> nameResolverFactory.resolvers.get(0).listener.onResult2(resolutionResult2)); + assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1); + } + @Test public void channelTracing_serviceConfigChange() throws Exception { timer.forwardNanos(1234); @@ -3197,6 +3312,69 @@ public void channelTracing_serviceConfigChange() throws Exception { .build()); } + @Test + public void channelTracing_serviceConfigChange_usesListener2OnResult2() throws Exception { + timer.forwardNanos(1234); + channelBuilder.maxTraceEvents(10); + List servers = new ArrayList<>(); + servers.add(new EquivalentAddressGroup(socketAddress)); + FakeNameResolverFactory nameResolverFactory = + new FakeNameResolverFactory.Builder(expectedUri).setServers(servers).build(); + channelBuilder.nameResolverFactory(nameResolverFactory); + createChannel(); + + int prevSize = getStats(channel).channelTrace.events.size(); + ManagedChannelServiceConfig mcsc1 = createManagedChannelServiceConfig( + ImmutableMap.of(), + new PolicySelection( + mockLoadBalancerProvider, null)); + ResolutionResult resolutionResult1 = ResolutionResult.newBuilder() + .setAddresses(Collections.singletonList( + new EquivalentAddressGroup( + Arrays.asList(new SocketAddress() {}, new SocketAddress() {})))) + .setServiceConfig(ConfigOrError.fromConfig(mcsc1)) + .build(); + + channel.syncContext.execute(() -> + nameResolverFactory.resolvers.get(0).listener.onResult2(resolutionResult1)); + assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1); + assertThat(getStats(channel).channelTrace.events.get(prevSize)) + .isEqualTo(new ChannelTrace.Event.Builder() + .setDescription("Service config changed") + .setSeverity(ChannelTrace.Event.Severity.CT_INFO) + .setTimestampNanos(timer.getTicker().read()) + .build()); + + prevSize = getStats(channel).channelTrace.events.size(); + ResolutionResult resolutionResult2 = ResolutionResult.newBuilder().setAddresses( + Collections.singletonList( + new EquivalentAddressGroup( + Arrays.asList(new SocketAddress() {}, new SocketAddress() {})))) + .setServiceConfig(ConfigOrError.fromConfig(mcsc1)) + .build(); + channel.syncContext.execute(() -> + nameResolverFactory.resolvers.get(0).listener.onResult(resolutionResult2)); + assertThat(getStats(channel).channelTrace.events).hasSize(prevSize); + + prevSize = getStats(channel).channelTrace.events.size(); + timer.forwardNanos(1234); + ResolutionResult resolutionResult3 = ResolutionResult.newBuilder() + .setAddresses(Collections.singletonList( + new EquivalentAddressGroup( + Arrays.asList(new SocketAddress() {}, new SocketAddress() {})))) + .setServiceConfig(ConfigOrError.fromConfig(ManagedChannelServiceConfig.empty())) + .build(); + channel.syncContext.execute(() -> + nameResolverFactory.resolvers.get(0).listener.onResult(resolutionResult3)); + assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1); + assertThat(getStats(channel).channelTrace.events.get(prevSize)) + .isEqualTo(new ChannelTrace.Event.Builder() + .setDescription("Service config changed") + .setSeverity(ChannelTrace.Event.Severity.CT_INFO) + .setTimestampNanos(timer.getTicker().read()) + .build()); + } + @Test public void channelTracing_stateChangeEvent() throws Exception { channelBuilder.maxTraceEvents(10); @@ -3857,6 +4035,120 @@ public ClientTransportFactory buildClientTransportFactory() { mychannel.shutdownNow(); } + @Test + public void badServiceConfigIsRecoverable_usesListener2OnResult2() throws Exception { + final List addresses = + ImmutableList.of(new EquivalentAddressGroup(new SocketAddress() {})); + final class FakeNameResolver extends NameResolver { + Listener2 listener; + private final SynchronizationContext syncContext; + + FakeNameResolver(Args args) { + this.syncContext = args.getSynchronizationContext(); + } + + @Override + public String getServiceAuthority() { + return "also fake"; + } + + @Override + public void start(Listener2 listener) { + this.listener = listener; + syncContext.execute(() -> + listener.onResult2( + ResolutionResult.newBuilder() + .setAddresses(addresses) + .setServiceConfig( + ConfigOrError.fromError( + Status.INTERNAL.withDescription("kaboom is invalid"))) + .build())); + } + + @Override + public void shutdown() {} + } + + final class FakeNameResolverFactory2 extends NameResolver.Factory { + FakeNameResolver resolver; + ManagedChannelImpl managedChannel; + SynchronizationContext syncContext; + + @Nullable + @Override + public NameResolver newNameResolver(URI targetUri, NameResolver.Args args) { + syncContext = args.getSynchronizationContext(); + return (resolver = new FakeNameResolver(args)); + } + + @Override + public String getDefaultScheme() { + return "fake"; + } + } + + FakeNameResolverFactory2 factory = new FakeNameResolverFactory2(); + + ManagedChannelImplBuilder customBuilder = new ManagedChannelImplBuilder(TARGET, + new ClientTransportFactoryBuilder() { + @Override + public ClientTransportFactory buildClientTransportFactory() { + return mockTransportFactory; + } + }, + null); + when(mockTransportFactory.getSupportedSocketAddressTypes()).thenReturn(Collections.singleton( + InetSocketAddress.class)); + customBuilder.executorPool = executorPool; + customBuilder.channelz = channelz; + ManagedChannel mychannel = customBuilder.nameResolverFactory(factory).build(); + + ClientCall call1 = + mychannel.newCall(TestMethodDescriptors.voidMethod(), CallOptions.DEFAULT); + ListenableFuture future1 = ClientCalls.futureUnaryCall(call1, null); + executor.runDueTasks(); + try { + future1.get(1, TimeUnit.SECONDS); + Assert.fail(); + } catch (ExecutionException e) { + assertThat(Throwables.getStackTraceAsString(e.getCause())).contains("kaboom"); + } + + // ok the service config is bad, let's fix it. + Map rawServiceConfig = + parseConfig("{\"loadBalancingConfig\": [{\"round_robin\": {}}]}"); + Object fakeLbConfig = new Object(); + PolicySelection lbConfigs = + new PolicySelection( + mockLoadBalancerProvider, fakeLbConfig); + mockLoadBalancerProvider.parseLoadBalancingPolicyConfig(rawServiceConfig); + ManagedChannelServiceConfig managedChannelServiceConfig = + createManagedChannelServiceConfig(rawServiceConfig, lbConfigs); + factory.syncContext.execute(() -> + factory.resolver.listener.onResult2( + ResolutionResult.newBuilder() + .setAddresses(addresses) + .setServiceConfig(ConfigOrError.fromConfig(managedChannelServiceConfig)) + .build())); + + ClientCall call2 = mychannel.newCall( + TestMethodDescriptors.voidMethod(), + CallOptions.DEFAULT.withDeadlineAfter(5, TimeUnit.SECONDS)); + ListenableFuture future2 = ClientCalls.futureUnaryCall(call2, null); + + timer.forwardTime(1234, TimeUnit.SECONDS); + + executor.runDueTasks(); + try { + future2.get(); + Assert.fail(); + } catch (ExecutionException e) { + assertThat(Throwables.getStackTraceAsString(e.getCause())).contains("deadline"); + } + + mychannel.shutdownNow(); + } + @Test public void nameResolverArgsPropagation() { final AtomicReference capturedArgs = new AtomicReference<>(); @@ -4518,7 +4810,7 @@ public NameResolver newNameResolver(final URI targetUri, NameResolver.Args args) } assertEquals(DEFAULT_PORT, args.getDefaultPort()); FakeNameResolverFactory.FakeNameResolver resolver = - new FakeNameResolverFactory.FakeNameResolver(targetUri, error); + new FakeNameResolverFactory.FakeNameResolver(targetUri, error, args); resolvers.add(resolver); return resolver; } @@ -4546,14 +4838,16 @@ void allResolved() { final class FakeNameResolver extends NameResolver { final URI targetUri; + final SynchronizationContext syncContext; Listener2 listener; boolean shutdown; int refreshCalled; Status error; - FakeNameResolver(URI targetUri, Status error) { + FakeNameResolver(URI targetUri, Status error, Args args) { this.targetUri = targetUri; this.error = error; + syncContext = args.getSynchronizationContext(); } @Override public String getServiceAuthority() { @@ -4585,7 +4879,7 @@ void resolved() { if (configOrError != null) { builder.setServiceConfig(configOrError); } - listener.onResult(builder.build()); + syncContext.execute(() -> listener.onResult(builder.build())); } @Override public void shutdown() { diff --git a/core/src/test/java/io/grpc/internal/RetryingNameResolverTest.java b/core/src/test/java/io/grpc/internal/RetryingNameResolverTest.java index 8801f540394..6347416f0ca 100644 --- a/core/src/test/java/io/grpc/internal/RetryingNameResolverTest.java +++ b/core/src/test/java/io/grpc/internal/RetryingNameResolverTest.java @@ -21,6 +21,7 @@ import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import io.grpc.NameResolver; import io.grpc.NameResolver.Listener2; @@ -79,7 +80,7 @@ public void startAndShutdown() { // Make sure the ResolutionResultListener callback is added to the ResolutionResult attributes, // and the retry scheduler is reset since the name resolution was successful. @Test - public void onResult_sucess() { + public void onResult_success() { retryingNameResolver.start(mockListener); verify(mockNameResolver).start(listenerCaptor.capture()); @@ -94,6 +95,18 @@ public void onResult_sucess() { verify(mockRetryScheduler).reset(); } + @Test + public void onResult2_sucesss() { + when(mockListener.onResult2(isA(ResolutionResult.class))).thenReturn(Status.OK); + retryingNameResolver.start(mockListener); + verify(mockNameResolver).start(listenerCaptor.capture()); + + assertThat(listenerCaptor.getValue().onResult2(ResolutionResult.newBuilder().build())) + .isEqualTo(Status.OK); + + verify(mockRetryScheduler).reset(); + } + // Make sure the ResolutionResultListener callback is added to the ResolutionResult attributes, // and that a retry gets scheduled when the resolution results are rejected. @Test @@ -112,6 +125,19 @@ public void onResult_failure() { verify(mockRetryScheduler).schedule(isA(Runnable.class)); } + // Make sure that a retry gets scheduled when the resolution results are rejected. + @Test + public void onResult2_failure() { + when(mockListener.onResult2(isA(ResolutionResult.class))).thenReturn(Status.UNAVAILABLE); + retryingNameResolver.start(mockListener); + verify(mockNameResolver).start(listenerCaptor.capture()); + + assertThat(listenerCaptor.getValue().onResult2(ResolutionResult.newBuilder().build())) + .isEqualTo(Status.UNAVAILABLE); + + verify(mockRetryScheduler).schedule(isA(Runnable.class)); + } + // Wrapping a NameResolver more than once is a misconfiguration. @Test public void onResult_failure_doubleWrapped() { diff --git a/grpclb/src/test/java/io/grpc/grpclb/GrpclbNameResolverTest.java b/grpclb/src/test/java/io/grpc/grpclb/GrpclbNameResolverTest.java index 3e2cf22605f..c195a78e6f4 100644 --- a/grpclb/src/test/java/io/grpc/grpclb/GrpclbNameResolverTest.java +++ b/grpclb/src/test/java/io/grpc/grpclb/GrpclbNameResolverTest.java @@ -152,7 +152,7 @@ public List resolveSrv(String host) throws Exception { resolver.start(mockListener); assertThat(fakeClock.runDueTasks()).isEqualTo(1); - verify(mockListener).onResult(resultCaptor.capture()); + verify(mockListener).onResult2(resultCaptor.capture()); ResolutionResult result = resultCaptor.getValue(); assertThat(result.getAddresses()).isEmpty(); assertThat(result.getAttributes()).isEqualTo(Attributes.EMPTY); @@ -192,7 +192,7 @@ public ConfigOrError answer(InvocationOnMock invocation) { resolver.start(mockListener); assertThat(fakeClock.runDueTasks()).isEqualTo(1); - verify(mockListener).onResult(resultCaptor.capture()); + verify(mockListener).onResult2(resultCaptor.capture()); ResolutionResult result = resultCaptor.getValue(); InetSocketAddress resolvedBackendAddr = (InetSocketAddress) Iterables.getOnlyElement( @@ -225,7 +225,7 @@ public void resolve_nullResourceResolver() throws Exception { resolver.start(mockListener); assertThat(fakeClock.runDueTasks()).isEqualTo(1); - verify(mockListener).onResult(resultCaptor.capture()); + verify(mockListener).onResult2(resultCaptor.capture()); ResolutionResult result = resultCaptor.getValue(); assertThat(result.getAddresses()) .containsExactly( @@ -272,7 +272,7 @@ public void resolve_addressFailure_stillLookUpBalancersAndServiceConfig() throws resolver.start(mockListener); assertThat(fakeClock.runDueTasks()).isEqualTo(1); - verify(mockListener).onResult(resultCaptor.capture()); + verify(mockListener).onResult2(resultCaptor.capture()); ResolutionResult result = resultCaptor.getValue(); assertThat(result.getAddresses()).isEmpty(); EquivalentAddressGroup resolvedBalancerAddr = @@ -306,7 +306,7 @@ public void resolveAll_balancerLookupFails_stillLookUpServiceConfig() throws Exc resolver.start(mockListener); assertThat(fakeClock.runDueTasks()).isEqualTo(1); - verify(mockListener).onResult(resultCaptor.capture()); + verify(mockListener).onResult2(resultCaptor.capture()); ResolutionResult result = resultCaptor.getValue(); InetSocketAddress resolvedBackendAddr = From b8e3ae9a4b486c6dfc322cf533178a1ace853985 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Tue, 18 Jul 2023 10:22:28 -0700 Subject: [PATCH 011/103] android-interop-testing: Enable -Xlint:deprecation --- android-interop-testing/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/android-interop-testing/build.gradle b/android-interop-testing/build.gradle index 4d96adbd0dc..22aa5f2288d 100644 --- a/android-interop-testing/build.gradle +++ b/android-interop-testing/build.gradle @@ -116,7 +116,6 @@ import net.ltgt.gradle.errorprone.CheckSeverity tasks.withType(JavaCompile).configureEach { options.compilerArgs += [ "-Xlint:-cast", - "-Xlint:-deprecation", // https://github.com/grpc/grpc-java/issues/10298 ] appendToProperty(it.options.errorprone.excludedPaths, ".*/R.java", "|") appendToProperty( From 780e4ba086265a33ed67c6de8b7f3d60c86f15a6 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Tue, 2 Apr 2024 10:42:25 -0700 Subject: [PATCH 012/103] api: Move ClientStreamTracerTest from core to api It uses nothing from core and tests an api class. --- {core => api}/src/test/java/io/grpc/ClientStreamTracerTest.java | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {core => api}/src/test/java/io/grpc/ClientStreamTracerTest.java (100%) diff --git a/core/src/test/java/io/grpc/ClientStreamTracerTest.java b/api/src/test/java/io/grpc/ClientStreamTracerTest.java similarity index 100% rename from core/src/test/java/io/grpc/ClientStreamTracerTest.java rename to api/src/test/java/io/grpc/ClientStreamTracerTest.java From 1f9d5022618c8b63babfd352dbb469f5cbf7ac54 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Wed, 29 May 2024 15:48:12 -0700 Subject: [PATCH 013/103] interop-testing: Remove unused implementation deps googleapis and rls can still be used at runtime. --- interop-testing/build.gradle | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/interop-testing/build.gradle b/interop-testing/build.gradle index d6155761c04..a19efb00155 100644 --- a/interop-testing/build.gradle +++ b/interop-testing/build.gradle @@ -13,12 +13,9 @@ dependencies { implementation project(path: ':grpc-alts', configuration: 'shadow'), project(':grpc-auth'), project(':grpc-census'), - project(':grpc-core'), project(':grpc-gcp-csm-observability'), - project(':grpc-googleapis'), project(':grpc-netty'), project(':grpc-okhttp'), - project(':grpc-rls'), project(':grpc-services'), project(':grpc-testing'), project(':grpc-protobuf-lite'), @@ -45,6 +42,7 @@ dependencies { libraries.netty.tcnative, libraries.netty.tcnative.classes, libraries.opentelemetry.exporter.prometheus, // For xds interop client + project(':grpc-googleapis'), project(':grpc-grpclb'), project(':grpc-rls') testImplementation testFixtures(project(':grpc-api')), From 00136096ed2180abc55ad2742d16a1df1a45fb3a Mon Sep 17 00:00:00 2001 From: Kurt Alfred Kluever Date: Fri, 2 Aug 2024 07:42:09 -0700 Subject: [PATCH 014/103] Migrate from the deprecated Charsets constants (in Guava) to the StandardCharsets constants (in the JDK). cl/658546708 --- .../Http2ClientStreamTransportState.java | 6 ++--- .../io/grpc/internal/MessageDeframerTest.java | 22 +++++++++---------- .../ObservabilityConfigImpl.java | 4 ++-- .../ObservabilityConfigImplTest.java | 5 +++-- .../GoogleCloudToProdNameResolver.java | 4 ++-- .../java/io/grpc/okhttp/AsyncSinkTest.java | 14 ++++++------ .../grpc/protobuf/services/BinlogHelper.java | 4 ++-- .../services/ChannelzProtoUtilTest.java | 14 ++++++------ .../io/grpc/util/CertificateUtilsTest.java | 6 ++--- 9 files changed, 40 insertions(+), 39 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/Http2ClientStreamTransportState.java b/core/src/main/java/io/grpc/internal/Http2ClientStreamTransportState.java index cc36ed3c0bb..e92bb7a4af1 100644 --- a/core/src/main/java/io/grpc/internal/Http2ClientStreamTransportState.java +++ b/core/src/main/java/io/grpc/internal/Http2ClientStreamTransportState.java @@ -16,7 +16,6 @@ package io.grpc.internal; -import com.google.common.base.Charsets; import com.google.common.base.Preconditions; import io.grpc.CallOptions; import io.grpc.InternalMetadata; @@ -24,6 +23,7 @@ import io.grpc.Metadata; import io.grpc.Status; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import javax.annotation.Nullable; /** @@ -62,7 +62,7 @@ public Integer parseAsciiString(byte[] serialized) { /** When non-{@code null}, {@link #transportErrorMetadata} must also be non-{@code null}. */ private Status transportError; private Metadata transportErrorMetadata; - private Charset errorCharset = Charsets.UTF_8; + private Charset errorCharset = StandardCharsets.UTF_8; private boolean headersReceived; protected Http2ClientStreamTransportState( @@ -241,7 +241,7 @@ private static Charset extractCharset(Metadata headers) { // Ignore and assume UTF-8 } } - return Charsets.UTF_8; + return StandardCharsets.UTF_8; } /** diff --git a/core/src/test/java/io/grpc/internal/MessageDeframerTest.java b/core/src/test/java/io/grpc/internal/MessageDeframerTest.java index 98ed0691458..1ec1ccb2082 100644 --- a/core/src/test/java/io/grpc/internal/MessageDeframerTest.java +++ b/core/src/test/java/io/grpc/internal/MessageDeframerTest.java @@ -31,7 +31,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import com.google.common.base.Charsets; import com.google.common.io.ByteStreams; import com.google.common.primitives.Bytes; import io.grpc.Codec; @@ -46,6 +45,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -347,7 +347,7 @@ public static class SizeEnforcingInputStreamTests { @Test public void sizeEnforcingInputStream_readByteBelowLimit() throws IOException { - ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(Charsets.UTF_8)); + ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(StandardCharsets.UTF_8)); SizeEnforcingInputStream stream = new MessageDeframer.SizeEnforcingInputStream(in, 4, statsTraceCtx); @@ -360,7 +360,7 @@ public void sizeEnforcingInputStream_readByteBelowLimit() throws IOException { @Test public void sizeEnforcingInputStream_readByteAtLimit() throws IOException { - ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(Charsets.UTF_8)); + ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(StandardCharsets.UTF_8)); SizeEnforcingInputStream stream = new MessageDeframer.SizeEnforcingInputStream(in, 3, statsTraceCtx); @@ -373,7 +373,7 @@ public void sizeEnforcingInputStream_readByteAtLimit() throws IOException { @Test public void sizeEnforcingInputStream_readByteAboveLimit() throws IOException { - ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(Charsets.UTF_8)); + ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(StandardCharsets.UTF_8)); SizeEnforcingInputStream stream = new MessageDeframer.SizeEnforcingInputStream(in, 2, statsTraceCtx); @@ -390,7 +390,7 @@ public void sizeEnforcingInputStream_readByteAboveLimit() throws IOException { @Test public void sizeEnforcingInputStream_readBelowLimit() throws IOException { - ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(Charsets.UTF_8)); + ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(StandardCharsets.UTF_8)); SizeEnforcingInputStream stream = new MessageDeframer.SizeEnforcingInputStream(in, 4, statsTraceCtx); byte[] buf = new byte[10]; @@ -404,7 +404,7 @@ public void sizeEnforcingInputStream_readBelowLimit() throws IOException { @Test public void sizeEnforcingInputStream_readAtLimit() throws IOException { - ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(Charsets.UTF_8)); + ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(StandardCharsets.UTF_8)); SizeEnforcingInputStream stream = new MessageDeframer.SizeEnforcingInputStream(in, 3, statsTraceCtx); byte[] buf = new byte[10]; @@ -418,7 +418,7 @@ public void sizeEnforcingInputStream_readAtLimit() throws IOException { @Test public void sizeEnforcingInputStream_readAboveLimit() throws IOException { - ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(Charsets.UTF_8)); + ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(StandardCharsets.UTF_8)); SizeEnforcingInputStream stream = new MessageDeframer.SizeEnforcingInputStream(in, 2, statsTraceCtx); byte[] buf = new byte[10]; @@ -435,7 +435,7 @@ public void sizeEnforcingInputStream_readAboveLimit() throws IOException { @Test public void sizeEnforcingInputStream_skipBelowLimit() throws IOException { - ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(Charsets.UTF_8)); + ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(StandardCharsets.UTF_8)); SizeEnforcingInputStream stream = new MessageDeframer.SizeEnforcingInputStream(in, 4, statsTraceCtx); @@ -449,7 +449,7 @@ public void sizeEnforcingInputStream_skipBelowLimit() throws IOException { @Test public void sizeEnforcingInputStream_skipAtLimit() throws IOException { - ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(Charsets.UTF_8)); + ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(StandardCharsets.UTF_8)); SizeEnforcingInputStream stream = new MessageDeframer.SizeEnforcingInputStream(in, 3, statsTraceCtx); @@ -462,7 +462,7 @@ public void sizeEnforcingInputStream_skipAtLimit() throws IOException { @Test public void sizeEnforcingInputStream_skipAboveLimit() throws IOException { - ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(Charsets.UTF_8)); + ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(StandardCharsets.UTF_8)); SizeEnforcingInputStream stream = new MessageDeframer.SizeEnforcingInputStream(in, 2, statsTraceCtx); @@ -478,7 +478,7 @@ public void sizeEnforcingInputStream_skipAboveLimit() throws IOException { @Test public void sizeEnforcingInputStream_markReset() throws IOException { - ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(Charsets.UTF_8)); + ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(StandardCharsets.UTF_8)); SizeEnforcingInputStream stream = new MessageDeframer.SizeEnforcingInputStream(in, 3, statsTraceCtx); // stream currently looks like: |foo diff --git a/gcp-observability/src/main/java/io/grpc/gcp/observability/ObservabilityConfigImpl.java b/gcp-observability/src/main/java/io/grpc/gcp/observability/ObservabilityConfigImpl.java index 2b0a44473d0..ae74bf10c43 100644 --- a/gcp-observability/src/main/java/io/grpc/gcp/observability/ObservabilityConfigImpl.java +++ b/gcp-observability/src/main/java/io/grpc/gcp/observability/ObservabilityConfigImpl.java @@ -19,7 +19,6 @@ import static com.google.common.base.Preconditions.checkArgument; import com.google.cloud.ServiceOptions; -import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -28,6 +27,7 @@ import io.opencensus.trace.Sampler; import io.opencensus.trace.samplers.Samplers; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Collections; @@ -75,7 +75,7 @@ static ObservabilityConfigImpl getInstance() throws IOException { void parseFile(String configFile) throws IOException { String configFileContent = - new String(Files.readAllBytes(Paths.get(configFile)), Charsets.UTF_8); + new String(Files.readAllBytes(Paths.get(configFile)), StandardCharsets.UTF_8); checkArgument(!configFileContent.isEmpty(), CONFIG_FILE_ENV_VAR_NAME + " is empty!"); parse(configFileContent); } diff --git a/gcp-observability/src/test/java/io/grpc/gcp/observability/ObservabilityConfigImplTest.java b/gcp-observability/src/test/java/io/grpc/gcp/observability/ObservabilityConfigImplTest.java index d6f23fbcc9a..a9e0d6e2235 100644 --- a/gcp-observability/src/test/java/io/grpc/gcp/observability/ObservabilityConfigImplTest.java +++ b/gcp-observability/src/test/java/io/grpc/gcp/observability/ObservabilityConfigImplTest.java @@ -21,12 +21,12 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import com.google.common.base.Charsets; import io.grpc.gcp.observability.ObservabilityConfig.LogFilter; import io.opencensus.trace.Sampler; import io.opencensus.trace.samplers.Samplers; import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Collections; @@ -401,7 +401,8 @@ public void badProbabilisticSampler_error() throws IOException { public void configFileLogFilters() throws Exception { File configFile = tempFolder.newFile(); Files.write( - Paths.get(configFile.getAbsolutePath()), CLIENT_LOG_FILTERS.getBytes(Charsets.US_ASCII)); + Paths.get(configFile.getAbsolutePath()), + CLIENT_LOG_FILTERS.getBytes(StandardCharsets.US_ASCII)); observabilityConfig.parseFile(configFile.getAbsolutePath()); assertTrue(observabilityConfig.isEnableCloudLogging()); assertThat(observabilityConfig.getProjectId()).isEqualTo("grpc-testing"); diff --git a/googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdNameResolver.java b/googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdNameResolver.java index 64c2e0f9c86..ebc7dd05ea4 100644 --- a/googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdNameResolver.java +++ b/googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdNameResolver.java @@ -19,7 +19,6 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Charsets; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; @@ -41,6 +40,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.Random; import java.util.concurrent.Executor; @@ -263,7 +263,7 @@ private String queryZoneMetadata(String url) throws IOException { if (con.getResponseCode() != 200) { return ""; } - try (Reader reader = new InputStreamReader(con.getInputStream(), Charsets.UTF_8)) { + try (Reader reader = new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8)) { respBody = CharStreams.toString(reader); } } finally { diff --git a/okhttp/src/test/java/io/grpc/okhttp/AsyncSinkTest.java b/okhttp/src/test/java/io/grpc/okhttp/AsyncSinkTest.java index 46011588b16..478e18d0a2b 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/AsyncSinkTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/AsyncSinkTest.java @@ -30,11 +30,11 @@ import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; -import com.google.common.base.Charsets; import io.grpc.internal.SerializingExecutor; import io.grpc.okhttp.ExceptionHandlingFrameWriter.TransportExceptionHandler; import java.io.IOException; import java.net.Socket; +import java.nio.charset.StandardCharsets; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; @@ -73,8 +73,8 @@ public void noCoalesceRequired() throws IOException { @Test public void flushCoalescing_shouldNotMergeTwoDistinctFlushes() throws IOException { - byte[] firstData = "a string".getBytes(Charsets.UTF_8); - byte[] secondData = "a longer string".getBytes(Charsets.UTF_8); + byte[] firstData = "a string".getBytes(StandardCharsets.UTF_8); + byte[] secondData = "a longer string".getBytes(StandardCharsets.UTF_8); sink.becomeConnected(mockedSink, socket); Buffer buffer = new Buffer(); @@ -95,8 +95,8 @@ public void flushCoalescing_shouldNotMergeTwoDistinctFlushes() throws IOExceptio @Test public void flushCoalescing_shouldMergeTwoQueuedFlushesAndWrites() throws IOException { - byte[] firstData = "a string".getBytes(Charsets.UTF_8); - byte[] secondData = "a longer string".getBytes(Charsets.UTF_8); + byte[] firstData = "a string".getBytes(StandardCharsets.UTF_8); + byte[] secondData = "a longer string".getBytes(StandardCharsets.UTF_8); Buffer buffer = new Buffer().write(firstData); sink.becomeConnected(mockedSink, socket); sink.write(buffer, buffer.size()); @@ -115,8 +115,8 @@ public void flushCoalescing_shouldMergeTwoQueuedFlushesAndWrites() throws IOExce @Test public void flushCoalescing_shouldMergeWrites() throws IOException { - byte[] firstData = "a string".getBytes(Charsets.UTF_8); - byte[] secondData = "a longer string".getBytes(Charsets.UTF_8); + byte[] firstData = "a string".getBytes(StandardCharsets.UTF_8); + byte[] secondData = "a longer string".getBytes(StandardCharsets.UTF_8); Buffer buffer = new Buffer(); sink.becomeConnected(mockedSink, socket); sink.write(buffer.write(firstData), buffer.size()); diff --git a/services/src/main/java/io/grpc/protobuf/services/BinlogHelper.java b/services/src/main/java/io/grpc/protobuf/services/BinlogHelper.java index 845ec1036ad..e810c983beb 100644 --- a/services/src/main/java/io/grpc/protobuf/services/BinlogHelper.java +++ b/services/src/main/java/io/grpc/protobuf/services/BinlogHelper.java @@ -22,7 +22,6 @@ import static io.grpc.protobuf.services.BinaryLogProvider.BYTEARRAY_MARSHALLER; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Charsets; import com.google.common.base.Preconditions; import com.google.common.base.Splitter; import com.google.protobuf.ByteString; @@ -59,6 +58,7 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -841,7 +841,7 @@ static MaybeTruncated createMetadataProto if (serialized != null) { int curBytes = 0; for (int i = 0; i < serialized.length; i += 2) { - String key = new String(serialized[i], Charsets.UTF_8); + String key = new String(serialized[i], StandardCharsets.UTF_8); byte[] value = serialized[i + 1]; if (NEVER_INCLUDED_METADATA.contains(key)) { continue; diff --git a/services/src/test/java/io/grpc/protobuf/services/ChannelzProtoUtilTest.java b/services/src/test/java/io/grpc/protobuf/services/ChannelzProtoUtilTest.java index 4098885fd0d..0d2e6063d5e 100644 --- a/services/src/test/java/io/grpc/protobuf/services/ChannelzProtoUtilTest.java +++ b/services/src/test/java/io/grpc/protobuf/services/ChannelzProtoUtilTest.java @@ -22,7 +22,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.protobuf.Any; @@ -82,6 +81,7 @@ import java.net.Inet4Address; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; import java.security.cert.Certificate; import java.util.Arrays; import java.util.Collections; @@ -437,8 +437,8 @@ public void toSocketData() throws Exception { public void socketSecurityTls() throws Exception { Certificate local = mock(Certificate.class); Certificate remote = mock(Certificate.class); - when(local.getEncoded()).thenReturn("localcert".getBytes(Charsets.UTF_8)); - when(remote.getEncoded()).thenReturn("remotecert".getBytes(Charsets.UTF_8)); + when(local.getEncoded()).thenReturn("localcert".getBytes(StandardCharsets.UTF_8)); + when(remote.getEncoded()).thenReturn("remotecert".getBytes(StandardCharsets.UTF_8)); socket.security = new InternalChannelz.Security( new InternalChannelz.Tls("TLS_NULL_WITH_NULL_NULL", local, remote)); @@ -446,8 +446,8 @@ public void socketSecurityTls() throws Exception { Security.newBuilder().setTls( Tls.newBuilder() .setStandardName("TLS_NULL_WITH_NULL_NULL") - .setLocalCertificate(ByteString.copyFrom("localcert", Charsets.UTF_8)) - .setRemoteCertificate(ByteString.copyFrom("remotecert", Charsets.UTF_8))) + .setLocalCertificate(ByteString.copyFrom("localcert", StandardCharsets.UTF_8)) + .setRemoteCertificate(ByteString.copyFrom("remotecert", StandardCharsets.UTF_8))) .build(), ChannelzProtoUtil.toSocket(socket).getSecurity()); @@ -457,7 +457,7 @@ public void socketSecurityTls() throws Exception { Security.newBuilder().setTls( Tls.newBuilder() .setStandardName("TLS_NULL_WITH_NULL_NULL") - .setRemoteCertificate(ByteString.copyFrom("remotecert", Charsets.UTF_8))) + .setRemoteCertificate(ByteString.copyFrom("remotecert", StandardCharsets.UTF_8))) .build(), ChannelzProtoUtil.toSocket(socket).getSecurity()); @@ -467,7 +467,7 @@ public void socketSecurityTls() throws Exception { Security.newBuilder().setTls( Tls.newBuilder() .setStandardName("TLS_NULL_WITH_NULL_NULL") - .setLocalCertificate(ByteString.copyFrom("localcert", Charsets.UTF_8))) + .setLocalCertificate(ByteString.copyFrom("localcert", StandardCharsets.UTF_8))) .build(), ChannelzProtoUtil.toSocket(socket).getSecurity()); } diff --git a/util/src/test/java/io/grpc/util/CertificateUtilsTest.java b/util/src/test/java/io/grpc/util/CertificateUtilsTest.java index aef99c0f378..dbddd35bca3 100644 --- a/util/src/test/java/io/grpc/util/CertificateUtilsTest.java +++ b/util/src/test/java/io/grpc/util/CertificateUtilsTest.java @@ -18,12 +18,12 @@ import static com.google.common.truth.Truth.assertThat; -import com.google.common.base.Charsets; import io.grpc.internal.testing.TestUtils; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; +import java.nio.charset.StandardCharsets; import java.security.PrivateKey; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; @@ -80,7 +80,7 @@ public void readCaPemFile() throws CertificateException, IOException { @Test public void readBadFormatKeyFile() throws Exception { - InputStream in = new ByteArrayInputStream(BAD_PEM_FORMAT.getBytes(Charsets.UTF_8)); + InputStream in = new ByteArrayInputStream(BAD_PEM_FORMAT.getBytes(StandardCharsets.UTF_8)); try { CertificateUtils.getPrivateKey(in); Assert.fail("no exception thrown"); @@ -92,7 +92,7 @@ public void readBadFormatKeyFile() throws Exception { @Test public void readBadContentKeyFile() { - InputStream in = new ByteArrayInputStream(BAD_PEM_CONTENT.getBytes(Charsets.UTF_8)); + InputStream in = new ByteArrayInputStream(BAD_PEM_CONTENT.getBytes(StandardCharsets.UTF_8)); try { CertificateUtils.getPrivateKey(in); Assert.fail("no exception thrown"); From c29763d88671247dee608d0108de658b893c58f7 Mon Sep 17 00:00:00 2001 From: Sergii Tkachenko Date: Fri, 2 Aug 2024 13:35:29 -0400 Subject: [PATCH 015/103] xds: Import RLQS protos (#11418) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Imports the protos of Rate Limiting Quota Service (RLQS) and Rate Limit Quota HTTP Filter. Note: the list below only shows the new top-level protos, and excludes their direct and transitional dependencies (those from import statements). #### RLQS Imports - Service — envoy/service/rate_limit_quota/v3/rlqs.proto (Service): 7b8a304 - HTTP Filter — envoy/extensions/filters/http/rate_limit_quota/v3/rate_limit_quota.proto: 49c77c4 #### CEL Imports - Initial third-party repo setup: 99a64bd - Parsed CEL Expression: cel/expr/syntax.proto: 99a64bd - Parsed and type-checked CEL Expression: cel/expr/checked.proto: 99a64bd #### Required typed_config extensions ##### `bucket_matchers` predicate input - `HttpAttributesCelMatchInput` — xds/type/matcher/v3/http_inputs.proto: 54924e0 - `HttpRequestHeaderMatchInput` — envoy/type/matcher/v3/http_inputs.proto: 49c77c4 ##### `bucket_matchers` predicate custom_match - `CelMatcher` — xds/type/matcher/v3/cel.proto: 54924e0 --- xds/build.gradle | 3 + .../v3/RateLimitQuotaServiceGrpc.java | 303 +++++++++++++ xds/third_party/cel-spec/LICENSE | 202 +++++++++ xds/third_party/cel-spec/import.sh | 59 +++ .../src/main/proto/cel/expr/checked.proto | 344 ++++++++++++++ .../src/main/proto/cel/expr/syntax.proto | 393 ++++++++++++++++ xds/third_party/envoy/import.sh | 7 + .../v3/rate_limit_quota.proto | 423 ++++++++++++++++++ .../service/rate_limit_quota/v3/rlqs.proto | 258 +++++++++++ .../envoy/type/matcher/v3/http_inputs.proto | 71 +++ .../proto/envoy/type/v3/http_status.proto | 143 ++++++ .../envoy/type/v3/ratelimit_strategy.proto | 79 ++++ .../proto/envoy/type/v3/ratelimit_unit.proto | 37 ++ .../proto/envoy/type/v3/token_bucket.proto | 39 ++ xds/third_party/xds/import.sh | 3 + .../main/proto/xds/type/matcher/v3/cel.proto | 42 ++ .../xds/type/matcher/v3/http_inputs.proto | 27 ++ .../xds/src/main/proto/xds/type/v3/cel.proto | 70 +++ 18 files changed, 2503 insertions(+) create mode 100644 xds/src/generated/thirdparty/grpc/io/envoyproxy/envoy/service/rate_limit_quota/v3/RateLimitQuotaServiceGrpc.java create mode 100644 xds/third_party/cel-spec/LICENSE create mode 100755 xds/third_party/cel-spec/import.sh create mode 100644 xds/third_party/cel-spec/src/main/proto/cel/expr/checked.proto create mode 100644 xds/third_party/cel-spec/src/main/proto/cel/expr/syntax.proto create mode 100644 xds/third_party/envoy/src/main/proto/envoy/extensions/filters/http/rate_limit_quota/v3/rate_limit_quota.proto create mode 100644 xds/third_party/envoy/src/main/proto/envoy/service/rate_limit_quota/v3/rlqs.proto create mode 100644 xds/third_party/envoy/src/main/proto/envoy/type/matcher/v3/http_inputs.proto create mode 100644 xds/third_party/envoy/src/main/proto/envoy/type/v3/http_status.proto create mode 100644 xds/third_party/envoy/src/main/proto/envoy/type/v3/ratelimit_strategy.proto create mode 100644 xds/third_party/envoy/src/main/proto/envoy/type/v3/ratelimit_unit.proto create mode 100644 xds/third_party/envoy/src/main/proto/envoy/type/v3/token_bucket.proto create mode 100644 xds/third_party/xds/src/main/proto/xds/type/matcher/v3/cel.proto create mode 100644 xds/third_party/xds/src/main/proto/xds/type/matcher/v3/http_inputs.proto create mode 100644 xds/third_party/xds/src/main/proto/xds/type/v3/cel.proto diff --git a/xds/build.gradle b/xds/build.gradle index cb3046db183..a1d5aa753cb 100644 --- a/xds/build.gradle +++ b/xds/build.gradle @@ -20,6 +20,7 @@ sourceSets { srcDir 'third_party/envoy/src/main/proto' srcDir 'third_party/protoc-gen-validate/src/main/proto' srcDir 'third_party/xds/src/main/proto' + srcDir 'third_party/cel-spec/src/main/proto' srcDir 'third_party/googleapis/src/main/proto' srcDir 'third_party/istio/src/main/proto' } @@ -185,6 +186,7 @@ tasks.named("shadowJar").configure { relocate 'com.google.api.expr', "${prefixName}.shaded.com.google.api.expr" relocate 'com.google.security', "${prefixName}.shaded.com.google.security" // TODO: missing java_package option in .proto + relocate 'dev.cel.expr', "${prefixName}.shaded.dev.cel.expr" relocate 'envoy.annotations', "${prefixName}.shaded.envoy.annotations" relocate 'io.envoyproxy', "${prefixName}.shaded.io.envoyproxy" relocate 'io.grpc.netty', 'io.grpc.netty.shaded.io.grpc.netty' @@ -212,6 +214,7 @@ tasks.named("jacocoTestReport").configure { '**/com/github/xds/**', '**/com/google/api/expr/**', '**/com/google/security/**', + '**/cel/expr/**', '**/envoy/annotations/**', '**/io/envoyproxy/**', '**/udpa/annotations/**', diff --git a/xds/src/generated/thirdparty/grpc/io/envoyproxy/envoy/service/rate_limit_quota/v3/RateLimitQuotaServiceGrpc.java b/xds/src/generated/thirdparty/grpc/io/envoyproxy/envoy/service/rate_limit_quota/v3/RateLimitQuotaServiceGrpc.java new file mode 100644 index 00000000000..2cbb7536d4c --- /dev/null +++ b/xds/src/generated/thirdparty/grpc/io/envoyproxy/envoy/service/rate_limit_quota/v3/RateLimitQuotaServiceGrpc.java @@ -0,0 +1,303 @@ +package io.envoyproxy.envoy.service.rate_limit_quota.v3; + +import static io.grpc.MethodDescriptor.generateFullMethodName; + +/** + *
+ * Defines the Rate Limit Quota Service (RLQS).
+ * 
+ */ +@javax.annotation.Generated( + value = "by gRPC proto compiler", + comments = "Source: envoy/service/rate_limit_quota/v3/rlqs.proto") +@io.grpc.stub.annotations.GrpcGenerated +public final class RateLimitQuotaServiceGrpc { + + private RateLimitQuotaServiceGrpc() {} + + public static final java.lang.String SERVICE_NAME = "envoy.service.rate_limit_quota.v3.RateLimitQuotaService"; + + // Static method descriptors that strictly reflect the proto. + private static volatile io.grpc.MethodDescriptor getStreamRateLimitQuotasMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "StreamRateLimitQuotas", + requestType = io.envoyproxy.envoy.service.rate_limit_quota.v3.RateLimitQuotaUsageReports.class, + responseType = io.envoyproxy.envoy.service.rate_limit_quota.v3.RateLimitQuotaResponse.class, + methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING) + public static io.grpc.MethodDescriptor getStreamRateLimitQuotasMethod() { + io.grpc.MethodDescriptor getStreamRateLimitQuotasMethod; + if ((getStreamRateLimitQuotasMethod = RateLimitQuotaServiceGrpc.getStreamRateLimitQuotasMethod) == null) { + synchronized (RateLimitQuotaServiceGrpc.class) { + if ((getStreamRateLimitQuotasMethod = RateLimitQuotaServiceGrpc.getStreamRateLimitQuotasMethod) == null) { + RateLimitQuotaServiceGrpc.getStreamRateLimitQuotasMethod = getStreamRateLimitQuotasMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "StreamRateLimitQuotas")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + io.envoyproxy.envoy.service.rate_limit_quota.v3.RateLimitQuotaUsageReports.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + io.envoyproxy.envoy.service.rate_limit_quota.v3.RateLimitQuotaResponse.getDefaultInstance())) + .setSchemaDescriptor(new RateLimitQuotaServiceMethodDescriptorSupplier("StreamRateLimitQuotas")) + .build(); + } + } + } + return getStreamRateLimitQuotasMethod; + } + + /** + * Creates a new async stub that supports all call types for the service + */ + public static RateLimitQuotaServiceStub newStub(io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @java.lang.Override + public RateLimitQuotaServiceStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new RateLimitQuotaServiceStub(channel, callOptions); + } + }; + return RateLimitQuotaServiceStub.newStub(factory, channel); + } + + /** + * Creates a new blocking-style stub that supports unary and streaming output calls on the service + */ + public static RateLimitQuotaServiceBlockingStub newBlockingStub( + io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @java.lang.Override + public RateLimitQuotaServiceBlockingStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new RateLimitQuotaServiceBlockingStub(channel, callOptions); + } + }; + return RateLimitQuotaServiceBlockingStub.newStub(factory, channel); + } + + /** + * Creates a new ListenableFuture-style stub that supports unary calls on the service + */ + public static RateLimitQuotaServiceFutureStub newFutureStub( + io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @java.lang.Override + public RateLimitQuotaServiceFutureStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new RateLimitQuotaServiceFutureStub(channel, callOptions); + } + }; + return RateLimitQuotaServiceFutureStub.newStub(factory, channel); + } + + /** + *
+   * Defines the Rate Limit Quota Service (RLQS).
+   * 
+ */ + public interface AsyncService { + + /** + *
+     * Main communication channel: the data plane sends usage reports to the RLQS server,
+     * and the server asynchronously responding with the assignments.
+     * 
+ */ + default io.grpc.stub.StreamObserver streamRateLimitQuotas( + io.grpc.stub.StreamObserver responseObserver) { + return io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall(getStreamRateLimitQuotasMethod(), responseObserver); + } + } + + /** + * Base class for the server implementation of the service RateLimitQuotaService. + *
+   * Defines the Rate Limit Quota Service (RLQS).
+   * 
+ */ + public static abstract class RateLimitQuotaServiceImplBase + implements io.grpc.BindableService, AsyncService { + + @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() { + return RateLimitQuotaServiceGrpc.bindService(this); + } + } + + /** + * A stub to allow clients to do asynchronous rpc calls to service RateLimitQuotaService. + *
+   * Defines the Rate Limit Quota Service (RLQS).
+   * 
+ */ + public static final class RateLimitQuotaServiceStub + extends io.grpc.stub.AbstractAsyncStub { + private RateLimitQuotaServiceStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @java.lang.Override + protected RateLimitQuotaServiceStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new RateLimitQuotaServiceStub(channel, callOptions); + } + + /** + *
+     * Main communication channel: the data plane sends usage reports to the RLQS server,
+     * and the server asynchronously responding with the assignments.
+     * 
+ */ + public io.grpc.stub.StreamObserver streamRateLimitQuotas( + io.grpc.stub.StreamObserver responseObserver) { + return io.grpc.stub.ClientCalls.asyncBidiStreamingCall( + getChannel().newCall(getStreamRateLimitQuotasMethod(), getCallOptions()), responseObserver); + } + } + + /** + * A stub to allow clients to do synchronous rpc calls to service RateLimitQuotaService. + *
+   * Defines the Rate Limit Quota Service (RLQS).
+   * 
+ */ + public static final class RateLimitQuotaServiceBlockingStub + extends io.grpc.stub.AbstractBlockingStub { + private RateLimitQuotaServiceBlockingStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @java.lang.Override + protected RateLimitQuotaServiceBlockingStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new RateLimitQuotaServiceBlockingStub(channel, callOptions); + } + } + + /** + * A stub to allow clients to do ListenableFuture-style rpc calls to service RateLimitQuotaService. + *
+   * Defines the Rate Limit Quota Service (RLQS).
+   * 
+ */ + public static final class RateLimitQuotaServiceFutureStub + extends io.grpc.stub.AbstractFutureStub { + private RateLimitQuotaServiceFutureStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @java.lang.Override + protected RateLimitQuotaServiceFutureStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new RateLimitQuotaServiceFutureStub(channel, callOptions); + } + } + + private static final int METHODID_STREAM_RATE_LIMIT_QUOTAS = 0; + + private static final class MethodHandlers implements + io.grpc.stub.ServerCalls.UnaryMethod, + io.grpc.stub.ServerCalls.ServerStreamingMethod, + io.grpc.stub.ServerCalls.ClientStreamingMethod, + io.grpc.stub.ServerCalls.BidiStreamingMethod { + private final AsyncService serviceImpl; + private final int methodId; + + MethodHandlers(AsyncService serviceImpl, int methodId) { + this.serviceImpl = serviceImpl; + this.methodId = methodId; + } + + @java.lang.Override + @java.lang.SuppressWarnings("unchecked") + public void invoke(Req request, io.grpc.stub.StreamObserver responseObserver) { + switch (methodId) { + default: + throw new AssertionError(); + } + } + + @java.lang.Override + @java.lang.SuppressWarnings("unchecked") + public io.grpc.stub.StreamObserver invoke( + io.grpc.stub.StreamObserver responseObserver) { + switch (methodId) { + case METHODID_STREAM_RATE_LIMIT_QUOTAS: + return (io.grpc.stub.StreamObserver) serviceImpl.streamRateLimitQuotas( + (io.grpc.stub.StreamObserver) responseObserver); + default: + throw new AssertionError(); + } + } + } + + public static final io.grpc.ServerServiceDefinition bindService(AsyncService service) { + return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor()) + .addMethod( + getStreamRateLimitQuotasMethod(), + io.grpc.stub.ServerCalls.asyncBidiStreamingCall( + new MethodHandlers< + io.envoyproxy.envoy.service.rate_limit_quota.v3.RateLimitQuotaUsageReports, + io.envoyproxy.envoy.service.rate_limit_quota.v3.RateLimitQuotaResponse>( + service, METHODID_STREAM_RATE_LIMIT_QUOTAS))) + .build(); + } + + private static abstract class RateLimitQuotaServiceBaseDescriptorSupplier + implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier { + RateLimitQuotaServiceBaseDescriptorSupplier() {} + + @java.lang.Override + public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() { + return io.envoyproxy.envoy.service.rate_limit_quota.v3.RlqsProto.getDescriptor(); + } + + @java.lang.Override + public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() { + return getFileDescriptor().findServiceByName("RateLimitQuotaService"); + } + } + + private static final class RateLimitQuotaServiceFileDescriptorSupplier + extends RateLimitQuotaServiceBaseDescriptorSupplier { + RateLimitQuotaServiceFileDescriptorSupplier() {} + } + + private static final class RateLimitQuotaServiceMethodDescriptorSupplier + extends RateLimitQuotaServiceBaseDescriptorSupplier + implements io.grpc.protobuf.ProtoMethodDescriptorSupplier { + private final java.lang.String methodName; + + RateLimitQuotaServiceMethodDescriptorSupplier(java.lang.String methodName) { + this.methodName = methodName; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() { + return getServiceDescriptor().findMethodByName(methodName); + } + } + + private static volatile io.grpc.ServiceDescriptor serviceDescriptor; + + public static io.grpc.ServiceDescriptor getServiceDescriptor() { + io.grpc.ServiceDescriptor result = serviceDescriptor; + if (result == null) { + synchronized (RateLimitQuotaServiceGrpc.class) { + result = serviceDescriptor; + if (result == null) { + serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME) + .setSchemaDescriptor(new RateLimitQuotaServiceFileDescriptorSupplier()) + .addMethod(getStreamRateLimitQuotasMethod()) + .build(); + } + } + } + return result; + } +} diff --git a/xds/third_party/cel-spec/LICENSE b/xds/third_party/cel-spec/LICENSE new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/xds/third_party/cel-spec/LICENSE @@ -0,0 +1,202 @@ + + 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/xds/third_party/cel-spec/import.sh b/xds/third_party/cel-spec/import.sh new file mode 100755 index 00000000000..bba8214fdfb --- /dev/null +++ b/xds/third_party/cel-spec/import.sh @@ -0,0 +1,59 @@ +#!/bin/bash +# Copyright 2024 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. + +# Update VERSION then execute this script + +set -e +VERSION="v0.15.0" +DOWNLOAD_URL="https://github.com/google/cel-spec/archive/refs/tags/${VERSION}.tar.gz" +DOWNLOAD_BASE_DIR="cel-spec-${VERSION#v}" +SOURCE_PROTO_BASE_DIR="${DOWNLOAD_BASE_DIR}/proto" +TARGET_PROTO_BASE_DIR="src/main/proto" +# Sorted alphabetically. +FILES=( +cel/expr/checked.proto +cel/expr/syntax.proto +) + +pushd `git rev-parse --show-toplevel`/xds/third_party/cel-spec > /dev/null + +# put the repo in a tmp directory +tmpdir="$(mktemp -d)" +trap "rm -rf ${tmpdir}" EXIT +curl -Ls "${DOWNLOAD_URL}" | tar xz -C "${tmpdir}" + +cp -p "${tmpdir}/${DOWNLOAD_BASE_DIR}/LICENSE" LICENSE + +rm -rf "${TARGET_PROTO_BASE_DIR}" +mkdir -p "${TARGET_PROTO_BASE_DIR}" +pushd "${TARGET_PROTO_BASE_DIR}" > /dev/null + +# copy proto files to project directory +TOTAL=${#FILES[@]} +COPIED=0 +for file in "${FILES[@]}" +do + mkdir -p "$(dirname "${file}")" + cp -p "${tmpdir}/${SOURCE_PROTO_BASE_DIR}/${file}" "${file}" && (( ++COPIED )) +done +popd > /dev/null + +popd > /dev/null + +echo "Imported ${COPIED} files." +if (( COPIED != TOTAL )); then + echo "Failed importing $(( TOTAL - COPIED )) files." 1>&2 + exit 1 +fi diff --git a/xds/third_party/cel-spec/src/main/proto/cel/expr/checked.proto b/xds/third_party/cel-spec/src/main/proto/cel/expr/checked.proto new file mode 100644 index 00000000000..e327db9b225 --- /dev/null +++ b/xds/third_party/cel-spec/src/main/proto/cel/expr/checked.proto @@ -0,0 +1,344 @@ +// Copyright 2023 Google LLC +// +// 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 cel.expr; + +import "cel/expr/syntax.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/struct.proto"; + +option cc_enable_arenas = true; +option go_package = "cel.dev/expr"; +option java_multiple_files = true; +option java_outer_classname = "DeclProto"; +option java_package = "dev.cel.expr"; + +// Protos for representing CEL declarations and typed checked expressions. + +// A CEL expression which has been successfully type checked. +message CheckedExpr { + // A map from expression ids to resolved references. + // + // The following entries are in this table: + // + // - An Ident or Select expression is represented here if it resolves to a + // declaration. For instance, if `a.b.c` is represented by + // `select(select(id(a), b), c)`, and `a.b` resolves to a declaration, + // while `c` is a field selection, then the reference is attached to the + // nested select expression (but not to the id or or the outer select). + // In turn, if `a` resolves to a declaration and `b.c` are field selections, + // the reference is attached to the ident expression. + // - Every Call expression has an entry here, identifying the function being + // called. + // - Every CreateStruct expression for a message has an entry, identifying + // the message. + map reference_map = 2; + + // A map from expression ids to types. + // + // Every expression node which has a type different than DYN has a mapping + // here. If an expression has type DYN, it is omitted from this map to save + // space. + map type_map = 3; + + // The source info derived from input that generated the parsed `expr` and + // any optimizations made during the type-checking pass. + SourceInfo source_info = 5; + + // The expr version indicates the major / minor version number of the `expr` + // representation. + // + // The most common reason for a version change will be to indicate to the CEL + // runtimes that transformations have been performed on the expr during static + // analysis. In some cases, this will save the runtime the work of applying + // the same or similar transformations prior to evaluation. + string expr_version = 6; + + // The checked expression. Semantically equivalent to the parsed `expr`, but + // may have structural differences. + Expr expr = 4; +} + +// Represents a CEL type. +message Type { + // List type with typed elements, e.g. `list`. + message ListType { + // The element type. + Type elem_type = 1; + } + + // Map type with parameterized key and value types, e.g. `map`. + message MapType { + // The type of the key. + Type key_type = 1; + + // The type of the value. + Type value_type = 2; + } + + // Function type with result and arg types. + message FunctionType { + // Result type of the function. + Type result_type = 1; + + // Argument types of the function. + repeated Type arg_types = 2; + } + + // Application defined abstract type. + message AbstractType { + // The fully qualified name of this abstract type. + string name = 1; + + // Parameter types for this abstract type. + repeated Type parameter_types = 2; + } + + // CEL primitive types. + enum PrimitiveType { + // Unspecified type. + PRIMITIVE_TYPE_UNSPECIFIED = 0; + + // Boolean type. + BOOL = 1; + + // Int64 type. + // + // 32-bit integer values are widened to int64. + INT64 = 2; + + // Uint64 type. + // + // 32-bit unsigned integer values are widened to uint64. + UINT64 = 3; + + // Double type. + // + // 32-bit float values are widened to double values. + DOUBLE = 4; + + // String type. + STRING = 5; + + // Bytes type. + BYTES = 6; + } + + // Well-known protobuf types treated with first-class support in CEL. + enum WellKnownType { + // Unspecified type. + WELL_KNOWN_TYPE_UNSPECIFIED = 0; + + // Well-known protobuf.Any type. + // + // Any types are a polymorphic message type. During type-checking they are + // treated like `DYN` types, but at runtime they are resolved to a specific + // message type specified at evaluation time. + ANY = 1; + + // Well-known protobuf.Timestamp type, internally referenced as `timestamp`. + TIMESTAMP = 2; + + // Well-known protobuf.Duration type, internally referenced as `duration`. + DURATION = 3; + } + + // The kind of type. + oneof type_kind { + // Dynamic type. + google.protobuf.Empty dyn = 1; + + // Null value. + google.protobuf.NullValue null = 2; + + // Primitive types: `true`, `1u`, `-2.0`, `'string'`, `b'bytes'`. + PrimitiveType primitive = 3; + + // Wrapper of a primitive type, e.g. `google.protobuf.Int64Value`. + PrimitiveType wrapper = 4; + + // Well-known protobuf type such as `google.protobuf.Timestamp`. + WellKnownType well_known = 5; + + // Parameterized list with elements of `list_type`, e.g. `list`. + ListType list_type = 6; + + // Parameterized map with typed keys and values. + MapType map_type = 7; + + // Function type. + FunctionType function = 8; + + // Protocol buffer message type. + // + // The `message_type` string specifies the qualified message type name. For + // example, `google.type.PhoneNumber`. + string message_type = 9; + + // Type param type. + // + // The `type_param` string specifies the type parameter name, e.g. `list` + // would be a `list_type` whose element type was a `type_param` type + // named `E`. + string type_param = 10; + + // Type type. + // + // The `type` value specifies the target type. e.g. int is type with a + // target type of `Primitive.INT64`. + Type type = 11; + + // Error type. + // + // During type-checking if an expression is an error, its type is propagated + // as the `ERROR` type. This permits the type-checker to discover other + // errors present in the expression. + google.protobuf.Empty error = 12; + + // Abstract, application defined type. + // + // An abstract type has no accessible field names, and it can only be + // inspected via helper / member functions. + AbstractType abstract_type = 14; + } +} + +// Represents a declaration of a named value or function. +// +// A declaration is part of the contract between the expression, the agent +// evaluating that expression, and the caller requesting evaluation. +message Decl { + // Identifier declaration which specifies its type and optional `Expr` value. + // + // An identifier without a value is a declaration that must be provided at + // evaluation time. An identifier with a value should resolve to a constant, + // but may be used in conjunction with other identifiers bound at evaluation + // time. + message IdentDecl { + // Required. The type of the identifier. + Type type = 1; + + // The constant value of the identifier. If not specified, the identifier + // must be supplied at evaluation time. + Constant value = 2; + + // Documentation string for the identifier. + string doc = 3; + } + + // Function declaration specifies one or more overloads which indicate the + // function's parameter types and return type. + // + // Functions have no observable side-effects (there may be side-effects like + // logging which are not observable from CEL). + message FunctionDecl { + // An overload indicates a function's parameter types and return type, and + // may optionally include a function body described in terms of + // [Expr][cel.expr.Expr] values. + // + // Functions overloads are declared in either a function or method + // call-style. For methods, the `params[0]` is the expected type of the + // target receiver. + // + // Overloads must have non-overlapping argument types after erasure of all + // parameterized type variables (similar as type erasure in Java). + message Overload { + // Required. Globally unique overload name of the function which reflects + // the function name and argument types. + // + // This will be used by a [Reference][cel.expr.Reference] to + // indicate the `overload_id` that was resolved for the function `name`. + string overload_id = 1; + + // List of function parameter [Type][cel.expr.Type] values. + // + // Param types are disjoint after generic type parameters have been + // replaced with the type `DYN`. Since the `DYN` type is compatible with + // any other type, this means that if `A` is a type parameter, the + // function types `int` and `int` are not disjoint. Likewise, + // `map` is not disjoint from `map`. + // + // When the `result_type` of a function is a generic type param, the + // type param name also appears as the `type` of on at least one params. + repeated Type params = 2; + + // The type param names associated with the function declaration. + // + // For example, `function ex(K key, map map) : V` would yield + // the type params of `K, V`. + repeated string type_params = 3; + + // Required. The result type of the function. For example, the operator + // `string.isEmpty()` would have `result_type` of `kind: BOOL`. + Type result_type = 4; + + // Whether the function is to be used in a method call-style `x.f(...)` + // of a function call-style `f(x, ...)`. + // + // For methods, the first parameter declaration, `params[0]` is the + // expected type of the target receiver. + bool is_instance_function = 5; + + // Documentation string for the overload. + string doc = 6; + } + + // Required. List of function overloads, must contain at least one overload. + repeated Overload overloads = 1; + } + + // The fully qualified name of the declaration. + // + // Declarations are organized in containers and this represents the full path + // to the declaration in its container, as in `cel.expr.Decl`. + // + // Declarations used as + // [FunctionDecl.Overload][cel.expr.Decl.FunctionDecl.Overload] + // parameters may or may not have a name depending on whether the overload is + // function declaration or a function definition containing a result + // [Expr][cel.expr.Expr]. + string name = 1; + + // Required. The declaration kind. + oneof decl_kind { + // Identifier declaration. + IdentDecl ident = 2; + + // Function declaration. + FunctionDecl function = 3; + } +} + +// Describes a resolved reference to a declaration. +message Reference { + // The fully qualified name of the declaration. + string name = 1; + + // For references to functions, this is a list of `Overload.overload_id` + // values which match according to typing rules. + // + // If the list has more than one element, overload resolution among the + // presented candidates must happen at runtime because of dynamic types. The + // type checker attempts to narrow down this list as much as possible. + // + // Empty if this is not a reference to a + // [Decl.FunctionDecl][cel.expr.Decl.FunctionDecl]. + repeated string overload_id = 3; + + // For references to constants, this may contain the value of the + // constant if known at compile time. + Constant value = 4; +} diff --git a/xds/third_party/cel-spec/src/main/proto/cel/expr/syntax.proto b/xds/third_party/cel-spec/src/main/proto/cel/expr/syntax.proto new file mode 100644 index 00000000000..ed124a74384 --- /dev/null +++ b/xds/third_party/cel-spec/src/main/proto/cel/expr/syntax.proto @@ -0,0 +1,393 @@ +// Copyright 2023 Google LLC +// +// 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 cel.expr; + +import "google/protobuf/duration.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/timestamp.proto"; + +option cc_enable_arenas = true; +option go_package = "cel.dev/expr"; +option java_multiple_files = true; +option java_outer_classname = "SyntaxProto"; +option java_package = "dev.cel.expr"; + +// A representation of the abstract syntax of the Common Expression Language. + +// An expression together with source information as returned by the parser. +message ParsedExpr { + // The parsed expression. + Expr expr = 2; + + // The source info derived from input that generated the parsed `expr`. + SourceInfo source_info = 3; +} + +// An abstract representation of a common expression. +// +// Expressions are abstractly represented as a collection of identifiers, +// select statements, function calls, literals, and comprehensions. All +// operators with the exception of the '.' operator are modelled as function +// calls. This makes it easy to represent new operators into the existing AST. +// +// All references within expressions must resolve to a +// [Decl][cel.expr.Decl] provided at type-check for an expression to be +// valid. A reference may either be a bare identifier `name` or a qualified +// identifier `google.api.name`. References may either refer to a value or a +// function declaration. +// +// For example, the expression `google.api.name.startsWith('expr')` references +// the declaration `google.api.name` within a +// [Expr.Select][cel.expr.Expr.Select] expression, and the function +// declaration `startsWith`. +message Expr { + // An identifier expression. e.g. `request`. + message Ident { + // Required. Holds a single, unqualified identifier, possibly preceded by a + // '.'. + // + // Qualified names are represented by the + // [Expr.Select][cel.expr.Expr.Select] expression. + string name = 1; + } + + // A field selection expression. e.g. `request.auth`. + message Select { + // Required. The target of the selection expression. + // + // For example, in the select expression `request.auth`, the `request` + // portion of the expression is the `operand`. + Expr operand = 1; + + // Required. The name of the field to select. + // + // For example, in the select expression `request.auth`, the `auth` portion + // of the expression would be the `field`. + string field = 2; + + // Whether the select is to be interpreted as a field presence test. + // + // This results from the macro `has(request.auth)`. + bool test_only = 3; + } + + // A call expression, including calls to predefined functions and operators. + // + // For example, `value == 10`, `size(map_value)`. + message Call { + // The target of an method call-style expression. For example, `x` in + // `x.f()`. + Expr target = 1; + + // Required. The name of the function or method being called. + string function = 2; + + // The arguments. + repeated Expr args = 3; + } + + // A list creation expression. + // + // Lists may either be homogenous, e.g. `[1, 2, 3]`, or heterogeneous, e.g. + // `dyn([1, 'hello', 2.0])` + message CreateList { + // The elements part of the list. + repeated Expr elements = 1; + + // The indices within the elements list which are marked as optional + // elements. + // + // When an optional-typed value is present, the value it contains + // is included in the list. If the optional-typed value is absent, the list + // element is omitted from the CreateList result. + repeated int32 optional_indices = 2; + } + + // A map or message creation expression. + // + // Maps are constructed as `{'key_name': 'value'}`. Message construction is + // similar, but prefixed with a type name and composed of field ids: + // `types.MyType{field_id: 'value'}`. + message CreateStruct { + // Represents an entry. + message Entry { + // Required. An id assigned to this node by the parser which is unique + // in a given expression tree. This is used to associate type + // information and other attributes to the node. + int64 id = 1; + + // The `Entry` key kinds. + oneof key_kind { + // The field key for a message creator statement. + string field_key = 2; + + // The key expression for a map creation statement. + Expr map_key = 3; + } + + // Required. The value assigned to the key. + // + // If the optional_entry field is true, the expression must resolve to an + // optional-typed value. If the optional value is present, the key will be + // set; however, if the optional value is absent, the key will be unset. + Expr value = 4; + + // Whether the key-value pair is optional. + bool optional_entry = 5; + } + + // The type name of the message to be created, empty when creating map + // literals. + string message_name = 1; + + // The entries in the creation expression. + repeated Entry entries = 2; + } + + // A comprehension expression applied to a list or map. + // + // Comprehensions are not part of the core syntax, but enabled with macros. + // A macro matches a specific call signature within a parsed AST and replaces + // the call with an alternate AST block. Macro expansion happens at parse + // time. + // + // The following macros are supported within CEL: + // + // Aggregate type macros may be applied to all elements in a list or all keys + // in a map: + // + // * `all`, `exists`, `exists_one` - test a predicate expression against + // the inputs and return `true` if the predicate is satisfied for all, + // any, or only one value `list.all(x, x < 10)`. + // * `filter` - test a predicate expression against the inputs and return + // the subset of elements which satisfy the predicate: + // `payments.filter(p, p > 1000)`. + // * `map` - apply an expression to all elements in the input and return the + // output aggregate type: `[1, 2, 3].map(i, i * i)`. + // + // The `has(m.x)` macro tests whether the property `x` is present in struct + // `m`. The semantics of this macro depend on the type of `m`. For proto2 + // messages `has(m.x)` is defined as 'defined, but not set`. For proto3, the + // macro tests whether the property is set to its default. For map and struct + // types, the macro tests whether the property `x` is defined on `m`. + // + // Comprehension evaluation can be best visualized as the following + // pseudocode: + // + // ``` + // let `accu_var` = `accu_init` + // for (let `iter_var` in `iter_range`) { + // if (!`loop_condition`) { + // break + // } + // `accu_var` = `loop_step` + // } + // return `result` + // ``` + message Comprehension { + // The name of the iteration variable. + string iter_var = 1; + + // The range over which var iterates. + Expr iter_range = 2; + + // The name of the variable used for accumulation of the result. + string accu_var = 3; + + // The initial value of the accumulator. + Expr accu_init = 4; + + // An expression which can contain iter_var and accu_var. + // + // Returns false when the result has been computed and may be used as + // a hint to short-circuit the remainder of the comprehension. + Expr loop_condition = 5; + + // An expression which can contain iter_var and accu_var. + // + // Computes the next value of accu_var. + Expr loop_step = 6; + + // An expression which can contain accu_var. + // + // Computes the result. + Expr result = 7; + } + + // Required. An id assigned to this node by the parser which is unique in a + // given expression tree. This is used to associate type information and other + // attributes to a node in the parse tree. + int64 id = 2; + + // Required. Variants of expressions. + oneof expr_kind { + // A constant expression. + Constant const_expr = 3; + + // An identifier expression. + Ident ident_expr = 4; + + // A field selection expression, e.g. `request.auth`. + Select select_expr = 5; + + // A call expression, including calls to predefined functions and operators. + Call call_expr = 6; + + // A list creation expression. + CreateList list_expr = 7; + + // A map or message creation expression. + CreateStruct struct_expr = 8; + + // A comprehension expression. + Comprehension comprehension_expr = 9; + } +} + +// Represents a primitive literal. +// +// Named 'Constant' here for backwards compatibility. +// +// This is similar as the primitives supported in the well-known type +// `google.protobuf.Value`, but richer so it can represent CEL's full range of +// primitives. +// +// Lists and structs are not included as constants as these aggregate types may +// contain [Expr][cel.expr.Expr] elements which require evaluation and +// are thus not constant. +// +// Examples of constants include: `"hello"`, `b'bytes'`, `1u`, `4.2`, `-2`, +// `true`, `null`. +message Constant { + // Required. The valid constant kinds. + oneof constant_kind { + // null value. + google.protobuf.NullValue null_value = 1; + + // boolean value. + bool bool_value = 2; + + // int64 value. + int64 int64_value = 3; + + // uint64 value. + uint64 uint64_value = 4; + + // double value. + double double_value = 5; + + // string value. + string string_value = 6; + + // bytes value. + bytes bytes_value = 7; + + // protobuf.Duration value. + // + // Deprecated: duration is no longer considered a builtin cel type. + google.protobuf.Duration duration_value = 8 [deprecated = true]; + + // protobuf.Timestamp value. + // + // Deprecated: timestamp is no longer considered a builtin cel type. + google.protobuf.Timestamp timestamp_value = 9 [deprecated = true]; + } +} + +// Source information collected at parse time. +message SourceInfo { + // The syntax version of the source, e.g. `cel1`. + string syntax_version = 1; + + // The location name. All position information attached to an expression is + // relative to this location. + // + // The location could be a file, UI element, or similar. For example, + // `acme/app/AnvilPolicy.cel`. + string location = 2; + + // Monotonically increasing list of code point offsets where newlines + // `\n` appear. + // + // The line number of a given position is the index `i` where for a given + // `id` the `line_offsets[i] < id_positions[id] < line_offsets[i+1]`. The + // column may be derived from `id_positions[id] - line_offsets[i]`. + repeated int32 line_offsets = 3; + + // A map from the parse node id (e.g. `Expr.id`) to the code point offset + // within the source. + map positions = 4; + + // A map from the parse node id where a macro replacement was made to the + // call `Expr` that resulted in a macro expansion. + // + // For example, `has(value.field)` is a function call that is replaced by a + // `test_only` field selection in the AST. Likewise, the call + // `list.exists(e, e > 10)` translates to a comprehension expression. The key + // in the map corresponds to the expression id of the expanded macro, and the + // value is the call `Expr` that was replaced. + map macro_calls = 5; + + // A list of tags for extensions that were used while parsing or type checking + // the source expression. For example, optimizations that require special + // runtime support may be specified. + // + // These are used to check feature support between components in separate + // implementations. This can be used to either skip redundant work or + // report an error if the extension is unsupported. + repeated Extension extensions = 6; + + // An extension that was requested for the source expression. + message Extension { + // Version + message Version { + // Major version changes indicate different required support level from + // the required components. + int64 major = 1; + // Minor version changes must not change the observed behavior from + // existing implementations, but may be provided informationally. + int64 minor = 2; + } + + // CEL component specifier. + enum Component { + // Unspecified, default. + COMPONENT_UNSPECIFIED = 0; + // Parser. Converts a CEL string to an AST. + COMPONENT_PARSER = 1; + // Type checker. Checks that references in an AST are defined and types + // agree. + COMPONENT_TYPE_CHECKER = 2; + // Runtime. Evaluates a parsed and optionally checked CEL AST against a + // context. + COMPONENT_RUNTIME = 3; + } + + // Identifier for the extension. Example: constant_folding + string id = 1; + + // If set, the listed components must understand the extension for the + // expression to evaluate correctly. + // + // This field has set semantics, repeated values should be deduplicated. + repeated Component affected_components = 2; + + // Version info. May be skipped if it isn't meaningful for the extension. + // (for example constant_folding might always be v0.0). + Version version = 3; + } +} diff --git a/xds/third_party/envoy/import.sh b/xds/third_party/envoy/import.sh index 3eeb46cf664..41506c2ed32 100755 --- a/xds/third_party/envoy/import.sh +++ b/xds/third_party/envoy/import.sh @@ -74,6 +74,7 @@ envoy/data/accesslog/v3/accesslog.proto envoy/extensions/clusters/aggregate/v3/cluster.proto envoy/extensions/filters/common/fault/v3/fault.proto envoy/extensions/filters/http/fault/v3/fault.proto +envoy/extensions/filters/http/rate_limit_quota/v3/rate_limit_quota.proto envoy/extensions/filters/http/rbac/v3/rbac.proto envoy/extensions/filters/http/router/v3/router.proto envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -91,9 +92,11 @@ envoy/extensions/transport_sockets/tls/v3/tls.proto envoy/service/discovery/v3/ads.proto envoy/service/discovery/v3/discovery.proto envoy/service/load_stats/v3/lrs.proto +envoy/service/rate_limit_quota/v3/rlqs.proto envoy/service/status/v3/csds.proto envoy/type/http/v3/path_transformation.proto envoy/type/matcher/v3/filter_state.proto +envoy/type/matcher/v3/http_inputs.proto envoy/type/matcher/v3/metadata.proto envoy/type/matcher/v3/node.proto envoy/type/matcher/v3/number.proto @@ -105,9 +108,13 @@ envoy/type/matcher/v3/value.proto envoy/type/metadata/v3/metadata.proto envoy/type/tracing/v3/custom_tag.proto envoy/type/v3/http.proto +envoy/type/v3/http_status.proto envoy/type/v3/percent.proto envoy/type/v3/range.proto +envoy/type/v3/ratelimit_strategy.proto +envoy/type/v3/ratelimit_unit.proto envoy/type/v3/semantic_version.proto +envoy/type/v3/token_bucket.proto ) pushd "$(git rev-parse --show-toplevel)/xds/third_party/envoy" > /dev/null diff --git a/xds/third_party/envoy/src/main/proto/envoy/extensions/filters/http/rate_limit_quota/v3/rate_limit_quota.proto b/xds/third_party/envoy/src/main/proto/envoy/extensions/filters/http/rate_limit_quota/v3/rate_limit_quota.proto new file mode 100644 index 00000000000..57b8bdecd78 --- /dev/null +++ b/xds/third_party/envoy/src/main/proto/envoy/extensions/filters/http/rate_limit_quota/v3/rate_limit_quota.proto @@ -0,0 +1,423 @@ +syntax = "proto3"; + +package envoy.extensions.filters.http.rate_limit_quota.v3; + +import "envoy/config/core/v3/base.proto"; +import "envoy/config/core/v3/extension.proto"; +import "envoy/config/core/v3/grpc_service.proto"; +import "envoy/type/v3/http_status.proto"; +import "envoy/type/v3/ratelimit_strategy.proto"; + +import "google/protobuf/duration.proto"; +import "google/protobuf/wrappers.proto"; +import "google/rpc/status.proto"; + +import "xds/annotations/v3/status.proto"; +import "xds/type/matcher/v3/matcher.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.filters.http.rate_limit_quota.v3"; +option java_outer_classname = "RateLimitQuotaProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/rate_limit_quota/v3;rate_limit_quotav3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; +option (xds.annotations.v3.file_status).work_in_progress = true; + +// [#protodoc-title: Rate Limit Quota] +// Rate Limit Quota :ref:`configuration overview `. +// [#extension: envoy.filters.http.rate_limit_quota] + +// Configures the Rate Limit Quota filter. +// +// Can be overridden in the per-route and per-host configurations. +// The more specific definition completely overrides the less specific definition. +// [#next-free-field: 7] +message RateLimitQuotaFilterConfig { + // Configures the gRPC Rate Limit Quota Service (RLQS) RateLimitQuotaService. + config.core.v3.GrpcService rlqs_server = 1 [(validate.rules).message = {required: true}]; + + // The application domain to use when calling the service. This enables sharing the quota + // server between different applications without fear of overlap. + // E.g., "envoy". + string domain = 2 [(validate.rules).string = {min_len: 1}]; + + // The match tree to use for grouping incoming requests into buckets. + // + // Example: + // + // .. validated-code-block:: yaml + // :type-name: xds.type.matcher.v3.Matcher + // + // matcher_list: + // matchers: + // # Assign requests with header['env'] set to 'staging' to the bucket { name: 'staging' } + // - predicate: + // single_predicate: + // input: + // typed_config: + // '@type': type.googleapis.com/envoy.type.matcher.v3.HttpRequestHeaderMatchInput + // header_name: env + // value_match: + // exact: staging + // on_match: + // action: + // typed_config: + // '@type': type.googleapis.com/envoy.extensions.filters.http.rate_limit_quota.v3.RateLimitQuotaBucketSettings + // bucket_id_builder: + // bucket_id_builder: + // name: + // string_value: staging + // + // # Assign requests with header['user_group'] set to 'admin' to the bucket { acl: 'admin_users' } + // - predicate: + // single_predicate: + // input: + // typed_config: + // '@type': type.googleapis.com/xds.type.matcher.v3.HttpAttributesCelMatchInput + // custom_match: + // typed_config: + // '@type': type.googleapis.com/xds.type.matcher.v3.CelMatcher + // expr_match: + // # Shortened for illustration purposes. Here should be parsed CEL expression: + // # request.headers['user_group'] == 'admin' + // parsed_expr: {} + // on_match: + // action: + // typed_config: + // '@type': type.googleapis.com/envoy.extensions.filters.http.rate_limit_quota.v3.RateLimitQuotaBucketSettings + // bucket_id_builder: + // bucket_id_builder: + // acl: + // string_value: admin_users + // + // # Catch-all clause for the requests not matched by any of the matchers. + // # In this example, deny all requests. + // on_no_match: + // action: + // typed_config: + // '@type': type.googleapis.com/envoy.extensions.filters.http.rate_limit_quota.v3.RateLimitQuotaBucketSettings + // no_assignment_behavior: + // fallback_rate_limit: + // blanket_rule: DENY_ALL + // + // .. attention:: + // The first matched group wins. Once the request is matched into a bucket, matcher + // evaluation ends. + // + // Use ``on_no_match`` field to assign the catch-all bucket. If a request is not matched + // into any bucket, and there's no ``on_no_match`` field configured, the request will be + // ALLOWED by default. It will NOT be reported to the RLQS server. + // + // Refer to :ref:`Unified Matcher API ` + // documentation for more information on the matcher trees. + xds.type.matcher.v3.Matcher bucket_matchers = 3 [(validate.rules).message = {required: true}]; + + // If set, this will enable -- but not necessarily enforce -- the rate limit for the given + // fraction of requests. + // + // Defaults to 100% of requests. + config.core.v3.RuntimeFractionalPercent filter_enabled = 4; + + // If set, this will enforce the rate limit decisions for the given fraction of requests. + // For requests that are not enforced the filter will still obtain the quota and include it + // in the load computation, however the request will always be allowed regardless of the outcome + // of quota application. This allows validation or testing of the rate limiting service + // infrastructure without disrupting existing traffic. + // + // Note: this only applies to the fraction of enabled requests. + // + // Defaults to 100% of requests. + config.core.v3.RuntimeFractionalPercent filter_enforced = 5; + + // Specifies a list of HTTP headers that should be added to each request that + // has been rate limited and is also forwarded upstream. This can only occur when the + // filter is enabled but not enforced. + repeated config.core.v3.HeaderValueOption request_headers_to_add_when_not_enforced = 6 + [(validate.rules).repeated = {max_items: 10}]; +} + +// Per-route and per-host configuration overrides. The more specific definition completely +// overrides the less specific definition. +message RateLimitQuotaOverride { + // The application domain to use when calling the service. This enables sharing the quota + // server between different applications without fear of overlap. + // E.g., "envoy". + // + // If empty, inherits the value from the less specific definition. + string domain = 1; + + // The match tree to use for grouping incoming requests into buckets. + // + // If set, fully overrides the bucket matchers provided on the less specific definition. + // If not set, inherits the value from the less specific definition. + // + // See usage example: :ref:`RateLimitQuotaFilterConfig.bucket_matchers + // `. + xds.type.matcher.v3.Matcher bucket_matchers = 2; +} + +// Rate Limit Quota Bucket Settings to apply on the successful ``bucket_matchers`` match. +// +// Specify this message in the :ref:`Matcher.OnMatch.action +// ` field of the +// ``bucket_matchers`` matcher tree to assign the matched requests to the Quota Bucket. +// Usage example: :ref:`RateLimitQuotaFilterConfig.bucket_matchers +// `. +// [#next-free-field: 6] +message RateLimitQuotaBucketSettings { + // Configures the behavior after the first request has been matched to the bucket, and before the + // the RLQS server returns the first quota assignment. + message NoAssignmentBehavior { + oneof no_assignment_behavior { + option (validate.required) = true; + + // Apply pre-configured rate limiting strategy until the server sends the first assignment. + type.v3.RateLimitStrategy fallback_rate_limit = 1; + } + } + + // Specifies the behavior when the bucket's assignment has expired, and cannot be refreshed for + // any reason. + message ExpiredAssignmentBehavior { + // Reuse the last known quota assignment, effectively extending it for the duration + // specified in the :ref:`expired_assignment_behavior_timeout + // ` + // field. + message ReuseLastAssignment { + } + + // Limit the time :ref:`ExpiredAssignmentBehavior + // ` + // is applied. If the server doesn't respond within this duration: + // + // 1. Selected ``ExpiredAssignmentBehavior`` is no longer applied. + // 2. The bucket is abandoned. The process of abandoning the bucket is described in the + // :ref:`AbandonAction ` + // message. + // 3. If a new request is matched into the bucket that has become abandoned, + // the data plane restarts the subscription to the bucket. The process of restarting the + // subscription is described in the :ref:`AbandonAction + // ` + // message. + // + // If not set, defaults to zero, and the bucket is abandoned immediately. + google.protobuf.Duration expired_assignment_behavior_timeout = 1 + [(validate.rules).duration = {gt {}}]; + + oneof expired_assignment_behavior { + option (validate.required) = true; + + // Apply the rate limiting strategy to all requests matched into the bucket until the RLQS + // server sends a new assignment, or the :ref:`expired_assignment_behavior_timeout + // ` + // runs out. + type.v3.RateLimitStrategy fallback_rate_limit = 2; + + // Reuse the last ``active`` assignment until the RLQS server sends a new assignment, or the + // :ref:`expired_assignment_behavior_timeout + // ` + // runs out. + ReuseLastAssignment reuse_last_assignment = 3; + } + } + + // Customize the deny response to the requests over the rate limit. + message DenyResponseSettings { + // HTTP response code to deny for HTTP requests (gRPC excluded). + // Defaults to 429 (:ref:`StatusCode.TooManyRequests`). + type.v3.HttpStatus http_status = 1; + + // HTTP response body used to deny for HTTP requests (gRPC excluded). + // If not set, an empty body is returned. + google.protobuf.BytesValue http_body = 2; + + // Configure the deny response for gRPC requests over the rate limit. + // Allows to specify the `RPC status code + // `_, + // and the error message. + // Defaults to the Status with the RPC Code ``UNAVAILABLE`` and empty message. + // + // To identify gRPC requests, Envoy checks that the ``Content-Type`` header is + // ``application/grpc``, or one of the various ``application/grpc+`` values. + // + // .. note:: + // The HTTP code for a gRPC response is always 200. + google.rpc.Status grpc_status = 3; + + // Specifies a list of HTTP headers that should be added to each response for requests that + // have been rate limited. Applies both to plain HTTP, and gRPC requests. + // The headers are added even when the rate limit quota was not enforced. + repeated config.core.v3.HeaderValueOption response_headers_to_add = 4 + [(validate.rules).repeated = {max_items: 10}]; + } + + // ``BucketIdBuilder`` makes it possible to build :ref:`BucketId + // ` with values substituted + // from the dynamic properties associated with each individual request. See usage examples in + // the docs to :ref:`bucket_id_builder + // ` + // field. + message BucketIdBuilder { + // Produces the value of the :ref:`BucketId + // ` map. + message ValueBuilder { + oneof value_specifier { + option (validate.required) = true; + + // Static string value — becomes the value in the :ref:`BucketId + // ` map as is. + string string_value = 1; + + // Dynamic value — evaluated for each request. Must produce a string output, which becomes + // the value in the :ref:`BucketId ` + // map. For example, extensions with the ``envoy.matching.http.input`` category can be used. + config.core.v3.TypedExtensionConfig custom_value = 2; + } + } + + // The map translated into the ``BucketId`` map. + // + // The ``string key`` of this map and becomes the key of ``BucketId`` map as is. + // + // The ``ValueBuilder value`` for the key can be: + // + // * static ``StringValue string_value`` — becomes the value in the ``BucketId`` map as is. + // * dynamic ``TypedExtensionConfig custom_value`` — evaluated for each request. Must produce + // a string output, which becomes the value in the the ``BucketId`` map. + // + // See usage examples in the docs to :ref:`bucket_id_builder + // ` + // field. + map bucket_id_builder = 1 [(validate.rules).map = {min_pairs: 1}]; + } + + // ``BucketId`` builder. + // + // :ref:`BucketId ` is a map from + // the string key to the string value which serves as bucket identifier common for on + // the control plane and the data plane. + // + // While ``BucketId`` is always static, ``BucketIdBuilder`` allows to populate map values + // with the dynamic properties associated with the each individual request. + // + // Example 1: static fields only + // + // ``BucketIdBuilder``: + // + // .. validated-code-block:: yaml + // :type-name: envoy.extensions.filters.http.rate_limit_quota.v3.RateLimitQuotaBucketSettings.BucketIdBuilder + // + // bucket_id_builder: + // name: + // string_value: my_bucket + // hello: + // string_value: world + // + // Produces the following ``BucketId`` for all requests: + // + // .. validated-code-block:: yaml + // :type-name: envoy.service.rate_limit_quota.v3.BucketId + // + // bucket: + // name: my_bucket + // hello: world + // + // Example 2: static and dynamic fields + // + // .. validated-code-block:: yaml + // :type-name: envoy.extensions.filters.http.rate_limit_quota.v3.RateLimitQuotaBucketSettings.BucketIdBuilder + // + // bucket_id_builder: + // name: + // string_value: my_bucket + // env: + // custom_value: + // typed_config: + // '@type': type.googleapis.com/envoy.type.matcher.v3.HttpRequestHeaderMatchInput + // header_name: environment + // + // In this example, the value of ``BucketId`` key ``env`` is substituted from the ``environment`` + // request header. + // + // This is equivalent to the following ``pseudo-code``: + // + // .. code-block:: yaml + // + // name: 'my_bucket' + // env: $header['environment'] + // + // For example, the request with the HTTP header ``env`` set to ``staging`` will produce + // the following ``BucketId``: + // + // .. validated-code-block:: yaml + // :type-name: envoy.service.rate_limit_quota.v3.BucketId + // + // bucket: + // name: my_bucket + // env: staging + // + // For the request with the HTTP header ``environment`` set to ``prod``, will produce: + // + // .. validated-code-block:: yaml + // :type-name: envoy.service.rate_limit_quota.v3.BucketId + // + // bucket: + // name: my_bucket + // env: prod + // + // .. note:: + // The order of ``BucketId`` keys do not matter. Buckets ``{ a: 'A', b: 'B' }`` and + // ``{ b: 'B', a: 'A' }`` are identical. + // + // If not set, requests will NOT be reported to the server, and will always limited + // according to :ref:`no_assignment_behavior + // ` + // configuration. + BucketIdBuilder bucket_id_builder = 1; + + // The interval at which the data plane (RLQS client) is to report quota usage for this bucket. + // + // When the first request is matched to a bucket with no assignment, the data plane is to report + // the request immediately in the :ref:`RateLimitQuotaUsageReports + // ` message. + // For the RLQS server, this signals that the data plane is now subscribed to + // the quota assignments in this bucket, and will start sending the assignment as described in + // the :ref:`RLQS documentation `. + // + // After sending the initial report, the data plane is to continue reporting the bucket usage with + // the internal specified in this field. + // + // If for any reason RLQS client doesn't receive the initial assignment for the reported bucket, + // the data plane will eventually consider the bucket abandoned and stop sending the usage + // reports. This is explained in more details at :ref:`Rate Limit Quota Service (RLQS) + // `. + // + // [#comment: 100000000 nanoseconds = 0.1 seconds] + google.protobuf.Duration reporting_interval = 2 [(validate.rules).duration = { + required: true + gt {nanos: 100000000} + }]; + + // Customize the deny response to the requests over the rate limit. + // If not set, the filter will be configured as if an empty message is set, + // and will behave according to the defaults specified in :ref:`DenyResponseSettings + // `. + DenyResponseSettings deny_response_settings = 3; + + // Configures the behavior in the "no assignment" state: after the first request has been + // matched to the bucket, and before the the RLQS server returns the first quota assignment. + // + // If not set, the default behavior is to allow all requests. + NoAssignmentBehavior no_assignment_behavior = 4; + + // Configures the behavior in the "expired assignment" state: the bucket's assignment has expired, + // and cannot be refreshed. + // + // If not set, the bucket is abandoned when its ``active`` assignment expires. + // The process of abandoning the bucket, and restarting the subscription is described in the + // :ref:`AbandonAction ` + // message. + ExpiredAssignmentBehavior expired_assignment_behavior = 5; +} diff --git a/xds/third_party/envoy/src/main/proto/envoy/service/rate_limit_quota/v3/rlqs.proto b/xds/third_party/envoy/src/main/proto/envoy/service/rate_limit_quota/v3/rlqs.proto new file mode 100644 index 00000000000..b8fa2cd8982 --- /dev/null +++ b/xds/third_party/envoy/src/main/proto/envoy/service/rate_limit_quota/v3/rlqs.proto @@ -0,0 +1,258 @@ +syntax = "proto3"; + +package envoy.service.rate_limit_quota.v3; + +import "envoy/type/v3/ratelimit_strategy.proto"; + +import "google/protobuf/duration.proto"; + +import "xds/annotations/v3/status.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.service.rate_limit_quota.v3"; +option java_outer_classname = "RlqsProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/service/rate_limit_quota/v3;rate_limit_quotav3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; +option (xds.annotations.v3.file_status).work_in_progress = true; + +// [#protodoc-title: Rate Limit Quota Service (RLQS)] + +// The Rate Limit Quota Service (RLQS) is a Envoy global rate limiting service that allows to +// delegate rate limit decisions to a remote service. The service will aggregate the usage reports +// from multiple data plane instances, and distribute Rate Limit Assignments to each instance +// based on its business logic. The logic is outside of the scope of the protocol API. +// +// The protocol is designed as a streaming-first API. It utilizes watch-like subscription model. +// The data plane groups requests into Quota Buckets as directed by the filter config, +// and periodically reports them to the RLQS server along with the Bucket identifier, :ref:`BucketId +// `. Once RLQS server has collected enough +// reports to make a decision, it'll send back the assignment with the rate limiting instructions. +// +// The first report sent by the data plane is interpreted by the RLQS server as a "watch" request, +// indicating that the data plane instance is interested in receiving further updates for the +// ``BucketId``. From then on, RLQS server may push assignments to this instance at will, even if +// the instance is not sending usage reports. It's the responsibility of the RLQS server +// to determine when the data plane instance didn't send ``BucketId`` reports for too long, +// and to respond with the :ref:`AbandonAction +// `, +// indicating that the server has now stopped sending quota assignments for the ``BucketId`` bucket, +// and the data plane instance should :ref:`abandon +// ` +// it. +// +// If for any reason the RLQS client doesn't receive the initial assignment for the reported bucket, +// in order to prevent memory exhaustion, the data plane will limit the time such bucket +// is retained. The exact time to wait for the initial assignment is chosen by the filter, +// and may vary based on the implementation. +// Once the duration ends, the data plane will stop reporting bucket usage, reject any enqueued +// requests, and purge the bucket from the memory. Subsequent requests matched into the bucket +// will re-initialize the bucket in the "no assignment" state, restarting the reports. +// +// Refer to Rate Limit Quota :ref:`configuration overview ` +// for further details. + +// Defines the Rate Limit Quota Service (RLQS). +service RateLimitQuotaService { + // Main communication channel: the data plane sends usage reports to the RLQS server, + // and the server asynchronously responding with the assignments. + rpc StreamRateLimitQuotas(stream RateLimitQuotaUsageReports) + returns (stream RateLimitQuotaResponse) { + } +} + +message RateLimitQuotaUsageReports { + // The usage report for a bucket. + // + // .. note:: + // Note that the first report sent for a ``BucketId`` indicates to the RLQS server that + // the RLQS client is subscribing for the future assignments for this ``BucketId``. + message BucketQuotaUsage { + // ``BucketId`` for which request quota usage is reported. + BucketId bucket_id = 1 [(validate.rules).message = {required: true}]; + + // Time elapsed since the last report. + google.protobuf.Duration time_elapsed = 2 [(validate.rules).duration = { + required: true + gt {} + }]; + + // Requests the data plane has allowed through. + uint64 num_requests_allowed = 3; + + // Requests throttled. + uint64 num_requests_denied = 4; + } + + // All quota requests must specify the domain. This enables sharing the quota + // server between different applications without fear of overlap. + // E.g., "envoy". + // + // Should only be provided in the first report, all subsequent messages on the same + // stream are considered to be in the same domain. In case the domain needs to be + // changes, close the stream, and reopen a new one with the different domain. + string domain = 1 [(validate.rules).string = {min_len: 1}]; + + // A list of quota usage reports. The list is processed by the RLQS server in the same order + // it's provided by the client. + repeated BucketQuotaUsage bucket_quota_usages = 2 [(validate.rules).repeated = {min_items: 1}]; +} + +message RateLimitQuotaResponse { + // Commands the data plane to apply one of the actions to the bucket with the + // :ref:`bucket_id `. + message BucketAction { + // Quota assignment for the bucket. Configures the rate limiting strategy and the duration + // for the given :ref:`bucket_id + // `. + // + // **Applying the first assignment to the bucket** + // + // Once the data plane receives the ``QuotaAssignmentAction``, it must send the current usage + // report for the bucket, and start rate limiting requests matched into the bucket + // using the strategy configured in the :ref:`rate_limit_strategy + // ` + // field. The assignment becomes bucket's ``active`` assignment. + // + // **Expiring the assignment** + // + // The duration of the assignment defined in the :ref:`assignment_time_to_live + // ` + // field. When the duration runs off, the assignment is ``expired``, and no longer ``active``. + // The data plane should stop applying the rate limiting strategy to the bucket, and transition + // the bucket to the "expired assignment" state. This activates the behavior configured in the + // :ref:`expired_assignment_behavior ` + // field. + // + // **Replacing the assignment** + // + // * If the rate limiting strategy is different from bucket's ``active`` assignment, or + // the current bucket assignment is ``expired``, the data plane must immediately + // end the current assignment, report the bucket usage, and apply the new assignment. + // The new assignment becomes bucket's ``active`` assignment. + // * If the rate limiting strategy is the same as the bucket's ``active`` (not ``expired``) + // assignment, the data plane should extend the duration of the ``active`` assignment + // for the duration of the new assignment provided in the :ref:`assignment_time_to_live + // ` + // field. The ``active`` assignment is considered unchanged. + message QuotaAssignmentAction { + // A duration after which the assignment is be considered ``expired``. The process of the + // expiration is described :ref:`above + // `. + // + // * If unset, the assignment has no expiration date. + // * If set to ``0``, the assignment expires immediately, forcing the client into the + // :ref:`"expired assignment" + // ` + // state. This may be used by the RLQS server in cases when it needs clients to proactively + // fall back to the pre-configured :ref:`ExpiredAssignmentBehavior + // `, + // f.e. before the server going into restart. + // + // .. attention:: + // Note that :ref:`expiring + // ` + // the assignment is not the same as :ref:`abandoning + // ` + // the assignment. While expiring the assignment just transitions the bucket to + // the "expired assignment" state; abandoning the assignment completely erases + // the bucket from the data plane memory, and stops the usage reports. + google.protobuf.Duration assignment_time_to_live = 2 [(validate.rules).duration = {gte {}}]; + + // Configures the local rate limiter for the request matched to the bucket. + // If not set, allow all requests. + type.v3.RateLimitStrategy rate_limit_strategy = 3; + } + + // Abandon action for the bucket. Indicates that the RLQS server will no longer be + // sending updates for the given :ref:`bucket_id + // `. + // + // If no requests are reported for a bucket, after some time the server considers the bucket + // inactive. The server stops tracking the bucket, and instructs the the data plane to abandon + // the bucket via this message. + // + // **Abandoning the assignment** + // + // The data plane is to erase the bucket (including its usage data) from the memory. + // It should stop tracking the bucket, and stop reporting its usage. This effectively resets + // the data plane to the state prior to matching the first request into the bucket. + // + // **Restarting the subscription** + // + // If a new request is matched into a bucket previously abandoned, the data plane must behave + // as if it has never tracked the bucket, and it's the first request matched into it: + // + // 1. The process of :ref:`subscription and reporting + // ` + // starts from the beginning. + // + // 2. The bucket transitions to the :ref:`"no assignment" + // ` + // state. + // + // 3. Once the new assignment is received, it's applied per + // "Applying the first assignment to the bucket" section of the :ref:`QuotaAssignmentAction + // `. + message AbandonAction { + } + + // ``BucketId`` for which request the action is applied. + BucketId bucket_id = 1 [(validate.rules).message = {required: true}]; + + oneof bucket_action { + option (validate.required) = true; + + // Apply the quota assignment to the bucket. + // + // Commands the data plane to apply a rate limiting strategy to the bucket. + // The process of applying and expiring the rate limiting strategy is detailed in the + // :ref:`QuotaAssignmentAction + // ` + // message. + QuotaAssignmentAction quota_assignment_action = 2; + + // Abandon the bucket. + // + // Commands the data plane to abandon the bucket. + // The process of abandoning the bucket is described in the :ref:`AbandonAction + // ` + // message. + AbandonAction abandon_action = 3; + } + } + + // An ordered list of actions to be applied to the buckets. The actions are applied in the + // given order, from top to bottom. + repeated BucketAction bucket_action = 1 [(validate.rules).repeated = {min_items: 1}]; +} + +// The identifier for the bucket. Used to match the bucket between the control plane (RLQS server), +// and the data plane (RLQS client), f.e.: +// +// * the data plane sends a usage report for requests matched into the bucket with ``BucketId`` +// to the control plane +// * the control plane sends an assignment for the bucket with ``BucketId`` to the data plane +// Bucket ID. +// +// Example: +// +// .. validated-code-block:: yaml +// :type-name: envoy.service.rate_limit_quota.v3.BucketId +// +// bucket: +// name: my_bucket +// env: staging +// +// .. note:: +// The order of ``BucketId`` keys do not matter. Buckets ``{ a: 'A', b: 'B' }`` and +// ``{ b: 'B', a: 'A' }`` are identical. +message BucketId { + map bucket = 1 [(validate.rules).map = { + min_pairs: 1 + keys {string {min_len: 1}} + values {string {min_len: 1}} + }]; +} diff --git a/xds/third_party/envoy/src/main/proto/envoy/type/matcher/v3/http_inputs.proto b/xds/third_party/envoy/src/main/proto/envoy/type/matcher/v3/http_inputs.proto new file mode 100644 index 00000000000..c90199eb618 --- /dev/null +++ b/xds/third_party/envoy/src/main/proto/envoy/type/matcher/v3/http_inputs.proto @@ -0,0 +1,71 @@ +syntax = "proto3"; + +package envoy.type.matcher.v3; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.type.matcher.v3"; +option java_outer_classname = "HttpInputsProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3;matcherv3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Common HTTP inputs] + +// Match input indicates that matching should be done on a specific request header. +// The resulting input string will be all headers for the given key joined by a comma, +// e.g. if the request contains two 'foo' headers with value 'bar' and 'baz', the input +// string will be 'bar,baz'. +// [#comment:TODO(snowp): Link to unified matching docs.] +// [#extension: envoy.matching.inputs.request_headers] +message HttpRequestHeaderMatchInput { + // The request header to match on. + string header_name = 1 + [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME strict: false}]; +} + +// Match input indicates that matching should be done on a specific request trailer. +// The resulting input string will be all headers for the given key joined by a comma, +// e.g. if the request contains two 'foo' headers with value 'bar' and 'baz', the input +// string will be 'bar,baz'. +// [#comment:TODO(snowp): Link to unified matching docs.] +// [#extension: envoy.matching.inputs.request_trailers] +message HttpRequestTrailerMatchInput { + // The request trailer to match on. + string header_name = 1 + [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME strict: false}]; +} + +// Match input indicating that matching should be done on a specific response header. +// The resulting input string will be all headers for the given key joined by a comma, +// e.g. if the response contains two 'foo' headers with value 'bar' and 'baz', the input +// string will be 'bar,baz'. +// [#comment:TODO(snowp): Link to unified matching docs.] +// [#extension: envoy.matching.inputs.response_headers] +message HttpResponseHeaderMatchInput { + // The response header to match on. + string header_name = 1 + [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME strict: false}]; +} + +// Match input indicates that matching should be done on a specific response trailer. +// The resulting input string will be all headers for the given key joined by a comma, +// e.g. if the request contains two 'foo' headers with value 'bar' and 'baz', the input +// string will be 'bar,baz'. +// [#comment:TODO(snowp): Link to unified matching docs.] +// [#extension: envoy.matching.inputs.response_trailers] +message HttpResponseTrailerMatchInput { + // The response trailer to match on. + string header_name = 1 + [(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME strict: false}]; +} + +// Match input indicates that matching should be done on a specific query parameter. +// The resulting input string will be the first query parameter for the value +// 'query_param'. +// [#extension: envoy.matching.inputs.query_params] +message HttpRequestQueryParamMatchInput { + // The query parameter to match on. + string query_param = 1 [(validate.rules).string = {min_len: 1}]; +} diff --git a/xds/third_party/envoy/src/main/proto/envoy/type/v3/http_status.proto b/xds/third_party/envoy/src/main/proto/envoy/type/v3/http_status.proto new file mode 100644 index 00000000000..ab03e1b2b72 --- /dev/null +++ b/xds/third_party/envoy/src/main/proto/envoy/type/v3/http_status.proto @@ -0,0 +1,143 @@ +syntax = "proto3"; + +package envoy.type.v3; + +import "udpa/annotations/status.proto"; +import "udpa/annotations/versioning.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.type.v3"; +option java_outer_classname = "HttpStatusProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/type/v3;typev3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: HTTP status codes] + +// HTTP response codes supported in Envoy. +// For more details: https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml +enum StatusCode { + // Empty - This code not part of the HTTP status code specification, but it is needed for proto + // `enum` type. + Empty = 0; + + Continue = 100; + + OK = 200; + + Created = 201; + + Accepted = 202; + + NonAuthoritativeInformation = 203; + + NoContent = 204; + + ResetContent = 205; + + PartialContent = 206; + + MultiStatus = 207; + + AlreadyReported = 208; + + IMUsed = 226; + + MultipleChoices = 300; + + MovedPermanently = 301; + + Found = 302; + + SeeOther = 303; + + NotModified = 304; + + UseProxy = 305; + + TemporaryRedirect = 307; + + PermanentRedirect = 308; + + BadRequest = 400; + + Unauthorized = 401; + + PaymentRequired = 402; + + Forbidden = 403; + + NotFound = 404; + + MethodNotAllowed = 405; + + NotAcceptable = 406; + + ProxyAuthenticationRequired = 407; + + RequestTimeout = 408; + + Conflict = 409; + + Gone = 410; + + LengthRequired = 411; + + PreconditionFailed = 412; + + PayloadTooLarge = 413; + + URITooLong = 414; + + UnsupportedMediaType = 415; + + RangeNotSatisfiable = 416; + + ExpectationFailed = 417; + + MisdirectedRequest = 421; + + UnprocessableEntity = 422; + + Locked = 423; + + FailedDependency = 424; + + UpgradeRequired = 426; + + PreconditionRequired = 428; + + TooManyRequests = 429; + + RequestHeaderFieldsTooLarge = 431; + + InternalServerError = 500; + + NotImplemented = 501; + + BadGateway = 502; + + ServiceUnavailable = 503; + + GatewayTimeout = 504; + + HTTPVersionNotSupported = 505; + + VariantAlsoNegotiates = 506; + + InsufficientStorage = 507; + + LoopDetected = 508; + + NotExtended = 510; + + NetworkAuthenticationRequired = 511; +} + +// HTTP status. +message HttpStatus { + option (udpa.annotations.versioning).previous_message_type = "envoy.type.HttpStatus"; + + // Supplies HTTP response code. + StatusCode code = 1 [(validate.rules).enum = {defined_only: true not_in: 0}]; +} diff --git a/xds/third_party/envoy/src/main/proto/envoy/type/v3/ratelimit_strategy.proto b/xds/third_party/envoy/src/main/proto/envoy/type/v3/ratelimit_strategy.proto new file mode 100644 index 00000000000..a86da55b854 --- /dev/null +++ b/xds/third_party/envoy/src/main/proto/envoy/type/v3/ratelimit_strategy.proto @@ -0,0 +1,79 @@ +syntax = "proto3"; + +package envoy.type.v3; + +import "envoy/type/v3/ratelimit_unit.proto"; +import "envoy/type/v3/token_bucket.proto"; + +import "xds/annotations/v3/status.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.type.v3"; +option java_outer_classname = "RatelimitStrategyProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/type/v3;typev3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; +option (xds.annotations.v3.file_status).work_in_progress = true; + +// [#protodoc-title: Rate Limit Strategies] + +message RateLimitStrategy { + // Choose between allow all and deny all. + enum BlanketRule { + ALLOW_ALL = 0; + DENY_ALL = 1; + } + + // Best-effort limit of the number of requests per time unit. + // + // Allows to specify the desired requests per second (RPS, QPS), requests per minute (QPM, RPM), + // etc., without specifying a rate limiting algorithm implementation. + // + // ``RequestsPerTimeUnit`` strategy does not demand any specific rate limiting algorithm to be + // used (in contrast to the :ref:`TokenBucket `, + // for example). It implies that the implementation details of rate limiting algorithm are + // irrelevant as long as the configured number of "requests per time unit" is achieved. + // + // Note that the ``TokenBucket`` is still a valid implementation of the ``RequestsPerTimeUnit`` + // strategy, and may be chosen to enforce the rate limit. However, there's no guarantee it will be + // the ``TokenBucket`` in particular, and not the Leaky Bucket, the Sliding Window, or any other + // rate limiting algorithm that fulfills the requirements. + message RequestsPerTimeUnit { + // The desired number of requests per :ref:`time_unit + // ` to allow. + // If set to ``0``, deny all (equivalent to ``BlanketRule.DENY_ALL``). + // + // .. note:: + // Note that the algorithm implementation determines the course of action for the requests + // over the limit. As long as the ``requests_per_time_unit`` converges on the desired value, + // it's allowed to treat this field as a soft-limit: allow bursts, redistribute the allowance + // over time, etc. + // + uint64 requests_per_time_unit = 1; + + // The unit of time. Ignored when :ref:`requests_per_time_unit + // ` + // is ``0`` (deny all). + RateLimitUnit time_unit = 2 [(validate.rules).enum = {defined_only: true}]; + } + + oneof strategy { + option (validate.required) = true; + + // Allow or Deny the requests. + // If unset, allow all. + BlanketRule blanket_rule = 1 [(validate.rules).enum = {defined_only: true}]; + + // Best-effort limit of the number of requests per time unit, f.e. requests per second. + // Does not prescribe any specific rate limiting algorithm, see :ref:`RequestsPerTimeUnit + // ` for details. + RequestsPerTimeUnit requests_per_time_unit = 2; + + // Limit the requests by consuming tokens from the Token Bucket. + // Allow the same number of requests as the number of tokens available in + // the token bucket. + TokenBucket token_bucket = 3; + } +} diff --git a/xds/third_party/envoy/src/main/proto/envoy/type/v3/ratelimit_unit.proto b/xds/third_party/envoy/src/main/proto/envoy/type/v3/ratelimit_unit.proto new file mode 100644 index 00000000000..1a96497926d --- /dev/null +++ b/xds/third_party/envoy/src/main/proto/envoy/type/v3/ratelimit_unit.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; + +package envoy.type.v3; + +import "udpa/annotations/status.proto"; + +option java_package = "io.envoyproxy.envoy.type.v3"; +option java_outer_classname = "RatelimitUnitProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/type/v3;typev3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Ratelimit Time Unit] + +// Identifies the unit of of time for rate limit. +enum RateLimitUnit { + // The time unit is not known. + UNKNOWN = 0; + + // The time unit representing a second. + SECOND = 1; + + // The time unit representing a minute. + MINUTE = 2; + + // The time unit representing an hour. + HOUR = 3; + + // The time unit representing a day. + DAY = 4; + + // The time unit representing a month. + MONTH = 5; + + // The time unit representing a year. + YEAR = 6; +} diff --git a/xds/third_party/envoy/src/main/proto/envoy/type/v3/token_bucket.proto b/xds/third_party/envoy/src/main/proto/envoy/type/v3/token_bucket.proto new file mode 100644 index 00000000000..157a271efc9 --- /dev/null +++ b/xds/third_party/envoy/src/main/proto/envoy/type/v3/token_bucket.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package envoy.type.v3; + +import "google/protobuf/duration.proto"; +import "google/protobuf/wrappers.proto"; + +import "udpa/annotations/status.proto"; +import "udpa/annotations/versioning.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.type.v3"; +option java_outer_classname = "TokenBucketProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/type/v3;typev3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Token bucket] + +// Configures a token bucket, typically used for rate limiting. +message TokenBucket { + option (udpa.annotations.versioning).previous_message_type = "envoy.type.TokenBucket"; + + // The maximum tokens that the bucket can hold. This is also the number of tokens that the bucket + // initially contains. + uint32 max_tokens = 1 [(validate.rules).uint32 = {gt: 0}]; + + // The number of tokens added to the bucket during each fill interval. If not specified, defaults + // to a single token. + google.protobuf.UInt32Value tokens_per_fill = 2 [(validate.rules).uint32 = {gt: 0}]; + + // The fill interval that tokens are added to the bucket. During each fill interval + // ``tokens_per_fill`` are added to the bucket. The bucket will never contain more than + // ``max_tokens`` tokens. + google.protobuf.Duration fill_interval = 3 [(validate.rules).duration = { + required: true + gt {} + }]; +} diff --git a/xds/third_party/xds/import.sh b/xds/third_party/xds/import.sh index 44f9ad12ed4..9e4bf71d52f 100755 --- a/xds/third_party/xds/import.sh +++ b/xds/third_party/xds/import.sh @@ -45,9 +45,12 @@ xds/core/v3/resource_locator.proto xds/core/v3/resource_name.proto xds/data/orca/v3/orca_load_report.proto xds/service/orca/v3/orca.proto +xds/type/matcher/v3/cel.proto xds/type/matcher/v3/matcher.proto xds/type/matcher/v3/regex.proto xds/type/matcher/v3/string.proto +xds/type/v3/cel.proto +xds/type/matcher/v3/http_inputs.proto xds/type/v3/typed_struct.proto ) diff --git a/xds/third_party/xds/src/main/proto/xds/type/matcher/v3/cel.proto b/xds/third_party/xds/src/main/proto/xds/type/matcher/v3/cel.proto new file mode 100644 index 00000000000..b1ad1faa281 --- /dev/null +++ b/xds/third_party/xds/src/main/proto/xds/type/matcher/v3/cel.proto @@ -0,0 +1,42 @@ +syntax = "proto3"; + +package xds.type.matcher.v3; + +import "xds/annotations/v3/status.proto"; +import "xds/type/v3/cel.proto"; + +import "validate/validate.proto"; + +option java_package = "com.github.xds.type.matcher.v3"; +option java_outer_classname = "CelProto"; +option java_multiple_files = true; +option go_package = "github.com/cncf/xds/go/xds/type/matcher/v3"; + +option (xds.annotations.v3.file_status).work_in_progress = true; + +// [#protodoc-title: Common Expression Language (CEL) matchers] + +// Performs a match by evaluating a `Common Expression Language +// `_ (CEL) expression against the standardized set of +// :ref:`HTTP attributes ` specified via ``HttpAttributesCelMatchInput``. +// +// .. attention:: +// +// The match is ``true``, iff the result of the evaluation is a bool AND true. +// In all other cases, the match is ``false``, including but not limited to: non-bool types, +// ``false``, ``null``,`` int(1)``, etc. +// In case CEL expression raises an error, the result of the evaluation is interpreted "no match". +// +// Refer to :ref:`Unified Matcher API ` documentation +// for usage details. +// +// [#comment:TODO(sergiitk): Link HttpAttributesMatchInput + usage example.] +// [#comment:TODO(sergiitk): When implemented, add the extension tag.] +message CelMatcher { + // Either parsed or checked representation of the CEL program. + type.v3.CelExpression expr_match = 1 [(validate.rules).message = {required: true}]; + + // Free-form description of the CEL AST, e.g. the original expression text, to be + // used for debugging assistance. + string description = 2; +} diff --git a/xds/third_party/xds/src/main/proto/xds/type/matcher/v3/http_inputs.proto b/xds/third_party/xds/src/main/proto/xds/type/matcher/v3/http_inputs.proto new file mode 100644 index 00000000000..0dd80cd6f66 --- /dev/null +++ b/xds/third_party/xds/src/main/proto/xds/type/matcher/v3/http_inputs.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +package xds.type.matcher.v3; + +import "xds/annotations/v3/status.proto"; + +option java_package = "com.github.xds.type.matcher.v3"; +option java_outer_classname = "HttpInputsProto"; +option java_multiple_files = true; +option go_package = "github.com/cncf/xds/go/xds/type/matcher/v3"; + +option (xds.annotations.v3.file_status).work_in_progress = true; + +// [#protodoc-title: Common HTTP Inputs] + +// Specifies that matching should be performed on the set of :ref:`HTTP attributes +// `. +// +// The attributes will be exposed via `Common Expression Language +// `_ runtime to associated CEL matcher. +// +// Refer to :ref:`Unified Matcher API ` documentation +// for usage details. +// +// [#comment:TODO(sergiitk): When implemented, add the extension tag.] +message HttpAttributesCelMatchInput { +} diff --git a/xds/third_party/xds/src/main/proto/xds/type/v3/cel.proto b/xds/third_party/xds/src/main/proto/xds/type/v3/cel.proto new file mode 100644 index 00000000000..df4f81d90d2 --- /dev/null +++ b/xds/third_party/xds/src/main/proto/xds/type/v3/cel.proto @@ -0,0 +1,70 @@ +syntax = "proto3"; + +package xds.type.v3; + +import "google/api/expr/v1alpha1/checked.proto"; +import "google/api/expr/v1alpha1/syntax.proto"; +import "cel/expr/checked.proto"; +import "cel/expr/syntax.proto"; +import "google/protobuf/wrappers.proto"; + +import "xds/annotations/v3/status.proto"; + +import "validate/validate.proto"; + +option java_package = "com.github.xds.type.v3"; +option java_outer_classname = "CelProto"; +option java_multiple_files = true; +option go_package = "github.com/cncf/xds/go/xds/type/v3"; + +option (xds.annotations.v3.file_status).work_in_progress = true; + +// [#protodoc-title: Common Expression Language (CEL)] + +// Either parsed or checked representation of the `Common Expression Language +// `_ (CEL) program. +message CelExpression { + oneof expr_specifier { + // Parsed expression in abstract syntax tree (AST) form. + // + // Deprecated -- use ``cel_expr_parsed`` field instead. + // If ``cel_expr_parsed`` or ``cel_expr_checked`` is set, this field is not used. + google.api.expr.v1alpha1.ParsedExpr parsed_expr = 1 [deprecated = true]; + + // Parsed expression in abstract syntax tree (AST) form that has been successfully type checked. + // + // Deprecated -- use ``cel_expr_checked`` field instead. + // If ``cel_expr_parsed`` or ``cel_expr_checked`` is set, this field is not used. + google.api.expr.v1alpha1.CheckedExpr checked_expr = 2 [deprecated = true]; + } + + // Parsed expression in abstract syntax tree (AST) form. + // + // If ``cel_expr_checked`` is set, this field is not used. + cel.expr.ParsedExpr cel_expr_parsed = 3; + + // Parsed expression in abstract syntax tree (AST) form that has been successfully type checked. + // + // If set, takes precedence over ``cel_expr_parsed``. + cel.expr.CheckedExpr cel_expr_checked = 4; +} + +// Extracts a string by evaluating a `Common Expression Language +// `_ (CEL) expression against the standardized set of +// :ref:`HTTP attributes `. +// +// .. attention:: +// +// Besides CEL evaluation raising an error explicitly, CEL program returning a type other than +// the ``string``, or not returning anything, are considered an error as well. +// +// [#comment:TODO(sergiitk): When implemented, add the extension tag.] +message CelExtractString { + // The CEL expression used to extract a string from the CEL environment. + // the "subject string") that should be replaced. + CelExpression expr_extract = 1 [(validate.rules).message = {required: true}]; + + // If CEL expression evaluates to an error, this value is be returned to the caller. + // If not set, the error is propagated to the caller. + google.protobuf.StringValue default_value = 2; +} From e567b4427aaef554de103de7c82c4448b6944d3d Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Fri, 2 Aug 2024 11:40:02 -0700 Subject: [PATCH 016/103] core: Don't reuse channels in PickFirstLeafLB test PickFirstLeafLB uses channel reference equality to see if it has re-created subchannels. The tests reusing channels breaks the expected invariant. --- .../PickFirstLeafLoadBalancerTest.java | 166 +++++++++++------- 1 file changed, 100 insertions(+), 66 deletions(-) diff --git a/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java b/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java index 7aae0c2731a..335d199d8b1 100644 --- a/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java +++ b/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java @@ -123,16 +123,14 @@ public void uncaughtException(Thread t, Throwable e) { private ArgumentCaptor createArgsCaptor; @Captor private ArgumentCaptor stateListenerCaptor; - private final Helper mockHelper = mock(Helper.class, delegatesTo(new MockHelperImpl())); - @Mock + private Helper mockHelper; private FakeSubchannel mockSubchannel1; - @Mock + private FakeSubchannel mockSubchannel1n2; private FakeSubchannel mockSubchannel2; - @Mock + private FakeSubchannel mockSubchannel2n2; private FakeSubchannel mockSubchannel3; - @Mock + private FakeSubchannel mockSubchannel3n2; private FakeSubchannel mockSubchannel4; - @Mock private FakeSubchannel mockSubchannel5; @Mock // This LoadBalancer doesn't use any of the arg fields, as verified in tearDown(). private PickSubchannelArgs mockArgs; @@ -150,23 +148,28 @@ public void setUp() { SocketAddress addr = new FakeSocketAddress("server" + i); servers.add(new EquivalentAddressGroup(addr)); } - mockSubchannel1 = mock(FakeSubchannel.class); - mockSubchannel2 = mock(FakeSubchannel.class); - mockSubchannel3 = mock(FakeSubchannel.class); - mockSubchannel4 = mock(FakeSubchannel.class); - mockSubchannel5 = mock(FakeSubchannel.class); - when(mockSubchannel1.getAttributes()).thenReturn(Attributes.EMPTY); - when(mockSubchannel2.getAttributes()).thenReturn(Attributes.EMPTY); - when(mockSubchannel3.getAttributes()).thenReturn(Attributes.EMPTY); - when(mockSubchannel4.getAttributes()).thenReturn(Attributes.EMPTY); - when(mockSubchannel5.getAttributes()).thenReturn(Attributes.EMPTY); - - when(mockSubchannel1.getAllAddresses()).thenReturn(Lists.newArrayList(servers.get(0))); - when(mockSubchannel2.getAllAddresses()).thenReturn(Lists.newArrayList(servers.get(1))); - when(mockSubchannel3.getAllAddresses()).thenReturn(Lists.newArrayList(servers.get(2))); - when(mockSubchannel4.getAllAddresses()).thenReturn(Lists.newArrayList(servers.get(3))); - when(mockSubchannel5.getAllAddresses()).thenReturn(Lists.newArrayList(servers.get(4))); - + mockSubchannel1 = mock(FakeSubchannel.class, delegatesTo( + new FakeSubchannel(Arrays.asList(servers.get(0)), Attributes.EMPTY))); + mockSubchannel1n2 = mock(FakeSubchannel.class, delegatesTo( + new FakeSubchannel(Arrays.asList(servers.get(0)), Attributes.EMPTY))); + mockSubchannel2 = mock(FakeSubchannel.class, delegatesTo( + new FakeSubchannel(Arrays.asList(servers.get(1)), Attributes.EMPTY))); + mockSubchannel2n2 = mock(FakeSubchannel.class, delegatesTo( + new FakeSubchannel(Arrays.asList(servers.get(1)), Attributes.EMPTY))); + mockSubchannel3 = mock(FakeSubchannel.class, delegatesTo( + new FakeSubchannel(Arrays.asList(servers.get(2)), Attributes.EMPTY))); + mockSubchannel3n2 = mock(FakeSubchannel.class, delegatesTo( + new FakeSubchannel(Arrays.asList(servers.get(2)), Attributes.EMPTY))); + mockSubchannel4 = mock(FakeSubchannel.class, delegatesTo( + new FakeSubchannel(Arrays.asList(servers.get(3)), Attributes.EMPTY))); + mockSubchannel5 = mock(FakeSubchannel.class, delegatesTo( + new FakeSubchannel(Arrays.asList(servers.get(4)), Attributes.EMPTY))); + + mockHelper = mock(Helper.class, delegatesTo(new MockHelperImpl(Arrays.asList( + mockSubchannel1, mockSubchannel1n2, + mockSubchannel2, mockSubchannel2n2, + mockSubchannel3, mockSubchannel3n2, + mockSubchannel4, mockSubchannel5)))); loadBalancer = new PickFirstLeafLoadBalancer(mockHelper); } @@ -251,14 +254,14 @@ public void pickAfterResolved_shuffle() { PickResult pick2 = pickerCaptor.getValue().pickSubchannel(mockArgs); assertEquals(pick1, pick2); verifyNoMoreInteractions(mockHelper); - assertThat(pick1.toString()).contains("subchannel=null"); + assertThat(pick1.getSubchannel()).isNull(); stateListener2.onSubchannelState(ConnectivityStateInfo.forNonError(READY)); verify(mockHelper).updateBalancingState(eq(READY), pickerCaptor.capture()); PickResult pick3 = pickerCaptor.getValue().pickSubchannel(mockArgs); PickResult pick4 = pickerCaptor.getValue().pickSubchannel(mockArgs); assertEquals(pick3, pick4); - assertThat(pick3.toString()).contains("subchannel=Mock"); + assertThat(pick3.getSubchannel()).isEqualTo(mockSubchannel2); } @Test @@ -569,7 +572,7 @@ public void pickWithDupAddressesUpDownUp() { InOrder inOrder = inOrder(mockHelper); SocketAddress socketAddress = servers.get(0).getAddresses().get(0); EquivalentAddressGroup badEag = new EquivalentAddressGroup( - Lists.newArrayList(socketAddress, socketAddress), affinity); + Lists.newArrayList(socketAddress, socketAddress)); List newServers = Lists.newArrayList(badEag); loadBalancer.acceptResolvedAddresses( @@ -727,7 +730,7 @@ public void nameResolutionSuccessAfterError() { @Test public void nameResolutionTemporaryError() { List newServers = Lists.newArrayList(servers.get(0)); - InOrder inOrder = inOrder(mockHelper, mockSubchannel1); + InOrder inOrder = inOrder(mockHelper, mockSubchannel1, mockSubchannel1n2); loadBalancer.acceptResolvedAddresses( ResolvedAddresses.newBuilder().setAddresses(newServers).setAttributes(affinity).build()); inOrder.verify(mockSubchannel1).start(stateListenerCaptor.capture()); @@ -744,14 +747,15 @@ public void nameResolutionTemporaryError() { loadBalancer.acceptResolvedAddresses( ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(affinity).build()); inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), pickerCaptor.capture()); - inOrder.verify(mockSubchannel1).start(stateListenerCaptor.capture()); + inOrder.verify(mockSubchannel1n2).start(stateListenerCaptor.capture()); SubchannelStateListener stateListener2 = stateListenerCaptor.getValue(); assertNull(pickerCaptor.getValue().pickSubchannel(mockArgs).getSubchannel()); stateListener2.onSubchannelState(ConnectivityStateInfo.forNonError(READY)); inOrder.verify(mockHelper).updateBalancingState(eq(READY), pickerCaptor.capture()); - assertEquals(mockSubchannel1, pickerCaptor.getValue().pickSubchannel(mockArgs).getSubchannel()); + assertEquals(mockSubchannel1n2, + pickerCaptor.getValue().pickSubchannel(mockArgs).getSubchannel()); } @@ -1027,7 +1031,7 @@ public void updateAddresses_disjoint_connecting() { @Test public void updateAddresses_disjoint_ready_twice() { InOrder inOrder = inOrder(mockHelper, mockSubchannel1, mockSubchannel2, - mockSubchannel3, mockSubchannel4); + mockSubchannel3, mockSubchannel4, mockSubchannel1n2, mockSubchannel2n2); // Creating first set of endpoints/addresses List oldServers = Lists.newArrayList(servers.get(0), servers.get(1)); SubchannelStateListener stateListener2 = null; @@ -1126,7 +1130,7 @@ public void updateAddresses_disjoint_ready_twice() { ResolvedAddresses.newBuilder().setAddresses(newestServers).setAttributes(affinity).build()); inOrder.verify(mockSubchannel3).shutdown(); inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), pickerCaptor.capture()); - inOrder.verify(mockSubchannel1).start(stateListenerCaptor.capture()); + inOrder.verify(mockSubchannel1n2).start(stateListenerCaptor.capture()); stateListener = stateListenerCaptor.getValue(); assertEquals(CONNECTING, loadBalancer.getConcludedConnectivityState()); picker = pickerCaptor.getValue(); @@ -1135,7 +1139,7 @@ public void updateAddresses_disjoint_ready_twice() { assertEquals(picker.pickSubchannel(mockArgs), picker.pickSubchannel(mockArgs)); // But the picker calls requestConnection() only once - inOrder.verify(mockSubchannel1).requestConnection(); + inOrder.verify(mockSubchannel1n2).requestConnection(); assertEquals(PickResult.withNoResult(), pickerCaptor.getValue().pickSubchannel(mockArgs)); assertEquals(CONNECTING, loadBalancer.getConcludedConnectivityState()); @@ -1150,23 +1154,24 @@ public void updateAddresses_disjoint_ready_twice() { stateListener.onSubchannelState(ConnectivityStateInfo.forTransientFailure(CONNECTION_ERROR)); // Starting connection attempt to address 2 - if (!enableHappyEyeballs) { - inOrder.verify(mockSubchannel2).start(stateListenerCaptor.capture()); - stateListener2 = stateListenerCaptor.getValue(); - } - inOrder.verify(mockSubchannel2).requestConnection(); + FakeSubchannel mockSubchannel2Attempt = + enableHappyEyeballs ? mockSubchannel2n2 : mockSubchannel2; + inOrder.verify(mockSubchannel2Attempt).start(stateListenerCaptor.capture()); + stateListener2 = stateListenerCaptor.getValue(); + inOrder.verify(mockSubchannel2Attempt).requestConnection(); // Connection attempt to address 2 is successful stateListener2.onSubchannelState(ConnectivityStateInfo.forNonError(READY)); assertEquals(READY, loadBalancer.getConcludedConnectivityState()); - inOrder.verify(mockSubchannel1).shutdown(); + inOrder.verify(mockSubchannel1n2).shutdown(); // Successful connection shuts down other subchannel inOrder.verify(mockHelper).updateBalancingState(eq(READY), pickerCaptor.capture()); picker = pickerCaptor.getValue(); // Verify that picker still returns correct subchannel - assertEquals(PickResult.withSubchannel(mockSubchannel2), picker.pickSubchannel(mockArgs)); + assertEquals( + PickResult.withSubchannel(mockSubchannel2Attempt), picker.pickSubchannel(mockArgs)); } @Test @@ -2048,7 +2053,7 @@ public void recreate_shutdown_subchannel() { // Starting first connection attempt InOrder inOrder = inOrder(mockHelper, mockSubchannel1, mockSubchannel2, - mockSubchannel3, mockSubchannel4); // captor: captures + mockSubchannel3, mockSubchannel4, mockSubchannel1n2); // captor: captures // Creating first set of endpoints/addresses List addrs = @@ -2084,9 +2089,9 @@ public void recreate_shutdown_subchannel() { // Calling pickSubchannel() requests a connection. assertEquals(picker.pickSubchannel(mockArgs), picker.pickSubchannel(mockArgs)); - inOrder.verify(mockSubchannel1).start(stateListenerCaptor.capture()); + inOrder.verify(mockSubchannel1n2).start(stateListenerCaptor.capture()); SubchannelStateListener stateListener3 = stateListenerCaptor.getValue(); - inOrder.verify(mockSubchannel1).requestConnection(); + inOrder.verify(mockSubchannel1n2).requestConnection(); when(mockSubchannel1.getAllAddresses()).thenReturn(Lists.newArrayList(servers.get(0))); // gives the same result when called twice @@ -2101,7 +2106,7 @@ public void recreate_shutdown_subchannel() { // second subchannel connection attempt succeeds inOrder.verify(mockSubchannel2).requestConnection(); stateListener2.onSubchannelState(ConnectivityStateInfo.forNonError(READY)); - inOrder.verify(mockSubchannel1).shutdown(); + inOrder.verify(mockSubchannel1n2).shutdown(); inOrder.verify(mockHelper).updateBalancingState(eq(READY), pickerCaptor.capture()); assertEquals(READY, loadBalancer.getConcludedConnectivityState()); @@ -2146,7 +2151,7 @@ public void shutdown() { public void ready_then_transient_failure_again() { // Starting first connection attempt InOrder inOrder = inOrder(mockHelper, mockSubchannel1, mockSubchannel2, - mockSubchannel3, mockSubchannel4); // captor: captures + mockSubchannel3, mockSubchannel4, mockSubchannel1n2); // captor: captures // Creating first set of endpoints/addresses List addrs = @@ -2183,9 +2188,9 @@ public void ready_then_transient_failure_again() { // Calling pickSubchannel() requests a connection, gives the same result when called twice. assertEquals(picker.pickSubchannel(mockArgs), picker.pickSubchannel(mockArgs)); - inOrder.verify(mockSubchannel1).start(stateListenerCaptor.capture()); + inOrder.verify(mockSubchannel1n2).start(stateListenerCaptor.capture()); SubchannelStateListener stateListener3 = stateListenerCaptor.getValue(); - inOrder.verify(mockSubchannel1).requestConnection(); + inOrder.verify(mockSubchannel1n2).requestConnection(); when(mockSubchannel3.getAllAddresses()).thenReturn(Lists.newArrayList(servers.get(0))); stateListener3.onSubchannelState(ConnectivityStateInfo.forNonError(CONNECTING)); inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), pickerCaptor.capture()); @@ -2201,7 +2206,7 @@ public void ready_then_transient_failure_again() { assertEquals(READY, loadBalancer.getConcludedConnectivityState()); // verify that picker returns correct subchannel - inOrder.verify(mockSubchannel1).shutdown(); + inOrder.verify(mockSubchannel1n2).shutdown(); inOrder.verify(mockHelper).updateBalancingState(eq(READY), pickerCaptor.capture()); picker = pickerCaptor.getValue(); assertEquals(PickResult.withSubchannel(mockSubchannel2), picker.pickSubchannel(mockArgs)); @@ -2309,7 +2314,8 @@ public void happy_eyeballs_connection_results_happen_after_get_to_end() { public void happy_eyeballs_pick_pushes_index_over_end() { Assume.assumeTrue(enableHappyEyeballs); // This test is only for happy eyeballs - InOrder inOrder = inOrder(mockHelper, mockSubchannel1, mockSubchannel2, mockSubchannel3); + InOrder inOrder = inOrder(mockHelper, mockSubchannel1, mockSubchannel2, mockSubchannel3, + mockSubchannel2n2, mockSubchannel3n2); Status error = Status.UNAUTHENTICATED.withDescription("simulated failure"); List addrs = @@ -2359,9 +2365,9 @@ public void happy_eyeballs_pick_pushes_index_over_end() { // Try pushing after end with just picks listeners[0].onSubchannelState(ConnectivityStateInfo.forNonError(READY)); - for (SubchannelStateListener listener : listeners) { - listener.onSubchannelState(ConnectivityStateInfo.forNonError(IDLE)); - } + verify(mockSubchannel2).shutdown(); + verify(mockSubchannel3).shutdown(); + listeners[0].onSubchannelState(ConnectivityStateInfo.forNonError(IDLE)); loadBalancer.acceptResolvedAddresses( ResolvedAddresses.newBuilder().setAddresses(addrs).setAttributes(affinity).build()); inOrder.verify(mockHelper).updateBalancingState(eq(IDLE), pickerCaptor.capture()); @@ -2372,11 +2378,14 @@ public void happy_eyeballs_pick_pushes_index_over_end() { } assertEquals(IDLE, loadBalancer.getConcludedConnectivityState()); - for (SubchannelStateListener listener : listeners) { - listener.onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); - } + listeners[0].onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); + inOrder.verify(mockSubchannel2n2).start(stateListenerCaptor.capture()); + stateListenerCaptor.getValue().onSubchannelState( + ConnectivityStateInfo.forTransientFailure(error)); + inOrder.verify(mockSubchannel3n2).start(stateListenerCaptor.capture()); + stateListenerCaptor.getValue().onSubchannelState( + ConnectivityStateInfo.forTransientFailure(error)); assertEquals(TRANSIENT_FAILURE, loadBalancer.getConcludedConnectivityState()); - } @Test @@ -2571,9 +2580,22 @@ private static class FakeSocketAddress extends SocketAddress { @Override public String toString() { - return "FakeSocketAddress-" + name; + return "FakeSocketAddress(" + name + ")"; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof FakeSocketAddress)) { + return false; + } + FakeSocketAddress that = (FakeSocketAddress) o; + return this.name.equals(that.name); } + @Override + public int hashCode() { + return name.hashCode(); + } } private void forwardTimeByConnectionDelay() { @@ -2631,15 +2653,26 @@ public void updateAddresses(List addrs) { @Override public void shutdown() { + listener.onSubchannelState(ConnectivityStateInfo.forNonError(SHUTDOWN)); } @Override public void requestConnection() { - listener.onSubchannelState(ConnectivityStateInfo.forNonError(CONNECTING)); + } + + @Override + public String toString() { + return "FakeSubchannel@" + hashCode() + "(" + eags + ")"; } } private class MockHelperImpl extends LoadBalancer.Helper { + private final List subchannels; + + public MockHelperImpl(List subchannels) { + this.subchannels = new ArrayList(subchannels); + } + @Override public ManagedChannel createOobChannel(EquivalentAddressGroup eag, String authority) { return null; @@ -2672,16 +2705,17 @@ public void refreshNameResolution() { @Override public Subchannel createSubchannel(CreateSubchannelArgs args) { - SocketAddress addr = args.getAddresses().get(0).getAddresses().get(0); - List fakeSubchannels = - Arrays.asList(mockSubchannel1, mockSubchannel2, mockSubchannel3, mockSubchannel4, - mockSubchannel5); - for (int i = 1; i <= 5; i++) { - if (addr.toString().equals(new FakeSocketAddress("server" + i).toString())) { - return fakeSubchannels.get(i - 1); + for (int i = 0; i < subchannels.size(); i++) { + Subchannel subchannel = subchannels.get(i); + List addrs = subchannel.getAllAddresses(); + verify(subchannel, atLeast(1)).getAllAddresses(); // ignore the interaction + if (!args.getAddresses().equals(addrs)) { + continue; } + subchannels.remove(i); + return subchannel; } - throw new IllegalArgumentException("Unexpected address: " + addr); + throw new IllegalArgumentException("Unexpected addresses: " + args.getAddresses()); } } -} \ No newline at end of file +} From f9b072cfe24daf3661994bd3a83825bee4069927 Mon Sep 17 00:00:00 2001 From: Kannan J Date: Sat, 3 Aug 2024 01:05:44 +0530 Subject: [PATCH 017/103] Netty upgrade to 4.1.110 in grpc-java (#11273) * Bump Netty to 4.1.110.Final. --- MODULE.bazel | 30 ++++++++-------- README.md | 36 +++++++++---------- SECURITY.md | 3 +- build.gradle | 2 +- .../golden/TestDeprecatedService.java.txt | 2 +- compiler/src/test/golden/TestService.java.txt | 2 +- .../main/java/io/grpc/internal/GrpcUtil.java | 2 +- examples/android/clientcache/app/build.gradle | 10 +++--- examples/android/helloworld/app/build.gradle | 8 ++--- examples/android/routeguide/app/build.gradle | 8 ++--- examples/android/strictmode/app/build.gradle | 8 ++--- examples/build.gradle | 2 +- examples/example-alts/build.gradle | 2 +- examples/example-debug/build.gradle | 2 +- examples/example-debug/pom.xml | 4 +-- examples/example-gauth/build.gradle | 2 +- examples/example-gauth/pom.xml | 4 +-- .../build.gradle | 2 +- .../example-gcp-observability/build.gradle | 2 +- examples/example-hostname/build.gradle | 2 +- examples/example-hostname/pom.xml | 4 +-- examples/example-jwt-auth/build.gradle | 2 +- examples/example-jwt-auth/pom.xml | 4 +-- examples/example-oauth/build.gradle | 2 +- examples/example-oauth/pom.xml | 4 +-- examples/example-opentelemetry/build.gradle | 2 +- examples/example-orca/build.gradle | 2 +- examples/example-reflection/build.gradle | 2 +- examples/example-servlet/build.gradle | 2 +- examples/example-tls/build.gradle | 2 +- examples/example-tls/pom.xml | 4 +-- examples/example-xds/build.gradle | 2 +- examples/pom.xml | 4 +-- gradle/libs.versions.toml | 5 +-- .../io/grpc/netty/shaded/ShadingTest.java | 2 +- repositories.bzl | 28 +++++++-------- 36 files changed, 103 insertions(+), 101 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index 2b5d85490f3..8260788c5cb 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -2,7 +2,7 @@ module( name = "grpc-java", compatibility_level = 0, repo_name = "io_grpc_grpc_java", - version = "1.67.0-SNAPSHOT", # CURRENT_GRPC_VERSION + version = "1.66.0-SNAPSHOT", # CURRENT_GRPC_VERSION ) # GRPC_DEPS_START @@ -22,20 +22,20 @@ IO_GRPC_GRPC_JAVA_ARTIFACTS = [ "com.google.truth:truth:1.4.2", "com.squareup.okhttp:okhttp:2.7.5", "com.squareup.okio:okio:2.10.0", # 3.0+ needs swapping to -jvm; need work to avoid flag-day - "io.netty:netty-buffer:4.1.100.Final", - "io.netty:netty-codec-http2:4.1.100.Final", - "io.netty:netty-codec-http:4.1.100.Final", - "io.netty:netty-codec-socks:4.1.100.Final", - "io.netty:netty-codec:4.1.100.Final", - "io.netty:netty-common:4.1.100.Final", - "io.netty:netty-handler-proxy:4.1.100.Final", - "io.netty:netty-handler:4.1.100.Final", - "io.netty:netty-resolver:4.1.100.Final", - "io.netty:netty-tcnative-boringssl-static:2.0.61.Final", - "io.netty:netty-tcnative-classes:2.0.61.Final", - "io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.100.Final", - "io.netty:netty-transport-native-unix-common:4.1.100.Final", - "io.netty:netty-transport:4.1.100.Final", + "io.netty:netty-buffer:4.1.110.Final", + "io.netty:netty-codec-http2:4.1.110.Final", + "io.netty:netty-codec-http:4.1.110.Final", + "io.netty:netty-codec-socks:4.1.110.Final", + "io.netty:netty-codec:4.1.110.Final", + "io.netty:netty-common:4.1.110.Final", + "io.netty:netty-handler-proxy:4.1.110.Final", + "io.netty:netty-handler:4.1.110.Final", + "io.netty:netty-resolver:4.1.110.Final", + "io.netty:netty-tcnative-boringssl-static:2.0.65.Final", + "io.netty:netty-tcnative-classes:2.0.65.Final", + "io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.110.Final", + "io.netty:netty-transport-native-unix-common:4.1.110.Final", + "io.netty:netty-transport:4.1.110.Final", "io.opencensus:opencensus-api:0.31.0", "io.opencensus:opencensus-contrib-grpc-metrics:0.31.0", "io.perfmark:perfmark-api:0.27.0", diff --git a/README.md b/README.md index fef37c1c3bb..cb38ad66394 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,8 @@ For a guided tour, take a look at the [quick start guide](https://grpc.io/docs/languages/java/quickstart) or the more explanatory [gRPC basics](https://grpc.io/docs/languages/java/basics). -The [examples](https://github.com/grpc/grpc-java/tree/v1.65.0/examples) and the -[Android example](https://github.com/grpc/grpc-java/tree/v1.65.0/examples/android) +The [examples](https://github.com/grpc/grpc-java/tree/v1.66.0/examples) and the +[Android example](https://github.com/grpc/grpc-java/tree/v1.66.0/examples/android) are standalone projects that showcase the usage of gRPC. Download @@ -56,18 +56,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`: io.grpc grpc-netty-shaded - 1.65.0 + 1.66.0 runtime io.grpc grpc-protobuf - 1.65.0 + 1.66.0 io.grpc grpc-stub - 1.65.0 + 1.66.0 org.apache.tomcat @@ -79,18 +79,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`: Or for Gradle with non-Android, add to your dependencies: ```gradle -runtimeOnly 'io.grpc:grpc-netty-shaded:1.65.0' -implementation 'io.grpc:grpc-protobuf:1.65.0' -implementation 'io.grpc:grpc-stub:1.65.0' +runtimeOnly 'io.grpc:grpc-netty-shaded:1.66.0' +implementation 'io.grpc:grpc-protobuf:1.66.0' +implementation 'io.grpc:grpc-stub:1.66.0' compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+ ``` For Android client, use `grpc-okhttp` instead of `grpc-netty-shaded` and `grpc-protobuf-lite` instead of `grpc-protobuf`: ```gradle -implementation 'io.grpc:grpc-okhttp:1.65.0' -implementation 'io.grpc:grpc-protobuf-lite:1.65.0' -implementation 'io.grpc:grpc-stub:1.65.0' +implementation 'io.grpc:grpc-okhttp:1.66.0' +implementation 'io.grpc:grpc-protobuf-lite:1.66.0' +implementation 'io.grpc:grpc-stub:1.66.0' compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+ ``` @@ -99,7 +99,7 @@ For [Bazel](https://bazel.build), you can either (with the GAVs from above), or use `@io_grpc_grpc_java//api` et al (see below). [the JARs]: -https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.65.0 +https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.66.0 Development snapshots are available in [Sonatypes's snapshot repository](https://oss.sonatype.org/content/repositories/snapshots/). @@ -129,9 +129,9 @@ For protobuf-based codegen integrated with the Maven build system, you can use protobuf-maven-plugin 0.6.1 - com.google.protobuf:protoc:3.25.1:exe:${os.detected.classifier} + com.google.protobuf:protoc:3.25.3:exe:${os.detected.classifier} grpc-java - io.grpc:protoc-gen-grpc-java:1.65.0:exe:${os.detected.classifier} + io.grpc:protoc-gen-grpc-java:1.66.0:exe:${os.detected.classifier} @@ -157,11 +157,11 @@ plugins { protobuf { protoc { - artifact = "com.google.protobuf:protoc:3.25.1" + artifact = "com.google.protobuf:protoc:3.25.3" } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.65.0' + artifact = 'io.grpc:protoc-gen-grpc-java:1.66.0' } } generateProtoTasks { @@ -190,11 +190,11 @@ plugins { protobuf { protoc { - artifact = "com.google.protobuf:protoc:3.25.1" + artifact = "com.google.protobuf:protoc:3.25.3" } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.65.0' + artifact = 'io.grpc:protoc-gen-grpc-java:1.66.0' } } generateProtoTasks { diff --git a/SECURITY.md b/SECURITY.md index 774579bf68b..47b54f1ef47 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -398,7 +398,8 @@ grpc-netty version | netty-handler version | netty-tcnative-boringssl-static ver 1.56.x | 4.1.87.Final | 2.0.61.Final 1.57.x-1.58.x | 4.1.93.Final | 2.0.61.Final 1.59.x | 4.1.97.Final | 2.0.61.Final -1.60.x- | 4.1.100.Final | 2.0.61.Final +1.60.x-1.65.x | 4.1.100.Final | 2.0.61.Final +1.66.x- | 4.1.110.Final | 2.0.65.Final _(grpc-netty-shaded avoids issues with keeping these versions in sync.)_ diff --git a/build.gradle b/build.gradle index 74cfacb800a..76449ec0107 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ subprojects { apply plugin: "net.ltgt.errorprone" group = "io.grpc" - version = "1.67.0-SNAPSHOT" // CURRENT_GRPC_VERSION + version = "1.66.0-SNAPSHOT" // CURRENT_GRPC_VERSION repositories { maven { // The google mirror is less flaky than mavenCentral() diff --git a/compiler/src/test/golden/TestDeprecatedService.java.txt b/compiler/src/test/golden/TestDeprecatedService.java.txt index 75e9e0b47e0..5666abe8fda 100644 --- a/compiler/src/test/golden/TestDeprecatedService.java.txt +++ b/compiler/src/test/golden/TestDeprecatedService.java.txt @@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.67.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.66.0-SNAPSHOT)", comments = "Source: grpc/testing/compiler/test.proto") @io.grpc.stub.annotations.GrpcGenerated @java.lang.Deprecated diff --git a/compiler/src/test/golden/TestService.java.txt b/compiler/src/test/golden/TestService.java.txt index 3852b6ee547..52e2a772414 100644 --- a/compiler/src/test/golden/TestService.java.txt +++ b/compiler/src/test/golden/TestService.java.txt @@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.67.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.66.0-SNAPSHOT)", comments = "Source: grpc/testing/compiler/test.proto") @io.grpc.stub.annotations.GrpcGenerated public final class TestServiceGrpc { diff --git a/core/src/main/java/io/grpc/internal/GrpcUtil.java b/core/src/main/java/io/grpc/internal/GrpcUtil.java index 593bdbce13f..fa488f30ef8 100644 --- a/core/src/main/java/io/grpc/internal/GrpcUtil.java +++ b/core/src/main/java/io/grpc/internal/GrpcUtil.java @@ -219,7 +219,7 @@ public byte[] parseAsciiString(byte[] serialized) { public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults(); - public static final String IMPLEMENTATION_VERSION = "1.67.0-SNAPSHOT"; // CURRENT_GRPC_VERSION + public static final String IMPLEMENTATION_VERSION = "1.66.0-SNAPSHOT"; // CURRENT_GRPC_VERSION /** * The default timeout in nanos for a keepalive ping request. diff --git a/examples/android/clientcache/app/build.gradle b/examples/android/clientcache/app/build.gradle index 0ca032fb0e4..64e95de4738 100644 --- a/examples/android/clientcache/app/build.gradle +++ b/examples/android/clientcache/app/build.gradle @@ -34,7 +34,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.25.1' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -54,12 +54,12 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.0.0' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' testImplementation 'junit:junit:4.13.2' testImplementation 'com.google.truth:truth:1.1.5' - testImplementation 'io.grpc:grpc-testing:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + testImplementation 'io.grpc:grpc-testing:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION } diff --git a/examples/android/helloworld/app/build.gradle b/examples/android/helloworld/app/build.gradle index 0f1e8b4047b..f9433f14010 100644 --- a/examples/android/helloworld/app/build.gradle +++ b/examples/android/helloworld/app/build.gradle @@ -32,7 +32,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.25.1' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -52,8 +52,8 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.0.0' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/android/routeguide/app/build.gradle b/examples/android/routeguide/app/build.gradle index c33135233ea..2431b473f29 100644 --- a/examples/android/routeguide/app/build.gradle +++ b/examples/android/routeguide/app/build.gradle @@ -32,7 +32,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.25.1' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -52,8 +52,8 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.0.0' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/android/strictmode/app/build.gradle b/examples/android/strictmode/app/build.gradle index e8e2e8cac29..699c8dd9d68 100644 --- a/examples/android/strictmode/app/build.gradle +++ b/examples/android/strictmode/app/build.gradle @@ -33,7 +33,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.25.1' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -53,8 +53,8 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.0.0' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/build.gradle b/examples/build.gradle index 076e0c4a25b..c9213cc6a21 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -23,7 +23,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.25.3' def protocVersion = protobufVersion diff --git a/examples/example-alts/build.gradle b/examples/example-alts/build.gradle index 3c998586bb6..06b7ac501d0 100644 --- a/examples/example-alts/build.gradle +++ b/examples/example-alts/build.gradle @@ -24,7 +24,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/example-debug/build.gradle b/examples/example-debug/build.gradle index ca151a13c1a..624483f663e 100644 --- a/examples/example-debug/build.gradle +++ b/examples/example-debug/build.gradle @@ -25,7 +25,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.25.3' dependencies { diff --git a/examples/example-debug/pom.xml b/examples/example-debug/pom.xml index 10ccf834d86..5aa8065ad31 100644 --- a/examples/example-debug/pom.xml +++ b/examples/example-debug/pom.xml @@ -6,13 +6,13 @@ jar - 1.67.0-SNAPSHOT + 1.66.0-SNAPSHOT example-debug https://github.com/grpc/grpc-java UTF-8 - 1.67.0-SNAPSHOT + 1.66.0-SNAPSHOT 3.25.3 1.8 diff --git a/examples/example-gauth/build.gradle b/examples/example-gauth/build.gradle index 40e72afad82..c43443c3860 100644 --- a/examples/example-gauth/build.gradle +++ b/examples/example-gauth/build.gradle @@ -24,7 +24,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.25.3' def protocVersion = protobufVersion diff --git a/examples/example-gauth/pom.xml b/examples/example-gauth/pom.xml index 1e58e21e975..d91eeb15ded 100644 --- a/examples/example-gauth/pom.xml +++ b/examples/example-gauth/pom.xml @@ -6,13 +6,13 @@ jar - 1.67.0-SNAPSHOT + 1.66.0-SNAPSHOT example-gauth https://github.com/grpc/grpc-java UTF-8 - 1.67.0-SNAPSHOT + 1.66.0-SNAPSHOT 3.25.3 1.8 diff --git a/examples/example-gcp-csm-observability/build.gradle b/examples/example-gcp-csm-observability/build.gradle index 5de2b1995e2..a24490918b5 100644 --- a/examples/example-gcp-csm-observability/build.gradle +++ b/examples/example-gcp-csm-observability/build.gradle @@ -25,7 +25,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' def openTelemetryVersion = '1.40.0' def openTelemetryPrometheusVersion = '1.40.0-alpha' diff --git a/examples/example-gcp-observability/build.gradle b/examples/example-gcp-observability/build.gradle index 0462c987f52..d6dd1aedc6e 100644 --- a/examples/example-gcp-observability/build.gradle +++ b/examples/example-gcp-observability/build.gradle @@ -25,7 +25,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/example-hostname/build.gradle b/examples/example-hostname/build.gradle index ab45ee2dc5b..ee5e5cf5c70 100644 --- a/examples/example-hostname/build.gradle +++ b/examples/example-hostname/build.gradle @@ -23,7 +23,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.25.3' dependencies { diff --git a/examples/example-hostname/pom.xml b/examples/example-hostname/pom.xml index 19b5f8b3c20..05131b89978 100644 --- a/examples/example-hostname/pom.xml +++ b/examples/example-hostname/pom.xml @@ -6,13 +6,13 @@ jar - 1.67.0-SNAPSHOT + 1.66.0-SNAPSHOT example-hostname https://github.com/grpc/grpc-java UTF-8 - 1.67.0-SNAPSHOT + 1.66.0-SNAPSHOT 3.25.3 1.8 diff --git a/examples/example-jwt-auth/build.gradle b/examples/example-jwt-auth/build.gradle index 6fdd4498c7d..2ad3c91f190 100644 --- a/examples/example-jwt-auth/build.gradle +++ b/examples/example-jwt-auth/build.gradle @@ -23,7 +23,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.25.3' def protocVersion = protobufVersion diff --git a/examples/example-jwt-auth/pom.xml b/examples/example-jwt-auth/pom.xml index ad530e33aa7..01cf0edce28 100644 --- a/examples/example-jwt-auth/pom.xml +++ b/examples/example-jwt-auth/pom.xml @@ -7,13 +7,13 @@ jar - 1.67.0-SNAPSHOT + 1.66.0-SNAPSHOT example-jwt-auth https://github.com/grpc/grpc-java UTF-8 - 1.67.0-SNAPSHOT + 1.66.0-SNAPSHOT 3.25.3 3.25.3 diff --git a/examples/example-oauth/build.gradle b/examples/example-oauth/build.gradle index 255633b4f9f..23a6633e264 100644 --- a/examples/example-oauth/build.gradle +++ b/examples/example-oauth/build.gradle @@ -23,7 +23,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.25.3' def protocVersion = protobufVersion diff --git a/examples/example-oauth/pom.xml b/examples/example-oauth/pom.xml index 2c38a05b3e4..afd45aecd39 100644 --- a/examples/example-oauth/pom.xml +++ b/examples/example-oauth/pom.xml @@ -7,13 +7,13 @@ jar - 1.67.0-SNAPSHOT + 1.66.0-SNAPSHOT example-oauth https://github.com/grpc/grpc-java UTF-8 - 1.67.0-SNAPSHOT + 1.66.0-SNAPSHOT 3.25.3 3.25.3 diff --git a/examples/example-opentelemetry/build.gradle b/examples/example-opentelemetry/build.gradle index 00f7dc101bf..55d6685d771 100644 --- a/examples/example-opentelemetry/build.gradle +++ b/examples/example-opentelemetry/build.gradle @@ -24,7 +24,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' def openTelemetryVersion = '1.40.0' def openTelemetryPrometheusVersion = '1.40.0-alpha' diff --git a/examples/example-orca/build.gradle b/examples/example-orca/build.gradle index 22feb8cae42..f3eae10ace4 100644 --- a/examples/example-orca/build.gradle +++ b/examples/example-orca/build.gradle @@ -18,7 +18,7 @@ java { targetCompatibility = JavaVersion.VERSION_1_8 } -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/example-reflection/build.gradle b/examples/example-reflection/build.gradle index 78821391911..0b5c99898ed 100644 --- a/examples/example-reflection/build.gradle +++ b/examples/example-reflection/build.gradle @@ -18,7 +18,7 @@ java { targetCompatibility = JavaVersion.VERSION_1_8 } -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/example-servlet/build.gradle b/examples/example-servlet/build.gradle index 9542ba0277f..b73d21fbc4c 100644 --- a/examples/example-servlet/build.gradle +++ b/examples/example-servlet/build.gradle @@ -16,7 +16,7 @@ java { targetCompatibility = JavaVersion.VERSION_1_8 } -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index 94257af4758..3791cc03271 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -24,7 +24,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/example-tls/pom.xml b/examples/example-tls/pom.xml index bc9c0a7a8ee..1263b347030 100644 --- a/examples/example-tls/pom.xml +++ b/examples/example-tls/pom.xml @@ -6,13 +6,13 @@ jar - 1.67.0-SNAPSHOT + 1.66.0-SNAPSHOT example-tls https://github.com/grpc/grpc-java UTF-8 - 1.67.0-SNAPSHOT + 1.66.0-SNAPSHOT 3.25.3 1.8 diff --git a/examples/example-xds/build.gradle b/examples/example-xds/build.gradle index 2554adb0033..9807b1f8b74 100644 --- a/examples/example-xds/build.gradle +++ b/examples/example-xds/build.gradle @@ -23,7 +23,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/pom.xml b/examples/pom.xml index 2b25d13b50c..a71e9d449c3 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -6,13 +6,13 @@ jar - 1.67.0-SNAPSHOT + 1.66.0-SNAPSHOT examples https://github.com/grpc/grpc-java UTF-8 - 1.67.0-SNAPSHOT + 1.66.0-SNAPSHOT 3.25.3 3.25.3 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 78550e9c95e..4eee9a6018e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,8 +1,9 @@ [versions] -netty = '4.1.100.Final' +googleauth = "1.22.0" +netty = '4.1.110.Final' # Keep the following references of tcnative version in sync whenever it's updated: # SECURITY.md -nettytcnative = '2.0.61.Final' +nettytcnative = '2.0.65.Final' opencensus = "0.31.1" # Not upgrading to 4.x as it is not yet ABI compatible. # https://github.com/protocolbuffers/protobuf/issues/17247 diff --git a/netty/shaded/src/testShadow/java/io/grpc/netty/shaded/ShadingTest.java b/netty/shaded/src/testShadow/java/io/grpc/netty/shaded/ShadingTest.java index 7a5e4b43c8b..89803998925 100644 --- a/netty/shaded/src/testShadow/java/io/grpc/netty/shaded/ShadingTest.java +++ b/netty/shaded/src/testShadow/java/io/grpc/netty/shaded/ShadingTest.java @@ -79,7 +79,7 @@ public void nettyResourcesUpdated() throws IOException { InputStream inputStream = NettyChannelBuilder.class.getClassLoader() .getResourceAsStream( "META-INF/native-image/io.grpc.netty.shaded.io.netty/netty-transport/" - + "reflection-config.json"); + + "reflect-config.json"); assertThat(inputStream).isNotNull(); Scanner s = new Scanner(inputStream, StandardCharsets.UTF_8.name()).useDelimiter("\\A"); diff --git a/repositories.bzl b/repositories.bzl index af3acc8ddcf..455e9dcf3ca 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -26,20 +26,20 @@ IO_GRPC_GRPC_JAVA_ARTIFACTS = [ "com.google.truth:truth:1.4.2", "com.squareup.okhttp:okhttp:2.7.5", "com.squareup.okio:okio:2.10.0", # 3.0+ needs swapping to -jvm; need work to avoid flag-day - "io.netty:netty-buffer:4.1.100.Final", - "io.netty:netty-codec-http2:4.1.100.Final", - "io.netty:netty-codec-http:4.1.100.Final", - "io.netty:netty-codec-socks:4.1.100.Final", - "io.netty:netty-codec:4.1.100.Final", - "io.netty:netty-common:4.1.100.Final", - "io.netty:netty-handler-proxy:4.1.100.Final", - "io.netty:netty-handler:4.1.100.Final", - "io.netty:netty-resolver:4.1.100.Final", - "io.netty:netty-tcnative-boringssl-static:2.0.61.Final", - "io.netty:netty-tcnative-classes:2.0.61.Final", - "io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.100.Final", - "io.netty:netty-transport-native-unix-common:4.1.100.Final", - "io.netty:netty-transport:4.1.100.Final", + "io.netty:netty-buffer:4.1.110.Final", + "io.netty:netty-codec-http2:4.1.110.Final", + "io.netty:netty-codec-http:4.1.110.Final", + "io.netty:netty-codec-socks:4.1.110.Final", + "io.netty:netty-codec:4.1.110.Final", + "io.netty:netty-common:4.1.110.Final", + "io.netty:netty-handler-proxy:4.1.110.Final", + "io.netty:netty-handler:4.1.110.Final", + "io.netty:netty-resolver:4.1.110.Final", + "io.netty:netty-tcnative-boringssl-static:2.0.65.Final", + "io.netty:netty-tcnative-classes:2.0.65.Final", + "io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.110.Final", + "io.netty:netty-transport-native-unix-common:4.1.110.Final", + "io.netty:netty-transport:4.1.110.Final", "io.opencensus:opencensus-api:0.31.0", "io.opencensus:opencensus-contrib-grpc-metrics:0.31.0", "io.perfmark:perfmark-api:0.27.0", From 15456f8f0afbd884bf930c2c71d6beef8bb5b99f Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Fri, 28 Jun 2024 23:24:11 -0700 Subject: [PATCH 018/103] core: In PF, pass around SubchannelData instead of Subchannel Each usage of the subchannel immediately looked up the SubchannelData. --- .../internal/PickFirstLeafLoadBalancer.java | 54 +++++++++---------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java index c3f9a52404e..253422d3dbd 100644 --- a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java +++ b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java @@ -214,14 +214,13 @@ public void handleNameResolutionError(Status error) { updateBalancingState(TRANSIENT_FAILURE, new Picker(PickResult.withError(error))); } - void processSubchannelState(Subchannel subchannel, ConnectivityStateInfo stateInfo) { + void processSubchannelState(SubchannelData subchannelData, ConnectivityStateInfo stateInfo) { ConnectivityState newState = stateInfo.getState(); - SubchannelData subchannelData = subchannels.get(getAddress(subchannel)); // Shutdown channels/previously relevant subchannels can still callback with state updates. // To prevent pickers from returning these obsolete subchannels, this logic // is included to check if the current list of active subchannels includes this subchannel. - if (subchannelData == null || subchannelData.getSubchannel() != subchannel) { + if (subchannelData != subchannels.get(getAddress(subchannelData.subchannel))) { return; } @@ -269,7 +268,7 @@ void processSubchannelState(Subchannel subchannel, ConnectivityStateInfo stateIn case READY: shutdownRemaining(subchannelData); - addressIndex.seekTo(getAddress(subchannel)); + addressIndex.seekTo(getAddress(subchannelData.subchannel)); rawConnectivityState = READY; updateHealthCheckedState(subchannelData); break; @@ -277,7 +276,7 @@ void processSubchannelState(Subchannel subchannel, ConnectivityStateInfo stateIn case TRANSIENT_FAILURE: // If we are looking at current channel, request a connection if possible if (addressIndex.isValid() - && subchannels.get(addressIndex.getCurrentAddress()).getSubchannel() == subchannel) { + && subchannels.get(addressIndex.getCurrentAddress()) == subchannelData) { if (addressIndex.increment()) { cancelScheduleTask(); requestConnection(); // is recursive so might hit the end of the addresses @@ -317,7 +316,7 @@ private void updateHealthCheckedState(SubchannelData subchannelData) { new FixedResultPicker(PickResult.withSubchannel(subchannelData.subchannel))); } else if (subchannelData.getHealthState() == TRANSIENT_FAILURE) { updateBalancingState(TRANSIENT_FAILURE, new Picker(PickResult.withError( - subchannelData.healthListener.healthStateInfo.getStatus()))); + subchannelData.healthStateInfo.getStatus()))); } else if (concludedState != TRANSIENT_FAILURE) { updateBalancingState(subchannelData.getHealthState(), new Picker(PickResult.withNoResult())); @@ -377,25 +376,24 @@ public void requestConnection() { return; } - Subchannel subchannel; - SocketAddress currentAddress; - currentAddress = addressIndex.getCurrentAddress(); - subchannel = subchannels.containsKey(currentAddress) - ? subchannels.get(currentAddress).getSubchannel() - : createNewSubchannel(currentAddress, addressIndex.getCurrentEagAttributes()); + SocketAddress currentAddress = addressIndex.getCurrentAddress(); + SubchannelData subchannelData = subchannels.get(currentAddress); + if (subchannelData == null) { + subchannelData = createNewSubchannel(currentAddress, addressIndex.getCurrentEagAttributes()); + } - ConnectivityState subchannelState = subchannels.get(currentAddress).getState(); + ConnectivityState subchannelState = subchannelData.getState(); switch (subchannelState) { case IDLE: - subchannel.requestConnection(); - subchannels.get(currentAddress).updateState(CONNECTING); + subchannelData.subchannel.requestConnection(); + subchannelData.updateState(CONNECTING); scheduleNextConnection(); break; case CONNECTING: if (enableHappyEyeballs) { scheduleNextConnection(); } else { - subchannel.requestConnection(); + subchannelData.subchannel.requestConnection(); } break; case TRANSIENT_FAILURE: @@ -455,7 +453,7 @@ private void cancelScheduleTask() { } } - private Subchannel createNewSubchannel(SocketAddress addr, Attributes attrs) { + private SubchannelData createNewSubchannel(SocketAddress addr, Attributes attrs) { HealthListener hcListener = new HealthListener(); final Subchannel subchannel = helper.createSubchannel( CreateSubchannelArgs.newBuilder() @@ -467,15 +465,15 @@ private Subchannel createNewSubchannel(SocketAddress addr, Attributes attrs) { log.warning("Was not able to create subchannel for " + addr); throw new IllegalStateException("Can't create subchannel"); } - SubchannelData subchannelData = new SubchannelData(subchannel, IDLE, hcListener); + SubchannelData subchannelData = new SubchannelData(subchannel, IDLE); hcListener.subchannelData = subchannelData; subchannels.put(addr, subchannelData); Attributes scAttrs = subchannel.getAttributes(); if (scAttrs.get(LoadBalancer.HAS_HEALTH_PRODUCER_LISTENER_KEY) == null) { - hcListener.healthStateInfo = ConnectivityStateInfo.forNonError(READY); + subchannelData.healthStateInfo = ConnectivityStateInfo.forNonError(READY); } - subchannel.start(stateInfo -> processSubchannelState(subchannel, stateInfo)); - return subchannel; + subchannel.start(stateInfo -> processSubchannelState(subchannelData, stateInfo)); + return subchannelData; } private boolean isPassComplete() { @@ -492,17 +490,15 @@ private boolean isPassComplete() { } private final class HealthListener implements SubchannelStateListener { - private ConnectivityStateInfo healthStateInfo = ConnectivityStateInfo.forNonError(IDLE); private SubchannelData subchannelData; @Override public void onSubchannelState(ConnectivityStateInfo newState) { log.log(Level.FINE, "Received health status {0} for subchannel {1}", new Object[]{newState, subchannelData.subchannel}); - healthStateInfo = newState; + subchannelData.healthStateInfo = newState; try { - SubchannelData curSubChanData = subchannels.get(addressIndex.getCurrentAddress()); - if (curSubChanData != null && curSubChanData.healthListener == this) { + if (subchannelData == subchannels.get(addressIndex.getCurrentAddress())) { updateHealthCheckedState(subchannelData); } } catch (IllegalStateException e) { @@ -663,14 +659,12 @@ public int size() { private static final class SubchannelData { private final Subchannel subchannel; private ConnectivityState state; - private final HealthListener healthListener; private boolean completedConnectivityAttempt = false; + private ConnectivityStateInfo healthStateInfo = ConnectivityStateInfo.forNonError(IDLE); - public SubchannelData(Subchannel subchannel, ConnectivityState state, - HealthListener subchannelHealthListener) { + public SubchannelData(Subchannel subchannel, ConnectivityState state) { this.subchannel = subchannel; this.state = state; - this.healthListener = subchannelHealthListener; } public Subchannel getSubchannel() { @@ -695,7 +689,7 @@ private void updateState(ConnectivityState newState) { } private ConnectivityState getHealthState() { - return healthListener.healthStateInfo.getState(); + return healthStateInfo.getState(); } } From 9bed655c568b4f09a32b3910745a949f5f08d956 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Fri, 2 Aug 2024 14:46:14 -0700 Subject: [PATCH 019/103] Revert "Netty upgrade to 4.1.110 in grpc-java (#11273)" This reverts commit f9b072cfe24daf3661994bd3a83825bee4069927. Changes from the release process got mixed in with the commit. --- MODULE.bazel | 30 ++++++++-------- README.md | 36 +++++++++---------- SECURITY.md | 3 +- build.gradle | 2 +- .../golden/TestDeprecatedService.java.txt | 2 +- compiler/src/test/golden/TestService.java.txt | 2 +- .../main/java/io/grpc/internal/GrpcUtil.java | 2 +- examples/android/clientcache/app/build.gradle | 10 +++--- examples/android/helloworld/app/build.gradle | 8 ++--- examples/android/routeguide/app/build.gradle | 8 ++--- examples/android/strictmode/app/build.gradle | 8 ++--- examples/build.gradle | 2 +- examples/example-alts/build.gradle | 2 +- examples/example-debug/build.gradle | 2 +- examples/example-debug/pom.xml | 4 +-- examples/example-gauth/build.gradle | 2 +- examples/example-gauth/pom.xml | 4 +-- .../build.gradle | 2 +- .../example-gcp-observability/build.gradle | 2 +- examples/example-hostname/build.gradle | 2 +- examples/example-hostname/pom.xml | 4 +-- examples/example-jwt-auth/build.gradle | 2 +- examples/example-jwt-auth/pom.xml | 4 +-- examples/example-oauth/build.gradle | 2 +- examples/example-oauth/pom.xml | 4 +-- examples/example-opentelemetry/build.gradle | 2 +- examples/example-orca/build.gradle | 2 +- examples/example-reflection/build.gradle | 2 +- examples/example-servlet/build.gradle | 2 +- examples/example-tls/build.gradle | 2 +- examples/example-tls/pom.xml | 4 +-- examples/example-xds/build.gradle | 2 +- examples/pom.xml | 4 +-- gradle/libs.versions.toml | 5 ++- .../io/grpc/netty/shaded/ShadingTest.java | 2 +- repositories.bzl | 28 +++++++-------- 36 files changed, 101 insertions(+), 103 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index 8260788c5cb..2b5d85490f3 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -2,7 +2,7 @@ module( name = "grpc-java", compatibility_level = 0, repo_name = "io_grpc_grpc_java", - version = "1.66.0-SNAPSHOT", # CURRENT_GRPC_VERSION + version = "1.67.0-SNAPSHOT", # CURRENT_GRPC_VERSION ) # GRPC_DEPS_START @@ -22,20 +22,20 @@ IO_GRPC_GRPC_JAVA_ARTIFACTS = [ "com.google.truth:truth:1.4.2", "com.squareup.okhttp:okhttp:2.7.5", "com.squareup.okio:okio:2.10.0", # 3.0+ needs swapping to -jvm; need work to avoid flag-day - "io.netty:netty-buffer:4.1.110.Final", - "io.netty:netty-codec-http2:4.1.110.Final", - "io.netty:netty-codec-http:4.1.110.Final", - "io.netty:netty-codec-socks:4.1.110.Final", - "io.netty:netty-codec:4.1.110.Final", - "io.netty:netty-common:4.1.110.Final", - "io.netty:netty-handler-proxy:4.1.110.Final", - "io.netty:netty-handler:4.1.110.Final", - "io.netty:netty-resolver:4.1.110.Final", - "io.netty:netty-tcnative-boringssl-static:2.0.65.Final", - "io.netty:netty-tcnative-classes:2.0.65.Final", - "io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.110.Final", - "io.netty:netty-transport-native-unix-common:4.1.110.Final", - "io.netty:netty-transport:4.1.110.Final", + "io.netty:netty-buffer:4.1.100.Final", + "io.netty:netty-codec-http2:4.1.100.Final", + "io.netty:netty-codec-http:4.1.100.Final", + "io.netty:netty-codec-socks:4.1.100.Final", + "io.netty:netty-codec:4.1.100.Final", + "io.netty:netty-common:4.1.100.Final", + "io.netty:netty-handler-proxy:4.1.100.Final", + "io.netty:netty-handler:4.1.100.Final", + "io.netty:netty-resolver:4.1.100.Final", + "io.netty:netty-tcnative-boringssl-static:2.0.61.Final", + "io.netty:netty-tcnative-classes:2.0.61.Final", + "io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.100.Final", + "io.netty:netty-transport-native-unix-common:4.1.100.Final", + "io.netty:netty-transport:4.1.100.Final", "io.opencensus:opencensus-api:0.31.0", "io.opencensus:opencensus-contrib-grpc-metrics:0.31.0", "io.perfmark:perfmark-api:0.27.0", diff --git a/README.md b/README.md index cb38ad66394..fef37c1c3bb 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,8 @@ For a guided tour, take a look at the [quick start guide](https://grpc.io/docs/languages/java/quickstart) or the more explanatory [gRPC basics](https://grpc.io/docs/languages/java/basics). -The [examples](https://github.com/grpc/grpc-java/tree/v1.66.0/examples) and the -[Android example](https://github.com/grpc/grpc-java/tree/v1.66.0/examples/android) +The [examples](https://github.com/grpc/grpc-java/tree/v1.65.0/examples) and the +[Android example](https://github.com/grpc/grpc-java/tree/v1.65.0/examples/android) are standalone projects that showcase the usage of gRPC. Download @@ -56,18 +56,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`: io.grpc grpc-netty-shaded - 1.66.0 + 1.65.0 runtime io.grpc grpc-protobuf - 1.66.0 + 1.65.0 io.grpc grpc-stub - 1.66.0 + 1.65.0 org.apache.tomcat @@ -79,18 +79,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`: Or for Gradle with non-Android, add to your dependencies: ```gradle -runtimeOnly 'io.grpc:grpc-netty-shaded:1.66.0' -implementation 'io.grpc:grpc-protobuf:1.66.0' -implementation 'io.grpc:grpc-stub:1.66.0' +runtimeOnly 'io.grpc:grpc-netty-shaded:1.65.0' +implementation 'io.grpc:grpc-protobuf:1.65.0' +implementation 'io.grpc:grpc-stub:1.65.0' compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+ ``` For Android client, use `grpc-okhttp` instead of `grpc-netty-shaded` and `grpc-protobuf-lite` instead of `grpc-protobuf`: ```gradle -implementation 'io.grpc:grpc-okhttp:1.66.0' -implementation 'io.grpc:grpc-protobuf-lite:1.66.0' -implementation 'io.grpc:grpc-stub:1.66.0' +implementation 'io.grpc:grpc-okhttp:1.65.0' +implementation 'io.grpc:grpc-protobuf-lite:1.65.0' +implementation 'io.grpc:grpc-stub:1.65.0' compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+ ``` @@ -99,7 +99,7 @@ For [Bazel](https://bazel.build), you can either (with the GAVs from above), or use `@io_grpc_grpc_java//api` et al (see below). [the JARs]: -https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.66.0 +https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.65.0 Development snapshots are available in [Sonatypes's snapshot repository](https://oss.sonatype.org/content/repositories/snapshots/). @@ -129,9 +129,9 @@ For protobuf-based codegen integrated with the Maven build system, you can use protobuf-maven-plugin 0.6.1 - com.google.protobuf:protoc:3.25.3:exe:${os.detected.classifier} + com.google.protobuf:protoc:3.25.1:exe:${os.detected.classifier} grpc-java - io.grpc:protoc-gen-grpc-java:1.66.0:exe:${os.detected.classifier} + io.grpc:protoc-gen-grpc-java:1.65.0:exe:${os.detected.classifier} @@ -157,11 +157,11 @@ plugins { protobuf { protoc { - artifact = "com.google.protobuf:protoc:3.25.3" + artifact = "com.google.protobuf:protoc:3.25.1" } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.66.0' + artifact = 'io.grpc:protoc-gen-grpc-java:1.65.0' } } generateProtoTasks { @@ -190,11 +190,11 @@ plugins { protobuf { protoc { - artifact = "com.google.protobuf:protoc:3.25.3" + artifact = "com.google.protobuf:protoc:3.25.1" } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.66.0' + artifact = 'io.grpc:protoc-gen-grpc-java:1.65.0' } } generateProtoTasks { diff --git a/SECURITY.md b/SECURITY.md index 47b54f1ef47..774579bf68b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -398,8 +398,7 @@ grpc-netty version | netty-handler version | netty-tcnative-boringssl-static ver 1.56.x | 4.1.87.Final | 2.0.61.Final 1.57.x-1.58.x | 4.1.93.Final | 2.0.61.Final 1.59.x | 4.1.97.Final | 2.0.61.Final -1.60.x-1.65.x | 4.1.100.Final | 2.0.61.Final -1.66.x- | 4.1.110.Final | 2.0.65.Final +1.60.x- | 4.1.100.Final | 2.0.61.Final _(grpc-netty-shaded avoids issues with keeping these versions in sync.)_ diff --git a/build.gradle b/build.gradle index 76449ec0107..74cfacb800a 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ subprojects { apply plugin: "net.ltgt.errorprone" group = "io.grpc" - version = "1.66.0-SNAPSHOT" // CURRENT_GRPC_VERSION + version = "1.67.0-SNAPSHOT" // CURRENT_GRPC_VERSION repositories { maven { // The google mirror is less flaky than mavenCentral() diff --git a/compiler/src/test/golden/TestDeprecatedService.java.txt b/compiler/src/test/golden/TestDeprecatedService.java.txt index 5666abe8fda..75e9e0b47e0 100644 --- a/compiler/src/test/golden/TestDeprecatedService.java.txt +++ b/compiler/src/test/golden/TestDeprecatedService.java.txt @@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.66.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.67.0-SNAPSHOT)", comments = "Source: grpc/testing/compiler/test.proto") @io.grpc.stub.annotations.GrpcGenerated @java.lang.Deprecated diff --git a/compiler/src/test/golden/TestService.java.txt b/compiler/src/test/golden/TestService.java.txt index 52e2a772414..3852b6ee547 100644 --- a/compiler/src/test/golden/TestService.java.txt +++ b/compiler/src/test/golden/TestService.java.txt @@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.66.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.67.0-SNAPSHOT)", comments = "Source: grpc/testing/compiler/test.proto") @io.grpc.stub.annotations.GrpcGenerated public final class TestServiceGrpc { diff --git a/core/src/main/java/io/grpc/internal/GrpcUtil.java b/core/src/main/java/io/grpc/internal/GrpcUtil.java index fa488f30ef8..593bdbce13f 100644 --- a/core/src/main/java/io/grpc/internal/GrpcUtil.java +++ b/core/src/main/java/io/grpc/internal/GrpcUtil.java @@ -219,7 +219,7 @@ public byte[] parseAsciiString(byte[] serialized) { public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults(); - public static final String IMPLEMENTATION_VERSION = "1.66.0-SNAPSHOT"; // CURRENT_GRPC_VERSION + public static final String IMPLEMENTATION_VERSION = "1.67.0-SNAPSHOT"; // CURRENT_GRPC_VERSION /** * The default timeout in nanos for a keepalive ping request. diff --git a/examples/android/clientcache/app/build.gradle b/examples/android/clientcache/app/build.gradle index 64e95de4738..0ca032fb0e4 100644 --- a/examples/android/clientcache/app/build.gradle +++ b/examples/android/clientcache/app/build.gradle @@ -34,7 +34,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.25.1' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -54,12 +54,12 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.0.0' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' testImplementation 'junit:junit:4.13.2' testImplementation 'com.google.truth:truth:1.1.5' - testImplementation 'io.grpc:grpc-testing:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION + testImplementation 'io.grpc:grpc-testing:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION } diff --git a/examples/android/helloworld/app/build.gradle b/examples/android/helloworld/app/build.gradle index f9433f14010..0f1e8b4047b 100644 --- a/examples/android/helloworld/app/build.gradle +++ b/examples/android/helloworld/app/build.gradle @@ -32,7 +32,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.25.1' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -52,8 +52,8 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.0.0' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/android/routeguide/app/build.gradle b/examples/android/routeguide/app/build.gradle index 2431b473f29..c33135233ea 100644 --- a/examples/android/routeguide/app/build.gradle +++ b/examples/android/routeguide/app/build.gradle @@ -32,7 +32,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.25.1' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -52,8 +52,8 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.0.0' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/android/strictmode/app/build.gradle b/examples/android/strictmode/app/build.gradle index 699c8dd9d68..e8e2e8cac29 100644 --- a/examples/android/strictmode/app/build.gradle +++ b/examples/android/strictmode/app/build.gradle @@ -33,7 +33,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.25.1' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -53,8 +53,8 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.0.0' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/build.gradle b/examples/build.gradle index c9213cc6a21..076e0c4a25b 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -23,7 +23,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.25.3' def protocVersion = protobufVersion diff --git a/examples/example-alts/build.gradle b/examples/example-alts/build.gradle index 06b7ac501d0..3c998586bb6 100644 --- a/examples/example-alts/build.gradle +++ b/examples/example-alts/build.gradle @@ -24,7 +24,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/example-debug/build.gradle b/examples/example-debug/build.gradle index 624483f663e..ca151a13c1a 100644 --- a/examples/example-debug/build.gradle +++ b/examples/example-debug/build.gradle @@ -25,7 +25,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.25.3' dependencies { diff --git a/examples/example-debug/pom.xml b/examples/example-debug/pom.xml index 5aa8065ad31..10ccf834d86 100644 --- a/examples/example-debug/pom.xml +++ b/examples/example-debug/pom.xml @@ -6,13 +6,13 @@ jar - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT example-debug https://github.com/grpc/grpc-java UTF-8 - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT 3.25.3 1.8 diff --git a/examples/example-gauth/build.gradle b/examples/example-gauth/build.gradle index c43443c3860..40e72afad82 100644 --- a/examples/example-gauth/build.gradle +++ b/examples/example-gauth/build.gradle @@ -24,7 +24,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.25.3' def protocVersion = protobufVersion diff --git a/examples/example-gauth/pom.xml b/examples/example-gauth/pom.xml index d91eeb15ded..1e58e21e975 100644 --- a/examples/example-gauth/pom.xml +++ b/examples/example-gauth/pom.xml @@ -6,13 +6,13 @@ jar - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT example-gauth https://github.com/grpc/grpc-java UTF-8 - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT 3.25.3 1.8 diff --git a/examples/example-gcp-csm-observability/build.gradle b/examples/example-gcp-csm-observability/build.gradle index a24490918b5..5de2b1995e2 100644 --- a/examples/example-gcp-csm-observability/build.gradle +++ b/examples/example-gcp-csm-observability/build.gradle @@ -25,7 +25,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' def openTelemetryVersion = '1.40.0' def openTelemetryPrometheusVersion = '1.40.0-alpha' diff --git a/examples/example-gcp-observability/build.gradle b/examples/example-gcp-observability/build.gradle index d6dd1aedc6e..0462c987f52 100644 --- a/examples/example-gcp-observability/build.gradle +++ b/examples/example-gcp-observability/build.gradle @@ -25,7 +25,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/example-hostname/build.gradle b/examples/example-hostname/build.gradle index ee5e5cf5c70..ab45ee2dc5b 100644 --- a/examples/example-hostname/build.gradle +++ b/examples/example-hostname/build.gradle @@ -23,7 +23,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.25.3' dependencies { diff --git a/examples/example-hostname/pom.xml b/examples/example-hostname/pom.xml index 05131b89978..19b5f8b3c20 100644 --- a/examples/example-hostname/pom.xml +++ b/examples/example-hostname/pom.xml @@ -6,13 +6,13 @@ jar - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT example-hostname https://github.com/grpc/grpc-java UTF-8 - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT 3.25.3 1.8 diff --git a/examples/example-jwt-auth/build.gradle b/examples/example-jwt-auth/build.gradle index 2ad3c91f190..6fdd4498c7d 100644 --- a/examples/example-jwt-auth/build.gradle +++ b/examples/example-jwt-auth/build.gradle @@ -23,7 +23,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.25.3' def protocVersion = protobufVersion diff --git a/examples/example-jwt-auth/pom.xml b/examples/example-jwt-auth/pom.xml index 01cf0edce28..ad530e33aa7 100644 --- a/examples/example-jwt-auth/pom.xml +++ b/examples/example-jwt-auth/pom.xml @@ -7,13 +7,13 @@ jar - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT example-jwt-auth https://github.com/grpc/grpc-java UTF-8 - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT 3.25.3 3.25.3 diff --git a/examples/example-oauth/build.gradle b/examples/example-oauth/build.gradle index 23a6633e264..255633b4f9f 100644 --- a/examples/example-oauth/build.gradle +++ b/examples/example-oauth/build.gradle @@ -23,7 +23,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.25.3' def protocVersion = protobufVersion diff --git a/examples/example-oauth/pom.xml b/examples/example-oauth/pom.xml index afd45aecd39..2c38a05b3e4 100644 --- a/examples/example-oauth/pom.xml +++ b/examples/example-oauth/pom.xml @@ -7,13 +7,13 @@ jar - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT example-oauth https://github.com/grpc/grpc-java UTF-8 - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT 3.25.3 3.25.3 diff --git a/examples/example-opentelemetry/build.gradle b/examples/example-opentelemetry/build.gradle index 55d6685d771..00f7dc101bf 100644 --- a/examples/example-opentelemetry/build.gradle +++ b/examples/example-opentelemetry/build.gradle @@ -24,7 +24,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' def openTelemetryVersion = '1.40.0' def openTelemetryPrometheusVersion = '1.40.0-alpha' diff --git a/examples/example-orca/build.gradle b/examples/example-orca/build.gradle index f3eae10ace4..22feb8cae42 100644 --- a/examples/example-orca/build.gradle +++ b/examples/example-orca/build.gradle @@ -18,7 +18,7 @@ java { targetCompatibility = JavaVersion.VERSION_1_8 } -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/example-reflection/build.gradle b/examples/example-reflection/build.gradle index 0b5c99898ed..78821391911 100644 --- a/examples/example-reflection/build.gradle +++ b/examples/example-reflection/build.gradle @@ -18,7 +18,7 @@ java { targetCompatibility = JavaVersion.VERSION_1_8 } -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/example-servlet/build.gradle b/examples/example-servlet/build.gradle index b73d21fbc4c..9542ba0277f 100644 --- a/examples/example-servlet/build.gradle +++ b/examples/example-servlet/build.gradle @@ -16,7 +16,7 @@ java { targetCompatibility = JavaVersion.VERSION_1_8 } -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index 3791cc03271..94257af4758 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -24,7 +24,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/example-tls/pom.xml b/examples/example-tls/pom.xml index 1263b347030..bc9c0a7a8ee 100644 --- a/examples/example-tls/pom.xml +++ b/examples/example-tls/pom.xml @@ -6,13 +6,13 @@ jar - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT example-tls https://github.com/grpc/grpc-java UTF-8 - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT 3.25.3 1.8 diff --git a/examples/example-xds/build.gradle b/examples/example-xds/build.gradle index 9807b1f8b74..2554adb0033 100644 --- a/examples/example-xds/build.gradle +++ b/examples/example-xds/build.gradle @@ -23,7 +23,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.66.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/pom.xml b/examples/pom.xml index a71e9d449c3..2b25d13b50c 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -6,13 +6,13 @@ jar - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT examples https://github.com/grpc/grpc-java UTF-8 - 1.66.0-SNAPSHOT + 1.67.0-SNAPSHOT 3.25.3 3.25.3 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4eee9a6018e..78550e9c95e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,9 +1,8 @@ [versions] -googleauth = "1.22.0" -netty = '4.1.110.Final' +netty = '4.1.100.Final' # Keep the following references of tcnative version in sync whenever it's updated: # SECURITY.md -nettytcnative = '2.0.65.Final' +nettytcnative = '2.0.61.Final' opencensus = "0.31.1" # Not upgrading to 4.x as it is not yet ABI compatible. # https://github.com/protocolbuffers/protobuf/issues/17247 diff --git a/netty/shaded/src/testShadow/java/io/grpc/netty/shaded/ShadingTest.java b/netty/shaded/src/testShadow/java/io/grpc/netty/shaded/ShadingTest.java index 89803998925..7a5e4b43c8b 100644 --- a/netty/shaded/src/testShadow/java/io/grpc/netty/shaded/ShadingTest.java +++ b/netty/shaded/src/testShadow/java/io/grpc/netty/shaded/ShadingTest.java @@ -79,7 +79,7 @@ public void nettyResourcesUpdated() throws IOException { InputStream inputStream = NettyChannelBuilder.class.getClassLoader() .getResourceAsStream( "META-INF/native-image/io.grpc.netty.shaded.io.netty/netty-transport/" - + "reflect-config.json"); + + "reflection-config.json"); assertThat(inputStream).isNotNull(); Scanner s = new Scanner(inputStream, StandardCharsets.UTF_8.name()).useDelimiter("\\A"); diff --git a/repositories.bzl b/repositories.bzl index 455e9dcf3ca..af3acc8ddcf 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -26,20 +26,20 @@ IO_GRPC_GRPC_JAVA_ARTIFACTS = [ "com.google.truth:truth:1.4.2", "com.squareup.okhttp:okhttp:2.7.5", "com.squareup.okio:okio:2.10.0", # 3.0+ needs swapping to -jvm; need work to avoid flag-day - "io.netty:netty-buffer:4.1.110.Final", - "io.netty:netty-codec-http2:4.1.110.Final", - "io.netty:netty-codec-http:4.1.110.Final", - "io.netty:netty-codec-socks:4.1.110.Final", - "io.netty:netty-codec:4.1.110.Final", - "io.netty:netty-common:4.1.110.Final", - "io.netty:netty-handler-proxy:4.1.110.Final", - "io.netty:netty-handler:4.1.110.Final", - "io.netty:netty-resolver:4.1.110.Final", - "io.netty:netty-tcnative-boringssl-static:2.0.65.Final", - "io.netty:netty-tcnative-classes:2.0.65.Final", - "io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.110.Final", - "io.netty:netty-transport-native-unix-common:4.1.110.Final", - "io.netty:netty-transport:4.1.110.Final", + "io.netty:netty-buffer:4.1.100.Final", + "io.netty:netty-codec-http2:4.1.100.Final", + "io.netty:netty-codec-http:4.1.100.Final", + "io.netty:netty-codec-socks:4.1.100.Final", + "io.netty:netty-codec:4.1.100.Final", + "io.netty:netty-common:4.1.100.Final", + "io.netty:netty-handler-proxy:4.1.100.Final", + "io.netty:netty-handler:4.1.100.Final", + "io.netty:netty-resolver:4.1.100.Final", + "io.netty:netty-tcnative-boringssl-static:2.0.61.Final", + "io.netty:netty-tcnative-classes:2.0.61.Final", + "io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.100.Final", + "io.netty:netty-transport-native-unix-common:4.1.100.Final", + "io.netty:netty-transport:4.1.100.Final", "io.opencensus:opencensus-api:0.31.0", "io.opencensus:opencensus-contrib-grpc-metrics:0.31.0", "io.perfmark:perfmark-api:0.27.0", From 06135a074568e0834795314fc422c869f4fe2533 Mon Sep 17 00:00:00 2001 From: Kurt Alfred Kluever Date: Mon, 5 Aug 2024 12:49:03 -0700 Subject: [PATCH 020/103] Migrate from the deprecated `Charsets` constants (in Guava) to the `StandardCharsets` constants (in the JDK) cl/658539667 --- api/src/main/java/io/grpc/Metadata.java | 2 +- api/src/main/java/io/grpc/Status.java | 5 ++--- api/src/test/java/io/grpc/MetadataTest.java | 4 ++-- api/src/testFixtures/java/io/grpc/StringMarshaller.java | 2 +- .../io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java | 2 +- core/src/main/java/io/grpc/internal/ReadableBuffers.java | 2 +- core/src/main/java/io/grpc/internal/TransportFrameUtil.java | 2 +- .../java/io/grpc/internal/CompositeReadableBufferTest.java | 2 +- .../grpc/internal/Http2ClientStreamTransportStateTest.java | 2 +- .../test/java/io/grpc/internal/ReadableBuffersArrayTest.java | 2 +- .../java/io/grpc/internal/ReadableBuffersByteBufferTest.java | 2 +- core/src/test/java/io/grpc/internal/ReadableBuffersTest.java | 2 +- core/src/test/java/io/grpc/internal/ServerCallImplTest.java | 2 +- .../test/java/io/grpc/internal/TransportFrameUtilTest.java | 4 ++-- .../java/io/grpc/internal/AbstractTransportTest.java | 2 +- .../java/io/grpc/internal/ReadableBufferTestBase.java | 2 +- .../grpc/testing/integration/GrpclbFallbackTestClient.java | 2 +- netty/src/main/java/io/grpc/netty/GrpcHttp2HeadersUtils.java | 2 +- .../io/grpc/okhttp/ExceptionHandlingFrameWriterTest.java | 2 +- .../test/java/io/grpc/okhttp/OkHttpClientTransportTest.java | 2 +- .../java/io/grpc/okhttp/OkHttpProtocolNegotiatorTest.java | 2 +- .../test/java/io/grpc/okhttp/OkHttpServerTransportTest.java | 2 +- .../io/grpc/protobuf/services/BinaryLogProviderTest.java | 2 +- .../main/java/io/grpc/internal/testing/StatsTestUtils.java | 2 +- .../internal/rbac/engine/GrpcAuthorizationEngineTest.java | 2 +- 25 files changed, 28 insertions(+), 29 deletions(-) diff --git a/api/src/main/java/io/grpc/Metadata.java b/api/src/main/java/io/grpc/Metadata.java index 58fcefe1373..fba2659776b 100644 --- a/api/src/main/java/io/grpc/Metadata.java +++ b/api/src/main/java/io/grpc/Metadata.java @@ -16,9 +16,9 @@ package io.grpc; -import static com.google.common.base.Charsets.US_ASCII; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static java.nio.charset.StandardCharsets.US_ASCII; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; diff --git a/api/src/main/java/io/grpc/Status.java b/api/src/main/java/io/grpc/Status.java index 8e7f0b835c2..5d7dd30df01 100644 --- a/api/src/main/java/io/grpc/Status.java +++ b/api/src/main/java/io/grpc/Status.java @@ -16,10 +16,10 @@ package io.grpc; -import static com.google.common.base.Charsets.US_ASCII; -import static com.google.common.base.Charsets.UTF_8; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Throwables.getStackTraceAsString; +import static java.nio.charset.StandardCharsets.US_ASCII; +import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; @@ -34,7 +34,6 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; - /** * Defines the status of an operation by providing a standard {@link Code} in conjunction with an * optional descriptive message. Instances of {@code Status} are created by starting with the diff --git a/api/src/test/java/io/grpc/MetadataTest.java b/api/src/test/java/io/grpc/MetadataTest.java index 84cd558a36e..14ba8ca9b23 100644 --- a/api/src/test/java/io/grpc/MetadataTest.java +++ b/api/src/test/java/io/grpc/MetadataTest.java @@ -16,8 +16,8 @@ package io.grpc; -import static com.google.common.base.Charsets.US_ASCII; -import static com.google.common.base.Charsets.UTF_8; +import static java.nio.charset.StandardCharsets.US_ASCII; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/api/src/testFixtures/java/io/grpc/StringMarshaller.java b/api/src/testFixtures/java/io/grpc/StringMarshaller.java index af53d420e2b..e8358b76333 100644 --- a/api/src/testFixtures/java/io/grpc/StringMarshaller.java +++ b/api/src/testFixtures/java/io/grpc/StringMarshaller.java @@ -16,7 +16,7 @@ package io.grpc; -import static com.google.common.base.Charsets.UTF_8; +import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.io.ByteStreams; import java.io.ByteArrayInputStream; diff --git a/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java b/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java index 6c350894929..1e8c27bca25 100644 --- a/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java +++ b/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java @@ -16,7 +16,7 @@ package io.grpc.auth; -import static com.google.common.base.Charsets.US_ASCII; +import static java.nio.charset.StandardCharsets.US_ASCII; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; diff --git a/core/src/main/java/io/grpc/internal/ReadableBuffers.java b/core/src/main/java/io/grpc/internal/ReadableBuffers.java index c54cb0e67d0..1435be138de 100644 --- a/core/src/main/java/io/grpc/internal/ReadableBuffers.java +++ b/core/src/main/java/io/grpc/internal/ReadableBuffers.java @@ -16,7 +16,7 @@ package io.grpc.internal; -import static com.google.common.base.Charsets.UTF_8; +import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.base.Preconditions; import io.grpc.Detachable; diff --git a/core/src/main/java/io/grpc/internal/TransportFrameUtil.java b/core/src/main/java/io/grpc/internal/TransportFrameUtil.java index 51854410843..f3c32416426 100644 --- a/core/src/main/java/io/grpc/internal/TransportFrameUtil.java +++ b/core/src/main/java/io/grpc/internal/TransportFrameUtil.java @@ -16,7 +16,7 @@ package io.grpc.internal; -import static com.google.common.base.Charsets.US_ASCII; +import static java.nio.charset.StandardCharsets.US_ASCII; import com.google.common.io.BaseEncoding; import io.grpc.InternalMetadata; diff --git a/core/src/test/java/io/grpc/internal/CompositeReadableBufferTest.java b/core/src/test/java/io/grpc/internal/CompositeReadableBufferTest.java index 011d83b548a..8d9248a8910 100644 --- a/core/src/test/java/io/grpc/internal/CompositeReadableBufferTest.java +++ b/core/src/test/java/io/grpc/internal/CompositeReadableBufferTest.java @@ -16,7 +16,7 @@ package io.grpc.internal; -import static com.google.common.base.Charsets.UTF_8; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/core/src/test/java/io/grpc/internal/Http2ClientStreamTransportStateTest.java b/core/src/test/java/io/grpc/internal/Http2ClientStreamTransportStateTest.java index e587a7709ec..9d32bf1af7d 100644 --- a/core/src/test/java/io/grpc/internal/Http2ClientStreamTransportStateTest.java +++ b/core/src/test/java/io/grpc/internal/Http2ClientStreamTransportStateTest.java @@ -16,9 +16,9 @@ package io.grpc.internal; -import static com.google.common.base.Charsets.US_ASCII; import static io.grpc.internal.ClientStreamListener.RpcProgress.PROCESSED; import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE; +import static java.nio.charset.StandardCharsets.US_ASCII; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; diff --git a/core/src/test/java/io/grpc/internal/ReadableBuffersArrayTest.java b/core/src/test/java/io/grpc/internal/ReadableBuffersArrayTest.java index d5c4fa77fd8..5b0fb02c611 100644 --- a/core/src/test/java/io/grpc/internal/ReadableBuffersArrayTest.java +++ b/core/src/test/java/io/grpc/internal/ReadableBuffersArrayTest.java @@ -16,8 +16,8 @@ package io.grpc.internal; -import static com.google.common.base.Charsets.UTF_8; import static io.grpc.internal.ReadableBuffers.wrap; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; diff --git a/core/src/test/java/io/grpc/internal/ReadableBuffersByteBufferTest.java b/core/src/test/java/io/grpc/internal/ReadableBuffersByteBufferTest.java index a040182c259..67e7aaf9132 100644 --- a/core/src/test/java/io/grpc/internal/ReadableBuffersByteBufferTest.java +++ b/core/src/test/java/io/grpc/internal/ReadableBuffersByteBufferTest.java @@ -16,7 +16,7 @@ package io.grpc.internal; -import static com.google.common.base.Charsets.UTF_8; +import static java.nio.charset.StandardCharsets.UTF_8; import java.nio.ByteBuffer; diff --git a/core/src/test/java/io/grpc/internal/ReadableBuffersTest.java b/core/src/test/java/io/grpc/internal/ReadableBuffersTest.java index 2bc5a8a3760..b9135b49503 100644 --- a/core/src/test/java/io/grpc/internal/ReadableBuffersTest.java +++ b/core/src/test/java/io/grpc/internal/ReadableBuffersTest.java @@ -16,7 +16,7 @@ package io.grpc.internal; -import static com.google.common.base.Charsets.UTF_8; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; diff --git a/core/src/test/java/io/grpc/internal/ServerCallImplTest.java b/core/src/test/java/io/grpc/internal/ServerCallImplTest.java index c3c2ab15e22..652c94a4640 100644 --- a/core/src/test/java/io/grpc/internal/ServerCallImplTest.java +++ b/core/src/test/java/io/grpc/internal/ServerCallImplTest.java @@ -16,8 +16,8 @@ package io.grpc.internal; -import static com.google.common.base.Charsets.UTF_8; import static io.grpc.internal.GrpcUtil.CONTENT_LENGTH_KEY; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; diff --git a/core/src/test/java/io/grpc/internal/TransportFrameUtilTest.java b/core/src/test/java/io/grpc/internal/TransportFrameUtilTest.java index 5fa789df4f3..8b4bc170d52 100644 --- a/core/src/test/java/io/grpc/internal/TransportFrameUtilTest.java +++ b/core/src/test/java/io/grpc/internal/TransportFrameUtilTest.java @@ -16,10 +16,10 @@ package io.grpc.internal; -import static com.google.common.base.Charsets.US_ASCII; -import static com.google.common.base.Charsets.UTF_8; import static io.grpc.Metadata.ASCII_STRING_MARSHALLER; import static io.grpc.Metadata.BINARY_BYTE_MARSHALLER; +import static java.nio.charset.StandardCharsets.US_ASCII; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; diff --git a/core/src/testFixtures/java/io/grpc/internal/AbstractTransportTest.java b/core/src/testFixtures/java/io/grpc/internal/AbstractTransportTest.java index 5d7aeca684b..62cbdc4f67b 100644 --- a/core/src/testFixtures/java/io/grpc/internal/AbstractTransportTest.java +++ b/core/src/testFixtures/java/io/grpc/internal/AbstractTransportTest.java @@ -16,8 +16,8 @@ package io.grpc.internal; -import static com.google.common.base.Charsets.UTF_8; import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; diff --git a/core/src/testFixtures/java/io/grpc/internal/ReadableBufferTestBase.java b/core/src/testFixtures/java/io/grpc/internal/ReadableBufferTestBase.java index 97e0df38ae7..202fb7ee8a4 100644 --- a/core/src/testFixtures/java/io/grpc/internal/ReadableBufferTestBase.java +++ b/core/src/testFixtures/java/io/grpc/internal/ReadableBufferTestBase.java @@ -16,7 +16,7 @@ package io.grpc.internal; -import static com.google.common.base.Charsets.UTF_8; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/GrpclbFallbackTestClient.java b/interop-testing/src/main/java/io/grpc/testing/integration/GrpclbFallbackTestClient.java index 9fc017c0e35..8ce83f73e6d 100644 --- a/interop-testing/src/main/java/io/grpc/testing/integration/GrpclbFallbackTestClient.java +++ b/interop-testing/src/main/java/io/grpc/testing/integration/GrpclbFallbackTestClient.java @@ -16,7 +16,7 @@ package io.grpc.testing.integration; -import static com.google.common.base.Charsets.UTF_8; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import com.google.common.io.CharStreams; diff --git a/netty/src/main/java/io/grpc/netty/GrpcHttp2HeadersUtils.java b/netty/src/main/java/io/grpc/netty/GrpcHttp2HeadersUtils.java index c0d60721a1b..96c4310ae3d 100644 --- a/netty/src/main/java/io/grpc/netty/GrpcHttp2HeadersUtils.java +++ b/netty/src/main/java/io/grpc/netty/GrpcHttp2HeadersUtils.java @@ -31,12 +31,12 @@ package io.grpc.netty; -import static com.google.common.base.Charsets.US_ASCII; import static com.google.common.base.Preconditions.checkArgument; import static io.grpc.netty.Utils.TE_HEADER; import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR; import static io.netty.handler.codec.http2.Http2Exception.connectionError; import static io.netty.util.AsciiString.isUpperCase; +import static java.nio.charset.StandardCharsets.US_ASCII; import com.google.common.io.BaseEncoding; import com.google.errorprone.annotations.CanIgnoreReturnValue; diff --git a/okhttp/src/test/java/io/grpc/okhttp/ExceptionHandlingFrameWriterTest.java b/okhttp/src/test/java/io/grpc/okhttp/ExceptionHandlingFrameWriterTest.java index a9d39088844..8829abac034 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/ExceptionHandlingFrameWriterTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/ExceptionHandlingFrameWriterTest.java @@ -16,9 +16,9 @@ package io.grpc.okhttp; -import static com.google.common.base.Charsets.UTF_8; import static com.google.common.truth.Truth.assertThat; import static io.grpc.okhttp.ExceptionHandlingFrameWriter.getLogLevel; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java index ab7dff98444..987cc09203e 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java @@ -16,7 +16,6 @@ package io.grpc.okhttp; -import static com.google.common.base.Charsets.UTF_8; import static com.google.common.truth.Truth.assertThat; import static io.grpc.internal.ClientStreamListener.RpcProgress.MISCARRIED; import static io.grpc.internal.ClientStreamListener.RpcProgress.PROCESSED; @@ -25,6 +24,7 @@ import static io.grpc.okhttp.Headers.HTTP_SCHEME_HEADER; import static io.grpc.okhttp.Headers.METHOD_HEADER; import static io.grpc.okhttp.Headers.TE_HEADER; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpProtocolNegotiatorTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpProtocolNegotiatorTest.java index 3a4a21b2467..cc9f30862af 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpProtocolNegotiatorTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpProtocolNegotiatorTest.java @@ -16,7 +16,7 @@ package io.grpc.okhttp; -import static com.google.common.base.Charsets.UTF_8; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java index 6438cf83a1d..d64d314d7d8 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java @@ -16,12 +16,12 @@ package io.grpc.okhttp; -import static com.google.common.base.Charsets.UTF_8; import static com.google.common.truth.Truth.assertThat; import static io.grpc.okhttp.Headers.CONTENT_TYPE_HEADER; import static io.grpc.okhttp.Headers.HTTP_SCHEME_HEADER; import static io.grpc.okhttp.Headers.METHOD_HEADER; import static io.grpc.okhttp.Headers.TE_HEADER; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.mockito.AdditionalAnswers.answerVoid; import static org.mockito.AdditionalAnswers.delegatesTo; import static org.mockito.ArgumentMatchers.any; diff --git a/services/src/test/java/io/grpc/protobuf/services/BinaryLogProviderTest.java b/services/src/test/java/io/grpc/protobuf/services/BinaryLogProviderTest.java index 2d2b7651c0a..67b187e9d7a 100644 --- a/services/src/test/java/io/grpc/protobuf/services/BinaryLogProviderTest.java +++ b/services/src/test/java/io/grpc/protobuf/services/BinaryLogProviderTest.java @@ -16,8 +16,8 @@ package io.grpc.protobuf.services; -import static com.google.common.base.Charsets.UTF_8; import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; diff --git a/testing/src/main/java/io/grpc/internal/testing/StatsTestUtils.java b/testing/src/main/java/io/grpc/internal/testing/StatsTestUtils.java index cd525eeeeb9..a15559ed5cb 100644 --- a/testing/src/main/java/io/grpc/internal/testing/StatsTestUtils.java +++ b/testing/src/main/java/io/grpc/internal/testing/StatsTestUtils.java @@ -16,8 +16,8 @@ package io.grpc.internal.testing; -import static com.google.common.base.Charsets.UTF_8; import static com.google.common.base.Preconditions.checkNotNull; +import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.base.Function; import com.google.common.collect.ImmutableMap; diff --git a/xds/src/test/java/io/grpc/xds/internal/rbac/engine/GrpcAuthorizationEngineTest.java b/xds/src/test/java/io/grpc/xds/internal/rbac/engine/GrpcAuthorizationEngineTest.java index 4fb38f661e1..10287c11262 100644 --- a/xds/src/test/java/io/grpc/xds/internal/rbac/engine/GrpcAuthorizationEngineTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/rbac/engine/GrpcAuthorizationEngineTest.java @@ -16,8 +16,8 @@ package io.grpc.xds.internal.rbac.engine; -import static com.google.common.base.Charsets.US_ASCII; import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.US_ASCII; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; From 70ae83288de6a712e905c0f97eebd5713ad01677 Mon Sep 17 00:00:00 2001 From: Kannan J Date: Tue, 6 Aug 2024 20:38:08 +0530 Subject: [PATCH 021/103] Upgrade Netty to 4.1.110 and tcnative to 2.0.65 (#11444) Upgrade Netty to 4.1.110 and tcnative to 2.0.65. --- MODULE.bazel | 28 +++++++++---------- SECURITY.md | 3 +- gradle/libs.versions.toml | 4 +-- .../io/grpc/netty/shaded/ShadingTest.java | 2 +- repositories.bzl | 28 +++++++++---------- 5 files changed, 33 insertions(+), 32 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index 2b5d85490f3..81c3249f47a 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -22,20 +22,20 @@ IO_GRPC_GRPC_JAVA_ARTIFACTS = [ "com.google.truth:truth:1.4.2", "com.squareup.okhttp:okhttp:2.7.5", "com.squareup.okio:okio:2.10.0", # 3.0+ needs swapping to -jvm; need work to avoid flag-day - "io.netty:netty-buffer:4.1.100.Final", - "io.netty:netty-codec-http2:4.1.100.Final", - "io.netty:netty-codec-http:4.1.100.Final", - "io.netty:netty-codec-socks:4.1.100.Final", - "io.netty:netty-codec:4.1.100.Final", - "io.netty:netty-common:4.1.100.Final", - "io.netty:netty-handler-proxy:4.1.100.Final", - "io.netty:netty-handler:4.1.100.Final", - "io.netty:netty-resolver:4.1.100.Final", - "io.netty:netty-tcnative-boringssl-static:2.0.61.Final", - "io.netty:netty-tcnative-classes:2.0.61.Final", - "io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.100.Final", - "io.netty:netty-transport-native-unix-common:4.1.100.Final", - "io.netty:netty-transport:4.1.100.Final", + "io.netty:netty-buffer:4.1.110.Final", + "io.netty:netty-codec-http2:4.1.110.Final", + "io.netty:netty-codec-http:4.1.110.Final", + "io.netty:netty-codec-socks:4.1.110.Final", + "io.netty:netty-codec:4.1.110.Final", + "io.netty:netty-common:4.1.110.Final", + "io.netty:netty-handler-proxy:4.1.110.Final", + "io.netty:netty-handler:4.1.110.Final", + "io.netty:netty-resolver:4.1.110.Final", + "io.netty:netty-tcnative-boringssl-static:2.0.65.Final", + "io.netty:netty-tcnative-classes:2.0.65.Final", + "io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.110.Final", + "io.netty:netty-transport-native-unix-common:4.1.110.Final", + "io.netty:netty-transport:4.1.110.Final", "io.opencensus:opencensus-api:0.31.0", "io.opencensus:opencensus-contrib-grpc-metrics:0.31.0", "io.perfmark:perfmark-api:0.27.0", diff --git a/SECURITY.md b/SECURITY.md index 774579bf68b..5c5e3598b29 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -398,7 +398,8 @@ grpc-netty version | netty-handler version | netty-tcnative-boringssl-static ver 1.56.x | 4.1.87.Final | 2.0.61.Final 1.57.x-1.58.x | 4.1.93.Final | 2.0.61.Final 1.59.x | 4.1.97.Final | 2.0.61.Final -1.60.x- | 4.1.100.Final | 2.0.61.Final +1.60.x-1.66.x | 4.1.100.Final | 2.0.61.Final +1.67.x | 4.1.110.Final | 2.0.65.Final _(grpc-netty-shaded avoids issues with keeping these versions in sync.)_ diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 78550e9c95e..299ca60ab4b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,8 +1,8 @@ [versions] -netty = '4.1.100.Final' +netty = '4.1.110.Final' # Keep the following references of tcnative version in sync whenever it's updated: # SECURITY.md -nettytcnative = '2.0.61.Final' +nettytcnative = '2.0.65.Final' opencensus = "0.31.1" # Not upgrading to 4.x as it is not yet ABI compatible. # https://github.com/protocolbuffers/protobuf/issues/17247 diff --git a/netty/shaded/src/testShadow/java/io/grpc/netty/shaded/ShadingTest.java b/netty/shaded/src/testShadow/java/io/grpc/netty/shaded/ShadingTest.java index 7a5e4b43c8b..89803998925 100644 --- a/netty/shaded/src/testShadow/java/io/grpc/netty/shaded/ShadingTest.java +++ b/netty/shaded/src/testShadow/java/io/grpc/netty/shaded/ShadingTest.java @@ -79,7 +79,7 @@ public void nettyResourcesUpdated() throws IOException { InputStream inputStream = NettyChannelBuilder.class.getClassLoader() .getResourceAsStream( "META-INF/native-image/io.grpc.netty.shaded.io.netty/netty-transport/" - + "reflection-config.json"); + + "reflect-config.json"); assertThat(inputStream).isNotNull(); Scanner s = new Scanner(inputStream, StandardCharsets.UTF_8.name()).useDelimiter("\\A"); diff --git a/repositories.bzl b/repositories.bzl index af3acc8ddcf..455e9dcf3ca 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -26,20 +26,20 @@ IO_GRPC_GRPC_JAVA_ARTIFACTS = [ "com.google.truth:truth:1.4.2", "com.squareup.okhttp:okhttp:2.7.5", "com.squareup.okio:okio:2.10.0", # 3.0+ needs swapping to -jvm; need work to avoid flag-day - "io.netty:netty-buffer:4.1.100.Final", - "io.netty:netty-codec-http2:4.1.100.Final", - "io.netty:netty-codec-http:4.1.100.Final", - "io.netty:netty-codec-socks:4.1.100.Final", - "io.netty:netty-codec:4.1.100.Final", - "io.netty:netty-common:4.1.100.Final", - "io.netty:netty-handler-proxy:4.1.100.Final", - "io.netty:netty-handler:4.1.100.Final", - "io.netty:netty-resolver:4.1.100.Final", - "io.netty:netty-tcnative-boringssl-static:2.0.61.Final", - "io.netty:netty-tcnative-classes:2.0.61.Final", - "io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.100.Final", - "io.netty:netty-transport-native-unix-common:4.1.100.Final", - "io.netty:netty-transport:4.1.100.Final", + "io.netty:netty-buffer:4.1.110.Final", + "io.netty:netty-codec-http2:4.1.110.Final", + "io.netty:netty-codec-http:4.1.110.Final", + "io.netty:netty-codec-socks:4.1.110.Final", + "io.netty:netty-codec:4.1.110.Final", + "io.netty:netty-common:4.1.110.Final", + "io.netty:netty-handler-proxy:4.1.110.Final", + "io.netty:netty-handler:4.1.110.Final", + "io.netty:netty-resolver:4.1.110.Final", + "io.netty:netty-tcnative-boringssl-static:2.0.65.Final", + "io.netty:netty-tcnative-classes:2.0.65.Final", + "io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.110.Final", + "io.netty:netty-transport-native-unix-common:4.1.110.Final", + "io.netty:netty-transport:4.1.110.Final", "io.opencensus:opencensus-api:0.31.0", "io.opencensus:opencensus-contrib-grpc-metrics:0.31.0", "io.perfmark:perfmark-api:0.27.0", From 18d73a36812440c1e62c51f5da76c9dfe9774da5 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Wed, 7 Aug 2024 16:34:11 -0700 Subject: [PATCH 022/103] CONTRIBUTING.md: Update commit guidelines It came up in #11073, and I saw it could use a little updating. Notably, I'm linking to a guide to what Git commit messages should look like. I also tried to make the language less heavy-handed and demanding. --- CONTRIBUTING.md | 57 ++++++++++++++++++++++--------------------------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ce40827e748..646a7d986fd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,43 +30,36 @@ style configurations are commonly useful. For IntelliJ 14, copy the style to `~/.IdeaIC14/config/codestyles/`, start IntelliJ, go to File > Settings > Code Style, and set the Scheme to `GoogleStyle`. -## Maintaining clean commit history - -We have few conventions for keeping history clean and making code reviews easier -for reviewers: - -* First line of commit messages should be in format of - - `package-name: summary of change` - - where the summary finishes the sentence: `This commit improves gRPC to ____________.` - - for example: - - `core,netty,interop-testing: add capacitive duractance to turbo encabulators` - -* Every time you receive a feedback on your pull request, push changes that - address it as a separate one or multiple commits with a descriptive commit - message (try avoid using vauge `addressed pr feedback` type of messages). - - Project maintainers are obligated to squash those commits into one when - merging. - ## Guidelines for Pull Requests How to get your contributions merged smoothly and quickly. - Create **small PRs** that are narrowly focused on **addressing a single concern**. We often times receive PRs that are trying to fix several things at a time, but only one fix is considered acceptable, nothing gets merged and both author's & review's time is wasted. Create more PRs to address different concerns and everyone will be happy. -- For speculative changes, consider opening an issue and discussing it first. If you are suggesting a behavioral or API change, consider starting with a [gRFC proposal](https://github.com/grpc/proposal). - -- Provide a good **PR description** as a record of **what** change is being made and **why** it was made. Link to a github issue if it exists. - -- Don't fix code style and formatting unless you are already changing that line to address an issue. PRs with irrelevant changes won't be merged. If you do want to fix formatting or style, do that in a separate PR. - -- Unless your PR is trivial, you should expect there will be reviewer comments that you'll need to address before merging. We expect you to be reasonably responsive to those comments, otherwise the PR will be closed after 2-3 weeks of inactivity. - -- Maintain **clean commit history** and use **meaningful commit messages**. See [maintaining clean commit history](#maintaining-clean-commit-history) for details. - +- For speculative changes, consider opening an issue and discussing it to avoid + wasting time on an inappropriate approach. If you are suggesting a behavioral + or API change, consider starting with a [gRFC + proposal](https://github.com/grpc/proposal). + +- Follow [typical Git commit message](https://cbea.ms/git-commit/#seven-rules) + structure. Have a good **commit description** as a record of **what** and + **why** the change is being made. Link to a GitHub issue if it exists. The + commit description makes a good PR description and is auto-copied by GitHub if + you have a single commit when creating the PR. + + If your change is mostly for a single module (e.g., other module changes are + trivial), prefix your commit summary with the module name changed. Instead of + "Add HTTP/2 faster-than-light support to gRPC Netty" it is more terse as + "netty: Add faster-than-light support". + +- Don't fix code style and formatting unless you are already changing that line + to address an issue. If you do want to fix formatting or style, do that in a + separate PR. + +- Unless your PR is trivial, you should expect there will be reviewer comments + that you'll need to address before merging. Address comments with additional + commits so the reviewer can review just the changes; do not squash reviewed + commits unless the reviewer agrees. PRs are squashed when merging. + - Keep your PR up to date with upstream/master (if there are merge conflicts, we can't really merge your change). - **All tests need to be passing** before your change can be merged. We recommend you **run tests locally** before creating your PR to catch breakages early on. Also, `./gradlew build` (`gradlew build` on Windows) **must not introduce any new warnings**. From 40e2b165b792ab1b812b8dc15829ac0a5531b1e5 Mon Sep 17 00:00:00 2001 From: Petr Portnov | PROgrm_JARvis Date: Thu, 8 Aug 2024 08:18:12 +0300 Subject: [PATCH 023/103] Make once-set fields of `AbstractClientStream` `final` (#11389) --- core/src/main/java/io/grpc/internal/AbstractClientStream.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/AbstractClientStream.java b/core/src/main/java/io/grpc/internal/AbstractClientStream.java index 51c31993f46..bb346657d53 100644 --- a/core/src/main/java/io/grpc/internal/AbstractClientStream.java +++ b/core/src/main/java/io/grpc/internal/AbstractClientStream.java @@ -92,8 +92,8 @@ void writeFrame( private final TransportTracer transportTracer; private final Framer framer; - private boolean shouldBeCountedForInUse; - private boolean useGet; + private final boolean shouldBeCountedForInUse; + private final boolean useGet; private Metadata headers; /** * Whether cancel() has been called. This is not strictly necessary, but removes the delay between From 72a977bf7fcecc40f5ee715f833aa008d5b5ee62 Mon Sep 17 00:00:00 2001 From: Larry Safran Date: Thu, 8 Aug 2024 15:59:57 -0700 Subject: [PATCH 024/103] Dualstack example (#11451) --- examples/example-dualstack/README.md | 54 ++++++++ examples/example-dualstack/build.gradle | 79 +++++++++++ examples/example-dualstack/pom.xml | 122 +++++++++++++++++ examples/example-dualstack/settings.gradle | 10 ++ .../examples/dualstack/DualStackClient.java | 95 +++++++++++++ .../examples/dualstack/DualStackServer.java | 126 ++++++++++++++++++ .../ExampleDualStackNameResolver.java | 98 ++++++++++++++ .../ExampleDualStackNameResolverProvider.java | 47 +++++++ .../main/proto/helloworld/helloworld.proto | 37 +++++ .../loadbalance/ExampleNameResolver.java | 13 +- .../loadbalance/LoadBalanceServer.java | 32 +++-- .../nameresolve/NameResolveClient.java | 12 +- 12 files changed, 694 insertions(+), 31 deletions(-) create mode 100644 examples/example-dualstack/README.md create mode 100644 examples/example-dualstack/build.gradle create mode 100644 examples/example-dualstack/pom.xml create mode 100644 examples/example-dualstack/settings.gradle create mode 100644 examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/DualStackClient.java create mode 100644 examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/DualStackServer.java create mode 100644 examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java create mode 100644 examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolverProvider.java create mode 100644 examples/example-dualstack/src/main/proto/helloworld/helloworld.proto diff --git a/examples/example-dualstack/README.md b/examples/example-dualstack/README.md new file mode 100644 index 00000000000..6c191661d1b --- /dev/null +++ b/examples/example-dualstack/README.md @@ -0,0 +1,54 @@ +# gRPC Dualstack Example + +The dualstack example uses a custom name resolver that provides both IPv4 and IPv6 localhost +endpoints for each of 3 server instances. The client will first use the default name resolver and +load balancers which will only connect tot he first server. It will then use the +custom name resolver with round robin to connect to each of the servers in turn. The 3 instances +of the server will bind respectively to: both IPv4 and IPv6, IPv4 only, and IPv6 only. + +The example requires grpc-java to already be built. You are strongly encouraged +to check out a git release tag, since there will already be a build of grpc +available. Otherwise, you must follow [COMPILING](../../COMPILING.md). + +### Build the example + +To build the dualstack example server and client. From the + `grpc-java/examples/example-dualstack` directory run: + +```bash +$ ../gradlew installDist +``` + +This creates the scripts +`build/install/example-dualstack/bin/dual-stack-server` + and `build/install/example-dualstack/bin/dual-stack-client`. + +To run the dualstack example, run the server with: + +```bash +$ ./build/install/example-dualstack/bin/dual-stack-server +``` + +And in a different terminal window run the client. + +```bash +$ ./build/install/example-dualstack/bin/dual-stack-client +``` + +### Maven + +If you prefer to use Maven: + +Run in the example-debug directory: + +```bash +$ mvn verify +$ # Run the server in one terminal +$ mvn exec:java -Dexec.mainClass=io.grpc.examples.dualstack.DualStackServer +``` + +```bash +$ # In another terminal run the client +$ mvn exec:java -Dexec.mainClass=io.grpc.examples.dualstack.DualStackClient +``` + diff --git a/examples/example-dualstack/build.gradle b/examples/example-dualstack/build.gradle new file mode 100644 index 00000000000..32b35af8a87 --- /dev/null +++ b/examples/example-dualstack/build.gradle @@ -0,0 +1,79 @@ +plugins { + id 'application' // Provide convenience executables for trying out the examples. + id 'java' + + id "com.google.protobuf" version "0.9.4" + + // Generate IntelliJ IDEA's .idea & .iml project files + id 'idea' +} + +repositories { + maven { // The google mirror is less flaky than mavenCentral() + url "https://maven-central.storage-download.googleapis.com/maven2/" } + mavenCentral() + mavenLocal() +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +// IMPORTANT: You probably want the non-SNAPSHOT version of gRPC. Make sure you +// are looking at a tagged version of the example and not "master"! + +// Feel free to delete the comment at the next line. It is just for safely +// updating the version in our release process. +def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def protobufVersion = '3.25.3' + +dependencies { + implementation "io.grpc:grpc-protobuf:${grpcVersion}" + implementation "io.grpc:grpc-netty:${grpcVersion}" + implementation "io.grpc:grpc-stub:${grpcVersion}" + implementation "io.grpc:grpc-services:${grpcVersion}" + compileOnly "org.apache.tomcat:annotations-api:6.0.53" +} + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:${protobufVersion}" + } + plugins { + grpc { + artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" + } + } + generateProtoTasks { + all()*.plugins { + grpc {} + } + } +} + +startScripts.enabled = false + +task DualStackClient(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.dualstack.DualStackClient' + applicationName = 'dual-stack-client' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + +task DualStackServer(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.dualstack.DualStackServer' + applicationName = 'dual-stack-server' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + +application { + applicationDistribution.into('bin') { + from(DualStackClient) + from(DualStackServer) + filePermissions { + unix(0755) + } + } +} diff --git a/examples/example-dualstack/pom.xml b/examples/example-dualstack/pom.xml new file mode 100644 index 00000000000..710b48ee617 --- /dev/null +++ b/examples/example-dualstack/pom.xml @@ -0,0 +1,122 @@ + + 4.0.0 + io.grpc + example-dualstack + jar + + 1.67.0-SNAPSHOT + example-dualstack + https://github.com/grpc/grpc-java + + + UTF-8 + 1.67.0-SNAPSHOT + 3.25.3 + + 1.8 + 1.8 + + + + + + io.grpc + grpc-bom + ${grpc.version} + pom + import + + + + + + + io.grpc + grpc-services + + + io.grpc + grpc-protobuf + + + io.grpc + grpc-stub + + + io.grpc + grpc-netty + + + org.apache.tomcat + annotations-api + 6.0.53 + provided + + + io.grpc + grpc-netty-shaded + runtime + + + junit + junit + 4.13.2 + test + + + io.grpc + grpc-testing + test + + + + + + + kr.motd.maven + os-maven-plugin + 1.7.1 + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + 0.6.1 + + com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier} + grpc-java + io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} + + + + + compile + compile-custom + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + 1.4.1 + + + enforce + + enforce + + + + + + + + + + + + diff --git a/examples/example-dualstack/settings.gradle b/examples/example-dualstack/settings.gradle new file mode 100644 index 00000000000..0aae8f7304e --- /dev/null +++ b/examples/example-dualstack/settings.gradle @@ -0,0 +1,10 @@ +pluginManagement { + repositories { + maven { // The google mirror is less flaky than mavenCentral() + url "https://maven-central.storage-download.googleapis.com/maven2/" + } + gradlePluginPortal() + } +} + +rootProject.name = 'example-dualstack' diff --git a/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/DualStackClient.java b/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/DualStackClient.java new file mode 100644 index 00000000000..b9993a524d6 --- /dev/null +++ b/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/DualStackClient.java @@ -0,0 +1,95 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.examples.dualstack; + +import io.grpc.Channel; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import io.grpc.NameResolverRegistry; +import io.grpc.StatusRuntimeException; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A client that requests greetings from the {@link DualStackServer}. + * First it sends 5 requests using the default nameresolver and load balancer. + * Then it sends 10 requests using the example nameresolver and round robin load balancer. These + * requests are evenly distributed among the 3 servers rather than favoring the server listening + * on both addresses because the ExampleDualStackNameResolver groups the 3 servers as 3 endpoints + * each with 2 addresses. + */ +public class DualStackClient { + public static final String channelTarget = "example:///lb.example.grpc.io"; + private static final Logger logger = Logger.getLogger(DualStackClient.class.getName()); + private final GreeterGrpc.GreeterBlockingStub blockingStub; + + public DualStackClient(Channel channel) { + blockingStub = GreeterGrpc.newBlockingStub(channel); + } + + public static void main(String[] args) throws Exception { + NameResolverRegistry.getDefaultRegistry() + .register(new ExampleDualStackNameResolverProvider()); + + logger.info("\n **** Use default DNS resolver ****"); + ManagedChannel channel = ManagedChannelBuilder.forTarget("localhost:50051") + .usePlaintext() + .build(); + try { + DualStackClient client = new DualStackClient(channel); + for (int i = 0; i < 5; i++) { + client.greet("request:" + i); + } + } finally { + channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + } + + logger.info("\n **** Change to use example name resolver ****"); + /* + Dial to "example:///resolver.example.grpc.io", use {@link ExampleNameResolver} to create connection + "resolver.example.grpc.io" is converted to {@link java.net.URI.path} + */ + channel = ManagedChannelBuilder.forTarget(channelTarget) + .defaultLoadBalancingPolicy("round_robin") + .usePlaintext() + .build(); + try { + DualStackClient client = new DualStackClient(channel); + for (int i = 0; i < 10; i++) { + client.greet("request:" + i); + } + } finally { + channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + } + } + + public void greet(String name) { + HelloRequest request = HelloRequest.newBuilder().setName(name).build(); + HelloReply response; + try { + response = blockingStub.sayHello(request); + } catch (StatusRuntimeException e) { + logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); + return; + } + logger.info("Greeting: " + response.getMessage()); + } +} diff --git a/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/DualStackServer.java b/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/DualStackServer.java new file mode 100644 index 00000000000..43b21e963f8 --- /dev/null +++ b/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/DualStackServer.java @@ -0,0 +1,126 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.examples.dualstack; + +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.netty.NettyServerBuilder; +import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +/** + * Starts 3 different greeter services each on its own port, but all for localhost. + * The first service listens on both IPv4 and IPv6, + * the second on just IPv4, and the third on just IPv6. + */ +public class DualStackServer { + private static final Logger logger = Logger.getLogger(DualStackServer.class.getName()); + private List servers; + + public static void main(String[] args) throws IOException, InterruptedException { + final DualStackServer server = new DualStackServer(); + server.start(); + server.blockUntilShutdown(); + } + + private void start() throws IOException { + InetSocketAddress inetSocketAddress; + + servers = new ArrayList<>(); + int[] serverPorts = ExampleDualStackNameResolver.SERVER_PORTS; + for (int i = 0; i < serverPorts.length; i++ ) { + String addressType; + int port = serverPorts[i]; + ServerBuilder serverBuilder; + switch (i) { + case 0: + serverBuilder = ServerBuilder.forPort(port); // bind to both IPv4 and IPv6 + addressType = "both IPv4 and IPv6"; + break; + case 1: + // bind to IPv4 only + inetSocketAddress = new InetSocketAddress("127.0.0.1", port); + serverBuilder = NettyServerBuilder.forAddress(inetSocketAddress); + addressType = "IPv4 only"; + break; + case 2: + // bind to IPv6 only + inetSocketAddress = new InetSocketAddress("::1", port); + serverBuilder = NettyServerBuilder.forAddress(inetSocketAddress); + addressType = "IPv6 only"; + break; + default: + throw new IllegalStateException("Unexpected value: " + i); + } + + servers.add(serverBuilder + .addService(new GreeterImpl(port, addressType)) + .build() + .start()); + logger.info("Server started, listening on " + port); + } + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + System.err.println("*** shutting down gRPC server since JVM is shutting down"); + try { + DualStackServer.this.stop(); + } catch (InterruptedException e) { + e.printStackTrace(System.err); + } + System.err.println("*** server shut down"); + })); + } + + private void stop() throws InterruptedException { + for (Server server : servers) { + server.shutdown().awaitTermination(30, TimeUnit.SECONDS); + } + } + + private void blockUntilShutdown() throws InterruptedException { + for (Server server : servers) { + server.awaitTermination(); + } + } + + static class GreeterImpl extends GreeterGrpc.GreeterImplBase { + + int port; + String addressType; + + public GreeterImpl(int port, String addressType) { + this.port = port; + this.addressType = addressType; + } + + @Override + public void sayHello(HelloRequest req, StreamObserver responseObserver) { + String msg = String.format("Hello %s from server<%d> type: %s", + req.getName(), this.port, addressType); + HelloReply reply = HelloReply.newBuilder().setMessage(msg).build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + } +} diff --git a/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java b/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java new file mode 100644 index 00000000000..70675b3de3d --- /dev/null +++ b/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolver.java @@ -0,0 +1,98 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.examples.dualstack; + +import com.google.common.collect.ImmutableMap; +import io.grpc.EquivalentAddressGroup; +import io.grpc.NameResolver; +import io.grpc.Status; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * A fake name resolver that resolves to a hard-coded list of 3 endpoints (EquivalentAddressGropu) + * each with 2 addresses (one IPv4 and one IPv6). + */ +public class ExampleDualStackNameResolver extends NameResolver { + static public final int[] SERVER_PORTS = {50051, 50052, 50053}; + + // This is a fake name resolver, so we just hard code the address here. + private static final ImmutableMap>> addrStore = + ImmutableMap.>>builder() + .put("lb.example.grpc.io", + Arrays.stream(SERVER_PORTS) + .mapToObj(port -> getLocalAddrs(port)) + .collect(Collectors.toList()) + ) + .build(); + + private Listener2 listener; + + private final URI uri; + + public ExampleDualStackNameResolver(URI targetUri) { + this.uri = targetUri; + } + + private static List getLocalAddrs(int port) { + return Arrays.asList( + new InetSocketAddress("127.0.0.1", port), + new InetSocketAddress("::1", port)); + } + + @Override + public String getServiceAuthority() { + return uri.getPath().substring(1); + } + + @Override + public void shutdown() { + } + + @Override + public void start(Listener2 listener) { + this.listener = listener; + this.resolve(); + } + + @Override + public void refresh() { + this.resolve(); + } + + private void resolve() { + List> addresses = addrStore.get(uri.getPath().substring(1)); + try { + List eagList = new ArrayList<>(); + for (List endpoint : addresses) { + // every server is an EquivalentAddressGroup, so they can be accessed randomly + eagList.add(new EquivalentAddressGroup(endpoint)); + } + + this.listener.onResult(ResolutionResult.newBuilder().setAddresses(eagList).build()); + } catch (Exception e){ + // when error occurs, notify listener + this.listener.onError(Status.UNAVAILABLE.withDescription("Unable to resolve host ").withCause(e)); + } + } + +} diff --git a/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolverProvider.java b/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolverProvider.java new file mode 100644 index 00000000000..a01d68aca3e --- /dev/null +++ b/examples/example-dualstack/src/main/java/io/grpc/examples/dualstack/ExampleDualStackNameResolverProvider.java @@ -0,0 +1,47 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.examples.dualstack; + +import io.grpc.NameResolver; +import io.grpc.NameResolverProvider; + +import java.net.URI; + +public class ExampleDualStackNameResolverProvider extends NameResolverProvider { + public static final String exampleScheme = "example"; + + @Override + public NameResolver newNameResolver(URI targetUri, NameResolver.Args args) { + return new ExampleDualStackNameResolver(targetUri); + } + + @Override + protected boolean isAvailable() { + return true; + } + + @Override + protected int priority() { + return 5; + } + + @Override + // gRPC choose the first NameResolverProvider that supports the target URI scheme. + public String getDefaultScheme() { + return exampleScheme; + } +} diff --git a/examples/example-dualstack/src/main/proto/helloworld/helloworld.proto b/examples/example-dualstack/src/main/proto/helloworld/helloworld.proto new file mode 100644 index 00000000000..c60d9416f1f --- /dev/null +++ b/examples/example-dualstack/src/main/proto/helloworld/helloworld.proto @@ -0,0 +1,37 @@ +// 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. +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) {} +} + +// 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/src/main/java/io/grpc/examples/loadbalance/ExampleNameResolver.java b/examples/src/main/java/io/grpc/examples/loadbalance/ExampleNameResolver.java index f562f0ac107..6ef327ade84 100644 --- a/examples/src/main/java/io/grpc/examples/loadbalance/ExampleNameResolver.java +++ b/examples/src/main/java/io/grpc/examples/loadbalance/ExampleNameResolver.java @@ -28,12 +28,12 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import java.util.stream.Stream; import static io.grpc.examples.loadbalance.LoadBalanceClient.exampleServiceName; public class ExampleNameResolver extends NameResolver { + static private final int[] SERVER_PORTS = {50051, 50052, 50053}; private Listener2 listener; private final URI uri; @@ -44,12 +44,11 @@ public ExampleNameResolver(URI targetUri) { this.uri = targetUri; // This is a fake name resolver, so we just hard code the address here. addrStore = ImmutableMap.>builder() - .put(exampleServiceName, - Stream.iterate(LoadBalanceServer.startPort,p->p+1) - .limit(LoadBalanceServer.serverCount) - .map(port->new InetSocketAddress("localhost",port)) - .collect(Collectors.toList()) - ) + .put(exampleServiceName, + Arrays.stream(SERVER_PORTS) + .mapToObj(port->new InetSocketAddress("localhost",port)) + .collect(Collectors.toList()) + ) .build(); } diff --git a/examples/src/main/java/io/grpc/examples/loadbalance/LoadBalanceServer.java b/examples/src/main/java/io/grpc/examples/loadbalance/LoadBalanceServer.java index c97d209497a..85ae92a537a 100644 --- a/examples/src/main/java/io/grpc/examples/loadbalance/LoadBalanceServer.java +++ b/examples/src/main/java/io/grpc/examples/loadbalance/LoadBalanceServer.java @@ -24,23 +24,24 @@ import io.grpc.stub.StreamObserver; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; public class LoadBalanceServer { private static final Logger logger = Logger.getLogger(LoadBalanceServer.class.getName()); - static public final int serverCount = 3; - static public final int startPort = 50051; - private Server[] servers; + static final int[] SERVER_PORTS = {50051, 50052, 50053}; + private List servers; private void start() throws IOException { - servers = new Server[serverCount]; - for (int i = 0; i < serverCount; i++) { - int port = startPort + i; - servers[i] = ServerBuilder.forPort(port) + servers = new ArrayList<>(); + for (int port : SERVER_PORTS) { + servers.add( + ServerBuilder.forPort(port) .addService(new GreeterImpl(port)) .build() - .start(); + .start()); logger.info("Server started, listening on " + port); } Runtime.getRuntime().addShutdownHook(new Thread(() -> { @@ -55,18 +56,14 @@ private void start() throws IOException { } private void stop() throws InterruptedException { - for (int i = 0; i < serverCount; i++) { - if (servers[i] != null) { - servers[i].shutdown().awaitTermination(30, TimeUnit.SECONDS); - } + for (Server server : servers) { + server.shutdown().awaitTermination(30, TimeUnit.SECONDS); } } private void blockUntilShutdown() throws InterruptedException { - for (int i = 0; i < serverCount; i++) { - if (servers[i] != null) { - servers[i].awaitTermination(); - } + for (Server server : servers) { + server.awaitTermination(); } } @@ -86,7 +83,8 @@ public GreeterImpl(int port) { @Override public void sayHello(HelloRequest req, StreamObserver responseObserver) { - HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName() + " from server<" + this.port + ">").build(); + HelloReply reply = HelloReply.newBuilder() + .setMessage("Hello " + req.getName() + " from server<" + this.port + ">").build(); responseObserver.onNext(reply); responseObserver.onCompleted(); } diff --git a/examples/src/main/java/io/grpc/examples/nameresolve/NameResolveClient.java b/examples/src/main/java/io/grpc/examples/nameresolve/NameResolveClient.java index ac6fdd32549..9aaccbe1096 100644 --- a/examples/src/main/java/io/grpc/examples/nameresolve/NameResolveClient.java +++ b/examples/src/main/java/io/grpc/examples/nameresolve/NameResolveClient.java @@ -26,8 +26,7 @@ import java.util.logging.Logger; public class NameResolveClient { - public static final String exampleScheme = "example"; - public static final String exampleServiceName = "lb.example.grpc.io"; + public static final String channelTarget = "example:///lb.example.grpc.io"; private static final Logger logger = Logger.getLogger(NameResolveClient.class.getName()); private final GreeterGrpc.GreeterBlockingStub blockingStub; @@ -56,11 +55,10 @@ public static void main(String[] args) throws Exception { Dial to "example:///resolver.example.grpc.io", use {@link ExampleNameResolver} to create connection "resolver.example.grpc.io" is converted to {@link java.net.URI.path} */ - channel = ManagedChannelBuilder.forTarget( - String.format("%s:///%s", exampleScheme, exampleServiceName)) - .defaultLoadBalancingPolicy("round_robin") - .usePlaintext() - .build(); + channel = ManagedChannelBuilder.forTarget(channelTarget) + .defaultLoadBalancingPolicy("round_robin") + .usePlaintext() + .build(); try { NameResolveClient client = new NameResolveClient(channel); for (int i = 0; i < 5; i++) { From f866c805c2f78271de9f2b61254363d009cee8c6 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 25 Jul 2024 22:42:34 -0700 Subject: [PATCH 025/103] util: SocketAddress.toString() cannot be used for equality Some addresses are equal even though their toString is different (InetSocketAddress ignores the hostname when it has an address). And some addresses are not equal even though their toString might be the same (AnonymousInProcessSocketAddress doesn't override toString()). InetSocketAddress/InetAddress do not cache the toString() result. Thus, even in the worst case that uses a HashSet, this should use less memory than the earlier approach, as no strings are formatted. It probably also significantly improves performance in the reasonably common case when an Endpoint is created just for looking up a key, because the string creation in the constructor isn't then amorized. updateChildrenWithResolvedAddresses(), for example, creates n^2 Endpoint objects for lookups. --- .../io/grpc/util/MultiChildLoadBalancer.java | 33 ++++++----- .../grpc/util/MultiChildLoadBalancerTest.java | 55 ++++++++----------- .../java/io/grpc/util/AbstractTestHelper.java | 16 +++++- 3 files changed, 54 insertions(+), 50 deletions(-) diff --git a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java index c5f774984fe..893dd1e1598 100644 --- a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java +++ b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java @@ -37,10 +37,10 @@ import io.grpc.internal.PickFirstLoadBalancerProvider; import java.net.SocketAddress; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -494,25 +494,27 @@ protected Helper delegate() { /** * Endpoint is an optimization to quickly lookup and compare EquivalentAddressGroup address sets. - * Ignores the attributes, orders the addresses in a deterministic manner and converts each - * address into a string for easy comparison. Also caches the hashcode. - * Is used as a key for ChildLbState for most load balancers (ClusterManagerLB uses a String). + * It ignores the attributes. Is used as a key for ChildLbState for most load balancers + * (ClusterManagerLB uses a String). */ protected static class Endpoint { - final String[] addrs; + final Collection addrs; final int hashCode; public Endpoint(EquivalentAddressGroup eag) { checkNotNull(eag, "eag"); - addrs = new String[eag.getAddresses().size()]; - int i = 0; + if (eag.getAddresses().size() < 10) { + addrs = eag.getAddresses(); + } else { + // This is expected to be very unlikely in practice + addrs = new HashSet<>(eag.getAddresses()); + } + int sum = 0; for (SocketAddress address : eag.getAddresses()) { - addrs[i++] = address.toString(); + sum += address.hashCode(); } - Arrays.sort(addrs); - - hashCode = Arrays.hashCode(addrs); + hashCode = sum; } @Override @@ -525,24 +527,21 @@ public boolean equals(Object other) { if (this == other) { return true; } - if (other == null) { - return false; - } if (!(other instanceof Endpoint)) { return false; } Endpoint o = (Endpoint) other; - if (o.hashCode != hashCode || o.addrs.length != addrs.length) { + if (o.hashCode != hashCode || o.addrs.size() != addrs.size()) { return false; } - return Arrays.equals(o.addrs, this.addrs); + return o.addrs.containsAll(addrs); } @Override public String toString() { - return Arrays.toString(addrs); + return addrs.toString(); } } diff --git a/util/src/test/java/io/grpc/util/MultiChildLoadBalancerTest.java b/util/src/test/java/io/grpc/util/MultiChildLoadBalancerTest.java index df226d5aee8..6bfd6d7a659 100644 --- a/util/src/test/java/io/grpc/util/MultiChildLoadBalancerTest.java +++ b/util/src/test/java/io/grpc/util/MultiChildLoadBalancerTest.java @@ -21,7 +21,6 @@ import static io.grpc.ConnectivityState.READY; import static io.grpc.ConnectivityState.SHUTDOWN; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.AdditionalAnswers.delegatesTo; @@ -34,6 +33,7 @@ import static org.mockito.Mockito.verify; import com.google.common.collect.Lists; +import com.google.common.testing.EqualsTester; import io.grpc.Attributes; import io.grpc.ConnectivityState; import io.grpc.ConnectivityStateInfo; @@ -244,37 +244,28 @@ public void testEndpoint_toString() { @Test public void testEndpoint_equals() { - assertEquals( - createEndpoint(Attributes.EMPTY, "addr1"), - createEndpoint(Attributes.EMPTY, "addr1")); - - assertEquals( - createEndpoint(Attributes.EMPTY, "addr1", "addr2"), - createEndpoint(Attributes.EMPTY, "addr2", "addr1")); - - assertEquals( - createEndpoint(Attributes.EMPTY, "addr1", "addr2"), - createEndpoint(affinity, "addr2", "addr1")); - - assertEquals( - createEndpoint(Attributes.EMPTY, "addr1", "addr2").hashCode(), - createEndpoint(affinity, "addr2", "addr1").hashCode()); - - } - - @Test - public void testEndpoint_notEquals() { - assertNotEquals( - createEndpoint(Attributes.EMPTY, "addr1", "addr2"), - createEndpoint(Attributes.EMPTY, "addr1", "addr3")); - - assertNotEquals( - createEndpoint(Attributes.EMPTY, "addr1"), - createEndpoint(Attributes.EMPTY, "addr1", "addr2")); - - assertNotEquals( - createEndpoint(Attributes.EMPTY, "addr1", "addr2"), - createEndpoint(Attributes.EMPTY, "addr1")); + new EqualsTester() + .addEqualityGroup( + createEndpoint(Attributes.EMPTY, "addr1"), + createEndpoint(Attributes.EMPTY, "addr1")) + .addEqualityGroup( + createEndpoint(Attributes.EMPTY, "addr1", "addr2"), + createEndpoint(Attributes.EMPTY, "addr2", "addr1"), + createEndpoint(affinity, "addr1", "addr2")) + .addEqualityGroup( + createEndpoint(Attributes.EMPTY, "addr1", "addr3")) + .addEqualityGroup( + createEndpoint(Attributes.EMPTY, "addr1", "addr2", "addr3", "addr4", "addr5", "addr6", + "addr7", "addr8", "addr9", "addr10"), + createEndpoint(Attributes.EMPTY, "addr2", "addr1", "addr3", "addr4", "addr5", "addr6", + "addr7", "addr8", "addr9", "addr10")) + .addEqualityGroup( + createEndpoint(Attributes.EMPTY, "addr1", "addr2", "addr3", "addr4", "addr5", "addr6", + "addr7", "addr8", "addr9", "addr11")) + .addEqualityGroup( + createEndpoint(Attributes.EMPTY, "addr1", "addr2", "addr3", "addr4", "addr5", "addr6", + "addr7", "addr8", "addr9", "addr10", "addr11")) + .testEquals(); } private String addressesOnlyString(EquivalentAddressGroup eag) { diff --git a/util/src/testFixtures/java/io/grpc/util/AbstractTestHelper.java b/util/src/testFixtures/java/io/grpc/util/AbstractTestHelper.java index b0239c56703..bdeff9d17c5 100644 --- a/util/src/testFixtures/java/io/grpc/util/AbstractTestHelper.java +++ b/util/src/testFixtures/java/io/grpc/util/AbstractTestHelper.java @@ -276,7 +276,7 @@ public String toString() { } } - public static class FakeSocketAddress extends SocketAddress { + public static final class FakeSocketAddress extends SocketAddress { private static final long serialVersionUID = 0L; final String name; @@ -288,6 +288,20 @@ public static class FakeSocketAddress extends SocketAddress { public String toString() { return "FakeSocketAddress-" + name; } + + @Override + public boolean equals(Object o) { + if (!(o instanceof FakeSocketAddress)) { + return false; + } + FakeSocketAddress that = (FakeSocketAddress) o; + return this.name.equals(that.name); + } + + @Override + public int hashCode() { + return name.hashCode(); + } } } From 2f4f7f0ece1e5e5ef5ccae4281a32f00c5a56ffa Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 25 Jul 2024 18:59:33 -0700 Subject: [PATCH 026/103] util: Delete unused MultiChildLB.ChildLbState.getSubchannels() --- .../src/main/java/io/grpc/util/MultiChildLoadBalancer.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java index 893dd1e1598..f2e2cc617ee 100644 --- a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java +++ b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java @@ -411,13 +411,6 @@ public final SubchannelPicker getCurrentPicker() { return currentPicker; } - protected final Subchannel getSubchannels(PickSubchannelArgs args) { - if (getCurrentPicker() == null) { - return null; - } - return getCurrentPicker().pickSubchannel(args).getSubchannel(); - } - public final ConnectivityState getCurrentState() { return currentState; } From d1dcfb0451a0a19ad391f7e51502f4adec710b4e Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Sat, 27 Jul 2024 11:55:27 -0700 Subject: [PATCH 027/103] xds: Replace WrrHelper with a per-child Helper There's no need to assume which child makes a subchannel based on the subchannel address. --- .../xds/WeightedRoundRobinLoadBalancer.java | 53 ++++++------------- 1 file changed, 16 insertions(+), 37 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java b/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java index 115857d43ff..abcb0941fd9 100644 --- a/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java @@ -17,7 +17,6 @@ package io.grpc.xds; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkElementIndex; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.VisibleForTesting; @@ -40,7 +39,6 @@ import io.grpc.SynchronizationContext; import io.grpc.SynchronizationContext.ScheduledHandle; import io.grpc.services.MetricReport; -import io.grpc.util.ForwardingLoadBalancerHelper; import io.grpc.util.ForwardingSubchannel; import io.grpc.util.MultiChildLoadBalancer; import io.grpc.xds.orca.OrcaOobUtil; @@ -137,12 +135,12 @@ final class WeightedRoundRobinLoadBalancer extends MultiChildLoadBalancer { } public WeightedRoundRobinLoadBalancer(Helper helper, Ticker ticker) { - this(new WrrHelper(OrcaOobUtil.newOrcaReportingHelper(helper)), ticker, new Random()); + this(helper, ticker, new Random()); } - public WeightedRoundRobinLoadBalancer(WrrHelper helper, Ticker ticker, Random random) { - super(helper); - helper.setLoadBalancer(this); + @VisibleForTesting + WeightedRoundRobinLoadBalancer(Helper helper, Ticker ticker, Random random) { + super(OrcaOobUtil.newOrcaReportingHelper(helper)); this.ticker = checkNotNull(ticker, "ticker"); this.infTime = ticker.nanoTime() + Long.MAX_VALUE; this.syncContext = checkNotNull(helper.getSynchronizationContext(), "syncContext"); @@ -152,11 +150,6 @@ public WeightedRoundRobinLoadBalancer(WrrHelper helper, Ticker ticker, Random ra log.log(Level.FINE, "weighted_round_robin LB created"); } - @VisibleForTesting - WeightedRoundRobinLoadBalancer(Helper helper, Ticker ticker, Random random) { - this(new WrrHelper(OrcaOobUtil.newOrcaReportingHelper(helper)), ticker, random); - } - @Override protected ChildLbState createChildLbState(Object key, Object policyConfig, SubchannelPicker initialPicker, ResolvedAddresses unused) { @@ -270,6 +263,11 @@ public WeightedChildLbState(Object key, LoadBalancerProvider policyProvider, Obj super(key, policyProvider, childConfig, initialPicker); } + @Override + protected ChildLbStateHelper createChildHelper() { + return new WrrChildLbStateHelper(); + } + private double getWeight(AtomicInteger staleEndpoints, AtomicInteger notYetUsableEndpoints) { if (config == null) { return 0; @@ -305,6 +303,13 @@ public void removeSubchannel(WrrSubchannel wrrSubchannel) { subchannels.remove(wrrSubchannel); } + final class WrrChildLbStateHelper extends ChildLbStateHelper { + @Override + public Subchannel createSubchannel(CreateSubchannelArgs args) { + return new WrrSubchannel(super.createSubchannel(args), WeightedChildLbState.this); + } + } + final class OrcaReportListener implements OrcaPerRequestReportListener, OrcaOobReportListener { private final float errorUtilizationPenalty; @@ -374,32 +379,6 @@ public void shutdown() { super.shutdown(); } - private static final class WrrHelper extends ForwardingLoadBalancerHelper { - private final Helper delegate; - private WeightedRoundRobinLoadBalancer wrr; - - WrrHelper(Helper helper) { - this.delegate = helper; - } - - void setLoadBalancer(WeightedRoundRobinLoadBalancer lb) { - this.wrr = lb; - } - - @Override - protected Helper delegate() { - return delegate; - } - - @Override - public Subchannel createSubchannel(CreateSubchannelArgs args) { - checkElementIndex(0, args.getAddresses().size(), "Empty address group"); - WeightedChildLbState childLbState = - (WeightedChildLbState) wrr.getChildLbStateEag(args.getAddresses().get(0)); - return wrr.new WrrSubchannel(delegate().createSubchannel(args), childLbState); - } - } - @VisibleForTesting final class WrrSubchannel extends ForwardingSubchannel { private final Subchannel delegate; From 043ba556b89eb7b700c825bb09bed6e7fb038608 Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Mon, 12 Aug 2024 11:16:54 -0700 Subject: [PATCH 028/103] otel tracing: add binary format, grpcTraceBinContextPropagator (#11409) * otel tracing: add binary format, grpcTraceBinContextPropagator * exception handling, use api base64 encoder omit padding remove binary format abstract class in favor of binary marshaller --- .../io/grpc/opentelemetry/BinaryFormat.java | 143 ++++++++ .../GrpcTraceBinContextPropagator.java | 147 ++++++++ .../io/grpc/opentelemetry/MetadataGetter.java | 87 +++++ .../io/grpc/opentelemetry/MetadataSetter.java | 74 +++++ .../GrpcTraceBinContextPropagatorTest.java | 313 ++++++++++++++++++ .../opentelemetry/MetadataGetterTest.java | 96 ++++++ .../opentelemetry/MetadataSetterTest.java | 83 +++++ 7 files changed, 943 insertions(+) create mode 100644 opentelemetry/src/main/java/io/grpc/opentelemetry/BinaryFormat.java create mode 100644 opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcTraceBinContextPropagator.java create mode 100644 opentelemetry/src/main/java/io/grpc/opentelemetry/MetadataGetter.java create mode 100644 opentelemetry/src/main/java/io/grpc/opentelemetry/MetadataSetter.java create mode 100644 opentelemetry/src/test/java/io/grpc/opentelemetry/GrpcTraceBinContextPropagatorTest.java create mode 100644 opentelemetry/src/test/java/io/grpc/opentelemetry/MetadataGetterTest.java create mode 100644 opentelemetry/src/test/java/io/grpc/opentelemetry/MetadataSetterTest.java diff --git a/opentelemetry/src/main/java/io/grpc/opentelemetry/BinaryFormat.java b/opentelemetry/src/main/java/io/grpc/opentelemetry/BinaryFormat.java new file mode 100644 index 00000000000..cdf27875903 --- /dev/null +++ b/opentelemetry/src/main/java/io/grpc/opentelemetry/BinaryFormat.java @@ -0,0 +1,143 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.opentelemetry; + + +import static com.google.common.base.Preconditions.checkNotNull; + +import io.grpc.Metadata; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.SpanId; +import io.opentelemetry.api.trace.TraceFlags; +import io.opentelemetry.api.trace.TraceId; +import io.opentelemetry.api.trace.TraceState; +import java.util.Arrays; + +/** + * Binary encoded {@link SpanContext} for context propagation. This is adapted from OpenCensus + * binary format. + * + *

BinaryFormat format: + * + *

    + *
  • Binary value: <version_id><version_format> + *
  • version_id: 1-byte representing the version id. + *
  • For version_id = 0: + *
      + *
    • version_format: <field><field> + *
    • field_format: <field_id><field_format> + *
    • Fields: + *
        + *
      • TraceId: (field_id = 0, len = 16, default = "0000000000000000") - + * 16-byte array representing the trace_id. + *
      • SpanId: (field_id = 1, len = 8, default = "00000000") - 8-byte array + * representing the span_id. + *
      • TraceFlags: (field_id = 2, len = 1, default = "0") - 1-byte array + * representing the trace_flags. + *
      + *
    • Fields MUST be encoded using the field id order (smaller to higher). + *
    • Valid value example: + *
        + *
      • {0, 0, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 1, 97, + * 98, 99, 100, 101, 102, 103, 104, 2, 1} + *
      • version_id = 0; + *
      • trace_id = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79} + *
      • span_id = {97, 98, 99, 100, 101, 102, 103, 104}; + *
      • trace_flags = {1}; + *
      + *
    + *
+ */ +final class BinaryFormat implements Metadata.BinaryMarshaller { + private static final byte VERSION_ID = 0; + private static final int VERSION_ID_OFFSET = 0; + private static final byte ID_SIZE = 1; + private static final byte TRACE_ID_FIELD_ID = 0; + + private static final int TRACE_ID_FIELD_ID_OFFSET = VERSION_ID_OFFSET + ID_SIZE; + private static final int TRACE_ID_OFFSET = TRACE_ID_FIELD_ID_OFFSET + ID_SIZE; + private static final int TRACE_ID_SIZE = TraceId.getLength() / 2; + + private static final byte SPAN_ID_FIELD_ID = 1; + private static final int SPAN_ID_FIELD_ID_OFFSET = TRACE_ID_OFFSET + TRACE_ID_SIZE; + private static final int SPAN_ID_OFFSET = SPAN_ID_FIELD_ID_OFFSET + ID_SIZE; + private static final int SPAN_ID_SIZE = SpanId.getLength() / 2; + + private static final byte TRACE_FLAG_FIELD_ID = 2; + private static final int TRACE_FLAG_FIELD_ID_OFFSET = SPAN_ID_OFFSET + SPAN_ID_SIZE; + private static final int TRACE_FLAG_OFFSET = TRACE_FLAG_FIELD_ID_OFFSET + ID_SIZE; + private static final int REQUIRED_FORMAT_LENGTH = 3 * ID_SIZE + TRACE_ID_SIZE + SPAN_ID_SIZE; + private static final int TRACE_FLAG_SIZE = TraceFlags.getLength() / 2; + private static final int ALL_FORMAT_LENGTH = REQUIRED_FORMAT_LENGTH + ID_SIZE + TRACE_FLAG_SIZE; + + private static final BinaryFormat INSTANCE = new BinaryFormat(); + + public static BinaryFormat getInstance() { + return INSTANCE; + } + + @Override + public byte[] toBytes(SpanContext spanContext) { + checkNotNull(spanContext, "spanContext"); + byte[] bytes = new byte[ALL_FORMAT_LENGTH]; + bytes[VERSION_ID_OFFSET] = VERSION_ID; + bytes[TRACE_ID_FIELD_ID_OFFSET] = TRACE_ID_FIELD_ID; + System.arraycopy(spanContext.getTraceIdBytes(), 0, bytes, TRACE_ID_OFFSET, TRACE_ID_SIZE); + bytes[SPAN_ID_FIELD_ID_OFFSET] = SPAN_ID_FIELD_ID; + System.arraycopy(spanContext.getSpanIdBytes(), 0, bytes, SPAN_ID_OFFSET, SPAN_ID_SIZE); + bytes[TRACE_FLAG_FIELD_ID_OFFSET] = TRACE_FLAG_FIELD_ID; + bytes[TRACE_FLAG_OFFSET] = spanContext.getTraceFlags().asByte(); + return bytes; + } + + + @Override + public SpanContext parseBytes(byte[] serialized) { + checkNotNull(serialized, "bytes"); + if (serialized.length == 0 || serialized[0] != VERSION_ID) { + throw new IllegalArgumentException("Unsupported version."); + } + if (serialized.length < REQUIRED_FORMAT_LENGTH) { + throw new IllegalArgumentException("Invalid input: truncated"); + } + String traceId; + String spanId; + TraceFlags traceFlags = TraceFlags.getDefault(); + int pos = 1; + if (serialized[pos] == TRACE_ID_FIELD_ID) { + traceId = TraceId.fromBytes( + Arrays.copyOfRange(serialized, pos + ID_SIZE, pos + ID_SIZE + TRACE_ID_SIZE)); + pos += ID_SIZE + TRACE_ID_SIZE; + } else { + throw new IllegalArgumentException("Invalid input: expected trace ID at offset " + pos); + } + if (serialized[pos] == SPAN_ID_FIELD_ID) { + spanId = SpanId.fromBytes( + Arrays.copyOfRange(serialized, pos + ID_SIZE, pos + ID_SIZE + SPAN_ID_SIZE)); + pos += ID_SIZE + SPAN_ID_SIZE; + } else { + throw new IllegalArgumentException("Invalid input: expected span ID at offset " + pos); + } + if (serialized.length > pos && serialized[pos] == TRACE_FLAG_FIELD_ID) { + if (serialized.length < ALL_FORMAT_LENGTH) { + throw new IllegalArgumentException("Invalid input: truncated"); + } + traceFlags = TraceFlags.fromByte(serialized[pos + ID_SIZE]); + } + return SpanContext.create(traceId, spanId, traceFlags, TraceState.getDefault()); + } +} diff --git a/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcTraceBinContextPropagator.java b/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcTraceBinContextPropagator.java new file mode 100644 index 00000000000..4825b203529 --- /dev/null +++ b/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcTraceBinContextPropagator.java @@ -0,0 +1,147 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.opentelemetry; + + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.grpc.InternalMetadata.BASE64_ENCODING_OMIT_PADDING; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.io.BaseEncoding; +import io.grpc.ExperimentalApi; +import io.grpc.Metadata; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.context.propagation.TextMapSetter; +import java.util.Collection; +import java.util.Collections; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.Nullable; + +/** + * A {@link TextMapPropagator} for transmitting "grpc-trace-bin" span context. + * + *

This propagator can transmit the "grpc-trace-bin" context in either binary or Base64-encoded + * text format, depending on the capabilities of the provided {@link TextMapGetter} and + * {@link TextMapSetter}. + * + *

If the {@code TextMapGetter} and {@code TextMapSetter} only support text format, Base64 + * encoding and decoding will be used when communicating with the carrier API. But gRPC uses + * it with gRPC's metadata-based getter/setter, and the propagator can directly transmit the binary + * header, avoiding the need for Base64 encoding. + */ + +@ExperimentalApi("https://github.com/grpc/grpc-java/issues/11400") +public final class GrpcTraceBinContextPropagator implements TextMapPropagator { + private static final Logger log = Logger.getLogger(GrpcTraceBinContextPropagator.class.getName()); + public static final String GRPC_TRACE_BIN_HEADER = "grpc-trace-bin"; + private final Metadata.BinaryMarshaller binaryFormat; + private static final GrpcTraceBinContextPropagator INSTANCE = + new GrpcTraceBinContextPropagator(BinaryFormat.getInstance()); + + public static GrpcTraceBinContextPropagator defaultInstance() { + return INSTANCE; + } + + @VisibleForTesting + GrpcTraceBinContextPropagator(Metadata.BinaryMarshaller binaryFormat) { + this.binaryFormat = checkNotNull(binaryFormat, "binaryFormat"); + } + + @Override + public Collection fields() { + return Collections.singleton(GRPC_TRACE_BIN_HEADER); + } + + @Override + public void inject(Context context, @Nullable C carrier, TextMapSetter setter) { + if (context == null || setter == null) { + return; + } + SpanContext spanContext = Span.fromContext(context).getSpanContext(); + if (!spanContext.isValid()) { + return; + } + try { + byte[] b = binaryFormat.toBytes(spanContext); + if (setter instanceof MetadataSetter) { + ((MetadataSetter) setter).set((Metadata) carrier, GRPC_TRACE_BIN_HEADER, b); + } else { + setter.set(carrier, GRPC_TRACE_BIN_HEADER, BASE64_ENCODING_OMIT_PADDING.encode(b)); + } + } catch (Exception e) { + log.log(Level.FINE, "Set grpc-trace-bin spanContext failed", e); + } + } + + @Override + public Context extract(Context context, @Nullable C carrier, TextMapGetter getter) { + if (context == null) { + return Context.root(); + } + if (getter == null) { + return context; + } + byte[] b; + if (getter instanceof MetadataGetter) { + try { + b = ((MetadataGetter) getter).getBinary((Metadata) carrier, GRPC_TRACE_BIN_HEADER); + if (b == null) { + log.log(Level.FINE, "No grpc-trace-bin present in carrier"); + return context; + } + } catch (Exception e) { + log.log(Level.FINE, "Get 'grpc-trace-bin' from MetadataGetter failed", e); + return context; + } + } else { + String value; + try { + value = getter.get(carrier, GRPC_TRACE_BIN_HEADER); + if (value == null) { + log.log(Level.FINE, "No grpc-trace-bin present in carrier"); + return context; + } + } catch (Exception e) { + log.log(Level.FINE, "Get 'grpc-trace-bin' from getter failed", e); + return context; + } + try { + b = BaseEncoding.base64().decode(value); + } catch (Exception e) { + log.log(Level.FINE, "Base64-decode spanContext bytes failed", e); + return context; + } + } + + SpanContext spanContext; + try { + spanContext = binaryFormat.parseBytes(b); + } catch (Exception e) { + log.log(Level.FINE, "Failed to parse tracing header", e); + return context; + } + if (!spanContext.isValid()) { + return context; + } + return context.with(Span.wrap(spanContext)); + } +} diff --git a/opentelemetry/src/main/java/io/grpc/opentelemetry/MetadataGetter.java b/opentelemetry/src/main/java/io/grpc/opentelemetry/MetadataGetter.java new file mode 100644 index 00000000000..f49c029f2fb --- /dev/null +++ b/opentelemetry/src/main/java/io/grpc/opentelemetry/MetadataGetter.java @@ -0,0 +1,87 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.opentelemetry; + + +import static io.grpc.InternalMetadata.BASE64_ENCODING_OMIT_PADDING; + +import io.grpc.Metadata; +import io.opentelemetry.context.propagation.TextMapGetter; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.Nullable; + +/** + * A TextMapGetter that reads value from gRPC {@link Metadata}. Supports both text and binary + * headers. Supporting binary header is an optimization path for GrpcTraceBinContextPropagator + * to work around the lack of binary propagator API and thus avoid + * base64 (de)encoding when passing data between propagator API interfaces. + */ +final class MetadataGetter implements TextMapGetter { + private static final Logger logger = Logger.getLogger(MetadataGetter.class.getName()); + private static final MetadataGetter INSTANCE = new MetadataGetter(); + + public static MetadataGetter getInstance() { + return INSTANCE; + } + + @Override + public Iterable keys(Metadata carrier) { + return carrier.keys(); + } + + @Nullable + @Override + public String get(@Nullable Metadata carrier, String key) { + if (carrier == null) { + logger.log(Level.FINE, "Carrier is null, getting no data"); + return null; + } + try { + if (key.equals("grpc-trace-bin")) { + byte[] value = carrier.get(Metadata.Key.of(key, Metadata.BINARY_BYTE_MARSHALLER)); + if (value == null) { + return null; + } + return BASE64_ENCODING_OMIT_PADDING.encode(value); + } else { + return carrier.get(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER)); + } + } catch (Exception e) { + logger.log(Level.FINE, String.format("Failed to get metadata key %s", key), e); + return null; + } + } + + @Nullable + public byte[] getBinary(@Nullable Metadata carrier, String key) { + if (carrier == null) { + logger.log(Level.FINE, "Carrier is null, getting no data"); + return null; + } + if (!key.equals("grpc-trace-bin")) { + logger.log(Level.FINE, "Only support 'grpc-trace-bin' binary header. Get no data"); + return null; + } + try { + return carrier.get(Metadata.Key.of(key, Metadata.BINARY_BYTE_MARSHALLER)); + } catch (Exception e) { + logger.log(Level.FINE, String.format("Failed to get metadata key %s", key), e); + return null; + } + } +} diff --git a/opentelemetry/src/main/java/io/grpc/opentelemetry/MetadataSetter.java b/opentelemetry/src/main/java/io/grpc/opentelemetry/MetadataSetter.java new file mode 100644 index 00000000000..5892c7accfe --- /dev/null +++ b/opentelemetry/src/main/java/io/grpc/opentelemetry/MetadataSetter.java @@ -0,0 +1,74 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.opentelemetry; + + +import com.google.common.io.BaseEncoding; +import io.grpc.Metadata; +import io.opentelemetry.context.propagation.TextMapSetter; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.Nullable; + +/** + * A {@link TextMapSetter} that sets value to gRPC {@link Metadata}. Supports both text and binary + * headers. Supporting binary header is an optimization path for GrpcTraceBinContextPropagator + * to work around the lack of binary propagator API and thus avoid + * base64 (de)encoding when passing data between propagator API interfaces. + */ +final class MetadataSetter implements TextMapSetter { + private static final Logger logger = Logger.getLogger(MetadataSetter.class.getName()); + private static final MetadataSetter INSTANCE = new MetadataSetter(); + + public static MetadataSetter getInstance() { + return INSTANCE; + } + + @Override + public void set(@Nullable Metadata carrier, String key, String value) { + if (carrier == null) { + logger.log(Level.FINE, "Carrier is null, setting no data"); + return; + } + try { + if (key.equals("grpc-trace-bin")) { + carrier.put(Metadata.Key.of(key, Metadata.BINARY_BYTE_MARSHALLER), + BaseEncoding.base64().decode(value)); + } else { + carrier.put(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER), value); + } + } catch (Exception e) { + logger.log(Level.INFO, String.format("Failed to set metadata, key=%s", key), e); + } + } + + void set(@Nullable Metadata carrier, String key, byte[] value) { + if (carrier == null) { + logger.log(Level.FINE, "Carrier is null, setting no data"); + return; + } + if (!key.equals("grpc-trace-bin")) { + logger.log(Level.INFO, "Only support 'grpc-trace-bin' binary header. Set no data"); + return; + } + try { + carrier.put(Metadata.Key.of(key, Metadata.BINARY_BYTE_MARSHALLER), value); + } catch (Exception e) { + logger.log(Level.INFO, String.format("Failed to set metadata key=%s", key), e); + } + } +} diff --git a/opentelemetry/src/test/java/io/grpc/opentelemetry/GrpcTraceBinContextPropagatorTest.java b/opentelemetry/src/test/java/io/grpc/opentelemetry/GrpcTraceBinContextPropagatorTest.java new file mode 100644 index 00000000000..f85b8067c26 --- /dev/null +++ b/opentelemetry/src/test/java/io/grpc/opentelemetry/GrpcTraceBinContextPropagatorTest.java @@ -0,0 +1,313 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.opentelemetry; + +import static com.google.common.truth.Truth.assertThat; +import static io.grpc.InternalMetadata.BASE64_ENCODING_OMIT_PADDING; +import static org.junit.Assert.assertTrue; + +import com.google.common.collect.ImmutableMap; +import io.grpc.Metadata; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.TraceFlags; +import io.opentelemetry.api.trace.TraceState; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.context.propagation.TextMapSetter; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nullable; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class GrpcTraceBinContextPropagatorTest { + private static final String TRACE_ID_BASE16 = "e384981d65129fa3e384981d65129fa3"; + private static final String SPAN_ID_BASE16 = "e384981d65129fa3"; + private static final String TRACE_HEADER_SAMPLED = + "0000" + TRACE_ID_BASE16 + "01" + SPAN_ID_BASE16 + "0201"; + private static final String TRACE_HEADER_NOT_SAMPLED = + "0000" + TRACE_ID_BASE16 + "01" + SPAN_ID_BASE16 + "0200"; + private final String goldenHeaderEncodedSampled = encode(TRACE_HEADER_SAMPLED); + private final String goldenHeaderEncodedNotSampled = encode(TRACE_HEADER_NOT_SAMPLED); + private static final TextMapSetter> setter = Map::put; + private static final TextMapGetter> getter = + new TextMapGetter>() { + @Override + public Iterable keys(Map carrier) { + return carrier.keySet(); + } + + @Nullable + @Override + public String get(Map carrier, String key) { + return carrier.get(key); + } + }; + private final GrpcTraceBinContextPropagator grpcTraceBinContextPropagator = + GrpcTraceBinContextPropagator.defaultInstance(); + + private static Context withSpanContext(SpanContext spanContext, Context context) { + return context.with(Span.wrap(spanContext)); + } + + private static SpanContext getSpanContext(Context context) { + return Span.fromContext(context).getSpanContext(); + } + + @Test + public void inject_map_Nothing() { + Map carrier = new HashMap<>(); + grpcTraceBinContextPropagator.inject(Context.current(), carrier, setter); + assertThat(carrier).hasSize(0); + } + + @Test + public void inject_map_invalidSpan() { + Map carrier = new HashMap<>(); + Context context = withSpanContext(SpanContext.getInvalid(), Context.current()); + grpcTraceBinContextPropagator.inject(context, carrier, setter); + assertThat(carrier).isEmpty(); + } + + @Test + public void inject_map_nullCarrier() { + Map carrier = new HashMap<>(); + Context context = + withSpanContext( + SpanContext.create( + TRACE_ID_BASE16, SPAN_ID_BASE16, TraceFlags.getSampled(), TraceState.getDefault()), + Context.current()); + grpcTraceBinContextPropagator.inject(context, null, + (TextMapSetter>) (ignored, key, value) -> carrier.put(key, value)); + assertThat(carrier) + .containsExactly( + GrpcTraceBinContextPropagator.GRPC_TRACE_BIN_HEADER, goldenHeaderEncodedSampled); + } + + @Test + public void inject_map_nullContext() { + Map carrier = new HashMap<>(); + grpcTraceBinContextPropagator.inject(null, carrier, setter); + assertThat(carrier).isEmpty(); + } + + @Test + public void inject_map_invalidBinaryFormat() { + GrpcTraceBinContextPropagator propagator = new GrpcTraceBinContextPropagator( + new Metadata.BinaryMarshaller() { + @Override + public byte[] toBytes(SpanContext value) { + throw new IllegalArgumentException("failed to byte"); + } + + @Override + public SpanContext parseBytes(byte[] serialized) { + return null; + } + }); + Map carrier = new HashMap<>(); + Context context = + withSpanContext( + SpanContext.create( + TRACE_ID_BASE16, SPAN_ID_BASE16, TraceFlags.getSampled(), TraceState.getDefault()), + Context.current()); + propagator.inject(context, carrier, setter); + assertThat(carrier).hasSize(0); + } + + @Test + public void inject_map_SampledContext() { + verify_inject_map(TraceFlags.getSampled(), goldenHeaderEncodedSampled); + } + + @Test + public void inject_map_NotSampledContext() { + verify_inject_map(TraceFlags.getDefault(), goldenHeaderEncodedNotSampled); + } + + private void verify_inject_map(TraceFlags traceFlags, String goldenHeader) { + Map carrier = new HashMap<>(); + Context context = + withSpanContext( + SpanContext.create( + TRACE_ID_BASE16, SPAN_ID_BASE16, traceFlags, TraceState.getDefault()), + Context.current()); + grpcTraceBinContextPropagator.inject(context, carrier, setter); + assertThat(carrier) + .containsExactly( + GrpcTraceBinContextPropagator.GRPC_TRACE_BIN_HEADER, goldenHeader); + } + + @Test + public void extract_map_nothing() { + Map carrier = new HashMap<>(); + assertThat(grpcTraceBinContextPropagator.extract(Context.current(), carrier, getter)) + .isSameInstanceAs(Context.current()); + } + + @Test + public void extract_map_SampledContext() { + verify_extract_map(TraceFlags.getSampled(), goldenHeaderEncodedSampled); + } + + @Test + public void extract_map_NotSampledContext() { + verify_extract_map(TraceFlags.getDefault(), goldenHeaderEncodedNotSampled); + } + + private void verify_extract_map(TraceFlags traceFlags, String goldenHeader) { + Map carrier = ImmutableMap.of( + GrpcTraceBinContextPropagator.GRPC_TRACE_BIN_HEADER, goldenHeader); + Context result = grpcTraceBinContextPropagator.extract(Context.current(), carrier, getter); + assertThat(getSpanContext(result)).isEqualTo(SpanContext.create( + TRACE_ID_BASE16, SPAN_ID_BASE16, traceFlags, TraceState.getDefault())); + } + + @Test + public void inject_metadata_Nothing() { + Metadata carrier = new Metadata(); + grpcTraceBinContextPropagator.inject(Context.current(), carrier, MetadataSetter.getInstance()); + assertThat(carrier.keys()).isEmpty(); + } + + @Test + public void inject_metadata_nullCarrier() { + Context context = + withSpanContext( + SpanContext.create( + TRACE_ID_BASE16, SPAN_ID_BASE16, TraceFlags.getSampled(), TraceState.getDefault()), + Context.current()); + grpcTraceBinContextPropagator.inject(context, null, MetadataSetter.getInstance()); + } + + @Test + public void inject_metadata_invalidSpan() { + Metadata carrier = new Metadata(); + Context context = withSpanContext(SpanContext.getInvalid(), Context.current()); + grpcTraceBinContextPropagator.inject(context, carrier, MetadataSetter.getInstance()); + assertThat(carrier.keys()).isEmpty(); + } + + @Test + public void inject_metadata_SampledContext() { + verify_inject_metadata(TraceFlags.getSampled(), hexStringToByteArray(TRACE_HEADER_SAMPLED)); + } + + @Test + public void inject_metadataSetter_NotSampledContext() { + verify_inject_metadata(TraceFlags.getDefault(), hexStringToByteArray(TRACE_HEADER_NOT_SAMPLED)); + } + + private void verify_inject_metadata(TraceFlags traceFlags, byte[] bytes) { + Metadata metadata = new Metadata(); + Context context = + withSpanContext( + SpanContext.create( + TRACE_ID_BASE16, SPAN_ID_BASE16, traceFlags, TraceState.getDefault()), + Context.current()); + grpcTraceBinContextPropagator.inject(context, metadata, MetadataSetter.getInstance()); + byte[] injected = metadata.get(Metadata.Key.of( + GrpcTraceBinContextPropagator.GRPC_TRACE_BIN_HEADER, Metadata.BINARY_BYTE_MARSHALLER)); + assertTrue(Arrays.equals(injected, bytes)); + } + + @Test + public void extract_metadata_nothing() { + assertThat(grpcTraceBinContextPropagator.extract( + Context.current(), new Metadata(), MetadataGetter.getInstance())) + .isSameInstanceAs(Context.current()); + } + + @Test + public void extract_metadata_nullCarrier() { + assertThat(grpcTraceBinContextPropagator.extract( + Context.current(), null, MetadataGetter.getInstance())) + .isSameInstanceAs(Context.current()); + } + + @Test + public void extract_metadata_SampledContext() { + verify_extract_metadata(TraceFlags.getSampled(), TRACE_HEADER_SAMPLED); + } + + @Test + public void extract_metadataGetter_NotSampledContext() { + verify_extract_metadata(TraceFlags.getDefault(), TRACE_HEADER_NOT_SAMPLED); + } + + private void verify_extract_metadata(TraceFlags traceFlags, String hex) { + Metadata carrier = new Metadata(); + carrier.put(Metadata.Key.of( + GrpcTraceBinContextPropagator.GRPC_TRACE_BIN_HEADER, Metadata.BINARY_BYTE_MARSHALLER), + hexStringToByteArray(hex)); + Context result = grpcTraceBinContextPropagator.extract(Context.current(), carrier, + MetadataGetter.getInstance()); + assertThat(getSpanContext(result)).isEqualTo(SpanContext.create( + TRACE_ID_BASE16, SPAN_ID_BASE16, traceFlags, TraceState.getDefault())); + } + + @Test + public void extract_metadata_invalidBinaryFormat() { + GrpcTraceBinContextPropagator propagator = new GrpcTraceBinContextPropagator( + new Metadata.BinaryMarshaller() { + @Override + public byte[] toBytes(SpanContext value) { + return new byte[0]; + } + + @Override + public SpanContext parseBytes(byte[] serialized) { + throw new IllegalArgumentException("failed to byte"); + } + }); + Metadata carrier = new Metadata(); + carrier.put(Metadata.Key.of( + GrpcTraceBinContextPropagator.GRPC_TRACE_BIN_HEADER, Metadata.BINARY_BYTE_MARSHALLER), + hexStringToByteArray(TRACE_HEADER_SAMPLED)); + assertThat(propagator.extract(Context.current(), carrier, MetadataGetter.getInstance())) + .isSameInstanceAs(Context.current()); + } + + @Test + public void extract_metadata_invalidBinaryFormatVersion() { + Metadata carrier = new Metadata(); + carrier.put(Metadata.Key.of( + GrpcTraceBinContextPropagator.GRPC_TRACE_BIN_HEADER, Metadata.BINARY_BYTE_MARSHALLER), + hexStringToByteArray("0100" + TRACE_ID_BASE16 + "01" + SPAN_ID_BASE16 + "0201")); + assertThat(grpcTraceBinContextPropagator.extract( + Context.current(), carrier, MetadataGetter.getInstance())) + .isSameInstanceAs(Context.current()); + } + + private static String encode(String hex) { + return BASE64_ENCODING_OMIT_PADDING.encode(hexStringToByteArray(hex)); + } + + private static byte[] hexStringToByteArray(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i + 1), 16)); + } + return data; + } +} diff --git a/opentelemetry/src/test/java/io/grpc/opentelemetry/MetadataGetterTest.java b/opentelemetry/src/test/java/io/grpc/opentelemetry/MetadataGetterTest.java new file mode 100644 index 00000000000..5934240e5c2 --- /dev/null +++ b/opentelemetry/src/test/java/io/grpc/opentelemetry/MetadataGetterTest.java @@ -0,0 +1,96 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.opentelemetry; + +import static io.grpc.InternalMetadata.BASE64_ENCODING_OMIT_PADDING; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import io.grpc.Metadata; +import java.nio.charset.Charset; +import java.util.Iterator; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class MetadataGetterTest { + private final MetadataGetter metadataGetter = MetadataGetter.getInstance(); + + @Test + public void getBinaryGrpcTraceBin() { + Metadata metadata = new Metadata(); + byte[] b = "generated".getBytes(Charset.defaultCharset()); + Metadata.Key grpc_trace_bin_key = + Metadata.Key.of("grpc-trace-bin", Metadata.BINARY_BYTE_MARSHALLER); + metadata.put(grpc_trace_bin_key, b); + assertArrayEquals(b, metadataGetter.getBinary(metadata, "grpc-trace-bin")); + } + + @Test + public void getBinaryEmptyMetadata() { + assertNull(metadataGetter.getBinary(new Metadata(), "grpc-trace-bin")); + } + + @Test + public void getBinaryNotGrpcTraceBin() { + Metadata metadata = new Metadata(); + byte[] b = "generated".getBytes(Charset.defaultCharset()); + Metadata.Key grpc_trace_bin_key = + Metadata.Key.of("another-bin", Metadata.BINARY_BYTE_MARSHALLER); + metadata.put(grpc_trace_bin_key, b); + assertNull(metadataGetter.getBinary(metadata, "another-bin")); + } + + @Test + public void getTextEmptyMetadata() { + assertNull(metadataGetter.get(new Metadata(), "a-key")); + } + + @Test + public void getTextBinHeader() { + assertNull(metadataGetter.get(new Metadata(), "a-key-bin")); + } + + @Test + public void getTestGrpcTraceBin() { + Metadata metadata = new Metadata(); + byte[] b = "generated".getBytes(Charset.defaultCharset()); + Metadata.Key grpc_trace_bin_key = + Metadata.Key.of("grpc-trace-bin", Metadata.BINARY_BYTE_MARSHALLER); + metadata.put(grpc_trace_bin_key, b); + assertEquals(BASE64_ENCODING_OMIT_PADDING.encode(b), + metadataGetter.get(metadata, "grpc-trace-bin")); + } + + @Test + public void getText() { + Metadata metadata = new Metadata(); + Metadata.Key other_key = + Metadata.Key.of("other", Metadata.ASCII_STRING_MARSHALLER); + metadata.put(other_key, "header-value"); + assertEquals("header-value", metadataGetter.get(metadata, "other")); + + Iterator iterator = metadataGetter.keys(metadata).iterator(); + assertTrue(iterator.hasNext()); + assertEquals("other", iterator.next()); + assertFalse(iterator.hasNext()); + } +} diff --git a/opentelemetry/src/test/java/io/grpc/opentelemetry/MetadataSetterTest.java b/opentelemetry/src/test/java/io/grpc/opentelemetry/MetadataSetterTest.java new file mode 100644 index 00000000000..fcd85480bb9 --- /dev/null +++ b/opentelemetry/src/test/java/io/grpc/opentelemetry/MetadataSetterTest.java @@ -0,0 +1,83 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.opentelemetry; + +import static io.grpc.InternalMetadata.BASE64_ENCODING_OMIT_PADDING; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import io.grpc.Metadata; +import java.nio.charset.Charset; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class MetadataSetterTest { + private final MetadataSetter metadataSetter = MetadataSetter.getInstance(); + + @Test + public void setGrpcTraceBin() { + Metadata metadata = new Metadata(); + byte[] b = "generated".getBytes(Charset.defaultCharset()); + Metadata.Key grpc_trace_bin_key = + Metadata.Key.of("grpc-trace-bin", Metadata.BINARY_BYTE_MARSHALLER); + metadataSetter.set(metadata, "grpc-trace-bin", b); + assertArrayEquals(b, metadata.get(grpc_trace_bin_key)); + } + + @Test + public void setOtherBinaryKey() { + Metadata metadata = new Metadata(); + byte[] b = "generated".getBytes(Charset.defaultCharset()); + Metadata.Key other_key = + Metadata.Key.of("for-test-bin", Metadata.BINARY_BYTE_MARSHALLER); + metadataSetter.set(metadata, other_key.name(), b); + assertNull(metadata.get(other_key)); + } + + @Test + public void setText() { + Metadata metadata = new Metadata(); + String v = "generated"; + Metadata.Key textKey = + Metadata.Key.of("text-key", Metadata.ASCII_STRING_MARSHALLER); + metadataSetter.set(metadata, textKey.name(), v); + assertEquals(metadata.get(textKey), v); + } + + @Test + public void setTextBin() { + Metadata metadata = new Metadata(); + Metadata.Key other_key = + Metadata.Key.of("for-test-bin", Metadata.BINARY_BYTE_MARSHALLER); + metadataSetter.set(metadata, other_key.name(), "generated"); + assertNull(metadata.get(other_key)); + } + + @Test + public void setTextGrpcTraceBin() { + Metadata metadata = new Metadata(); + byte[] b = "generated".getBytes(Charset.defaultCharset()); + metadataSetter.set(metadata, "grpc-trace-bin", BASE64_ENCODING_OMIT_PADDING.encode(b)); + + Metadata.Key grpc_trace_bin_key = + Metadata.Key.of("grpc-trace-bin", Metadata.BINARY_BYTE_MARSHALLER); + assertArrayEquals(metadata.get(grpc_trace_bin_key), b); + } +} From 0d2ad890164dd88a5e8819354e3fa6243211002c Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 8 Aug 2024 07:35:01 -0700 Subject: [PATCH 029/103] xds: Remove useless ExperimentalApi for WRR A package-private class isn't visible and `@Internal` is stronger than experimental. The only way users should use WRR is via the weight_round_robin string, and that's already not suffixed with _experimental. Closes #9885 --- .../main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java | 2 -- .../io/grpc/xds/WeightedRoundRobinLoadBalancerProvider.java | 2 -- 2 files changed, 4 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java b/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java index abcb0941fd9..f45bb571a36 100644 --- a/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java @@ -29,7 +29,6 @@ import io.grpc.Deadline.Ticker; import io.grpc.DoubleHistogramMetricInstrument; import io.grpc.EquivalentAddressGroup; -import io.grpc.ExperimentalApi; import io.grpc.LoadBalancer; import io.grpc.LoadBalancerProvider; import io.grpc.LongCounterMetricInstrument; @@ -87,7 +86,6 @@ * * See related documentation: https://cloud.google.com/service-mesh/legacy/load-balancing-apis/proxyless-configure-advanced-traffic-management#custom-lb-config */ -@ExperimentalApi("https://github.com/grpc/grpc-java/issues/9885") final class WeightedRoundRobinLoadBalancer extends MultiChildLoadBalancer { private static final LongCounterMetricInstrument RR_FALLBACK_COUNTER; diff --git a/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancerProvider.java b/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancerProvider.java index 161e7c4ed0c..433ea34b857 100644 --- a/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancerProvider.java +++ b/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancerProvider.java @@ -18,7 +18,6 @@ import com.google.common.annotations.VisibleForTesting; import io.grpc.Deadline; -import io.grpc.ExperimentalApi; import io.grpc.Internal; import io.grpc.LoadBalancer; import io.grpc.LoadBalancer.Helper; @@ -32,7 +31,6 @@ /** * Provides a {@link WeightedRoundRobinLoadBalancer}. * */ -@ExperimentalApi("https://github.com/grpc/grpc-java/issues/9885") @Internal public final class WeightedRoundRobinLoadBalancerProvider extends LoadBalancerProvider { From 0d47f5bd1baff87d412223c4ac22ea061eafb506 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Mon, 12 Aug 2024 11:23:37 -0700 Subject: [PATCH 030/103] xds: WRRPicker must not access unsynchronized data in ChildLbState There was no point to using subchannels as keys to subchannelToReportListenerMap, as the listener is per-child. That meant the keys would be guaranteed to be known ahead-of-time and the unsynchronized getOrCreateOrcaListener() during picking was unnecessary. The picker still stores ChildLbStates to make sure that updating weights uses the correct children, but the picker itself no longer references ChildLbStates except in the constructor. That means weight calculation is moved into the LB policy, as child.getWeight() is unsynchronized, and the picker no longer needs a reference to helper. --- .../xds/WeightedRoundRobinLoadBalancer.java | 132 +++++++++--------- .../WeightedRoundRobinLoadBalancerTest.java | 2 +- 2 files changed, 67 insertions(+), 67 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java b/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java index f45bb571a36..e4502da8740 100644 --- a/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java @@ -44,11 +44,10 @@ import io.grpc.xds.orca.OrcaOobUtil.OrcaOobReportListener; import io.grpc.xds.orca.OrcaPerRequestUtil; import io.grpc.xds.orca.OrcaPerRequestUtil.OrcaPerRequestReportListener; +import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Random; import java.util.Set; import java.util.concurrent.ScheduledExecutorService; @@ -233,9 +232,44 @@ protected void updateOverallBalancingState() { } private SubchannelPicker createReadyPicker(Collection activeList) { - return new WeightedRoundRobinPicker(ImmutableList.copyOf(activeList), - config.enableOobLoadReport, config.errorUtilizationPenalty, sequence, getHelper(), - locality); + WeightedRoundRobinPicker picker = new WeightedRoundRobinPicker(ImmutableList.copyOf(activeList), + config.enableOobLoadReport, config.errorUtilizationPenalty, sequence); + updateWeight(picker); + return picker; + } + + private void updateWeight(WeightedRoundRobinPicker picker) { + Helper helper = getHelper(); + float[] newWeights = new float[picker.children.size()]; + AtomicInteger staleEndpoints = new AtomicInteger(); + AtomicInteger notYetUsableEndpoints = new AtomicInteger(); + for (int i = 0; i < picker.children.size(); i++) { + double newWeight = ((WeightedChildLbState) picker.children.get(i)).getWeight(staleEndpoints, + notYetUsableEndpoints); + helper.getMetricRecorder() + .recordDoubleHistogram(ENDPOINT_WEIGHTS_HISTOGRAM, newWeight, + ImmutableList.of(helper.getChannelTarget()), + ImmutableList.of(locality)); + newWeights[i] = newWeight > 0 ? (float) newWeight : 0.0f; + } + + if (staleEndpoints.get() > 0) { + helper.getMetricRecorder() + .addLongCounter(ENDPOINT_WEIGHT_STALE_COUNTER, staleEndpoints.get(), + ImmutableList.of(helper.getChannelTarget()), + ImmutableList.of(locality)); + } + if (notYetUsableEndpoints.get() > 0) { + helper.getMetricRecorder() + .addLongCounter(ENDPOINT_WEIGHT_NOT_YET_USEABLE_COUNTER, notYetUsableEndpoints.get(), + ImmutableList.of(helper.getChannelTarget()), ImmutableList.of(locality)); + } + boolean weightsEffective = picker.updateWeight(newWeights); + if (!weightsEffective) { + helper.getMetricRecorder() + .addLongCounter(RR_FALLBACK_COUNTER, 1, ImmutableList.of(helper.getChannelTarget()), + ImmutableList.of(locality)); + } } private void updateBalancingState(ConnectivityState state, SubchannelPicker picker) { @@ -345,7 +379,7 @@ private final class UpdateWeightTask implements Runnable { @Override public void run() { if (currentPicker != null && currentPicker instanceof WeightedRoundRobinPicker) { - ((WeightedRoundRobinPicker) currentPicker).updateWeight(); + updateWeight((WeightedRoundRobinPicker) currentPicker); } weightUpdateTimer = syncContext.schedule(this, config.weightUpdatePeriodNanos, TimeUnit.NANOSECONDS, timeService); @@ -415,53 +449,50 @@ public void shutdown() { @VisibleForTesting static final class WeightedRoundRobinPicker extends SubchannelPicker { - private final List children; - private final Map subchannelToReportListenerMap = - new HashMap<>(); + // Parallel lists (column-based storage instead of normal row-based storage of List). + // The ith element of children corresponds to the ith element of pickers, listeners, and even + // updateWeight(float[]). + private final List children; // May only be accessed from sync context + private final List pickers; + private final List reportListeners; private final boolean enableOobLoadReport; private final float errorUtilizationPenalty; private final AtomicInteger sequence; private final int hashCode; - private final LoadBalancer.Helper helper; - private final String locality; private volatile StaticStrideScheduler scheduler; WeightedRoundRobinPicker(List children, boolean enableOobLoadReport, - float errorUtilizationPenalty, AtomicInteger sequence, LoadBalancer.Helper helper, - String locality) { + float errorUtilizationPenalty, AtomicInteger sequence) { checkNotNull(children, "children"); Preconditions.checkArgument(!children.isEmpty(), "empty child list"); this.children = children; + List pickers = new ArrayList<>(children.size()); + List reportListeners = new ArrayList<>(children.size()); for (ChildLbState child : children) { WeightedChildLbState wChild = (WeightedChildLbState) child; - for (WrrSubchannel subchannel : wChild.subchannels) { - this.subchannelToReportListenerMap - .put(subchannel, wChild.getOrCreateOrcaListener(errorUtilizationPenalty)); - } + pickers.add(wChild.getCurrentPicker()); + reportListeners.add(wChild.getOrCreateOrcaListener(errorUtilizationPenalty)); } + this.pickers = pickers; + this.reportListeners = reportListeners; this.enableOobLoadReport = enableOobLoadReport; this.errorUtilizationPenalty = errorUtilizationPenalty; this.sequence = checkNotNull(sequence, "sequence"); - this.helper = helper; - this.locality = checkNotNull(locality, "locality"); - // For equality we treat children as a set; use hash code as defined by Set + // For equality we treat pickers as a set; use hash code as defined by Set int sum = 0; - for (ChildLbState child : children) { - sum += child.hashCode(); + for (SubchannelPicker picker : pickers) { + sum += picker.hashCode(); } this.hashCode = sum ^ Boolean.hashCode(enableOobLoadReport) ^ Float.hashCode(errorUtilizationPenalty); - - updateWeight(); } @Override public PickResult pickSubchannel(PickSubchannelArgs args) { - ChildLbState childLbState = children.get(scheduler.pick()); - WeightedChildLbState wChild = (WeightedChildLbState) childLbState; - PickResult pickResult = childLbState.getCurrentPicker().pickSubchannel(args); + int pick = scheduler.pick(); + PickResult pickResult = pickers.get(pick).pickSubchannel(args); Subchannel subchannel = pickResult.getSubchannel(); if (subchannel == null) { return pickResult; @@ -469,48 +500,16 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { if (!enableOobLoadReport) { return PickResult.withSubchannel(subchannel, OrcaPerRequestUtil.getInstance().newOrcaClientStreamTracerFactory( - subchannelToReportListenerMap.getOrDefault(subchannel, - wChild.getOrCreateOrcaListener(errorUtilizationPenalty)))); + reportListeners.get(pick))); } else { return PickResult.withSubchannel(subchannel); } } - private void updateWeight() { - float[] newWeights = new float[children.size()]; - AtomicInteger staleEndpoints = new AtomicInteger(); - AtomicInteger notYetUsableEndpoints = new AtomicInteger(); - for (int i = 0; i < children.size(); i++) { - double newWeight = ((WeightedChildLbState) children.get(i)).getWeight(staleEndpoints, - notYetUsableEndpoints); - // TODO: add locality label once available - helper.getMetricRecorder() - .recordDoubleHistogram(ENDPOINT_WEIGHTS_HISTOGRAM, newWeight, - ImmutableList.of(helper.getChannelTarget()), - ImmutableList.of(locality)); - newWeights[i] = newWeight > 0 ? (float) newWeight : 0.0f; - } - if (staleEndpoints.get() > 0) { - // TODO: add locality label once available - helper.getMetricRecorder() - .addLongCounter(ENDPOINT_WEIGHT_STALE_COUNTER, staleEndpoints.get(), - ImmutableList.of(helper.getChannelTarget()), - ImmutableList.of(locality)); - } - if (notYetUsableEndpoints.get() > 0) { - // TODO: add locality label once available - helper.getMetricRecorder() - .addLongCounter(ENDPOINT_WEIGHT_NOT_YET_USEABLE_COUNTER, notYetUsableEndpoints.get(), - ImmutableList.of(helper.getChannelTarget()), ImmutableList.of(locality)); - } - + /** Returns {@code true} if weights are different than round_robin. */ + private boolean updateWeight(float[] newWeights) { this.scheduler = new StaticStrideScheduler(newWeights, sequence); - if (this.scheduler.usesRoundRobin()) { - // TODO: locality label once available - helper.getMetricRecorder() - .addLongCounter(RR_FALLBACK_COUNTER, 1, ImmutableList.of(helper.getChannelTarget()), - ImmutableList.of(locality)); - } + return !this.scheduler.usesRoundRobin(); } @Override @@ -518,7 +517,8 @@ public String toString() { return MoreObjects.toStringHelper(WeightedRoundRobinPicker.class) .add("enableOobLoadReport", enableOobLoadReport) .add("errorUtilizationPenalty", errorUtilizationPenalty) - .add("list", children).toString(); + .add("pickers", pickers) + .toString(); } @VisibleForTesting @@ -545,8 +545,8 @@ public boolean equals(Object o) { && sequence == other.sequence && enableOobLoadReport == other.enableOobLoadReport && Float.compare(errorUtilizationPenalty, other.errorUtilizationPenalty) == 0 - && children.size() == other.children.size() - && new HashSet<>(children).containsAll(other.children); + && pickers.size() == other.pickers.size() + && new HashSet<>(pickers).containsAll(other.pickers); } } diff --git a/xds/src/test/java/io/grpc/xds/WeightedRoundRobinLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/WeightedRoundRobinLoadBalancerTest.java index dd98f1e1ae6..05ad1f56ece 100644 --- a/xds/src/test/java/io/grpc/xds/WeightedRoundRobinLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/WeightedRoundRobinLoadBalancerTest.java @@ -244,7 +244,7 @@ public void wrrLifeCycle() { String weightedPickerStr = weightedPicker.toString(); assertThat(weightedPickerStr).contains("enableOobLoadReport=false"); assertThat(weightedPickerStr).contains("errorUtilizationPenalty=1.0"); - assertThat(weightedPickerStr).contains("list="); + assertThat(weightedPickerStr).contains("pickers="); WeightedChildLbState weightedChild1 = (WeightedChildLbState) getChild(weightedPicker, 0); WeightedChildLbState weightedChild2 = (WeightedChildLbState) getChild(weightedPicker, 1); From 4ab34229fb32f00a2146302e16e226080d13a4aa Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Fri, 9 Aug 2024 16:21:19 -0700 Subject: [PATCH 031/103] netty: Use DefaultELG with LocalChannel in test LocalChannel is not guaranteed to be compatible with NioEventLoopGroup, and is failing with Netty 4.2.0.Alpha3-SNAPSHOT. See #11447 --- .../grpc/netty/NettyClientTransportTest.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java index f94960cbab3..b40cd9d5607 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java @@ -82,6 +82,7 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPromise; +import io.netty.channel.DefaultEventLoopGroup; import io.netty.channel.EventLoopGroup; import io.netty.channel.ReflectiveChannelFactory; import io.netty.channel.local.LocalChannel; @@ -519,15 +520,20 @@ public void channelFactoryShouldSetSocketOptionKeepAlive() throws Exception { @Test public void channelFactoryShouldNNotSetSocketOptionKeepAlive() throws Exception { startServer(); - NettyClientTransport transport = newTransport(newNegotiator(), - DEFAULT_MAX_MESSAGE_SIZE, GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE, "testUserAgent", true, - TimeUnit.SECONDS.toNanos(10L), TimeUnit.SECONDS.toNanos(1L), - new ReflectiveChannelFactory<>(LocalChannel.class), group); + DefaultEventLoopGroup group = new DefaultEventLoopGroup(1); + try { + NettyClientTransport transport = newTransport(newNegotiator(), + DEFAULT_MAX_MESSAGE_SIZE, GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE, "testUserAgent", true, + TimeUnit.SECONDS.toNanos(10L), TimeUnit.SECONDS.toNanos(1L), + new ReflectiveChannelFactory<>(LocalChannel.class), group); - callMeMaybe(transport.start(clientTransportListener)); + callMeMaybe(transport.start(clientTransportListener)); - assertThat(transport.channel().config().getOption(ChannelOption.SO_KEEPALIVE)) - .isNull(); + assertThat(transport.channel().config().getOption(ChannelOption.SO_KEEPALIVE)) + .isNull(); + } finally { + group.shutdownGracefully(0, 10, TimeUnit.SECONDS); + } } @Test From a6f8ebf33dd34e202dcd253f404606f161ddb741 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 25 Jul 2024 15:25:42 -0700 Subject: [PATCH 032/103] Remove implicit requestConnection() on IDLE from MultiChildLB One LB no longer needs to extend ChildLbState and one has to start, so it is a bit of a wash. There are more LBs that need the auto-request logic, but if we have an API where subclasses override it without calling super then we can't change the implementation in the future. Adding behavior on top of a base class allows subclasses to call super, which lets the base class change over time. --- .../io/grpc/util/MultiChildLoadBalancer.java | 5 -- .../io/grpc/util/RoundRobinLoadBalancer.java | 19 +++++++ .../io/grpc/xds/LeastRequestLoadBalancer.java | 13 +++++ .../io/grpc/xds/RingHashLoadBalancer.java | 49 +++---------------- .../xds/WeightedRoundRobinLoadBalancer.java | 8 +++ .../io/grpc/xds/RingHashLoadBalancerTest.java | 6 +-- 6 files changed, 48 insertions(+), 52 deletions(-) diff --git a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java index f2e2cc617ee..951721adced 100644 --- a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java +++ b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java @@ -456,8 +456,6 @@ protected class ChildLbStateHelper extends ForwardingLoadBalancerHelper { /** * Update current state and picker for this child and then use * {@link #updateOverallBalancingState()} for the parent LB. - * - *

Override this if you don't want to automatically request a connection when in IDLE */ @Override public void updateBalancingState(final ConnectivityState newState, @@ -471,9 +469,6 @@ public void updateBalancingState(final ConnectivityState newState, // If we are already in the process of resolving addresses, the overall balancing state // will be updated at the end of it, and we don't need to trigger that update here. if (!resolvingAddresses) { - if (newState == IDLE) { - lb.requestConnection(); - } updateOverallBalancingState(); } } diff --git a/util/src/main/java/io/grpc/util/RoundRobinLoadBalancer.java b/util/src/main/java/io/grpc/util/RoundRobinLoadBalancer.java index 7c235bb3640..765e2a4d4b6 100644 --- a/util/src/main/java/io/grpc/util/RoundRobinLoadBalancer.java +++ b/util/src/main/java/io/grpc/util/RoundRobinLoadBalancer.java @@ -95,6 +95,25 @@ private SubchannelPicker createReadyPicker(Collection children) { return new ReadyPicker(pickerList, sequence); } + @Override + protected ChildLbState createChildLbState(Object key, Object policyConfig, + SubchannelPicker initialPicker, ResolvedAddresses resolvedAddresses) { + return new ChildLbState(key, pickFirstLbProvider, policyConfig, initialPicker) { + @Override + protected ChildLbStateHelper createChildHelper() { + return new ChildLbStateHelper() { + @Override + public void updateBalancingState(ConnectivityState newState, SubchannelPicker newPicker) { + super.updateBalancingState(newState, newPicker); + if (!resolvingAddresses && newState == IDLE) { + getLb().requestConnection(); + } + } + }; + } + }; + } + @VisibleForTesting static class ReadyPicker extends SubchannelPicker { private final List subchannelPickers; // non-empty diff --git a/xds/src/main/java/io/grpc/xds/LeastRequestLoadBalancer.java b/xds/src/main/java/io/grpc/xds/LeastRequestLoadBalancer.java index f96c171ee9c..a11622d492f 100644 --- a/xds/src/main/java/io/grpc/xds/LeastRequestLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/LeastRequestLoadBalancer.java @@ -328,5 +328,18 @@ public LeastRequestLbState(Object key, LoadBalancerProvider policyProvider, int getActiveRequests() { return activeRequests.get(); } + + @Override + protected ChildLbStateHelper createChildHelper() { + return new ChildLbStateHelper() { + @Override + public void updateBalancingState(ConnectivityState newState, SubchannelPicker newPicker) { + super.updateBalancingState(newState, newPicker); + if (!resolvingAddresses && newState == IDLE) { + getLb().requestConnection(); + } + } + }; + } } } diff --git a/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java b/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java index 3b7e451f2a5..72618b7bbaa 100644 --- a/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java @@ -229,7 +229,7 @@ protected void updateOverallBalancingState() { @Override protected ChildLbState createChildLbState(Object key, Object policyConfig, SubchannelPicker initialPicker, ResolvedAddresses resolvedAddresses) { - return new RingHashChildLbState((Endpoint)key); + return new ChildLbState(key, lazyLbFactory, null, EMPTY_PICKER); } private Status validateAddrList(List addrList) { @@ -358,7 +358,7 @@ private RingHashPicker( this.ring = ring; pickableSubchannels = new HashMap<>(subchannels.size()); for (Map.Entry entry : subchannels.entrySet()) { - RingHashChildLbState childLbState = (RingHashChildLbState) entry.getValue(); + ChildLbState childLbState = entry.getValue(); pickableSubchannels.put((Endpoint)entry.getKey(), new SubchannelView(childLbState, childLbState.getCurrentState())); } @@ -405,7 +405,7 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { for (int i = 0; i < ring.size(); i++) { int index = (targetIndex + i) % ring.size(); SubchannelView subchannelView = pickableSubchannels.get(ring.get(index).addrKey); - RingHashChildLbState childLbState = subchannelView.childLbState; + ChildLbState childLbState = subchannelView.childLbState; if (subchannelView.connectivityState == READY) { return childLbState.getCurrentPicker().pickSubchannel(args); @@ -427,7 +427,7 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { } // return the pick from the original subchannel hit by hash, which is probably an error - RingHashChildLbState originalSubchannel = + ChildLbState originalSubchannel = pickableSubchannels.get(ring.get(targetIndex).addrKey).childLbState; return originalSubchannel.getCurrentPicker().pickSubchannel(args); } @@ -439,10 +439,10 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { * state changes. */ private static final class SubchannelView { - private final RingHashChildLbState childLbState; + private final ChildLbState childLbState; private final ConnectivityState connectivityState; - private SubchannelView(RingHashChildLbState childLbState, ConnectivityState state) { + private SubchannelView(ChildLbState childLbState, ConnectivityState state) { this.childLbState = childLbState; this.connectivityState = state; } @@ -487,41 +487,4 @@ public String toString() { .toString(); } } - - class RingHashChildLbState extends MultiChildLoadBalancer.ChildLbState { - - public RingHashChildLbState(Endpoint key) { - super(key, lazyLbFactory, null, EMPTY_PICKER); - } - - @Override - protected ChildLbStateHelper createChildHelper() { - return new RingHashChildHelper(); - } - - // Need to expose this to the LB class - @Override - protected void shutdown() { - super.shutdown(); - } - - private class RingHashChildHelper extends ChildLbStateHelper { - @Override - public void updateBalancingState(final ConnectivityState newState, - final SubchannelPicker newPicker) { - setCurrentState(newState); - setCurrentPicker(newPicker); - - if (getChildLbState(getKey()) == null) { - return; - } - - // If we are already in the process of resolving addresses, the overall balancing state - // will be updated at the end of it, and we don't need to trigger that update here. - if (!resolvingAddresses) { - updateOverallBalancingState(); - } - } - } - } } diff --git a/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java b/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java index e4502da8740..388eca2579d 100644 --- a/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java @@ -340,6 +340,14 @@ final class WrrChildLbStateHelper extends ChildLbStateHelper { public Subchannel createSubchannel(CreateSubchannelArgs args) { return new WrrSubchannel(super.createSubchannel(args), WeightedChildLbState.this); } + + @Override + public void updateBalancingState(ConnectivityState newState, SubchannelPicker newPicker) { + super.updateBalancingState(newState, newPicker); + if (!resolvingAddresses && newState == ConnectivityState.IDLE) { + getLb().requestConnection(); + } + } } final class OrcaReportListener implements OrcaPerRequestReportListener, OrcaOobReportListener { diff --git a/xds/src/test/java/io/grpc/xds/RingHashLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/RingHashLoadBalancerTest.java index de871cdd8f1..047ba71bbe0 100644 --- a/xds/src/test/java/io/grpc/xds/RingHashLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/RingHashLoadBalancerTest.java @@ -66,7 +66,6 @@ import io.grpc.testing.TestMethodDescriptors; import io.grpc.util.AbstractTestHelper; import io.grpc.util.MultiChildLoadBalancer.ChildLbState; -import io.grpc.xds.RingHashLoadBalancer.RingHashChildLbState; import io.grpc.xds.RingHashLoadBalancer.RingHashConfig; import java.lang.Thread.UncaughtExceptionHandler; import java.net.SocketAddress; @@ -177,8 +176,7 @@ public void subchannelNotAutoReconnectAfterReenteringIdle() { assertThat(addressesAcceptanceStatus.isOk()).isTrue(); verify(helper).updateBalancingState(eq(IDLE), pickerCaptor.capture()); - RingHashChildLbState childLbState = - (RingHashChildLbState) loadBalancer.getChildLbStates().iterator().next(); + ChildLbState childLbState = loadBalancer.getChildLbStates().iterator().next(); assertThat(subchannels.get(Collections.singletonList(childLbState.getEag()))).isNull(); // Picking subchannel triggers connection. @@ -422,7 +420,7 @@ public void skipFailingHosts_pickNextNonFailingHost() { assertThat(addressesAcceptanceStatus.isOk()).isTrue(); // Create subchannel for the first address - ((RingHashChildLbState) loadBalancer.getChildLbStateEag(servers.get(0))).getCurrentPicker() + loadBalancer.getChildLbStateEag(servers.get(0)).getCurrentPicker() .pickSubchannel(getDefaultPickSubchannelArgs(hashFunc.hashVoid())); verifyConnection(1); From b5989a54014b9066defb26d4cca79432a5dba1be Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 25 Jul 2024 15:46:02 -0700 Subject: [PATCH 033/103] util: MultiChildLb children should always start with a NoResult picker That's the obvious default, and all current usages use (something equivalent to) that default. --- .../io/grpc/util/MultiChildLoadBalancer.java | 22 +++++-------------- .../io/grpc/util/RoundRobinLoadBalancer.java | 4 ++-- .../grpc/xds/ClusterManagerLoadBalancer.java | 6 ++--- .../io/grpc/xds/LeastRequestLoadBalancer.java | 8 +++---- .../io/grpc/xds/RingHashLoadBalancer.java | 4 ++-- .../xds/WeightedRoundRobinLoadBalancer.java | 11 +++++----- 6 files changed, 21 insertions(+), 34 deletions(-) diff --git a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java index 951721adced..51144b7c017 100644 --- a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java +++ b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java @@ -91,8 +91,7 @@ protected Map createChildLbMap(ResolvedAddresses resolvedA if (existingChildLbState != null) { childLbMap.put(endpoint, existingChildLbState); } else { - childLbMap.put(endpoint, - createChildLbState(endpoint, null, getInitialPicker(), resolvedAddresses)); + childLbMap.put(endpoint, createChildLbState(endpoint, null, resolvedAddresses)); } } return childLbMap; @@ -102,8 +101,8 @@ protected Map createChildLbMap(ResolvedAddresses resolvedA * Override to create an instance of a subclass. */ protected ChildLbState createChildLbState(Object key, Object policyConfig, - SubchannelPicker initialPicker, ResolvedAddresses resolvedAddresses) { - return new ChildLbState(key, pickFirstLbProvider, policyConfig, initialPicker); + ResolvedAddresses resolvedAddresses) { + return new ChildLbState(key, pickFirstLbProvider, policyConfig); } /** @@ -187,15 +186,6 @@ protected void handleNameResolutionError(ChildLbState child, Status error) { child.lb.handleNameResolutionError(error); } - /** - * Creates a picker representing the state before any connections have been established. - * - *

Override to produce a custom picker. - */ - protected SubchannelPicker getInitialPicker() { - return new FixedResultPicker(PickResult.withNoResult()); - } - /** * Creates a new picker representing an error status. * @@ -365,12 +355,10 @@ public class ChildLbState { private final LoadBalancer lb; private ConnectivityState currentState; - private SubchannelPicker currentPicker; + private SubchannelPicker currentPicker = new FixedResultPicker(PickResult.withNoResult()); - public ChildLbState(Object key, LoadBalancer.Factory policyFactory, Object childConfig, - SubchannelPicker initialPicker) { + public ChildLbState(Object key, LoadBalancer.Factory policyFactory, Object childConfig) { this.key = key; - this.currentPicker = initialPicker; this.config = childConfig; this.lb = policyFactory.newLoadBalancer(createChildHelper()); this.currentState = CONNECTING; diff --git a/util/src/main/java/io/grpc/util/RoundRobinLoadBalancer.java b/util/src/main/java/io/grpc/util/RoundRobinLoadBalancer.java index 765e2a4d4b6..a8d829cfb8d 100644 --- a/util/src/main/java/io/grpc/util/RoundRobinLoadBalancer.java +++ b/util/src/main/java/io/grpc/util/RoundRobinLoadBalancer.java @@ -97,8 +97,8 @@ private SubchannelPicker createReadyPicker(Collection children) { @Override protected ChildLbState createChildLbState(Object key, Object policyConfig, - SubchannelPicker initialPicker, ResolvedAddresses resolvedAddresses) { - return new ChildLbState(key, pickFirstLbProvider, policyConfig, initialPicker) { + ResolvedAddresses resolvedAddresses) { + return new ChildLbState(key, pickFirstLbProvider, policyConfig) { @Override protected ChildLbStateHelper createChildHelper() { return new ChildLbStateHelper() { diff --git a/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java index 9e9ca5e1da3..50669cfeeb3 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java @@ -85,7 +85,7 @@ protected Map createChildLbMap(ResolvedAddresses resolvedA ChildLbState child = getChildLbState(entry.getKey()); if (child == null) { child = new ClusterManagerLbState(entry.getKey(), - entry.getValue().getProvider(), entry.getValue().getConfig(), getInitialPicker()); + entry.getValue().getProvider(), entry.getValue().getConfig()); } newChildPolicies.put(entry.getKey(), child); } @@ -202,8 +202,8 @@ private class ClusterManagerLbState extends ChildLbState { ScheduledHandle deletionTimer; public ClusterManagerLbState(Object key, LoadBalancerProvider policyProvider, - Object childConfig, SubchannelPicker initialPicker) { - super(key, policyProvider, childConfig, initialPicker); + Object childConfig) { + super(key, policyProvider, childConfig); } @Override diff --git a/xds/src/main/java/io/grpc/xds/LeastRequestLoadBalancer.java b/xds/src/main/java/io/grpc/xds/LeastRequestLoadBalancer.java index a11622d492f..52fa1298e60 100644 --- a/xds/src/main/java/io/grpc/xds/LeastRequestLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/LeastRequestLoadBalancer.java @@ -127,8 +127,8 @@ protected void updateOverallBalancingState() { @Override protected ChildLbState createChildLbState(Object key, Object policyConfig, - SubchannelPicker initialPicker, ResolvedAddresses unused) { - return new LeastRequestLbState(key, pickFirstLbProvider, policyConfig, initialPicker); + ResolvedAddresses unused) { + return new LeastRequestLbState(key, pickFirstLbProvider, policyConfig); } private void updateBalancingState(ConnectivityState state, SubchannelPicker picker) { @@ -321,8 +321,8 @@ protected class LeastRequestLbState extends ChildLbState { private final AtomicInteger activeRequests = new AtomicInteger(0); public LeastRequestLbState(Object key, LoadBalancerProvider policyProvider, - Object childConfig, SubchannelPicker initialPicker) { - super(key, policyProvider, childConfig, initialPicker); + Object childConfig) { + super(key, policyProvider, childConfig); } int getActiveRequests() { diff --git a/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java b/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java index 72618b7bbaa..4e380dcd3d3 100644 --- a/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java @@ -228,8 +228,8 @@ protected void updateOverallBalancingState() { @Override protected ChildLbState createChildLbState(Object key, Object policyConfig, - SubchannelPicker initialPicker, ResolvedAddresses resolvedAddresses) { - return new ChildLbState(key, lazyLbFactory, null, EMPTY_PICKER); + ResolvedAddresses resolvedAddresses) { + return new ChildLbState(key, lazyLbFactory, null); } private Status validateAddrList(List addrList) { diff --git a/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java b/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java index 388eca2579d..65cd146fdd3 100644 --- a/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java @@ -149,9 +149,8 @@ public WeightedRoundRobinLoadBalancer(Helper helper, Ticker ticker) { @Override protected ChildLbState createChildLbState(Object key, Object policyConfig, - SubchannelPicker initialPicker, ResolvedAddresses unused) { - ChildLbState childLbState = new WeightedChildLbState(key, pickFirstLbProvider, policyConfig, - initialPicker); + ResolvedAddresses unused) { + ChildLbState childLbState = new WeightedChildLbState(key, pickFirstLbProvider, policyConfig); return childLbState; } @@ -290,9 +289,9 @@ final class WeightedChildLbState extends ChildLbState { private OrcaReportListener orcaReportListener; - public WeightedChildLbState(Object key, LoadBalancerProvider policyProvider, Object childConfig, - SubchannelPicker initialPicker) { - super(key, policyProvider, childConfig, initialPicker); + public WeightedChildLbState( + Object key, LoadBalancerProvider policyProvider, Object childConfig) { + super(key, policyProvider, childConfig); } @Override From fd8734f341a12999e7dd5c14aed744768775c855 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Fri, 26 Jul 2024 08:53:46 -0700 Subject: [PATCH 034/103] xds: Delegate more RingHashLB address updates to MultiChildLB Since 04474970 RingHashLB has not used acceptResolvedAddressesInternal(). At the time that was needed because deactivated children were part of MultiChildLB. But in 9de8e443, the logic of RingHashLB and MultiChildLB.acceptResolvedAddressesInternal() converged, so it can now swap back to using the base class for more logic. --- .../io/grpc/util/MultiChildLoadBalancer.java | 6 +++--- .../java/io/grpc/xds/RingHashLoadBalancer.java | 16 ++++------------ 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java index 51144b7c017..02ed6a00cca 100644 --- a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java +++ b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java @@ -231,7 +231,7 @@ protected final AcceptResolvedAddrRetVal acceptResolvedAddressesInternal( return new AcceptResolvedAddrRetVal(Status.OK, getRemovedChildren(newChildren.keySet())); } - protected final void addMissingChildren(Map newChildren) { + private void addMissingChildren(Map newChildren) { // Do adds and identify reused children for (Map.Entry entry : newChildren.entrySet()) { final Object key = entry.getKey(); @@ -241,7 +241,7 @@ protected final void addMissingChildren(Map newChildren) { } } - protected final void updateChildrenWithResolvedAddresses(ResolvedAddresses resolvedAddresses, + private void updateChildrenWithResolvedAddresses(ResolvedAddresses resolvedAddresses, Map newChildren) { for (Map.Entry entry : newChildren.entrySet()) { Object childConfig = entry.getValue().getConfig(); @@ -256,7 +256,7 @@ protected final void updateChildrenWithResolvedAddresses(ResolvedAddresses resol /** * Identifies which children have been removed (are not part of the newChildKeys). */ - protected final List getRemovedChildren(Set newChildKeys) { + private List getRemovedChildren(Set newChildKeys) { List removedChildren = new ArrayList<>(); // Do removals for (Object key : ImmutableList.copyOf(childLbStates.keySet())) { diff --git a/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java b/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java index 4e380dcd3d3..e2f9a00f25b 100644 --- a/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java @@ -89,19 +89,11 @@ public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { try { resolvingAddresses = true; - // Subclass handles any special manipulation to create appropriate types of ChildLbStates - Map newChildren = createChildLbMap(resolvedAddresses); - - if (newChildren.isEmpty()) { - addressValidityStatus = Status.UNAVAILABLE.withDescription( - "Ring hash lb error: EDS resolution was successful, but there were no valid addresses"); - handleNameResolutionError(addressValidityStatus); - return addressValidityStatus; + AcceptResolvedAddrRetVal acceptRetVal = acceptResolvedAddressesInternal(resolvedAddresses); + if (!acceptRetVal.status.isOk()) { + return acceptRetVal.status; } - addMissingChildren(newChildren); - updateChildrenWithResolvedAddresses(resolvedAddresses, newChildren); - // Now do the ringhash specific logic with weights and building the ring RingHashConfig config = (RingHashConfig) resolvedAddresses.getLoadBalancingPolicyConfig(); if (config == null) { @@ -145,7 +137,7 @@ public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { // clusters and resolver can remove them in service config. updateOverallBalancingState(); - shutdownRemoved(getRemovedChildren(newChildren.keySet())); + shutdownRemoved(acceptRetVal.removedChildren); } finally { this.resolvingAddresses = false; } From c2eccca3bc1037be121b299ec3a3a4c47f6ee506 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Tue, 13 Aug 2024 10:27:48 -0700 Subject: [PATCH 035/103] cronet: Add internal API to specify Network cl/661194496 --- .../io/grpc/cronet/CronetChannelBuilder.java | 28 +++++++++++++++++-- .../cronet/InternalCronetChannelBuilder.java | 7 +++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/cronet/src/main/java/io/grpc/cronet/CronetChannelBuilder.java b/cronet/src/main/java/io/grpc/cronet/CronetChannelBuilder.java index 1c60f82846d..f42dabdd55a 100644 --- a/cronet/src/main/java/io/grpc/cronet/CronetChannelBuilder.java +++ b/cronet/src/main/java/io/grpc/cronet/CronetChannelBuilder.java @@ -20,9 +20,12 @@ import static com.google.common.base.Preconditions.checkNotNull; import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE; +import android.net.Network; +import android.os.Build; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.util.concurrent.MoreExecutors; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotCall; import io.grpc.ChannelCredentials; import io.grpc.ChannelLogger; @@ -105,6 +108,7 @@ public static CronetChannelBuilder forAddress(String name, int port) { private int trafficStatsTag; private boolean trafficStatsUidSet; private int trafficStatsUid; + private Network network; private CronetChannelBuilder(String host, int port, CronetEngine cronetEngine) { final class CronetChannelTransportFactoryBuilder implements ClientTransportFactoryBuilder { @@ -190,6 +194,13 @@ CronetChannelBuilder setTrafficStatsUid(int uid) { return this; } + /** Sets the network ID to use for this channel traffic. */ + @CanIgnoreReturnValue + CronetChannelBuilder bindToNetwork(@Nullable Network network) { + this.network = network; + return this; + } + /** * Provides a custom scheduled executor service. * @@ -210,7 +221,12 @@ public CronetChannelBuilder scheduledExecutorService( ClientTransportFactory buildTransportFactory() { return new CronetTransportFactory( new TaggingStreamFactory( - cronetEngine, trafficStatsTagSet, trafficStatsTag, trafficStatsUidSet, trafficStatsUid), + cronetEngine, + trafficStatsTagSet, + trafficStatsTag, + trafficStatsUidSet, + trafficStatsUid, + network), MoreExecutors.directExecutor(), scheduledExecutorService, maxMessageSize, @@ -294,18 +310,21 @@ private static class TaggingStreamFactory extends StreamBuilderFactory { private final int trafficStatsTag; private final boolean trafficStatsUidSet; private final int trafficStatsUid; + private final Network network; TaggingStreamFactory( CronetEngine cronetEngine, boolean trafficStatsTagSet, int trafficStatsTag, boolean trafficStatsUidSet, - int trafficStatsUid) { + int trafficStatsUid, + Network network) { this.cronetEngine = cronetEngine; this.trafficStatsTagSet = trafficStatsTagSet; this.trafficStatsTag = trafficStatsTag; this.trafficStatsUidSet = trafficStatsUidSet; this.trafficStatsUid = trafficStatsUid; + this.network = network; } @Override @@ -320,6 +339,11 @@ public BidirectionalStream.Builder newBidirectionalStreamBuilder( if (trafficStatsUidSet) { builder.setTrafficStatsUid(trafficStatsUid); } + if (network != null) { + if (Build.VERSION.SDK_INT >= 23) { + builder.bindToNetwork(network.getNetworkHandle()); + } + } return builder; } } diff --git a/cronet/src/main/java/io/grpc/cronet/InternalCronetChannelBuilder.java b/cronet/src/main/java/io/grpc/cronet/InternalCronetChannelBuilder.java index 2954f1eee81..7e5e610ca67 100644 --- a/cronet/src/main/java/io/grpc/cronet/InternalCronetChannelBuilder.java +++ b/cronet/src/main/java/io/grpc/cronet/InternalCronetChannelBuilder.java @@ -16,7 +16,9 @@ package io.grpc.cronet; +import android.net.Network; import io.grpc.Internal; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Internal {@link CronetChannelBuilder} accessor. This is intended for usage internal to the gRPC @@ -58,4 +60,9 @@ public static void setTrafficStatsTag(CronetChannelBuilder builder, int tag) { public static void setTrafficStatsUid(CronetChannelBuilder builder, int uid) { builder.setTrafficStatsUid(uid); } + + /** Sets the network {@link android.net.Network} to use when relying traffic by this channel. */ + public static void bindToNetwork(CronetChannelBuilder builder, @Nullable Network network) { + builder.bindToNetwork(network); + } } From d5840448d4145e7b9d649a484628682781fd3318 Mon Sep 17 00:00:00 2001 From: Kannan J Date: Tue, 13 Aug 2024 23:52:35 +0530 Subject: [PATCH 036/103] Update README etc to reference 1.66.0 (#11472) --- README.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index fef37c1c3bb..cb38ad66394 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,8 @@ For a guided tour, take a look at the [quick start guide](https://grpc.io/docs/languages/java/quickstart) or the more explanatory [gRPC basics](https://grpc.io/docs/languages/java/basics). -The [examples](https://github.com/grpc/grpc-java/tree/v1.65.0/examples) and the -[Android example](https://github.com/grpc/grpc-java/tree/v1.65.0/examples/android) +The [examples](https://github.com/grpc/grpc-java/tree/v1.66.0/examples) and the +[Android example](https://github.com/grpc/grpc-java/tree/v1.66.0/examples/android) are standalone projects that showcase the usage of gRPC. Download @@ -56,18 +56,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`: io.grpc grpc-netty-shaded - 1.65.0 + 1.66.0 runtime io.grpc grpc-protobuf - 1.65.0 + 1.66.0 io.grpc grpc-stub - 1.65.0 + 1.66.0 org.apache.tomcat @@ -79,18 +79,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`: Or for Gradle with non-Android, add to your dependencies: ```gradle -runtimeOnly 'io.grpc:grpc-netty-shaded:1.65.0' -implementation 'io.grpc:grpc-protobuf:1.65.0' -implementation 'io.grpc:grpc-stub:1.65.0' +runtimeOnly 'io.grpc:grpc-netty-shaded:1.66.0' +implementation 'io.grpc:grpc-protobuf:1.66.0' +implementation 'io.grpc:grpc-stub:1.66.0' compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+ ``` For Android client, use `grpc-okhttp` instead of `grpc-netty-shaded` and `grpc-protobuf-lite` instead of `grpc-protobuf`: ```gradle -implementation 'io.grpc:grpc-okhttp:1.65.0' -implementation 'io.grpc:grpc-protobuf-lite:1.65.0' -implementation 'io.grpc:grpc-stub:1.65.0' +implementation 'io.grpc:grpc-okhttp:1.66.0' +implementation 'io.grpc:grpc-protobuf-lite:1.66.0' +implementation 'io.grpc:grpc-stub:1.66.0' compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+ ``` @@ -99,7 +99,7 @@ For [Bazel](https://bazel.build), you can either (with the GAVs from above), or use `@io_grpc_grpc_java//api` et al (see below). [the JARs]: -https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.65.0 +https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.66.0 Development snapshots are available in [Sonatypes's snapshot repository](https://oss.sonatype.org/content/repositories/snapshots/). @@ -129,9 +129,9 @@ For protobuf-based codegen integrated with the Maven build system, you can use protobuf-maven-plugin 0.6.1 - com.google.protobuf:protoc:3.25.1:exe:${os.detected.classifier} + com.google.protobuf:protoc:3.25.3:exe:${os.detected.classifier} grpc-java - io.grpc:protoc-gen-grpc-java:1.65.0:exe:${os.detected.classifier} + io.grpc:protoc-gen-grpc-java:1.66.0:exe:${os.detected.classifier} @@ -157,11 +157,11 @@ plugins { protobuf { protoc { - artifact = "com.google.protobuf:protoc:3.25.1" + artifact = "com.google.protobuf:protoc:3.25.3" } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.65.0' + artifact = 'io.grpc:protoc-gen-grpc-java:1.66.0' } } generateProtoTasks { @@ -190,11 +190,11 @@ plugins { protobuf { protoc { - artifact = "com.google.protobuf:protoc:3.25.1" + artifact = "com.google.protobuf:protoc:3.25.3" } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.65.0' + artifact = 'io.grpc:protoc-gen-grpc-java:1.66.0' } } generateProtoTasks { From 75012a5be2e8dbcbe171ac119434239a30bee385 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Tue, 13 Aug 2024 16:43:44 -0700 Subject: [PATCH 037/103] examples: Upgrade Maven plugin versions Upgrade Maven to 3.8.8, the oldest supported version, as the plugins required a newer version. --- buildscripts/grpc-java-artifacts/Dockerfile | 4 ++-- examples/example-debug/pom.xml | 2 +- examples/example-dualstack/pom.xml | 2 +- examples/example-gauth/pom.xml | 2 +- examples/example-hostname/pom.xml | 2 +- examples/example-jwt-auth/pom.xml | 4 ++-- examples/example-oauth/pom.xml | 4 ++-- examples/example-tls/pom.xml | 2 +- examples/pom.xml | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/buildscripts/grpc-java-artifacts/Dockerfile b/buildscripts/grpc-java-artifacts/Dockerfile index 97c152780a3..736babe9d8e 100644 --- a/buildscripts/grpc-java-artifacts/Dockerfile +++ b/buildscripts/grpc-java-artifacts/Dockerfile @@ -28,6 +28,6 @@ RUN mkdir -p "$ANDROID_HOME/cmdline-tools" && \ yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --licenses # Install Maven -RUN curl -Ls https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.tar.gz | \ +RUN curl -Ls https://dlcdn.apache.org/maven/maven-3/3.8.8/binaries/apache-maven-3.8.8-bin.tar.gz | \ tar xz -C /var/local -ENV PATH /var/local/apache-maven-3.3.9/bin:$PATH +ENV PATH /var/local/apache-maven-3.8.8/bin:$PATH diff --git a/examples/example-debug/pom.xml b/examples/example-debug/pom.xml index 10ccf834d86..7365b35daab 100644 --- a/examples/example-debug/pom.xml +++ b/examples/example-debug/pom.xml @@ -98,7 +98,7 @@ org.apache.maven.plugins maven-enforcer-plugin - 1.4.1 + 3.5.0 enforce diff --git a/examples/example-dualstack/pom.xml b/examples/example-dualstack/pom.xml index 710b48ee617..f955cada0f6 100644 --- a/examples/example-dualstack/pom.xml +++ b/examples/example-dualstack/pom.xml @@ -102,7 +102,7 @@ org.apache.maven.plugins maven-enforcer-plugin - 1.4.1 + 3.5.0 enforce diff --git a/examples/example-gauth/pom.xml b/examples/example-gauth/pom.xml index 1e58e21e975..2512cb37ea5 100644 --- a/examples/example-gauth/pom.xml +++ b/examples/example-gauth/pom.xml @@ -96,7 +96,7 @@ org.apache.maven.plugins maven-enforcer-plugin - 1.4.1 + 3.5.0 enforce diff --git a/examples/example-hostname/pom.xml b/examples/example-hostname/pom.xml index 19b5f8b3c20..443ecb85ef2 100644 --- a/examples/example-hostname/pom.xml +++ b/examples/example-hostname/pom.xml @@ -98,7 +98,7 @@ org.apache.maven.plugins maven-enforcer-plugin - 1.4.1 + 3.5.0 enforce diff --git a/examples/example-jwt-auth/pom.xml b/examples/example-jwt-auth/pom.xml index ad530e33aa7..f2e92e28c84 100644 --- a/examples/example-jwt-auth/pom.xml +++ b/examples/example-jwt-auth/pom.xml @@ -94,7 +94,7 @@ org.xolstice.maven.plugins protobuf-maven-plugin - 0.5.1 + 0.6.1 com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier} @@ -116,7 +116,7 @@ org.apache.maven.plugins maven-enforcer-plugin - 1.4.1 + 3.5.0 enforce diff --git a/examples/example-oauth/pom.xml b/examples/example-oauth/pom.xml index 2c38a05b3e4..4e284b5f248 100644 --- a/examples/example-oauth/pom.xml +++ b/examples/example-oauth/pom.xml @@ -99,7 +99,7 @@ org.xolstice.maven.plugins protobuf-maven-plugin - 0.5.1 + 0.6.1 com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier} @@ -121,7 +121,7 @@ org.apache.maven.plugins maven-enforcer-plugin - 1.4.1 + 3.5.0 enforce diff --git a/examples/example-tls/pom.xml b/examples/example-tls/pom.xml index bc9c0a7a8ee..eab02cb5919 100644 --- a/examples/example-tls/pom.xml +++ b/examples/example-tls/pom.xml @@ -82,7 +82,7 @@ org.apache.maven.plugins maven-enforcer-plugin - 1.4.1 + 3.5.0 enforce diff --git a/examples/pom.xml b/examples/pom.xml index 2b25d13b50c..fdf92e9eca1 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -110,7 +110,7 @@ org.apache.maven.plugins maven-enforcer-plugin - 1.4.1 + 3.5.0 enforce From 909c4bc382c653399eddff25bb5cc9357a166713 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Sat, 27 Jul 2024 12:50:03 -0700 Subject: [PATCH 038/103] util: Remove minor convenience functions from MultiChildLB These were once needed to be overridden (e.g., by RoundRobinLB), but now nothing overrides them and MultiChildLB doesn't even call one of them. --- .../io/grpc/util/MultiChildLoadBalancer.java | 21 ++----------------- .../grpc/xds/ClusterManagerLoadBalancer.java | 5 +++-- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java index 02ed6a00cca..748f58924e1 100644 --- a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java +++ b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java @@ -173,28 +173,11 @@ protected ResolvedAddresses getChildAddresses(Object key, ResolvedAddresses reso @Override public void handleNameResolutionError(Status error) { if (currentConnectivityState != READY) { - helper.updateBalancingState(TRANSIENT_FAILURE, getErrorPicker(error)); + helper.updateBalancingState( + TRANSIENT_FAILURE, new FixedResultPicker(PickResult.withError(error))); } } - /** - * Handle the name resolution error only for the specified child. - * - *

Override if you need special handling. - */ - protected void handleNameResolutionError(ChildLbState child, Status error) { - child.lb.handleNameResolutionError(error); - } - - /** - * Creates a new picker representing an error status. - * - *

Override to produce a custom picker when there are errors. - */ - protected SubchannelPicker getErrorPicker(Status error) { - return new FixedResultPicker(PickResult.withError(error)); - } - @Override public void shutdown() { logger.log(Level.FINE, "Shutdown"); diff --git a/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java index 50669cfeeb3..6b6d2b81352 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java @@ -183,11 +183,12 @@ public void handleNameResolutionError(Status error) { for (ChildLbState state : getChildLbStates()) { if (((ClusterManagerLbState) state).deletionTimer == null) { gotoTransientFailure = false; - handleNameResolutionError(state, error); + state.getLb().handleNameResolutionError(error); } } if (gotoTransientFailure) { - getHelper().updateBalancingState(TRANSIENT_FAILURE, getErrorPicker(error)); + getHelper().updateBalancingState( + TRANSIENT_FAILURE, new FixedResultPicker(PickResult.withError(error))); } } From ff8e4137603b88bf2dcc43b502554c4fae437dac Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Tue, 13 Aug 2024 21:33:55 -0700 Subject: [PATCH 039/103] Remove direct dependency on j2objc Bazel had the dependency added because of #5046, where Guava was depending on it as compile-only and Bazel build have "unknown enum constant" warnings. Guava now has a compile dependency on j2objc, so this workaround is no longer needed. There are currently no version skew issues in Gradle, which was the only usage. --- alts/BUILD.bazel | 2 -- api/BUILD.bazel | 1 - auth/BUILD.bazel | 1 - core/BUILD.bazel | 1 - examples/pom.xml | 5 +++++ gcp-observability/build.gradle | 3 +-- gradle/libs.versions.toml | 1 - grpclb/BUILD.bazel | 1 - grpclb/build.gradle | 4 ++-- inprocess/BUILD.bazel | 1 - netty/BUILD.bazel | 1 - okhttp/BUILD.bazel | 1 - protobuf-lite/BUILD.bazel | 1 - protobuf/BUILD.bazel | 1 - services/build.gradle | 5 ++--- stub/BUILD.bazel | 1 - testing/BUILD.bazel | 1 - util/BUILD.bazel | 1 - xds/build.gradle | 1 + 19 files changed, 11 insertions(+), 22 deletions(-) diff --git a/alts/BUILD.bazel b/alts/BUILD.bazel index 819daedcc82..73420e11053 100644 --- a/alts/BUILD.bazel +++ b/alts/BUILD.bazel @@ -19,7 +19,6 @@ java_library( "@com_google_protobuf//:protobuf_java_util", artifact("com.google.code.findbugs:jsr305"), artifact("com.google.guava:guava"), - artifact("com.google.j2objc:j2objc-annotations"), artifact("io.netty:netty-buffer"), artifact("io.netty:netty-codec"), artifact("io.netty:netty-common"), @@ -45,7 +44,6 @@ java_library( artifact("com.google.auth:google-auth-library-oauth2-http"), artifact("com.google.code.findbugs:jsr305"), artifact("com.google.guava:guava"), - artifact("com.google.j2objc:j2objc-annotations"), artifact("io.netty:netty-common"), artifact("io.netty:netty-handler"), artifact("io.netty:netty-transport"), diff --git a/api/BUILD.bazel b/api/BUILD.bazel index 07be1d58dc7..6bf3375e9f0 100644 --- a/api/BUILD.bazel +++ b/api/BUILD.bazel @@ -13,6 +13,5 @@ java_library( artifact("com.google.errorprone:error_prone_annotations"), artifact("com.google.guava:failureaccess"), # future transitive dep of Guava. See #5214 artifact("com.google.guava:guava"), - artifact("com.google.j2objc:j2objc-annotations"), ], ) diff --git a/auth/BUILD.bazel b/auth/BUILD.bazel index 095fae5af8b..a19562fa7f7 100644 --- a/auth/BUILD.bazel +++ b/auth/BUILD.bazel @@ -11,6 +11,5 @@ java_library( artifact("com.google.auth:google-auth-library-credentials"), artifact("com.google.code.findbugs:jsr305"), artifact("com.google.guava:guava"), - artifact("com.google.j2objc:j2objc-annotations"), ], ) diff --git a/core/BUILD.bazel b/core/BUILD.bazel index a1d3d19e828..35c20628d0b 100644 --- a/core/BUILD.bazel +++ b/core/BUILD.bazel @@ -30,7 +30,6 @@ java_library( artifact("com.google.code.findbugs:jsr305"), artifact("com.google.errorprone:error_prone_annotations"), artifact("com.google.guava:guava"), - artifact("com.google.j2objc:j2objc-annotations"), artifact("io.perfmark:perfmark-api"), artifact("org.codehaus.mojo:animal-sniffer-annotations"), ], diff --git a/examples/pom.xml b/examples/pom.xml index fdf92e9eca1..5cd721b50d1 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -55,6 +55,11 @@ protobuf-java-util ${protobuf.version} + + com.google.j2objc + j2objc-annotations + 3.0.0 + org.apache.tomcat annotations-api diff --git a/gcp-observability/build.gradle b/gcp-observability/build.gradle index 0de7f8363bc..f869bd61a76 100644 --- a/gcp-observability/build.gradle +++ b/gcp-observability/build.gradle @@ -65,8 +65,7 @@ dependencies { libraries.auto.value.annotations, // Use our newer version libraries.guava.jre, // Use our newer version libraries.protobuf.java.util, // Use our newer version - libraries.re2j, // Use our newer version - libraries.j2objc.annotations // Explicit dependency to keep in step with version used by guava + libraries.re2j // Use our newer version testImplementation testFixtures(project(':grpc-api')), project(':grpc-testing'), diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 299ca60ab4b..488ead9ad86 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -42,7 +42,6 @@ guava-testlib = "com.google.guava:guava-testlib:33.2.1-android" # May be different from the -android version. guava-jre = "com.google.guava:guava:33.2.1-jre" hdrhistogram = "org.hdrhistogram:HdrHistogram:2.2.2" -j2objc-annotations = " com.google.j2objc:j2objc-annotations:3.0.0" jakarta-servlet-api = "jakarta.servlet:jakarta.servlet-api:5.0.0" javax-annotation = "org.apache.tomcat:annotations-api:6.0.53" javax-servlet-api = "javax.servlet:javax.servlet-api:4.0.1" diff --git a/grpclb/BUILD.bazel b/grpclb/BUILD.bazel index 517155bbfc1..2dd24bb52a2 100644 --- a/grpclb/BUILD.bazel +++ b/grpclb/BUILD.bazel @@ -21,7 +21,6 @@ java_library( "@io_grpc_grpc_proto//:grpclb_load_balancer_java_proto", artifact("com.google.code.findbugs:jsr305"), artifact("com.google.guava:guava"), - artifact("com.google.j2objc:j2objc-annotations"), ], ) diff --git a/grpclb/build.gradle b/grpclb/build.gradle index cea599828f5..93331053b09 100644 --- a/grpclb/build.gradle +++ b/grpclb/build.gradle @@ -19,9 +19,9 @@ dependencies { implementation project(':grpc-core'), project(':grpc-protobuf'), project(':grpc-stub'), + libraries.guava, libraries.protobuf.java, - libraries.protobuf.java.util, - libraries.guava + libraries.protobuf.java.util runtimeOnly libraries.errorprone.annotations compileOnly libraries.javax.annotation testImplementation libraries.truth, diff --git a/inprocess/BUILD.bazel b/inprocess/BUILD.bazel index aa614df654c..bef38612713 100644 --- a/inprocess/BUILD.bazel +++ b/inprocess/BUILD.bazel @@ -13,6 +13,5 @@ java_library( artifact("com.google.code.findbugs:jsr305"), artifact("com.google.errorprone:error_prone_annotations"), artifact("com.google.guava:guava"), - artifact("com.google.j2objc:j2objc-annotations"), ], ) diff --git a/netty/BUILD.bazel b/netty/BUILD.bazel index daf2e83e59a..9fe52ea5868 100644 --- a/netty/BUILD.bazel +++ b/netty/BUILD.bazel @@ -15,7 +15,6 @@ java_library( artifact("com.google.code.findbugs:jsr305"), artifact("com.google.errorprone:error_prone_annotations"), artifact("com.google.guava:guava"), - artifact("com.google.j2objc:j2objc-annotations"), artifact("io.netty:netty-buffer"), artifact("io.netty:netty-codec"), artifact("io.netty:netty-codec-http"), diff --git a/okhttp/BUILD.bazel b/okhttp/BUILD.bazel index 7cf1775da2c..80068c9bb5b 100644 --- a/okhttp/BUILD.bazel +++ b/okhttp/BUILD.bazel @@ -17,7 +17,6 @@ java_library( artifact("com.google.code.findbugs:jsr305"), artifact("com.google.errorprone:error_prone_annotations"), artifact("com.google.guava:guava"), - artifact("com.google.j2objc:j2objc-annotations"), artifact("com.squareup.okhttp:okhttp"), artifact("com.squareup.okio:okio"), artifact("io.perfmark:perfmark-api"), diff --git a/protobuf-lite/BUILD.bazel b/protobuf-lite/BUILD.bazel index 087723e95fb..dad794e8b58 100644 --- a/protobuf-lite/BUILD.bazel +++ b/protobuf-lite/BUILD.bazel @@ -10,7 +10,6 @@ java_library( "//api", artifact("com.google.code.findbugs:jsr305"), artifact("com.google.guava:guava"), - artifact("com.google.j2objc:j2objc-annotations"), ] + select({ ":android": ["@com_google_protobuf//:protobuf_javalite"], "//conditions:default": ["@com_google_protobuf//:protobuf_java"], diff --git a/protobuf/BUILD.bazel b/protobuf/BUILD.bazel index 47cc8f9d032..724c78ca6ee 100644 --- a/protobuf/BUILD.bazel +++ b/protobuf/BUILD.bazel @@ -13,6 +13,5 @@ java_library( artifact("com.google.api.grpc:proto-google-common-protos"), artifact("com.google.code.findbugs:jsr305"), artifact("com.google.guava:guava"), - artifact("com.google.j2objc:j2objc-annotations"), ], ) diff --git a/services/build.gradle b/services/build.gradle index de716c9fa1d..fade7aef3fb 100644 --- a/services/build.gradle +++ b/services/build.gradle @@ -27,11 +27,10 @@ dependencies { implementation project(':grpc-core'), project(':grpc-protobuf'), project(':grpc-util'), - libraries.protobuf.java.util, - libraries.guava.jre // JRE required by protobuf-java-util + libraries.guava.jre, // JRE required by protobuf-java-util + libraries.protobuf.java.util runtimeOnly libraries.errorprone.annotations, - libraries.j2objc.annotations, // Explicit dependency to keep in step with version used by guava libraries.gson // to fix checkUpperBoundDeps error here compileOnly libraries.javax.annotation testImplementation project(':grpc-testing'), diff --git a/stub/BUILD.bazel b/stub/BUILD.bazel index 8950a1cfd3f..6d06e01f918 100644 --- a/stub/BUILD.bazel +++ b/stub/BUILD.bazel @@ -12,7 +12,6 @@ java_library( artifact("com.google.code.findbugs:jsr305"), artifact("com.google.errorprone:error_prone_annotations"), artifact("com.google.guava:guava"), - artifact("com.google.j2objc:j2objc-annotations"), ], ) diff --git a/testing/BUILD.bazel b/testing/BUILD.bazel index 668a666c2fe..78f9b840754 100644 --- a/testing/BUILD.bazel +++ b/testing/BUILD.bazel @@ -18,7 +18,6 @@ java_library( "//util", artifact("com.google.code.findbugs:jsr305"), artifact("com.google.guava:guava"), - artifact("com.google.j2objc:j2objc-annotations"), artifact("com.google.truth:truth"), artifact("junit:junit"), ], diff --git a/util/BUILD.bazel b/util/BUILD.bazel index 7a38063a983..8fb00e21d56 100644 --- a/util/BUILD.bazel +++ b/util/BUILD.bazel @@ -15,7 +15,6 @@ java_library( artifact("com.google.code.findbugs:jsr305"), artifact("com.google.errorprone:error_prone_annotations"), artifact("com.google.guava:guava"), - artifact("com.google.j2objc:j2objc-annotations"), artifact("org.codehaus.mojo:animal-sniffer-annotations"), ], ) diff --git a/xds/build.gradle b/xds/build.gradle index a1d5aa753cb..a738145a2a0 100644 --- a/xds/build.gradle +++ b/xds/build.gradle @@ -52,6 +52,7 @@ dependencies { project(':grpc-services'), project(':grpc-auth'), project(path: ':grpc-alts', configuration: 'shadow'), + libraries.guava, libraries.gson, libraries.re2j, libraries.auto.value.annotations, From 6a9bc3ba1761ffd27961d8aa4f12a44aa45a3e6a Mon Sep 17 00:00:00 2001 From: sunpe Date: Tue, 13 Aug 2024 22:31:32 +0800 Subject: [PATCH 040/103] example: delete duplicate and unused code in KeepAliveClient.java --- .../main/java/io/grpc/examples/keepalive/KeepAliveClient.java | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/src/main/java/io/grpc/examples/keepalive/KeepAliveClient.java b/examples/src/main/java/io/grpc/examples/keepalive/KeepAliveClient.java index a7c59c3952f..414d92dea4c 100644 --- a/examples/src/main/java/io/grpc/examples/keepalive/KeepAliveClient.java +++ b/examples/src/main/java/io/grpc/examples/keepalive/KeepAliveClient.java @@ -78,7 +78,6 @@ public static void main(String[] args) throws Exception { // frames. // More details see: https://github.com/grpc/proposal/blob/master/A8-client-side-keepalive.md ManagedChannel channel = Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()) - .keepAliveTime(5, TimeUnit.MINUTES) .keepAliveTime(10, TimeUnit.SECONDS) // Change to a larger value, e.g. 5min. .keepAliveTimeout(1, TimeUnit.SECONDS) // Change to a larger value, e.g. 10s. .keepAliveWithoutCalls(true)// You should normally avoid enabling this. From 6dbd1b9d5a3a0a8d9c64f161e23b7f355a56e588 Mon Sep 17 00:00:00 2001 From: John Cormie Date: Wed, 14 Aug 2024 09:11:49 -0700 Subject: [PATCH 041/103] Add newAttachMetadataServerInterceptor() MetadataUtil (#11458) --- .../main/java/io/grpc/stub/MetadataUtils.java | 64 +++++++ .../java/io/grpc/stub/MetadataUtilsTest.java | 175 ++++++++++++++++++ 2 files changed, 239 insertions(+) create mode 100644 stub/src/test/java/io/grpc/stub/MetadataUtilsTest.java diff --git a/stub/src/main/java/io/grpc/stub/MetadataUtils.java b/stub/src/main/java/io/grpc/stub/MetadataUtils.java index addf54c0f81..4208d3ca652 100644 --- a/stub/src/main/java/io/grpc/stub/MetadataUtils.java +++ b/stub/src/main/java/io/grpc/stub/MetadataUtils.java @@ -22,10 +22,15 @@ import io.grpc.Channel; import io.grpc.ClientCall; import io.grpc.ClientInterceptor; +import io.grpc.ExperimentalApi; import io.grpc.ForwardingClientCall.SimpleForwardingClientCall; import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener; +import io.grpc.ForwardingServerCall.SimpleForwardingServerCall; import io.grpc.Metadata; import io.grpc.MethodDescriptor; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; import io.grpc.Status; import java.util.concurrent.atomic.AtomicReference; @@ -143,4 +148,63 @@ public void onClose(Status status, Metadata trailers) { } } } + + /** + * Returns a ServerInterceptor that adds the specified Metadata to every response stream, one way + * or another. + * + *

If, absent this interceptor, a stream would have headers, 'extras' will be added to those + * headers. Otherwise, 'extras' will be sent as trailers. This pattern is useful when you have + * some fixed information, server identity say, that should be included no matter how the call + * turns out. The fallback to trailers avoids artificially committing clients to error responses + * that could otherwise be retried (see https://grpc.io/docs/guides/retry/ for more). + * + *

For correct operation, be sure to arrange for this interceptor to run *before* any others + * that might add headers. + * + * @param extras the Metadata to be added to each stream. Caller gives up ownership. + */ + @ExperimentalApi("https://github.com/grpc/grpc-java/issues/11462") + public static ServerInterceptor newAttachMetadataServerInterceptor(Metadata extras) { + return new MetadataAttachingServerInterceptor(extras); + } + + private static final class MetadataAttachingServerInterceptor implements ServerInterceptor { + + private final Metadata extras; + + MetadataAttachingServerInterceptor(Metadata extras) { + this.extras = extras; + } + + @Override + public ServerCall.Listener interceptCall( + ServerCall call, Metadata headers, ServerCallHandler next) { + return next.startCall(new MetadataAttachingServerCall<>(call), headers); + } + + final class MetadataAttachingServerCall + extends SimpleForwardingServerCall { + boolean headersSent; + + MetadataAttachingServerCall(ServerCall delegate) { + super(delegate); + } + + @Override + public void sendHeaders(Metadata headers) { + headers.merge(extras); + headersSent = true; + super.sendHeaders(headers); + } + + @Override + public void close(Status status, Metadata trailers) { + if (!headersSent) { + trailers.merge(extras); + } + super.close(status, trailers); + } + } + } } diff --git a/stub/src/test/java/io/grpc/stub/MetadataUtilsTest.java b/stub/src/test/java/io/grpc/stub/MetadataUtilsTest.java new file mode 100644 index 00000000000..f9890ac0433 --- /dev/null +++ b/stub/src/test/java/io/grpc/stub/MetadataUtilsTest.java @@ -0,0 +1,175 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.stub; + +import static com.google.common.truth.Truth.assertThat; +import static io.grpc.stub.MetadataUtils.newAttachMetadataServerInterceptor; +import static io.grpc.stub.MetadataUtils.newCaptureMetadataInterceptor; +import static org.junit.Assert.fail; + +import com.google.common.collect.ImmutableList; +import io.grpc.CallOptions; +import io.grpc.ManagedChannel; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptors; +import io.grpc.ServerMethodDefinition; +import io.grpc.ServerServiceDefinition; +import io.grpc.Status; +import io.grpc.Status.Code; +import io.grpc.StatusRuntimeException; +import io.grpc.StringMarshaller; +import io.grpc.inprocess.InProcessChannelBuilder; +import io.grpc.inprocess.InProcessServerBuilder; +import io.grpc.testing.GrpcCleanupRule; +import java.io.IOException; +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class MetadataUtilsTest { + + @Rule public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule(); + + private static final String SERVER_NAME = "test"; + private static final Metadata.Key FOO_KEY = + Metadata.Key.of("foo-key", Metadata.ASCII_STRING_MARSHALLER); + + private final MethodDescriptor echoMethod = + MethodDescriptor.newBuilder(StringMarshaller.INSTANCE, StringMarshaller.INSTANCE) + .setFullMethodName("test/echo") + .setType(MethodDescriptor.MethodType.UNARY) + .build(); + + private final ServerCallHandler echoCallHandler = + ServerCalls.asyncUnaryCall( + (req, respObserver) -> { + respObserver.onNext(req); + respObserver.onCompleted(); + }); + + MethodDescriptor echoServerStreamingMethod = + MethodDescriptor.newBuilder(StringMarshaller.INSTANCE, StringMarshaller.INSTANCE) + .setFullMethodName("test/echoStream") + .setType(MethodDescriptor.MethodType.SERVER_STREAMING) + .build(); + + private final AtomicReference trailersCapture = new AtomicReference<>(); + private final AtomicReference headersCapture = new AtomicReference<>(); + + @Test + public void shouldAttachHeadersToResponse() throws IOException { + Metadata extras = new Metadata(); + extras.put(FOO_KEY, "foo-value"); + + ServerServiceDefinition serviceDef = + ServerInterceptors.intercept( + ServerServiceDefinition.builder("test").addMethod(echoMethod, echoCallHandler).build(), + ImmutableList.of(newAttachMetadataServerInterceptor(extras))); + + grpcCleanup.register(newInProcessServerBuilder().addService(serviceDef).build().start()); + ManagedChannel channel = + grpcCleanup.register( + newInProcessChannelBuilder() + .intercept(newCaptureMetadataInterceptor(headersCapture, trailersCapture)) + .build()); + + String response = + ClientCalls.blockingUnaryCall(channel, echoMethod, CallOptions.DEFAULT, "hello"); + assertThat(response).isEqualTo("hello"); + assertThat(trailersCapture.get() == null || !trailersCapture.get().containsKey(FOO_KEY)) + .isTrue(); + assertThat(headersCapture.get().get(FOO_KEY)).isEqualTo("foo-value"); + } + + @Test + public void shouldAttachTrailersWhenNoResponse() throws IOException { + Metadata extras = new Metadata(); + extras.put(FOO_KEY, "foo-value"); + + ServerServiceDefinition serviceDef = + ServerInterceptors.intercept( + ServerServiceDefinition.builder("test") + .addMethod( + ServerMethodDefinition.create( + echoServerStreamingMethod, + ServerCalls.asyncUnaryCall( + (req, respObserver) -> respObserver.onCompleted()))) + .build(), + ImmutableList.of(newAttachMetadataServerInterceptor(extras))); + grpcCleanup.register(newInProcessServerBuilder().addService(serviceDef).build().start()); + + ManagedChannel channel = + grpcCleanup.register( + newInProcessChannelBuilder() + .intercept(newCaptureMetadataInterceptor(headersCapture, trailersCapture)) + .build()); + + Iterator response = + ClientCalls.blockingServerStreamingCall( + channel, echoServerStreamingMethod, CallOptions.DEFAULT, "hello"); + assertThat(response.hasNext()).isFalse(); + assertThat(headersCapture.get() == null || !headersCapture.get().containsKey(FOO_KEY)).isTrue(); + assertThat(trailersCapture.get().get(FOO_KEY)).isEqualTo("foo-value"); + } + + @Test + public void shouldAttachTrailersToErrorResponse() throws IOException { + Metadata extras = new Metadata(); + extras.put(FOO_KEY, "foo-value"); + + ServerServiceDefinition serviceDef = + ServerInterceptors.intercept( + ServerServiceDefinition.builder("test") + .addMethod( + echoMethod, + ServerCalls.asyncUnaryCall( + (req, respObserver) -> + respObserver.onError(Status.INVALID_ARGUMENT.asRuntimeException()))) + .build(), + ImmutableList.of(newAttachMetadataServerInterceptor(extras))); + grpcCleanup.register(newInProcessServerBuilder().addService(serviceDef).build().start()); + + ManagedChannel channel = + grpcCleanup.register( + newInProcessChannelBuilder() + .intercept(newCaptureMetadataInterceptor(headersCapture, trailersCapture)) + .build()); + try { + ClientCalls.blockingUnaryCall(channel, echoMethod, CallOptions.DEFAULT, "hello"); + fail(); + } catch (StatusRuntimeException e) { + assertThat(e.getStatus()).isNotNull(); + assertThat(e.getStatus().getCode()).isEqualTo(Code.INVALID_ARGUMENT); + } + assertThat(headersCapture.get() == null || !headersCapture.get().containsKey(FOO_KEY)).isTrue(); + assertThat(trailersCapture.get().get(FOO_KEY)).isEqualTo("foo-value"); + } + + private static InProcessServerBuilder newInProcessServerBuilder() { + return InProcessServerBuilder.forName(SERVER_NAME).directExecutor(); + } + + private static InProcessChannelBuilder newInProcessChannelBuilder() { + return InProcessChannelBuilder.forName(SERVER_NAME).directExecutor(); + } +} From c120e364d2aea8d42e5b6df4a8dea8c48eca6ebe Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 15 Aug 2024 16:59:27 -0700 Subject: [PATCH 042/103] core: PF Index.size() should be number of addresses This would impact TRANSIENT_FAILURE and refreshNameResolver() logic for dual-stack endpoints. --- .../internal/PickFirstLeafLoadBalancer.java | 16 ++++-- .../PickFirstLeafLoadBalancerTest.java | 53 ++++++++++++++++++- 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java index 253422d3dbd..dae055650d9 100644 --- a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java +++ b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java @@ -208,7 +208,7 @@ public void handleNameResolutionError(Status error) { } subchannels.clear(); if (addressIndex != null) { - addressIndex.updateGroups(null); + addressIndex.updateGroups(ImmutableList.of()); } rawConnectivityState = TRANSIENT_FAILURE; updateBalancingState(TRANSIENT_FAILURE, new Picker(PickResult.withError(error))); @@ -566,11 +566,12 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { @VisibleForTesting static final class Index { private List addressGroups; + private int size; private int groupIndex; private int addressIndex; public Index(List groups) { - this.addressGroups = groups != null ? groups : Collections.emptyList(); + updateGroups(groups); } public boolean isValid() { @@ -629,9 +630,14 @@ public List getCurrentEagAsList() { /** * Update to new groups, resetting the current index. */ - public void updateGroups(ImmutableList newGroups) { - addressGroups = newGroups != null ? newGroups : Collections.emptyList(); + public void updateGroups(List newGroups) { + addressGroups = checkNotNull(newGroups, "newGroups"); reset(); + int size = 0; + for (EquivalentAddressGroup eag : newGroups) { + size += eag.getAddresses().size(); + } + this.size = size; } /** @@ -652,7 +658,7 @@ public boolean seekTo(SocketAddress needle) { } public int size() { - return (addressGroups != null) ? addressGroups.size() : 0; + return size; } } diff --git a/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java b/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java index 335d199d8b1..1ce024366c2 100644 --- a/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java +++ b/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java @@ -662,7 +662,7 @@ public void nameResolutionError_emptyAddressList() { } @Test - public void nameResolutionAfterSufficientTFs() { + public void nameResolutionAfterSufficientTFs_multipleEags() { InOrder inOrder = inOrder(mockHelper); acceptXSubchannels(3); Status error = Status.UNAVAILABLE.withDescription("boom!"); @@ -707,6 +707,57 @@ public void nameResolutionAfterSufficientTFs() { inOrder.verify(mockHelper).refreshNameResolution(); } + @Test + public void nameResolutionAfterSufficientTFs_singleEag() { + InOrder inOrder = inOrder(mockHelper); + EquivalentAddressGroup eag = new EquivalentAddressGroup(Arrays.asList( + new FakeSocketAddress("server1"), + new FakeSocketAddress("server2"), + new FakeSocketAddress("server3"))); + loadBalancer.acceptResolvedAddresses( + ResolvedAddresses.newBuilder().setAddresses(Arrays.asList(eag)).build()); + Status error = Status.UNAVAILABLE.withDescription("boom!"); + + // Initial subchannel gets TF, LB is still in CONNECTING + verify(mockSubchannel1).start(stateListenerCaptor.capture()); + SubchannelStateListener stateListener1 = stateListenerCaptor.getValue(); + stateListener1.onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); + inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), pickerCaptor.capture()); + assertEquals(Status.OK, pickerCaptor.getValue().pickSubchannel(mockArgs).getStatus()); + + // Second subchannel gets TF, no UpdateBalancingState called + verify(mockSubchannel2).start(stateListenerCaptor.capture()); + SubchannelStateListener stateListener2 = stateListenerCaptor.getValue(); + stateListener2.onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); + inOrder.verify(mockHelper, never()).refreshNameResolution(); + inOrder.verify(mockHelper, never()).updateBalancingState(any(), any()); + + // Third subchannel gets TF, LB goes into TRANSIENT_FAILURE and does a refreshNameResolution + verify(mockSubchannel3).start(stateListenerCaptor.capture()); + SubchannelStateListener stateListener3 = stateListenerCaptor.getValue(); + stateListener3.onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); + inOrder.verify(mockHelper).updateBalancingState(eq(TRANSIENT_FAILURE), pickerCaptor.capture()); + inOrder.verify(mockHelper).refreshNameResolution(); + assertEquals(error, pickerCaptor.getValue().pickSubchannel(mockArgs).getStatus()); + + // Only after we have TFs reported for # of subchannels do we call refreshNameResolution + stateListener2.onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); + inOrder.verify(mockHelper, never()).refreshNameResolution(); + stateListener2.onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); + inOrder.verify(mockHelper, never()).refreshNameResolution(); + stateListener2.onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); + inOrder.verify(mockHelper).refreshNameResolution(); + + // Now that we have refreshed, the count should have been reset + // Only after we have TFs reported for # of subchannels do we call refreshNameResolution + stateListener1.onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); + inOrder.verify(mockHelper, never()).refreshNameResolution(); + stateListener2.onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); + inOrder.verify(mockHelper, never()).refreshNameResolution(); + stateListener3.onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); + inOrder.verify(mockHelper).refreshNameResolution(); + } + @Test public void nameResolutionSuccessAfterError() { loadBalancer.handleNameResolutionError(Status.NOT_FOUND.withDescription("nameResolutionError")); From 778a00b6239d9ac3e346ccde1a65a6e1eae2937e Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Mon, 29 Jul 2024 11:40:05 -0700 Subject: [PATCH 043/103] util: Remove MultiChildLB.getImmutableChildMap() No usages actually needed a map nor a copy. --- .../java/io/grpc/util/MultiChildLoadBalancer.java | 6 ------ .../main/java/io/grpc/xds/RingHashLoadBalancer.java | 13 ++++++------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java index 748f58924e1..5dfb81ee4b8 100644 --- a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java +++ b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java @@ -26,7 +26,6 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import io.grpc.Attributes; import io.grpc.ConnectivityState; import io.grpc.EquivalentAddressGroup; @@ -281,11 +280,6 @@ protected final Helper getHelper() { return helper; } - @VisibleForTesting - public final ImmutableMap getImmutableChildMap() { - return ImmutableMap.copyOf(childLbStates); - } - @VisibleForTesting public final Collection getChildLbStates() { return childLbStates.values(); diff --git a/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java b/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java index e2f9a00f25b..74ea9dbb111 100644 --- a/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java @@ -27,7 +27,6 @@ import com.google.common.base.MoreObjects; import com.google.common.collect.HashMultiset; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.Multiset; import com.google.common.primitives.UnsignedInteger; import io.grpc.Attributes; @@ -42,6 +41,7 @@ import io.grpc.xds.client.XdsLogger.XdsLogLevel; import java.net.SocketAddress; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -213,7 +213,7 @@ protected void updateOverallBalancingState() { overallState = TRANSIENT_FAILURE; } - RingHashPicker picker = new RingHashPicker(syncContext, ring, getImmutableChildMap()); + RingHashPicker picker = new RingHashPicker(syncContext, ring, getChildLbStates()); getHelper().updateBalancingState(overallState, picker); this.currentConnectivityState = overallState; } @@ -345,13 +345,12 @@ private static final class RingHashPicker extends SubchannelPicker { private RingHashPicker( SynchronizationContext syncContext, List ring, - ImmutableMap subchannels) { + Collection children) { this.syncContext = syncContext; this.ring = ring; - pickableSubchannels = new HashMap<>(subchannels.size()); - for (Map.Entry entry : subchannels.entrySet()) { - ChildLbState childLbState = entry.getValue(); - pickableSubchannels.put((Endpoint)entry.getKey(), + pickableSubchannels = new HashMap<>(children.size()); + for (ChildLbState childLbState : children) { + pickableSubchannels.put((Endpoint)childLbState.getKey(), new SubchannelView(childLbState, childLbState.getCurrentState())); } } From 8bd97953ad0eaab41f18b09e3892f8b0c962acda Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 15 Aug 2024 17:01:14 -0700 Subject: [PATCH 044/103] core: Never have null PF Index This prevents many null checks and combines two code paths, with no additional allocations. --- .../grpc/internal/PickFirstLeafLoadBalancer.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java index dae055650d9..87c0f9c92ca 100644 --- a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java +++ b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java @@ -61,7 +61,7 @@ final class PickFirstLeafLoadBalancer extends LoadBalancer { static final int CONNECTION_DELAY_INTERVAL_MS = 250; private final Helper helper; private final Map subchannels = new HashMap<>(); - private Index addressIndex; + private final Index addressIndex = new Index(ImmutableList.of()); private int numTf = 0; private boolean firstPass = true; @Nullable @@ -122,9 +122,7 @@ public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { final ImmutableList newImmutableAddressGroups = ImmutableList.builder().addAll(cleanServers).build(); - if (addressIndex == null) { - addressIndex = new Index(newImmutableAddressGroups); - } else if (rawConnectivityState == READY) { + if (rawConnectivityState == READY) { // If the previous ready subchannel exists in new address list, // keep this connection and don't create new subchannels SocketAddress previousAddress = addressIndex.getCurrentAddress(); @@ -207,9 +205,7 @@ public void handleNameResolutionError(Status error) { subchannelData.getSubchannel().shutdown(); } subchannels.clear(); - if (addressIndex != null) { - addressIndex.updateGroups(ImmutableList.of()); - } + addressIndex.updateGroups(ImmutableList.of()); rawConnectivityState = TRANSIENT_FAILURE; updateBalancingState(TRANSIENT_FAILURE, new Picker(PickResult.withError(error))); } @@ -372,7 +368,7 @@ private void shutdownRemaining(SubchannelData activeSubchannelData) { */ @Override public void requestConnection() { - if (addressIndex == null || !addressIndex.isValid() || rawConnectivityState == SHUTDOWN ) { + if (!addressIndex.isValid() || rawConnectivityState == SHUTDOWN) { return; } @@ -477,8 +473,7 @@ private SubchannelData createNewSubchannel(SocketAddress addr, Attributes attrs) } private boolean isPassComplete() { - if (addressIndex == null || addressIndex.isValid() - || subchannels.size() < addressIndex.size()) { + if (addressIndex.isValid() || subchannels.size() < addressIndex.size()) { return false; } for (SubchannelData sc : subchannels.values()) { From 33687d3983cfbcb332b2b53f87a0b87edf14eb60 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Sat, 17 Aug 2024 10:20:14 -0700 Subject: [PATCH 045/103] core: Remove useless NPE check for syncContext in PF It will never throw, because it would only throw if helper is null, but helper is checkNotNull()ed in the constructor. It could have checked for a null return value instead; since it hasn't been, it is clear we don't need this check. --- .../io/grpc/internal/PickFirstLeafLoadBalancer.java | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java index 87c0f9c92ca..5ff8a50953c 100644 --- a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java +++ b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java @@ -33,7 +33,6 @@ import io.grpc.EquivalentAddressGroup; import io.grpc.LoadBalancer; import io.grpc.Status; -import io.grpc.SynchronizationContext; import io.grpc.SynchronizationContext.ScheduledHandle; import java.net.SocketAddress; import java.util.ArrayList; @@ -426,16 +425,7 @@ public void run() { } } - SynchronizationContext synchronizationContext = null; - try { - synchronizationContext = helper.getSynchronizationContext(); - } catch (NullPointerException e) { - // All helpers should have a sync context, but if one doesn't (ex. user had a custom test) - // we don't want to break previously working functionality. - return; - } - - scheduleConnectionTask = synchronizationContext.schedule( + scheduleConnectionTask = helper.getSynchronizationContext().schedule( new StartNextConnection(), CONNECTION_DELAY_INTERVAL_MS, TimeUnit.MILLISECONDS, From 4914ffc59aef537a4ca4f855ebf160a141a72604 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Sat, 17 Aug 2024 10:26:03 -0700 Subject: [PATCH 046/103] core: Avoid exception handling in PF for invalid index It is trivial to avoid the exception from addressIndex.getCurrentAddress(). The log message was inaccurate, as the subchannel might have been TRANSIENT_FAILURE. The only important part of the condition was whether the subchannel was the current subchannel. --- .../java/io/grpc/internal/PickFirstLeafLoadBalancer.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java index 5ff8a50953c..f12100dcf0a 100644 --- a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java +++ b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java @@ -482,12 +482,9 @@ public void onSubchannelState(ConnectivityStateInfo newState) { log.log(Level.FINE, "Received health status {0} for subchannel {1}", new Object[]{newState, subchannelData.subchannel}); subchannelData.healthStateInfo = newState; - try { - if (subchannelData == subchannels.get(addressIndex.getCurrentAddress())) { - updateHealthCheckedState(subchannelData); - } - } catch (IllegalStateException e) { - log.fine("Health listener received state change after subchannel was removed"); + if (addressIndex.isValid() + && subchannelData == subchannels.get(addressIndex.getCurrentAddress())) { + updateHealthCheckedState(subchannelData); } } } From 2c93791c985eab46ab7c10d93980ead0f90e55db Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Sat, 17 Aug 2024 10:49:10 -0700 Subject: [PATCH 047/103] core: PF.requestConnection() is possible when READY requestConnection() is public API, and it is allowed to be called even if the load balancer is already READY. --- .../java/io/grpc/internal/PickFirstLeafLoadBalancer.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java index f12100dcf0a..e77afccfb60 100644 --- a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java +++ b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java @@ -395,12 +395,8 @@ public void requestConnection() { addressIndex.increment(); requestConnection(); break; - case READY: // Shouldn't ever happen - log.warning("Requesting a connection even though we have a READY subchannel"); - break; - case SHUTDOWN: default: - // Makes checkstyle happy + // Wait for current subchannel to change state } } From 82a8d573969690423d442fd9c85ebd6642a10d80 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Sat, 17 Aug 2024 10:50:06 -0700 Subject: [PATCH 048/103] core: In PF, remove useless requestConnection for CONNECTING subchannel It doesn't do anything. Call scheduleNextConnection() unconditionally since it is responsible for checking if `enableHappyEyeballs == true`. It's also surprising to check in the CONNECTING case but not the IDLE case. --- .../grpc/internal/PickFirstLeafLoadBalancer.java | 6 +----- .../internal/PickFirstLeafLoadBalancerTest.java | 15 +++------------ 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java index e77afccfb60..815255ba6a7 100644 --- a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java +++ b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java @@ -385,11 +385,7 @@ public void requestConnection() { scheduleNextConnection(); break; case CONNECTING: - if (enableHappyEyeballs) { - scheduleNextConnection(); - } else { - subchannelData.subchannel.requestConnection(); - } + scheduleNextConnection(); break; case TRANSIENT_FAILURE: addressIndex.increment(); diff --git a/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java b/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java index 1ce024366c2..a6e3f99ab32 100644 --- a/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java +++ b/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java @@ -361,11 +361,7 @@ public void pickAfterResolvedAndUnchanged() { // Second acceptResolvedAddresses shouldn't do anything loadBalancer.acceptResolvedAddresses( ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(affinity).build()); - if (enableHappyEyeballs) { - inOrder.verify(mockSubchannel1, never()).requestConnection(); - } else { - inOrder.verify(mockSubchannel1, times(1)).requestConnection(); - } + inOrder.verify(mockSubchannel1, never()).requestConnection(); inOrder.verify(mockHelper, never()).updateBalancingState(any(), any()); } @@ -862,8 +858,7 @@ public void requestConnection() { loadBalancer.requestConnection(); inOrder.verify(mockSubchannel2).start(stateListenerCaptor.capture()); SubchannelStateListener stateListener2 = stateListenerCaptor.getValue(); - int expectedRequests = enableHappyEyeballs ? 1 : 2; - inOrder.verify(mockSubchannel2, times(expectedRequests)).requestConnection(); + inOrder.verify(mockSubchannel2).requestConnection(); stateListener2.onSubchannelState(ConnectivityStateInfo.forNonError(CONNECTING)); @@ -871,11 +866,7 @@ public void requestConnection() { loadBalancer.requestConnection(); inOrder.verify(mockHelper, never()).updateBalancingState(any(), any()); inOrder.verify(mockSubchannel1, never()).requestConnection(); - if (enableHappyEyeballs) { - inOrder.verify(mockSubchannel2, never()).requestConnection(); - } else { - inOrder.verify(mockSubchannel2).requestConnection(); - } + inOrder.verify(mockSubchannel2, never()).requestConnection(); } @Test From 9762945f813da20e4a49075cd010fa4959955f2d Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Sat, 17 Aug 2024 10:58:29 -0700 Subject: [PATCH 049/103] core: In PF, remove extraneous index.reset() The index was just reset by updateGroups(). --- .../main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java index 815255ba6a7..8d9c5aa8a1c 100644 --- a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java +++ b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java @@ -130,9 +130,8 @@ public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { SubchannelData subchannelData = subchannels.get(previousAddress); subchannelData.getSubchannel().updateAddresses(addressIndex.getCurrentEagAsList()); return Status.OK; - } else { - addressIndex.reset(); // Previous ready subchannel not in the new list of addresses } + // Previous ready subchannel not in the new list of addresses } else { addressIndex.updateGroups(newImmutableAddressGroups); } From 66075eab85d85095bd396fc747d218c62e105f73 Mon Sep 17 00:00:00 2001 From: Eng Zer Jun Date: Thu, 22 Aug 2024 14:31:02 +0800 Subject: [PATCH 050/103] .github/workflows: Bump action major versions from Node16 to Node20 (#11476) GitHub began the Node16 deprecation process a year ago [1][2]. This commit updates all workflows to use the latest Node20 actions. [1]: https://github.blog/changelog/2023-09-22-github-actions-transitioning-from-node-16-to-node-20/ [2]: https://github.blog/changelog/2024-03-07-github-actions-all-actions-will-run-on-node20-instead-of-node16-by-default/ --- .../workflows/gradle-wrapper-validation.yml | 4 ++-- .github/workflows/lock.yml | 2 +- .github/workflows/testing.yml | 20 ++++++++++--------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index b827468d719..da1e2fed114 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -9,5 +9,5 @@ jobs: name: "Gradle wrapper validation" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: gradle/wrapper-validation-action@v1 + - uses: actions/checkout@v4 + - uses: gradle/actions/wrapper-validation@v4 diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index 907a9dad2b5..3070a1a2f7c 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -13,7 +13,7 @@ jobs: lock: runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v4 + - uses: dessant/lock-threads@v5 with: github-token: ${{ github.token }} issue-inactive-days: 90 diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index bc5a175906f..8c639cf14ed 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -21,14 +21,14 @@ jobs: fail-fast: false # Should swap to true if we grow a large matrix steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 with: java-version: ${{ matrix.jre }} distribution: 'temurin' - name: Gradle cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.gradle/caches @@ -37,7 +37,7 @@ jobs: restore-keys: | ${{ runner.os }}-gradle- - name: Maven cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.m2/repository @@ -46,7 +46,7 @@ jobs: restore-keys: | ${{ runner.os }}-maven- - name: Protobuf cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: /tmp/protobuf-cache key: ${{ runner.os }}-maven-${{ hashFiles('buildscripts/make_dependencies.sh') }} @@ -55,7 +55,7 @@ jobs: run: buildscripts/kokoro/unix.sh - name: Post Failure Upload Test Reports to Artifacts if: ${{ failure() }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Test Reports (JRE ${{ matrix.jre }}) path: | @@ -71,7 +71,9 @@ jobs: COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} run: ./gradlew :grpc-all:coveralls -PskipAndroid=true -x compileJava - name: Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} bazel: runs-on: ubuntu-latest @@ -79,7 +81,7 @@ jobs: USE_BAZEL_VERSION: 6.0.0 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Check versions match in MODULE.bazel and repositories.bzl run: | @@ -87,7 +89,7 @@ jobs: <(sed -n '/GRPC_DEPS_START/,/GRPC_DEPS_END/ {/GRPC_DEPS_/! p}' repositories.bzl) - name: Bazel cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.cache/bazel/*/cache From 2fe1a13cd07c86937d02c508025616c309424c9a Mon Sep 17 00:00:00 2001 From: Kurt Alfred Kluever Date: Thu, 22 Aug 2024 02:41:43 -0400 Subject: [PATCH 051/103] Migrate from `Charsets` to `StandardCharsets`. (#11482) --- netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java | 2 +- netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java | 2 +- netty/src/test/java/io/grpc/netty/NettyHandlerTestBase.java | 2 +- netty/src/test/java/io/grpc/netty/NettyReadableBufferTest.java | 2 +- netty/src/test/java/io/grpc/netty/NettyServerHandlerTest.java | 2 +- netty/src/test/java/io/grpc/netty/NettyStreamTestBase.java | 2 +- netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java b/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java index bbe9fab9233..73988f773cb 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java @@ -16,7 +16,6 @@ package io.grpc.netty; -import static com.google.common.base.Charsets.UTF_8; import static com.google.common.truth.Truth.assertThat; import static io.grpc.internal.ClientStreamListener.RpcProgress.MISCARRIED; import static io.grpc.internal.ClientStreamListener.RpcProgress.PROCESSED; @@ -30,6 +29,7 @@ import static io.grpc.netty.Utils.TE_HEADER; import static io.grpc.netty.Utils.TE_TRAILERS; import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java index b40cd9d5607..9777bb0926c 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java @@ -16,7 +16,6 @@ package io.grpc.netty; -import static com.google.common.base.Charsets.UTF_8; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.TruthJUnit.assume; import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE; @@ -29,6 +28,7 @@ import static io.grpc.netty.NettyServerBuilder.MAX_CONNECTION_IDLE_NANOS_DISABLED; import static io.grpc.netty.NettyServerBuilder.MAX_RST_COUNT_DISABLED; import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_WINDOW_SIZE; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; diff --git a/netty/src/test/java/io/grpc/netty/NettyHandlerTestBase.java b/netty/src/test/java/io/grpc/netty/NettyHandlerTestBase.java index fbab1ca5fae..eef8d30e05a 100644 --- a/netty/src/test/java/io/grpc/netty/NettyHandlerTestBase.java +++ b/netty/src/test/java/io/grpc/netty/NettyHandlerTestBase.java @@ -16,8 +16,8 @@ package io.grpc.netty; -import static com.google.common.base.Charsets.UTF_8; import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_WINDOW_SIZE; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.mockito.AdditionalAnswers.delegatesTo; import static org.mockito.ArgumentMatchers.any; diff --git a/netty/src/test/java/io/grpc/netty/NettyReadableBufferTest.java b/netty/src/test/java/io/grpc/netty/NettyReadableBufferTest.java index 1a0ac229a89..4f10504c07d 100644 --- a/netty/src/test/java/io/grpc/netty/NettyReadableBufferTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyReadableBufferTest.java @@ -16,7 +16,7 @@ package io.grpc.netty; -import static com.google.common.base.Charsets.UTF_8; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; diff --git a/netty/src/test/java/io/grpc/netty/NettyServerHandlerTest.java b/netty/src/test/java/io/grpc/netty/NettyServerHandlerTest.java index ce902a9620b..541490847c0 100644 --- a/netty/src/test/java/io/grpc/netty/NettyServerHandlerTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyServerHandlerTest.java @@ -16,7 +16,6 @@ package io.grpc.netty; -import static com.google.common.base.Charsets.UTF_8; import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE; import static io.grpc.internal.GrpcUtil.DEFAULT_SERVER_KEEPALIVE_TIMEOUT_NANOS; import static io.grpc.internal.GrpcUtil.DEFAULT_SERVER_KEEPALIVE_TIME_NANOS; @@ -29,6 +28,7 @@ import static io.grpc.netty.Utils.HTTP_METHOD; import static io.grpc.netty.Utils.TE_HEADER; import static io.grpc.netty.Utils.TE_TRAILERS; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/netty/src/test/java/io/grpc/netty/NettyStreamTestBase.java b/netty/src/test/java/io/grpc/netty/NettyStreamTestBase.java index f073fb6b2e4..ce42e3d25df 100644 --- a/netty/src/test/java/io/grpc/netty/NettyStreamTestBase.java +++ b/netty/src/test/java/io/grpc/netty/NettyStreamTestBase.java @@ -16,8 +16,8 @@ package io.grpc.netty; -import static com.google.common.base.Charsets.US_ASCII; import static io.grpc.netty.NettyTestUtil.messageFrame; +import static java.nio.charset.StandardCharsets.US_ASCII; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; diff --git a/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java b/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java index 1852213da52..6939d835892 100644 --- a/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java +++ b/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java @@ -16,10 +16,10 @@ package io.grpc.netty; -import static com.google.common.base.Charsets.UTF_8; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; From 6c9f92a725e23754b8855ac909ed5aa34688ab88 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Tue, 2 Apr 2024 11:41:04 -0700 Subject: [PATCH 052/103] interop-testing: Move interop tests only used by test client to the test client This removes the auth dependency and the implicit xds/orca from AbstractInteropTest for things that weren't used in all but one case. --- android-interop-testing/build.gradle | 5 - .../integration/AbstractInteropTest.java | 320 +--------------- ...tomBackendMetricsLoadBalancerProvider.java | 4 +- .../integration/TestServiceClient.java | 343 ++++++++++++++++++ 4 files changed, 346 insertions(+), 326 deletions(-) diff --git a/android-interop-testing/build.gradle b/android-interop-testing/build.gradle index 22aa5f2288d..9b3b021afce 100644 --- a/android-interop-testing/build.gradle +++ b/android-interop-testing/build.gradle @@ -69,7 +69,6 @@ dependencies { implementation project(':grpc-android'), project(':grpc-core'), - project(':grpc-auth'), project(':grpc-census'), project(':grpc-okhttp'), project(':grpc-protobuf-lite'), @@ -81,10 +80,6 @@ dependencies { libraries.androidx.test.rules, libraries.opencensus.contrib.grpc.metrics - implementation (libraries.google.auth.oauth2Http) { - exclude group: 'org.apache.httpcomponents' - } - implementation (project(':grpc-services')) { exclude group: 'com.google.protobuf' exclude group: 'com.google.guava' diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java b/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java index 3efd576abe6..a581c750028 100644 --- a/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java +++ b/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java @@ -21,18 +21,12 @@ import static io.grpc.stub.ClientCalls.blockingServerStreamingCall; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import com.google.auth.oauth2.AccessToken; -import com.google.auth.oauth2.ComputeEngineCredentials; -import com.google.auth.oauth2.GoogleCredentials; -import com.google.auth.oauth2.OAuth2Credentials; -import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; @@ -45,7 +39,6 @@ import io.grpc.Channel; import io.grpc.ClientCall; import io.grpc.ClientInterceptor; -import io.grpc.ClientInterceptors; import io.grpc.ClientStreamTracer; import io.grpc.Context; import io.grpc.Grpc; @@ -62,7 +55,6 @@ import io.grpc.ServerStreamTracer; import io.grpc.Status; import io.grpc.StatusRuntimeException; -import io.grpc.auth.MoreCallCredentials; import io.grpc.census.InternalCensusStatsAccessor; import io.grpc.census.internal.DeprecatedCensusConstants; import io.grpc.internal.GrpcUtil; @@ -77,7 +69,6 @@ import io.grpc.internal.testing.TestServerStreamTracer; import io.grpc.internal.testing.TestStreamTracer; import io.grpc.stub.ClientCallStreamObserver; -import io.grpc.stub.ClientCalls; import io.grpc.stub.MetadataUtils; import io.grpc.stub.StreamObserver; import io.grpc.testing.TestUtils; @@ -92,7 +83,6 @@ import io.grpc.testing.integration.Messages.StreamingInputCallResponse; import io.grpc.testing.integration.Messages.StreamingOutputCallRequest; import io.grpc.testing.integration.Messages.StreamingOutputCallResponse; -import io.grpc.testing.integration.Messages.TestOrcaReport; import io.opencensus.contrib.grpc.metrics.RpcMeasureConstants; import io.opencensus.stats.Measure; import io.opencensus.stats.Measure.MeasureDouble; @@ -118,7 +108,6 @@ import java.util.Locale; import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; @@ -191,11 +180,6 @@ public abstract class AbstractInteropTest { private final LinkedBlockingQueue serverStreamTracers = new LinkedBlockingQueue<>(); - static final CallOptions.Key> - ORCA_RPC_REPORT_KEY = CallOptions.Key.create("orca-rpc-report"); - static final CallOptions.Key> - ORCA_OOB_REPORT_KEY = CallOptions.Key.create("orca-oob-report"); - private static final class ServerStreamTracerInfo { final String fullMethodName; final InteropServerStreamTracer tracer; @@ -451,47 +435,6 @@ public void emptyUnaryWithRetriableStream() throws Exception { assertEquals(EMPTY, TestServiceGrpc.newBlockingStub(channel).emptyCall(EMPTY)); } - /** Sends a cacheable unary rpc using GET. Requires that the server is behind a caching proxy. */ - public void cacheableUnary() { - // THIS TEST IS BROKEN. Enabling safe just on the MethodDescriptor does nothing by itself. This - // test would need to enable GET on the channel. - // Set safe to true. - MethodDescriptor safeCacheableUnaryCallMethod = - TestServiceGrpc.getCacheableUnaryCallMethod().toBuilder().setSafe(true).build(); - // Set fake user IP since some proxies (GFE) won't cache requests from localhost. - Metadata.Key userIpKey = Metadata.Key.of("x-user-ip", Metadata.ASCII_STRING_MARSHALLER); - Metadata metadata = new Metadata(); - metadata.put(userIpKey, "1.2.3.4"); - Channel channelWithUserIpKey = - ClientInterceptors.intercept(channel, MetadataUtils.newAttachHeadersInterceptor(metadata)); - SimpleRequest requests1And2 = - SimpleRequest.newBuilder() - .setPayload( - Payload.newBuilder() - .setBody(ByteString.copyFromUtf8(String.valueOf(System.nanoTime())))) - .build(); - SimpleRequest request3 = - SimpleRequest.newBuilder() - .setPayload( - Payload.newBuilder() - .setBody(ByteString.copyFromUtf8(String.valueOf(System.nanoTime())))) - .build(); - - SimpleResponse response1 = - ClientCalls.blockingUnaryCall( - channelWithUserIpKey, safeCacheableUnaryCallMethod, CallOptions.DEFAULT, requests1And2); - SimpleResponse response2 = - ClientCalls.blockingUnaryCall( - channelWithUserIpKey, safeCacheableUnaryCallMethod, CallOptions.DEFAULT, requests1And2); - SimpleResponse response3 = - ClientCalls.blockingUnaryCall( - channelWithUserIpKey, safeCacheableUnaryCallMethod, CallOptions.DEFAULT, request3); - - assertEquals(response1, response2); - assertNotEquals(response1, response3); - // THIS TEST IS BROKEN. See comment at start of method. - } - @Test public void largeUnary() throws Exception { assumeEnoughMemory(); @@ -603,26 +546,6 @@ public void serverCompressedUnary() throws Exception { Collections.singleton(goldenResponse)); } - /** - * Assuming "pick_first" policy is used, tests that all requests are sent to the same server. - */ - public void pickFirstUnary() throws Exception { - SimpleRequest request = SimpleRequest.newBuilder() - .setResponseSize(1) - .setFillServerId(true) - .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[1]))) - .build(); - - SimpleResponse firstResponse = blockingStub.unaryCall(request); - // Increase the chance of all servers are connected, in case the channel should be doing - // round_robin instead. - Thread.sleep(5000); - for (int i = 0; i < 100; i++) { - SimpleResponse response = blockingStub.unaryCall(request); - assertThat(response.getServerId()).isEqualTo(firstResponse.getServerId()); - } - } - @Test public void serverStreaming() throws Exception { final StreamingOutputCallRequest request = StreamingOutputCallRequest.newBuilder() @@ -1757,247 +1680,6 @@ public void getServerAddressAndLocalAddressFromClient() { assertNotNull(obtainLocalClientAddr()); } - /** - * Test backend metrics per query reporting: expect the test client LB policy to receive load - * reports. - */ - public void testOrcaPerRpc() throws Exception { - AtomicReference reportHolder = new AtomicReference<>(); - TestOrcaReport answer = TestOrcaReport.newBuilder() - .setCpuUtilization(0.8210) - .setMemoryUtilization(0.5847) - .putRequestCost("cost", 3456.32) - .putUtilization("util", 0.30499) - .build(); - blockingStub.withOption(ORCA_RPC_REPORT_KEY, reportHolder).unaryCall( - SimpleRequest.newBuilder().setOrcaPerQueryReport(answer).build()); - assertThat(reportHolder.get()).isEqualTo(answer); - } - - /** - * Test backend metrics OOB reporting: expect the test client LB policy to receive load reports. - */ - public void testOrcaOob() throws Exception { - AtomicReference reportHolder = new AtomicReference<>(); - final TestOrcaReport answer = TestOrcaReport.newBuilder() - .setCpuUtilization(0.8210) - .setMemoryUtilization(0.5847) - .putUtilization("util", 0.30499) - .build(); - final TestOrcaReport answer2 = TestOrcaReport.newBuilder() - .setCpuUtilization(0.29309) - .setMemoryUtilization(0.2) - .putUtilization("util", 0.2039) - .build(); - - final int retryLimit = 5; - BlockingQueue queue = new LinkedBlockingQueue<>(); - final Object lastItem = new Object(); - StreamObserver streamObserver = - asyncStub.fullDuplexCall(new StreamObserver() { - - @Override - public void onNext(StreamingOutputCallResponse value) { - queue.add(value); - } - - @Override - public void onError(Throwable t) { - queue.add(t); - } - - @Override - public void onCompleted() { - queue.add(lastItem); - } - }); - - streamObserver.onNext(StreamingOutputCallRequest.newBuilder() - .setOrcaOobReport(answer) - .addResponseParameters(ResponseParameters.newBuilder().setSize(1).build()).build()); - assertThat(queue.take()).isInstanceOf(StreamingOutputCallResponse.class); - int i = 0; - for (; i < retryLimit; i++) { - Thread.sleep(1000); - blockingStub.withOption(ORCA_OOB_REPORT_KEY, reportHolder).emptyCall(EMPTY); - if (answer.equals(reportHolder.get())) { - break; - } - } - assertThat(i).isLessThan(retryLimit); - streamObserver.onNext(StreamingOutputCallRequest.newBuilder() - .setOrcaOobReport(answer2) - .addResponseParameters(ResponseParameters.newBuilder().setSize(1).build()).build()); - assertThat(queue.take()).isInstanceOf(StreamingOutputCallResponse.class); - - for (i = 0; i < retryLimit; i++) { - Thread.sleep(1000); - blockingStub.withOption(ORCA_OOB_REPORT_KEY, reportHolder).emptyCall(EMPTY); - if (reportHolder.get().equals(answer2)) { - break; - } - } - assertThat(i).isLessThan(retryLimit); - streamObserver.onCompleted(); - assertThat(queue.take()).isSameInstanceAs(lastItem); - } - - /** Sends a large unary rpc with service account credentials. */ - public void serviceAccountCreds(String jsonKey, InputStream credentialsStream, String authScope) - throws Exception { - // cast to ServiceAccountCredentials to double-check the right type of object was created. - GoogleCredentials credentials = - ServiceAccountCredentials.class.cast(GoogleCredentials.fromStream(credentialsStream)); - credentials = credentials.createScoped(Arrays.asList(authScope)); - TestServiceGrpc.TestServiceBlockingStub stub = blockingStub - .withCallCredentials(MoreCallCredentials.from(credentials)); - final SimpleRequest request = SimpleRequest.newBuilder() - .setFillUsername(true) - .setFillOauthScope(true) - .setResponseSize(314159) - .setPayload(Payload.newBuilder() - .setBody(ByteString.copyFrom(new byte[271828]))) - .build(); - - final SimpleResponse response = stub.unaryCall(request); - assertFalse(response.getUsername().isEmpty()); - assertTrue("Received username: " + response.getUsername(), - jsonKey.contains(response.getUsername())); - assertFalse(response.getOauthScope().isEmpty()); - assertTrue("Received oauth scope: " + response.getOauthScope(), - authScope.contains(response.getOauthScope())); - - final SimpleResponse goldenResponse = SimpleResponse.newBuilder() - .setOauthScope(response.getOauthScope()) - .setUsername(response.getUsername()) - .setPayload(Payload.newBuilder() - .setBody(ByteString.copyFrom(new byte[314159]))) - .build(); - assertResponse(goldenResponse, response); - } - - /** Sends a large unary rpc with compute engine credentials. */ - public void computeEngineCreds(String serviceAccount, String oauthScope) throws Exception { - ComputeEngineCredentials credentials = ComputeEngineCredentials.create(); - TestServiceGrpc.TestServiceBlockingStub stub = blockingStub - .withCallCredentials(MoreCallCredentials.from(credentials)); - final SimpleRequest request = SimpleRequest.newBuilder() - .setFillUsername(true) - .setFillOauthScope(true) - .setResponseSize(314159) - .setPayload(Payload.newBuilder() - .setBody(ByteString.copyFrom(new byte[271828]))) - .build(); - - final SimpleResponse response = stub.unaryCall(request); - assertEquals(serviceAccount, response.getUsername()); - assertFalse(response.getOauthScope().isEmpty()); - assertTrue("Received oauth scope: " + response.getOauthScope(), - oauthScope.contains(response.getOauthScope())); - - final SimpleResponse goldenResponse = SimpleResponse.newBuilder() - .setOauthScope(response.getOauthScope()) - .setUsername(response.getUsername()) - .setPayload(Payload.newBuilder() - .setBody(ByteString.copyFrom(new byte[314159]))) - .build(); - assertResponse(goldenResponse, response); - } - - /** Sends an unary rpc with ComputeEngineChannelBuilder. */ - public void computeEngineChannelCredentials( - String defaultServiceAccount, - TestServiceGrpc.TestServiceBlockingStub computeEngineStub) throws Exception { - final SimpleRequest request = SimpleRequest.newBuilder() - .setFillUsername(true) - .setResponseSize(314159) - .setPayload(Payload.newBuilder() - .setBody(ByteString.copyFrom(new byte[271828]))) - .build(); - final SimpleResponse response = computeEngineStub.unaryCall(request); - assertEquals(defaultServiceAccount, response.getUsername()); - final SimpleResponse goldenResponse = SimpleResponse.newBuilder() - .setUsername(defaultServiceAccount) - .setPayload(Payload.newBuilder() - .setBody(ByteString.copyFrom(new byte[314159]))) - .build(); - assertResponse(goldenResponse, response); - } - - /** Test JWT-based auth. */ - public void jwtTokenCreds(InputStream serviceAccountJson) throws Exception { - final SimpleRequest request = SimpleRequest.newBuilder() - .setResponseSize(314159) - .setPayload(Payload.newBuilder() - .setBody(ByteString.copyFrom(new byte[271828]))) - .setFillUsername(true) - .build(); - - ServiceAccountCredentials credentials = (ServiceAccountCredentials) - GoogleCredentials.fromStream(serviceAccountJson); - TestServiceGrpc.TestServiceBlockingStub stub = blockingStub - .withCallCredentials(MoreCallCredentials.from(credentials)); - SimpleResponse response = stub.unaryCall(request); - assertEquals(credentials.getClientEmail(), response.getUsername()); - assertEquals(314159, response.getPayload().getBody().size()); - } - - /** Sends a unary rpc with raw oauth2 access token credentials. */ - public void oauth2AuthToken(String jsonKey, InputStream credentialsStream, String authScope) - throws Exception { - GoogleCredentials utilCredentials = - GoogleCredentials.fromStream(credentialsStream); - utilCredentials = utilCredentials.createScoped(Arrays.asList(authScope)); - AccessToken accessToken = utilCredentials.refreshAccessToken(); - - OAuth2Credentials credentials = OAuth2Credentials.create(accessToken); - - TestServiceGrpc.TestServiceBlockingStub stub = blockingStub - .withCallCredentials(MoreCallCredentials.from(credentials)); - final SimpleRequest request = SimpleRequest.newBuilder() - .setFillUsername(true) - .setFillOauthScope(true) - .build(); - - final SimpleResponse response = stub.unaryCall(request); - assertFalse(response.getUsername().isEmpty()); - assertTrue("Received username: " + response.getUsername(), - jsonKey.contains(response.getUsername())); - assertFalse(response.getOauthScope().isEmpty()); - assertTrue("Received oauth scope: " + response.getOauthScope(), - authScope.contains(response.getOauthScope())); - } - - /** Sends a unary rpc with "per rpc" raw oauth2 access token credentials. */ - public void perRpcCreds(String jsonKey, InputStream credentialsStream, String oauthScope) - throws Exception { - // In gRpc Java, we don't have per Rpc credentials, user can use an intercepted stub only once - // for that purpose. - // So, this test is identical to oauth2_auth_token test. - oauth2AuthToken(jsonKey, credentialsStream, oauthScope); - } - - /** Sends an unary rpc with "google default credentials". */ - public void googleDefaultCredentials( - String defaultServiceAccount, - TestServiceGrpc.TestServiceBlockingStub googleDefaultStub) throws Exception { - final SimpleRequest request = SimpleRequest.newBuilder() - .setFillUsername(true) - .setResponseSize(314159) - .setPayload(Payload.newBuilder() - .setBody(ByteString.copyFrom(new byte[271828]))) - .build(); - final SimpleResponse response = googleDefaultStub.unaryCall(request); - assertEquals(defaultServiceAccount, response.getUsername()); - - final SimpleResponse goldenResponse = SimpleResponse.newBuilder() - .setUsername(defaultServiceAccount) - .setPayload(Payload.newBuilder() - .setBody(ByteString.copyFrom(new byte[314159]))) - .build(); - assertResponse(goldenResponse, response); - } - private static class SoakIterationResult { public SoakIterationResult(long latencyMs, Status status) { this.latencyMs = latencyMs; @@ -2481,7 +2163,7 @@ private void assertResponse( } } - private void assertResponse(SimpleResponse expected, SimpleResponse actual) { + public void assertResponse(SimpleResponse expected, SimpleResponse actual) { assertPayload(expected.getPayload(), actual.getPayload()); assertEquals(expected.getUsername(), actual.getUsername()); assertEquals(expected.getOauthScope(), actual.getOauthScope()); diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/CustomBackendMetricsLoadBalancerProvider.java b/interop-testing/src/main/java/io/grpc/testing/integration/CustomBackendMetricsLoadBalancerProvider.java index 87ecf308674..b9a89a01e3a 100644 --- a/interop-testing/src/main/java/io/grpc/testing/integration/CustomBackendMetricsLoadBalancerProvider.java +++ b/interop-testing/src/main/java/io/grpc/testing/integration/CustomBackendMetricsLoadBalancerProvider.java @@ -16,8 +16,8 @@ package io.grpc.testing.integration; -import static io.grpc.testing.integration.AbstractInteropTest.ORCA_OOB_REPORT_KEY; -import static io.grpc.testing.integration.AbstractInteropTest.ORCA_RPC_REPORT_KEY; +import static io.grpc.testing.integration.TestServiceClient.ORCA_OOB_REPORT_KEY; +import static io.grpc.testing.integration.TestServiceClient.ORCA_RPC_REPORT_KEY; import io.grpc.ConnectivityState; import io.grpc.LoadBalancer; diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceClient.java b/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceClient.java index 0c8f697ada5..e6829be11cb 100644 --- a/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceClient.java +++ b/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceClient.java @@ -16,10 +16,25 @@ package io.grpc.testing.integration; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import com.google.auth.oauth2.AccessToken; +import com.google.auth.oauth2.ComputeEngineCredentials; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.auth.oauth2.OAuth2Credentials; +import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.common.annotations.VisibleForTesting; import com.google.common.io.Files; +import com.google.protobuf.ByteString; +import io.grpc.CallOptions; +import io.grpc.Channel; import io.grpc.ChannelCredentials; import io.grpc.ClientInterceptor; +import io.grpc.ClientInterceptors; import io.grpc.Grpc; import io.grpc.InsecureChannelCredentials; import io.grpc.InsecureServerCredentials; @@ -28,11 +43,13 @@ import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.Metadata; +import io.grpc.MethodDescriptor; import io.grpc.ServerBuilder; import io.grpc.TlsChannelCredentials; import io.grpc.alts.AltsChannelCredentials; import io.grpc.alts.ComputeEngineChannelCredentials; import io.grpc.alts.GoogleDefaultChannelCredentials; +import io.grpc.auth.MoreCallCredentials; import io.grpc.internal.GrpcUtil; import io.grpc.internal.JsonParser; import io.grpc.netty.InsecureFromHttp1ChannelCredentials; @@ -40,13 +57,27 @@ import io.grpc.netty.NettyChannelBuilder; import io.grpc.okhttp.InternalOkHttpChannelBuilder; import io.grpc.okhttp.OkHttpChannelBuilder; +import io.grpc.stub.ClientCalls; import io.grpc.stub.MetadataUtils; +import io.grpc.stub.StreamObserver; import io.grpc.testing.TlsTesting; +import io.grpc.testing.integration.Messages.Payload; +import io.grpc.testing.integration.Messages.ResponseParameters; +import io.grpc.testing.integration.Messages.SimpleRequest; +import io.grpc.testing.integration.Messages.SimpleResponse; +import io.grpc.testing.integration.Messages.StreamingOutputCallRequest; +import io.grpc.testing.integration.Messages.StreamingOutputCallResponse; +import io.grpc.testing.integration.Messages.TestOrcaReport; import java.io.File; import java.io.FileInputStream; +import java.io.InputStream; import java.nio.charset.Charset; +import java.util.Arrays; import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nullable; /** @@ -57,6 +88,11 @@ public class TestServiceClient { private static final Charset UTF_8 = Charset.forName("UTF-8"); + static final CallOptions.Key> + ORCA_RPC_REPORT_KEY = CallOptions.Key.create("orca-rpc-report"); + static final CallOptions.Key> + ORCA_OOB_REPORT_KEY = CallOptions.Key.create("orca-oob-report"); + /** * The main application allowing this client to be launched from the command line. */ @@ -668,6 +704,313 @@ protected ManagedChannelBuilder createChannelBuilder() { return okBuilder.intercept(createCensusStatsClientInterceptor()); } + /** + * Assuming "pick_first" policy is used, tests that all requests are sent to the same server. + */ + public void pickFirstUnary() throws Exception { + SimpleRequest request = SimpleRequest.newBuilder() + .setResponseSize(1) + .setFillServerId(true) + .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[1]))) + .build(); + + SimpleResponse firstResponse = blockingStub.unaryCall(request); + // Increase the chance of all servers are connected, in case the channel should be doing + // round_robin instead. + Thread.sleep(5000); + for (int i = 0; i < 100; i++) { + SimpleResponse response = blockingStub.unaryCall(request); + assertThat(response.getServerId()).isEqualTo(firstResponse.getServerId()); + } + } + + /** + * Sends a cacheable unary rpc using GET. Requires that the server is behind a caching proxy. + */ + public void cacheableUnary() { + // THIS TEST IS BROKEN. Enabling safe just on the MethodDescriptor does nothing by itself. + // This test would need to enable GET on the channel. + // Set safe to true. + MethodDescriptor safeCacheableUnaryCallMethod = + TestServiceGrpc.getCacheableUnaryCallMethod().toBuilder().setSafe(true).build(); + // Set fake user IP since some proxies (GFE) won't cache requests from localhost. + Metadata.Key userIpKey = + Metadata.Key.of("x-user-ip", Metadata.ASCII_STRING_MARSHALLER); + Metadata metadata = new Metadata(); + metadata.put(userIpKey, "1.2.3.4"); + Channel channelWithUserIpKey = ClientInterceptors.intercept( + channel, MetadataUtils.newAttachHeadersInterceptor(metadata)); + SimpleRequest requests1And2 = + SimpleRequest.newBuilder() + .setPayload( + Payload.newBuilder() + .setBody(ByteString.copyFromUtf8(String.valueOf(System.nanoTime())))) + .build(); + SimpleRequest request3 = + SimpleRequest.newBuilder() + .setPayload( + Payload.newBuilder() + .setBody(ByteString.copyFromUtf8(String.valueOf(System.nanoTime())))) + .build(); + + SimpleResponse response1 = + ClientCalls.blockingUnaryCall( + channelWithUserIpKey, safeCacheableUnaryCallMethod, CallOptions.DEFAULT, + requests1And2); + SimpleResponse response2 = + ClientCalls.blockingUnaryCall( + channelWithUserIpKey, safeCacheableUnaryCallMethod, CallOptions.DEFAULT, + requests1And2); + SimpleResponse response3 = + ClientCalls.blockingUnaryCall( + channelWithUserIpKey, safeCacheableUnaryCallMethod, CallOptions.DEFAULT, request3); + + assertEquals(response1, response2); + assertNotEquals(response1, response3); + // THIS TEST IS BROKEN. See comment at start of method. + } + + /** Sends a large unary rpc with service account credentials. */ + public void serviceAccountCreds(String jsonKey, InputStream credentialsStream, String authScope) + throws Exception { + // cast to ServiceAccountCredentials to double-check the right type of object was created. + GoogleCredentials credentials = + ServiceAccountCredentials.class.cast(GoogleCredentials.fromStream(credentialsStream)); + credentials = credentials.createScoped(Arrays.asList(authScope)); + TestServiceGrpc.TestServiceBlockingStub stub = blockingStub + .withCallCredentials(MoreCallCredentials.from(credentials)); + final SimpleRequest request = SimpleRequest.newBuilder() + .setFillUsername(true) + .setFillOauthScope(true) + .setResponseSize(314159) + .setPayload(Payload.newBuilder() + .setBody(ByteString.copyFrom(new byte[271828]))) + .build(); + + final SimpleResponse response = stub.unaryCall(request); + assertFalse(response.getUsername().isEmpty()); + assertTrue("Received username: " + response.getUsername(), + jsonKey.contains(response.getUsername())); + assertFalse(response.getOauthScope().isEmpty()); + assertTrue("Received oauth scope: " + response.getOauthScope(), + authScope.contains(response.getOauthScope())); + + final SimpleResponse goldenResponse = SimpleResponse.newBuilder() + .setOauthScope(response.getOauthScope()) + .setUsername(response.getUsername()) + .setPayload(Payload.newBuilder() + .setBody(ByteString.copyFrom(new byte[314159]))) + .build(); + assertResponse(goldenResponse, response); + } + + /** Sends a large unary rpc with compute engine credentials. */ + public void computeEngineCreds(String serviceAccount, String oauthScope) throws Exception { + ComputeEngineCredentials credentials = ComputeEngineCredentials.create(); + TestServiceGrpc.TestServiceBlockingStub stub = blockingStub + .withCallCredentials(MoreCallCredentials.from(credentials)); + final SimpleRequest request = SimpleRequest.newBuilder() + .setFillUsername(true) + .setFillOauthScope(true) + .setResponseSize(314159) + .setPayload(Payload.newBuilder() + .setBody(ByteString.copyFrom(new byte[271828]))) + .build(); + + final SimpleResponse response = stub.unaryCall(request); + assertEquals(serviceAccount, response.getUsername()); + assertFalse(response.getOauthScope().isEmpty()); + assertTrue("Received oauth scope: " + response.getOauthScope(), + oauthScope.contains(response.getOauthScope())); + + final SimpleResponse goldenResponse = SimpleResponse.newBuilder() + .setOauthScope(response.getOauthScope()) + .setUsername(response.getUsername()) + .setPayload(Payload.newBuilder() + .setBody(ByteString.copyFrom(new byte[314159]))) + .build(); + assertResponse(goldenResponse, response); + } + + /** Sends an unary rpc with ComputeEngineChannelBuilder. */ + public void computeEngineChannelCredentials( + String defaultServiceAccount, + TestServiceGrpc.TestServiceBlockingStub computeEngineStub) throws Exception { + final SimpleRequest request = SimpleRequest.newBuilder() + .setFillUsername(true) + .setResponseSize(314159) + .setPayload(Payload.newBuilder() + .setBody(ByteString.copyFrom(new byte[271828]))) + .build(); + final SimpleResponse response = computeEngineStub.unaryCall(request); + assertEquals(defaultServiceAccount, response.getUsername()); + final SimpleResponse goldenResponse = SimpleResponse.newBuilder() + .setUsername(defaultServiceAccount) + .setPayload(Payload.newBuilder() + .setBody(ByteString.copyFrom(new byte[314159]))) + .build(); + assertResponse(goldenResponse, response); + } + + /** Test JWT-based auth. */ + public void jwtTokenCreds(InputStream serviceAccountJson) throws Exception { + final SimpleRequest request = SimpleRequest.newBuilder() + .setResponseSize(314159) + .setPayload(Payload.newBuilder() + .setBody(ByteString.copyFrom(new byte[271828]))) + .setFillUsername(true) + .build(); + + ServiceAccountCredentials credentials = (ServiceAccountCredentials) + GoogleCredentials.fromStream(serviceAccountJson); + TestServiceGrpc.TestServiceBlockingStub stub = blockingStub + .withCallCredentials(MoreCallCredentials.from(credentials)); + SimpleResponse response = stub.unaryCall(request); + assertEquals(credentials.getClientEmail(), response.getUsername()); + assertEquals(314159, response.getPayload().getBody().size()); + } + + /** Sends a unary rpc with raw oauth2 access token credentials. */ + public void oauth2AuthToken(String jsonKey, InputStream credentialsStream, String authScope) + throws Exception { + GoogleCredentials utilCredentials = + GoogleCredentials.fromStream(credentialsStream); + utilCredentials = utilCredentials.createScoped(Arrays.asList(authScope)); + AccessToken accessToken = utilCredentials.refreshAccessToken(); + + OAuth2Credentials credentials = OAuth2Credentials.create(accessToken); + + TestServiceGrpc.TestServiceBlockingStub stub = blockingStub + .withCallCredentials(MoreCallCredentials.from(credentials)); + final SimpleRequest request = SimpleRequest.newBuilder() + .setFillUsername(true) + .setFillOauthScope(true) + .build(); + + final SimpleResponse response = stub.unaryCall(request); + assertFalse(response.getUsername().isEmpty()); + assertTrue("Received username: " + response.getUsername(), + jsonKey.contains(response.getUsername())); + assertFalse(response.getOauthScope().isEmpty()); + assertTrue("Received oauth scope: " + response.getOauthScope(), + authScope.contains(response.getOauthScope())); + } + + /** Sends a unary rpc with "per rpc" raw oauth2 access token credentials. */ + public void perRpcCreds(String jsonKey, InputStream credentialsStream, String oauthScope) + throws Exception { + // In gRpc Java, we don't have per Rpc credentials, user can use an intercepted stub only once + // for that purpose. + // So, this test is identical to oauth2_auth_token test. + oauth2AuthToken(jsonKey, credentialsStream, oauthScope); + } + + /** Sends an unary rpc with "google default credentials". */ + public void googleDefaultCredentials( + String defaultServiceAccount, + TestServiceGrpc.TestServiceBlockingStub googleDefaultStub) throws Exception { + final SimpleRequest request = SimpleRequest.newBuilder() + .setFillUsername(true) + .setResponseSize(314159) + .setPayload(Payload.newBuilder() + .setBody(ByteString.copyFrom(new byte[271828]))) + .build(); + final SimpleResponse response = googleDefaultStub.unaryCall(request); + assertEquals(defaultServiceAccount, response.getUsername()); + + final SimpleResponse goldenResponse = SimpleResponse.newBuilder() + .setUsername(defaultServiceAccount) + .setPayload(Payload.newBuilder() + .setBody(ByteString.copyFrom(new byte[314159]))) + .build(); + assertResponse(goldenResponse, response); + } + + /** + * Test backend metrics per query reporting: expect the test client LB policy to receive load + * reports. + */ + public void testOrcaPerRpc() throws Exception { + AtomicReference reportHolder = new AtomicReference<>(); + TestOrcaReport answer = TestOrcaReport.newBuilder() + .setCpuUtilization(0.8210) + .setMemoryUtilization(0.5847) + .putRequestCost("cost", 3456.32) + .putUtilization("util", 0.30499) + .build(); + blockingStub.withOption(ORCA_RPC_REPORT_KEY, reportHolder).unaryCall( + SimpleRequest.newBuilder().setOrcaPerQueryReport(answer).build()); + assertThat(reportHolder.get()).isEqualTo(answer); + } + + /** + * Test backend metrics OOB reporting: expect the test client LB policy to receive load reports. + */ + public void testOrcaOob() throws Exception { + AtomicReference reportHolder = new AtomicReference<>(); + final TestOrcaReport answer = TestOrcaReport.newBuilder() + .setCpuUtilization(0.8210) + .setMemoryUtilization(0.5847) + .putUtilization("util", 0.30499) + .build(); + final TestOrcaReport answer2 = TestOrcaReport.newBuilder() + .setCpuUtilization(0.29309) + .setMemoryUtilization(0.2) + .putUtilization("util", 0.2039) + .build(); + + final int retryLimit = 5; + BlockingQueue queue = new LinkedBlockingQueue<>(); + final Object lastItem = new Object(); + StreamObserver streamObserver = + asyncStub.fullDuplexCall(new StreamObserver() { + + @Override + public void onNext(StreamingOutputCallResponse value) { + queue.add(value); + } + + @Override + public void onError(Throwable t) { + queue.add(t); + } + + @Override + public void onCompleted() { + queue.add(lastItem); + } + }); + + streamObserver.onNext(StreamingOutputCallRequest.newBuilder() + .setOrcaOobReport(answer) + .addResponseParameters(ResponseParameters.newBuilder().setSize(1).build()).build()); + assertThat(queue.take()).isInstanceOf(StreamingOutputCallResponse.class); + int i = 0; + for (; i < retryLimit; i++) { + Thread.sleep(1000); + blockingStub.withOption(ORCA_OOB_REPORT_KEY, reportHolder).emptyCall(EMPTY); + if (answer.equals(reportHolder.get())) { + break; + } + } + assertThat(i).isLessThan(retryLimit); + streamObserver.onNext(StreamingOutputCallRequest.newBuilder() + .setOrcaOobReport(answer2) + .addResponseParameters(ResponseParameters.newBuilder().setSize(1).build()).build()); + assertThat(queue.take()).isInstanceOf(StreamingOutputCallResponse.class); + + for (i = 0; i < retryLimit; i++) { + Thread.sleep(1000); + blockingStub.withOption(ORCA_OOB_REPORT_KEY, reportHolder).emptyCall(EMPTY); + if (reportHolder.get().equals(answer2)) { + break; + } + } + assertThat(i).isLessThan(retryLimit); + streamObserver.onCompleted(); + assertThat(queue.take()).isSameInstanceAs(lastItem); + } + @Override protected boolean metricsExpected() { // Exact message size doesn't match when testing with Go servers: From f20167d60247588fa83a82620333c2e1e3216644 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 25 Jul 2024 13:28:49 -0700 Subject: [PATCH 053/103] util: Replace RR.EmptyPicker with FixedResultPicker --- .../io/grpc/util/RoundRobinLoadBalancer.java | 22 ++---------------- .../grpc/util/RoundRobinLoadBalancerTest.java | 23 +++++++++++-------- 2 files changed, 15 insertions(+), 30 deletions(-) diff --git a/util/src/main/java/io/grpc/util/RoundRobinLoadBalancer.java b/util/src/main/java/io/grpc/util/RoundRobinLoadBalancer.java index a8d829cfb8d..0804978cf7f 100644 --- a/util/src/main/java/io/grpc/util/RoundRobinLoadBalancer.java +++ b/util/src/main/java/io/grpc/util/RoundRobinLoadBalancer.java @@ -42,7 +42,7 @@ */ final class RoundRobinLoadBalancer extends MultiChildLoadBalancer { private final AtomicInteger sequence = new AtomicInteger(new Random().nextInt()); - private SubchannelPicker currentPicker = new EmptyPicker(); + private SubchannelPicker currentPicker = new FixedResultPicker(PickResult.withNoResult()); public RoundRobinLoadBalancer(Helper helper) { super(helper); @@ -68,7 +68,7 @@ protected void updateOverallBalancingState() { } if (isConnecting) { - updateBalancingState(CONNECTING, new EmptyPicker()); + updateBalancingState(CONNECTING, new FixedResultPicker(PickResult.withNoResult())); } else { updateBalancingState(TRANSIENT_FAILURE, createReadyPicker(getChildLbStates())); } @@ -180,22 +180,4 @@ public boolean equals(Object o) { && new HashSet<>(subchannelPickers).containsAll(other.subchannelPickers); } } - - @VisibleForTesting - static final class EmptyPicker extends SubchannelPicker { - @Override - public PickResult pickSubchannel(PickSubchannelArgs args) { - return PickResult.withNoResult(); - } - - @Override - public int hashCode() { - return getClass().hashCode(); - } - - @Override - public boolean equals(Object o) { - return o instanceof EmptyPicker; - } - } } diff --git a/util/src/test/java/io/grpc/util/RoundRobinLoadBalancerTest.java b/util/src/test/java/io/grpc/util/RoundRobinLoadBalancerTest.java index 449230f0f45..743bbbef796 100644 --- a/util/src/test/java/io/grpc/util/RoundRobinLoadBalancerTest.java +++ b/util/src/test/java/io/grpc/util/RoundRobinLoadBalancerTest.java @@ -46,7 +46,9 @@ import io.grpc.EquivalentAddressGroup; import io.grpc.LoadBalancer; import io.grpc.LoadBalancer.CreateSubchannelArgs; +import io.grpc.LoadBalancer.FixedResultPicker; import io.grpc.LoadBalancer.Helper; +import io.grpc.LoadBalancer.PickResult; import io.grpc.LoadBalancer.PickSubchannelArgs; import io.grpc.LoadBalancer.ResolvedAddresses; import io.grpc.LoadBalancer.Subchannel; @@ -54,7 +56,6 @@ import io.grpc.Status; import io.grpc.internal.TestUtils; import io.grpc.util.MultiChildLoadBalancer.ChildLbState; -import io.grpc.util.RoundRobinLoadBalancer.EmptyPicker; import io.grpc.util.RoundRobinLoadBalancer.ReadyPicker; import java.net.SocketAddress; import java.util.ArrayList; @@ -84,6 +85,8 @@ @RunWith(JUnit4.class) public class RoundRobinLoadBalancerTest { private static final Attributes.Key MAJOR_KEY = Attributes.Key.create("major-key"); + private static final SubchannelPicker EMPTY_PICKER = + new FixedResultPicker(PickResult.withNoResult()); @Rule public final MockitoRule mocks = MockitoJUnit.rule(); @@ -248,7 +251,7 @@ public void pickAfterStateChange() throws Exception { ChildLbState childLbState = loadBalancer.getChildLbStates().iterator().next(); Subchannel subchannel = subchannels.get(Arrays.asList(childLbState.getEag())); - inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), isA(EmptyPicker.class)); + inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), eq(EMPTY_PICKER)); assertThat(childLbState.getCurrentState()).isEqualTo(CONNECTING); deliverSubchannelState(subchannel, ConnectivityStateInfo.forNonError(READY)); @@ -261,7 +264,7 @@ public void pickAfterStateChange() throws Exception { ConnectivityStateInfo.forTransientFailure(error)); assertThat(childLbState.getCurrentState()).isEqualTo(TRANSIENT_FAILURE); AbstractTestHelper.refreshInvokedAndUpdateBS(inOrder, CONNECTING, mockHelper, pickerCaptor); - assertThat(pickerCaptor.getValue()).isInstanceOf(EmptyPicker.class); + assertThat(pickerCaptor.getValue()).isEqualTo(EMPTY_PICKER); deliverSubchannelState(subchannel, ConnectivityStateInfo.forNonError(IDLE)); inOrder.verify(mockHelper).refreshNameResolution(); @@ -277,7 +280,7 @@ public void ignoreShutdownSubchannelStateChange() { InOrder inOrder = inOrder(mockHelper); Status addressesAcceptanceStatus = acceptAddresses(servers, Attributes.EMPTY); assertThat(addressesAcceptanceStatus.isOk()).isTrue(); - inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), isA(EmptyPicker.class)); + inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), eq(EMPTY_PICKER)); loadBalancer.shutdown(); for (ChildLbState child : loadBalancer.getChildLbStates()) { @@ -297,7 +300,7 @@ public void stayTransientFailureUntilReady() { Status addressesAcceptanceStatus = acceptAddresses(servers, Attributes.EMPTY); assertThat(addressesAcceptanceStatus.isOk()).isTrue(); - inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), isA(EmptyPicker.class)); + inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), eq(EMPTY_PICKER)); Map childToSubChannelMap = new HashMap<>(); // Simulate state transitions for each subchannel individually. @@ -336,7 +339,7 @@ public void refreshNameResolutionWhenSubchannelConnectionBroken() { assertThat(addressesAcceptanceStatus.isOk()).isTrue(); verify(mockHelper, times(3)).createSubchannel(any(CreateSubchannelArgs.class)); - inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), isA(EmptyPicker.class)); + inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), eq(EMPTY_PICKER)); // Simulate state transitions for each subchannel individually. for (ChildLbState child : loadBalancer.getChildLbStates()) { @@ -352,7 +355,7 @@ public void refreshNameResolutionWhenSubchannelConnectionBroken() { deliverSubchannelState(sc, ConnectivityStateInfo.forNonError(IDLE)); inOrder.verify(mockHelper).refreshNameResolution(); verify(sc, times(2)).requestConnection(); - inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), isA(EmptyPicker.class)); + inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), eq(EMPTY_PICKER)); } AbstractTestHelper.verifyNoMoreMeaningfulInteractions(mockHelper); @@ -461,7 +464,7 @@ public void subchannelStateIsolation() throws Exception { Iterator pickers = pickerCaptor.getAllValues().iterator(); // The picker is incrementally updated as subchannels become READY assertEquals(CONNECTING, stateIterator.next()); - assertThat(pickers.next()).isInstanceOf(EmptyPicker.class); + assertThat(pickers.next()).isEqualTo(EMPTY_PICKER); assertEquals(READY, stateIterator.next()); assertThat(getList(pickers.next())).containsExactly(sc1); assertEquals(READY, stateIterator.next()); @@ -492,8 +495,8 @@ public void readyPicker_emptyList() { @Test public void internalPickerComparisons() { - SubchannelPicker empty1 = new EmptyPicker(); - SubchannelPicker empty2 = new EmptyPicker(); + SubchannelPicker empty1 = new FixedResultPicker(PickResult.withNoResult()); + SubchannelPicker empty2 = new FixedResultPicker(PickResult.withNoResult()); AtomicInteger seq = new AtomicInteger(0); acceptAddresses(servers, Attributes.EMPTY); // create subchannels From ee3ffef3ee0eca9d63424b15e6c31d6e729ef4ed Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Sat, 17 Aug 2024 11:23:01 -0700 Subject: [PATCH 054/103] core: In PF, disjoint update while READY should transition to IDLE This is the same as if we received a GOAWAY. We wait for the next RPC to begin connecting again. This is InternalSubchannel's behavior. --- .../internal/PickFirstLeafLoadBalancer.java | 18 ++++++++---------- .../PickFirstLeafLoadBalancerTest.java | 19 ++++++++++++++----- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java index 8d9c5aa8a1c..344012fef2b 100644 --- a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java +++ b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java @@ -152,20 +152,18 @@ public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { } } - if (oldAddrs.size() == 0 || rawConnectivityState == CONNECTING - || rawConnectivityState == READY) { - // start connection attempt at first address + if (oldAddrs.size() == 0) { + // Make tests happy; they don't properly assume starting in CONNECTING rawConnectivityState = CONNECTING; updateBalancingState(CONNECTING, new Picker(PickResult.withNoResult())); - cancelScheduleTask(); - requestConnection(); + } - } else if (rawConnectivityState == IDLE) { - // start connection attempt at first address when requested - SubchannelPicker picker = new RequestConnectionPicker(this); - updateBalancingState(IDLE, picker); + if (rawConnectivityState == READY) { + // connect from beginning when prompted + rawConnectivityState = IDLE; + updateBalancingState(IDLE, new RequestConnectionPicker(this)); - } else if (rawConnectivityState == TRANSIENT_FAILURE) { + } else if (rawConnectivityState == CONNECTING || rawConnectivityState == TRANSIENT_FAILURE) { // start connection attempt at first address cancelScheduleTask(); requestConnection(); diff --git a/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java b/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java index a6e3f99ab32..9c22c4a7086 100644 --- a/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java +++ b/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java @@ -1121,10 +1121,17 @@ public void updateAddresses_disjoint_ready_twice() { loadBalancer.acceptResolvedAddresses( ResolvedAddresses.newBuilder().setAddresses(newServers).setAttributes(affinity).build()); inOrder.verify(mockSubchannel1).shutdown(); - inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), pickerCaptor.capture()); + inOrder.verify(mockHelper).updateBalancingState(eq(IDLE), pickerCaptor.capture()); + inOrder.verify(mockSubchannel3, never()).start(stateListenerCaptor.capture()); + + // Trigger connection creation + picker = pickerCaptor.getValue(); + assertEquals(PickResult.withNoResult(), picker.pickSubchannel(mockArgs)); inOrder.verify(mockSubchannel3).start(stateListenerCaptor.capture()); SubchannelStateListener stateListener3 = stateListenerCaptor.getValue(); inOrder.verify(mockSubchannel3).requestConnection(); + stateListener3.onSubchannelState(ConnectivityStateInfo.forNonError(CONNECTING)); + inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), pickerCaptor.capture()); if (enableHappyEyeballs) { forwardTimeByConnectionDelay(); @@ -1171,17 +1178,19 @@ public void updateAddresses_disjoint_ready_twice() { loadBalancer.acceptResolvedAddresses( ResolvedAddresses.newBuilder().setAddresses(newestServers).setAttributes(affinity).build()); inOrder.verify(mockSubchannel3).shutdown(); - inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), pickerCaptor.capture()); - inOrder.verify(mockSubchannel1n2).start(stateListenerCaptor.capture()); - stateListener = stateListenerCaptor.getValue(); - assertEquals(CONNECTING, loadBalancer.getConcludedConnectivityState()); + inOrder.verify(mockHelper).updateBalancingState(eq(IDLE), pickerCaptor.capture()); + assertEquals(IDLE, loadBalancer.getConcludedConnectivityState()); picker = pickerCaptor.getValue(); // Calling pickSubchannel() twice gave the same result assertEquals(picker.pickSubchannel(mockArgs), picker.pickSubchannel(mockArgs)); // But the picker calls requestConnection() only once + inOrder.verify(mockSubchannel1n2).start(stateListenerCaptor.capture()); + stateListener = stateListenerCaptor.getValue(); inOrder.verify(mockSubchannel1n2).requestConnection(); + stateListener.onSubchannelState(ConnectivityStateInfo.forNonError(CONNECTING)); + inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), pickerCaptor.capture()); assertEquals(PickResult.withNoResult(), pickerCaptor.getValue().pickSubchannel(mockArgs)); assertEquals(CONNECTING, loadBalancer.getConcludedConnectivityState()); From d034a56cb0de3b5e3635c8ecb381085d02b4ddbf Mon Sep 17 00:00:00 2001 From: Larry Safran Date: Fri, 23 Aug 2024 13:05:38 -0700 Subject: [PATCH 055/103] Xds client split (#11484) --- .../main/java/io/grpc/xds/CsdsService.java | 80 ++++++++++++------ .../InternalSharedXdsClientPoolProvider.java | 2 +- .../grpc/xds/SharedXdsClientPoolProvider.java | 37 +++++++-- .../io/grpc/xds/XdsClientPoolFactory.java | 7 +- .../java/io/grpc/xds/XdsNameResolver.java | 17 ++-- .../io/grpc/xds/XdsNameResolverProvider.java | 2 +- .../java/io/grpc/xds/XdsServerWrapper.java | 2 +- .../java/io/grpc/xds/CsdsServiceTest.java | 82 +++++++++++++++++-- .../grpc/xds/GrpcXdsClientImplTestBase.java | 4 +- .../io/grpc/xds/GrpcXdsClientImplV3Test.java | 36 +++++++- .../xds/SharedXdsClientPoolProviderTest.java | 21 ++--- .../io/grpc/xds/XdsClientFederationTest.java | 3 +- .../java/io/grpc/xds/XdsNameResolverTest.java | 66 +++++++++++---- .../java/io/grpc/xds/XdsServerTestHelper.java | 9 +- .../grpc/xds/XdsTestControlPlaneService.java | 9 +- 15 files changed, 284 insertions(+), 93 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/CsdsService.java b/xds/src/main/java/io/grpc/xds/CsdsService.java index 0102836660c..a296beb45d0 100644 --- a/xds/src/main/java/io/grpc/xds/CsdsService.java +++ b/xds/src/main/java/io/grpc/xds/CsdsService.java @@ -39,6 +39,8 @@ import io.grpc.xds.client.XdsClient.ResourceMetadata.ResourceMetadataStatus; import io.grpc.xds.client.XdsClient.ResourceMetadata.UpdateFailureState; import io.grpc.xds.client.XdsResourceType; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -117,43 +119,58 @@ public void onCompleted() { private boolean handleRequest( ClientStatusRequest request, StreamObserver responseObserver) { - StatusException error; - try { - responseObserver.onNext(getConfigDumpForRequest(request)); - return true; - } catch (StatusException e) { - error = e; - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - logger.log(Level.FINE, "Server interrupted while building CSDS config dump", e); - error = Status.ABORTED.withDescription("Thread interrupted").withCause(e).asException(); - } catch (RuntimeException e) { - logger.log(Level.WARNING, "Unexpected error while building CSDS config dump", e); - error = - Status.INTERNAL.withDescription("Unexpected internal error").withCause(e).asException(); - } - responseObserver.onError(error); - return false; - } + StatusException error = null; - private ClientStatusResponse getConfigDumpForRequest(ClientStatusRequest request) - throws StatusException, InterruptedException { if (request.getNodeMatchersCount() > 0) { - throw new StatusException( + error = new StatusException( Status.INVALID_ARGUMENT.withDescription("node_matchers not supported")); + } else { + List targets = xdsClientPoolFactory.getTargets(); + List clientConfigs = new ArrayList<>(targets.size()); + + for (int i = 0; i < targets.size() && error == null; i++) { + try { + ClientConfig clientConfig = getConfigForRequest(targets.get(i)); + if (clientConfig != null) { + clientConfigs.add(clientConfig); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + logger.log(Level.FINE, "Server interrupted while building CSDS config dump", e); + error = Status.ABORTED.withDescription("Thread interrupted").withCause(e).asException(); + } catch (RuntimeException e) { + logger.log(Level.WARNING, "Unexpected error while building CSDS config dump", e); + error = Status.INTERNAL.withDescription("Unexpected internal error").withCause(e) + .asException(); + } + } + + try { + responseObserver.onNext(getStatusResponse(clientConfigs)); + } catch (RuntimeException e) { + logger.log(Level.WARNING, "Unexpected error while processing CSDS config dump", e); + error = Status.INTERNAL.withDescription("Unexpected internal error").withCause(e) + .asException(); + } } - ObjectPool xdsClientPool = xdsClientPoolFactory.get(); + if (error == null) { + return true; // All clients reported without error + } + responseObserver.onError(error); + return false; + } + + private ClientConfig getConfigForRequest(String target) throws InterruptedException { + ObjectPool xdsClientPool = xdsClientPoolFactory.get(target); if (xdsClientPool == null) { - return ClientStatusResponse.getDefaultInstance(); + return null; } XdsClient xdsClient = null; try { xdsClient = xdsClientPool.getObject(); - return ClientStatusResponse.newBuilder() - .addConfig(getClientConfigForXdsClient(xdsClient)) - .build(); + return getClientConfigForXdsClient(xdsClient, target); } finally { if (xdsClient != null) { xdsClientPool.returnObject(xdsClient); @@ -161,9 +178,18 @@ private ClientStatusResponse getConfigDumpForRequest(ClientStatusRequest request } } + private ClientStatusResponse getStatusResponse(List clientConfigs) { + if (clientConfigs.isEmpty()) { + return ClientStatusResponse.getDefaultInstance(); + } + return ClientStatusResponse.newBuilder().addAllConfig(clientConfigs).build(); + } + @VisibleForTesting - static ClientConfig getClientConfigForXdsClient(XdsClient xdsClient) throws InterruptedException { + static ClientConfig getClientConfigForXdsClient(XdsClient xdsClient, String target) + throws InterruptedException { ClientConfig.Builder builder = ClientConfig.newBuilder() + .setClientScope(target) .setNode(xdsClient.getBootstrapInfo().node().toEnvoyProtoNode()); Map, Map> metadataByType = diff --git a/xds/src/main/java/io/grpc/xds/InternalSharedXdsClientPoolProvider.java b/xds/src/main/java/io/grpc/xds/InternalSharedXdsClientPoolProvider.java index 39b9ed0d095..0073cce1a88 100644 --- a/xds/src/main/java/io/grpc/xds/InternalSharedXdsClientPoolProvider.java +++ b/xds/src/main/java/io/grpc/xds/InternalSharedXdsClientPoolProvider.java @@ -36,6 +36,6 @@ public static void setDefaultProviderBootstrapOverride(Map bootstrap) public static ObjectPool getOrCreate(String target) throws XdsInitializationException { - return SharedXdsClientPoolProvider.getDefaultProvider().getOrCreate(); + return SharedXdsClientPoolProvider.getDefaultProvider().getOrCreate(target); } } diff --git a/xds/src/main/java/io/grpc/xds/SharedXdsClientPoolProvider.java b/xds/src/main/java/io/grpc/xds/SharedXdsClientPoolProvider.java index 5ae1f5bbce5..c9195896d82 100644 --- a/xds/src/main/java/io/grpc/xds/SharedXdsClientPoolProvider.java +++ b/xds/src/main/java/io/grpc/xds/SharedXdsClientPoolProvider.java @@ -20,6 +20,7 @@ import static io.grpc.xds.GrpcXdsTransportFactory.DEFAULT_XDS_TRANSPORT_FACTORY; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; import io.grpc.internal.ExponentialBackoffPolicy; import io.grpc.internal.GrpcUtil; import io.grpc.internal.ObjectPool; @@ -32,6 +33,7 @@ import io.grpc.xds.client.XdsInitializationException; import io.grpc.xds.internal.security.TlsContextManagerImpl; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; @@ -53,7 +55,7 @@ final class SharedXdsClientPoolProvider implements XdsClientPoolFactory { private final Bootstrapper bootstrapper; private final Object lock = new Object(); private final AtomicReference> bootstrapOverride = new AtomicReference<>(); - private volatile ObjectPool xdsClientPool; + private final Map> targetToXdsClientMap = new ConcurrentHashMap<>(); SharedXdsClientPoolProvider() { this(new GrpcBootstrapperImpl()); @@ -75,16 +77,16 @@ public void setBootstrapOverride(Map bootstrap) { @Override @Nullable - public ObjectPool get() { - return xdsClientPool; + public ObjectPool get(String target) { + return targetToXdsClientMap.get(target); } @Override - public ObjectPool getOrCreate() throws XdsInitializationException { - ObjectPool ref = xdsClientPool; + public ObjectPool getOrCreate(String target) throws XdsInitializationException { + ObjectPool ref = targetToXdsClientMap.get(target); if (ref == null) { synchronized (lock) { - ref = xdsClientPool; + ref = targetToXdsClientMap.get(target); if (ref == null) { BootstrapInfo bootstrapInfo; Map rawBootstrap = bootstrapOverride.get(); @@ -96,13 +98,20 @@ public ObjectPool getOrCreate() throws XdsInitializationException { if (bootstrapInfo.servers().isEmpty()) { throw new XdsInitializationException("No xDS server provided"); } - ref = xdsClientPool = new RefCountedXdsClientObjectPool(bootstrapInfo); + ref = new RefCountedXdsClientObjectPool(bootstrapInfo, target); + targetToXdsClientMap.put(target, ref); } } } return ref; } + @Override + public ImmutableList getTargets() { + return ImmutableList.copyOf(targetToXdsClientMap.keySet()); + } + + private static class SharedXdsClientPoolProviderHolder { private static final SharedXdsClientPoolProvider instance = new SharedXdsClientPoolProvider(); } @@ -110,7 +119,11 @@ private static class SharedXdsClientPoolProviderHolder { @ThreadSafe @VisibleForTesting static class RefCountedXdsClientObjectPool implements ObjectPool { + + private static final ExponentialBackoffPolicy.Provider BACKOFF_POLICY_PROVIDER = + new ExponentialBackoffPolicy.Provider(); private final BootstrapInfo bootstrapInfo; + private final String target; // The target associated with the xDS client. private final Object lock = new Object(); @GuardedBy("lock") private ScheduledExecutorService scheduler; @@ -120,8 +133,9 @@ static class RefCountedXdsClientObjectPool implements ObjectPool { private int refCount; @VisibleForTesting - RefCountedXdsClientObjectPool(BootstrapInfo bootstrapInfo) { + RefCountedXdsClientObjectPool(BootstrapInfo bootstrapInfo, String target) { this.bootstrapInfo = checkNotNull(bootstrapInfo); + this.target = target; } @Override @@ -136,7 +150,7 @@ public XdsClient getObject() { DEFAULT_XDS_TRANSPORT_FACTORY, bootstrapInfo, scheduler, - new ExponentialBackoffPolicy.Provider(), + BACKOFF_POLICY_PROVIDER, GrpcUtil.STOPWATCH_SUPPLIER, TimeProvider.SYSTEM_TIME_PROVIDER, MessagePrinter.INSTANCE, @@ -167,5 +181,10 @@ XdsClient getXdsClientForTest() { return xdsClient; } } + + public String getTarget() { + return target; + } } + } diff --git a/xds/src/main/java/io/grpc/xds/XdsClientPoolFactory.java b/xds/src/main/java/io/grpc/xds/XdsClientPoolFactory.java index c649b3b3069..313eb675116 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClientPoolFactory.java +++ b/xds/src/main/java/io/grpc/xds/XdsClientPoolFactory.java @@ -19,6 +19,7 @@ import io.grpc.internal.ObjectPool; import io.grpc.xds.client.XdsClient; import io.grpc.xds.client.XdsInitializationException; +import java.util.List; import java.util.Map; import javax.annotation.Nullable; @@ -26,7 +27,9 @@ interface XdsClientPoolFactory { void setBootstrapOverride(Map bootstrap); @Nullable - ObjectPool get(); + ObjectPool get(String target); - ObjectPool getOrCreate() throws XdsInitializationException; + ObjectPool getOrCreate(String target) throws XdsInitializationException; + + List getTargets(); } diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java index 9ad9b6e82f0..f0329387fc9 100644 --- a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java +++ b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java @@ -66,6 +66,7 @@ import io.grpc.xds.client.XdsClient.ResourceWatcher; import io.grpc.xds.client.XdsLogger; import io.grpc.xds.client.XdsLogger.XdsLogLevel; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -104,6 +105,7 @@ final class XdsNameResolver extends NameResolver { private final XdsLogger logger; @Nullable private final String targetAuthority; + private final String target; private final String serviceAuthority; // Encoded version of the service authority as per // https://datatracker.ietf.org/doc/html/rfc3986#section-3.2. @@ -133,23 +135,24 @@ final class XdsNameResolver extends NameResolver { private boolean receivedConfig; XdsNameResolver( - @Nullable String targetAuthority, String name, @Nullable String overrideAuthority, + URI targetUri, String name, @Nullable String overrideAuthority, ServiceConfigParser serviceConfigParser, SynchronizationContext syncContext, ScheduledExecutorService scheduler, @Nullable Map bootstrapOverride) { - this(targetAuthority, name, overrideAuthority, serviceConfigParser, syncContext, scheduler, - SharedXdsClientPoolProvider.getDefaultProvider(), ThreadSafeRandomImpl.instance, - FilterRegistry.getDefaultRegistry(), bootstrapOverride); + this(targetUri, targetUri.getAuthority(), name, overrideAuthority, serviceConfigParser, + syncContext, scheduler, SharedXdsClientPoolProvider.getDefaultProvider(), + ThreadSafeRandomImpl.instance, FilterRegistry.getDefaultRegistry(), bootstrapOverride); } @VisibleForTesting XdsNameResolver( - @Nullable String targetAuthority, String name, @Nullable String overrideAuthority, - ServiceConfigParser serviceConfigParser, + URI targetUri, @Nullable String targetAuthority, String name, + @Nullable String overrideAuthority, ServiceConfigParser serviceConfigParser, SynchronizationContext syncContext, ScheduledExecutorService scheduler, XdsClientPoolFactory xdsClientPoolFactory, ThreadSafeRandom random, FilterRegistry filterRegistry, @Nullable Map bootstrapOverride) { this.targetAuthority = targetAuthority; + target = targetUri.toString(); // The name might have multiple slashes so encode it before verifying. serviceAuthority = checkNotNull(name, "name"); @@ -180,7 +183,7 @@ public String getServiceAuthority() { public void start(Listener2 listener) { this.listener = checkNotNull(listener, "listener"); try { - xdsClientPool = xdsClientPoolFactory.getOrCreate(); + xdsClientPool = xdsClientPoolFactory.getOrCreate(target); } catch (Exception e) { listener.onError( Status.UNAVAILABLE.withDescription("Failed to initialize xDS").withCause(e)); diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolverProvider.java b/xds/src/main/java/io/grpc/xds/XdsNameResolverProvider.java index 598be07fcd8..8d0e59eaa91 100644 --- a/xds/src/main/java/io/grpc/xds/XdsNameResolverProvider.java +++ b/xds/src/main/java/io/grpc/xds/XdsNameResolverProvider.java @@ -78,7 +78,7 @@ public XdsNameResolver newNameResolver(URI targetUri, Args args) { targetUri); String name = targetPath.substring(1); return new XdsNameResolver( - targetUri.getAuthority(), name, args.getOverrideAuthority(), + targetUri, name, args.getOverrideAuthority(), args.getServiceConfigParser(), args.getSynchronizationContext(), args.getScheduledExecutorService(), bootstrapOverride); diff --git a/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java b/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java index bf8603fb3e4..dfb7c4fb7db 100644 --- a/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java +++ b/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java @@ -171,7 +171,7 @@ public void run() { private void internalStart() { try { - xdsClientPool = xdsClientPoolFactory.getOrCreate(); + xdsClientPool = xdsClientPoolFactory.getOrCreate(""); } catch (Exception e) { StatusException statusException = Status.UNAVAILABLE.withDescription( "Failed to initialize xDS").withCause(e).asException(); diff --git a/xds/src/test/java/io/grpc/xds/CsdsServiceTest.java b/xds/src/test/java/io/grpc/xds/CsdsServiceTest.java index bf330b1007a..63b9cda043c 100644 --- a/xds/src/test/java/io/grpc/xds/CsdsServiceTest.java +++ b/xds/src/test/java/io/grpc/xds/CsdsServiceTest.java @@ -54,6 +54,7 @@ import io.grpc.xds.client.XdsClient.ResourceMetadata; import io.grpc.xds.client.XdsClient.ResourceMetadata.ResourceMetadataStatus; import io.grpc.xds.client.XdsResourceType; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -85,6 +86,7 @@ public class CsdsServiceTest { private static final XdsResourceType CDS = XdsClusterResource.getInstance(); private static final XdsResourceType RDS = XdsRouteConfigureResource.getInstance(); private static final XdsResourceType EDS = XdsEndpointResource.getInstance(); + public static final String FAKE_CLIENT_SCOPE = "fake"; @RunWith(JUnit4.class) public static class ServiceTests { @@ -198,13 +200,13 @@ public void streamClientStatus_happyPath() { @Override @Nullable - public ObjectPool get() { + public ObjectPool get(String target) { // xDS client not ready on the first call, then becomes ready. if (!calledOnce) { calledOnce = true; return null; } else { - return super.get(); + return super.get(target); } } }); @@ -267,11 +269,51 @@ public void streamClientStatus_onClientError() { assertThat(responseObserver.getError()).isNull(); } + @Test + public void multipleXdsClients() { + FakeXdsClient xdsClient1 = new FakeXdsClient(); + FakeXdsClient xdsClient2 = new FakeXdsClient(); + Map clientMap = new HashMap<>(); + clientMap.put("target1", xdsClient1); + clientMap.put("target2", xdsClient2); + FakeXdsClientPoolFactory factory = new FakeXdsClientPoolFactory(clientMap); + CsdsService csdsService = new CsdsService(factory); + grpcServerRule.getServiceRegistry().addService(csdsService); + + StreamRecorder responseObserver = StreamRecorder.create(); + StreamObserver requestObserver = + csdsAsyncStub.streamClientStatus(responseObserver); + + requestObserver.onNext(REQUEST); + requestObserver.onCompleted(); + + List responses = responseObserver.getValues(); + assertThat(responses).hasSize(1); + Collection targets = verifyMultiResponse(responses.get(0), 2); + assertThat(targets).containsExactly("target1", "target2"); + responseObserver.onCompleted(); + } + private void verifyResponse(ClientStatusResponse response) { assertThat(response.getConfigCount()).isEqualTo(1); ClientConfig clientConfig = response.getConfig(0); verifyClientConfigNode(clientConfig); verifyClientConfigNoResources(XDS_CLIENT_NO_RESOURCES, clientConfig); + assertThat(clientConfig.getClientScope()).isEmpty(); + } + + private Collection verifyMultiResponse(ClientStatusResponse response, int numExpected) { + assertThat(response.getConfigCount()).isEqualTo(numExpected); + + List clientScopes = new ArrayList<>(); + for (int i = 0; i < numExpected; i++) { + ClientConfig clientConfig = response.getConfig(i); + verifyClientConfigNode(clientConfig); + verifyClientConfigNoResources(XDS_CLIENT_NO_RESOURCES, clientConfig); + clientScopes.add(clientConfig.getClientScope()); + } + + return clientScopes; } private void verifyRequestInvalidResponseStatus(Status status) { @@ -350,9 +392,11 @@ public Map> getSubscribedResourceTypesWithTypeUrl() { ); } }; - ClientConfig clientConfig = CsdsService.getClientConfigForXdsClient(fakeXdsClient); + ClientConfig clientConfig = CsdsService.getClientConfigForXdsClient(fakeXdsClient, + FAKE_CLIENT_SCOPE); verifyClientConfigNode(clientConfig); + assertThat(clientConfig.getClientScope()).isEqualTo(FAKE_CLIENT_SCOPE); // Minimal verification to confirm that the data/metadata XdsClient provides, // is propagated to the correct resource types. @@ -390,9 +434,11 @@ public Map> getSubscribedResourceTypesWithTypeUrl() { @Test public void getClientConfigForXdsClient_noSubscribedResources() throws InterruptedException { - ClientConfig clientConfig = CsdsService.getClientConfigForXdsClient(XDS_CLIENT_NO_RESOURCES); + ClientConfig clientConfig = + CsdsService.getClientConfigForXdsClient(XDS_CLIENT_NO_RESOURCES, FAKE_CLIENT_SCOPE); verifyClientConfigNode(clientConfig); verifyClientConfigNoResources(XDS_CLIENT_NO_RESOURCES, clientConfig); + assertThat(clientConfig.getClientScope()).isEqualTo(FAKE_CLIENT_SCOPE); } } @@ -460,22 +506,35 @@ public Collection getSubscribedResources(ServerInfo serverInfo, public Map> getSubscribedResourceTypesWithTypeUrl() { return ImmutableMap.of(); } + } private static class FakeXdsClientPoolFactory implements XdsClientPoolFactory { - @Nullable private final XdsClient xdsClient; + private final Map xdsClientMap = new HashMap<>(); + private boolean isOldStyle + ; private FakeXdsClientPoolFactory(@Nullable XdsClient xdsClient) { - this.xdsClient = xdsClient; + if (xdsClient != null) { + xdsClientMap.put("", xdsClient); + } + isOldStyle = true; + } + + private FakeXdsClientPoolFactory(Map xdsClientMap) { + this.xdsClientMap.putAll(xdsClientMap); + isOldStyle = false; } @Override @Nullable - public ObjectPool get() { + public ObjectPool get(String target) { + String targetToUse = isOldStyle ? "" : target; + return new ObjectPool() { @Override public XdsClient getObject() { - return xdsClient; + return xdsClientMap.get(targetToUse); } @Override @@ -485,13 +544,18 @@ public XdsClient returnObject(Object object) { }; } + @Override + public List getTargets() { + return new ArrayList<>(xdsClientMap.keySet()); + } + @Override public void setBootstrapOverride(Map bootstrap) { throw new UnsupportedOperationException("Should not be called"); } @Override - public ObjectPool getOrCreate() { + public ObjectPool getOrCreate(String target) { throw new UnsupportedOperationException("Should not be called"); } } diff --git a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java index 6b04edcb9b8..d41630cdb4a 100644 --- a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java +++ b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java @@ -340,8 +340,7 @@ public XdsTransport create(ServerInfo serverInfo) { } }; - xdsServerInfo = ServerInfo.create(SERVER_URI, CHANNEL_CREDENTIALS, - ignoreResourceDeletion()); + xdsServerInfo = ServerInfo.create(SERVER_URI, CHANNEL_CREDENTIALS, ignoreResourceDeletion()); BootstrapInfo bootstrapInfo = Bootstrapper.BootstrapInfo.builder() .servers(Collections.singletonList(xdsServerInfo)) @@ -2974,6 +2973,7 @@ public void flowControlAbsent() throws Exception { anotherWatcher, fakeWatchClock.getScheduledExecutorService()); verifyResourceMetadataRequested(CDS, CDS_RESOURCE); verifyResourceMetadataRequested(CDS, anotherCdsResource); + DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); call.verifyRequest(CDS, Arrays.asList(CDS_RESOURCE, anotherCdsResource), "", "", NODE); assertThat(fakeWatchClock.runDueTasks()).isEqualTo(2); diff --git a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplV3Test.java b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplV3Test.java index 2b2ce5cbd72..40a9bff514f 100644 --- a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplV3Test.java +++ b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplV3Test.java @@ -17,6 +17,7 @@ package io.grpc.xds; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.AdditionalAnswers.delegatesTo; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; @@ -144,7 +145,8 @@ public StreamObserver streamAggregatedResources( assertThat(adsEnded.get()).isTrue(); // ensure previous call was ended adsEnded.set(false); @SuppressWarnings("unchecked") - StreamObserver requestObserver = mock(StreamObserver.class); + StreamObserver requestObserver = + mock(StreamObserver.class, delegatesTo(new MockStreamObserver())); DiscoveryRpcCall call = new DiscoveryRpcCallV3(requestObserver, responseObserver); resourceDiscoveryCalls.offer(call); Context.current().addListener( @@ -874,6 +876,19 @@ public boolean matches(DiscoveryRequest argument) { } return node.equals(argument.getNode()); } + + @Override + public String toString() { + return "DiscoveryRequestMatcher{" + + "node=" + node + + ", versionInfo='" + versionInfo + '\'' + + ", typeUrl='" + typeUrl + '\'' + + ", resources=" + resources + + ", responseNonce='" + responseNonce + '\'' + + ", errorCode=" + errorCode + + ", errorMessages=" + errorMessages + + '}'; + } } /** @@ -901,4 +916,23 @@ public boolean matches(LoadStatsRequest argument) { return actual.equals(expected); } } + + private static class MockStreamObserver implements StreamObserver { + private final List requests = new ArrayList<>(); + + @Override + public void onNext(DiscoveryRequest value) { + requests.add(value); + } + + @Override + public void onError(Throwable t) { + // Ignore + } + + @Override + public void onCompleted() { + // Ignore + } + } } diff --git a/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java b/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java index 0687b51aea6..ee164938b2d 100644 --- a/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java @@ -51,6 +51,7 @@ public class SharedXdsClientPoolProviderTest { @Rule public final ExpectedException thrown = ExpectedException.none(); private final Node node = Node.newBuilder().setId("SharedXdsClientPoolProviderTest").build(); + private static final String DUMMY_TARGET = "dummy"; @Mock private GrpcBootstrapperImpl bootstrapper; @@ -63,8 +64,8 @@ public void noServer() throws XdsInitializationException { SharedXdsClientPoolProvider provider = new SharedXdsClientPoolProvider(bootstrapper); thrown.expect(XdsInitializationException.class); thrown.expectMessage("No xDS server provided"); - provider.getOrCreate(); - assertThat(provider.get()).isNull(); + provider.getOrCreate(DUMMY_TARGET); + assertThat(provider.get(DUMMY_TARGET)).isNull(); } @Test @@ -75,12 +76,12 @@ public void sharedXdsClientObjectPool() throws XdsInitializationException { when(bootstrapper.bootstrap()).thenReturn(bootstrapInfo); SharedXdsClientPoolProvider provider = new SharedXdsClientPoolProvider(bootstrapper); - assertThat(provider.get()).isNull(); - ObjectPool xdsClientPool = provider.getOrCreate(); + assertThat(provider.get(DUMMY_TARGET)).isNull(); + ObjectPool xdsClientPool = provider.getOrCreate(DUMMY_TARGET); verify(bootstrapper).bootstrap(); - assertThat(provider.getOrCreate()).isSameInstanceAs(xdsClientPool); - assertThat(provider.get()).isNotNull(); - assertThat(provider.get()).isSameInstanceAs(xdsClientPool); + assertThat(provider.getOrCreate(DUMMY_TARGET)).isSameInstanceAs(xdsClientPool); + assertThat(provider.get(DUMMY_TARGET)).isNotNull(); + assertThat(provider.get(DUMMY_TARGET)).isSameInstanceAs(xdsClientPool); verifyNoMoreInteractions(bootstrapper); } @@ -90,7 +91,7 @@ public void refCountedXdsClientObjectPool_delayedCreation() { BootstrapInfo bootstrapInfo = BootstrapInfo.builder().servers(Collections.singletonList(server)).node(node).build(); RefCountedXdsClientObjectPool xdsClientPool = - new RefCountedXdsClientObjectPool(bootstrapInfo); + new RefCountedXdsClientObjectPool(bootstrapInfo, DUMMY_TARGET); assertThat(xdsClientPool.getXdsClientForTest()).isNull(); XdsClient xdsClient = xdsClientPool.getObject(); assertThat(xdsClientPool.getXdsClientForTest()).isNotNull(); @@ -103,7 +104,7 @@ public void refCountedXdsClientObjectPool_refCounted() { BootstrapInfo bootstrapInfo = BootstrapInfo.builder().servers(Collections.singletonList(server)).node(node).build(); RefCountedXdsClientObjectPool xdsClientPool = - new RefCountedXdsClientObjectPool(bootstrapInfo); + new RefCountedXdsClientObjectPool(bootstrapInfo, DUMMY_TARGET); // getObject once XdsClient xdsClient = xdsClientPool.getObject(); assertThat(xdsClient).isNotNull(); @@ -123,7 +124,7 @@ public void refCountedXdsClientObjectPool_getObjectCreatesNewInstanceIfAlreadySh BootstrapInfo bootstrapInfo = BootstrapInfo.builder().servers(Collections.singletonList(server)).node(node).build(); RefCountedXdsClientObjectPool xdsClientPool = - new RefCountedXdsClientObjectPool(bootstrapInfo); + new RefCountedXdsClientObjectPool(bootstrapInfo, DUMMY_TARGET); XdsClient xdsClient1 = xdsClientPool.getObject(); assertThat(xdsClientPool.returnObject(xdsClient1)).isNull(); assertThat(xdsClient1.isShutDown()).isTrue(); diff --git a/xds/src/test/java/io/grpc/xds/XdsClientFederationTest.java b/xds/src/test/java/io/grpc/xds/XdsClientFederationTest.java index 149c1d6170d..0b8e89de721 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientFederationTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientFederationTest.java @@ -72,12 +72,13 @@ public class XdsClientFederationTest { private ObjectPool xdsClientPool; private XdsClient xdsClient; + private static final String DUMMY_TARGET = "dummy"; @Before public void setUp() throws XdsInitializationException { SharedXdsClientPoolProvider clientPoolProvider = new SharedXdsClientPoolProvider(); clientPoolProvider.setBootstrapOverride(defaultBootstrapOverride()); - xdsClientPool = clientPoolProvider.getOrCreate(); + xdsClientPool = clientPoolProvider.getOrCreate(DUMMY_TARGET); xdsClient = xdsClientPool.getObject(); } diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java index 28871850e72..24c2a43b83a 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java @@ -95,11 +95,15 @@ import io.grpc.xds.client.XdsInitializationException; import io.grpc.xds.client.XdsResourceType; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -166,15 +170,22 @@ public ConfigOrError parseServiceConfig(Map rawServiceConfig) { private XdsNameResolver resolver; private TestCall testCall; private boolean originalEnableTimeout; + private URI targetUri; @Before public void setUp() { + try { + targetUri = new URI(AUTHORITY); + } catch (URISyntaxException e) { + targetUri = null; + } + originalEnableTimeout = XdsNameResolver.enableTimeout; XdsNameResolver.enableTimeout = true; FilterRegistry filterRegistry = FilterRegistry.newRegistry().register( new FaultFilter(mockRandom, new AtomicLong()), RouterFilter.INSTANCE); - resolver = new XdsNameResolver(null, AUTHORITY, null, + resolver = new XdsNameResolver(targetUri, null, AUTHORITY, null, serviceConfigParser, syncContext, scheduler, xdsClientPoolFactory, mockRandom, filterRegistry, null); } @@ -199,16 +210,22 @@ public void setBootstrapOverride(Map bootstrap) { @Override @Nullable - public ObjectPool get() { + public ObjectPool get(String target) { throw new UnsupportedOperationException("Should not be called"); } @Override - public ObjectPool getOrCreate() throws XdsInitializationException { + public ObjectPool getOrCreate(String target) throws XdsInitializationException { throw new XdsInitializationException("Fail to read bootstrap file"); } + + @Override + public List getTargets() { + return null; + } }; - resolver = new XdsNameResolver(null, AUTHORITY, null, + + resolver = new XdsNameResolver(targetUri, null, AUTHORITY, null, serviceConfigParser, syncContext, scheduler, xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); resolver.start(mockListener); @@ -221,7 +238,7 @@ public ObjectPool getOrCreate() throws XdsInitializationException { @Test public void resolving_withTargetAuthorityNotFound() { - resolver = new XdsNameResolver( + resolver = new XdsNameResolver(targetUri, "notfound.google.com", AUTHORITY, null, serviceConfigParser, syncContext, scheduler, xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); resolver.start(mockListener); @@ -243,7 +260,7 @@ public void resolving_noTargetAuthority_templateWithoutXdstp() { String serviceAuthority = "[::FFFF:129.144.52.38]:80"; expectedLdsResourceName = "[::FFFF:129.144.52.38]:80/id=1"; resolver = new XdsNameResolver( - null, serviceAuthority, null, serviceConfigParser, syncContext, + targetUri, null, serviceAuthority, null, serviceConfigParser, syncContext, scheduler, xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); resolver.start(mockListener); @@ -264,7 +281,7 @@ public void resolving_noTargetAuthority_templateWithXdstp() { "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/" + "%5B::FFFF:129.144.52.38%5D:80?id=1"; resolver = new XdsNameResolver( - null, serviceAuthority, null, serviceConfigParser, syncContext, scheduler, + targetUri, null, serviceAuthority, null, serviceConfigParser, syncContext, scheduler, xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); resolver.start(mockListener); verify(mockListener, never()).onError(any(Status.class)); @@ -284,7 +301,7 @@ public void resolving_noTargetAuthority_xdstpWithMultipleSlashes() { "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/" + "path/to/service?id=1"; resolver = new XdsNameResolver( - null, serviceAuthority, null, serviceConfigParser, syncContext, scheduler, + targetUri, null, serviceAuthority, null, serviceConfigParser, syncContext, scheduler, xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); @@ -311,7 +328,7 @@ public void resolving_targetAuthorityInAuthoritiesMap() { .build(); expectedLdsResourceName = "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/" + "%5B::FFFF:129.144.52.38%5D:80?bar=2&foo=1"; // query param canonified - resolver = new XdsNameResolver( + resolver = new XdsNameResolver(targetUri, "xds.authority.com", serviceAuthority, null, serviceConfigParser, syncContext, scheduler, xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); resolver.start(mockListener); @@ -343,7 +360,7 @@ public void resolving_ldsResourceUpdateRdsName() { .clientDefaultListenerResourceNameTemplate("test-%s") .node(Node.newBuilder().build()) .build(); - resolver = new XdsNameResolver(null, AUTHORITY, null, + resolver = new XdsNameResolver(targetUri, null, AUTHORITY, null, serviceConfigParser, syncContext, scheduler, xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); // use different ldsResourceName and service authority. The virtualhost lookup should use @@ -524,7 +541,7 @@ public void resolving_matchingVirtualHostNotFound_matchingOverrideAuthority() { Collections.singletonList(route), ImmutableMap.of()); - resolver = new XdsNameResolver(null, AUTHORITY, "random", + resolver = new XdsNameResolver(targetUri, null, AUTHORITY, "random", serviceConfigParser, syncContext, scheduler, xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); resolver.start(mockListener); @@ -547,7 +564,7 @@ public void resolving_matchingVirtualHostNotFound_notMatchingOverrideAuthority() Collections.singletonList(route), ImmutableMap.of()); - resolver = new XdsNameResolver(null, AUTHORITY, "random", + resolver = new XdsNameResolver(targetUri, null, AUTHORITY, "random", serviceConfigParser, syncContext, scheduler, xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); resolver.start(mockListener); @@ -558,7 +575,7 @@ public void resolving_matchingVirtualHostNotFound_notMatchingOverrideAuthority() @Test public void resolving_matchingVirtualHostNotFoundForOverrideAuthority() { - resolver = new XdsNameResolver(null, AUTHORITY, AUTHORITY, + resolver = new XdsNameResolver(targetUri, null, AUTHORITY, AUTHORITY, serviceConfigParser, syncContext, scheduler, xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); resolver.start(mockListener); @@ -643,8 +660,8 @@ public void resolved_fallbackToHttpMaxStreamDurationAsTimeout() { public void retryPolicyInPerMethodConfigGeneratedByResolverIsValid() { ServiceConfigParser realParser = new ScParser( true, 5, 5, new AutoConfiguredLoadBalancerFactory("pick-first")); - resolver = new XdsNameResolver(null, AUTHORITY, null, realParser, syncContext, scheduler, - xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); + resolver = new XdsNameResolver(targetUri, null, AUTHORITY, null, realParser, syncContext, + scheduler, xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); resolver.start(mockListener); FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); RetryPolicy retryPolicy = RetryPolicy.create( @@ -847,7 +864,7 @@ public void resolved_rpcHashingByChannelId() { resolver.shutdown(); reset(mockListener); when(mockRandom.nextLong()).thenReturn(123L); - resolver = new XdsNameResolver(null, AUTHORITY, null, serviceConfigParser, + resolver = new XdsNameResolver(targetUri, null, AUTHORITY, null, serviceConfigParser, syncContext, scheduler, xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); resolver.start(mockListener); @@ -1896,17 +1913,20 @@ private PickSubchannelArgs newPickSubchannelArgs( } private final class FakeXdsClientPoolFactory implements XdsClientPoolFactory { + Set targets = new HashSet<>(); + @Override public void setBootstrapOverride(Map bootstrap) {} @Override @Nullable - public ObjectPool get() { + public ObjectPool get(String target) { throw new UnsupportedOperationException("Should not be called"); } @Override - public ObjectPool getOrCreate() throws XdsInitializationException { + public ObjectPool getOrCreate(String target) throws XdsInitializationException { + targets.add(target); return new ObjectPool() { @Override public XdsClient getObject() { @@ -1919,6 +1939,16 @@ public XdsClient returnObject(Object object) { } }; } + + @Override + public List getTargets() { + if (targets.isEmpty()) { + List targetList = new ArrayList<>(); + targetList.add(targetUri.toString()); + return targetList; + } + return new ArrayList<>(targets); + } } private class FakeXdsClient extends XdsClient { diff --git a/xds/src/test/java/io/grpc/xds/XdsServerTestHelper.java b/xds/src/test/java/io/grpc/xds/XdsServerTestHelper.java index 5d59e97335e..791318c5355 100644 --- a/xds/src/test/java/io/grpc/xds/XdsServerTestHelper.java +++ b/xds/src/test/java/io/grpc/xds/XdsServerTestHelper.java @@ -146,12 +146,12 @@ public void setBootstrapOverride(Map bootstrap) { @Override @Nullable - public ObjectPool get() { + public ObjectPool get(String target) { throw new UnsupportedOperationException("Should not be called"); } @Override - public ObjectPool getOrCreate() throws XdsInitializationException { + public ObjectPool getOrCreate(String target) throws XdsInitializationException { return new ObjectPool() { @Override public XdsClient getObject() { @@ -165,6 +165,11 @@ public XdsClient returnObject(Object object) { } }; } + + @Override + public List getTargets() { + return Collections.singletonList("fake-target"); + } } static final class FakeXdsClient extends XdsClient { diff --git a/xds/src/test/java/io/grpc/xds/XdsTestControlPlaneService.java b/xds/src/test/java/io/grpc/xds/XdsTestControlPlaneService.java index c51327dc84d..cc12e3863ba 100644 --- a/xds/src/test/java/io/grpc/xds/XdsTestControlPlaneService.java +++ b/xds/src/test/java/io/grpc/xds/XdsTestControlPlaneService.java @@ -135,12 +135,15 @@ public void run() { new Object[]{value.getResourceNamesList(), value.getErrorDetail()}); return; } + String resourceType = value.getTypeUrl(); - if (!value.getResponseNonce().isEmpty() - && !String.valueOf(xdsNonces.get(resourceType)).equals(value.getResponseNonce())) { + if (!value.getResponseNonce().isEmpty() && xdsNonces.containsKey(resourceType) + && !String.valueOf(xdsNonces.get(resourceType).get(responseObserver)) + .equals(value.getResponseNonce())) { logger.log(Level.FINE, "Resource nonce does not match, ignore."); return; } + Set requestedResourceNames = new HashSet<>(value.getResourceNamesList()); if (subscribers.get(resourceType).containsKey(responseObserver) && subscribers.get(resourceType).get(responseObserver) @@ -149,9 +152,11 @@ public void run() { value.getResourceNamesList()); return; } + if (!xdsNonces.get(resourceType).containsKey(responseObserver)) { xdsNonces.get(resourceType).put(responseObserver, new AtomicInteger(0)); } + DiscoveryResponse response = generateResponse(resourceType, String.valueOf(xdsVersions.get(resourceType)), String.valueOf(xdsNonces.get(resourceType).get(responseObserver)), From 10d6002cbda0345d2f3fd1b1d91ce5b36319fb16 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 25 Jul 2024 18:07:11 -0700 Subject: [PATCH 056/103] xds: ClusterManagerLB must update child configuration While child LB policies are unlikey to change for each cluster name (RLS returns regular cluster names, so should be unique), and the configuration for CDS policies won't change, RLS configuration can definitely change. --- .../grpc/xds/ClusterManagerLoadBalancer.java | 39 ++++++++++++------- .../ClusterManagerLoadBalancerProvider.java | 31 +++++---------- ...lusterManagerLoadBalancerProviderTest.java | 7 ++-- .../xds/ClusterManagerLoadBalancerTest.java | 24 +++++++++--- 4 files changed, 56 insertions(+), 45 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java index 6b6d2b81352..6b155545b12 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java @@ -23,11 +23,11 @@ import com.google.common.base.MoreObjects; import io.grpc.ConnectivityState; import io.grpc.InternalLogId; -import io.grpc.LoadBalancerProvider; +import io.grpc.LoadBalancer; import io.grpc.Status; import io.grpc.SynchronizationContext; import io.grpc.SynchronizationContext.ScheduledHandle; -import io.grpc.internal.ServiceConfigUtil.PolicySelection; +import io.grpc.util.GracefulSwitchLoadBalancer; import io.grpc.util.MultiChildLoadBalancer; import io.grpc.xds.ClusterManagerLoadBalancerProvider.ClusterManagerConfig; import io.grpc.xds.client.XdsLogger; @@ -71,7 +71,10 @@ class ClusterManagerLoadBalancer extends MultiChildLoadBalancer { @Override protected ResolvedAddresses getChildAddresses(Object key, ResolvedAddresses resolvedAddresses, - Object childConfig) { + Object unusedChildConfig) { + ClusterManagerConfig config = (ClusterManagerConfig) + resolvedAddresses.getLoadBalancingPolicyConfig(); + Object childConfig = config.childPolicies.get(key); return resolvedAddresses.toBuilder().setLoadBalancingPolicyConfig(childConfig).build(); } @@ -81,13 +84,12 @@ protected Map createChildLbMap(ResolvedAddresses resolvedA resolvedAddresses.getLoadBalancingPolicyConfig(); Map newChildPolicies = new HashMap<>(); if (config != null) { - for (Entry entry : config.childPolicies.entrySet()) { - ChildLbState child = getChildLbState(entry.getKey()); + for (String key : config.childPolicies.keySet()) { + ChildLbState child = getChildLbState(key); if (child == null) { - child = new ClusterManagerLbState(entry.getKey(), - entry.getValue().getProvider(), entry.getValue().getConfig()); + child = new ClusterManagerLbState(key, GracefulSwitchLoadBalancerFactory.INSTANCE, null); } - newChildPolicies.put(entry.getKey(), child); + newChildPolicies.put(key, child); } } logger.log( @@ -108,8 +110,8 @@ public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { resolvedAddresses.getLoadBalancingPolicyConfig(); ClusterManagerConfig lastConfig = (ClusterManagerConfig) lastResolvedAddresses.getLoadBalancingPolicyConfig(); - Map adjChildPolicies = new HashMap<>(config.childPolicies); - for (Entry entry : lastConfig.childPolicies.entrySet()) { + Map adjChildPolicies = new HashMap<>(config.childPolicies); + for (Entry entry : lastConfig.childPolicies.entrySet()) { ClusterManagerLbState state = (ClusterManagerLbState) getChildLbState(entry.getKey()); if (adjChildPolicies.containsKey(entry.getKey())) { if (state.deletionTimer != null) { @@ -202,9 +204,9 @@ private class ClusterManagerLbState extends ChildLbState { @Nullable ScheduledHandle deletionTimer; - public ClusterManagerLbState(Object key, LoadBalancerProvider policyProvider, + public ClusterManagerLbState(Object key, LoadBalancer.Factory policyFactory, Object childConfig) { - super(key, policyProvider, childConfig); + super(key, policyFactory, childConfig); } @Override @@ -237,8 +239,8 @@ class DeletionTask implements Runnable { public void run() { ClusterManagerConfig config = (ClusterManagerConfig) lastResolvedAddresses.getLoadBalancingPolicyConfig(); - Map childPolicies = new HashMap<>(config.childPolicies); - PolicySelection removed = childPolicies.remove(getKey()); + Map childPolicies = new HashMap<>(config.childPolicies); + Object removed = childPolicies.remove(getKey()); assert removed != null; config = new ClusterManagerConfig(childPolicies); lastResolvedAddresses = @@ -276,4 +278,13 @@ public void updateBalancingState(final ConnectivityState newState, } } } + + static final class GracefulSwitchLoadBalancerFactory extends LoadBalancer.Factory { + static final LoadBalancer.Factory INSTANCE = new GracefulSwitchLoadBalancerFactory(); + + @Override + public LoadBalancer newLoadBalancer(LoadBalancer.Helper helper) { + return new GracefulSwitchLoadBalancer(helper); + } + } } diff --git a/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancerProvider.java b/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancerProvider.java index 9c97d3fe966..7a7e16286f8 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancerProvider.java +++ b/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancerProvider.java @@ -26,12 +26,9 @@ import io.grpc.NameResolver.ConfigOrError; import io.grpc.Status; import io.grpc.internal.JsonUtil; -import io.grpc.internal.ServiceConfigUtil; -import io.grpc.internal.ServiceConfigUtil.LbConfig; -import io.grpc.internal.ServiceConfigUtil.PolicySelection; +import io.grpc.util.GracefulSwitchLoadBalancer; import java.util.Collections; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.Objects; import javax.annotation.Nullable; @@ -73,7 +70,7 @@ public String getPolicyName() { @Override public ConfigOrError parseLoadBalancingPolicyConfig(Map rawConfig) { - Map parsedChildPolicies = new LinkedHashMap<>(); + Map parsedChildPolicies = new LinkedHashMap<>(); try { Map childPolicies = JsonUtil.getObject(rawConfig, "childPolicy"); if (childPolicies == null || childPolicies.isEmpty()) { @@ -86,27 +83,19 @@ public ConfigOrError parseLoadBalancingPolicyConfig(Map rawConfig) { return ConfigOrError.fromError(Status.INTERNAL.withDescription( "No config for child " + name + " in cluster_manager LB policy: " + rawConfig)); } - List childConfigCandidates = - ServiceConfigUtil.unwrapLoadBalancingConfigList( - JsonUtil.getListOfObjects(childPolicy, "lbPolicy")); - if (childConfigCandidates == null || childConfigCandidates.isEmpty()) { - return ConfigOrError.fromError(Status.INTERNAL.withDescription( - "No config specified for child " + name + " in cluster_manager Lb policy: " - + rawConfig)); - } LoadBalancerRegistry registry = lbRegistry != null ? lbRegistry : LoadBalancerRegistry.getDefaultRegistry(); - ConfigOrError selectedConfig = - ServiceConfigUtil.selectLbPolicyFromList(childConfigCandidates, registry); - if (selectedConfig.getError() != null) { - Status error = selectedConfig.getError(); + ConfigOrError childConfig = GracefulSwitchLoadBalancer.parseLoadBalancingPolicyConfig( + JsonUtil.getListOfObjects(childPolicy, "lbPolicy"), registry); + if (childConfig.getError() != null) { + Status error = childConfig.getError(); return ConfigOrError.fromError( Status.INTERNAL .withCause(error.getCause()) .withDescription(error.getDescription()) - .augmentDescription("Failed to select config for child " + name)); + .augmentDescription("Failed to parse config for child " + name)); } - parsedChildPolicies.put(name, (PolicySelection) selectedConfig.getConfig()); + parsedChildPolicies.put(name, childConfig.getConfig()); } } catch (RuntimeException e) { return ConfigOrError.fromError( @@ -122,9 +111,9 @@ public LoadBalancer newLoadBalancer(Helper helper) { } static class ClusterManagerConfig { - final Map childPolicies; + final Map childPolicies; - ClusterManagerConfig(Map childPolicies) { + ClusterManagerConfig(Map childPolicies) { this.childPolicies = Collections.unmodifiableMap(childPolicies); } diff --git a/xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerProviderTest.java b/xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerProviderTest.java index 515f6fef3ef..40943658520 100644 --- a/xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerProviderTest.java @@ -26,7 +26,7 @@ import io.grpc.Status; import io.grpc.Status.Code; import io.grpc.internal.JsonParser; -import io.grpc.internal.ServiceConfigUtil.PolicySelection; +import io.grpc.util.GracefulSwitchLoadBalancer; import io.grpc.xds.ClusterManagerLoadBalancerProvider.ClusterManagerConfig; import java.io.IOException; import java.util.Map; @@ -133,10 +133,9 @@ public ConfigOrError parseLoadBalancingPolicyConfig( assertThat(config.childPolicies) .containsExactly( "child1", - new PolicySelection( - lbProviderFoo, fooConfig), + GracefulSwitchLoadBalancer.createLoadBalancingPolicyConfig(lbProviderFoo, fooConfig), "child2", - new PolicySelection(lbProviderBar, barConfig)); + GracefulSwitchLoadBalancer.createLoadBalancingPolicyConfig(lbProviderBar, barConfig)); } @Test diff --git a/xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerTest.java index f55b0d73f79..aa0e205dd8f 100644 --- a/xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerTest.java @@ -52,10 +52,11 @@ import io.grpc.SynchronizationContext; import io.grpc.internal.FakeClock; import io.grpc.internal.PickSubchannelArgsImpl; -import io.grpc.internal.ServiceConfigUtil.PolicySelection; import io.grpc.testing.TestMethodDescriptors; +import io.grpc.util.GracefulSwitchLoadBalancer; import io.grpc.xds.ClusterManagerLoadBalancerProvider.ClusterManagerConfig; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; @@ -288,16 +289,27 @@ private void deliverResolvedAddresses(final Map childPolicies, b .build()); } + // Prevent ClusterManagerLB from detecting different providers even when the configuration is the + // same. + private Map, FakeLoadBalancerProvider> fakeLoadBalancerProviderCache + = new HashMap<>(); + private ClusterManagerConfig buildConfig(Map childPolicies, boolean failing) { - Map childPolicySelections = new LinkedHashMap<>(); + Map childConfigs = new LinkedHashMap<>(); for (String name : childPolicies.keySet()) { String childPolicyName = childPolicies.get(name); Object childConfig = lbConfigInventory.get(name); - PolicySelection policy = - new PolicySelection(new FakeLoadBalancerProvider(childPolicyName, failing), childConfig); - childPolicySelections.put(name, policy); + FakeLoadBalancerProvider lbProvider = + fakeLoadBalancerProviderCache.get(Arrays.asList(childPolicyName, failing)); + if (lbProvider == null) { + lbProvider = new FakeLoadBalancerProvider(childPolicyName, failing); + fakeLoadBalancerProviderCache.put(Arrays.asList(childPolicyName, failing), lbProvider); + } + Object policy = + GracefulSwitchLoadBalancer.createLoadBalancingPolicyConfig(lbProvider, childConfig); + childConfigs.put(name, policy); } - return new ClusterManagerConfig(childPolicySelections); + return new ClusterManagerConfig(childConfigs); } private static PickResult pickSubchannel(SubchannelPicker picker, String clusterName) { From 01389774d55b4784deda71b22135e0ec3e5ceb9b Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 25 Jul 2024 18:21:26 -0700 Subject: [PATCH 057/103] util: Remove child policy config from MultiChildLB state The child policy config should be refreshed every address update, so it shouldn't be stored in the ChildLbState. In addition, none of the current usages actually used what was stored in the ChildLbState in a meaningful way (it was always null). ResolvedAddresses was also removed from createChildLbState(), as nothing in it should be needed for creation; it varies over time and the values passed at creation are immutable. --- .../io/grpc/util/MultiChildLoadBalancer.java | 25 ++++++------------- .../io/grpc/util/RoundRobinLoadBalancer.java | 5 ++-- .../grpc/xds/ClusterManagerLoadBalancer.java | 10 +++----- .../io/grpc/xds/LeastRequestLoadBalancer.java | 10 +++----- .../io/grpc/xds/RingHashLoadBalancer.java | 5 ++-- .../xds/WeightedRoundRobinLoadBalancer.java | 11 +++----- 6 files changed, 23 insertions(+), 43 deletions(-) diff --git a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java index 5dfb81ee4b8..4e89e537416 100644 --- a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java +++ b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java @@ -90,7 +90,7 @@ protected Map createChildLbMap(ResolvedAddresses resolvedA if (existingChildLbState != null) { childLbMap.put(endpoint, existingChildLbState); } else { - childLbMap.put(endpoint, createChildLbState(endpoint, null, resolvedAddresses)); + childLbMap.put(endpoint, createChildLbState(endpoint)); } } return childLbMap; @@ -99,9 +99,8 @@ protected Map createChildLbMap(ResolvedAddresses resolvedA /** * Override to create an instance of a subclass. */ - protected ChildLbState createChildLbState(Object key, Object policyConfig, - ResolvedAddresses resolvedAddresses) { - return new ChildLbState(key, pickFirstLbProvider, policyConfig); + protected ChildLbState createChildLbState(Object key) { + return new ChildLbState(key, pickFirstLbProvider); } /** @@ -133,11 +132,9 @@ public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { * Override this if your keys are not of type Endpoint. * @param key Key to identify the ChildLbState * @param resolvedAddresses list of addresses which include attributes - * @param childConfig a load balancing policy config. This field is optional. * @return a fully loaded ResolvedAddresses object for the specified key */ - protected ResolvedAddresses getChildAddresses(Object key, ResolvedAddresses resolvedAddresses, - Object childConfig) { + protected ResolvedAddresses getChildAddresses(Object key, ResolvedAddresses resolvedAddresses) { Endpoint endpointKey; if (key instanceof EquivalentAddressGroup) { endpointKey = new Endpoint((EquivalentAddressGroup) key); @@ -160,7 +157,7 @@ protected ResolvedAddresses getChildAddresses(Object key, ResolvedAddresses reso return resolvedAddresses.toBuilder() .setAddresses(Collections.singletonList(eagToUse)) .setAttributes(Attributes.newBuilder().set(IS_PETIOLE_POLICY, true).build()) - .setLoadBalancingPolicyConfig(childConfig) + .setLoadBalancingPolicyConfig(null) .build(); } @@ -226,10 +223,8 @@ private void addMissingChildren(Map newChildren) { private void updateChildrenWithResolvedAddresses(ResolvedAddresses resolvedAddresses, Map newChildren) { for (Map.Entry entry : newChildren.entrySet()) { - Object childConfig = entry.getValue().getConfig(); ChildLbState childLbState = childLbStates.get(entry.getKey()); - ResolvedAddresses childAddresses = - getChildAddresses(entry.getKey(), resolvedAddresses, childConfig); + ResolvedAddresses childAddresses = getChildAddresses(entry.getKey(), resolvedAddresses); childLbState.setResolvedAddresses(childAddresses); // update child childLbState.lb.handleResolvedAddresses(childAddresses); // update child LB } @@ -328,15 +323,13 @@ protected final List getReadyChildren() { public class ChildLbState { private final Object key; private ResolvedAddresses resolvedAddresses; - private final Object config; private final LoadBalancer lb; private ConnectivityState currentState; private SubchannelPicker currentPicker = new FixedResultPicker(PickResult.withNoResult()); - public ChildLbState(Object key, LoadBalancer.Factory policyFactory, Object childConfig) { + public ChildLbState(Object key, LoadBalancer.Factory policyFactory) { this.key = key; - this.config = childConfig; this.lb = policyFactory.newLoadBalancer(createChildHelper()); this.currentState = CONNECTING; } @@ -400,10 +393,6 @@ protected final void setResolvedAddresses(ResolvedAddresses newAddresses) { resolvedAddresses = newAddresses; } - private Object getConfig() { - return config; - } - @VisibleForTesting public final ResolvedAddresses getResolvedAddresses() { return resolvedAddresses; diff --git a/util/src/main/java/io/grpc/util/RoundRobinLoadBalancer.java b/util/src/main/java/io/grpc/util/RoundRobinLoadBalancer.java index 0804978cf7f..22940e875ac 100644 --- a/util/src/main/java/io/grpc/util/RoundRobinLoadBalancer.java +++ b/util/src/main/java/io/grpc/util/RoundRobinLoadBalancer.java @@ -96,9 +96,8 @@ private SubchannelPicker createReadyPicker(Collection children) { } @Override - protected ChildLbState createChildLbState(Object key, Object policyConfig, - ResolvedAddresses resolvedAddresses) { - return new ChildLbState(key, pickFirstLbProvider, policyConfig) { + protected ChildLbState createChildLbState(Object key) { + return new ChildLbState(key, pickFirstLbProvider) { @Override protected ChildLbStateHelper createChildHelper() { return new ChildLbStateHelper() { diff --git a/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java index 6b155545b12..44fa5313a06 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java @@ -70,8 +70,7 @@ class ClusterManagerLoadBalancer extends MultiChildLoadBalancer { } @Override - protected ResolvedAddresses getChildAddresses(Object key, ResolvedAddresses resolvedAddresses, - Object unusedChildConfig) { + protected ResolvedAddresses getChildAddresses(Object key, ResolvedAddresses resolvedAddresses) { ClusterManagerConfig config = (ClusterManagerConfig) resolvedAddresses.getLoadBalancingPolicyConfig(); Object childConfig = config.childPolicies.get(key); @@ -87,7 +86,7 @@ protected Map createChildLbMap(ResolvedAddresses resolvedA for (String key : config.childPolicies.keySet()) { ChildLbState child = getChildLbState(key); if (child == null) { - child = new ClusterManagerLbState(key, GracefulSwitchLoadBalancerFactory.INSTANCE, null); + child = new ClusterManagerLbState(key, GracefulSwitchLoadBalancerFactory.INSTANCE); } newChildPolicies.put(key, child); } @@ -204,9 +203,8 @@ private class ClusterManagerLbState extends ChildLbState { @Nullable ScheduledHandle deletionTimer; - public ClusterManagerLbState(Object key, LoadBalancer.Factory policyFactory, - Object childConfig) { - super(key, policyFactory, childConfig); + public ClusterManagerLbState(Object key, LoadBalancer.Factory policyFactory) { + super(key, policyFactory); } @Override diff --git a/xds/src/main/java/io/grpc/xds/LeastRequestLoadBalancer.java b/xds/src/main/java/io/grpc/xds/LeastRequestLoadBalancer.java index 52fa1298e60..6c13530ff49 100644 --- a/xds/src/main/java/io/grpc/xds/LeastRequestLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/LeastRequestLoadBalancer.java @@ -126,9 +126,8 @@ protected void updateOverallBalancingState() { } @Override - protected ChildLbState createChildLbState(Object key, Object policyConfig, - ResolvedAddresses unused) { - return new LeastRequestLbState(key, pickFirstLbProvider, policyConfig); + protected ChildLbState createChildLbState(Object key) { + return new LeastRequestLbState(key, pickFirstLbProvider); } private void updateBalancingState(ConnectivityState state, SubchannelPicker picker) { @@ -320,9 +319,8 @@ public String toString() { protected class LeastRequestLbState extends ChildLbState { private final AtomicInteger activeRequests = new AtomicInteger(0); - public LeastRequestLbState(Object key, LoadBalancerProvider policyProvider, - Object childConfig) { - super(key, policyProvider, childConfig); + public LeastRequestLbState(Object key, LoadBalancerProvider policyProvider) { + super(key, policyProvider); } int getActiveRequests() { diff --git a/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java b/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java index 74ea9dbb111..4f93974b52c 100644 --- a/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java @@ -219,9 +219,8 @@ protected void updateOverallBalancingState() { } @Override - protected ChildLbState createChildLbState(Object key, Object policyConfig, - ResolvedAddresses resolvedAddresses) { - return new ChildLbState(key, lazyLbFactory, null); + protected ChildLbState createChildLbState(Object key) { + return new ChildLbState(key, lazyLbFactory); } private Status validateAddrList(List addrList) { diff --git a/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java b/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java index 65cd146fdd3..73764c63c80 100644 --- a/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java @@ -148,10 +148,8 @@ public WeightedRoundRobinLoadBalancer(Helper helper, Ticker ticker) { } @Override - protected ChildLbState createChildLbState(Object key, Object policyConfig, - ResolvedAddresses unused) { - ChildLbState childLbState = new WeightedChildLbState(key, pickFirstLbProvider, policyConfig); - return childLbState; + protected ChildLbState createChildLbState(Object key) { + return new WeightedChildLbState(key, pickFirstLbProvider); } @Override @@ -289,9 +287,8 @@ final class WeightedChildLbState extends ChildLbState { private OrcaReportListener orcaReportListener; - public WeightedChildLbState( - Object key, LoadBalancerProvider policyProvider, Object childConfig) { - super(key, policyProvider, childConfig); + public WeightedChildLbState(Object key, LoadBalancerProvider policyProvider) { + super(key, policyProvider); } @Override From 4cb6465194128fa01161984baf21ab3ec0381e1d Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Mon, 29 Jul 2024 11:31:18 -0700 Subject: [PATCH 058/103] util: MultiChildLB children know if they are active No need to look up in the map to see if they are still a child. --- util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java | 2 +- .../main/java/io/grpc/xds/ClusterManagerLoadBalancer.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java index 4e89e537416..5c37b4a34ed 100644 --- a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java +++ b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java @@ -414,7 +414,7 @@ protected class ChildLbStateHelper extends ForwardingLoadBalancerHelper { @Override public void updateBalancingState(final ConnectivityState newState, final SubchannelPicker newPicker) { - if (!childLbStates.containsKey(key)) { + if (currentState == SHUTDOWN) { return; } diff --git a/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java index 44fa5313a06..5aafd8240af 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java @@ -260,9 +260,7 @@ private class ClusterManagerChildHelper extends ChildLbStateHelper { @Override public void updateBalancingState(final ConnectivityState newState, final SubchannelPicker newPicker) { - // If we are already in the process of resolving addresses, the overall balancing state - // will be updated at the end of it, and we don't need to trigger that update here. - if (getChildLbState(getKey()) == null) { + if (getCurrentState() == ConnectivityState.SHUTDOWN) { return; } @@ -270,6 +268,8 @@ public void updateBalancingState(final ConnectivityState newState, // when the child instance exits deactivated state. setCurrentState(newState); setCurrentPicker(newPicker); + // If we are already in the process of resolving addresses, the overall balancing state + // will be updated at the end of it, and we don't need to trigger that update here. if (deletionTimer == null && !resolvingAddresses) { updateOverallBalancingState(); } From cfecc4754b603e950f6f65ee1a2deb697c3f9a61 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Fri, 26 Jul 2024 09:19:14 -0700 Subject: [PATCH 059/103] Focus MultiChildLB updates around ResolvedAddresses of children This makes ClusterManagerLB more straight-forward, focusing on just the things that are relevant to it, and it avoids specialized map key handling in updateChildrenWithResolvedAddresses(). --- .../io/grpc/util/MultiChildLoadBalancer.java | 96 +++++-------------- .../grpc/xds/ClusterManagerLoadBalancer.java | 27 +++--- 2 files changed, 38 insertions(+), 85 deletions(-) diff --git a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java index 5c37b4a34ed..626c2e1104e 100644 --- a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java +++ b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java @@ -16,7 +16,6 @@ package io.grpc.util; -import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static io.grpc.ConnectivityState.CONNECTING; import static io.grpc.ConnectivityState.IDLE; @@ -80,20 +79,20 @@ protected MultiChildLoadBalancer(Helper helper) { /** * Override to utilize parsing of the policy configuration or alternative helper/lb generation. + * Override this if keys are not Endpoints or if child policies have configuration. */ - protected Map createChildLbMap(ResolvedAddresses resolvedAddresses) { - Map childLbMap = new HashMap<>(); - List addresses = resolvedAddresses.getAddresses(); - for (EquivalentAddressGroup eag : addresses) { - Endpoint endpoint = new Endpoint(eag); // keys need to be just addresses - ChildLbState existingChildLbState = childLbStates.get(endpoint); - if (existingChildLbState != null) { - childLbMap.put(endpoint, existingChildLbState); - } else { - childLbMap.put(endpoint, createChildLbState(endpoint)); - } - } - return childLbMap; + protected Map createChildAddressesMap( + ResolvedAddresses resolvedAddresses) { + Map childAddresses = new HashMap<>(); + for (EquivalentAddressGroup eag : resolvedAddresses.getAddresses()) { + ResolvedAddresses addresses = resolvedAddresses.toBuilder() + .setAddresses(Collections.singletonList(eag)) + .setAttributes(Attributes.newBuilder().set(IS_PETIOLE_POLICY, true).build()) + .setLoadBalancingPolicyConfig(null) + .build(); + childAddresses.put(new Endpoint(eag), addresses); + } + return childAddresses; } /** @@ -128,39 +127,6 @@ public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { } } - /** - * Override this if your keys are not of type Endpoint. - * @param key Key to identify the ChildLbState - * @param resolvedAddresses list of addresses which include attributes - * @return a fully loaded ResolvedAddresses object for the specified key - */ - protected ResolvedAddresses getChildAddresses(Object key, ResolvedAddresses resolvedAddresses) { - Endpoint endpointKey; - if (key instanceof EquivalentAddressGroup) { - endpointKey = new Endpoint((EquivalentAddressGroup) key); - } else { - checkArgument(key instanceof Endpoint, "key is wrong type"); - endpointKey = (Endpoint) key; - } - - // Retrieve the non-stripped version - EquivalentAddressGroup eagToUse = null; - for (EquivalentAddressGroup currEag : resolvedAddresses.getAddresses()) { - if (endpointKey.equals(new Endpoint(currEag))) { - eagToUse = currEag; - break; - } - } - - checkNotNull(eagToUse, key + " no longer present in load balancer children"); - - return resolvedAddresses.toBuilder() - .setAddresses(Collections.singletonList(eagToUse)) - .setAttributes(Attributes.newBuilder().set(IS_PETIOLE_POLICY, true).build()) - .setLoadBalancingPolicyConfig(null) - .build(); - } - /** * Handle the name resolution error. * @@ -192,41 +158,31 @@ protected final AcceptResolvedAddrRetVal acceptResolvedAddressesInternal( ResolvedAddresses resolvedAddresses) { logger.log(Level.FINE, "Received resolution result: {0}", resolvedAddresses); - // Subclass handles any special manipulation to create appropriate types of keyed ChildLbStates - Map newChildren = createChildLbMap(resolvedAddresses); + Map newChildAddresses = createChildAddressesMap(resolvedAddresses); // Handle error case - if (newChildren.isEmpty()) { + if (newChildAddresses.isEmpty()) { Status unavailableStatus = Status.UNAVAILABLE.withDescription( "NameResolver returned no usable address. " + resolvedAddresses); handleNameResolutionError(unavailableStatus); return new AcceptResolvedAddrRetVal(unavailableStatus, null); } - addMissingChildren(newChildren); + updateChildrenWithResolvedAddresses(newChildAddresses); - updateChildrenWithResolvedAddresses(resolvedAddresses, newChildren); - - return new AcceptResolvedAddrRetVal(Status.OK, getRemovedChildren(newChildren.keySet())); + return new AcceptResolvedAddrRetVal(Status.OK, getRemovedChildren(newChildAddresses.keySet())); } - private void addMissingChildren(Map newChildren) { - // Do adds and identify reused children - for (Map.Entry entry : newChildren.entrySet()) { - final Object key = entry.getKey(); - if (!childLbStates.containsKey(key)) { - childLbStates.put(key, entry.getValue()); - } - } - } - - private void updateChildrenWithResolvedAddresses(ResolvedAddresses resolvedAddresses, - Map newChildren) { - for (Map.Entry entry : newChildren.entrySet()) { + private void updateChildrenWithResolvedAddresses( + Map newChildAddresses) { + for (Map.Entry entry : newChildAddresses.entrySet()) { ChildLbState childLbState = childLbStates.get(entry.getKey()); - ResolvedAddresses childAddresses = getChildAddresses(entry.getKey(), resolvedAddresses); - childLbState.setResolvedAddresses(childAddresses); // update child - childLbState.lb.handleResolvedAddresses(childAddresses); // update child LB + if (childLbState == null) { + childLbState = createChildLbState(entry.getKey()); + childLbStates.put(entry.getKey(), childLbState); + } + childLbState.setResolvedAddresses(entry.getValue()); // update child + childLbState.lb.handleResolvedAddresses(entry.getValue()); // update child LB } } diff --git a/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java index 5aafd8240af..c175b847c63 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java @@ -70,31 +70,28 @@ class ClusterManagerLoadBalancer extends MultiChildLoadBalancer { } @Override - protected ResolvedAddresses getChildAddresses(Object key, ResolvedAddresses resolvedAddresses) { - ClusterManagerConfig config = (ClusterManagerConfig) - resolvedAddresses.getLoadBalancingPolicyConfig(); - Object childConfig = config.childPolicies.get(key); - return resolvedAddresses.toBuilder().setLoadBalancingPolicyConfig(childConfig).build(); + protected ChildLbState createChildLbState(Object key) { + return new ClusterManagerLbState(key, GracefulSwitchLoadBalancerFactory.INSTANCE); } @Override - protected Map createChildLbMap(ResolvedAddresses resolvedAddresses) { + protected Map createChildAddressesMap( + ResolvedAddresses resolvedAddresses) { ClusterManagerConfig config = (ClusterManagerConfig) resolvedAddresses.getLoadBalancingPolicyConfig(); - Map newChildPolicies = new HashMap<>(); + Map childAddresses = new HashMap<>(); if (config != null) { - for (String key : config.childPolicies.keySet()) { - ChildLbState child = getChildLbState(key); - if (child == null) { - child = new ClusterManagerLbState(key, GracefulSwitchLoadBalancerFactory.INSTANCE); - } - newChildPolicies.put(key, child); + for (Map.Entry childPolicy : config.childPolicies.entrySet()) { + ResolvedAddresses addresses = resolvedAddresses.toBuilder() + .setLoadBalancingPolicyConfig(childPolicy.getValue()) + .build(); + childAddresses.put(childPolicy.getKey(), addresses); } } logger.log( XdsLogLevel.INFO, - "Received cluster_manager lb config: child names={0}", newChildPolicies.keySet()); - return newChildPolicies; + "Received cluster_manager lb config: child names={0}", childAddresses.keySet()); + return childAddresses; } /** From c63e3548835e838ded2f9eaf3be03dcb6b2b53a7 Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Thu, 29 Aug 2024 16:12:59 -0700 Subject: [PATCH 060/103] rls: Fix log statements incorrectly referring to "LRS" (#11497) --- rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java b/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java index 0dcffadeb40..d0661ba3be8 100644 --- a/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java +++ b/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java @@ -329,7 +329,7 @@ final CachedRouteLookupResponse get(final RouteLookupRequest request) { final CacheEntry cacheEntry; cacheEntry = linkedHashLruCache.read(request); if (cacheEntry == null) { - logger.log(ChannelLogLevel.DEBUG, "No cache entry found, making a new lrs request"); + logger.log(ChannelLogLevel.DEBUG, "No cache entry found, making a new RLS request"); PendingCacheEntry pendingEntry = pendingCallCache.get(request); if (pendingEntry != null) { return CachedRouteLookupResponse.pendingResponse(pendingEntry); @@ -988,7 +988,7 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { new Object[]{serviceName, methodName, args.getHeaders(), response}); if (response.getHeaderData() != null && !response.getHeaderData().isEmpty()) { - logger.log(ChannelLogLevel.DEBUG, "Updating LRS metadata from the LRS response headers"); + logger.log(ChannelLogLevel.DEBUG, "Updating RLS metadata from the RLS response headers"); Metadata headers = args.getHeaders(); headers.discardAll(RLS_DATA_KEY); headers.put(RLS_DATA_KEY, response.getHeaderData()); @@ -997,7 +997,7 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { logger.log(ChannelLogLevel.DEBUG, "defaultTarget = {0}", defaultTarget); boolean hasFallback = defaultTarget != null && !defaultTarget.isEmpty(); if (response.hasData()) { - logger.log(ChannelLogLevel.DEBUG, "LRS response has data, proceed with selecting a picker"); + logger.log(ChannelLogLevel.DEBUG, "RLS response has data, proceed with selecting a picker"); ChildPolicyWrapper childPolicyWrapper = response.getChildPolicyWrapper(); SubchannelPicker picker = (childPolicyWrapper != null) ? childPolicyWrapper.getPicker() : null; From 421e2371e9136a768fa878318af4a96b4d11f784 Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Fri, 30 Aug 2024 12:17:28 -0700 Subject: [PATCH 061/103] add OpenTelemetryTracingModule (#11477) --- .../OpenTelemetryTracingModule.java | 408 ++++++++++++ .../OpenTelemetryTracingModuleTest.java | 582 ++++++++++++++++++ 2 files changed, 990 insertions(+) create mode 100644 opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryTracingModule.java create mode 100644 opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryTracingModuleTest.java diff --git a/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryTracingModule.java b/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryTracingModule.java new file mode 100644 index 00000000000..11659c87708 --- /dev/null +++ b/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryTracingModule.java @@ -0,0 +1,408 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.opentelemetry; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.grpc.ClientStreamTracer.NAME_RESOLUTION_DELAYED; + +import com.google.common.annotations.VisibleForTesting; +import io.grpc.Attributes; +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ClientStreamTracer; +import io.grpc.ForwardingClientCall.SimpleForwardingClientCall; +import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.ServerStreamTracer; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.ContextPropagators; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.Nullable; + +/** + * Provides factories for {@link io.grpc.StreamTracer} that records tracing to OpenTelemetry. + */ +final class OpenTelemetryTracingModule { + private static final Logger logger = Logger.getLogger(OpenTelemetryTracingModule.class.getName()); + + @VisibleForTesting + static final String OTEL_TRACING_SCOPE_NAME = "grpc-java"; + @Nullable + private static final AtomicIntegerFieldUpdater callEndedUpdater; + @Nullable + private static final AtomicIntegerFieldUpdater streamClosedUpdater; + + /* + * When using Atomic*FieldUpdater, some Samsung Android 5.0.x devices encounter a bug in their JDK + * reflection API that triggers a NoSuchFieldException. When this occurs, we fallback to + * (potentially racy) direct updates of the volatile variables. + */ + static { + AtomicIntegerFieldUpdater tmpCallEndedUpdater; + AtomicIntegerFieldUpdater tmpStreamClosedUpdater; + try { + tmpCallEndedUpdater = + AtomicIntegerFieldUpdater.newUpdater(CallAttemptsTracerFactory.class, "callEnded"); + tmpStreamClosedUpdater = + AtomicIntegerFieldUpdater.newUpdater(ServerTracer.class, "streamClosed"); + } catch (Throwable t) { + logger.log(Level.SEVERE, "Creating atomic field updaters failed", t); + tmpCallEndedUpdater = null; + tmpStreamClosedUpdater = null; + } + callEndedUpdater = tmpCallEndedUpdater; + streamClosedUpdater = tmpStreamClosedUpdater; + } + + private final Tracer otelTracer; + private final ContextPropagators contextPropagators; + private final MetadataGetter metadataGetter = MetadataGetter.getInstance(); + private final MetadataSetter metadataSetter = MetadataSetter.getInstance(); + private final TracingClientInterceptor clientInterceptor = new TracingClientInterceptor(); + private final ServerTracerFactory serverTracerFactory = new ServerTracerFactory(); + + OpenTelemetryTracingModule(OpenTelemetry openTelemetry) { + this.otelTracer = checkNotNull(openTelemetry.getTracer(OTEL_TRACING_SCOPE_NAME), "otelTracer"); + this.contextPropagators = checkNotNull(openTelemetry.getPropagators(), "contextPropagators"); + } + + /** + * Creates a {@link CallAttemptsTracerFactory} for a new call. + */ + @VisibleForTesting + CallAttemptsTracerFactory newClientCallTracer(Span clientSpan, MethodDescriptor method) { + return new CallAttemptsTracerFactory(clientSpan, method); + } + + /** + * Returns the server tracer factory. + */ + ServerStreamTracer.Factory getServerTracerFactory() { + return serverTracerFactory; + } + + /** + * Returns the client interceptor that facilitates otel tracing reporting. + */ + ClientInterceptor getClientInterceptor() { + return clientInterceptor; + } + + @VisibleForTesting + final class CallAttemptsTracerFactory extends ClientStreamTracer.Factory { + volatile int callEnded; + private final Span clientSpan; + private final String fullMethodName; + + CallAttemptsTracerFactory(Span clientSpan, MethodDescriptor method) { + checkNotNull(method, "method"); + this.fullMethodName = checkNotNull(method.getFullMethodName(), "fullMethodName"); + this.clientSpan = checkNotNull(clientSpan, "clientSpan"); + } + + @Override + public ClientStreamTracer newClientStreamTracer( + ClientStreamTracer.StreamInfo info, Metadata headers) { + Span attemptSpan = otelTracer.spanBuilder( + "Attempt." + fullMethodName.replace('/', '.')) + .setParent(Context.current().with(clientSpan)) + .startSpan(); + attemptSpan.setAttribute( + "previous-rpc-attempts", info.getPreviousAttempts()); + attemptSpan.setAttribute( + "transparent-retry",info.isTransparentRetry()); + if (info.getCallOptions().getOption(NAME_RESOLUTION_DELAYED) != null) { + clientSpan.addEvent("Delayed name resolution complete"); + } + return new ClientTracer(attemptSpan, clientSpan); + } + + /** + * Record a finished call and mark the current time as the end time. + * + *

Can be called from any thread without synchronization. Calling it the second time or more + * is a no-op. + */ + void callEnded(io.grpc.Status status) { + if (callEndedUpdater != null) { + if (callEndedUpdater.getAndSet(this, 1) != 0) { + return; + } + } else { + if (callEnded != 0) { + return; + } + callEnded = 1; + } + endSpanWithStatus(clientSpan, status); + } + } + + private final class ClientTracer extends ClientStreamTracer { + private final Span span; + private final Span parentSpan; + volatile int seqNo; + boolean isPendingStream; + + ClientTracer(Span span, Span parentSpan) { + this.span = checkNotNull(span, "span"); + this.parentSpan = checkNotNull(parentSpan, "parent span"); + } + + @Override + public void streamCreated(Attributes transportAtts, Metadata headers) { + contextPropagators.getTextMapPropagator().inject(Context.current().with(span), headers, + metadataSetter); + if (isPendingStream) { + span.addEvent("Delayed LB pick complete"); + } + } + + @Override + public void createPendingStream() { + isPendingStream = true; + } + + @Override + public void outboundMessageSent( + int seqNo, long optionalWireSize, long optionalUncompressedSize) { + recordOutboundMessageSentEvent(span, seqNo, optionalWireSize, optionalUncompressedSize); + } + + @Override + public void inboundMessageRead( + int seqNo, long optionalWireSize, long optionalUncompressedSize) { + //TODO(yifeizhuang): needs support from message deframer. + if (optionalWireSize != optionalUncompressedSize) { + recordInboundCompressedMessage(span, seqNo, optionalWireSize); + } + } + + @Override + public void inboundMessage(int seqNo) { + this.seqNo = seqNo; + } + + @Override + public void inboundUncompressedSize(long bytes) { + recordInboundMessageSize(parentSpan, seqNo, bytes); + } + + @Override + public void streamClosed(io.grpc.Status status) { + endSpanWithStatus(span, status); + } + } + + private final class ServerTracer extends ServerStreamTracer { + private final Span span; + volatile int streamClosed; + private int seqNo; + + ServerTracer(String fullMethodName, @Nullable Span remoteSpan) { + checkNotNull(fullMethodName, "fullMethodName"); + this.span = + otelTracer.spanBuilder(generateTraceSpanName(true, fullMethodName)) + .setParent(remoteSpan == null ? null : Context.current().with(remoteSpan)) + .startSpan(); + } + + /** + * Record a finished stream and mark the current time as the end time. + * + *

Can be called from any thread without synchronization. Calling it the second time or more + * is a no-op. + */ + @Override + public void streamClosed(io.grpc.Status status) { + if (streamClosedUpdater != null) { + if (streamClosedUpdater.getAndSet(this, 1) != 0) { + return; + } + } else { + if (streamClosed != 0) { + return; + } + streamClosed = 1; + } + endSpanWithStatus(span, status); + } + + @Override + public void outboundMessageSent( + int seqNo, long optionalWireSize, long optionalUncompressedSize) { + recordOutboundMessageSentEvent(span, seqNo, optionalWireSize, optionalUncompressedSize); + } + + @Override + public void inboundMessageRead( + int seqNo, long optionalWireSize, long optionalUncompressedSize) { + if (optionalWireSize != optionalUncompressedSize) { + recordInboundCompressedMessage(span, seqNo, optionalWireSize); + } + } + + @Override + public void inboundMessage(int seqNo) { + this.seqNo = seqNo; + } + + @Override + public void inboundUncompressedSize(long bytes) { + recordInboundMessageSize(span, seqNo, bytes); + } + } + + @VisibleForTesting + final class ServerTracerFactory extends ServerStreamTracer.Factory { + @SuppressWarnings("ReferenceEquality") + @Override + public ServerStreamTracer newServerStreamTracer(String fullMethodName, Metadata headers) { + Context context = contextPropagators.getTextMapPropagator().extract( + Context.current(), headers, metadataGetter + ); + Span remoteSpan = Span.fromContext(context); + if (remoteSpan == Span.getInvalid()) { + remoteSpan = null; + } + return new ServerTracer(fullMethodName, remoteSpan); + } + } + + @VisibleForTesting + final class TracingClientInterceptor implements ClientInterceptor { + + @Override + public ClientCall interceptCall( + MethodDescriptor method, CallOptions callOptions, Channel next) { + Span clientSpan = otelTracer.spanBuilder( + generateTraceSpanName(false, method.getFullMethodName())) + .startSpan(); + + final CallAttemptsTracerFactory tracerFactory = newClientCallTracer(clientSpan, method); + ClientCall call = + next.newCall( + method, + callOptions.withStreamTracerFactory(tracerFactory)); + return new SimpleForwardingClientCall(call) { + @Override + public void start(Listener responseListener, Metadata headers) { + delegate().start( + new SimpleForwardingClientCallListener(responseListener) { + @Override + public void onClose(io.grpc.Status status, Metadata trailers) { + tracerFactory.callEnded(status); + super.onClose(status, trailers); + } + }, + headers); + } + }; + } + } + + // Attribute named "message-size" always means the message size the application sees. + // If there was compression, additional event reports "message-size-compressed". + // + // An example trace with message compression: + // + // Sending: + // |-- Event 'Outbound message sent', attributes('sequence-numer' = 0, 'message-size' = 7854, + // 'message-size-compressed' = 5493) ----| + // + // Receiving: + // |-- Event 'Inbound compressed message', attributes('sequence-numer' = 0, + // 'message-size-compressed' = 5493 ) ----| + // |-- Event 'Inbound message received', attributes('sequence-numer' = 0, + // 'message-size' = 7854) ----| + // + // An example trace with no message compression: + // + // Sending: + // |-- Event 'Outbound message sent', attributes('sequence-numer' = 0, 'message-size' = 7854) ---| + // + // Receiving: + // |-- Event 'Inbound message received', attributes('sequence-numer' = 0, + // 'message-size' = 7854) ----| + private void recordOutboundMessageSentEvent(Span span, + int seqNo, long optionalWireSize, long optionalUncompressedSize) { + AttributesBuilder attributesBuilder = io.opentelemetry.api.common.Attributes.builder(); + attributesBuilder.put("sequence-number", seqNo); + if (optionalUncompressedSize != -1) { + attributesBuilder.put("message-size", optionalUncompressedSize); + } + if (optionalWireSize != -1 && optionalWireSize != optionalUncompressedSize) { + attributesBuilder.put("message-size-compressed", optionalWireSize); + } + span.addEvent("Outbound message sent", attributesBuilder.build()); + } + + private void recordInboundCompressedMessage(Span span, int seqNo, long optionalWireSize) { + AttributesBuilder attributesBuilder = io.opentelemetry.api.common.Attributes.builder(); + attributesBuilder.put("sequence-number", seqNo); + attributesBuilder.put("message-size-compressed", optionalWireSize); + span.addEvent("Inbound compressed message", attributesBuilder.build()); + } + + private void recordInboundMessageSize(Span span, int seqNo, long bytes) { + AttributesBuilder attributesBuilder = io.opentelemetry.api.common.Attributes.builder(); + attributesBuilder.put("sequence-number", seqNo); + attributesBuilder.put("message-size", bytes); + span.addEvent("Inbound message received", attributesBuilder.build()); + } + + private String generateErrorStatusDescription(io.grpc.Status status) { + if (status.getDescription() != null) { + return status.getCode() + ": " + status.getDescription(); + } else { + return status.getCode().toString(); + } + } + + private void endSpanWithStatus(Span span, io.grpc.Status status) { + if (status.isOk()) { + span.setStatus(StatusCode.OK); + } else { + span.setStatus(StatusCode.ERROR, generateErrorStatusDescription(status)); + } + span.end(); + } + + /** + * Convert a full method name to a tracing span name. + * + * @param isServer {@code false} if the span is on the client-side, {@code true} if on the + * server-side + * @param fullMethodName the method name as returned by + * {@link MethodDescriptor#getFullMethodName}. + */ + @VisibleForTesting + static String generateTraceSpanName(boolean isServer, String fullMethodName) { + String prefix = isServer ? "Recv" : "Sent"; + return prefix + "." + fullMethodName.replace('/', '.'); + } +} diff --git a/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryTracingModuleTest.java b/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryTracingModuleTest.java new file mode 100644 index 00000000000..68cba17e802 --- /dev/null +++ b/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryTracingModuleTest.java @@ -0,0 +1,582 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.opentelemetry; + +import static io.grpc.ClientStreamTracer.NAME_RESOLUTION_DELAYED; +import static io.grpc.opentelemetry.OpenTelemetryTracingModule.OTEL_TRACING_SCOPE_NAME; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import com.google.common.collect.ImmutableSet; +import io.grpc.Attributes; +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ClientInterceptors; +import io.grpc.ClientStreamTracer; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerServiceDefinition; +import io.grpc.ServerStreamTracer; +import io.grpc.Status; +import io.grpc.opentelemetry.OpenTelemetryTracingModule.CallAttemptsTracerFactory; +import io.grpc.testing.GrpcServerRule; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.SpanId; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.TraceId; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.sdk.testing.junit4.OpenTelemetryRule; +import io.opentelemetry.sdk.trace.data.EventData; +import io.opentelemetry.sdk.trace.data.SpanData; +import java.io.InputStream; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@RunWith(JUnit4.class) +public class OpenTelemetryTracingModuleTest { + @Rule + public final MockitoRule mocks = MockitoJUnit.rule(); + + private static final ClientStreamTracer.StreamInfo STREAM_INFO = + ClientStreamTracer.StreamInfo.newBuilder() + .setCallOptions(CallOptions.DEFAULT.withOption(NAME_RESOLUTION_DELAYED, 10L)).build(); + private static final CallOptions.Key CUSTOM_OPTION = + CallOptions.Key.createWithDefault("option1", "default"); + private static final CallOptions CALL_OPTIONS = + CallOptions.DEFAULT.withOption(CUSTOM_OPTION, "customvalue"); + + private static class StringInputStream extends InputStream { + final String string; + + StringInputStream(String string) { + this.string = string; + } + + @Override + public int read() { + // InProcessTransport doesn't actually read bytes from the InputStream. The InputStream is + // passed to the InProcess server and consumed by MARSHALLER.parse(). + throw new UnsupportedOperationException("Should not be called"); + } + } + + private static final MethodDescriptor.Marshaller MARSHALLER = + new MethodDescriptor.Marshaller() { + @Override + public InputStream stream(String value) { + return new StringInputStream(value); + } + + @Override + public String parse(InputStream stream) { + return ((StringInputStream) stream).string; + } + }; + + private final MethodDescriptor method = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNKNOWN) + .setRequestMarshaller(MARSHALLER) + .setResponseMarshaller(MARSHALLER) + .setFullMethodName("package1.service2/method3") + .build(); + + @Rule + public final OpenTelemetryRule openTelemetryRule = OpenTelemetryRule.create(); + @Rule + public final GrpcServerRule grpcServerRule = new GrpcServerRule().directExecutor(); + private Tracer tracerRule; + @Mock + private Tracer mockTracer; + @Mock + TextMapPropagator mockPropagator; + @Mock + private Span mockClientSpan; + @Mock + private Span mockAttemptSpan; + @Mock + private ServerCall.Listener mockServerCallListener; + @Mock + private ClientCall.Listener mockClientCallListener; + @Mock + private SpanBuilder mockSpanBuilder; + @Mock + private OpenTelemetry mockOpenTelemetry; + @Captor + private ArgumentCaptor eventNameCaptor; + @Captor + private ArgumentCaptor attributesCaptor; + @Captor + private ArgumentCaptor statusCaptor; + + @Before + public void setUp() { + tracerRule = openTelemetryRule.getOpenTelemetry().getTracer(OTEL_TRACING_SCOPE_NAME); + when(mockOpenTelemetry.getTracer(OTEL_TRACING_SCOPE_NAME)).thenReturn(mockTracer); + when(mockOpenTelemetry.getPropagators()).thenReturn(ContextPropagators.create(mockPropagator)); + when(mockSpanBuilder.startSpan()).thenReturn(mockAttemptSpan); + when(mockSpanBuilder.setParent(any())).thenReturn(mockSpanBuilder); + when(mockTracer.spanBuilder(any())).thenReturn(mockSpanBuilder); + } + + // Use mock instead of OpenTelemetryRule to verify inOrder and propagator. + @Test + public void clientBasicTracingMocking() { + OpenTelemetryTracingModule tracingModule = new OpenTelemetryTracingModule(mockOpenTelemetry); + CallAttemptsTracerFactory callTracer = + tracingModule.newClientCallTracer(mockClientSpan, method); + Metadata headers = new Metadata(); + ClientStreamTracer clientStreamTracer = callTracer.newClientStreamTracer(STREAM_INFO, headers); + clientStreamTracer.createPendingStream(); + clientStreamTracer.streamCreated(Attributes.EMPTY, headers); + + verify(mockTracer).spanBuilder(eq("Attempt.package1.service2.method3")); + verify(mockPropagator).inject(any(), eq(headers), eq(MetadataSetter.getInstance())); + verify(mockClientSpan, never()).end(); + verify(mockAttemptSpan, never()).end(); + + clientStreamTracer.outboundMessage(0); + clientStreamTracer.outboundMessageSent(0, 882, -1); + clientStreamTracer.inboundMessage(0); + clientStreamTracer.outboundMessage(1); + clientStreamTracer.outboundMessageSent(1, -1, 27); + clientStreamTracer.inboundMessageRead(0, 255, 90); + + clientStreamTracer.streamClosed(Status.OK); + callTracer.callEnded(Status.OK); + + InOrder inOrder = inOrder(mockClientSpan, mockAttemptSpan); + inOrder.verify(mockAttemptSpan) + .setAttribute("previous-rpc-attempts", 0); + inOrder.verify(mockAttemptSpan) + .setAttribute("transparent-retry", false); + inOrder.verify(mockClientSpan).addEvent("Delayed name resolution complete"); + inOrder.verify(mockAttemptSpan).addEvent("Delayed LB pick complete"); + inOrder.verify(mockAttemptSpan, times(3)).addEvent( + eventNameCaptor.capture(), attributesCaptor.capture() + ); + List events = eventNameCaptor.getAllValues(); + List attributes = attributesCaptor.getAllValues(); + assertEquals( + "Outbound message sent" , + events.get(0)); + assertEquals( + io.opentelemetry.api.common.Attributes.builder() + .put("sequence-number", 0) + .put("message-size-compressed", 882) + .build(), + attributes.get(0)); + + assertEquals( + "Outbound message sent" , + events.get(1)); + assertEquals( + io.opentelemetry.api.common.Attributes.builder() + .put("sequence-number", 1) + .put("message-size", 27) + .build(), + attributes.get(1)); + + assertEquals( + "Inbound compressed message" , + events.get(2)); + assertEquals( + io.opentelemetry.api.common.Attributes.builder() + .put("sequence-number", 0) + .put("message-size-compressed", 255) + .build(), + attributes.get(2)); + + inOrder.verify(mockAttemptSpan).setStatus(StatusCode.OK); + inOrder.verify(mockAttemptSpan).end(); + inOrder.verify(mockClientSpan).setStatus(StatusCode.OK); + inOrder.verify(mockClientSpan).end(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void clientBasicTracingRule() { + OpenTelemetryTracingModule tracingModule = new OpenTelemetryTracingModule( + openTelemetryRule.getOpenTelemetry()); + Span clientSpan = tracerRule.spanBuilder("test-client-span").startSpan(); + CallAttemptsTracerFactory callTracer = + tracingModule.newClientCallTracer(clientSpan, method); + Metadata headers = new Metadata(); + ClientStreamTracer clientStreamTracer = callTracer.newClientStreamTracer(STREAM_INFO, headers); + clientStreamTracer.createPendingStream(); + clientStreamTracer.streamCreated(Attributes.EMPTY, headers); + clientStreamTracer.outboundMessage(0); + clientStreamTracer.outboundMessageSent(0, 882, -1); + clientStreamTracer.inboundMessage(0); + clientStreamTracer.outboundMessage(1); + clientStreamTracer.outboundMessageSent(1, -1, 27); + clientStreamTracer.inboundMessageRead(0, 255, -1); + clientStreamTracer.inboundUncompressedSize(288); + clientStreamTracer.inboundMessageRead(1, 128, 128); + clientStreamTracer.inboundMessage(1); + clientStreamTracer.inboundUncompressedSize(128); + + clientStreamTracer.streamClosed(Status.OK); + callTracer.callEnded(Status.OK); + + List spans = openTelemetryRule.getSpans(); + assertEquals(spans.size(), 2); + SpanData attemptSpanData = spans.get(0); + SpanData clientSpanData = spans.get(1); + assertEquals(attemptSpanData.getName(), "Attempt.package1.service2.method3"); + assertEquals(clientSpanData.getName(), "test-client-span"); + assertEquals(headers.keys(), ImmutableSet.of("traceparent")); + String spanContext = headers.get( + Metadata.Key.of("traceparent", Metadata.ASCII_STRING_MARSHALLER)); + assertEquals(spanContext.substring(3, 3 + TraceId.getLength()), + spans.get(1).getSpanContext().getTraceId()); + + // parent(client) span data + List clientSpanEvents = clientSpanData.getEvents(); + assertEquals(clientSpanEvents.size(), 3); + assertEquals( + "Delayed name resolution complete", + clientSpanEvents.get(0).getName()); + assertTrue(clientSpanEvents.get(0).getAttributes().isEmpty()); + + assertEquals( + "Inbound message received" , + clientSpanEvents.get(1).getName()); + assertEquals( + io.opentelemetry.api.common.Attributes.builder() + .put("sequence-number", 0) + .put("message-size", 288) + .build(), + clientSpanEvents.get(1).getAttributes()); + + assertEquals( + "Inbound message received" , + clientSpanEvents.get(2).getName()); + assertEquals( + io.opentelemetry.api.common.Attributes.builder() + .put("sequence-number", 1) + .put("message-size", 128) + .build(), + clientSpanEvents.get(2).getAttributes()); + assertEquals(clientSpanData.hasEnded(), true); + + // child(attempt) span data + List attemptSpanEvents = attemptSpanData.getEvents(); + assertEquals(clientSpanEvents.size(), 3); + assertEquals( + "Delayed LB pick complete", + attemptSpanEvents.get(0).getName()); + assertTrue(clientSpanEvents.get(0).getAttributes().isEmpty()); + + assertEquals( + "Outbound message sent" , + attemptSpanEvents.get(1).getName()); + assertEquals( + io.opentelemetry.api.common.Attributes.builder() + .put("sequence-number", 0) + .put("message-size-compressed", 882) + .build(), + attemptSpanEvents.get(1).getAttributes()); + + assertEquals( + "Outbound message sent" , + attemptSpanEvents.get(2).getName()); + assertEquals( + io.opentelemetry.api.common.Attributes.builder() + .put("sequence-number", 1) + .put("message-size", 27) + .build(), + attemptSpanEvents.get(2).getAttributes()); + + assertEquals( + "Inbound compressed message" , + attemptSpanEvents.get(3).getName()); + assertEquals( + io.opentelemetry.api.common.Attributes.builder() + .put("sequence-number", 0) + .put("message-size-compressed", 255) + .build(), + attemptSpanEvents.get(3).getAttributes()); + + assertEquals(attemptSpanData.hasEnded(), true); + } + + @Test + public void clientInterceptor() { + testClientInterceptors(false); + } + + @Test + public void clientInterceptorNonDefaultOtelContext() { + testClientInterceptors(true); + } + + private void testClientInterceptors(boolean nonDefaultOtelContext) { + final AtomicReference capturedMetadata = new AtomicReference<>(); + grpcServerRule.getServiceRegistry().addService( + ServerServiceDefinition.builder("package1.service2").addMethod( + method, new ServerCallHandler() { + @Override + public ServerCall.Listener startCall( + ServerCall call, Metadata headers) { + capturedMetadata.set(headers); + call.sendHeaders(new Metadata()); + call.sendMessage("Hello"); + call.close( + Status.PERMISSION_DENIED.withDescription("No you don't"), new Metadata()); + return mockServerCallListener; + } + }).build()); + + final AtomicReference capturedCallOptions = new AtomicReference<>(); + ClientInterceptor callOptionsCaptureInterceptor = new ClientInterceptor() { + @Override + public ClientCall interceptCall( + MethodDescriptor method, CallOptions callOptions, Channel next) { + capturedCallOptions.set(callOptions); + return next.newCall(method, callOptions); + } + }; + OpenTelemetryTracingModule tracingModule = new OpenTelemetryTracingModule( + openTelemetryRule.getOpenTelemetry()); + Channel interceptedChannel = + ClientInterceptors.intercept( + grpcServerRule.getChannel(), callOptionsCaptureInterceptor, + tracingModule.getClientInterceptor()); + Span parentSpan = tracerRule.spanBuilder("test-parent-span").startSpan(); + ClientCall call; + + if (nonDefaultOtelContext) { + try (Scope scope = io.opentelemetry.context.Context.current().with(parentSpan) + .makeCurrent()) { + call = interceptedChannel.newCall(method, CALL_OPTIONS); + } + } else { + call = interceptedChannel.newCall(method, CALL_OPTIONS); + } + assertEquals("customvalue", capturedCallOptions.get().getOption(CUSTOM_OPTION)); + assertEquals(1, capturedCallOptions.get().getStreamTracerFactories().size()); + assertTrue( + capturedCallOptions.get().getStreamTracerFactories().get(0) + instanceof CallAttemptsTracerFactory); + + // Make the call + Metadata headers = new Metadata(); + call.start(mockClientCallListener, headers); + + // End the call + call.halfClose(); + call.request(1); + parentSpan.end(); + + verify(mockClientCallListener).onClose(statusCaptor.capture(), any(Metadata.class)); + Status status = statusCaptor.getValue(); + assertEquals(Status.Code.PERMISSION_DENIED, status.getCode()); + assertEquals("No you don't", status.getDescription()); + + List spans = openTelemetryRule.getSpans(); + assertEquals(spans.size(), 3); + + SpanData clientSpan = spans.get(1); + SpanData attemptSpan = spans.get(0); + if (nonDefaultOtelContext) { + assertEquals(clientSpan.getParentSpanContext(), parentSpan.getSpanContext()); + } else { + assertEquals(clientSpan.getParentSpanContext(), + Span.fromContext(Context.root()).getSpanContext()); + } + String spanContext = capturedMetadata.get().get( + Metadata.Key.of("traceparent", Metadata.ASCII_STRING_MARSHALLER)); + // W3C format: 00--- + assertEquals(spanContext.substring(3, 3 + TraceId.getLength()), + attemptSpan.getSpanContext().getTraceId()); + assertEquals(spanContext.substring(3 + TraceId.getLength() + 1, + 3 + TraceId.getLength() + 1 + SpanId.getLength()), + attemptSpan.getSpanContext().getSpanId()); + + assertEquals(attemptSpan.getParentSpanContext(), clientSpan.getSpanContext()); + assertTrue(clientSpan.hasEnded()); + assertEquals(clientSpan.getStatus().getStatusCode(), StatusCode.ERROR); + assertEquals(clientSpan.getStatus().getDescription(), "PERMISSION_DENIED: No you don't"); + assertTrue(attemptSpan.hasEnded()); + assertTrue(attemptSpan.hasEnded()); + assertEquals(attemptSpan.getStatus().getStatusCode(), StatusCode.ERROR); + assertEquals(attemptSpan.getStatus().getDescription(), "PERMISSION_DENIED: No you don't"); + } + + @Test + public void clientStreamNeverCreatedStillRecordTracing() { + OpenTelemetryTracingModule tracingModule = new OpenTelemetryTracingModule(mockOpenTelemetry); + CallAttemptsTracerFactory callTracer = + tracingModule.newClientCallTracer(mockClientSpan, method); + + callTracer.callEnded(Status.DEADLINE_EXCEEDED.withDescription("3 seconds")); + verify(mockClientSpan).end(); + verify(mockClientSpan).setStatus(eq(StatusCode.ERROR), + eq("DEADLINE_EXCEEDED: 3 seconds")); + verifyNoMoreInteractions(mockClientSpan); + } + + @Test + public void serverBasicTracingNoHeaders() { + OpenTelemetryTracingModule tracingModule = new OpenTelemetryTracingModule( + openTelemetryRule.getOpenTelemetry()); + ServerStreamTracer.Factory tracerFactory = tracingModule.getServerTracerFactory(); + ServerStreamTracer serverStreamTracer = + tracerFactory.newServerStreamTracer(method.getFullMethodName(), new Metadata()); + assertSame(Span.fromContext(Context.current()), Span.getInvalid()); + + serverStreamTracer.outboundMessage(0); + serverStreamTracer.outboundMessageSent(0, 882, 998); + serverStreamTracer.inboundMessage(0); + serverStreamTracer.outboundMessage(1); + serverStreamTracer.outboundMessageSent(1, -1, 27); + serverStreamTracer.inboundMessageRead(0, 90, -1); + serverStreamTracer.inboundUncompressedSize(255); + + serverStreamTracer.streamClosed(Status.CANCELLED); + + List spans = openTelemetryRule.getSpans(); + assertEquals(spans.size(), 1); + assertEquals(spans.get(0).getName(), "Recv.package1.service2.method3"); + assertEquals(spans.get(0).getParentSpanContext(), Span.getInvalid().getSpanContext()); + + List events = spans.get(0).getEvents(); + assertEquals(events.size(), 4); + assertEquals( + "Outbound message sent" , + events.get(0).getName()); + assertEquals( + io.opentelemetry.api.common.Attributes.builder() + .put("sequence-number", 0) + .put("message-size-compressed", 882) + .put("message-size", 998) + .build(), + events.get(0).getAttributes()); + + assertEquals( + "Outbound message sent" , + events.get(1).getName()); + assertEquals( + io.opentelemetry.api.common.Attributes.builder() + .put("sequence-number", 1) + .put("message-size", 27) + .build(), + events.get(1).getAttributes()); + + assertEquals( + "Inbound compressed message" , + events.get(2).getName()); + assertEquals( + io.opentelemetry.api.common.Attributes.builder() + .put("sequence-number", 0) + .put("message-size-compressed", 90) + .build(), + events.get(2).getAttributes()); + + assertEquals( + "Inbound message received" , + events.get(3).getName()); + assertEquals( + io.opentelemetry.api.common.Attributes.builder() + .put("sequence-number", 0) + .put("message-size", 255) + .build(), + events.get(3).getAttributes()); + + assertEquals(spans.get(0).hasEnded(), true); + } + + @Test + public void grpcTraceBinPropagator() { + when(mockOpenTelemetry.getPropagators()).thenReturn( + ContextPropagators.create(GrpcTraceBinContextPropagator.defaultInstance())); + ArgumentCaptor contextArgumentCaptor = ArgumentCaptor.forClass(Context.class); + OpenTelemetryTracingModule tracingModule = new OpenTelemetryTracingModule(mockOpenTelemetry); + Span testClientSpan = tracerRule.spanBuilder("test-client-span").startSpan(); + CallAttemptsTracerFactory callTracer = + tracingModule.newClientCallTracer(testClientSpan, method); + Span testAttemptSpan = tracerRule.spanBuilder("test-attempt-span").startSpan(); + when(mockSpanBuilder.startSpan()).thenReturn(testAttemptSpan); + + Metadata headers = new Metadata(); + ClientStreamTracer clientStreamTracer = callTracer.newClientStreamTracer(STREAM_INFO, headers); + clientStreamTracer.streamCreated(Attributes.EMPTY, headers); + clientStreamTracer.streamClosed(Status.CANCELLED); + + Metadata.Key key = Metadata.Key.of( + GrpcTraceBinContextPropagator.GRPC_TRACE_BIN_HEADER, Metadata.BINARY_BYTE_MARSHALLER); + assertTrue(Arrays.equals(BinaryFormat.getInstance().toBytes(testAttemptSpan.getSpanContext()), + headers.get(key) + )); + verify(mockSpanBuilder).setParent(contextArgumentCaptor.capture()); + assertEquals(testClientSpan, Span.fromContext(contextArgumentCaptor.getValue())); + + Span serverSpan = tracerRule.spanBuilder("test-server-span").startSpan(); + when(mockSpanBuilder.startSpan()).thenReturn(serverSpan); + ServerStreamTracer.Factory tracerFactory = tracingModule.getServerTracerFactory(); + ServerStreamTracer serverStreamTracer = + tracerFactory.newServerStreamTracer(method.getFullMethodName(), headers); + serverStreamTracer.streamClosed(Status.CANCELLED); + + verify(mockSpanBuilder, times(2)) + .setParent(contextArgumentCaptor.capture()); + assertEquals(testAttemptSpan.getSpanContext(), + Span.fromContext(contextArgumentCaptor.getValue()).getSpanContext()); + } + + @Test + public void generateTraceSpanName() { + assertEquals( + "Sent.io.grpc.Foo", OpenTelemetryTracingModule.generateTraceSpanName( + false, "io.grpc/Foo")); + assertEquals( + "Recv.io.grpc.Bar", OpenTelemetryTracingModule.generateTraceSpanName( + true, "io.grpc/Bar")); + } +} From 1dae144f0a3013e8056adf9c78eb095a1dc8fcad Mon Sep 17 00:00:00 2001 From: Vindhya Ningegowda Date: Sat, 31 Aug 2024 16:07:53 -0700 Subject: [PATCH 062/103] xds: Fix load reporting when pick first is used for locality-routing. (#11495) * Determine subchannel's network locality from connected address, instead of assuming that all addresses for a subchannel are in the same locality. --- .../InternalSubchannelAddressAttributes.java | 31 +++ api/src/main/java/io/grpc/LoadBalancer.java | 12 ++ .../io/grpc/internal/InternalSubchannel.java | 10 + .../io/grpc/internal/ManagedChannelImpl.java | 5 + .../grpc/internal/InternalSubchannelTest.java | 26 +++ .../io/grpc/util/ForwardingSubchannel.java | 6 + .../io/grpc/xds/ClusterImplLoadBalancer.java | 141 ++++++++++---- .../io/grpc/xds/client/LoadStatsManager2.java | 2 +- .../grpc/xds/ClusterImplLoadBalancerTest.java | 183 +++++++++++++++--- 9 files changed, 352 insertions(+), 64 deletions(-) create mode 100644 api/src/main/java/io/grpc/InternalSubchannelAddressAttributes.java diff --git a/api/src/main/java/io/grpc/InternalSubchannelAddressAttributes.java b/api/src/main/java/io/grpc/InternalSubchannelAddressAttributes.java new file mode 100644 index 00000000000..cfc2f7c5137 --- /dev/null +++ b/api/src/main/java/io/grpc/InternalSubchannelAddressAttributes.java @@ -0,0 +1,31 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc; + +/** + * An internal class. Do not use. + * + *

An interface to provide the attributes for address connected by subchannel. + */ +@Internal +public interface InternalSubchannelAddressAttributes { + + /** + * Return attributes of the server address connected by sub channel. + */ + public Attributes getConnectedAddressAttributes(); +} diff --git a/api/src/main/java/io/grpc/LoadBalancer.java b/api/src/main/java/io/grpc/LoadBalancer.java index 15106a5ffc6..0fbce5fa5be 100644 --- a/api/src/main/java/io/grpc/LoadBalancer.java +++ b/api/src/main/java/io/grpc/LoadBalancer.java @@ -1428,6 +1428,18 @@ public void updateAddresses(List addrs) { public Object getInternalSubchannel() { throw new UnsupportedOperationException(); } + + /** + * (Internal use only) returns attributes of the address subchannel is connected to. + * + *

Warning: this is INTERNAL API, is not supposed to be used by external users, and may + * change without notice. If you think you must use it, please file an issue and we can consider + * removing its "internal" status. + */ + @Internal + public Attributes getConnectedAddressAttributes() { + throw new UnsupportedOperationException(); + } } /** diff --git a/core/src/main/java/io/grpc/internal/InternalSubchannel.java b/core/src/main/java/io/grpc/internal/InternalSubchannel.java index a986cb2deff..70e42e2f5f1 100644 --- a/core/src/main/java/io/grpc/internal/InternalSubchannel.java +++ b/core/src/main/java/io/grpc/internal/InternalSubchannel.java @@ -157,6 +157,8 @@ protected void handleNotInUse() { private Status shutdownReason; + private volatile Attributes connectedAddressAttributes; + InternalSubchannel(List addressGroups, String authority, String userAgent, BackoffPolicy.Provider backoffPolicyProvider, ClientTransportFactory transportFactory, ScheduledExecutorService scheduledExecutor, @@ -525,6 +527,13 @@ public void run() { return channelStatsFuture; } + /** + * Return attributes for server address connected by sub channel. + */ + public Attributes getConnectedAddressAttributes() { + return connectedAddressAttributes; + } + ConnectivityState getState() { return state.getState(); } @@ -568,6 +577,7 @@ public void run() { } else if (pendingTransport == transport) { activeTransport = transport; pendingTransport = null; + connectedAddressAttributes = addressIndex.getCurrentEagAttributes(); gotoNonErrorState(READY); } } diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java index 7f45ca967ea..07dcf9ee7bb 100644 --- a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java +++ b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java @@ -2044,6 +2044,11 @@ public void updateAddresses(List addrs) { subchannel.updateAddresses(addrs); } + @Override + public Attributes getConnectedAddressAttributes() { + return subchannel.getConnectedAddressAttributes(); + } + private List stripOverrideAuthorityAttributes( List eags) { List eagsWithoutOverrideAttr = new ArrayList<>(); diff --git a/core/src/test/java/io/grpc/internal/InternalSubchannelTest.java b/core/src/test/java/io/grpc/internal/InternalSubchannelTest.java index f7631c34c0d..e4d9f27ed46 100644 --- a/core/src/test/java/io/grpc/internal/InternalSubchannelTest.java +++ b/core/src/test/java/io/grpc/internal/InternalSubchannelTest.java @@ -1339,6 +1339,32 @@ public void channelzStatContainsTransport() throws Exception { assertThat(index.getCurrentAddress()).isSameInstanceAs(addr2); } + @Test + public void connectedAddressAttributes_ready() { + SocketAddress addr = new SocketAddress() {}; + Attributes attr = Attributes.newBuilder().set(Attributes.Key.create("some-key"), "1").build(); + createInternalSubchannel(new EquivalentAddressGroup(Arrays.asList(addr), attr)); + + assertEquals(IDLE, internalSubchannel.getState()); + assertNoCallbackInvoke(); + assertNull(internalSubchannel.obtainActiveTransport()); + assertNull(internalSubchannel.getConnectedAddressAttributes()); + + assertExactCallbackInvokes("onStateChange:CONNECTING"); + assertEquals(CONNECTING, internalSubchannel.getState()); + verify(mockTransportFactory).newClientTransport( + eq(addr), + eq(createClientTransportOptions().setEagAttributes(attr)), + isA(TransportLogger.class)); + assertNull(internalSubchannel.getConnectedAddressAttributes()); + + internalSubchannel.obtainActiveTransport(); + transports.peek().listener.transportReady(); + assertExactCallbackInvokes("onStateChange:READY"); + assertEquals(READY, internalSubchannel.getState()); + assertEquals(attr, internalSubchannel.getConnectedAddressAttributes()); + } + /** Create ClientTransportOptions. Should not be reused if it may be mutated. */ private ClientTransportFactory.ClientTransportOptions createClientTransportOptions() { return new ClientTransportFactory.ClientTransportOptions() diff --git a/util/src/main/java/io/grpc/util/ForwardingSubchannel.java b/util/src/main/java/io/grpc/util/ForwardingSubchannel.java index 51f2583186e..416be378162 100644 --- a/util/src/main/java/io/grpc/util/ForwardingSubchannel.java +++ b/util/src/main/java/io/grpc/util/ForwardingSubchannel.java @@ -74,11 +74,17 @@ public Object getInternalSubchannel() { return delegate().getInternalSubchannel(); } + @Override public void updateAddresses(List addrs) { delegate().updateAddresses(addrs); } + @Override + public Attributes getConnectedAddressAttributes() { + return delegate().getConnectedAddressAttributes(); + } + @Override public String toString() { return MoreObjects.toStringHelper(this).add("delegate", delegate()).toString(); diff --git a/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java index 702b2aa6caa..0ea2c7dd75f 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java @@ -27,6 +27,7 @@ import io.grpc.ClientStreamTracer; import io.grpc.ClientStreamTracer.StreamInfo; import io.grpc.ConnectivityState; +import io.grpc.ConnectivityStateInfo; import io.grpc.EquivalentAddressGroup; import io.grpc.InternalLogId; import io.grpc.LoadBalancer; @@ -59,6 +60,7 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nullable; /** @@ -77,10 +79,8 @@ final class ClusterImplLoadBalancer extends LoadBalancer { Strings.isNullOrEmpty(System.getenv("GRPC_XDS_EXPERIMENTAL_CIRCUIT_BREAKING")) || Boolean.parseBoolean(System.getenv("GRPC_XDS_EXPERIMENTAL_CIRCUIT_BREAKING")); - private static final Attributes.Key ATTR_CLUSTER_LOCALITY_STATS = - Attributes.Key.create("io.grpc.xds.ClusterImplLoadBalancer.clusterLocalityStats"); - private static final Attributes.Key ATTR_CLUSTER_LOCALITY_NAME = - Attributes.Key.create("io.grpc.xds.ClusterImplLoadBalancer.clusterLocalityName"); + private static final Attributes.Key> ATTR_CLUSTER_LOCALITY = + Attributes.Key.create("io.grpc.xds.ClusterImplLoadBalancer.clusterLocality"); private final XdsLogger logger; private final Helper helper; @@ -213,36 +213,45 @@ public void updateBalancingState(ConnectivityState newState, SubchannelPicker ne @Override public Subchannel createSubchannel(CreateSubchannelArgs args) { List addresses = withAdditionalAttributes(args.getAddresses()); - Locality locality = args.getAddresses().get(0).getAttributes().get( - InternalXdsAttributes.ATTR_LOCALITY); // all addresses should be in the same locality - String localityName = args.getAddresses().get(0).getAttributes().get( - InternalXdsAttributes.ATTR_LOCALITY_NAME); - // Endpoint addresses resolved by ClusterResolverLoadBalancer should always contain - // attributes with its locality, including endpoints in LOGICAL_DNS clusters. - // In case of not (which really shouldn't), loads are aggregated under an empty locality. - if (locality == null) { - locality = Locality.create("", "", ""); - localityName = ""; - } - final ClusterLocalityStats localityStats = - (lrsServerInfo == null) - ? null - : xdsClient.addClusterLocalityStats(lrsServerInfo, cluster, - edsServiceName, locality); - + // This value for ClusterLocality is not recommended for general use. + // Currently, we extract locality data from the first address, even before the subchannel is + // READY. + // This is mainly to accommodate scenarios where a Load Balancing API (like "pick first") + // might return the subchannel before it is READY. Typically, we wouldn't report load for such + // selections because the channel will disregard the chosen (not-ready) subchannel. + // However, we needed to ensure this case is handled. + ClusterLocality clusterLocality = createClusterLocalityFromAttributes( + args.getAddresses().get(0).getAttributes()); + AtomicReference localityAtomicReference = new AtomicReference<>( + clusterLocality); Attributes attrs = args.getAttributes().toBuilder() - .set(ATTR_CLUSTER_LOCALITY_STATS, localityStats) - .set(ATTR_CLUSTER_LOCALITY_NAME, localityName) + .set(ATTR_CLUSTER_LOCALITY, localityAtomicReference) .build(); args = args.toBuilder().setAddresses(addresses).setAttributes(attrs).build(); final Subchannel subchannel = delegate().createSubchannel(args); return new ForwardingSubchannel() { + @Override + public void start(SubchannelStateListener listener) { + delegate().start(new SubchannelStateListener() { + @Override + public void onSubchannelState(ConnectivityStateInfo newState) { + if (newState.getState().equals(ConnectivityState.READY)) { + // Get locality based on the connected address attributes + ClusterLocality updatedClusterLocality = createClusterLocalityFromAttributes( + subchannel.getConnectedAddressAttributes()); + ClusterLocality oldClusterLocality = localityAtomicReference + .getAndSet(updatedClusterLocality); + oldClusterLocality.release(); + } + listener.onSubchannelState(newState); + } + }); + } + @Override public void shutdown() { - if (localityStats != null) { - localityStats.release(); - } + localityAtomicReference.get().release(); delegate().shutdown(); } @@ -274,6 +283,28 @@ private List withAdditionalAttributes( return newAddresses; } + private ClusterLocality createClusterLocalityFromAttributes(Attributes addressAttributes) { + Locality locality = addressAttributes.get(InternalXdsAttributes.ATTR_LOCALITY); + String localityName = addressAttributes.get(InternalXdsAttributes.ATTR_LOCALITY_NAME); + + // Endpoint addresses resolved by ClusterResolverLoadBalancer should always contain + // attributes with its locality, including endpoints in LOGICAL_DNS clusters. + // In case of not (which really shouldn't), loads are aggregated under an empty + // locality. + if (locality == null) { + locality = Locality.create("", "", ""); + localityName = ""; + } + + final ClusterLocalityStats localityStats = + (lrsServerInfo == null) + ? null + : xdsClient.addClusterLocalityStats(lrsServerInfo, cluster, + edsServiceName, locality); + + return new ClusterLocality(localityStats, localityName); + } + @Override protected Helper delegate() { return helper; @@ -361,18 +392,23 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { "Cluster max concurrent requests limit exceeded")); } } - final ClusterLocalityStats stats = - result.getSubchannel().getAttributes().get(ATTR_CLUSTER_LOCALITY_STATS); - if (stats != null) { - String localityName = - result.getSubchannel().getAttributes().get(ATTR_CLUSTER_LOCALITY_NAME); - args.getPickDetailsConsumer().addOptionalLabel("grpc.lb.locality", localityName); - - ClientStreamTracer.Factory tracerFactory = new CountingStreamTracerFactory( - stats, inFlights, result.getStreamTracerFactory()); - ClientStreamTracer.Factory orcaTracerFactory = OrcaPerRequestUtil.getInstance() - .newOrcaClientStreamTracerFactory(tracerFactory, new OrcaPerRpcListener(stats)); - return PickResult.withSubchannel(result.getSubchannel(), orcaTracerFactory); + final AtomicReference clusterLocality = + result.getSubchannel().getAttributes().get(ATTR_CLUSTER_LOCALITY); + + if (clusterLocality != null) { + ClusterLocalityStats stats = clusterLocality.get().getClusterLocalityStats(); + if (stats != null) { + String localityName = + result.getSubchannel().getAttributes().get(ATTR_CLUSTER_LOCALITY).get() + .getClusterLocalityName(); + args.getPickDetailsConsumer().addOptionalLabel("grpc.lb.locality", localityName); + + ClientStreamTracer.Factory tracerFactory = new CountingStreamTracerFactory( + stats, inFlights, result.getStreamTracerFactory()); + ClientStreamTracer.Factory orcaTracerFactory = OrcaPerRequestUtil.getInstance() + .newOrcaClientStreamTracerFactory(tracerFactory, new OrcaPerRpcListener(stats)); + return PickResult.withSubchannel(result.getSubchannel(), orcaTracerFactory); + } } } return result; @@ -447,4 +483,33 @@ public void onLoadReport(MetricReport report) { stats.recordBackendLoadMetricStats(report.getNamedMetrics()); } } + + /** + * Represents the {@link ClusterLocalityStats} and network locality name of a cluster. + */ + static final class ClusterLocality { + private final ClusterLocalityStats clusterLocalityStats; + private final String clusterLocalityName; + + @VisibleForTesting + ClusterLocality(ClusterLocalityStats localityStats, String localityName) { + this.clusterLocalityStats = localityStats; + this.clusterLocalityName = localityName; + } + + ClusterLocalityStats getClusterLocalityStats() { + return clusterLocalityStats; + } + + String getClusterLocalityName() { + return clusterLocalityName; + } + + @VisibleForTesting + void release() { + if (clusterLocalityStats != null) { + clusterLocalityStats.release(); + } + } + } } diff --git a/xds/src/main/java/io/grpc/xds/client/LoadStatsManager2.java b/xds/src/main/java/io/grpc/xds/client/LoadStatsManager2.java index 393cce16194..be9d3587d14 100644 --- a/xds/src/main/java/io/grpc/xds/client/LoadStatsManager2.java +++ b/xds/src/main/java/io/grpc/xds/client/LoadStatsManager2.java @@ -91,7 +91,7 @@ private synchronized void releaseClusterDropCounter( String cluster, @Nullable String edsServiceName) { checkState(allDropStats.containsKey(cluster) && allDropStats.get(cluster).containsKey(edsServiceName), - "stats for cluster %s, edsServiceName %s not exits", cluster, edsServiceName); + "stats for cluster %s, edsServiceName %s do not exist", cluster, edsServiceName); ReferenceCounted ref = allDropStats.get(cluster).get(edsServiceName); ref.release(); } diff --git a/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java index 4e12a5717ae..0082a2aa59d 100644 --- a/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java @@ -16,6 +16,7 @@ package io.grpc.xds; +import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; @@ -29,6 +30,7 @@ import io.grpc.CallOptions; import io.grpc.ClientStreamTracer; import io.grpc.ConnectivityState; +import io.grpc.ConnectivityStateInfo; import io.grpc.EquivalentAddressGroup; import io.grpc.InsecureChannelCredentials; import io.grpc.LoadBalancer; @@ -40,7 +42,9 @@ import io.grpc.LoadBalancer.ResolvedAddresses; import io.grpc.LoadBalancer.Subchannel; import io.grpc.LoadBalancer.SubchannelPicker; +import io.grpc.LoadBalancer.SubchannelStateListener; import io.grpc.LoadBalancerProvider; +import io.grpc.LoadBalancerRegistry; import io.grpc.ManagedChannel; import io.grpc.Metadata; import io.grpc.Status; @@ -76,9 +80,11 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Queue; import java.util.concurrent.atomic.AtomicLong; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -145,7 +151,7 @@ public AtomicLong getOrCreate(String cluster, @Nullable String edsServiceName) { return new AtomicLong(); } }; - private final Helper helper = new FakeLbHelper(); + private final FakeLbHelper helper = new FakeLbHelper(); private PickSubchannelArgs pickSubchannelArgs = new PickSubchannelArgsImpl( TestMethodDescriptors.voidMethod(), new Metadata(), CallOptions.DEFAULT, new PickDetailsConsumer() {}); @@ -272,9 +278,10 @@ public void pick_addsLocalityLabel() { EquivalentAddressGroup endpoint = makeAddress("endpoint-addr", locality); deliverAddressesAndConfig(Collections.singletonList(endpoint), config); FakeLoadBalancer leafBalancer = Iterables.getOnlyElement(downstreamBalancers); - Subchannel subchannel = leafBalancer.helper.createSubchannel( - CreateSubchannelArgs.newBuilder().setAddresses(leafBalancer.addresses).build()); - leafBalancer.deliverSubchannelState(subchannel, ConnectivityState.READY); + leafBalancer.createSubChannel(); + FakeSubchannel fakeSubchannel = helper.subchannels.poll(); + fakeSubchannel.setConnectedEagIndex(0); + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); assertThat(currentState).isEqualTo(ConnectivityState.READY); PickDetailsConsumer detailsConsumer = mock(PickDetailsConsumer.class); @@ -300,9 +307,10 @@ public void recordLoadStats() { EquivalentAddressGroup endpoint = makeAddress("endpoint-addr", locality); deliverAddressesAndConfig(Collections.singletonList(endpoint), config); FakeLoadBalancer leafBalancer = Iterables.getOnlyElement(downstreamBalancers); - Subchannel subchannel = leafBalancer.helper.createSubchannel( - CreateSubchannelArgs.newBuilder().setAddresses(leafBalancer.addresses).build()); - leafBalancer.deliverSubchannelState(subchannel, ConnectivityState.READY); + Subchannel subchannel = leafBalancer.createSubChannel(); + FakeSubchannel fakeSubchannel = helper.subchannels.poll(); + fakeSubchannel.setConnectedEagIndex(0); + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); assertThat(currentState).isEqualTo(ConnectivityState.READY); PickResult result = currentPicker.pickSubchannel(pickSubchannelArgs); assertThat(result.getStatus().isOk()).isTrue(); @@ -357,7 +365,7 @@ public void recordLoadStats() { TOLERANCE).of(0.009); streamTracer3.streamClosed(Status.OK); - subchannel.shutdown(); // stats recorder released + subchannel.shutdown(); // stats recorder released clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER)); // Locality load is reported for one last time in case of loads occurred since the previous // load report. @@ -373,6 +381,95 @@ public void recordLoadStats() { assertThat(clusterStats.upstreamLocalityStatsList()).isEmpty(); // no longer reported } + // TODO(dnvindhya): This test has been added as a fix to verify + // https://github.com/grpc/grpc-java/issues/11434. + // Once we update PickFirstLeafLoadBalancer as default LoadBalancer, update the test. + @Test + public void pickFirstLoadReport_onUpdateAddress() { + Locality locality1 = + Locality.create("test-region", "test-zone", "test-subzone"); + Locality locality2 = + Locality.create("other-region", "other-zone", "other-subzone"); + + LoadBalancerProvider pickFirstProvider = LoadBalancerRegistry + .getDefaultRegistry().getProvider("pick_first"); + Object pickFirstConfig = pickFirstProvider.parseLoadBalancingPolicyConfig(new HashMap<>()) + .getConfig(); + ClusterImplConfig config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, + null, Collections.emptyList(), + GracefulSwitchLoadBalancer.createLoadBalancingPolicyConfig(pickFirstProvider, + pickFirstConfig), + null, Collections.emptyMap()); + EquivalentAddressGroup endpoint1 = makeAddress("endpoint-addr1", locality1); + EquivalentAddressGroup endpoint2 = makeAddress("endpoint-addr2", locality2); + deliverAddressesAndConfig(Arrays.asList(endpoint1, endpoint2), config); + + // Leaf balancer is created by Pick First. Get FakeSubchannel created to update attributes + // A real subchannel would get these attributes from the connected address's EAG locality. + FakeSubchannel fakeSubchannel = helper.subchannels.poll(); + fakeSubchannel.setConnectedEagIndex(0); + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); + assertThat(currentState).isEqualTo(ConnectivityState.READY); + PickResult result = currentPicker.pickSubchannel(pickSubchannelArgs); + assertThat(result.getStatus().isOk()).isTrue(); + + ClientStreamTracer streamTracer1 = result.getStreamTracerFactory().newClientStreamTracer( + ClientStreamTracer.StreamInfo.newBuilder().build(), new Metadata()); // first RPC call + streamTracer1.streamClosed(Status.OK); + + ClusterStats clusterStats = Iterables.getOnlyElement( + loadStatsManager.getClusterStatsReports(CLUSTER)); + UpstreamLocalityStats localityStats = Iterables.getOnlyElement( + clusterStats.upstreamLocalityStatsList()); + assertThat(localityStats.locality()).isEqualTo(locality1); + assertThat(localityStats.totalIssuedRequests()).isEqualTo(1L); + assertThat(localityStats.totalSuccessfulRequests()).isEqualTo(1L); + assertThat(localityStats.totalErrorRequests()).isEqualTo(0L); + + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.IDLE)); + loadBalancer.requestConnection(); + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.CONNECTING)); + + // Faksubchannel mimics update address and returns different locality + fakeSubchannel.setConnectedEagIndex(1); + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); + result = currentPicker.pickSubchannel(pickSubchannelArgs); + assertThat(result.getStatus().isOk()).isTrue(); + ClientStreamTracer streamTracer2 = result.getStreamTracerFactory().newClientStreamTracer( + ClientStreamTracer.StreamInfo.newBuilder().build(), new Metadata()); // second RPC call + streamTracer2.streamClosed(Status.UNAVAILABLE); + + clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER)); + UpstreamLocalityStats localityStats1 = Iterables.get(clusterStats.upstreamLocalityStatsList(), + 0); + assertThat(localityStats1.locality()).isEqualTo(locality1); + assertThat(localityStats1.totalIssuedRequests()).isEqualTo(0L); + assertThat(localityStats1.totalSuccessfulRequests()).isEqualTo(0L); + assertThat(localityStats1.totalErrorRequests()).isEqualTo(0L); + UpstreamLocalityStats localityStats2 = Iterables.get(clusterStats.upstreamLocalityStatsList(), + 1); + assertThat(localityStats2.locality()).isEqualTo(locality2); + assertThat(localityStats2.totalIssuedRequests()).isEqualTo(1L); + assertThat(localityStats2.totalSuccessfulRequests()).isEqualTo(0L); + assertThat(localityStats2.totalErrorRequests()).isEqualTo(1L); + + loadBalancer.shutdown(); + loadBalancer = null; + // No more references are held for localityStats1 hence dropped. + // Locality load is reported for one last time in case of loads occurred since the previous + // load report. + clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER)); + localityStats2 = Iterables.getOnlyElement(clusterStats.upstreamLocalityStatsList()); + + assertThat(localityStats2.locality()).isEqualTo(locality2); + assertThat(localityStats2.totalIssuedRequests()).isEqualTo(0L); + assertThat(localityStats2.totalSuccessfulRequests()).isEqualTo(0L); + assertThat(localityStats2.totalErrorRequests()).isEqualTo(0L); + assertThat(localityStats2.totalRequestsInProgress()).isEqualTo(0L); + + assertThat(loadStatsManager.getClusterStatsReports(CLUSTER)).isEmpty(); + } + @Test public void dropRpcsWithRespectToLbConfigDropCategories() { LoadBalancerProvider weightedTargetProvider = new WeightedTargetLoadBalancerProvider(); @@ -391,9 +488,11 @@ public void dropRpcsWithRespectToLbConfigDropCategories() { assertThat(leafBalancer.name).isEqualTo("round_robin"); assertThat(Iterables.getOnlyElement(leafBalancer.addresses).getAddresses()) .isEqualTo(endpoint.getAddresses()); - Subchannel subchannel = leafBalancer.helper.createSubchannel( - CreateSubchannelArgs.newBuilder().setAddresses(leafBalancer.addresses).build()); - leafBalancer.deliverSubchannelState(subchannel, ConnectivityState.READY); + leafBalancer.createSubChannel(); + FakeSubchannel fakeSubchannel = helper.subchannels.poll(); + fakeSubchannel.setConnectedEagIndex(0); + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); + assertThat(currentState).isEqualTo(ConnectivityState.READY); PickResult result = currentPicker.pickSubchannel(pickSubchannelArgs); assertThat(result.getStatus().isOk()).isFalse(); @@ -470,9 +569,11 @@ private void subtest_maxConcurrentRequests_appliedByLbConfig(boolean enableCircu assertThat(leafBalancer.name).isEqualTo("round_robin"); assertThat(Iterables.getOnlyElement(leafBalancer.addresses).getAddresses()) .isEqualTo(endpoint.getAddresses()); - Subchannel subchannel = leafBalancer.helper.createSubchannel( - CreateSubchannelArgs.newBuilder().setAddresses(leafBalancer.addresses).build()); - leafBalancer.deliverSubchannelState(subchannel, ConnectivityState.READY); + leafBalancer.createSubChannel(); + FakeSubchannel fakeSubchannel = helper.subchannels.poll(); + fakeSubchannel.setConnectedEagIndex(0); + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); + assertThat(currentState).isEqualTo(ConnectivityState.READY); assertThat(currentState).isEqualTo(ConnectivityState.READY); for (int i = 0; i < maxConcurrentRequests; i++) { PickResult result = currentPicker.pickSubchannel(pickSubchannelArgs); @@ -562,9 +663,11 @@ private void subtest_maxConcurrentRequests_appliedWithDefaultValue( assertThat(leafBalancer.name).isEqualTo("round_robin"); assertThat(Iterables.getOnlyElement(leafBalancer.addresses).getAddresses()) .isEqualTo(endpoint.getAddresses()); - Subchannel subchannel = leafBalancer.helper.createSubchannel( - CreateSubchannelArgs.newBuilder().setAddresses(leafBalancer.addresses).build()); - leafBalancer.deliverSubchannelState(subchannel, ConnectivityState.READY); + leafBalancer.createSubChannel(); + FakeSubchannel fakeSubchannel = helper.subchannels.poll(); + fakeSubchannel.setConnectedEagIndex(0); + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); + assertThat(currentState).isEqualTo(ConnectivityState.READY); assertThat(currentState).isEqualTo(ConnectivityState.READY); for (int i = 0; i < ClusterImplLoadBalancer.DEFAULT_PER_CLUSTER_MAX_CONCURRENT_REQUESTS; i++) { PickResult result = currentPicker.pickSubchannel(pickSubchannelArgs); @@ -830,19 +933,24 @@ public void shutdown() { downstreamBalancers.remove(this); } - void deliverSubchannelState(final Subchannel subchannel, ConnectivityState state) { - SubchannelPicker picker = new SubchannelPicker() { - @Override - public PickResult pickSubchannel(PickSubchannelArgs args) { - return PickResult.withSubchannel(subchannel); + Subchannel createSubChannel() { + Subchannel subchannel = helper.createSubchannel( + CreateSubchannelArgs.newBuilder().setAddresses(addresses).build()); + subchannel.start(infoObject -> { + if (infoObject.getState() == ConnectivityState.READY) { + helper.updateBalancingState( + ConnectivityState.READY, + new FixedResultPicker(PickResult.withSubchannel(subchannel))); } - }; - helper.updateBalancingState(state, picker); + }); + return subchannel; } } private final class FakeLbHelper extends LoadBalancer.Helper { + private final Queue subchannels = new LinkedList<>(); + @Override public SynchronizationContext getSynchronizationContext() { return syncContext; @@ -857,7 +965,9 @@ public void updateBalancingState( @Override public Subchannel createSubchannel(CreateSubchannelArgs args) { - return new FakeSubchannel(args.getAddresses(), args.getAttributes()); + FakeSubchannel subchannel = new FakeSubchannel(args.getAddresses(), args.getAttributes()); + subchannels.add(subchannel); + return subchannel; } @Override @@ -869,17 +979,27 @@ public ManagedChannel createOobChannel(EquivalentAddressGroup eag, String author public String getAuthority() { return AUTHORITY; } + + @Override + public void refreshNameResolution() {} } private static final class FakeSubchannel extends Subchannel { private final List eags; private final Attributes attrs; + private SubchannelStateListener listener; + private Attributes connectedAttributes; private FakeSubchannel(List eags, Attributes attrs) { this.eags = eags; this.attrs = attrs; } + @Override + public void start(SubchannelStateListener listener) { + this.listener = checkNotNull(listener, "listener"); + } + @Override public void shutdown() { } @@ -901,6 +1021,19 @@ public Attributes getAttributes() { @Override public void updateAddresses(List addrs) { } + + @Override + public Attributes getConnectedAddressAttributes() { + return connectedAttributes; + } + + public void updateState(ConnectivityStateInfo newState) { + listener.onSubchannelState(newState); + } + + public void setConnectedEagIndex(int eagIndex) { + this.connectedAttributes = eags.get(eagIndex).getAttributes(); + } } private final class FakeXdsClient extends XdsClient { From 8adfbf9ac52e635e7e00a4dae5f9493080241d68 Mon Sep 17 00:00:00 2001 From: MV Shiva Date: Wed, 4 Sep 2024 19:33:28 +0530 Subject: [PATCH 063/103] Start 1.68.0 development cycle (#11507) --- MODULE.bazel | 2 +- build.gradle | 2 +- .../src/test/golden/TestDeprecatedService.java.txt | 2 +- compiler/src/test/golden/TestService.java.txt | 2 +- core/src/main/java/io/grpc/internal/GrpcUtil.java | 2 +- examples/android/clientcache/app/build.gradle | 10 +++++----- examples/android/helloworld/app/build.gradle | 8 ++++---- examples/android/routeguide/app/build.gradle | 8 ++++---- examples/android/strictmode/app/build.gradle | 8 ++++---- examples/build.gradle | 2 +- examples/example-alts/build.gradle | 2 +- examples/example-debug/build.gradle | 2 +- examples/example-debug/pom.xml | 4 ++-- examples/example-dualstack/build.gradle | 2 +- examples/example-dualstack/pom.xml | 4 ++-- examples/example-gauth/build.gradle | 2 +- examples/example-gauth/pom.xml | 4 ++-- examples/example-gcp-csm-observability/build.gradle | 2 +- examples/example-gcp-observability/build.gradle | 2 +- examples/example-hostname/build.gradle | 2 +- examples/example-hostname/pom.xml | 4 ++-- examples/example-jwt-auth/build.gradle | 2 +- examples/example-jwt-auth/pom.xml | 4 ++-- examples/example-oauth/build.gradle | 2 +- examples/example-oauth/pom.xml | 4 ++-- examples/example-opentelemetry/build.gradle | 2 +- examples/example-orca/build.gradle | 2 +- examples/example-reflection/build.gradle | 2 +- examples/example-servlet/build.gradle | 2 +- examples/example-tls/build.gradle | 2 +- examples/example-tls/pom.xml | 4 ++-- examples/example-xds/build.gradle | 2 +- examples/pom.xml | 4 ++-- 33 files changed, 54 insertions(+), 54 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index 81c3249f47a..b60ea565073 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -2,7 +2,7 @@ module( name = "grpc-java", compatibility_level = 0, repo_name = "io_grpc_grpc_java", - version = "1.67.0-SNAPSHOT", # CURRENT_GRPC_VERSION + version = "1.68.0-SNAPSHOT", # CURRENT_GRPC_VERSION ) # GRPC_DEPS_START diff --git a/build.gradle b/build.gradle index 74cfacb800a..740f534e136 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ subprojects { apply plugin: "net.ltgt.errorprone" group = "io.grpc" - version = "1.67.0-SNAPSHOT" // CURRENT_GRPC_VERSION + version = "1.68.0-SNAPSHOT" // CURRENT_GRPC_VERSION repositories { maven { // The google mirror is less flaky than mavenCentral() diff --git a/compiler/src/test/golden/TestDeprecatedService.java.txt b/compiler/src/test/golden/TestDeprecatedService.java.txt index 75e9e0b47e0..04a7f2406b3 100644 --- a/compiler/src/test/golden/TestDeprecatedService.java.txt +++ b/compiler/src/test/golden/TestDeprecatedService.java.txt @@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.67.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.68.0-SNAPSHOT)", comments = "Source: grpc/testing/compiler/test.proto") @io.grpc.stub.annotations.GrpcGenerated @java.lang.Deprecated diff --git a/compiler/src/test/golden/TestService.java.txt b/compiler/src/test/golden/TestService.java.txt index 3852b6ee547..d69abad7cbb 100644 --- a/compiler/src/test/golden/TestService.java.txt +++ b/compiler/src/test/golden/TestService.java.txt @@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.67.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.68.0-SNAPSHOT)", comments = "Source: grpc/testing/compiler/test.proto") @io.grpc.stub.annotations.GrpcGenerated public final class TestServiceGrpc { diff --git a/core/src/main/java/io/grpc/internal/GrpcUtil.java b/core/src/main/java/io/grpc/internal/GrpcUtil.java index 593bdbce13f..a1fe34c2edc 100644 --- a/core/src/main/java/io/grpc/internal/GrpcUtil.java +++ b/core/src/main/java/io/grpc/internal/GrpcUtil.java @@ -219,7 +219,7 @@ public byte[] parseAsciiString(byte[] serialized) { public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults(); - public static final String IMPLEMENTATION_VERSION = "1.67.0-SNAPSHOT"; // CURRENT_GRPC_VERSION + public static final String IMPLEMENTATION_VERSION = "1.68.0-SNAPSHOT"; // CURRENT_GRPC_VERSION /** * The default timeout in nanos for a keepalive ping request. diff --git a/examples/android/clientcache/app/build.gradle b/examples/android/clientcache/app/build.gradle index 0ca032fb0e4..6b5b966e7f6 100644 --- a/examples/android/clientcache/app/build.gradle +++ b/examples/android/clientcache/app/build.gradle @@ -34,7 +34,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.25.1' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -54,12 +54,12 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.0.0' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' testImplementation 'junit:junit:4.13.2' testImplementation 'com.google.truth:truth:1.1.5' - testImplementation 'io.grpc:grpc-testing:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + testImplementation 'io.grpc:grpc-testing:1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION } diff --git a/examples/android/helloworld/app/build.gradle b/examples/android/helloworld/app/build.gradle index 0f1e8b4047b..4edbcb14612 100644 --- a/examples/android/helloworld/app/build.gradle +++ b/examples/android/helloworld/app/build.gradle @@ -32,7 +32,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.25.1' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -52,8 +52,8 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.0.0' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/android/routeguide/app/build.gradle b/examples/android/routeguide/app/build.gradle index c33135233ea..4a08f40e4ee 100644 --- a/examples/android/routeguide/app/build.gradle +++ b/examples/android/routeguide/app/build.gradle @@ -32,7 +32,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.25.1' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -52,8 +52,8 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.0.0' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/android/strictmode/app/build.gradle b/examples/android/strictmode/app/build.gradle index e8e2e8cac29..9f41994e3c2 100644 --- a/examples/android/strictmode/app/build.gradle +++ b/examples/android/strictmode/app/build.gradle @@ -33,7 +33,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.25.1' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -53,8 +53,8 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.0.0' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/build.gradle b/examples/build.gradle index 076e0c4a25b..c10b4eef46a 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -23,7 +23,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.25.3' def protocVersion = protobufVersion diff --git a/examples/example-alts/build.gradle b/examples/example-alts/build.gradle index 3c998586bb6..0d7d959de93 100644 --- a/examples/example-alts/build.gradle +++ b/examples/example-alts/build.gradle @@ -24,7 +24,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/example-debug/build.gradle b/examples/example-debug/build.gradle index ca151a13c1a..5565747cb19 100644 --- a/examples/example-debug/build.gradle +++ b/examples/example-debug/build.gradle @@ -25,7 +25,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.25.3' dependencies { diff --git a/examples/example-debug/pom.xml b/examples/example-debug/pom.xml index 7365b35daab..064d989c04c 100644 --- a/examples/example-debug/pom.xml +++ b/examples/example-debug/pom.xml @@ -6,13 +6,13 @@ jar - 1.67.0-SNAPSHOT + 1.68.0-SNAPSHOT example-debug https://github.com/grpc/grpc-java UTF-8 - 1.67.0-SNAPSHOT + 1.68.0-SNAPSHOT 3.25.3 1.8 diff --git a/examples/example-dualstack/build.gradle b/examples/example-dualstack/build.gradle index 32b35af8a87..554b5f758d9 100644 --- a/examples/example-dualstack/build.gradle +++ b/examples/example-dualstack/build.gradle @@ -25,7 +25,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.25.3' dependencies { diff --git a/examples/example-dualstack/pom.xml b/examples/example-dualstack/pom.xml index f955cada0f6..dfd650cdfa4 100644 --- a/examples/example-dualstack/pom.xml +++ b/examples/example-dualstack/pom.xml @@ -6,13 +6,13 @@ jar - 1.67.0-SNAPSHOT + 1.68.0-SNAPSHOT example-dualstack https://github.com/grpc/grpc-java UTF-8 - 1.67.0-SNAPSHOT + 1.68.0-SNAPSHOT 3.25.3 1.8 diff --git a/examples/example-gauth/build.gradle b/examples/example-gauth/build.gradle index 40e72afad82..47e812fde15 100644 --- a/examples/example-gauth/build.gradle +++ b/examples/example-gauth/build.gradle @@ -24,7 +24,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.25.3' def protocVersion = protobufVersion diff --git a/examples/example-gauth/pom.xml b/examples/example-gauth/pom.xml index 2512cb37ea5..d2cba1a7959 100644 --- a/examples/example-gauth/pom.xml +++ b/examples/example-gauth/pom.xml @@ -6,13 +6,13 @@ jar - 1.67.0-SNAPSHOT + 1.68.0-SNAPSHOT example-gauth https://github.com/grpc/grpc-java UTF-8 - 1.67.0-SNAPSHOT + 1.68.0-SNAPSHOT 3.25.3 1.8 diff --git a/examples/example-gcp-csm-observability/build.gradle b/examples/example-gcp-csm-observability/build.gradle index 5de2b1995e2..a392018ba25 100644 --- a/examples/example-gcp-csm-observability/build.gradle +++ b/examples/example-gcp-csm-observability/build.gradle @@ -25,7 +25,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' def openTelemetryVersion = '1.40.0' def openTelemetryPrometheusVersion = '1.40.0-alpha' diff --git a/examples/example-gcp-observability/build.gradle b/examples/example-gcp-observability/build.gradle index 0462c987f52..dcb8d420020 100644 --- a/examples/example-gcp-observability/build.gradle +++ b/examples/example-gcp-observability/build.gradle @@ -25,7 +25,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/example-hostname/build.gradle b/examples/example-hostname/build.gradle index ab45ee2dc5b..df8b0fde121 100644 --- a/examples/example-hostname/build.gradle +++ b/examples/example-hostname/build.gradle @@ -23,7 +23,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.25.3' dependencies { diff --git a/examples/example-hostname/pom.xml b/examples/example-hostname/pom.xml index 443ecb85ef2..c6d39887bac 100644 --- a/examples/example-hostname/pom.xml +++ b/examples/example-hostname/pom.xml @@ -6,13 +6,13 @@ jar - 1.67.0-SNAPSHOT + 1.68.0-SNAPSHOT example-hostname https://github.com/grpc/grpc-java UTF-8 - 1.67.0-SNAPSHOT + 1.68.0-SNAPSHOT 3.25.3 1.8 diff --git a/examples/example-jwt-auth/build.gradle b/examples/example-jwt-auth/build.gradle index 6fdd4498c7d..f996282bbb0 100644 --- a/examples/example-jwt-auth/build.gradle +++ b/examples/example-jwt-auth/build.gradle @@ -23,7 +23,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.25.3' def protocVersion = protobufVersion diff --git a/examples/example-jwt-auth/pom.xml b/examples/example-jwt-auth/pom.xml index f2e92e28c84..c84f9893980 100644 --- a/examples/example-jwt-auth/pom.xml +++ b/examples/example-jwt-auth/pom.xml @@ -7,13 +7,13 @@ jar - 1.67.0-SNAPSHOT + 1.68.0-SNAPSHOT example-jwt-auth https://github.com/grpc/grpc-java UTF-8 - 1.67.0-SNAPSHOT + 1.68.0-SNAPSHOT 3.25.3 3.25.3 diff --git a/examples/example-oauth/build.gradle b/examples/example-oauth/build.gradle index 255633b4f9f..7f600c2bc53 100644 --- a/examples/example-oauth/build.gradle +++ b/examples/example-oauth/build.gradle @@ -23,7 +23,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.25.3' def protocVersion = protobufVersion diff --git a/examples/example-oauth/pom.xml b/examples/example-oauth/pom.xml index 4e284b5f248..fa2eaa41e36 100644 --- a/examples/example-oauth/pom.xml +++ b/examples/example-oauth/pom.xml @@ -7,13 +7,13 @@ jar - 1.67.0-SNAPSHOT + 1.68.0-SNAPSHOT example-oauth https://github.com/grpc/grpc-java UTF-8 - 1.67.0-SNAPSHOT + 1.68.0-SNAPSHOT 3.25.3 3.25.3 diff --git a/examples/example-opentelemetry/build.gradle b/examples/example-opentelemetry/build.gradle index 00f7dc101bf..21264ffcc17 100644 --- a/examples/example-opentelemetry/build.gradle +++ b/examples/example-opentelemetry/build.gradle @@ -24,7 +24,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' def openTelemetryVersion = '1.40.0' def openTelemetryPrometheusVersion = '1.40.0-alpha' diff --git a/examples/example-orca/build.gradle b/examples/example-orca/build.gradle index 22feb8cae42..d087a532aff 100644 --- a/examples/example-orca/build.gradle +++ b/examples/example-orca/build.gradle @@ -18,7 +18,7 @@ java { targetCompatibility = JavaVersion.VERSION_1_8 } -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/example-reflection/build.gradle b/examples/example-reflection/build.gradle index 78821391911..d7d5c50b7e6 100644 --- a/examples/example-reflection/build.gradle +++ b/examples/example-reflection/build.gradle @@ -18,7 +18,7 @@ java { targetCompatibility = JavaVersion.VERSION_1_8 } -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/example-servlet/build.gradle b/examples/example-servlet/build.gradle index 9542ba0277f..995e2d0979b 100644 --- a/examples/example-servlet/build.gradle +++ b/examples/example-servlet/build.gradle @@ -16,7 +16,7 @@ java { targetCompatibility = JavaVersion.VERSION_1_8 } -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index 94257af4758..8aad6b62bcb 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -24,7 +24,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/example-tls/pom.xml b/examples/example-tls/pom.xml index eab02cb5919..e1d569a628c 100644 --- a/examples/example-tls/pom.xml +++ b/examples/example-tls/pom.xml @@ -6,13 +6,13 @@ jar - 1.67.0-SNAPSHOT + 1.68.0-SNAPSHOT example-tls https://github.com/grpc/grpc-java UTF-8 - 1.67.0-SNAPSHOT + 1.68.0-SNAPSHOT 3.25.3 1.8 diff --git a/examples/example-xds/build.gradle b/examples/example-xds/build.gradle index 2554adb0033..8339db77e0c 100644 --- a/examples/example-xds/build.gradle +++ b/examples/example-xds/build.gradle @@ -23,7 +23,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.67.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.25.3' dependencies { diff --git a/examples/pom.xml b/examples/pom.xml index 5cd721b50d1..247df4a73ce 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -6,13 +6,13 @@ jar - 1.67.0-SNAPSHOT + 1.68.0-SNAPSHOT examples https://github.com/grpc/grpc-java UTF-8 - 1.67.0-SNAPSHOT + 1.68.0-SNAPSHOT 3.25.3 3.25.3 From 721d063d554f742c4bed7b698f8cab373f5e0b1d Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 5 Sep 2024 13:24:51 -0700 Subject: [PATCH 064/103] core: touch() buffer when detach()ing Detachable lets a buffer outlive its original lifetime. The new lifetime is application-controlled. If the application fails to read/close the stream, then the leak detector wouldn't make clear what code was responsible for the buffer's lifetime. With this touch, we'll be able to see detach() was called and thus know the application needs debugging. Realized when looking at b/364531464, although I think the issue is unrelated. --- core/src/main/java/io/grpc/internal/ReadableBuffers.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/io/grpc/internal/ReadableBuffers.java b/core/src/main/java/io/grpc/internal/ReadableBuffers.java index 1435be138de..e512c810f84 100644 --- a/core/src/main/java/io/grpc/internal/ReadableBuffers.java +++ b/core/src/main/java/io/grpc/internal/ReadableBuffers.java @@ -415,6 +415,7 @@ public ByteBuffer getByteBuffer() { public InputStream detach() { ReadableBuffer detachedBuffer = buffer; buffer = buffer.readBytes(0); + detachedBuffer.touch(); return new BufferInputStream(detachedBuffer); } From f6d2f20fcdc9388b46debbc840c838f97ec7122d Mon Sep 17 00:00:00 2001 From: Vindhya Ningegowda Date: Fri, 6 Sep 2024 09:15:14 -0700 Subject: [PATCH 065/103] Fix assertion to resolve flakiness in upstreamLocalityStatsList order (#11514) --- .../io/grpc/xds/ClusterImplLoadBalancerTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java index 0082a2aa59d..aaaed9554f4 100644 --- a/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java @@ -440,15 +440,15 @@ public void pickFirstLoadReport_onUpdateAddress() { streamTracer2.streamClosed(Status.UNAVAILABLE); clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER)); - UpstreamLocalityStats localityStats1 = Iterables.get(clusterStats.upstreamLocalityStatsList(), - 0); - assertThat(localityStats1.locality()).isEqualTo(locality1); + List upstreamLocalityStatsList = + clusterStats.upstreamLocalityStatsList(); + UpstreamLocalityStats localityStats1 = Iterables.find(upstreamLocalityStatsList, + upstreamLocalityStats -> upstreamLocalityStats.locality().equals(locality1)); assertThat(localityStats1.totalIssuedRequests()).isEqualTo(0L); assertThat(localityStats1.totalSuccessfulRequests()).isEqualTo(0L); assertThat(localityStats1.totalErrorRequests()).isEqualTo(0L); - UpstreamLocalityStats localityStats2 = Iterables.get(clusterStats.upstreamLocalityStatsList(), - 1); - assertThat(localityStats2.locality()).isEqualTo(locality2); + UpstreamLocalityStats localityStats2 = Iterables.find(upstreamLocalityStatsList, + upstreamLocalityStats -> upstreamLocalityStats.locality().equals(locality2)); assertThat(localityStats2.totalIssuedRequests()).isEqualTo(1L); assertThat(localityStats2.totalSuccessfulRequests()).isEqualTo(0L); assertThat(localityStats2.totalErrorRequests()).isEqualTo(1L); From 5de65a6d504ad9557572d04cf3f84e9caa9201fe Mon Sep 17 00:00:00 2001 From: Larry Safran Date: Fri, 6 Sep 2024 11:43:07 -0700 Subject: [PATCH 066/103] use an attribute from resolved addresses IS_PETIOLE_POLICY to control whether or not health checking is supported (#11513) * use an attribute from resolved addresses IS_PETIOLE_POLICY to control whether or not health checking is supported so that top level versions can't do any health checking, while those under petiole policies can. Fixes #11413 --- .../internal/PickFirstLeafLoadBalancer.java | 17 +++++++-- .../PickFirstLeafLoadBalancerTest.java | 36 +++++++++++++++++-- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java index 344012fef2b..bfa462e16e1 100644 --- a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java +++ b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java @@ -69,6 +69,7 @@ final class PickFirstLeafLoadBalancer extends LoadBalancer { private ConnectivityState concludedState = IDLE; private final boolean enableHappyEyeballs = PickFirstLoadBalancerProvider.isEnabledHappyEyeballs(); + private boolean notAPetiolePolicy = true; // means not under a petiole policy PickFirstLeafLoadBalancer(Helper helper) { this.helper = checkNotNull(helper, "helper"); @@ -80,6 +81,10 @@ public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { return Status.FAILED_PRECONDITION.withDescription("Already shut down"); } + // Cache whether or not this is a petiole policy, which is based off of an address attribute + Boolean isPetiolePolicy = resolvedAddresses.getAttributes().get(IS_PETIOLE_POLICY); + this.notAPetiolePolicy = isPetiolePolicy == null || !isPetiolePolicy; + List servers = resolvedAddresses.getAddresses(); // Validate the address list @@ -303,7 +308,8 @@ private void updateHealthCheckedState(SubchannelData subchannelData) { if (subchannelData.state != READY) { return; } - if (subchannelData.getHealthState() == READY) { + + if (notAPetiolePolicy || subchannelData.getHealthState() == READY) { updateBalancingState(READY, new FixedResultPicker(PickResult.withSubchannel(subchannelData.subchannel))); } else if (subchannelData.getHealthState() == TRANSIENT_FAILURE) { @@ -444,7 +450,7 @@ private SubchannelData createNewSubchannel(SocketAddress addr, Attributes attrs) hcListener.subchannelData = subchannelData; subchannels.put(addr, subchannelData); Attributes scAttrs = subchannel.getAttributes(); - if (scAttrs.get(LoadBalancer.HAS_HEALTH_PRODUCER_LISTENER_KEY) == null) { + if (notAPetiolePolicy || scAttrs.get(LoadBalancer.HAS_HEALTH_PRODUCER_LISTENER_KEY) == null) { subchannelData.healthStateInfo = ConnectivityStateInfo.forNonError(READY); } subchannel.start(stateInfo -> processSubchannelState(subchannelData, stateInfo)); @@ -468,6 +474,13 @@ private final class HealthListener implements SubchannelStateListener { @Override public void onSubchannelState(ConnectivityStateInfo newState) { + if (notAPetiolePolicy) { + log.log(Level.WARNING, + "Ignoring health status {0} for subchannel {1} as this is not under a petiole policy", + new Object[]{newState, subchannelData.subchannel}); + return; + } + log.log(Level.FINE, "Received health status {0} for subchannel {1}", new Object[]{newState, subchannelData.subchannel}); subchannelData.healthStateInfo = newState; diff --git a/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java b/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java index 9c22c4a7086..63915bddc99 100644 --- a/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java +++ b/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java @@ -25,6 +25,7 @@ import static io.grpc.ConnectivityState.TRANSIENT_FAILURE; import static io.grpc.LoadBalancer.HAS_HEALTH_PRODUCER_LISTENER_KEY; import static io.grpc.LoadBalancer.HEALTH_CONSUMER_LISTENER_ARG_KEY; +import static io.grpc.LoadBalancer.IS_PETIOLE_POLICY; import static io.grpc.internal.PickFirstLeafLoadBalancer.CONNECTION_DELAY_INTERVAL_MS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -389,15 +390,44 @@ public void pickAfterResolvedAndChanged() { verify(mockSubchannel2).requestConnection(); } + @Test + public void healthCheck_nonPetiolePolicy() { + when(mockSubchannel1.getAttributes()).thenReturn( + Attributes.newBuilder().set(HAS_HEALTH_PRODUCER_LISTENER_KEY, true).build()); + + // Initialize with one server loadbalancer and both health and state listeners + List oneServer = Lists.newArrayList(servers.get(0)); + loadBalancer.acceptResolvedAddresses(ResolvedAddresses.newBuilder().setAddresses(oneServer) + .setAttributes(Attributes.EMPTY).build()); + InOrder inOrder = inOrder(mockHelper, mockSubchannel1); + inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), any(SubchannelPicker.class)); + inOrder.verify(mockHelper).createSubchannel(createArgsCaptor.capture()); + SubchannelStateListener healthListener = createArgsCaptor.getValue() + .getOption(HEALTH_CONSUMER_LISTENER_ARG_KEY); + inOrder.verify(mockSubchannel1).start(stateListenerCaptor.capture()); + SubchannelStateListener stateListener = stateListenerCaptor.getValue(); + + stateListener.onSubchannelState(ConnectivityStateInfo.forNonError(CONNECTING)); + healthListener.onSubchannelState(ConnectivityStateInfo.forNonError(CONNECTING)); + inOrder.verify(mockHelper, never()).updateBalancingState(any(), any()); + + stateListener.onSubchannelState(ConnectivityStateInfo.forNonError(READY)); + inOrder.verify(mockHelper).updateBalancingState(eq(READY), any()); // health listener ignored + + healthListener.onSubchannelState(ConnectivityStateInfo.forTransientFailure(Status.INTERNAL)); + inOrder.verify(mockHelper, never()).updateBalancingState(any(), any(SubchannelPicker.class)); + } + @Test public void healthCheckFlow() { when(mockSubchannel1.getAttributes()).thenReturn( Attributes.newBuilder().set(HAS_HEALTH_PRODUCER_LISTENER_KEY, true).build()); when(mockSubchannel2.getAttributes()).thenReturn( Attributes.newBuilder().set(HAS_HEALTH_PRODUCER_LISTENER_KEY, true).build()); + List oneServer = Lists.newArrayList(servers.get(0), servers.get(1)); loadBalancer.acceptResolvedAddresses(ResolvedAddresses.newBuilder().setAddresses(oneServer) - .setAttributes(Attributes.EMPTY).build()); + .setAttributes(Attributes.newBuilder().set(IS_PETIOLE_POLICY, true).build()).build()); InOrder inOrder = inOrder(mockHelper, mockSubchannel1, mockSubchannel2); inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), any(SubchannelPicker.class)); @@ -413,13 +443,13 @@ public void healthCheckFlow() { // subchannel2 | IDLE | IDLE stateListener.onSubchannelState(ConnectivityStateInfo.forNonError(CONNECTING)); healthListener.onSubchannelState(ConnectivityStateInfo.forNonError(CONNECTING)); - inOrder.verify(mockHelper, times(0)).updateBalancingState(any(), any()); + inOrder.verify(mockHelper, never()).updateBalancingState(any(), any()); // subchannel | state | health // subchannel1 | READY | CONNECTING // subchannel2 | IDLE | IDLE stateListener.onSubchannelState(ConnectivityStateInfo.forNonError(READY)); - inOrder.verify(mockHelper, times(0)).updateBalancingState(any(), any()); + inOrder.verify(mockHelper, never()).updateBalancingState(any(), any()); // subchannel | state | health // subchannel1 | READY | READY From 15cd2f9443e363adcf6c579c2ebe6e9a5544b6e4 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 11 Jul 2024 13:29:33 -0700 Subject: [PATCH 067/103] buildscripts: OS X env should be in macos.sh unix.sh is shared by multiple OSes and environments. Clear JAVA_HOME, since we never want to use that as PATH is more reliable, better supported, and more typical. --- buildscripts/kokoro/macos.sh | 3 +++ buildscripts/kokoro/unix.sh | 5 ----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/buildscripts/kokoro/macos.sh b/buildscripts/kokoro/macos.sh index 97259231ee8..018d15dd2f9 100755 --- a/buildscripts/kokoro/macos.sh +++ b/buildscripts/kokoro/macos.sh @@ -15,4 +15,7 @@ export GRADLE_FLAGS="${GRADLE_FLAGS:-} --max-workers=2" . "$GRPC_JAVA_DIR"/buildscripts/kokoro/kokoro.sh trap spongify_logs EXIT +export -n JAVA_HOME +export PATH="$(/usr/libexec/java_home -v"1.8.0")/bin:${PATH}" + "$GRPC_JAVA_DIR"/buildscripts/kokoro/unix.sh diff --git a/buildscripts/kokoro/unix.sh b/buildscripts/kokoro/unix.sh index 9b1a4054c7e..1b88b56ab40 100755 --- a/buildscripts/kokoro/unix.sh +++ b/buildscripts/kokoro/unix.sh @@ -23,11 +23,6 @@ readonly GRPC_JAVA_DIR="$(cd "$(dirname "$0")"/../.. && pwd)" # cd to the root dir of grpc-java cd $(dirname $0)/../.. -# TODO(zpencer): always make sure we are using Oracle jdk8 -if [[ -f /usr/libexec/java_home ]]; then - JAVA_HOME=$(/usr/libexec/java_home -v"1.8.0") -fi - # ARCH is x86_64 unless otherwise specified. ARCH="${ARCH:-x86_64}" From f3cf7c3c75a2dd80b5e852b42efb6dc41e0d073a Mon Sep 17 00:00:00 2001 From: Vindhya Ningegowda Date: Thu, 12 Sep 2024 15:40:20 -0700 Subject: [PATCH 068/103] xds: Add xDS node ID in few control plane errors (#11519) --- .../java/io/grpc/xds/CdsLoadBalancer2.java | 18 +++++--- .../java/io/grpc/xds/XdsNameResolver.java | 4 +- .../java/io/grpc/xds/XdsServerWrapper.java | 15 +++++-- .../io/grpc/xds/CdsLoadBalancer2Test.java | 44 ++++++++++++++----- 4 files changed, 59 insertions(+), 22 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java b/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java index 773fdf20563..3f1eb3e7e4f 100644 --- a/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java +++ b/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java @@ -206,8 +206,9 @@ private void handleClusterDiscovered() { } loopStatus = Status.UNAVAILABLE.withDescription(String.format( "CDS error: circular aggregate clusters directly under %s for " - + "root cluster %s, named %s", - clusterState.name, root.name, namesCausingLoops)); + + "root cluster %s, named %s, xDS node ID: %s", + clusterState.name, root.name, namesCausingLoops, + xdsClient.getBootstrapInfo().node().getId())); } } } @@ -224,9 +225,9 @@ private void handleClusterDiscovered() { childLb.shutdown(); childLb = null; } - Status unavailable = - Status.UNAVAILABLE.withDescription("CDS error: found 0 leaf (logical DNS or EDS) " - + "clusters for root cluster " + root.name); + Status unavailable = Status.UNAVAILABLE.withDescription(String.format( + "CDS error: found 0 leaf (logical DNS or EDS) clusters for root cluster %s" + + " xDS node ID: %s", root.name, xdsClient.getBootstrapInfo().node().getId())); helper.updateBalancingState( TRANSIENT_FAILURE, new FixedResultPicker(PickResult.withError(unavailable))); return; @@ -288,11 +289,14 @@ private void addAncestors(Set ancestors, ClusterState clusterState, } private void handleClusterDiscoveryError(Status error) { + String description = error.getDescription() == null ? "" : error.getDescription() + " "; + Status errorWithNodeId = error.withDescription( + description + "xDS node ID: " + xdsClient.getBootstrapInfo().node().getId()); if (childLb != null) { - childLb.handleNameResolutionError(error); + childLb.handleNameResolutionError(errorWithNodeId); } else { helper.updateBalancingState( - TRANSIENT_FAILURE, new FixedResultPicker(PickResult.withError(error))); + TRANSIENT_FAILURE, new FixedResultPicker(PickResult.withError(errorWithNodeId))); } } diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java index f0329387fc9..ca73b7d8451 100644 --- a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java +++ b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java @@ -815,10 +815,12 @@ private void cleanUpRoutes(String error) { // the config selector handles the error message itself. Once the LB API allows providing // failure information for addresses yet still providing a service config, the config seector // could be avoided. + String errorWithNodeId = + error + ", xDS node ID: " + xdsClient.getBootstrapInfo().node().getId(); listener.onResult(ResolutionResult.newBuilder() .setAttributes(Attributes.newBuilder() .set(InternalConfigSelector.KEY, - new FailingConfigSelector(Status.UNAVAILABLE.withDescription(error))) + new FailingConfigSelector(Status.UNAVAILABLE.withDescription(errorWithNodeId))) .build()) .setServiceConfig(emptyServiceConfig) .build()); diff --git a/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java b/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java index dfb7c4fb7db..bd622a71124 100644 --- a/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java +++ b/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java @@ -425,7 +425,8 @@ public void onResourceDoesNotExist(final String resourceName) { return; } StatusException statusException = Status.UNAVAILABLE.withDescription( - "Listener " + resourceName + " unavailable").asException(); + String.format("Listener %s unavailable, xDS node ID: %s", resourceName, + xdsClient.getBootstrapInfo().node().getId())).asException(); handleConfigNotFound(statusException); } @@ -434,9 +435,12 @@ public void onError(final Status error) { if (stopped) { return; } - logger.log(Level.FINE, "Error from XdsClient", error); + String description = error.getDescription() == null ? "" : error.getDescription() + " "; + Status errorWithNodeId = error.withDescription( + description + "xDS node ID: " + xdsClient.getBootstrapInfo().node().getId()); + logger.log(Level.FINE, "Error from XdsClient", errorWithNodeId); if (!isServing) { - listener.onNotServing(error.asException()); + listener.onNotServing(errorWithNodeId.asException()); } } @@ -664,8 +668,11 @@ public void run() { if (!routeDiscoveryStates.containsKey(resourceName)) { return; } + String description = error.getDescription() == null ? "" : error.getDescription() + " "; + Status errorWithNodeId = error.withDescription( + description + "xDS node ID: " + xdsClient.getBootstrapInfo().node().getId()); logger.log(Level.WARNING, "Error loading RDS resource {0} from XdsClient: {1}.", - new Object[]{resourceName, error}); + new Object[]{resourceName, errorWithNodeId}); maybeUpdateSelector(); } }); diff --git a/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java b/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java index 0884587cd95..da32332a2a5 100644 --- a/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java +++ b/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java @@ -58,7 +58,9 @@ import io.grpc.xds.LeastRequestLoadBalancer.LeastRequestConfig; import io.grpc.xds.RingHashLoadBalancer.RingHashConfig; import io.grpc.xds.XdsClusterResource.CdsUpdate; +import io.grpc.xds.client.Bootstrapper.BootstrapInfo; import io.grpc.xds.client.Bootstrapper.ServerInfo; +import io.grpc.xds.client.EnvoyProtoData; import io.grpc.xds.client.XdsClient; import io.grpc.xds.client.XdsResourceType; import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; @@ -94,6 +96,16 @@ public class CdsLoadBalancer2Test { private static final String DNS_HOST_NAME = "backend-service-dns.googleapis.com:443"; private static final ServerInfo LRS_SERVER_INFO = ServerInfo.create("lrs.googleapis.com", InsecureChannelCredentials.create()); + private static final String SERVER_URI = "trafficdirector.googleapis.com"; + private static final String NODE_ID = + "projects/42/networks/default/nodes/5c85b298-6f5b-4722-b74a-f7d1f0ccf5ad"; + private static final EnvoyProtoData.Node BOOTSTRAP_NODE = + EnvoyProtoData.Node.newBuilder().setId(NODE_ID).build(); + private static final BootstrapInfo BOOTSTRAP_INFO = BootstrapInfo.builder() + .servers(ImmutableList.of( + ServerInfo.create(SERVER_URI, InsecureChannelCredentials.create()))) + .node(BOOTSTRAP_NODE) + .build(); private final UpstreamTlsContext upstreamTlsContext = CommonTlsContextTestsUtil.buildUpstreamTlsContext("google_cloud_private_spiffe", true); private final OutlierDetection outlierDetection = OutlierDetection.create( @@ -211,7 +223,8 @@ public void nonAggregateCluster_resourceNotExist_returnErrorPicker() { verify(helper).updateBalancingState( eq(ConnectivityState.TRANSIENT_FAILURE), pickerCaptor.capture()); Status unavailable = Status.UNAVAILABLE.withDescription( - "CDS error: found 0 leaf (logical DNS or EDS) clusters for root cluster " + CLUSTER); + "CDS error: found 0 leaf (logical DNS or EDS) clusters for root cluster " + CLUSTER + + " xDS node ID: " + NODE_ID); assertPicker(pickerCaptor.getValue(), unavailable, null); assertThat(childBalancers).isEmpty(); } @@ -254,7 +267,8 @@ public void nonAggregateCluster_resourceRevoked() { xdsClient.deliverResourceNotExist(CLUSTER); assertThat(childBalancer.shutdown).isTrue(); Status unavailable = Status.UNAVAILABLE.withDescription( - "CDS error: found 0 leaf (logical DNS or EDS) clusters for root cluster " + CLUSTER); + "CDS error: found 0 leaf (logical DNS or EDS) clusters for root cluster " + CLUSTER + + " xDS node ID: " + NODE_ID); verify(helper).updateBalancingState( eq(ConnectivityState.TRANSIENT_FAILURE), pickerCaptor.capture()); assertPicker(pickerCaptor.getValue(), unavailable, null); @@ -331,7 +345,8 @@ public void aggregateCluster_noNonAggregateClusterExits_returnErrorPicker() { verify(helper).updateBalancingState( eq(ConnectivityState.TRANSIENT_FAILURE), pickerCaptor.capture()); Status unavailable = Status.UNAVAILABLE.withDescription( - "CDS error: found 0 leaf (logical DNS or EDS) clusters for root cluster " + CLUSTER); + "CDS error: found 0 leaf (logical DNS or EDS) clusters for root cluster " + CLUSTER + + " xDS node ID: " + NODE_ID); assertPicker(pickerCaptor.getValue(), unavailable, null); assertThat(childBalancers).isEmpty(); } @@ -379,7 +394,8 @@ public void aggregateCluster_descendantClustersRevoked() { verify(helper).updateBalancingState( eq(ConnectivityState.TRANSIENT_FAILURE), pickerCaptor.capture()); Status unavailable = Status.UNAVAILABLE.withDescription( - "CDS error: found 0 leaf (logical DNS or EDS) clusters for root cluster " + CLUSTER); + "CDS error: found 0 leaf (logical DNS or EDS) clusters for root cluster " + CLUSTER + + " xDS node ID: " + NODE_ID); assertPicker(pickerCaptor.getValue(), unavailable, null); assertThat(childBalancer.shutdown).isTrue(); assertThat(childBalancers).isEmpty(); @@ -418,7 +434,8 @@ public void aggregateCluster_rootClusterRevoked() { verify(helper).updateBalancingState( eq(ConnectivityState.TRANSIENT_FAILURE), pickerCaptor.capture()); Status unavailable = Status.UNAVAILABLE.withDescription( - "CDS error: found 0 leaf (logical DNS or EDS) clusters for root cluster " + CLUSTER); + "CDS error: found 0 leaf (logical DNS or EDS) clusters for root cluster " + CLUSTER + + " xDS node ID: " + NODE_ID); assertPicker(pickerCaptor.getValue(), unavailable, null); assertThat(childBalancer.shutdown).isTrue(); assertThat(childBalancers).isEmpty(); @@ -466,7 +483,8 @@ public void aggregateCluster_intermediateClusterChanges() { verify(helper).updateBalancingState( eq(ConnectivityState.TRANSIENT_FAILURE), pickerCaptor.capture()); Status unavailable = Status.UNAVAILABLE.withDescription( - "CDS error: found 0 leaf (logical DNS or EDS) clusters for root cluster " + CLUSTER); + "CDS error: found 0 leaf (logical DNS or EDS) clusters for root cluster " + CLUSTER + + " xDS node ID: " + NODE_ID); assertPicker(pickerCaptor.getValue(), unavailable, null); assertThat(childBalancer.shutdown).isTrue(); assertThat(childBalancers).isEmpty(); @@ -507,7 +525,7 @@ public void aggregateCluster_withLoops() { Status unavailable = Status.UNAVAILABLE.withDescription( "CDS error: circular aggregate clusters directly under cluster-02.googleapis.com for root" + " cluster cluster-foo.googleapis.com, named [cluster-01.googleapis.com," - + " cluster-02.googleapis.com]"); + + " cluster-02.googleapis.com], xDS node ID: " + NODE_ID); assertPicker(pickerCaptor.getValue(), unavailable, null); } @@ -549,7 +567,7 @@ public void aggregateCluster_withLoops_afterEds() { Status unavailable = Status.UNAVAILABLE.withDescription( "CDS error: circular aggregate clusters directly under cluster-02.googleapis.com for root" + " cluster cluster-foo.googleapis.com, named [cluster-01.googleapis.com," - + " cluster-02.googleapis.com]"); + + " cluster-02.googleapis.com], xDS node ID: " + NODE_ID); assertPicker(pickerCaptor.getValue(), unavailable, null); } @@ -617,7 +635,7 @@ public void aggregateCluster_discoveryErrorBeforeChildLbCreated_returnErrorPicke eq(ConnectivityState.TRANSIENT_FAILURE), pickerCaptor.capture()); Status expectedError = Status.UNAVAILABLE.withDescription( "Unable to load CDS cluster-foo.googleapis.com. xDS server returned: " - + "RESOURCE_EXHAUSTED: OOM"); + + "RESOURCE_EXHAUSTED: OOM xDS node ID: " + NODE_ID); assertPicker(pickerCaptor.getValue(), expectedError, null); assertThat(childBalancers).isEmpty(); } @@ -647,7 +665,8 @@ public void aggregateCluster_discoveryErrorAfterChildLbCreated_propagateToChildL @Test public void handleNameResolutionErrorFromUpstream_beforeChildLbCreated_returnErrorPicker() { - Status upstreamError = Status.UNAVAILABLE.withDescription("unreachable"); + Status upstreamError = Status.UNAVAILABLE.withDescription( + "unreachable xDS node ID: " + NODE_ID); loadBalancer.handleNameResolutionError(upstreamError); verify(helper).updateBalancingState( eq(ConnectivityState.TRANSIENT_FAILURE), pickerCaptor.capture()); @@ -821,6 +840,11 @@ public void cancelXdsResourceWatch(XdsResourceType } } + @Override + public BootstrapInfo getBootstrapInfo() { + return BOOTSTRAP_INFO; + } + private void deliverCdsUpdate(String clusterName, CdsUpdate update) { if (watchers.containsKey(clusterName)) { List> resourceWatchers = From b8c1aa517a0de2746977af856fb2c30b7fb7a26b Mon Sep 17 00:00:00 2001 From: Riya Mehta <55350838+rmehta19@users.noreply.github.com> Date: Fri, 13 Sep 2024 17:11:17 -0700 Subject: [PATCH 069/103] s2a: Add gRPC S2A (#11113) --- MODULE.bazel | 1 + buildscripts/sync-protos.sh | 2 +- repositories.bzl | 2 + s2a/BUILD.bazel | 194 +++++++++ s2a/build.gradle | 151 +++++++ .../grpc/s2a/handshaker/S2AServiceGrpc.java | 285 +++++++++++++ .../grpc/s2a/MtlsToS2AChannelCredentials.java | 96 +++++ .../io/grpc/s2a/S2AChannelCredentials.java | 130 ++++++ .../io/grpc/s2a/channel/S2AChannelPool.java | 43 ++ .../grpc/s2a/channel/S2AGrpcChannelPool.java | 109 +++++ .../channel/S2AHandshakerServiceChannel.java | 195 +++++++++ .../handshaker/ConnectionClosedException.java | 27 ++ .../GetAuthenticationMechanisms.java | 56 +++ .../io/grpc/s2a/handshaker/ProtoUtil.java | 96 +++++ .../handshaker/S2AConnectionException.java | 25 ++ .../io/grpc/s2a/handshaker/S2AIdentity.java | 62 +++ .../s2a/handshaker/S2APrivateKeyMethod.java | 143 +++++++ .../S2AProtocolNegotiatorFactory.java | 249 +++++++++++ .../java/io/grpc/s2a/handshaker/S2AStub.java | 221 ++++++++++ .../grpc/s2a/handshaker/S2ATrustManager.java | 152 +++++++ .../s2a/handshaker/SslContextFactory.java | 178 ++++++++ .../tokenmanager/AccessTokenManager.java | 61 +++ .../tokenmanager/SingleTokenFetcher.java | 57 +++ .../handshaker/tokenmanager/TokenFetcher.java | 28 ++ s2a/src/main/proto/grpc/gcp/s2a/common.proto | 82 ++++ s2a/src/main/proto/grpc/gcp/s2a/s2a.proto | 369 +++++++++++++++++ .../main/proto/grpc/gcp/s2a/s2a_context.proto | 62 +++ .../s2a/MtlsToS2AChannelCredentialsTest.java | 135 ++++++ .../grpc/s2a/S2AChannelCredentialsTest.java | 112 +++++ .../s2a/channel/S2AGrpcChannelPoolTest.java | 125 ++++++ .../S2AHandshakerServiceChannelTest.java | 390 ++++++++++++++++++ .../io/grpc/s2a/handshaker/FakeS2AServer.java | 55 +++ .../s2a/handshaker/FakeS2AServerTest.java | 265 ++++++++++++ .../io/grpc/s2a/handshaker/FakeWriter.java | 363 ++++++++++++++++ .../GetAuthenticationMechanismsTest.java | 61 +++ .../grpc/s2a/handshaker/IntegrationTest.java | 320 ++++++++++++++ .../io/grpc/s2a/handshaker/ProtoUtilTest.java | 131 ++++++ .../handshaker/S2APrivateKeyMethodTest.java | 308 ++++++++++++++ .../S2AProtocolNegotiatorFactoryTest.java | 284 +++++++++++++ .../io/grpc/s2a/handshaker/S2AStubTest.java | 260 ++++++++++++ .../s2a/handshaker/S2ATrustManagerTest.java | 262 ++++++++++++ .../s2a/handshaker/SslContextFactoryTest.java | 177 ++++++++ .../SingleTokenAccessTokenManagerTest.java | 71 ++++ s2a/src/test/resources/README.md | 32 ++ s2a/src/test/resources/client.csr | 16 + s2a/src/test/resources/client_cert.pem | 18 + s2a/src/test/resources/client_key.pem | 28 ++ s2a/src/test/resources/config.cnf | 17 + s2a/src/test/resources/root_cert.pem | 22 + s2a/src/test/resources/root_key.pem | 30 ++ s2a/src/test/resources/server.csr | 16 + s2a/src/test/resources/server_cert.pem | 20 + s2a/src/test/resources/server_key.pem | 28 ++ settings.gradle | 2 + 54 files changed, 6623 insertions(+), 1 deletion(-) create mode 100644 s2a/BUILD.bazel create mode 100644 s2a/build.gradle create mode 100644 s2a/src/generated/main/grpc/io/grpc/s2a/handshaker/S2AServiceGrpc.java create mode 100644 s2a/src/main/java/io/grpc/s2a/MtlsToS2AChannelCredentials.java create mode 100644 s2a/src/main/java/io/grpc/s2a/S2AChannelCredentials.java create mode 100644 s2a/src/main/java/io/grpc/s2a/channel/S2AChannelPool.java create mode 100644 s2a/src/main/java/io/grpc/s2a/channel/S2AGrpcChannelPool.java create mode 100644 s2a/src/main/java/io/grpc/s2a/channel/S2AHandshakerServiceChannel.java create mode 100644 s2a/src/main/java/io/grpc/s2a/handshaker/ConnectionClosedException.java create mode 100644 s2a/src/main/java/io/grpc/s2a/handshaker/GetAuthenticationMechanisms.java create mode 100644 s2a/src/main/java/io/grpc/s2a/handshaker/ProtoUtil.java create mode 100644 s2a/src/main/java/io/grpc/s2a/handshaker/S2AConnectionException.java create mode 100644 s2a/src/main/java/io/grpc/s2a/handshaker/S2AIdentity.java create mode 100644 s2a/src/main/java/io/grpc/s2a/handshaker/S2APrivateKeyMethod.java create mode 100644 s2a/src/main/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactory.java create mode 100644 s2a/src/main/java/io/grpc/s2a/handshaker/S2AStub.java create mode 100644 s2a/src/main/java/io/grpc/s2a/handshaker/S2ATrustManager.java create mode 100644 s2a/src/main/java/io/grpc/s2a/handshaker/SslContextFactory.java create mode 100644 s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/AccessTokenManager.java create mode 100644 s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/SingleTokenFetcher.java create mode 100644 s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/TokenFetcher.java create mode 100644 s2a/src/main/proto/grpc/gcp/s2a/common.proto create mode 100644 s2a/src/main/proto/grpc/gcp/s2a/s2a.proto create mode 100644 s2a/src/main/proto/grpc/gcp/s2a/s2a_context.proto create mode 100644 s2a/src/test/java/io/grpc/s2a/MtlsToS2AChannelCredentialsTest.java create mode 100644 s2a/src/test/java/io/grpc/s2a/S2AChannelCredentialsTest.java create mode 100644 s2a/src/test/java/io/grpc/s2a/channel/S2AGrpcChannelPoolTest.java create mode 100644 s2a/src/test/java/io/grpc/s2a/channel/S2AHandshakerServiceChannelTest.java create mode 100644 s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServer.java create mode 100644 s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServerTest.java create mode 100644 s2a/src/test/java/io/grpc/s2a/handshaker/FakeWriter.java create mode 100644 s2a/src/test/java/io/grpc/s2a/handshaker/GetAuthenticationMechanismsTest.java create mode 100644 s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java create mode 100644 s2a/src/test/java/io/grpc/s2a/handshaker/ProtoUtilTest.java create mode 100644 s2a/src/test/java/io/grpc/s2a/handshaker/S2APrivateKeyMethodTest.java create mode 100644 s2a/src/test/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactoryTest.java create mode 100644 s2a/src/test/java/io/grpc/s2a/handshaker/S2AStubTest.java create mode 100644 s2a/src/test/java/io/grpc/s2a/handshaker/S2ATrustManagerTest.java create mode 100644 s2a/src/test/java/io/grpc/s2a/handshaker/SslContextFactoryTest.java create mode 100644 s2a/src/test/java/io/grpc/s2a/handshaker/tokenmanager/SingleTokenAccessTokenManagerTest.java create mode 100644 s2a/src/test/resources/README.md create mode 100644 s2a/src/test/resources/client.csr create mode 100644 s2a/src/test/resources/client_cert.pem create mode 100644 s2a/src/test/resources/client_key.pem create mode 100644 s2a/src/test/resources/config.cnf create mode 100644 s2a/src/test/resources/root_cert.pem create mode 100644 s2a/src/test/resources/root_key.pem create mode 100644 s2a/src/test/resources/server.csr create mode 100644 s2a/src/test/resources/server_cert.pem create mode 100644 s2a/src/test/resources/server_key.pem diff --git a/MODULE.bazel b/MODULE.bazel index b60ea565073..81fa8795e68 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -41,6 +41,7 @@ IO_GRPC_GRPC_JAVA_ARTIFACTS = [ "io.perfmark:perfmark-api:0.27.0", "junit:junit:4.13.2", "org.apache.tomcat:annotations-api:6.0.53", + "org.checkerframework:checker-qual:3.12.0", "org.codehaus.mojo:animal-sniffer-annotations:1.24", ] # GRPC_DEPS_END diff --git a/buildscripts/sync-protos.sh b/buildscripts/sync-protos.sh index 5f01be2e5c9..628b1688d4c 100755 --- a/buildscripts/sync-protos.sh +++ b/buildscripts/sync-protos.sh @@ -8,7 +8,7 @@ curl -Ls https://github.com/grpc/grpc-proto/archive/master.tar.gz | tar xz -C "$ base="$tmpdir/grpc-proto-master" # Copy protos in 'src/main/proto' from grpc-proto for these projects -for project in alts grpclb services rls interop-testing; do +for project in alts grpclb services s2a rls interop-testing; do while read -r proto; do [ -f "$base/$proto" ] && cp "$base/$proto" "$project/src/main/proto/$proto" echo "$proto" diff --git a/repositories.bzl b/repositories.bzl index 455e9dcf3ca..f8cf77c8190 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -45,6 +45,7 @@ IO_GRPC_GRPC_JAVA_ARTIFACTS = [ "io.perfmark:perfmark-api:0.27.0", "junit:junit:4.13.2", "org.apache.tomcat:annotations-api:6.0.53", + "org.checkerframework:checker-qual:3.12.0", "org.codehaus.mojo:animal-sniffer-annotations:1.24", ] # GRPC_DEPS_END @@ -80,6 +81,7 @@ IO_GRPC_GRPC_JAVA_OVERRIDE_TARGETS = { "io.grpc:grpc-rls": "@io_grpc_grpc_java//rls", "io.grpc:grpc-services": "@io_grpc_grpc_java//services:services_maven", "io.grpc:grpc-stub": "@io_grpc_grpc_java//stub", + "io.grpc:grpc-s2a": "@io_grpc_grpc_java//s2a", "io.grpc:grpc-testing": "@io_grpc_grpc_java//testing", "io.grpc:grpc-xds": "@io_grpc_grpc_java//xds:xds_maven", "io.grpc:grpc-util": "@io_grpc_grpc_java//util", diff --git a/s2a/BUILD.bazel b/s2a/BUILD.bazel new file mode 100644 index 00000000000..5aeaedbe358 --- /dev/null +++ b/s2a/BUILD.bazel @@ -0,0 +1,194 @@ +load("@rules_proto//proto:defs.bzl", "proto_library") +load("//:java_grpc_library.bzl", "java_grpc_library") +load("@rules_jvm_external//:defs.bzl", "artifact") + +java_library( + name = "s2a_channel_pool", + srcs = glob([ + "src/main/java/io/grpc/s2a/channel/*.java", + ]), + deps = [ + "//api", + "//core", + "//core:internal", + "//netty", + artifact("com.google.code.findbugs:jsr305"), + artifact("com.google.errorprone:error_prone_annotations"), + artifact("com.google.guava:guava"), + artifact("org.checkerframework:checker-qual"), + artifact("io.netty:netty-common"), + artifact("io.netty:netty-transport"), + ], +) + +java_library( + name = "s2a_identity", + srcs = ["src/main/java/io/grpc/s2a/handshaker/S2AIdentity.java"], + deps = [ + ":common_java_proto", + artifact("com.google.errorprone:error_prone_annotations"), + artifact("com.google.guava:guava"), + ], +) + +java_library( + name = "token_fetcher", + srcs = ["src/main/java/io/grpc/s2a/handshaker/tokenmanager/TokenFetcher.java"], + deps = [ + ":s2a_identity", + ], +) + +java_library( + name = "access_token_manager", + srcs = [ + "src/main/java/io/grpc/s2a/handshaker/tokenmanager/AccessTokenManager.java", + ], + deps = [ + ":s2a_identity", + ":token_fetcher", + artifact("com.google.code.findbugs:jsr305"), + ], +) + +java_library( + name = "single_token_fetcher", + srcs = [ + "src/main/java/io/grpc/s2a/handshaker/tokenmanager/SingleTokenFetcher.java", + ], + deps = [ + ":s2a_identity", + ":token_fetcher", + artifact("com.google.guava:guava"), + ], +) + +java_library( + name = "s2a_handshaker", + srcs = [ + "src/main/java/io/grpc/s2a/handshaker/ConnectionClosedException.java", + "src/main/java/io/grpc/s2a/handshaker/GetAuthenticationMechanisms.java", + "src/main/java/io/grpc/s2a/handshaker/ProtoUtil.java", + "src/main/java/io/grpc/s2a/handshaker/S2AConnectionException.java", + "src/main/java/io/grpc/s2a/handshaker/S2APrivateKeyMethod.java", + "src/main/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactory.java", + "src/main/java/io/grpc/s2a/handshaker/S2AStub.java", + "src/main/java/io/grpc/s2a/handshaker/S2ATrustManager.java", + "src/main/java/io/grpc/s2a/handshaker/SslContextFactory.java", + ], + deps = [ + ":access_token_manager", + ":common_java_proto", + ":s2a_channel_pool", + ":s2a_identity", + ":s2a_java_proto", + ":s2a_java_grpc_proto", + ":single_token_fetcher", + "//api", + "//core:internal", + "//netty", + "//stub", + artifact("com.google.code.findbugs:jsr305"), + artifact("com.google.errorprone:error_prone_annotations"), + artifact("com.google.guava:guava"), + artifact("org.checkerframework:checker-qual"), + "@com_google_protobuf//:protobuf_java", + artifact("io.netty:netty-common"), + artifact("io.netty:netty-handler"), + artifact("io.netty:netty-transport"), + ], +) + +java_library( + name = "s2av2_credentials", + srcs = ["src/main/java/io/grpc/s2a/S2AChannelCredentials.java"], + visibility = ["//visibility:public"], + deps = [ + ":s2a_channel_pool", + ":s2a_handshaker", + ":s2a_identity", + "//api", + "//core:internal", + "//netty", + artifact("com.google.code.findbugs:jsr305"), + artifact("com.google.errorprone:error_prone_annotations"), + artifact("com.google.guava:guava"), + artifact("org.checkerframework:checker-qual"), + ], +) + +java_library( + name = "mtls_to_s2av2_credentials", + srcs = ["src/main/java/io/grpc/s2a/MtlsToS2AChannelCredentials.java"], + visibility = ["//visibility:public"], + deps = [ + ":s2a_channel_pool", + ":s2av2_credentials", + "//api", + "//util", + artifact("com.google.guava:guava"), + ], +) + +# bazel only accepts proto import with absolute path. +genrule( + name = "protobuf_imports", + srcs = glob(["src/main/proto/grpc/gcp/s2a/*.proto"]), + outs = [ + "protobuf_out/grpc/gcp/s2a/s2a.proto", + "protobuf_out/grpc/gcp/s2a/s2a_context.proto", + "protobuf_out/grpc/gcp/s2a/common.proto", + ], + cmd = "for fname in $(SRCS); do " + + "sed 's,import \",import \"s2a/protobuf_out/,g' $$fname > " + + "$(@D)/protobuf_out/grpc/gcp/s2a/$$(basename $$fname); done", +) + +proto_library( + name = "common_proto", + srcs = [ + "protobuf_out/grpc/gcp/s2a/common.proto", + ], +) + +proto_library( + name = "s2a_context_proto", + srcs = [ + "protobuf_out/grpc/gcp/s2a/s2a_context.proto", + ], + deps = [ + ":common_proto", + ], +) + +proto_library( + name = "s2a_proto", + srcs = [ + "protobuf_out/grpc/gcp/s2a/s2a.proto", + ], + deps = [ + ":common_proto", + ":s2a_context_proto", + ], +) + +java_proto_library( + name = "s2a_java_proto", + deps = [":s2a_proto"], +) + +java_proto_library( + name = "s2a_context_java_proto", + deps = [":s2a_context_proto"], +) + +java_proto_library( + name = "common_java_proto", + deps = [":common_proto"], +) + +java_grpc_library( + name = "s2a_java_grpc_proto", + srcs = [":s2a_proto"], + deps = [":s2a_java_proto"], +) diff --git a/s2a/build.gradle b/s2a/build.gradle new file mode 100644 index 00000000000..403ac93552f --- /dev/null +++ b/s2a/build.gradle @@ -0,0 +1,151 @@ +buildscript { + dependencies { + classpath 'com.google.gradle:osdetector-gradle-plugin:1.4.0' + } +} + +plugins { + id "java-library" + id "maven-publish" + + id "com.github.johnrengelman.shadow" + id "com.google.protobuf" + id "ru.vyarus.animalsniffer" +} + +description = "gRPC: S2A" + +apply plugin: "com.google.osdetector" + +dependencies { + + api project(':grpc-api') + implementation project(':grpc-stub'), + project(':grpc-protobuf'), + project(':grpc-core'), + libraries.protobuf.java, + libraries.conscrypt, + libraries.guava.jre // JRE required by protobuf-java-util from grpclb + def nettyDependency = implementation project(':grpc-netty') + compileOnly libraries.javax.annotation + + shadow configurations.implementation.getDependencies().minus(nettyDependency) + shadow project(path: ':grpc-netty-shaded', configuration: 'shadow') + + testImplementation project(':grpc-benchmarks'), + project(':grpc-testing'), + project(':grpc-testing-proto'), + testFixtures(project(':grpc-core')), + libraries.guava, + libraries.junit, + libraries.mockito.core, + libraries.truth, + libraries.conscrypt, + libraries.netty.transport.epoll + + testImplementation 'com.google.truth:truth:1.4.2' + testImplementation 'com.google.truth.extensions:truth-proto-extension:1.4.2' + testImplementation libraries.guava.testlib + + testRuntimeOnly libraries.netty.tcnative, + libraries.netty.tcnative.classes + testRuntimeOnly (libraries.netty.tcnative) { + artifact { + classifier = "linux-x86_64" + } + } + testRuntimeOnly (libraries.netty.tcnative) { + artifact { + classifier = "linux-aarch_64" + } + } + testRuntimeOnly (libraries.netty.tcnative) { + artifact { + classifier = "osx-x86_64" + } + } + testRuntimeOnly (libraries.netty.tcnative) { + artifact { + classifier = "osx-aarch_64" + } + } + testRuntimeOnly (libraries.netty.tcnative) { + artifact { + classifier = "windows-x86_64" + } + } + testRuntimeOnly (libraries.netty.transport.epoll) { + artifact { + classifier = "linux-x86_64" + } + } + + signature libraries.signature.java +} + +tasks.named("compileJava") { + dependsOn(tasks.named("generateProto")) + //dependsOn(tasks.named("syncGeneratedSourcesmain")) +} + + +tasks.named("sourcesJar") { + dependsOn(tasks.named("generateProto")) + //dependsOn(tasks.named("syncGeneratedSourcesmain")) +} + +sourceSets { + main { + //java.srcDirs += "src/generated/main/java" + //java.srcDirs += "src/generated/main/grpc" + } +} +//println sourceSets.main.java.srcDirs +//println sourceSets.test.resources.srcDirs + +configureProtoCompilation() + +tasks.named("javadoc").configure { + exclude 'io/grpc/s2a/**' +} + +tasks.named("jar").configure { + // Must use a different archiveClassifier to avoid conflicting with shadowJar + archiveClassifier = 'original' + manifest { + attributes('Automatic-Module-Name': 'io.grpc.s2a') + } +} + +// We want to use grpc-netty-shaded instead of grpc-netty. But we also want our +// source to work with Bazel, so we rewrite the code as part of the build. +tasks.named("shadowJar").configure { + archiveClassifier = null + dependencies { + exclude(dependency {true}) + } + relocate 'io.grpc.netty', 'io.grpc.netty.shaded.io.grpc.netty' + relocate 'io.netty', 'io.grpc.netty.shaded.io.netty' +} + +publishing { + publications { + maven(MavenPublication) { + // We want this to throw an exception if it isn't working + def originalJar = artifacts.find { dep -> dep.classifier == 'original'} + artifacts.remove(originalJar) + + pom.withXml { + def dependenciesNode = new Node(null, 'dependencies') + project.configurations.shadow.allDependencies.each { dep -> + def dependencyNode = dependenciesNode.appendNode('dependency') + dependencyNode.appendNode('groupId', dep.group) + dependencyNode.appendNode('artifactId', dep.name) + dependencyNode.appendNode('version', dep.version) + dependencyNode.appendNode('scope', 'compile') + } + asNode().dependencies[0].replaceNode(dependenciesNode) + } + } + } +} diff --git a/s2a/src/generated/main/grpc/io/grpc/s2a/handshaker/S2AServiceGrpc.java b/s2a/src/generated/main/grpc/io/grpc/s2a/handshaker/S2AServiceGrpc.java new file mode 100644 index 00000000000..b365954b189 --- /dev/null +++ b/s2a/src/generated/main/grpc/io/grpc/s2a/handshaker/S2AServiceGrpc.java @@ -0,0 +1,285 @@ +package io.grpc.s2a.handshaker; + +import static io.grpc.MethodDescriptor.generateFullMethodName; + +/** + */ +@javax.annotation.Generated( + value = "by gRPC proto compiler", + comments = "Source: grpc/gcp/s2a/s2a.proto") +@io.grpc.stub.annotations.GrpcGenerated +public final class S2AServiceGrpc { + + private S2AServiceGrpc() {} + + public static final java.lang.String SERVICE_NAME = "grpc.gcp.s2a.S2AService"; + + // Static method descriptors that strictly reflect the proto. + private static volatile io.grpc.MethodDescriptor getSetUpSessionMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "SetUpSession", + requestType = io.grpc.s2a.handshaker.SessionReq.class, + responseType = io.grpc.s2a.handshaker.SessionResp.class, + methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING) + public static io.grpc.MethodDescriptor getSetUpSessionMethod() { + io.grpc.MethodDescriptor getSetUpSessionMethod; + if ((getSetUpSessionMethod = S2AServiceGrpc.getSetUpSessionMethod) == null) { + synchronized (S2AServiceGrpc.class) { + if ((getSetUpSessionMethod = S2AServiceGrpc.getSetUpSessionMethod) == null) { + S2AServiceGrpc.getSetUpSessionMethod = getSetUpSessionMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "SetUpSession")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + io.grpc.s2a.handshaker.SessionReq.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + io.grpc.s2a.handshaker.SessionResp.getDefaultInstance())) + .setSchemaDescriptor(new S2AServiceMethodDescriptorSupplier("SetUpSession")) + .build(); + } + } + } + return getSetUpSessionMethod; + } + + /** + * Creates a new async stub that supports all call types for the service + */ + public static S2AServiceStub newStub(io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @java.lang.Override + public S2AServiceStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new S2AServiceStub(channel, callOptions); + } + }; + return S2AServiceStub.newStub(factory, channel); + } + + /** + * Creates a new blocking-style stub that supports unary and streaming output calls on the service + */ + public static S2AServiceBlockingStub newBlockingStub( + io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @java.lang.Override + public S2AServiceBlockingStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new S2AServiceBlockingStub(channel, callOptions); + } + }; + return S2AServiceBlockingStub.newStub(factory, channel); + } + + /** + * Creates a new ListenableFuture-style stub that supports unary calls on the service + */ + public static S2AServiceFutureStub newFutureStub( + io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @java.lang.Override + public S2AServiceFutureStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new S2AServiceFutureStub(channel, callOptions); + } + }; + return S2AServiceFutureStub.newStub(factory, channel); + } + + /** + */ + public interface AsyncService { + + /** + *

+     * SetUpSession is a bidirectional stream used by applications to offload
+     * operations from the TLS handshake.
+     * 
+ */ + default io.grpc.stub.StreamObserver setUpSession( + io.grpc.stub.StreamObserver responseObserver) { + return io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall(getSetUpSessionMethod(), responseObserver); + } + } + + /** + * Base class for the server implementation of the service S2AService. + */ + public static abstract class S2AServiceImplBase + implements io.grpc.BindableService, AsyncService { + + @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() { + return S2AServiceGrpc.bindService(this); + } + } + + /** + * A stub to allow clients to do asynchronous rpc calls to service S2AService. + */ + public static final class S2AServiceStub + extends io.grpc.stub.AbstractAsyncStub { + private S2AServiceStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @java.lang.Override + protected S2AServiceStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new S2AServiceStub(channel, callOptions); + } + + /** + *
+     * SetUpSession is a bidirectional stream used by applications to offload
+     * operations from the TLS handshake.
+     * 
+ */ + public io.grpc.stub.StreamObserver setUpSession( + io.grpc.stub.StreamObserver responseObserver) { + return io.grpc.stub.ClientCalls.asyncBidiStreamingCall( + getChannel().newCall(getSetUpSessionMethod(), getCallOptions()), responseObserver); + } + } + + /** + * A stub to allow clients to do synchronous rpc calls to service S2AService. + */ + public static final class S2AServiceBlockingStub + extends io.grpc.stub.AbstractBlockingStub { + private S2AServiceBlockingStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @java.lang.Override + protected S2AServiceBlockingStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new S2AServiceBlockingStub(channel, callOptions); + } + } + + /** + * A stub to allow clients to do ListenableFuture-style rpc calls to service S2AService. + */ + public static final class S2AServiceFutureStub + extends io.grpc.stub.AbstractFutureStub { + private S2AServiceFutureStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @java.lang.Override + protected S2AServiceFutureStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new S2AServiceFutureStub(channel, callOptions); + } + } + + private static final int METHODID_SET_UP_SESSION = 0; + + private static final class MethodHandlers implements + io.grpc.stub.ServerCalls.UnaryMethod, + io.grpc.stub.ServerCalls.ServerStreamingMethod, + io.grpc.stub.ServerCalls.ClientStreamingMethod, + io.grpc.stub.ServerCalls.BidiStreamingMethod { + private final AsyncService serviceImpl; + private final int methodId; + + MethodHandlers(AsyncService serviceImpl, int methodId) { + this.serviceImpl = serviceImpl; + this.methodId = methodId; + } + + @java.lang.Override + @java.lang.SuppressWarnings("unchecked") + public void invoke(Req request, io.grpc.stub.StreamObserver responseObserver) { + switch (methodId) { + default: + throw new AssertionError(); + } + } + + @java.lang.Override + @java.lang.SuppressWarnings("unchecked") + public io.grpc.stub.StreamObserver invoke( + io.grpc.stub.StreamObserver responseObserver) { + switch (methodId) { + case METHODID_SET_UP_SESSION: + return (io.grpc.stub.StreamObserver) serviceImpl.setUpSession( + (io.grpc.stub.StreamObserver) responseObserver); + default: + throw new AssertionError(); + } + } + } + + public static final io.grpc.ServerServiceDefinition bindService(AsyncService service) { + return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor()) + .addMethod( + getSetUpSessionMethod(), + io.grpc.stub.ServerCalls.asyncBidiStreamingCall( + new MethodHandlers< + io.grpc.s2a.handshaker.SessionReq, + io.grpc.s2a.handshaker.SessionResp>( + service, METHODID_SET_UP_SESSION))) + .build(); + } + + private static abstract class S2AServiceBaseDescriptorSupplier + implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier { + S2AServiceBaseDescriptorSupplier() {} + + @java.lang.Override + public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() { + return io.grpc.s2a.handshaker.S2AProto.getDescriptor(); + } + + @java.lang.Override + public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() { + return getFileDescriptor().findServiceByName("S2AService"); + } + } + + private static final class S2AServiceFileDescriptorSupplier + extends S2AServiceBaseDescriptorSupplier { + S2AServiceFileDescriptorSupplier() {} + } + + private static final class S2AServiceMethodDescriptorSupplier + extends S2AServiceBaseDescriptorSupplier + implements io.grpc.protobuf.ProtoMethodDescriptorSupplier { + private final java.lang.String methodName; + + S2AServiceMethodDescriptorSupplier(java.lang.String methodName) { + this.methodName = methodName; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() { + return getServiceDescriptor().findMethodByName(methodName); + } + } + + private static volatile io.grpc.ServiceDescriptor serviceDescriptor; + + public static io.grpc.ServiceDescriptor getServiceDescriptor() { + io.grpc.ServiceDescriptor result = serviceDescriptor; + if (result == null) { + synchronized (S2AServiceGrpc.class) { + result = serviceDescriptor; + if (result == null) { + serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME) + .setSchemaDescriptor(new S2AServiceFileDescriptorSupplier()) + .addMethod(getSetUpSessionMethod()) + .build(); + } + } + } + return result; + } +} diff --git a/s2a/src/main/java/io/grpc/s2a/MtlsToS2AChannelCredentials.java b/s2a/src/main/java/io/grpc/s2a/MtlsToS2AChannelCredentials.java new file mode 100644 index 00000000000..56f612502bf --- /dev/null +++ b/s2a/src/main/java/io/grpc/s2a/MtlsToS2AChannelCredentials.java @@ -0,0 +1,96 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Strings.isNullOrEmpty; + +import io.grpc.ChannelCredentials; +import io.grpc.TlsChannelCredentials; +import io.grpc.util.AdvancedTlsX509KeyManager; +import io.grpc.util.AdvancedTlsX509TrustManager; +import java.io.File; +import java.io.IOException; +import java.security.GeneralSecurityException; + +/** + * Configures an {@code S2AChannelCredentials.Builder} instance with credentials used to establish a + * connection with the S2A to support talking to the S2A over mTLS. + */ +public final class MtlsToS2AChannelCredentials { + /** + * Creates a {@code S2AChannelCredentials.Builder} builder, that talks to the S2A over mTLS. + * + * @param s2aAddress the address of the S2A server used to secure the connection. + * @param privateKeyPath the path to the private key PEM to use for authenticating to the S2A. + * @param certChainPath the path to the cert chain PEM to use for authenticating to the S2A. + * @param trustBundlePath the path to the trust bundle PEM. + * @return a {@code MtlsToS2AChannelCredentials.Builder} instance. + */ + public static Builder createBuilder( + String s2aAddress, String privateKeyPath, String certChainPath, String trustBundlePath) { + checkArgument(!isNullOrEmpty(s2aAddress), "S2A address must not be null or empty."); + checkArgument(!isNullOrEmpty(privateKeyPath), "privateKeyPath must not be null or empty."); + checkArgument(!isNullOrEmpty(certChainPath), "certChainPath must not be null or empty."); + checkArgument(!isNullOrEmpty(trustBundlePath), "trustBundlePath must not be null or empty."); + return new Builder(s2aAddress, privateKeyPath, certChainPath, trustBundlePath); + } + + /** Builds an {@code MtlsToS2AChannelCredentials} instance. */ + public static final class Builder { + private final String s2aAddress; + private final String privateKeyPath; + private final String certChainPath; + private final String trustBundlePath; + + Builder( + String s2aAddress, String privateKeyPath, String certChainPath, String trustBundlePath) { + this.s2aAddress = s2aAddress; + this.privateKeyPath = privateKeyPath; + this.certChainPath = certChainPath; + this.trustBundlePath = trustBundlePath; + } + + public S2AChannelCredentials.Builder build() throws GeneralSecurityException, IOException { + checkState(!isNullOrEmpty(s2aAddress), "S2A address must not be null or empty."); + checkState(!isNullOrEmpty(privateKeyPath), "privateKeyPath must not be null or empty."); + checkState(!isNullOrEmpty(certChainPath), "certChainPath must not be null or empty."); + checkState(!isNullOrEmpty(trustBundlePath), "trustBundlePath must not be null or empty."); + File privateKeyFile = new File(privateKeyPath); + File certChainFile = new File(certChainPath); + File trustBundleFile = new File(trustBundlePath); + + AdvancedTlsX509KeyManager keyManager = new AdvancedTlsX509KeyManager(); + keyManager.updateIdentityCredentials(certChainFile, privateKeyFile); + + AdvancedTlsX509TrustManager trustManager = AdvancedTlsX509TrustManager.newBuilder().build(); + trustManager.updateTrustCredentials(trustBundleFile); + + ChannelCredentials channelToS2ACredentials = + TlsChannelCredentials.newBuilder() + .keyManager(keyManager) + .trustManager(trustManager) + .build(); + + return S2AChannelCredentials.createBuilder(s2aAddress) + .setS2AChannelCredentials(channelToS2ACredentials); + } + } + + private MtlsToS2AChannelCredentials() {} +} \ No newline at end of file diff --git a/s2a/src/main/java/io/grpc/s2a/S2AChannelCredentials.java b/s2a/src/main/java/io/grpc/s2a/S2AChannelCredentials.java new file mode 100644 index 00000000000..8a5f1f51350 --- /dev/null +++ b/s2a/src/main/java/io/grpc/s2a/S2AChannelCredentials.java @@ -0,0 +1,130 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Strings.isNullOrEmpty; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import io.grpc.Channel; +import io.grpc.ChannelCredentials; +import io.grpc.internal.ObjectPool; +import io.grpc.internal.SharedResourcePool; +import io.grpc.netty.InternalNettyChannelCredentials; +import io.grpc.netty.InternalProtocolNegotiator; +import io.grpc.s2a.channel.S2AHandshakerServiceChannel; +import io.grpc.s2a.handshaker.S2AIdentity; +import io.grpc.s2a.handshaker.S2AProtocolNegotiatorFactory; +import java.util.Optional; +import javax.annotation.concurrent.NotThreadSafe; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Configures gRPC to use S2A for transport security when establishing a secure channel. Only for + * use on the client side of a gRPC connection. + */ +public final class S2AChannelCredentials { + /** + * Creates a channel credentials builder for establishing an S2A-secured connection. + * + * @param s2aAddress the address of the S2A server used to secure the connection. + * @return a {@code S2AChannelCredentials.Builder} instance. + */ + public static Builder createBuilder(String s2aAddress) { + checkArgument(!isNullOrEmpty(s2aAddress), "S2A address must not be null or empty."); + return new Builder(s2aAddress); + } + + /** Builds an {@code S2AChannelCredentials} instance. */ + @NotThreadSafe + public static final class Builder { + private final String s2aAddress; + private ObjectPool s2aChannelPool; + private Optional s2aChannelCredentials; + private @Nullable S2AIdentity localIdentity = null; + + Builder(String s2aAddress) { + this.s2aAddress = s2aAddress; + this.s2aChannelPool = null; + this.s2aChannelCredentials = Optional.empty(); + } + + /** + * Sets the local identity of the client in the form of a SPIFFE ID. The client may set at most + * 1 local identity. If no local identity is specified, then the S2A chooses a default local + * identity, if one exists. + */ + @CanIgnoreReturnValue + public Builder setLocalSpiffeId(String localSpiffeId) { + checkNotNull(localSpiffeId); + checkArgument(localIdentity == null, "localIdentity is already set."); + localIdentity = S2AIdentity.fromSpiffeId(localSpiffeId); + return this; + } + + /** + * Sets the local identity of the client in the form of a hostname. The client may set at most 1 + * local identity. If no local identity is specified, then the S2A chooses a default local + * identity, if one exists. + */ + @CanIgnoreReturnValue + public Builder setLocalHostname(String localHostname) { + checkNotNull(localHostname); + checkArgument(localIdentity == null, "localIdentity is already set."); + localIdentity = S2AIdentity.fromHostname(localHostname); + return this; + } + + /** + * Sets the local identity of the client in the form of a UID. The client may set at most 1 + * local identity. If no local identity is specified, then the S2A chooses a default local + * identity, if one exists. + */ + @CanIgnoreReturnValue + public Builder setLocalUid(String localUid) { + checkNotNull(localUid); + checkArgument(localIdentity == null, "localIdentity is already set."); + localIdentity = S2AIdentity.fromUid(localUid); + return this; + } + + /** Sets the credentials to be used when connecting to the S2A. */ + @CanIgnoreReturnValue + public Builder setS2AChannelCredentials(ChannelCredentials s2aChannelCredentials) { + this.s2aChannelCredentials = Optional.of(s2aChannelCredentials); + return this; + } + + public ChannelCredentials build() { + checkState(!isNullOrEmpty(s2aAddress), "S2A address must not be null or empty."); + ObjectPool s2aChannelPool = + SharedResourcePool.forResource( + S2AHandshakerServiceChannel.getChannelResource(s2aAddress, s2aChannelCredentials)); + checkNotNull(s2aChannelPool, "s2aChannelPool"); + this.s2aChannelPool = s2aChannelPool; + return InternalNettyChannelCredentials.create(buildProtocolNegotiatorFactory()); + } + + InternalProtocolNegotiator.ClientFactory buildProtocolNegotiatorFactory() { + return S2AProtocolNegotiatorFactory.createClientFactory(localIdentity, s2aChannelPool); + } + } + + private S2AChannelCredentials() {} +} \ No newline at end of file diff --git a/s2a/src/main/java/io/grpc/s2a/channel/S2AChannelPool.java b/s2a/src/main/java/io/grpc/s2a/channel/S2AChannelPool.java new file mode 100644 index 00000000000..e5caf5e69bd --- /dev/null +++ b/s2a/src/main/java/io/grpc/s2a/channel/S2AChannelPool.java @@ -0,0 +1,43 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.channel; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import io.grpc.Channel; +import javax.annotation.concurrent.ThreadSafe; + +/** Manages a channel pool to be used for communication with the S2A. */ +@ThreadSafe +public interface S2AChannelPool extends AutoCloseable { + /** + * Retrieves an open channel to the S2A from the channel pool. + * + * @throws IllegalStateException if no channel is available. + */ + @CanIgnoreReturnValue + Channel getChannel(); + + /** Returns a channel to the channel pool. */ + void returnToPool(Channel channel); + + /** + * Returns all channels to the channel pool and closes the pool so that no new channels can be + * retrieved from the pool. + */ + @Override + void close(); +} \ No newline at end of file diff --git a/s2a/src/main/java/io/grpc/s2a/channel/S2AGrpcChannelPool.java b/s2a/src/main/java/io/grpc/s2a/channel/S2AGrpcChannelPool.java new file mode 100644 index 00000000000..4794cd9ee49 --- /dev/null +++ b/s2a/src/main/java/io/grpc/s2a/channel/S2AGrpcChannelPool.java @@ -0,0 +1,109 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.channel; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.errorprone.annotations.concurrent.GuardedBy; +import io.grpc.Channel; +import io.grpc.internal.ObjectPool; +import javax.annotation.concurrent.ThreadSafe; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Manages a gRPC channel pool and a cached gRPC channel to be used for communication with the S2A. + */ +@ThreadSafe +public final class S2AGrpcChannelPool implements S2AChannelPool { + private static final int MAX_NUMBER_USERS_OF_CACHED_CHANNEL = 100000; + private final ObjectPool channelPool; + + @GuardedBy("this") + private @Nullable Channel cachedChannel; + + @GuardedBy("this") + private int numberOfUsersOfCachedChannel = 0; + + private enum State { + OPEN, + CLOSED, + } + + ; + + @GuardedBy("this") + private State state = State.OPEN; + + public static S2AChannelPool create(ObjectPool channelPool) { + checkNotNull(channelPool, "Channel pool should not be null."); + return new S2AGrpcChannelPool(channelPool); + } + + private S2AGrpcChannelPool(ObjectPool channelPool) { + this.channelPool = channelPool; + } + + /** + * Retrieves a channel from {@code channelPool} if {@code channel} is null, and returns {@code + * channel} otherwise. + * + * @return a {@link Channel} obtained from the channel pool. + */ + @Override + public synchronized Channel getChannel() { + checkState(state.equals(State.OPEN), "Channel pool is not open."); + checkState( + numberOfUsersOfCachedChannel < MAX_NUMBER_USERS_OF_CACHED_CHANNEL, + "Max number of channels have been retrieved from the channel pool."); + if (cachedChannel == null) { + cachedChannel = channelPool.getObject(); + } + numberOfUsersOfCachedChannel += 1; + return cachedChannel; + } + + /** + * Returns {@code channel} to {@code channelPool}. + * + *

The caller must ensure that {@code channel} was retrieved from this channel pool. + */ + @Override + public synchronized void returnToPool(Channel channel) { + checkState(state.equals(State.OPEN), "Channel pool is not open."); + checkArgument( + cachedChannel != null && numberOfUsersOfCachedChannel > 0 && cachedChannel.equals(channel), + "Cannot return the channel to channel pool because the channel was not obtained from" + + " channel pool."); + numberOfUsersOfCachedChannel -= 1; + if (numberOfUsersOfCachedChannel == 0) { + channelPool.returnObject(channel); + cachedChannel = null; + } + } + + @Override + public synchronized void close() { + state = State.CLOSED; + numberOfUsersOfCachedChannel = 0; + if (cachedChannel != null) { + channelPool.returnObject(cachedChannel); + cachedChannel = null; + } + } +} \ No newline at end of file diff --git a/s2a/src/main/java/io/grpc/s2a/channel/S2AHandshakerServiceChannel.java b/s2a/src/main/java/io/grpc/s2a/channel/S2AHandshakerServiceChannel.java new file mode 100644 index 00000000000..75ec7347bb5 --- /dev/null +++ b/s2a/src/main/java/io/grpc/s2a/channel/S2AHandshakerServiceChannel.java @@ -0,0 +1,195 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.channel; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.concurrent.TimeUnit.SECONDS; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Maps; +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ChannelCredentials; +import io.grpc.ClientCall; +import io.grpc.ManagedChannel; +import io.grpc.MethodDescriptor; +import io.grpc.internal.SharedResourceHolder.Resource; +import io.grpc.netty.NettyChannelBuilder; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.util.concurrent.DefaultThreadFactory; +import java.time.Duration; +import java.util.Optional; +import java.util.concurrent.ConcurrentMap; +import javax.annotation.concurrent.ThreadSafe; + +/** + * Provides APIs for managing gRPC channels to S2A servers. Each channel is local and plaintext. If + * credentials are provided, they are used to secure the channel. + * + *

This is done as follows: for each S2A server, provides an implementation of gRPC's {@link + * SharedResourceHolder.Resource} interface called a {@code Resource}. A {@code + * Resource} is a factory for creating gRPC channels to the S2A server at a given address, + * and a channel must be returned to the {@code Resource} when it is no longer needed. + * + *

Typical usage pattern is below: + * + *

{@code
+ * Resource resource = S2AHandshakerServiceChannel.getChannelResource("localhost:1234",
+ * creds);
+ * Channel channel = resource.create();
+ * // Send an RPC over the channel to the S2A server running at localhost:1234.
+ * resource.close(channel);
+ * }
+ */ +@ThreadSafe +public final class S2AHandshakerServiceChannel { + private static final ConcurrentMap> SHARED_RESOURCE_CHANNELS = + Maps.newConcurrentMap(); + private static final Duration DELEGATE_TERMINATION_TIMEOUT = Duration.ofSeconds(2); + private static final Duration CHANNEL_SHUTDOWN_TIMEOUT = Duration.ofSeconds(10); + + /** + * Returns a {@link SharedResourceHolder.Resource} instance for managing channels to an S2A server + * running at {@code s2aAddress}. + * + * @param s2aAddress the address of the S2A, typically in the format {@code host:port}. + * @param s2aChannelCredentials the credentials to use when establishing a connection to the S2A. + * @return a {@link ChannelResource} instance that manages a {@link Channel} to the S2A server + * running at {@code s2aAddress}. + */ + public static Resource getChannelResource( + String s2aAddress, Optional s2aChannelCredentials) { + checkNotNull(s2aAddress); + return SHARED_RESOURCE_CHANNELS.computeIfAbsent( + s2aAddress, channelResource -> new ChannelResource(s2aAddress, s2aChannelCredentials)); + } + + /** + * Defines how to create and destroy a {@link Channel} instance that uses shared resources. A + * channel created by {@code ChannelResource} is a plaintext, local channel to the service running + * at {@code targetAddress}. + */ + private static class ChannelResource implements Resource { + private final String targetAddress; + private final Optional channelCredentials; + + public ChannelResource(String targetAddress, Optional channelCredentials) { + this.targetAddress = targetAddress; + this.channelCredentials = channelCredentials; + } + + /** + * Creates a {@code EventLoopHoldingChannel} instance to the service running at {@code + * targetAddress}. This channel uses a dedicated thread pool for its {@code EventLoopGroup} + * instance to avoid blocking. + */ + @Override + public Channel create() { + EventLoopGroup eventLoopGroup = + new NioEventLoopGroup(1, new DefaultThreadFactory("S2A channel pool", true)); + ManagedChannel channel = null; + if (channelCredentials.isPresent()) { + // Create a secure channel. + channel = + NettyChannelBuilder.forTarget(targetAddress, channelCredentials.get()) + .channelType(NioSocketChannel.class) + .directExecutor() + .eventLoopGroup(eventLoopGroup) + .build(); + } else { + // Create a plaintext channel. + channel = + NettyChannelBuilder.forTarget(targetAddress) + .channelType(NioSocketChannel.class) + .directExecutor() + .eventLoopGroup(eventLoopGroup) + .usePlaintext() + .build(); + } + return EventLoopHoldingChannel.create(channel, eventLoopGroup); + } + + /** Destroys a {@code EventLoopHoldingChannel} instance. */ + @Override + public void close(Channel instanceChannel) { + checkNotNull(instanceChannel); + EventLoopHoldingChannel channel = (EventLoopHoldingChannel) instanceChannel; + channel.close(); + } + + @Override + public String toString() { + return "grpc-s2a-channel"; + } + } + + /** + * Manages a channel using a {@link ManagedChannel} instance that belong to the {@code + * EventLoopGroup} thread pool. + */ + @VisibleForTesting + static class EventLoopHoldingChannel extends Channel { + private final ManagedChannel delegate; + private final EventLoopGroup eventLoopGroup; + + static EventLoopHoldingChannel create(ManagedChannel delegate, EventLoopGroup eventLoopGroup) { + checkNotNull(delegate); + checkNotNull(eventLoopGroup); + return new EventLoopHoldingChannel(delegate, eventLoopGroup); + } + + private EventLoopHoldingChannel(ManagedChannel delegate, EventLoopGroup eventLoopGroup) { + this.delegate = delegate; + this.eventLoopGroup = eventLoopGroup; + } + + /** + * Returns the address of the service to which the {@code delegate} channel connects, which is + * typically of the form {@code host:port}. + */ + @Override + public String authority() { + return delegate.authority(); + } + + /** Creates a {@link ClientCall} that invokes the operations in {@link MethodDescriptor}. */ + @Override + public ClientCall newCall( + MethodDescriptor methodDescriptor, CallOptions options) { + return delegate.newCall(methodDescriptor, options); + } + + @SuppressWarnings("FutureReturnValueIgnored") + public void close() { + delegate.shutdownNow(); + boolean isDelegateTerminated; + try { + isDelegateTerminated = + delegate.awaitTermination(DELEGATE_TERMINATION_TIMEOUT.getSeconds(), SECONDS); + } catch (InterruptedException e) { + isDelegateTerminated = false; + } + long quietPeriodSeconds = isDelegateTerminated ? 0 : 1; + eventLoopGroup.shutdownGracefully( + quietPeriodSeconds, CHANNEL_SHUTDOWN_TIMEOUT.getSeconds(), SECONDS); + } + } + + private S2AHandshakerServiceChannel() {} +} \ No newline at end of file diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/ConnectionClosedException.java b/s2a/src/main/java/io/grpc/s2a/handshaker/ConnectionClosedException.java new file mode 100644 index 00000000000..1a7f86bda91 --- /dev/null +++ b/s2a/src/main/java/io/grpc/s2a/handshaker/ConnectionClosedException.java @@ -0,0 +1,27 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.handshaker; + +import java.io.IOException; + +/** Indicates that a connection has been closed. */ +@SuppressWarnings("serial") // This class is never serialized. +final class ConnectionClosedException extends IOException { + public ConnectionClosedException(String errorMessage) { + super(errorMessage); + } +} \ No newline at end of file diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/GetAuthenticationMechanisms.java b/s2a/src/main/java/io/grpc/s2a/handshaker/GetAuthenticationMechanisms.java new file mode 100644 index 00000000000..56d74a9b766 --- /dev/null +++ b/s2a/src/main/java/io/grpc/s2a/handshaker/GetAuthenticationMechanisms.java @@ -0,0 +1,56 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.handshaker; + +import com.google.errorprone.annotations.Immutable; +import io.grpc.s2a.handshaker.S2AIdentity; +import io.grpc.s2a.handshaker.tokenmanager.AccessTokenManager; +import java.util.Optional; + +/** Retrieves the authentication mechanism for a given local identity. */ +@Immutable +final class GetAuthenticationMechanisms { + private static final Optional TOKEN_MANAGER = AccessTokenManager.create(); + + /** + * Retrieves the authentication mechanism for a given local identity. + * + * @param localIdentity the identity for which to fetch a token. + * @return an {@link AuthenticationMechanism} for the given local identity. + */ + static Optional getAuthMechanism(Optional localIdentity) { + if (!TOKEN_MANAGER.isPresent()) { + return Optional.empty(); + } + AccessTokenManager manager = TOKEN_MANAGER.get(); + // If no identity is provided, fetch the default access token and DO NOT attach an identity + // to the request. + if (!localIdentity.isPresent()) { + return Optional.of( + AuthenticationMechanism.newBuilder().setToken(manager.getDefaultToken()).build()); + } else { + // Fetch an access token for the provided identity. + return Optional.of( + AuthenticationMechanism.newBuilder() + .setIdentity(localIdentity.get().getIdentity()) + .setToken(manager.getToken(localIdentity.get())) + .build()); + } + } + + private GetAuthenticationMechanisms() {} +} \ No newline at end of file diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/ProtoUtil.java b/s2a/src/main/java/io/grpc/s2a/handshaker/ProtoUtil.java new file mode 100644 index 00000000000..59e3931d9e6 --- /dev/null +++ b/s2a/src/main/java/io/grpc/s2a/handshaker/ProtoUtil.java @@ -0,0 +1,96 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.handshaker; + +import com.google.common.collect.ImmutableSet; + +/** Converts proto messages to Netty strings. */ +final class ProtoUtil { + /** + * Converts {@link Ciphersuite} to its {@link String} representation. + * + * @param ciphersuite the {@link Ciphersuite} to be converted. + * @return a {@link String} representing the ciphersuite. + * @throws AssertionError if the {@link Ciphersuite} is not one of the supported ciphersuites. + */ + static String convertCiphersuite(Ciphersuite ciphersuite) { + switch (ciphersuite) { + case CIPHERSUITE_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + return "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"; + case CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + return "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"; + case CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: + return "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"; + case CIPHERSUITE_ECDHE_RSA_WITH_AES_128_GCM_SHA256: + return "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"; + case CIPHERSUITE_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + return "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"; + case CIPHERSUITE_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: + return "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"; + default: + throw new AssertionError( + String.format("Ciphersuite %d is not supported.", ciphersuite.getNumber())); + } + } + + /** + * Converts a {@link TLSVersion} object to its {@link String} representation. + * + * @param tlsVersion the {@link TLSVersion} object to be converted. + * @return a {@link String} representation of the TLS version. + * @throws AssertionError if the {@code tlsVersion} is not one of the supported TLS versions. + */ + static String convertTlsProtocolVersion(TLSVersion tlsVersion) { + switch (tlsVersion) { + case TLS_VERSION_1_3: + return "TLSv1.3"; + case TLS_VERSION_1_2: + return "TLSv1.2"; + case TLS_VERSION_1_1: + return "TLSv1.1"; + case TLS_VERSION_1_0: + return "TLSv1"; + default: + throw new AssertionError( + String.format("TLS version %d is not supported.", tlsVersion.getNumber())); + } + } + + /** + * Builds a set of strings representing all {@link TLSVersion}s between {@code minTlsVersion} and + * {@code maxTlsVersion}. + */ + static ImmutableSet buildTlsProtocolVersionSet( + TLSVersion minTlsVersion, TLSVersion maxTlsVersion) { + ImmutableSet.Builder tlsVersions = ImmutableSet.builder(); + for (TLSVersion tlsVersion : TLSVersion.values()) { + int versionNumber; + try { + versionNumber = tlsVersion.getNumber(); + } catch (IllegalArgumentException e) { + continue; + } + if (versionNumber >= minTlsVersion.getNumber() + && versionNumber <= maxTlsVersion.getNumber()) { + tlsVersions.add(convertTlsProtocolVersion(tlsVersion)); + } + } + return tlsVersions.build(); + } + + private ProtoUtil() {} +} \ No newline at end of file diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/S2AConnectionException.java b/s2a/src/main/java/io/grpc/s2a/handshaker/S2AConnectionException.java new file mode 100644 index 00000000000..d976308ad22 --- /dev/null +++ b/s2a/src/main/java/io/grpc/s2a/handshaker/S2AConnectionException.java @@ -0,0 +1,25 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.handshaker; + +/** Exception that denotes a runtime error that was encountered when talking to the S2A server. */ +@SuppressWarnings("serial") // This class is never serialized. +public class S2AConnectionException extends RuntimeException { + S2AConnectionException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/S2AIdentity.java b/s2a/src/main/java/io/grpc/s2a/handshaker/S2AIdentity.java new file mode 100644 index 00000000000..c4fed7377ac --- /dev/null +++ b/s2a/src/main/java/io/grpc/s2a/handshaker/S2AIdentity.java @@ -0,0 +1,62 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.handshaker; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.errorprone.annotations.ThreadSafe; + +/** + * Stores an identity in such a way that it can be sent to the S2A handshaker service. The identity + * may be formatted as a SPIFFE ID or as a hostname. + */ +@ThreadSafe +public final class S2AIdentity { + private final Identity identity; + + /** Returns an {@link S2AIdentity} instance with SPIFFE ID set to {@code spiffeId}. */ + public static S2AIdentity fromSpiffeId(String spiffeId) { + checkNotNull(spiffeId); + return new S2AIdentity(Identity.newBuilder().setSpiffeId(spiffeId).build()); + } + + /** Returns an {@link S2AIdentity} instance with hostname set to {@code hostname}. */ + public static S2AIdentity fromHostname(String hostname) { + checkNotNull(hostname); + return new S2AIdentity(Identity.newBuilder().setHostname(hostname).build()); + } + + /** Returns an {@link S2AIdentity} instance with UID set to {@code uid}. */ + public static S2AIdentity fromUid(String uid) { + checkNotNull(uid); + return new S2AIdentity(Identity.newBuilder().setUid(uid).build()); + } + + /** Returns an {@link S2AIdentity} instance with {@code identity} set. */ + public static S2AIdentity fromIdentity(Identity identity) { + return new S2AIdentity(identity == null ? Identity.getDefaultInstance() : identity); + } + + private S2AIdentity(Identity identity) { + this.identity = identity; + } + + /** Returns the proto {@link Identity} representation of this identity instance. */ + public Identity getIdentity() { + return identity; + } +} \ No newline at end of file diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/S2APrivateKeyMethod.java b/s2a/src/main/java/io/grpc/s2a/handshaker/S2APrivateKeyMethod.java new file mode 100644 index 00000000000..fb6d5761355 --- /dev/null +++ b/s2a/src/main/java/io/grpc/s2a/handshaker/S2APrivateKeyMethod.java @@ -0,0 +1,143 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.handshaker; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableMap; +import com.google.protobuf.ByteString; +import io.grpc.s2a.handshaker.S2AIdentity; +import io.netty.handler.ssl.OpenSslPrivateKeyMethod; +import java.io.IOException; +import java.util.Optional; +import javax.annotation.concurrent.NotThreadSafe; +import javax.net.ssl.SSLEngine; + +/** + * Handles requests on signing bytes with a private key designated by {@code stub}. + * + *

This is done by sending the to-be-signed bytes to an S2A server (designated by {@code stub}) + * and read the signature from the server. + * + *

OpenSSL libraries must be appropriately initialized before using this class. One possible way + * to initialize OpenSSL library is to call {@code + * GrpcSslContexts.configure(SslContextBuilder.forClient());}. + */ +@NotThreadSafe +final class S2APrivateKeyMethod implements OpenSslPrivateKeyMethod { + private final S2AStub stub; + private final Optional localIdentity; + private static final ImmutableMap + OPENSSL_TO_S2A_SIGNATURE_ALGORITHM_MAP = + ImmutableMap.of( + OpenSslPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA256, + SignatureAlgorithm.S2A_SSL_SIGN_RSA_PKCS1_SHA256, + OpenSslPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA384, + SignatureAlgorithm.S2A_SSL_SIGN_RSA_PKCS1_SHA384, + OpenSslPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA512, + SignatureAlgorithm.S2A_SSL_SIGN_RSA_PKCS1_SHA512, + OpenSslPrivateKeyMethod.SSL_SIGN_ECDSA_SECP256R1_SHA256, + SignatureAlgorithm.S2A_SSL_SIGN_ECDSA_SECP256R1_SHA256, + OpenSslPrivateKeyMethod.SSL_SIGN_ECDSA_SECP384R1_SHA384, + SignatureAlgorithm.S2A_SSL_SIGN_ECDSA_SECP384R1_SHA384, + OpenSslPrivateKeyMethod.SSL_SIGN_ECDSA_SECP521R1_SHA512, + SignatureAlgorithm.S2A_SSL_SIGN_ECDSA_SECP521R1_SHA512, + OpenSslPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA256, + SignatureAlgorithm.S2A_SSL_SIGN_RSA_PSS_RSAE_SHA256, + OpenSslPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA384, + SignatureAlgorithm.S2A_SSL_SIGN_RSA_PSS_RSAE_SHA384, + OpenSslPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA512, + SignatureAlgorithm.S2A_SSL_SIGN_RSA_PSS_RSAE_SHA512); + + public static S2APrivateKeyMethod create(S2AStub stub, Optional localIdentity) { + checkNotNull(stub); + return new S2APrivateKeyMethod(stub, localIdentity); + } + + private S2APrivateKeyMethod(S2AStub stub, Optional localIdentity) { + this.stub = stub; + this.localIdentity = localIdentity; + } + + /** + * Converts the signature algorithm to an enum understood by S2A. + * + * @param signatureAlgorithm the int representation of the signature algorithm define by {@code + * OpenSslPrivateKeyMethod}. + * @return the signature algorithm enum defined by S2A proto. + * @throws UnsupportedOperationException if the algorithm is not supported by S2A. + */ + @VisibleForTesting + static SignatureAlgorithm convertOpenSslSignAlgToS2ASignAlg(int signatureAlgorithm) { + SignatureAlgorithm sig = OPENSSL_TO_S2A_SIGNATURE_ALGORITHM_MAP.get(signatureAlgorithm); + if (sig == null) { + throw new UnsupportedOperationException( + String.format("Signature Algorithm %d is not supported.", signatureAlgorithm)); + } + return sig; + } + + /** + * Signs the input bytes by sending the request to the S2A srever. + * + * @param engine not used. + * @param signatureAlgorithm the {@link OpenSslPrivateKeyMethod}'s signature algorithm + * representation + * @param input the bytes to be signed. + * @return the signature of the {@code input}. + * @throws IOException if the connection to the S2A server is corrupted. + * @throws InterruptedException if the connection to the S2A server is interrupted. + * @throws S2AConnectionException if the response from the S2A server does not contain valid data. + */ + @Override + public byte[] sign(SSLEngine engine, int signatureAlgorithm, byte[] input) + throws IOException, InterruptedException { + checkArgument(input.length > 0, "No bytes to sign."); + SignatureAlgorithm s2aSignatureAlgorithm = + convertOpenSslSignAlgToS2ASignAlg(signatureAlgorithm); + SessionReq.Builder reqBuilder = + SessionReq.newBuilder() + .setOffloadPrivateKeyOperationReq( + OffloadPrivateKeyOperationReq.newBuilder() + .setOperation(OffloadPrivateKeyOperationReq.PrivateKeyOperation.SIGN) + .setSignatureAlgorithm(s2aSignatureAlgorithm) + .setRawBytes(ByteString.copyFrom(input))); + if (localIdentity.isPresent()) { + reqBuilder.setLocalIdentity(localIdentity.get().getIdentity()); + } + + SessionResp resp = stub.send(reqBuilder.build()); + + if (resp.hasStatus() && resp.getStatus().getCode() != 0) { + throw new S2AConnectionException( + String.format( + "Error occurred in response from S2A, error code: %d, error message: \"%s\".", + resp.getStatus().getCode(), resp.getStatus().getDetails())); + } + if (!resp.hasOffloadPrivateKeyOperationResp()) { + throw new S2AConnectionException("No valid response received from S2A."); + } + return resp.getOffloadPrivateKeyOperationResp().getOutBytes().toByteArray(); + } + + @Override + public byte[] decrypt(SSLEngine engine, byte[] input) { + throw new UnsupportedOperationException("decrypt is not supported."); + } +} \ No newline at end of file diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactory.java b/s2a/src/main/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactory.java new file mode 100644 index 00000000000..25d1e325ea8 --- /dev/null +++ b/s2a/src/main/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactory.java @@ -0,0 +1,249 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.handshaker; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Strings.isNullOrEmpty; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.net.HostAndPort; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.errorprone.annotations.ThreadSafe; +import io.grpc.Channel; +import io.grpc.internal.ObjectPool; +import io.grpc.netty.GrpcHttp2ConnectionHandler; +import io.grpc.netty.InternalProtocolNegotiator; +import io.grpc.netty.InternalProtocolNegotiator.ProtocolNegotiator; +import io.grpc.netty.InternalProtocolNegotiators; +import io.grpc.netty.InternalProtocolNegotiators.ProtocolNegotiationHandler; +import io.grpc.s2a.channel.S2AChannelPool; +import io.grpc.s2a.channel.S2AGrpcChannelPool; +import io.grpc.s2a.handshaker.S2AIdentity; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerAdapter; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.ssl.SslContext; +import io.netty.util.AsciiString; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.Executors; +import javax.annotation.Nullable; + +/** Factory for performing negotiation of a secure channel using the S2A. */ +@ThreadSafe +public final class S2AProtocolNegotiatorFactory { + @VisibleForTesting static final int DEFAULT_PORT = 443; + private static final AsciiString SCHEME = AsciiString.of("https"); + + /** + * Creates a {@code S2AProtocolNegotiatorFactory} configured for a client to establish secure + * connections using the S2A. + * + * @param localIdentity the identity of the client; if none is provided, the S2A will use the + * client's default identity. + * @param s2aChannelPool a pool of shared channels that can be used to connect to the S2A. + * @return a factory for creating a client-side protocol negotiator. + */ + public static InternalProtocolNegotiator.ClientFactory createClientFactory( + @Nullable S2AIdentity localIdentity, ObjectPool s2aChannelPool) { + checkNotNull(s2aChannelPool, "S2A channel pool should not be null."); + S2AChannelPool channelPool = S2AGrpcChannelPool.create(s2aChannelPool); + return new S2AClientProtocolNegotiatorFactory(localIdentity, channelPool); + } + + static final class S2AClientProtocolNegotiatorFactory + implements InternalProtocolNegotiator.ClientFactory { + private final @Nullable S2AIdentity localIdentity; + private final S2AChannelPool channelPool; + + S2AClientProtocolNegotiatorFactory( + @Nullable S2AIdentity localIdentity, S2AChannelPool channelPool) { + this.localIdentity = localIdentity; + this.channelPool = channelPool; + } + + @Override + public ProtocolNegotiator newNegotiator() { + return S2AProtocolNegotiator.createForClient(channelPool, localIdentity); + } + + @Override + public int getDefaultPort() { + return DEFAULT_PORT; + } + } + + /** Negotiates the TLS handshake using S2A. */ + @VisibleForTesting + static final class S2AProtocolNegotiator implements ProtocolNegotiator { + + private final S2AChannelPool channelPool; + private final Optional localIdentity; + private final ListeningExecutorService service = + MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(1)); + + static S2AProtocolNegotiator createForClient( + S2AChannelPool channelPool, @Nullable S2AIdentity localIdentity) { + checkNotNull(channelPool, "Channel pool should not be null."); + if (localIdentity == null) { + return new S2AProtocolNegotiator(channelPool, Optional.empty()); + } else { + return new S2AProtocolNegotiator(channelPool, Optional.of(localIdentity)); + } + } + + @VisibleForTesting + static @Nullable String getHostNameFromAuthority(@Nullable String authority) { + if (authority == null) { + return null; + } + return HostAndPort.fromString(authority).getHost(); + } + + private S2AProtocolNegotiator(S2AChannelPool channelPool, Optional localIdentity) { + this.channelPool = channelPool; + this.localIdentity = localIdentity; + } + + @Override + public AsciiString scheme() { + return SCHEME; + } + + @Override + public ChannelHandler newHandler(GrpcHttp2ConnectionHandler grpcHandler) { + checkNotNull(grpcHandler, "grpcHandler should not be null."); + String hostname = getHostNameFromAuthority(grpcHandler.getAuthority()); + checkArgument(!isNullOrEmpty(hostname), "hostname should not be null or empty."); + return new S2AProtocolNegotiationHandler( + grpcHandler, channelPool, localIdentity, hostname, service); + } + + @Override + public void close() { + service.shutdown(); + channelPool.close(); + } + } + + @VisibleForTesting + static class BufferReadsHandler extends ChannelInboundHandlerAdapter { + private final List reads = new ArrayList<>(); + private boolean readComplete; + + public List getReads() { + return reads; + } + + @Override + public void channelRead(ChannelHandlerContext unused, Object msg) { + reads.add(msg); + } + + @Override + public void channelReadComplete(ChannelHandlerContext unused) { + readComplete = true; + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + for (Object msg : reads) { + super.channelRead(ctx, msg); + } + if (readComplete) { + super.channelReadComplete(ctx); + } + } + } + + private static final class S2AProtocolNegotiationHandler extends ProtocolNegotiationHandler { + private final S2AChannelPool channelPool; + private final Optional localIdentity; + private final String hostname; + private final GrpcHttp2ConnectionHandler grpcHandler; + private final ListeningExecutorService service; + + private S2AProtocolNegotiationHandler( + GrpcHttp2ConnectionHandler grpcHandler, + S2AChannelPool channelPool, + Optional localIdentity, + String hostname, + ListeningExecutorService service) { + super( + // superclass (InternalProtocolNegotiators.ProtocolNegotiationHandler) expects 'next' + // handler but we don't have a next handler _yet_. So we "disable" superclass's behavior + // here and then manually add 'next' when we call fireProtocolNegotiationEvent() + new ChannelHandlerAdapter() { + @Override + public void handlerAdded(ChannelHandlerContext ctx) { + ctx.pipeline().remove(this); + } + }, + grpcHandler.getNegotiationLogger()); + this.grpcHandler = grpcHandler; + this.channelPool = channelPool; + this.localIdentity = localIdentity; + this.hostname = hostname; + checkNotNull(service, "service should not be null."); + this.service = service; + } + + @Override + protected void handlerAdded0(ChannelHandlerContext ctx) { + // Buffer all reads until the TLS Handler is added. + BufferReadsHandler bufferReads = new BufferReadsHandler(); + ctx.pipeline().addBefore(ctx.name(), /* name= */ null, bufferReads); + + Channel ch = channelPool.getChannel(); + S2AServiceGrpc.S2AServiceStub stub = S2AServiceGrpc.newStub(ch); + S2AStub s2aStub = S2AStub.newInstance(stub); + + ListenableFuture sslContextFuture = + service.submit(() -> SslContextFactory.createForClient(s2aStub, hostname, localIdentity)); + Futures.addCallback( + sslContextFuture, + new FutureCallback() { + @Override + public void onSuccess(SslContext sslContext) { + ChannelHandler handler = + InternalProtocolNegotiators.tls(sslContext).newHandler(grpcHandler); + + // Remove the bufferReads handler and delegate the rest of the handshake to the TLS + // handler. + ctx.pipeline().addAfter(ctx.name(), /* name= */ null, handler); + fireProtocolNegotiationEvent(ctx); + ctx.pipeline().remove(bufferReads); + } + + @Override + public void onFailure(Throwable t) { + ctx.fireExceptionCaught(t); + } + }, + service); + } + } + + private S2AProtocolNegotiatorFactory() {} +} \ No newline at end of file diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/S2AStub.java b/s2a/src/main/java/io/grpc/s2a/handshaker/S2AStub.java new file mode 100644 index 00000000000..8249ca59d09 --- /dev/null +++ b/s2a/src/main/java/io/grpc/s2a/handshaker/S2AStub.java @@ -0,0 +1,221 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.handshaker; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Verify.verify; +import static java.util.concurrent.TimeUnit.SECONDS; + +import com.google.common.annotations.VisibleForTesting; +import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.util.Optional; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.concurrent.NotThreadSafe; + +/** Reads and writes messages to and from the S2A. */ +@NotThreadSafe +class S2AStub implements AutoCloseable { + private static final Logger logger = Logger.getLogger(S2AStub.class.getName()); + private static final long HANDSHAKE_RPC_DEADLINE_SECS = 20; + private final StreamObserver reader = new Reader(); + private final BlockingQueue responses = new ArrayBlockingQueue<>(10); + private S2AServiceGrpc.S2AServiceStub serviceStub; + private StreamObserver writer; + private boolean doneReading = false; + private boolean doneWriting = false; + + static S2AStub newInstance(S2AServiceGrpc.S2AServiceStub serviceStub) { + checkNotNull(serviceStub); + return new S2AStub(serviceStub); + } + + @VisibleForTesting + static S2AStub newInstanceForTesting(StreamObserver writer) { + checkNotNull(writer); + return new S2AStub(writer); + } + + private S2AStub(S2AServiceGrpc.S2AServiceStub serviceStub) { + this.serviceStub = serviceStub; + } + + private S2AStub(StreamObserver writer) { + this.writer = writer; + } + + @VisibleForTesting + StreamObserver getReader() { + return reader; + } + + @VisibleForTesting + BlockingQueue getResponses() { + return responses; + } + + /** + * Sends a request and returns the response. Caller must wait until this method executes prior to + * calling it again. If this method throws {@code ConnectionClosedException}, then it should not + * be called again, and both {@code reader} and {@code writer} are closed. + * + * @param req the {@code SessionReq} message to be sent to the S2A server. + * @return the {@code SessionResp} message received from the S2A server. + * @throws ConnectionClosedException if {@code reader} or {@code writer} calls their {@code + * onCompleted} method. + * @throws IOException if an unexpected response is received, or if the {@code reader} or {@code + * writer} calls their {@code onError} method. + */ + public SessionResp send(SessionReq req) throws IOException, InterruptedException { + if (doneWriting && doneReading) { + logger.log(Level.INFO, "Stream to the S2A is closed."); + throw new ConnectionClosedException("Stream to the S2A is closed."); + } + createWriterIfNull(); + if (!responses.isEmpty()) { + IOException exception = null; + SessionResp resp = null; + try { + resp = responses.take().getResultOrThrow(); + } catch (IOException e) { + exception = e; + } + responses.clear(); + if (exception != null) { + throw new IOException( + "Received an unexpected response from a host at the S2A's address. The S2A might be" + + " unavailable." + + exception.getMessage()); + } + return resp; + } + try { + writer.onNext(req); + } catch (RuntimeException e) { + writer.onError(e); + responses.offer(Result.createWithThrowable(e)); + } + try { + return responses.take().getResultOrThrow(); + } catch (ConnectionClosedException e) { + // A ConnectionClosedException is thrown by getResultOrThrow when reader calls its + // onCompleted method. The close method is called to also close the writer, and then the + // ConnectionClosedException is re-thrown in order to indicate to the caller that send + // should not be called again. + close(); + throw e; + } + } + + @Override + public void close() { + if (doneWriting && doneReading) { + return; + } + verify(!doneWriting); + doneReading = true; + doneWriting = true; + if (writer != null) { + writer.onCompleted(); + } + } + + /** Create a new writer if the writer is null. */ + private void createWriterIfNull() { + if (writer == null) { + writer = + serviceStub + .withWaitForReady() + .withDeadlineAfter(HANDSHAKE_RPC_DEADLINE_SECS, SECONDS) + .setUpSession(reader); + } + } + + private class Reader implements StreamObserver { + /** + * Places a {@code SessionResp} message in the {@code responses} queue, or an {@code + * IOException} if reading is complete. + * + * @param resp the {@code SessionResp} message received from the S2A handshaker module. + */ + @Override + public void onNext(SessionResp resp) { + verify(!doneReading); + responses.offer(Result.createWithResponse(resp)); + } + + /** + * Places a {@code Throwable} in the {@code responses} queue. + * + * @param t the {@code Throwable} caught when reading the stream to the S2A handshaker module. + */ + @Override + public void onError(Throwable t) { + responses.offer(Result.createWithThrowable(t)); + } + + /** + * Sets {@code doneReading} to true, and places a {@code ConnectionClosedException} in the + * {@code responses} queue. + */ + @Override + public void onCompleted() { + logger.log(Level.INFO, "Reading from the S2A is complete."); + doneReading = true; + responses.offer( + Result.createWithThrowable( + new ConnectionClosedException("Reading from the S2A is complete."))); + } + } + + private static final class Result { + private final Optional response; + private final Optional throwable; + + static Result createWithResponse(SessionResp response) { + return new Result(Optional.of(response), Optional.empty()); + } + + static Result createWithThrowable(Throwable throwable) { + return new Result(Optional.empty(), Optional.of(throwable)); + } + + private Result(Optional response, Optional throwable) { + checkArgument(response.isPresent() != throwable.isPresent()); + this.response = response; + this.throwable = throwable; + } + + /** Throws {@code throwable} if present, and returns {@code response} otherwise. */ + SessionResp getResultOrThrow() throws IOException { + if (throwable.isPresent()) { + if (throwable.get() instanceof ConnectionClosedException) { + ConnectionClosedException exception = (ConnectionClosedException) throwable.get(); + throw exception; + } else { + throw new IOException(throwable.get()); + } + } + verify(response.isPresent()); + return response.get(); + } + } +} \ No newline at end of file diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/S2ATrustManager.java b/s2a/src/main/java/io/grpc/s2a/handshaker/S2ATrustManager.java new file mode 100644 index 00000000000..fb113bb29cc --- /dev/null +++ b/s2a/src/main/java/io/grpc/s2a/handshaker/S2ATrustManager.java @@ -0,0 +1,152 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.handshaker; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableList; +import com.google.protobuf.ByteString; +import io.grpc.s2a.handshaker.S2AIdentity; +import io.grpc.s2a.handshaker.ValidatePeerCertificateChainReq.VerificationMode; +import java.io.IOException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Optional; +import javax.annotation.concurrent.NotThreadSafe; +import javax.net.ssl.X509TrustManager; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** Offloads verification of the peer certificate chain to S2A. */ +@NotThreadSafe +final class S2ATrustManager implements X509TrustManager { + private final Optional localIdentity; + private final S2AStub stub; + private final String hostname; + + static S2ATrustManager createForClient( + S2AStub stub, String hostname, Optional localIdentity) { + checkNotNull(stub); + checkNotNull(hostname); + return new S2ATrustManager(stub, hostname, localIdentity); + } + + private S2ATrustManager(S2AStub stub, String hostname, Optional localIdentity) { + this.stub = stub; + this.hostname = hostname; + this.localIdentity = localIdentity; + } + + /** + * Validates the given certificate chain provided by the peer. + * + * @param chain the peer certificate chain + * @param authType the authentication type based on the client certificate + * @throws IllegalArgumentException if null or zero-length chain is passed in for the chain + * parameter. + * @throws CertificateException if the certificate chain is not trusted by this TrustManager. + */ + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + checkPeerTrusted(chain, /* isCheckingClientCertificateChain= */ true); + } + + /** + * Validates the given certificate chain provided by the peer. + * + * @param chain the peer certificate chain + * @param authType the authentication type based on the client certificate + * @throws IllegalArgumentException if null or zero-length chain is passed in for the chain + * parameter. + * @throws CertificateException if the certificate chain is not trusted by this TrustManager. + */ + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + checkPeerTrusted(chain, /* isCheckingClientCertificateChain= */ false); + } + + /** + * Returns null because the accepted issuers are held in S2A and this class receives decision made + * from S2A on the fly about which to use to verify a given chain. + * + * @return null. + */ + @Override + public X509Certificate @Nullable [] getAcceptedIssuers() { + return null; + } + + private void checkPeerTrusted(X509Certificate[] chain, boolean isCheckingClientCertificateChain) + throws CertificateException { + checkNotNull(chain); + checkArgument(chain.length > 0, "Certificate chain has zero certificates."); + + ValidatePeerCertificateChainReq.Builder validatePeerCertificateChainReq = + ValidatePeerCertificateChainReq.newBuilder().setMode(VerificationMode.UNSPECIFIED); + if (isCheckingClientCertificateChain) { + validatePeerCertificateChainReq.setClientPeer( + ValidatePeerCertificateChainReq.ClientPeer.newBuilder() + .addAllCertificateChain(certificateChainToDerChain(chain))); + } else { + validatePeerCertificateChainReq.setServerPeer( + ValidatePeerCertificateChainReq.ServerPeer.newBuilder() + .addAllCertificateChain(certificateChainToDerChain(chain)) + .setServerHostname(hostname)); + } + + SessionReq.Builder reqBuilder = + SessionReq.newBuilder().setValidatePeerCertificateChainReq(validatePeerCertificateChainReq); + if (localIdentity.isPresent()) { + reqBuilder.setLocalIdentity(localIdentity.get().getIdentity()); + } + + SessionResp resp; + try { + resp = stub.send(reqBuilder.build()); + } catch (IOException | InterruptedException e) { + throw new CertificateException("Failed to send request to S2A.", e); + } + if (resp.hasStatus() && resp.getStatus().getCode() != 0) { + throw new CertificateException( + String.format( + "Error occurred in response from S2A, error code: %d, error message: %s.", + resp.getStatus().getCode(), resp.getStatus().getDetails())); + } + + if (!resp.hasValidatePeerCertificateChainResp()) { + throw new CertificateException("No valid response received from S2A."); + } + + ValidatePeerCertificateChainResp validationResult = resp.getValidatePeerCertificateChainResp(); + if (validationResult.getValidationResult() + != ValidatePeerCertificateChainResp.ValidationResult.SUCCESS) { + throw new CertificateException(validationResult.getValidationDetails()); + } + } + + private static ImmutableList certificateChainToDerChain(X509Certificate[] chain) + throws CertificateEncodingException { + ImmutableList.Builder derChain = ImmutableList.builder(); + for (X509Certificate certificate : chain) { + derChain.add(ByteString.copyFrom(certificate.getEncoded())); + } + return derChain.build(); + } +} \ No newline at end of file diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/SslContextFactory.java b/s2a/src/main/java/io/grpc/s2a/handshaker/SslContextFactory.java new file mode 100644 index 00000000000..1ac5887ebc4 --- /dev/null +++ b/s2a/src/main/java/io/grpc/s2a/handshaker/SslContextFactory.java @@ -0,0 +1,178 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.handshaker; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.collect.ImmutableSet; +import io.grpc.netty.GrpcSslContexts; +import io.grpc.s2a.handshaker.S2AIdentity; +import io.netty.handler.ssl.OpenSslContextOption; +import io.netty.handler.ssl.OpenSslSessionContext; +import io.netty.handler.ssl.OpenSslX509KeyManagerFactory; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Optional; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLSessionContext; + +/** Creates {@link SslContext} objects with TLS configurations from S2A server. */ +final class SslContextFactory { + + /** + * Creates {@link SslContext} objects for client with TLS configurations from S2A server. + * + * @param stub the {@link S2AStub} to talk to the S2A server. + * @param targetName the {@link String} of the server that this client makes connection to. + * @param localIdentity the {@link S2AIdentity} that should be used when talking to S2A server. + * Will use default identity if empty. + * @return a {@link SslContext} object. + * @throws NullPointerException if either {@code stub} or {@code targetName} is null. + * @throws IOException if an unexpected response from S2A server is received. + * @throws InterruptedException if {@code stub} is closed. + */ + static SslContext createForClient( + S2AStub stub, String targetName, Optional localIdentity) + throws IOException, + InterruptedException, + CertificateException, + KeyStoreException, + NoSuchAlgorithmException, + UnrecoverableKeyException, + GeneralSecurityException { + checkNotNull(stub, "stub should not be null."); + checkNotNull(targetName, "targetName should not be null on client side."); + GetTlsConfigurationResp.ClientTlsConfiguration clientTlsConfiguration; + try { + clientTlsConfiguration = getClientTlsConfigurationFromS2A(stub, localIdentity); + } catch (IOException | InterruptedException e) { + throw new GeneralSecurityException("Failed to get client TLS configuration from S2A.", e); + } + + // Use the default value for timeout. + // Use the smallest possible value for cache size. + // The Provider is by default OPENSSL. No need to manually set it. + SslContextBuilder sslContextBuilder = + GrpcSslContexts.configure(SslContextBuilder.forClient()) + .sessionCacheSize(1) + .sessionTimeout(0); + + configureSslContextWithClientTlsConfiguration(clientTlsConfiguration, sslContextBuilder); + sslContextBuilder.trustManager( + S2ATrustManager.createForClient(stub, targetName, localIdentity)); + sslContextBuilder.option( + OpenSslContextOption.PRIVATE_KEY_METHOD, S2APrivateKeyMethod.create(stub, localIdentity)); + + SslContext sslContext = sslContextBuilder.build(); + SSLSessionContext sslSessionContext = sslContext.sessionContext(); + if (sslSessionContext instanceof OpenSslSessionContext) { + OpenSslSessionContext openSslSessionContext = (OpenSslSessionContext) sslSessionContext; + openSslSessionContext.setSessionCacheEnabled(false); + } + + return sslContext; + } + + private static GetTlsConfigurationResp.ClientTlsConfiguration getClientTlsConfigurationFromS2A( + S2AStub stub, Optional localIdentity) throws IOException, InterruptedException { + checkNotNull(stub, "stub should not be null."); + SessionReq.Builder reqBuilder = SessionReq.newBuilder(); + if (localIdentity.isPresent()) { + reqBuilder.setLocalIdentity(localIdentity.get().getIdentity()); + } + Optional authMechanism = + GetAuthenticationMechanisms.getAuthMechanism(localIdentity); + if (authMechanism.isPresent()) { + reqBuilder.addAuthenticationMechanisms(authMechanism.get()); + } + SessionResp resp = + stub.send( + reqBuilder + .setGetTlsConfigurationReq( + GetTlsConfigurationReq.newBuilder() + .setConnectionSide(ConnectionSide.CONNECTION_SIDE_CLIENT)) + .build()); + if (resp.hasStatus() && resp.getStatus().getCode() != 0) { + throw new S2AConnectionException( + String.format( + "response from S2A server has ean error %d with error message %s.", + resp.getStatus().getCode(), resp.getStatus().getDetails())); + } + if (!resp.getGetTlsConfigurationResp().hasClientTlsConfiguration()) { + throw new S2AConnectionException( + "Response from S2A server does NOT contain ClientTlsConfiguration."); + } + return resp.getGetTlsConfigurationResp().getClientTlsConfiguration(); + } + + private static void configureSslContextWithClientTlsConfiguration( + GetTlsConfigurationResp.ClientTlsConfiguration clientTlsConfiguration, + SslContextBuilder sslContextBuilder) + throws CertificateException, + IOException, + KeyStoreException, + NoSuchAlgorithmException, + UnrecoverableKeyException { + sslContextBuilder.keyManager(createKeylessManager(clientTlsConfiguration)); + ImmutableSet tlsVersions = + ProtoUtil.buildTlsProtocolVersionSet( + clientTlsConfiguration.getMinTlsVersion(), clientTlsConfiguration.getMaxTlsVersion()); + if (tlsVersions.isEmpty()) { + throw new S2AConnectionException("Set of TLS versions received from S2A server is empty."); + } + sslContextBuilder.protocols(tlsVersions); + } + + private static KeyManager createKeylessManager( + GetTlsConfigurationResp.ClientTlsConfiguration clientTlsConfiguration) + throws CertificateException, + IOException, + KeyStoreException, + NoSuchAlgorithmException, + UnrecoverableKeyException { + X509Certificate[] certificates = + new X509Certificate[clientTlsConfiguration.getCertificateChainCount()]; + for (int i = 0; i < clientTlsConfiguration.getCertificateChainCount(); ++i) { + certificates[i] = convertStringToX509Cert(clientTlsConfiguration.getCertificateChain(i)); + } + KeyManager[] keyManagers = + OpenSslX509KeyManagerFactory.newKeyless(certificates).getKeyManagers(); + if (keyManagers == null || keyManagers.length == 0) { + throw new IllegalStateException("No key managers created."); + } + return keyManagers[0]; + } + + private static X509Certificate convertStringToX509Cert(String certificate) + throws CertificateException { + return (X509Certificate) + CertificateFactory.getInstance("X509") + .generateCertificate(new ByteArrayInputStream(certificate.getBytes(UTF_8))); + } + + private SslContextFactory() {} +} \ No newline at end of file diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/AccessTokenManager.java b/s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/AccessTokenManager.java new file mode 100644 index 00000000000..94549d11c87 --- /dev/null +++ b/s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/AccessTokenManager.java @@ -0,0 +1,61 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.handshaker.tokenmanager; + +import io.grpc.s2a.handshaker.S2AIdentity; +import java.lang.reflect.Method; +import java.util.Optional; +import javax.annotation.concurrent.ThreadSafe; + +/** Manages access tokens for authenticating to the S2A. */ +@ThreadSafe +public final class AccessTokenManager { + private final TokenFetcher tokenFetcher; + + /** Creates an {@code AccessTokenManager} based on the environment where the application runs. */ + @SuppressWarnings("RethrowReflectiveOperationExceptionAsLinkageError") + public static Optional create() { + Optional tokenFetcher; + try { + Class singleTokenFetcherClass = + Class.forName("io.grpc.s2a.handshaker.tokenmanager.SingleTokenFetcher"); + Method createTokenFetcher = singleTokenFetcherClass.getMethod("create"); + tokenFetcher = (Optional) createTokenFetcher.invoke(null); + } catch (ClassNotFoundException e) { + tokenFetcher = Optional.empty(); + } catch (ReflectiveOperationException e) { + throw new AssertionError(e); + } + return tokenFetcher.isPresent() + ? Optional.of(new AccessTokenManager((TokenFetcher) tokenFetcher.get())) + : Optional.empty(); + } + + private AccessTokenManager(TokenFetcher tokenFetcher) { + this.tokenFetcher = tokenFetcher; + } + + /** Returns an access token when no identity is specified. */ + public String getDefaultToken() { + return tokenFetcher.getDefaultToken(); + } + + /** Returns an access token for the given identity. */ + public String getToken(S2AIdentity identity) { + return tokenFetcher.getToken(identity); + } +} \ No newline at end of file diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/SingleTokenFetcher.java b/s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/SingleTokenFetcher.java new file mode 100644 index 00000000000..c3dffd2b715 --- /dev/null +++ b/s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/SingleTokenFetcher.java @@ -0,0 +1,57 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.handshaker.tokenmanager; + +import com.google.common.annotations.VisibleForTesting; +import io.grpc.s2a.handshaker.S2AIdentity; +import java.util.Optional; + +/** Fetches a single access token via an environment variable. */ +@SuppressWarnings("NonFinalStaticField") +public final class SingleTokenFetcher implements TokenFetcher { + private static final String ENVIRONMENT_VARIABLE = "S2A_ACCESS_TOKEN"; + private static String accessToken = System.getenv(ENVIRONMENT_VARIABLE); + + private final String token; + + /** + * Creates a {@code SingleTokenFetcher} from {@code ENVIRONMENT_VARIABLE}, and returns an empty + * {@code Optional} instance if the token could not be fetched. + */ + public static Optional create() { + return Optional.ofNullable(accessToken).map(SingleTokenFetcher::new); + } + + @VisibleForTesting + public static void setAccessToken(String token) { + accessToken = token; + } + + private SingleTokenFetcher(String token) { + this.token = token; + } + + @Override + public String getDefaultToken() { + return token; + } + + @Override + public String getToken(S2AIdentity identity) { + return token; + } +} \ No newline at end of file diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/TokenFetcher.java b/s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/TokenFetcher.java new file mode 100644 index 00000000000..9eeddaad844 --- /dev/null +++ b/s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/TokenFetcher.java @@ -0,0 +1,28 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.handshaker.tokenmanager; + +import io.grpc.s2a.handshaker.S2AIdentity; + +/** Fetches tokens used to authenticate to S2A. */ +interface TokenFetcher { + /** Returns an access token when no identity is specified. */ + String getDefaultToken(); + + /** Returns an access token for the given identity. */ + String getToken(S2AIdentity identity); +} \ No newline at end of file diff --git a/s2a/src/main/proto/grpc/gcp/s2a/common.proto b/s2a/src/main/proto/grpc/gcp/s2a/common.proto new file mode 100644 index 00000000000..749739553dd --- /dev/null +++ b/s2a/src/main/proto/grpc/gcp/s2a/common.proto @@ -0,0 +1,82 @@ +// Copyright 2024 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/gcp/s2a/common.proto + +syntax = "proto3"; + +package grpc.gcp.s2a; + +option java_multiple_files = true; +option java_outer_classname = "CommonProto"; +option java_package = "io.grpc.s2a.handshaker"; + +// The TLS 1.0-1.2 ciphersuites that the application can negotiate when using +// S2A. +enum Ciphersuite { + CIPHERSUITE_UNSPECIFIED = 0; + CIPHERSUITE_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 1; + CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 2; + CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = 3; + CIPHERSUITE_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 4; + CIPHERSUITE_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 5; + CIPHERSUITE_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 6; +} + +// The TLS versions supported by S2A's handshaker module. +enum TLSVersion { + TLS_VERSION_UNSPECIFIED = 0; + TLS_VERSION_1_0 = 1; + TLS_VERSION_1_1 = 2; + TLS_VERSION_1_2 = 3; + TLS_VERSION_1_3 = 4; +} + +// The side in the TLS connection. +enum ConnectionSide { + CONNECTION_SIDE_UNSPECIFIED = 0; + CONNECTION_SIDE_CLIENT = 1; + CONNECTION_SIDE_SERVER = 2; +} + +// The ALPN protocols that the application can negotiate during a TLS handshake. +enum AlpnProtocol { + ALPN_PROTOCOL_UNSPECIFIED = 0; + ALPN_PROTOCOL_GRPC = 1; + ALPN_PROTOCOL_HTTP2 = 2; + ALPN_PROTOCOL_HTTP1_1 = 3; +} + +message Identity { + oneof identity_oneof { + // The SPIFFE ID of a connection endpoint. + string spiffe_id = 1; + + // The hostname of a connection endpoint. + string hostname = 2; + + // The UID of a connection endpoint. + string uid = 4; + + // The username of a connection endpoint. + string username = 5; + + // The GCP ID of a connection endpoint. + string gcp_id = 6; + } + + // Additional identity-specific attributes. + map attributes = 3; +} diff --git a/s2a/src/main/proto/grpc/gcp/s2a/s2a.proto b/s2a/src/main/proto/grpc/gcp/s2a/s2a.proto new file mode 100644 index 00000000000..8a85e348c24 --- /dev/null +++ b/s2a/src/main/proto/grpc/gcp/s2a/s2a.proto @@ -0,0 +1,369 @@ +// Copyright 2024 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/gcp/s2a/s2a.proto + +syntax = "proto3"; + +package grpc.gcp.s2a; + +import "grpc/gcp/s2a/common.proto"; +import "grpc/gcp/s2a/s2a_context.proto"; + +option java_multiple_files = true; +option java_outer_classname = "S2AProto"; +option java_package = "io.grpc.s2a.handshaker"; + +enum SignatureAlgorithm { + S2A_SSL_SIGN_UNSPECIFIED = 0; + // RSA Public-Key Cryptography Standards #1. + S2A_SSL_SIGN_RSA_PKCS1_SHA256 = 1; + S2A_SSL_SIGN_RSA_PKCS1_SHA384 = 2; + S2A_SSL_SIGN_RSA_PKCS1_SHA512 = 3; + // ECDSA. + S2A_SSL_SIGN_ECDSA_SECP256R1_SHA256 = 4; + S2A_SSL_SIGN_ECDSA_SECP384R1_SHA384 = 5; + S2A_SSL_SIGN_ECDSA_SECP521R1_SHA512 = 6; + // RSA Probabilistic Signature Scheme. + S2A_SSL_SIGN_RSA_PSS_RSAE_SHA256 = 7; + S2A_SSL_SIGN_RSA_PSS_RSAE_SHA384 = 8; + S2A_SSL_SIGN_RSA_PSS_RSAE_SHA512 = 9; + // ED25519. + S2A_SSL_SIGN_ED25519 = 10; +} + +message AlpnPolicy { + // If true, the application MUST perform ALPN negotiation. + bool enable_alpn_negotiation = 1; + + // The ordered list of ALPN protocols that specify how the application SHOULD + // negotiate ALPN during the TLS handshake. + // + // The application MAY ignore any ALPN protocols in this list that are not + // supported by the application. + repeated AlpnProtocol alpn_protocols = 2; +} + +message AuthenticationMechanism { + // Applications may specify an identity associated to an authentication + // mechanism. Otherwise, S2A assumes that the authentication mechanism is + // associated with the default identity. If the default identity cannot be + // determined, the request is rejected. + Identity identity = 1; + + oneof mechanism_oneof { + // A token that the application uses to authenticate itself to S2A. + string token = 2; + } +} + +message Status { + // The status code that is specific to the application and the implementation + // of S2A, e.g., gRPC status code. + uint32 code = 1; + + // The status details. + string details = 2; +} + +message GetTlsConfigurationReq { + // The role of the application in the TLS connection. + ConnectionSide connection_side = 1; + + // The server name indication (SNI) extension, which MAY be populated when a + // server is offloading to S2A. The SNI is used to determine the server + // identity if the local identity in the request is empty. + string sni = 2; +} + +message GetTlsConfigurationResp { + // Next ID: 8 + message ClientTlsConfiguration { + reserved 4, 5; + + // The certificate chain that the client MUST use for the TLS handshake. + // It's a list of PEM-encoded certificates, ordered from leaf to root, + // excluding the root. + repeated string certificate_chain = 1; + + // The minimum TLS version number that the client MUST use for the TLS + // handshake. If this field is not provided, the client MUST use the default + // minimum version of the client's TLS library. + TLSVersion min_tls_version = 2; + + // The maximum TLS version number that the client MUST use for the TLS + // handshake. If this field is not provided, the client MUST use the default + // maximum version of the client's TLS library. + TLSVersion max_tls_version = 3; + + // The ordered list of TLS 1.0-1.2 ciphersuites that the client MAY offer to + // negotiate in the TLS handshake. + repeated Ciphersuite ciphersuites = 6; + + // The policy that dictates how the client negotiates ALPN during the TLS + // handshake. + AlpnPolicy alpn_policy = 7; + } + + // Next ID: 12 + message ServerTlsConfiguration { + reserved 4, 5; + + enum RequestClientCertificate { + UNSPECIFIED = 0; + DONT_REQUEST_CLIENT_CERTIFICATE = 1; + REQUEST_CLIENT_CERTIFICATE_BUT_DONT_VERIFY = 2; + REQUEST_CLIENT_CERTIFICATE_AND_VERIFY = 3; + REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_BUT_DONT_VERIFY = 4; + REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY = 5; + } + + // The certificate chain that the server MUST use for the TLS handshake. + // It's a list of PEM-encoded certificates, ordered from leaf to root, + // excluding the root. + repeated string certificate_chain = 1; + + // The minimum TLS version number that the server MUST use for the TLS + // handshake. If this field is not provided, the server MUST use the default + // minimum version of the server's TLS library. + TLSVersion min_tls_version = 2; + + // The maximum TLS version number that the server MUST use for the TLS + // handshake. If this field is not provided, the server MUST use the default + // maximum version of the server's TLS library. + TLSVersion max_tls_version = 3; + + // The ordered list of TLS 1.0-1.2 ciphersuites that the server MAY offer to + // negotiate in the TLS handshake. + repeated Ciphersuite ciphersuites = 10; + + // Whether to enable TLS resumption. + bool tls_resumption_enabled = 6; + + // Whether the server MUST request a client certificate (i.e. to negotiate + // TLS vs. mTLS). + RequestClientCertificate request_client_certificate = 7; + + // Returns the maximum number of extra bytes that + // |OffloadResumptionKeyOperation| can add to the number of unencrypted + // bytes to form the encrypted bytes. + uint32 max_overhead_of_ticket_aead = 9; + + // The policy that dictates how the server negotiates ALPN during the TLS + // handshake. + AlpnPolicy alpn_policy = 11; + } + + oneof tls_configuration { + ClientTlsConfiguration client_tls_configuration = 1; + ServerTlsConfiguration server_tls_configuration = 2; + } +} + +message OffloadPrivateKeyOperationReq { + enum PrivateKeyOperation { + UNSPECIFIED = 0; + // When performing a TLS 1.2 or 1.3 handshake, the (partial) transcript of + // the TLS handshake must be signed to prove possession of the private key. + // + // See https://www.rfc-editor.org/rfc/rfc8446.html#section-4.4.3. + SIGN = 1; + // When performing a TLS 1.2 handshake using an RSA algorithm, the key + // exchange algorithm involves the client generating a premaster secret, + // encrypting it using the server's public key, and sending this encrypted + // blob to the server in a ClientKeyExchange message. + // + // See https://www.rfc-editor.org/rfc/rfc4346#section-7.4.7.1. + DECRYPT = 2; + } + + // The operation the private key is used for. + PrivateKeyOperation operation = 1; + + // The signature algorithm to be used for signing operations. + SignatureAlgorithm signature_algorithm = 2; + + // The input bytes to be signed or decrypted. + oneof in_bytes { + // Raw bytes to be hashed and signed, or decrypted. + bytes raw_bytes = 4; + // A SHA256 hash to be signed. Must be 32 bytes. + bytes sha256_digest = 5; + // A SHA384 hash to be signed. Must be 48 bytes. + bytes sha384_digest = 6; + // A SHA512 hash to be signed. Must be 64 bytes. + bytes sha512_digest = 7; + } +} + +message OffloadPrivateKeyOperationResp { + // The signed or decrypted output bytes. + bytes out_bytes = 1; +} + +message OffloadResumptionKeyOperationReq { + enum ResumptionKeyOperation { + UNSPECIFIED = 0; + ENCRYPT = 1; + DECRYPT = 2; + } + + // The operation the resumption key is used for. + ResumptionKeyOperation operation = 1; + + // The bytes to be encrypted or decrypted. + bytes in_bytes = 2; +} + +message OffloadResumptionKeyOperationResp { + // The encrypted or decrypted bytes. + bytes out_bytes = 1; +} + +message ValidatePeerCertificateChainReq { + enum VerificationMode { + // The default verification mode supported by S2A. + UNSPECIFIED = 0; + // The SPIFFE verification mode selects the set of trusted certificates to + // use for path building based on the SPIFFE trust domain in the peer's leaf + // certificate. + SPIFFE = 1; + // The connect-to-Google verification mode uses the trust bundle for + // connecting to Google, e.g. *.mtls.googleapis.com endpoints. + CONNECT_TO_GOOGLE = 2; + } + + message ClientPeer { + // The certificate chain to be verified. The chain MUST be a list of + // DER-encoded certificates, ordered from leaf to root, excluding the root. + repeated bytes certificate_chain = 1; + } + + message ServerPeer { + // The certificate chain to be verified. The chain MUST be a list of + // DER-encoded certificates, ordered from leaf to root, excluding the root. + repeated bytes certificate_chain = 1; + + // The expected hostname of the server. + string server_hostname = 2; + + // The UnrestrictedClientPolicy specified by the user. + bytes serialized_unrestricted_client_policy = 3; + } + + // The verification mode that S2A MUST use to validate the peer certificate + // chain. + VerificationMode mode = 1; + + oneof peer_oneof { + ClientPeer client_peer = 2; + ServerPeer server_peer = 3; + } +} + +message ValidatePeerCertificateChainResp { + enum ValidationResult { + UNSPECIFIED = 0; + SUCCESS = 1; + FAILURE = 2; + } + + // The result of validating the peer certificate chain. + ValidationResult validation_result = 1; + + // The validation details. This field is only populated when the validation + // result is NOT SUCCESS. + string validation_details = 2; + + // The S2A context contains information from the peer certificate chain. + // + // The S2A context MAY be populated even if validation of the peer certificate + // chain fails. + S2AContext context = 3; +} + +message SessionReq { + // The identity corresponding to the TLS configurations that MUST be used for + // the TLS handshake. + // + // If a managed identity already exists, the local identity and authentication + // mechanisms are ignored. If a managed identity doesn't exist and the local + // identity is not populated, S2A will try to deduce the managed identity to + // use from the SNI extension. If that also fails, S2A uses the default + // identity (if one exists). + Identity local_identity = 1; + + // The authentication mechanisms that the application wishes to use to + // authenticate to S2A, ordered by preference. S2A will always use the first + // authentication mechanism that matches the managed identity. + repeated AuthenticationMechanism authentication_mechanisms = 2; + + oneof req_oneof { + // Requests the certificate chain and TLS configuration corresponding to the + // local identity, which the application MUST use to negotiate the TLS + // handshake. + GetTlsConfigurationReq get_tls_configuration_req = 3; + + // Signs or decrypts the input bytes using a private key corresponding to + // the local identity in the request. + // + // WARNING: More than one OffloadPrivateKeyOperationReq may be sent to the + // S2Av2 by a server during a TLS 1.2 handshake. + OffloadPrivateKeyOperationReq offload_private_key_operation_req = 4; + + // Encrypts or decrypts the input bytes using a resumption key corresponding + // to the local identity in the request. + OffloadResumptionKeyOperationReq offload_resumption_key_operation_req = 5; + + // Verifies the peer's certificate chain using + // (a) trust bundles corresponding to the local identity in the request, and + // (b) the verification mode in the request. + ValidatePeerCertificateChainReq validate_peer_certificate_chain_req = 6; + } +} + +message SessionResp { + // Status of the session response. + // + // The status field is populated so that if an error occurs when making an + // individual request, then communication with the S2A may continue. If an + // error is returned directly (e.g. at the gRPC layer), then it may result + // that the bidirectional stream being closed. + Status status = 1; + + oneof resp_oneof { + // Contains the certificate chain and TLS configurations corresponding to + // the local identity. + GetTlsConfigurationResp get_tls_configuration_resp = 2; + + // Contains the signed or encrypted output bytes using the private key + // corresponding to the local identity. + OffloadPrivateKeyOperationResp offload_private_key_operation_resp = 3; + + // Contains the encrypted or decrypted output bytes using the resumption key + // corresponding to the local identity. + OffloadResumptionKeyOperationResp offload_resumption_key_operation_resp = 4; + + // Contains the validation result, peer identity and fingerprints of peer + // certificates. + ValidatePeerCertificateChainResp validate_peer_certificate_chain_resp = 5; + } +} + +service S2AService { + // SetUpSession is a bidirectional stream used by applications to offload + // operations from the TLS handshake. + rpc SetUpSession(stream SessionReq) returns (stream SessionResp) {} +} diff --git a/s2a/src/main/proto/grpc/gcp/s2a/s2a_context.proto b/s2a/src/main/proto/grpc/gcp/s2a/s2a_context.proto new file mode 100644 index 00000000000..edaeaf22669 --- /dev/null +++ b/s2a/src/main/proto/grpc/gcp/s2a/s2a_context.proto @@ -0,0 +1,62 @@ +// Copyright 2024 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/gcp/s2a/s2a_context.proto + +syntax = "proto3"; + +package grpc.gcp.s2a; + +import "grpc/gcp/s2a/common.proto"; + +option java_multiple_files = true; +option java_outer_classname = "S2AContextProto"; +option java_package = "io.grpc.s2a.handshaker"; + +message S2AContext { + // The SPIFFE ID from the peer leaf certificate, if present. + // + // This field is only populated if the leaf certificate is a valid SPIFFE + // SVID; in particular, there is a unique URI SAN and this URI SAN is a valid + // SPIFFE ID. + string leaf_cert_spiffe_id = 1; + + // The URIs that are present in the SubjectAltName extension of the peer leaf + // certificate. + // + // Note that the extracted URIs are not validated and may not be properly + // formatted. + repeated string leaf_cert_uris = 2; + + // The DNSNames that are present in the SubjectAltName extension of the peer + // leaf certificate. + repeated string leaf_cert_dnsnames = 3; + + // The (ordered) list of fingerprints in the certificate chain used to verify + // the given leaf certificate. The order MUST be from leaf certificate + // fingerprint to root certificate fingerprint. + // + // A fingerprint is the base-64 encoding of the SHA256 hash of the + // DER-encoding of a certificate. The list MAY be populated even if the peer + // certificate chain was NOT validated successfully. + repeated string peer_certificate_chain_fingerprints = 4; + + // The local identity used during session setup. + Identity local_identity = 5; + + // The SHA256 hash of the DER-encoding of the local leaf certificate used in + // the handshake. + bytes local_leaf_cert_fingerprint = 6; +} diff --git a/s2a/src/test/java/io/grpc/s2a/MtlsToS2AChannelCredentialsTest.java b/s2a/src/test/java/io/grpc/s2a/MtlsToS2AChannelCredentialsTest.java new file mode 100644 index 00000000000..5ccc522292e --- /dev/null +++ b/s2a/src/test/java/io/grpc/s2a/MtlsToS2AChannelCredentialsTest.java @@ -0,0 +1,135 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class MtlsToS2AChannelCredentialsTest { + @Test + public void createBuilder_nullAddress_throwsException() throws Exception { + assertThrows( + IllegalArgumentException.class, + () -> + MtlsToS2AChannelCredentials.createBuilder( + /* s2aAddress= */ null, + /* privateKeyPath= */ "src/test/resources/client_key.pem", + /* certChainPath= */ "src/test/resources/client_cert.pem", + /* trustBundlePath= */ "src/test/resources/root_cert.pem")); + } + + @Test + public void createBuilder_nullPrivateKeyPath_throwsException() throws Exception { + assertThrows( + IllegalArgumentException.class, + () -> + MtlsToS2AChannelCredentials.createBuilder( + /* s2aAddress= */ "s2a_address", + /* privateKeyPath= */ null, + /* certChainPath= */ "src/test/resources/client_cert.pem", + /* trustBundlePath= */ "src/test/resources/root_cert.pem")); + } + + @Test + public void createBuilder_nullCertChainPath_throwsException() throws Exception { + assertThrows( + IllegalArgumentException.class, + () -> + MtlsToS2AChannelCredentials.createBuilder( + /* s2aAddress= */ "s2a_address", + /* privateKeyPath= */ "src/test/resources/client_key.pem", + /* certChainPath= */ null, + /* trustBundlePath= */ "src/test/resources/root_cert.pem")); + } + + @Test + public void createBuilder_nullTrustBundlePath_throwsException() throws Exception { + assertThrows( + IllegalArgumentException.class, + () -> + MtlsToS2AChannelCredentials.createBuilder( + /* s2aAddress= */ "s2a_address", + /* privateKeyPath= */ "src/test/resources/client_key.pem", + /* certChainPath= */ "src/test/resources/client_cert.pem", + /* trustBundlePath= */ null)); + } + + @Test + public void createBuilder_emptyAddress_throwsException() throws Exception { + assertThrows( + IllegalArgumentException.class, + () -> + MtlsToS2AChannelCredentials.createBuilder( + /* s2aAddress= */ "", + /* privateKeyPath= */ "src/test/resources/client_key.pem", + /* certChainPath= */ "src/test/resources/client_cert.pem", + /* trustBundlePath= */ "src/test/resources/root_cert.pem")); + } + + @Test + public void createBuilder_emptyPrivateKeyPath_throwsException() throws Exception { + assertThrows( + IllegalArgumentException.class, + () -> + MtlsToS2AChannelCredentials.createBuilder( + /* s2aAddress= */ "s2a_address", + /* privateKeyPath= */ "", + /* certChainPath= */ "src/test/resources/client_cert.pem", + /* trustBundlePath= */ "src/test/resources/root_cert.pem")); + } + + @Test + public void createBuilder_emptyCertChainPath_throwsException() throws Exception { + assertThrows( + IllegalArgumentException.class, + () -> + MtlsToS2AChannelCredentials.createBuilder( + /* s2aAddress= */ "s2a_address", + /* privateKeyPath= */ "src/test/resources/client_key.pem", + /* certChainPath= */ "", + /* trustBundlePath= */ "src/test/resources/root_cert.pem")); + } + + @Test + public void createBuilder_emptyTrustBundlePath_throwsException() throws Exception { + assertThrows( + IllegalArgumentException.class, + () -> + MtlsToS2AChannelCredentials.createBuilder( + /* s2aAddress= */ "s2a_address", + /* privateKeyPath= */ "src/test/resources/client_key.pem", + /* certChainPath= */ "src/test/resources/client_cert.pem", + /* trustBundlePath= */ "")); + } + + @Test + public void build_s2AChannelCredentials_success() throws Exception { + assertThat( + MtlsToS2AChannelCredentials.createBuilder( + /* s2aAddress= */ "s2a_address", + /* privateKeyPath= */ "src/test/resources/client_key.pem", + /* certChainPath= */ "src/test/resources/client_cert.pem", + /* trustBundlePath= */ "src/test/resources/root_cert.pem") + .build()) + .isInstanceOf(S2AChannelCredentials.Builder.class); + } +} \ No newline at end of file diff --git a/s2a/src/test/java/io/grpc/s2a/S2AChannelCredentialsTest.java b/s2a/src/test/java/io/grpc/s2a/S2AChannelCredentialsTest.java new file mode 100644 index 00000000000..a6133ed0af8 --- /dev/null +++ b/s2a/src/test/java/io/grpc/s2a/S2AChannelCredentialsTest.java @@ -0,0 +1,112 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import io.grpc.ChannelCredentials; +import io.grpc.TlsChannelCredentials; +import java.io.File; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@code S2AChannelCredentials}. */ +@RunWith(JUnit4.class) +public final class S2AChannelCredentialsTest { + @Test + public void createBuilder_nullArgument_throwsException() throws Exception { + assertThrows(IllegalArgumentException.class, () -> S2AChannelCredentials.createBuilder(null)); + } + + @Test + public void createBuilder_emptyAddress_throwsException() throws Exception { + assertThrows(IllegalArgumentException.class, () -> S2AChannelCredentials.createBuilder("")); + } + + @Test + public void setLocalSpiffeId_nullArgument_throwsException() throws Exception { + assertThrows( + NullPointerException.class, + () -> S2AChannelCredentials.createBuilder("s2a_address").setLocalSpiffeId(null)); + } + + @Test + public void setLocalHostname_nullArgument_throwsException() throws Exception { + assertThrows( + NullPointerException.class, + () -> S2AChannelCredentials.createBuilder("s2a_address").setLocalHostname(null)); + } + + @Test + public void setLocalUid_nullArgument_throwsException() throws Exception { + assertThrows( + NullPointerException.class, + () -> S2AChannelCredentials.createBuilder("s2a_address").setLocalUid(null)); + } + + @Test + public void build_withLocalSpiffeId_succeeds() throws Exception { + assertThat( + S2AChannelCredentials.createBuilder("s2a_address") + .setLocalSpiffeId("spiffe://test") + .build()) + .isNotNull(); + } + + @Test + public void build_withLocalHostname_succeeds() throws Exception { + assertThat( + S2AChannelCredentials.createBuilder("s2a_address") + .setLocalHostname("local_hostname") + .build()) + .isNotNull(); + } + + @Test + public void build_withLocalUid_succeeds() throws Exception { + assertThat(S2AChannelCredentials.createBuilder("s2a_address").setLocalUid("local_uid").build()) + .isNotNull(); + } + + @Test + public void build_withNoLocalIdentity_succeeds() throws Exception { + assertThat(S2AChannelCredentials.createBuilder("s2a_address").build()) + .isNotNull(); + } + + @Test + public void build_withTlsChannelCredentials_succeeds() throws Exception { + assertThat( + S2AChannelCredentials.createBuilder("s2a_address") + .setLocalSpiffeId("spiffe://test") + .setS2AChannelCredentials(getTlsChannelCredentials()) + .build()) + .isNotNull(); + } + + private static ChannelCredentials getTlsChannelCredentials() throws Exception { + File clientCert = new File("src/test/resources/client_cert.pem"); + File clientKey = new File("src/test/resources/client_key.pem"); + File rootCert = new File("src/test/resources/root_cert.pem"); + return TlsChannelCredentials.newBuilder() + .keyManager(clientCert, clientKey) + .trustManager(rootCert) + .build(); + } +} \ No newline at end of file diff --git a/s2a/src/test/java/io/grpc/s2a/channel/S2AGrpcChannelPoolTest.java b/s2a/src/test/java/io/grpc/s2a/channel/S2AGrpcChannelPoolTest.java new file mode 100644 index 00000000000..260129f8f56 --- /dev/null +++ b/s2a/src/test/java/io/grpc/s2a/channel/S2AGrpcChannelPoolTest.java @@ -0,0 +1,125 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.channel; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.mock; + +import io.grpc.Channel; +import io.grpc.internal.ObjectPool; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link S2AGrpcChannelPool}. */ +@RunWith(JUnit4.class) +public final class S2AGrpcChannelPoolTest { + @Test + public void getChannel_success() throws Exception { + FakeChannelPool fakeChannelPool = new FakeChannelPool(); + S2AChannelPool s2aChannelPool = S2AGrpcChannelPool.create(fakeChannelPool); + + Channel channel = s2aChannelPool.getChannel(); + + assertThat(channel).isNotNull(); + assertThat(fakeChannelPool.isChannelCached()).isTrue(); + assertThat(s2aChannelPool.getChannel()).isEqualTo(channel); + } + + @Test + public void returnToPool_success() throws Exception { + FakeChannelPool fakeChannelPool = new FakeChannelPool(); + S2AChannelPool s2aChannelPool = S2AGrpcChannelPool.create(fakeChannelPool); + + s2aChannelPool.returnToPool(s2aChannelPool.getChannel()); + + assertThat(fakeChannelPool.isChannelCached()).isFalse(); + } + + @Test + public void returnToPool_channelStillCachedBecauseMultipleChannelsRetrieved() throws Exception { + FakeChannelPool fakeChannelPool = new FakeChannelPool(); + S2AChannelPool s2aChannelPool = S2AGrpcChannelPool.create(fakeChannelPool); + + s2aChannelPool.getChannel(); + s2aChannelPool.returnToPool(s2aChannelPool.getChannel()); + + assertThat(fakeChannelPool.isChannelCached()).isTrue(); + } + + @Test + public void returnToPool_failureBecauseChannelWasNotFromPool() throws Exception { + S2AChannelPool s2aChannelPool = S2AGrpcChannelPool.create(new FakeChannelPool()); + + IllegalArgumentException expected = + assertThrows( + IllegalArgumentException.class, + () -> s2aChannelPool.returnToPool(mock(Channel.class))); + assertThat(expected) + .hasMessageThat() + .isEqualTo( + "Cannot return the channel to channel pool because the channel was not obtained from" + + " channel pool."); + } + + @Test + public void close_success() throws Exception { + FakeChannelPool fakeChannelPool = new FakeChannelPool(); + try (S2AChannelPool s2aChannelPool = S2AGrpcChannelPool.create(fakeChannelPool)) { + s2aChannelPool.getChannel(); + } + + assertThat(fakeChannelPool.isChannelCached()).isFalse(); + } + + @Test + public void close_poolIsUnusable() throws Exception { + S2AChannelPool s2aChannelPool = S2AGrpcChannelPool.create(new FakeChannelPool()); + s2aChannelPool.close(); + + IllegalStateException expected = + assertThrows(IllegalStateException.class, s2aChannelPool::getChannel); + + assertThat(expected).hasMessageThat().isEqualTo("Channel pool is not open."); + } + + private static class FakeChannelPool implements ObjectPool { + private final Channel mockChannel = mock(Channel.class); + private @Nullable Channel cachedChannel = null; + + @Override + public Channel getObject() { + if (cachedChannel == null) { + cachedChannel = mockChannel; + } + return cachedChannel; + } + + @Override + public Channel returnObject(Object object) { + assertThat(object).isSameInstanceAs(mockChannel); + cachedChannel = null; + return null; + } + + public boolean isChannelCached() { + return (cachedChannel != null); + } + } +} \ No newline at end of file diff --git a/s2a/src/test/java/io/grpc/s2a/channel/S2AHandshakerServiceChannelTest.java b/s2a/src/test/java/io/grpc/s2a/channel/S2AHandshakerServiceChannelTest.java new file mode 100644 index 00000000000..57288be1b6f --- /dev/null +++ b/s2a/src/test/java/io/grpc/s2a/channel/S2AHandshakerServiceChannelTest.java @@ -0,0 +1,390 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.channel; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ChannelCredentials; +import io.grpc.ClientCall; +import io.grpc.ManagedChannel; +import io.grpc.MethodDescriptor; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.ServerCredentials; +import io.grpc.StatusRuntimeException; +import io.grpc.TlsChannelCredentials; +import io.grpc.TlsServerCredentials; +import io.grpc.benchmarks.Utils; +import io.grpc.internal.SharedResourceHolder.Resource; +import io.grpc.netty.NettyServerBuilder; +import io.grpc.s2a.channel.S2AHandshakerServiceChannel.EventLoopHoldingChannel; +import io.grpc.stub.StreamObserver; +import io.grpc.testing.GrpcCleanupRule; +import io.grpc.testing.protobuf.SimpleRequest; +import io.grpc.testing.protobuf.SimpleResponse; +import io.grpc.testing.protobuf.SimpleServiceGrpc; +import io.netty.channel.EventLoopGroup; +import java.io.File; +import java.time.Duration; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link S2AHandshakerServiceChannel}. */ +@RunWith(JUnit4.class) +public final class S2AHandshakerServiceChannelTest { + @ClassRule public static final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule(); + private static final Duration CHANNEL_SHUTDOWN_TIMEOUT = Duration.ofSeconds(10); + private final EventLoopGroup mockEventLoopGroup = mock(EventLoopGroup.class); + private Server mtlsServer; + private Server plaintextServer; + + @Before + public void setUp() throws Exception { + mtlsServer = createMtlsServer(); + plaintextServer = createPlaintextServer(); + mtlsServer.start(); + plaintextServer.start(); + } + + /** + * Creates a {@code Resource} and verifies that it produces a {@code ChannelResource} + * instance by using its {@code toString()} method. + */ + @Test + public void getChannelResource_success() { + Resource resource = + S2AHandshakerServiceChannel.getChannelResource( + "localhost:" + plaintextServer.getPort(), + /* s2aChannelCredentials= */ Optional.empty()); + assertThat(resource.toString()).isEqualTo("grpc-s2a-channel"); + } + + /** Same as getChannelResource_success, but use mTLS. */ + @Test + public void getChannelResource_mtlsSuccess() throws Exception { + Resource resource = + S2AHandshakerServiceChannel.getChannelResource( + "localhost:" + mtlsServer.getPort(), getTlsChannelCredentials()); + assertThat(resource.toString()).isEqualTo("grpc-s2a-channel"); + } + + /** + * Creates two {@code Resoure}s for the same target address and verifies that they are + * equal. + */ + @Test + public void getChannelResource_twoEqualChannels() { + Resource resource = + S2AHandshakerServiceChannel.getChannelResource( + "localhost:" + plaintextServer.getPort(), + /* s2aChannelCredentials= */ Optional.empty()); + Resource resourceTwo = + S2AHandshakerServiceChannel.getChannelResource( + "localhost:" + plaintextServer.getPort(), + /* s2aChannelCredentials= */ Optional.empty()); + assertThat(resource).isEqualTo(resourceTwo); + } + + /** Same as getChannelResource_twoEqualChannels, but use mTLS. */ + @Test + public void getChannelResource_mtlsTwoEqualChannels() throws Exception { + Resource resource = + S2AHandshakerServiceChannel.getChannelResource( + "localhost:" + mtlsServer.getPort(), getTlsChannelCredentials()); + Resource resourceTwo = + S2AHandshakerServiceChannel.getChannelResource( + "localhost:" + mtlsServer.getPort(), getTlsChannelCredentials()); + assertThat(resource).isEqualTo(resourceTwo); + } + + /** + * Creates two {@code Resoure}s for different target addresses and verifies that they are + * distinct. + */ + @Test + public void getChannelResource_twoDistinctChannels() { + Resource resource = + S2AHandshakerServiceChannel.getChannelResource( + "localhost:" + plaintextServer.getPort(), + /* s2aChannelCredentials= */ Optional.empty()); + Resource resourceTwo = + S2AHandshakerServiceChannel.getChannelResource( + "localhost:" + Utils.pickUnusedPort(), /* s2aChannelCredentials= */ Optional.empty()); + assertThat(resourceTwo).isNotEqualTo(resource); + } + + /** Same as getChannelResource_twoDistinctChannels, but use mTLS. */ + @Test + public void getChannelResource_mtlsTwoDistinctChannels() throws Exception { + Resource resource = + S2AHandshakerServiceChannel.getChannelResource( + "localhost:" + mtlsServer.getPort(), getTlsChannelCredentials()); + Resource resourceTwo = + S2AHandshakerServiceChannel.getChannelResource( + "localhost:" + Utils.pickUnusedPort(), getTlsChannelCredentials()); + assertThat(resourceTwo).isNotEqualTo(resource); + } + + /** + * Uses a {@code Resource} to create a channel, closes the channel, and verifies that the + * channel is closed by attempting to make a simple RPC. + */ + @Test + public void close_success() { + Resource resource = + S2AHandshakerServiceChannel.getChannelResource( + "localhost:" + plaintextServer.getPort(), + /* s2aChannelCredentials= */ Optional.empty()); + Channel channel = resource.create(); + resource.close(channel); + StatusRuntimeException expected = + assertThrows( + StatusRuntimeException.class, + () -> + SimpleServiceGrpc.newBlockingStub(channel) + .unaryRpc(SimpleRequest.getDefaultInstance())); + assertThat(expected).hasMessageThat().isEqualTo("UNAVAILABLE: Channel shutdown invoked"); + } + + /** Same as close_success, but use mTLS. */ + @Test + public void close_mtlsSuccess() throws Exception { + Resource resource = + S2AHandshakerServiceChannel.getChannelResource( + "localhost:" + mtlsServer.getPort(), getTlsChannelCredentials()); + Channel channel = resource.create(); + resource.close(channel); + StatusRuntimeException expected = + assertThrows( + StatusRuntimeException.class, + () -> + SimpleServiceGrpc.newBlockingStub(channel) + .unaryRpc(SimpleRequest.getDefaultInstance())); + assertThat(expected).hasMessageThat().isEqualTo("UNAVAILABLE: Channel shutdown invoked"); + } + + /** + * Verifies that an {@code EventLoopHoldingChannel}'s {@code newCall} method can be used to + * perform a simple RPC. + */ + @Test + public void newCall_performSimpleRpcSuccess() { + Resource resource = + S2AHandshakerServiceChannel.getChannelResource( + "localhost:" + plaintextServer.getPort(), + /* s2aChannelCredentials= */ Optional.empty()); + Channel channel = resource.create(); + assertThat(channel).isInstanceOf(EventLoopHoldingChannel.class); + assertThat( + SimpleServiceGrpc.newBlockingStub(channel).unaryRpc(SimpleRequest.getDefaultInstance())) + .isEqualToDefaultInstance(); + } + + /** Same as newCall_performSimpleRpcSuccess, but use mTLS. */ + @Test + public void newCall_mtlsPerformSimpleRpcSuccess() throws Exception { + Resource resource = + S2AHandshakerServiceChannel.getChannelResource( + "localhost:" + mtlsServer.getPort(), getTlsChannelCredentials()); + Channel channel = resource.create(); + assertThat(channel).isInstanceOf(EventLoopHoldingChannel.class); + assertThat( + SimpleServiceGrpc.newBlockingStub(channel).unaryRpc(SimpleRequest.getDefaultInstance())) + .isEqualToDefaultInstance(); + } + + /** Creates a {@code EventLoopHoldingChannel} instance and verifies its authority. */ + @Test + public void authority_success() throws Exception { + ManagedChannel channel = new FakeManagedChannel(true); + EventLoopHoldingChannel eventLoopHoldingChannel = + EventLoopHoldingChannel.create(channel, mockEventLoopGroup); + assertThat(eventLoopHoldingChannel.authority()).isEqualTo("FakeManagedChannel"); + } + + /** + * Creates and closes a {@code EventLoopHoldingChannel} when its {@code ManagedChannel} terminates + * successfully. + */ + @Test + public void close_withDelegateTerminatedSuccess() throws Exception { + ManagedChannel channel = new FakeManagedChannel(true); + EventLoopHoldingChannel eventLoopHoldingChannel = + EventLoopHoldingChannel.create(channel, mockEventLoopGroup); + eventLoopHoldingChannel.close(); + assertThat(channel.isShutdown()).isTrue(); + verify(mockEventLoopGroup, times(1)) + .shutdownGracefully(0, CHANNEL_SHUTDOWN_TIMEOUT.getSeconds(), SECONDS); + } + + /** + * Creates and closes a {@code EventLoopHoldingChannel} when its {@code ManagedChannel} does not + * terminate successfully. + */ + @Test + public void close_withDelegateTerminatedFailure() throws Exception { + ManagedChannel channel = new FakeManagedChannel(false); + EventLoopHoldingChannel eventLoopHoldingChannel = + EventLoopHoldingChannel.create(channel, mockEventLoopGroup); + eventLoopHoldingChannel.close(); + assertThat(channel.isShutdown()).isTrue(); + verify(mockEventLoopGroup, times(1)) + .shutdownGracefully(1, CHANNEL_SHUTDOWN_TIMEOUT.getSeconds(), SECONDS); + } + + /** + * Creates and closes a {@code EventLoopHoldingChannel}, creates a new channel from the same + * resource, and verifies that this second channel is useable. + */ + @Test + public void create_succeedsAfterCloseIsCalledOnce() throws Exception { + Resource resource = + S2AHandshakerServiceChannel.getChannelResource( + "localhost:" + plaintextServer.getPort(), + /* s2aChannelCredentials= */ Optional.empty()); + Channel channelOne = resource.create(); + resource.close(channelOne); + + Channel channelTwo = resource.create(); + assertThat(channelTwo).isInstanceOf(EventLoopHoldingChannel.class); + assertThat( + SimpleServiceGrpc.newBlockingStub(channelTwo) + .unaryRpc(SimpleRequest.getDefaultInstance())) + .isEqualToDefaultInstance(); + resource.close(channelTwo); + } + + /** Same as create_succeedsAfterCloseIsCalledOnce, but use mTLS. */ + @Test + public void create_mtlsSucceedsAfterCloseIsCalledOnce() throws Exception { + Resource resource = + S2AHandshakerServiceChannel.getChannelResource( + "localhost:" + mtlsServer.getPort(), getTlsChannelCredentials()); + Channel channelOne = resource.create(); + resource.close(channelOne); + + Channel channelTwo = resource.create(); + assertThat(channelTwo).isInstanceOf(EventLoopHoldingChannel.class); + assertThat( + SimpleServiceGrpc.newBlockingStub(channelTwo) + .unaryRpc(SimpleRequest.getDefaultInstance())) + .isEqualToDefaultInstance(); + resource.close(channelTwo); + } + + private static Server createMtlsServer() throws Exception { + SimpleServiceImpl service = new SimpleServiceImpl(); + File serverCert = new File("src/test/resources/server_cert.pem"); + File serverKey = new File("src/test/resources/server_key.pem"); + File rootCert = new File("src/test/resources/root_cert.pem"); + ServerCredentials creds = + TlsServerCredentials.newBuilder() + .keyManager(serverCert, serverKey) + .trustManager(rootCert) + .clientAuth(TlsServerCredentials.ClientAuth.REQUIRE) + .build(); + return grpcCleanup.register( + NettyServerBuilder.forPort(Utils.pickUnusedPort(), creds).addService(service).build()); + } + + private static Server createPlaintextServer() { + SimpleServiceImpl service = new SimpleServiceImpl(); + return grpcCleanup.register( + ServerBuilder.forPort(Utils.pickUnusedPort()).addService(service).build()); + } + + private static Optional getTlsChannelCredentials() throws Exception { + File clientCert = new File("src/test/resources/client_cert.pem"); + File clientKey = new File("src/test/resources/client_key.pem"); + File rootCert = new File("src/test/resources/root_cert.pem"); + return Optional.of( + TlsChannelCredentials.newBuilder() + .keyManager(clientCert, clientKey) + .trustManager(rootCert) + .build()); + } + + private static class SimpleServiceImpl extends SimpleServiceGrpc.SimpleServiceImplBase { + @Override + public void unaryRpc(SimpleRequest request, StreamObserver streamObserver) { + streamObserver.onNext(SimpleResponse.getDefaultInstance()); + streamObserver.onCompleted(); + } + } + + private static class FakeManagedChannel extends ManagedChannel { + private final boolean isDelegateTerminatedSuccess; + private boolean isShutdown = false; + + FakeManagedChannel(boolean isDelegateTerminatedSuccess) { + this.isDelegateTerminatedSuccess = isDelegateTerminatedSuccess; + } + + @Override + public String authority() { + return "FakeManagedChannel"; + } + + @Override + public ClientCall newCall( + MethodDescriptor methodDescriptor, CallOptions options) { + throw new UnsupportedOperationException("This method should not be called."); + } + + @Override + public ManagedChannel shutdown() { + throw new UnsupportedOperationException("This method should not be called."); + } + + @Override + public boolean isShutdown() { + return isShutdown; + } + + @Override + public boolean isTerminated() { + throw new UnsupportedOperationException("This method should not be called."); + } + + @Override + public ManagedChannel shutdownNow() { + isShutdown = true; + return null; + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + if (isDelegateTerminatedSuccess) { + return true; + } + throw new InterruptedException("Await termination was interrupted."); + } + } +} diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServer.java b/s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServer.java new file mode 100644 index 00000000000..66f636ada22 --- /dev/null +++ b/s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServer.java @@ -0,0 +1,55 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.handshaker; + +import io.grpc.stub.StreamObserver; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.logging.Logger; + +/** A fake S2Av2 server that should be used for testing only. */ +public final class FakeS2AServer extends S2AServiceGrpc.S2AServiceImplBase { + private static final Logger logger = Logger.getLogger(FakeS2AServer.class.getName()); + + private final FakeWriter writer; + + public FakeS2AServer() throws InvalidKeySpecException, NoSuchAlgorithmException { + this.writer = new FakeWriter(); + this.writer.setVerificationResult(FakeWriter.VerificationResult.SUCCESS).initializePrivateKey(); + } + + @Override + public StreamObserver setUpSession(StreamObserver responseObserver) { + return new StreamObserver() { + @Override + public void onNext(SessionReq req) { + logger.info("Received a request from client."); + responseObserver.onNext(writer.handleResponse(req)); + } + + @Override + public void onError(Throwable t) { + responseObserver.onError(t); + } + + @Override + public void onCompleted() { + responseObserver.onCompleted(); + } + }; + } +} \ No newline at end of file diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServerTest.java b/s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServerTest.java new file mode 100644 index 00000000000..e200d119867 --- /dev/null +++ b/s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServerTest.java @@ -0,0 +1,265 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.handshaker; + +import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; +import static java.util.concurrent.TimeUnit.SECONDS; + +import com.google.common.collect.ImmutableList; +import com.google.protobuf.ByteString; +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; +import io.grpc.ManagedChannel; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.benchmarks.Utils; +import io.grpc.s2a.handshaker.ValidatePeerCertificateChainReq.VerificationMode; +import io.grpc.stub.StreamObserver; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.logging.Logger; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link FakeS2AServer}. */ +@RunWith(JUnit4.class) +public final class FakeS2AServerTest { + private static final Logger logger = Logger.getLogger(FakeS2AServerTest.class.getName()); + + private static final ImmutableList FAKE_CERT_DER_CHAIN = + ImmutableList.of( + ByteString.copyFrom( + new byte[] {'f', 'a', 'k', 'e', '-', 'd', 'e', 'r', '-', 'c', 'h', 'a', 'i', 'n'})); + private int port; + private String serverAddress; + private SessionResp response = null; + private Server fakeS2AServer; + + @Before + public void setUp() throws Exception { + port = Utils.pickUnusedPort(); + fakeS2AServer = ServerBuilder.forPort(port).addService(new FakeS2AServer()).build(); + fakeS2AServer.start(); + serverAddress = String.format("localhost:%d", port); + } + + @After + public void tearDown() { + fakeS2AServer.shutdown(); + } + + @Test + public void callS2AServerOnce_getTlsConfiguration_returnsValidResult() + throws InterruptedException { + ExecutorService executor = Executors.newSingleThreadExecutor(); + logger.info("Client connecting to: " + serverAddress); + ManagedChannel channel = + Grpc.newChannelBuilder(serverAddress, InsecureChannelCredentials.create()) + .executor(executor) + .build(); + + try { + S2AServiceGrpc.S2AServiceStub asyncStub = S2AServiceGrpc.newStub(channel); + StreamObserver requestObserver = + asyncStub.setUpSession( + new StreamObserver() { + @Override + public void onNext(SessionResp resp) { + response = resp; + } + + @Override + public void onError(Throwable t) { + throw new RuntimeException(t); + } + + @Override + public void onCompleted() {} + }); + try { + requestObserver.onNext( + SessionReq.newBuilder() + .setGetTlsConfigurationReq( + GetTlsConfigurationReq.newBuilder() + .setConnectionSide(ConnectionSide.CONNECTION_SIDE_CLIENT)) + .build()); + } catch (RuntimeException e) { + // Cancel the RPC. + requestObserver.onError(e); + throw e; + } + // Mark the end of requests. + requestObserver.onCompleted(); + // Wait for receiving to happen. + } finally { + channel.shutdown(); + channel.awaitTermination(1, SECONDS); + executor.shutdown(); + executor.awaitTermination(1, SECONDS); + } + + SessionResp expected = + SessionResp.newBuilder() + .setGetTlsConfigurationResp( + GetTlsConfigurationResp.newBuilder() + .setClientTlsConfiguration( + GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() + .addCertificateChain(FakeWriter.LEAF_CERT) + .addCertificateChain(FakeWriter.INTERMEDIATE_CERT_2) + .addCertificateChain(FakeWriter.INTERMEDIATE_CERT_1) + .setMinTlsVersion(TLSVersion.TLS_VERSION_1_3) + .setMaxTlsVersion(TLSVersion.TLS_VERSION_1_3) + .addCiphersuites( + Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) + .addCiphersuites( + Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) + .addCiphersuites( + Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256))) + .build(); + assertThat(response).ignoringRepeatedFieldOrder().isEqualTo(expected); + } + + @Test + public void callS2AServerOnce_validatePeerCertifiate_returnsValidResult() + throws InterruptedException { + ExecutorService executor = Executors.newSingleThreadExecutor(); + logger.info("Client connecting to: " + serverAddress); + ManagedChannel channel = + Grpc.newChannelBuilder(serverAddress, InsecureChannelCredentials.create()) + .executor(executor) + .build(); + + try { + S2AServiceGrpc.S2AServiceStub asyncStub = S2AServiceGrpc.newStub(channel); + StreamObserver requestObserver = + asyncStub.setUpSession( + new StreamObserver() { + @Override + public void onNext(SessionResp resp) { + response = resp; + } + + @Override + public void onError(Throwable t) { + throw new RuntimeException(t); + } + + @Override + public void onCompleted() {} + }); + try { + requestObserver.onNext( + SessionReq.newBuilder() + .setValidatePeerCertificateChainReq( + ValidatePeerCertificateChainReq.newBuilder() + .setMode(VerificationMode.UNSPECIFIED) + .setClientPeer( + ValidatePeerCertificateChainReq.ClientPeer.newBuilder() + .addAllCertificateChain(FAKE_CERT_DER_CHAIN))) + .build()); + } catch (RuntimeException e) { + // Cancel the RPC. + requestObserver.onError(e); + throw e; + } + // Mark the end of requests. + requestObserver.onCompleted(); + // Wait for receiving to happen. + } finally { + channel.shutdown(); + channel.awaitTermination(1, SECONDS); + executor.shutdown(); + executor.awaitTermination(1, SECONDS); + } + + SessionResp expected = + SessionResp.newBuilder() + .setValidatePeerCertificateChainResp( + ValidatePeerCertificateChainResp.newBuilder() + .setValidationResult(ValidatePeerCertificateChainResp.ValidationResult.SUCCESS)) + .build(); + assertThat(response).ignoringRepeatedFieldOrder().isEqualTo(expected); + } + + @Test + public void callS2AServerRepeatedly_returnsValidResult() throws InterruptedException { + final int numberOfRequests = 10; + ExecutorService executor = Executors.newSingleThreadExecutor(); + logger.info("Client connecting to: " + serverAddress); + ManagedChannel channel = + Grpc.newChannelBuilder(serverAddress, InsecureChannelCredentials.create()) + .executor(executor) + .build(); + + try { + S2AServiceGrpc.S2AServiceStub asyncStub = S2AServiceGrpc.newStub(channel); + CountDownLatch finishLatch = new CountDownLatch(1); + StreamObserver requestObserver = + asyncStub.setUpSession( + new StreamObserver() { + private int expectedNumberOfReplies = numberOfRequests; + + @Override + public void onNext(SessionResp reply) { + System.out.println("Received a message from the S2AService service."); + expectedNumberOfReplies -= 1; + } + + @Override + public void onError(Throwable t) { + finishLatch.countDown(); + if (expectedNumberOfReplies != 0) { + throw new RuntimeException(t); + } + } + + @Override + public void onCompleted() { + finishLatch.countDown(); + if (expectedNumberOfReplies != 0) { + throw new RuntimeException(); + } + } + }); + try { + for (int i = 0; i < numberOfRequests; i++) { + requestObserver.onNext(SessionReq.getDefaultInstance()); + } + } catch (RuntimeException e) { + // Cancel the RPC. + requestObserver.onError(e); + throw e; + } + // Mark the end of requests. + requestObserver.onCompleted(); + // Wait for receiving to happen. + if (!finishLatch.await(10, SECONDS)) { + throw new RuntimeException(); + } + } finally { + channel.shutdown(); + channel.awaitTermination(1, SECONDS); + executor.shutdown(); + executor.awaitTermination(1, SECONDS); + } + } + +} \ No newline at end of file diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/FakeWriter.java b/s2a/src/test/java/io/grpc/s2a/handshaker/FakeWriter.java new file mode 100644 index 00000000000..45961b81b7b --- /dev/null +++ b/s2a/src/test/java/io/grpc/s2a/handshaker/FakeWriter.java @@ -0,0 +1,363 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.handshaker; + +import static io.grpc.s2a.handshaker.TLSVersion.TLS_VERSION_1_2; +import static io.grpc.s2a.handshaker.TLSVersion.TLS_VERSION_1_3; + +import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.protobuf.ByteString; +import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.Signature; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; + +/** A fake Writer Class to mock the behavior of S2A server. */ +final class FakeWriter implements StreamObserver { + /** Fake behavior of S2A service. */ + enum Behavior { + OK_STATUS, + EMPTY_RESPONSE, + ERROR_STATUS, + ERROR_RESPONSE, + COMPLETE_STATUS, + BAD_TLS_VERSION_RESPONSE, + } + + enum VerificationResult { + UNSPECIFIED, + SUCCESS, + FAILURE + } + + public static final String LEAF_CERT = + "-----BEGIN CERTIFICATE-----\n" + + "MIICkDCCAjagAwIBAgIUSAtcrPhNNs1zxv51lIfGOVtkw6QwCgYIKoZIzj0EAwIw\n" + + "QTEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xEDAOBgNVBAsMB2NvbnRleHQxFDAS\n" + + "BgorBgEEAdZ5AggBDAQyMDIyMCAXDTIzMDcxNDIyMzYwNFoYDzIwNTAxMTI5MjIz\n" + + "NjA0WjARMQ8wDQYDVQQDDAZ1bnVzZWQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC\n" + + "AAQGFlJpLxJMh4HuUm0DKjnUF7larH3tJvroQ12xpk+pPKQepn4ILoq9lZ8Xd3jz\n" + + "U98eDRXG5f4VjnX98DDHE4Ido4IBODCCATQwDgYDVR0PAQH/BAQDAgeAMCAGA1Ud\n" + + "JQEB/wQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMIGxBgNV\n" + + "HREBAf8EgaYwgaOGSnNwaWZmZTovL3NpZ25lci1yb2xlLmNvbnRleHQuc2VjdXJp\n" + + "dHktcmVhbG0ucHJvZC5nb29nbGUuY29tL3JvbGUvbGVhZi1yb2xlgjNzaWduZXIt\n" + + "cm9sZS5jb250ZXh0LnNlY3VyaXR5LXJlYWxtLnByb2Quc3BpZmZlLmdvb2eCIGZx\n" + + "ZG4tb2YtdGhlLW5vZGUucHJvZC5nb29nbGUuY29tMB0GA1UdDgQWBBSWSd5Fw6dI\n" + + "TGpt0m1Uxwf0iKqebzAfBgNVHSMEGDAWgBRm5agVVdpWfRZKM7u6OMuzHhqPcDAK\n" + + "BggqhkjOPQQDAgNIADBFAiB0sjRPSYy2eFq8Y0vQ8QN4AZ2NMajskvxnlifu7O4U\n" + + "RwIhANTh5Fkyx2nMYFfyl+W45dY8ODTw3HnlZ4b51hTAdkWl\n" + + "-----END CERTIFICATE-----"; + public static final String INTERMEDIATE_CERT_2 = + "-----BEGIN CERTIFICATE-----\n" + + "MIICQjCCAeigAwIBAgIUKxXRDlnWXefNV5lj5CwhDuXEq7MwCgYIKoZIzj0EAwIw\n" + + "OzEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xEDAOBgNVBAsMB2NvbnRleHQxDjAM\n" + + "BgNVBAMMBTEyMzQ1MCAXDTIzMDcxNDIyMzYwNFoYDzIwNTAxMTI5MjIzNjA0WjBB\n" + + "MRcwFQYDVQQKDA5zZWN1cml0eS1yZWFsbTEQMA4GA1UECwwHY29udGV4dDEUMBIG\n" + + "CisGAQQB1nkCCAEMBDIwMjIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT/Zu7x\n" + + "UYVyg+T/vg2H+y4I6t36Kc4qxD0eqqZjRLYBVKkUQHxBqc14t0DpoROMYQCNd4DF\n" + + "pcxv/9m6DaJbRk6Ao4HBMIG+MA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG\n" + + "AQH/AgEBMFgGA1UdHgEB/wROMEygSjA1gjNzaWduZXItcm9sZS5jb250ZXh0LnNl\n" + + "Y3VyaXR5LXJlYWxtLnByb2Quc3BpZmZlLmdvb2cwEYIPcHJvZC5nb29nbGUuY29t\n" + + "MB0GA1UdDgQWBBRm5agVVdpWfRZKM7u6OMuzHhqPcDAfBgNVHSMEGDAWgBQcjNAh\n" + + "SCHTj+BW8KrzSSLo2ASEgjAKBggqhkjOPQQDAgNIADBFAiEA6KyGd9VxXDZceMZG\n" + + "IsbC40rtunFjLYI0mjZw9RcRWx8CIHCIiIHxafnDaCi+VB99NZfzAdu37g6pJptB\n" + + "gjIY71MO\n" + + "-----END CERTIFICATE-----"; + public static final String INTERMEDIATE_CERT_1 = + "-----BEGIN CERTIFICATE-----\n" + + "MIICODCCAd6gAwIBAgIUXtZECORWRSKnS9rRTJYkiALUXswwCgYIKoZIzj0EAwIw\n" + + "NzEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xDTALBgNVBAsMBHJvb3QxDTALBgNV\n" + + "BAMMBDEyMzQwIBcNMjMwNzE0MjIzNjA0WhgPMjA1MDExMjkyMjM2MDRaMDsxFzAV\n" + + "BgNVBAoMDnNlY3VyaXR5LXJlYWxtMRAwDgYDVQQLDAdjb250ZXh0MQ4wDAYDVQQD\n" + + "DAUxMjM0NTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAycVTZrjockbpD59f1a\n" + + "4l1SNL7nSyXz66Guz4eDveQqLmaMBg7vpACfO4CtiAGnolHEffuRtSkdM434m5En\n" + + "bXCjgcEwgb4wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQIwWAYD\n" + + "VR0eAQH/BE4wTKBKMDWCM3NpZ25lci1yb2xlLmNvbnRleHQuc2VjdXJpdHktcmVh\n" + + "bG0ucHJvZC5zcGlmZmUuZ29vZzARgg9wcm9kLmdvb2dsZS5jb20wHQYDVR0OBBYE\n" + + "FByM0CFIIdOP4FbwqvNJIujYBISCMB8GA1UdIwQYMBaAFMX+vebuj/lYfYEC23IA\n" + + "8HoIW0HsMAoGCCqGSM49BAMCA0gAMEUCIQCfxeXEBd7UPmeImT16SseCRu/6cHxl\n" + + "kTDsq9sKZ+eXBAIgA+oViAVOUhUQO1/6Mjlczg8NmMy2vNtG4V/7g9dMMVU=\n" + + "-----END CERTIFICATE-----"; + + private static final String PRIVATE_KEY = + "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgqA2U0ld1OOHLMXWf" + + "uyN4GSaqhhudEIaKkll3rdIq0M+hRANCAAQGFlJpLxJMh4HuUm0DKjnUF7larH3t" + + "JvroQ12xpk+pPKQepn4ILoq9lZ8Xd3jzU98eDRXG5f4VjnX98DDHE4Id"; + private static final ImmutableMap + ALGORITHM_TO_SIGNATURE_INSTANCE_IDENTIFIER = + ImmutableMap.of( + SignatureAlgorithm.S2A_SSL_SIGN_ECDSA_SECP256R1_SHA256, + "SHA256withECDSA", + SignatureAlgorithm.S2A_SSL_SIGN_ECDSA_SECP384R1_SHA384, + "SHA384withECDSA", + SignatureAlgorithm.S2A_SSL_SIGN_ECDSA_SECP521R1_SHA512, + "SHA512withECDSA"); + + private boolean fakeWriterClosed = false; + private Behavior behavior = Behavior.OK_STATUS; + private StreamObserver reader; + private VerificationResult verificationResult = VerificationResult.UNSPECIFIED; + private String failureReason; + private PrivateKey privateKey; + + @CanIgnoreReturnValue + FakeWriter setReader(StreamObserver reader) { + this.reader = reader; + return this; + } + + @CanIgnoreReturnValue + FakeWriter setBehavior(Behavior behavior) { + this.behavior = behavior; + return this; + } + + @CanIgnoreReturnValue + FakeWriter setVerificationResult(VerificationResult verificationResult) { + this.verificationResult = verificationResult; + return this; + } + + @CanIgnoreReturnValue + FakeWriter setFailureReason(String failureReason) { + this.failureReason = failureReason; + return this; + } + + @CanIgnoreReturnValue + FakeWriter initializePrivateKey() throws InvalidKeySpecException, NoSuchAlgorithmException { + privateKey = + KeyFactory.getInstance("EC") + .generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(PRIVATE_KEY))); + return this; + } + + @CanIgnoreReturnValue + FakeWriter resetPrivateKey() { + privateKey = null; + return this; + } + + void sendUnexpectedResponse() { + reader.onNext(SessionResp.getDefaultInstance()); + } + + void sendIoError() { + reader.onError(new IOException("Intended ERROR from FakeWriter.")); + } + + void sendGetTlsConfigResp() { + reader.onNext( + SessionResp.newBuilder() + .setGetTlsConfigurationResp( + GetTlsConfigurationResp.newBuilder() + .setClientTlsConfiguration( + GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() + .addCertificateChain(LEAF_CERT) + .addCertificateChain(INTERMEDIATE_CERT_2) + .addCertificateChain(INTERMEDIATE_CERT_1) + .setMinTlsVersion(TLS_VERSION_1_3) + .setMaxTlsVersion(TLS_VERSION_1_3) + .addCiphersuites( + Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) + .addCiphersuites( + Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) + .addCiphersuites( + Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256))) + .build()); + } + + boolean isFakeWriterClosed() { + return fakeWriterClosed; + } + + @Override + public void onNext(SessionReq sessionReq) { + switch (behavior) { + case OK_STATUS: + reader.onNext(handleResponse(sessionReq)); + break; + case EMPTY_RESPONSE: + reader.onNext(SessionResp.getDefaultInstance()); + break; + case ERROR_STATUS: + reader.onNext( + SessionResp.newBuilder() + .setStatus( + Status.newBuilder() + .setCode(1) + .setDetails("Intended ERROR Status from FakeWriter.")) + .build()); + break; + case ERROR_RESPONSE: + reader.onError(new S2AConnectionException("Intended ERROR from FakeWriter.")); + break; + case COMPLETE_STATUS: + reader.onCompleted(); + break; + case BAD_TLS_VERSION_RESPONSE: + reader.onNext( + SessionResp.newBuilder() + .setGetTlsConfigurationResp( + GetTlsConfigurationResp.newBuilder() + .setClientTlsConfiguration( + GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() + .addCertificateChain(LEAF_CERT) + .addCertificateChain(INTERMEDIATE_CERT_2) + .addCertificateChain(INTERMEDIATE_CERT_1) + .setMinTlsVersion(TLS_VERSION_1_3) + .setMaxTlsVersion(TLS_VERSION_1_2))) + .build()); + break; + default: + reader.onNext(handleResponse(sessionReq)); + } + } + + SessionResp handleResponse(SessionReq sessionReq) { + if (sessionReq.hasGetTlsConfigurationReq()) { + return handleGetTlsConfigurationReq(sessionReq.getGetTlsConfigurationReq()); + } + + if (sessionReq.hasValidatePeerCertificateChainReq()) { + return handleValidatePeerCertificateChainReq(sessionReq.getValidatePeerCertificateChainReq()); + } + + if (sessionReq.hasOffloadPrivateKeyOperationReq()) { + return handleOffloadPrivateKeyOperationReq(sessionReq.getOffloadPrivateKeyOperationReq()); + } + + return SessionResp.newBuilder() + .setStatus( + Status.newBuilder().setCode(255).setDetails("No supported operation designated.")) + .build(); + } + + private SessionResp handleGetTlsConfigurationReq(GetTlsConfigurationReq req) { + if (!req.getConnectionSide().equals(ConnectionSide.CONNECTION_SIDE_CLIENT)) { + return SessionResp.newBuilder() + .setStatus( + Status.newBuilder() + .setCode(255) + .setDetails("No TLS configuration for the server side.")) + .build(); + } + return SessionResp.newBuilder() + .setGetTlsConfigurationResp( + GetTlsConfigurationResp.newBuilder() + .setClientTlsConfiguration( + GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() + .addCertificateChain(LEAF_CERT) + .addCertificateChain(INTERMEDIATE_CERT_2) + .addCertificateChain(INTERMEDIATE_CERT_1) + .setMinTlsVersion(TLS_VERSION_1_3) + .setMaxTlsVersion(TLS_VERSION_1_3) + .addCiphersuites( + Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) + .addCiphersuites( + Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) + .addCiphersuites( + Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256))) + .build(); + } + + private SessionResp handleValidatePeerCertificateChainReq(ValidatePeerCertificateChainReq req) { + if (verifyValidatePeerCertificateChainReq(req) + && verificationResult == VerificationResult.SUCCESS) { + return SessionResp.newBuilder() + .setValidatePeerCertificateChainResp( + ValidatePeerCertificateChainResp.newBuilder() + .setValidationResult(ValidatePeerCertificateChainResp.ValidationResult.SUCCESS)) + .build(); + } + return SessionResp.newBuilder() + .setValidatePeerCertificateChainResp( + ValidatePeerCertificateChainResp.newBuilder() + .setValidationResult( + verificationResult == VerificationResult.FAILURE + ? ValidatePeerCertificateChainResp.ValidationResult.FAILURE + : ValidatePeerCertificateChainResp.ValidationResult.UNSPECIFIED) + .setValidationDetails(failureReason)) + .build(); + } + + private boolean verifyValidatePeerCertificateChainReq(ValidatePeerCertificateChainReq req) { + if (req.getMode() != ValidatePeerCertificateChainReq.VerificationMode.UNSPECIFIED) { + return false; + } + if (req.getClientPeer().getCertificateChainCount() > 0) { + return true; + } + if (req.getServerPeer().getCertificateChainCount() > 0 + && !req.getServerPeer().getServerHostname().isEmpty()) { + return true; + } + return false; + } + + private SessionResp handleOffloadPrivateKeyOperationReq(OffloadPrivateKeyOperationReq req) { + if (privateKey == null) { + return SessionResp.newBuilder() + .setStatus(Status.newBuilder().setCode(255).setDetails("No Private Key available.")) + .build(); + } + String signatureIdentifier = + ALGORITHM_TO_SIGNATURE_INSTANCE_IDENTIFIER.get(req.getSignatureAlgorithm()); + if (signatureIdentifier == null) { + return SessionResp.newBuilder() + .setStatus( + Status.newBuilder() + .setCode(255) + .setDetails("Only ECDSA key algorithms are supported.")) + .build(); + } + + byte[] signature; + try { + Signature sig = Signature.getInstance(signatureIdentifier); + sig.initSign(privateKey); + sig.update(req.getRawBytes().toByteArray()); + signature = sig.sign(); + } catch (Exception e) { + return SessionResp.newBuilder() + .setStatus(Status.newBuilder().setCode(255).setDetails(e.getMessage())) + .build(); + } + + return SessionResp.newBuilder() + .setOffloadPrivateKeyOperationResp( + OffloadPrivateKeyOperationResp.newBuilder().setOutBytes(ByteString.copyFrom(signature))) + .build(); + } + + @Override + public void onError(Throwable t) { + throw new UnsupportedOperationException("onError is not supported by FakeWriter."); + } + + @Override + public void onCompleted() { + fakeWriterClosed = true; + reader.onCompleted(); + } +} \ No newline at end of file diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/GetAuthenticationMechanismsTest.java b/s2a/src/test/java/io/grpc/s2a/handshaker/GetAuthenticationMechanismsTest.java new file mode 100644 index 00000000000..884e1ec88eb --- /dev/null +++ b/s2a/src/test/java/io/grpc/s2a/handshaker/GetAuthenticationMechanismsTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.handshaker; + +import com.google.common.truth.Expect; +import io.grpc.s2a.handshaker.S2AIdentity; +import io.grpc.s2a.handshaker.tokenmanager.SingleTokenFetcher; +import java.util.Optional; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link GetAuthenticationMechanisms}. */ +@RunWith(JUnit4.class) +public final class GetAuthenticationMechanismsTest { + @Rule public final Expect expect = Expect.create(); + private static final String TOKEN = "access_token"; + + @BeforeClass + public static void setUpClass() { + // Set the token that the client will use to authenticate to the S2A. + SingleTokenFetcher.setAccessToken(TOKEN); + } + + @Test + public void getAuthMechanisms_emptyIdentity_success() { + expect + .that(GetAuthenticationMechanisms.getAuthMechanism(Optional.empty())) + .isEqualTo( + Optional.of(AuthenticationMechanism.newBuilder().setToken("access_token").build())); + } + + @Test + public void getAuthMechanisms_nonEmptyIdentity_success() { + S2AIdentity fakeIdentity = S2AIdentity.fromSpiffeId("fake-spiffe-id"); + expect + .that(GetAuthenticationMechanisms.getAuthMechanism(Optional.of(fakeIdentity))) + .isEqualTo( + Optional.of( + AuthenticationMechanism.newBuilder() + .setIdentity(fakeIdentity.getIdentity()) + .setToken("access_token") + .build())); + } +} \ No newline at end of file diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java b/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java new file mode 100644 index 00000000000..859771a4afa --- /dev/null +++ b/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java @@ -0,0 +1,320 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.handshaker; + +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.concurrent.TimeUnit.SECONDS; + +import io.grpc.ChannelCredentials; +import io.grpc.Grpc; +import io.grpc.ManagedChannel; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.ServerCredentials; +import io.grpc.TlsServerCredentials; +import io.grpc.benchmarks.Utils; +import io.grpc.netty.GrpcSslContexts; +import io.grpc.netty.NettyServerBuilder; +import io.grpc.s2a.MtlsToS2AChannelCredentials; +import io.grpc.s2a.S2AChannelCredentials; +import io.grpc.s2a.handshaker.FakeS2AServer; +import io.grpc.stub.StreamObserver; +import io.grpc.testing.protobuf.SimpleRequest; +import io.grpc.testing.protobuf.SimpleResponse; +import io.grpc.testing.protobuf.SimpleServiceGrpc; +import io.netty.handler.ssl.ClientAuth; +import io.netty.handler.ssl.OpenSslSessionContext; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.SslProvider; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSessionContext; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class IntegrationTest { + private static final Logger logger = Logger.getLogger(FakeS2AServer.class.getName()); + + private static final String CERT_CHAIN = + "-----BEGIN CERTIFICATE-----\n" + + "MIICkDCCAjagAwIBAgIUSAtcrPhNNs1zxv51lIfGOVtkw6QwCgYIKoZIzj0EAwIw\n" + + "QTEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xEDAOBgNVBAsMB2NvbnRleHQxFDAS\n" + + "BgorBgEEAdZ5AggBDAQyMDIyMCAXDTIzMDcxNDIyMzYwNFoYDzIwNTAxMTI5MjIz\n" + + "NjA0WjARMQ8wDQYDVQQDDAZ1bnVzZWQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC\n" + + "AAQGFlJpLxJMh4HuUm0DKjnUF7larH3tJvroQ12xpk+pPKQepn4ILoq9lZ8Xd3jz\n" + + "U98eDRXG5f4VjnX98DDHE4Ido4IBODCCATQwDgYDVR0PAQH/BAQDAgeAMCAGA1Ud\n" + + "JQEB/wQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMIGxBgNV\n" + + "HREBAf8EgaYwgaOGSnNwaWZmZTovL3NpZ25lci1yb2xlLmNvbnRleHQuc2VjdXJp\n" + + "dHktcmVhbG0ucHJvZC5nb29nbGUuY29tL3JvbGUvbGVhZi1yb2xlgjNzaWduZXIt\n" + + "cm9sZS5jb250ZXh0LnNlY3VyaXR5LXJlYWxtLnByb2Quc3BpZmZlLmdvb2eCIGZx\n" + + "ZG4tb2YtdGhlLW5vZGUucHJvZC5nb29nbGUuY29tMB0GA1UdDgQWBBSWSd5Fw6dI\n" + + "TGpt0m1Uxwf0iKqebzAfBgNVHSMEGDAWgBRm5agVVdpWfRZKM7u6OMuzHhqPcDAK\n" + + "BggqhkjOPQQDAgNIADBFAiB0sjRPSYy2eFq8Y0vQ8QN4AZ2NMajskvxnlifu7O4U\n" + + "RwIhANTh5Fkyx2nMYFfyl+W45dY8ODTw3HnlZ4b51hTAdkWl\n" + + "-----END CERTIFICATE-----\n" + + "-----BEGIN CERTIFICATE-----\n" + + "MIICQjCCAeigAwIBAgIUKxXRDlnWXefNV5lj5CwhDuXEq7MwCgYIKoZIzj0EAwIw\n" + + "OzEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xEDAOBgNVBAsMB2NvbnRleHQxDjAM\n" + + "BgNVBAMMBTEyMzQ1MCAXDTIzMDcxNDIyMzYwNFoYDzIwNTAxMTI5MjIzNjA0WjBB\n" + + "MRcwFQYDVQQKDA5zZWN1cml0eS1yZWFsbTEQMA4GA1UECwwHY29udGV4dDEUMBIG\n" + + "CisGAQQB1nkCCAEMBDIwMjIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT/Zu7x\n" + + "UYVyg+T/vg2H+y4I6t36Kc4qxD0eqqZjRLYBVKkUQHxBqc14t0DpoROMYQCNd4DF\n" + + "pcxv/9m6DaJbRk6Ao4HBMIG+MA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG\n" + + "AQH/AgEBMFgGA1UdHgEB/wROMEygSjA1gjNzaWduZXItcm9sZS5jb250ZXh0LnNl\n" + + "Y3VyaXR5LXJlYWxtLnByb2Quc3BpZmZlLmdvb2cwEYIPcHJvZC5nb29nbGUuY29t\n" + + "MB0GA1UdDgQWBBRm5agVVdpWfRZKM7u6OMuzHhqPcDAfBgNVHSMEGDAWgBQcjNAh\n" + + "SCHTj+BW8KrzSSLo2ASEgjAKBggqhkjOPQQDAgNIADBFAiEA6KyGd9VxXDZceMZG\n" + + "IsbC40rtunFjLYI0mjZw9RcRWx8CIHCIiIHxafnDaCi+VB99NZfzAdu37g6pJptB\n" + + "gjIY71MO\n" + + "-----END CERTIFICATE-----\n" + + "-----BEGIN CERTIFICATE-----\n" + + "MIICODCCAd6gAwIBAgIUXtZECORWRSKnS9rRTJYkiALUXswwCgYIKoZIzj0EAwIw\n" + + "NzEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xDTALBgNVBAsMBHJvb3QxDTALBgNV\n" + + "BAMMBDEyMzQwIBcNMjMwNzE0MjIzNjA0WhgPMjA1MDExMjkyMjM2MDRaMDsxFzAV\n" + + "BgNVBAoMDnNlY3VyaXR5LXJlYWxtMRAwDgYDVQQLDAdjb250ZXh0MQ4wDAYDVQQD\n" + + "DAUxMjM0NTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAycVTZrjockbpD59f1a\n" + + "4l1SNL7nSyXz66Guz4eDveQqLmaMBg7vpACfO4CtiAGnolHEffuRtSkdM434m5En\n" + + "bXCjgcEwgb4wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQIwWAYD\n" + + "VR0eAQH/BE4wTKBKMDWCM3NpZ25lci1yb2xlLmNvbnRleHQuc2VjdXJpdHktcmVh\n" + + "bG0ucHJvZC5zcGlmZmUuZ29vZzARgg9wcm9kLmdvb2dsZS5jb20wHQYDVR0OBBYE\n" + + "FByM0CFIIdOP4FbwqvNJIujYBISCMB8GA1UdIwQYMBaAFMX+vebuj/lYfYEC23IA\n" + + "8HoIW0HsMAoGCCqGSM49BAMCA0gAMEUCIQCfxeXEBd7UPmeImT16SseCRu/6cHxl\n" + + "kTDsq9sKZ+eXBAIgA+oViAVOUhUQO1/6Mjlczg8NmMy2vNtG4V/7g9dMMVU=\n" + + "-----END CERTIFICATE-----"; + private static final String ROOT_PEM = + "-----BEGIN CERTIFICATE-----\n" + + "MIIBtTCCAVqgAwIBAgIUbAe+8OocndQXRBCElLBxBSdfdV8wCgYIKoZIzj0EAwIw\n" + + "NzEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xDTALBgNVBAsMBHJvb3QxDTALBgNV\n" + + "BAMMBDEyMzQwIBcNMjMwNzE0MjIzNjA0WhgPMjA1MDExMjkyMjM2MDRaMDcxFzAV\n" + + "BgNVBAoMDnNlY3VyaXR5LXJlYWxtMQ0wCwYDVQQLDARyb290MQ0wCwYDVQQDDAQx\n" + + "MjM0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaMY2tBW5r1t0+vhayz0ZoGMF\n" + + "boX/ZmmCmIh0iTWg4madvwNOh74CMVVvDUlXZcuVqZ3vVIX/a7PTFVqUwQlKW6NC\n" + + "MEAwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMX+\n" + + "vebuj/lYfYEC23IA8HoIW0HsMAoGCCqGSM49BAMCA0kAMEYCIQDETd27nsUTXKWY\n" + + "CiOno78O09gK95NoTkPU5e2chJYMqAIhALYFAyh7PU5xgFQsN9hiqgsHUc5/pmBG\n" + + "BGjJ1iz8rWGJ\n" + + "-----END CERTIFICATE-----"; + private static final String PRIVATE_KEY = + "-----BEGIN PRIVATE KEY-----\n" + + "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgqA2U0ld1OOHLMXWf\n" + + "uyN4GSaqhhudEIaKkll3rdIq0M+hRANCAAQGFlJpLxJMh4HuUm0DKjnUF7larH3t\n" + + "JvroQ12xpk+pPKQepn4ILoq9lZ8Xd3jzU98eDRXG5f4VjnX98DDHE4Id\n" + + "-----END PRIVATE KEY-----"; + + private String s2aAddress; + private int s2aPort; + private Server s2aServer; + private String s2aDelayAddress; + private int s2aDelayPort; + private Server s2aDelayServer; + private String mtlsS2AAddress; + private int mtlsS2APort; + private Server mtlsS2AServer; + private int serverPort; + private String serverAddress; + private Server server; + + @Before + public void setUp() throws Exception { + s2aPort = Utils.pickUnusedPort(); + s2aAddress = "localhost:" + s2aPort; + s2aServer = ServerBuilder.forPort(s2aPort).addService(new FakeS2AServer()).build(); + logger.info("S2A service listening on localhost:" + s2aPort); + s2aServer.start(); + + mtlsS2APort = Utils.pickUnusedPort(); + mtlsS2AAddress = "localhost:" + mtlsS2APort; + File s2aCert = new File("src/test/resources/server_cert.pem"); + File s2aKey = new File("src/test/resources/server_key.pem"); + File rootCert = new File("src/test/resources/root_cert.pem"); + ServerCredentials s2aCreds = + TlsServerCredentials.newBuilder() + .keyManager(s2aCert, s2aKey) + .trustManager(rootCert) + .clientAuth(TlsServerCredentials.ClientAuth.REQUIRE) + .build(); + mtlsS2AServer = + NettyServerBuilder.forPort(mtlsS2APort, s2aCreds).addService(new FakeS2AServer()).build(); + logger.info("mTLS S2A service listening on localhost:" + mtlsS2APort); + mtlsS2AServer.start(); + + s2aDelayPort = Utils.pickUnusedPort(); + s2aDelayAddress = "localhost:" + s2aDelayPort; + s2aDelayServer = ServerBuilder.forPort(s2aDelayPort).addService(new FakeS2AServer()).build(); + + serverPort = Utils.pickUnusedPort(); + serverAddress = "localhost:" + serverPort; + server = + NettyServerBuilder.forPort(serverPort) + .addService(new SimpleServiceImpl()) + .sslContext(buildSslContext()) + .build(); + logger.info("Simple Service listening on localhost:" + serverPort); + server.start(); + } + + @After + public void tearDown() throws Exception { + server.awaitTermination(10, SECONDS); + server.shutdown(); + s2aServer.awaitTermination(10, SECONDS); + s2aServer.shutdown(); + s2aDelayServer.awaitTermination(10, SECONDS); + s2aDelayServer.shutdown(); + mtlsS2AServer.awaitTermination(10, SECONDS); + mtlsS2AServer.shutdown(); + } + + @Test + public void clientCommunicateUsingS2ACredentials_succeeds() throws Exception { + ExecutorService executor = Executors.newSingleThreadExecutor(); + ChannelCredentials credentials = + S2AChannelCredentials.createBuilder(s2aAddress).setLocalSpiffeId("test-spiffe-id").build(); + ManagedChannel channel = + Grpc.newChannelBuilder(serverAddress, credentials).executor(executor).build(); + + assertThat(doUnaryRpc(executor, channel)).isTrue(); + } + + @Test + public void clientCommunicateUsingS2ACredentialsNoLocalIdentity_succeeds() throws Exception { + ExecutorService executor = Executors.newSingleThreadExecutor(); + ChannelCredentials credentials = S2AChannelCredentials.createBuilder(s2aAddress).build(); + ManagedChannel channel = + Grpc.newChannelBuilder(serverAddress, credentials).executor(executor).build(); + + assertThat(doUnaryRpc(executor, channel)).isTrue(); + } + + @Test + public void clientCommunicateUsingMtlsToS2ACredentials_succeeds() throws Exception { + ExecutorService executor = Executors.newSingleThreadExecutor(); + ChannelCredentials credentials = + MtlsToS2AChannelCredentials.createBuilder( + /* s2aAddress= */ mtlsS2AAddress, + /* privateKeyPath= */ "src/test/resources/client_key.pem", + /* certChainPath= */ "src/test/resources/client_cert.pem", + /* trustBundlePath= */ "src/test/resources/root_cert.pem") + .build() + .setLocalSpiffeId("test-spiffe-id") + .build(); + ManagedChannel channel = + Grpc.newChannelBuilder(serverAddress, credentials).executor(executor).build(); + + assertThat(doUnaryRpc(executor, channel)).isTrue(); + } + + @Test + public void clientCommunicateUsingS2ACredentials_s2AdelayStart_succeeds() throws Exception { + DoUnaryRpc doUnaryRpc = new DoUnaryRpc(); + doUnaryRpc.start(); + Thread.sleep(2000); + s2aDelayServer.start(); + doUnaryRpc.join(); + } + + private class DoUnaryRpc extends Thread { + @Override + public void run() { + ExecutorService executor = Executors.newSingleThreadExecutor(); + ChannelCredentials credentials = S2AChannelCredentials.createBuilder(s2aDelayAddress).build(); + ManagedChannel channel = + Grpc.newChannelBuilder(serverAddress, credentials).executor(executor).build(); + boolean result = false; + try { + result = doUnaryRpc(executor, channel); + } catch (InterruptedException e) { + logger.log(Level.SEVERE, "Failed to do unary rpc", e); + result = false; + } + assertThat(result).isTrue(); + } + } + + public static boolean doUnaryRpc(ExecutorService executor, ManagedChannel channel) + throws InterruptedException { + try { + SimpleServiceGrpc.SimpleServiceBlockingStub stub = + SimpleServiceGrpc.newBlockingStub(channel); + SimpleResponse resp = stub.unaryRpc(SimpleRequest.newBuilder() + .setRequestMessage("S2A team") + .build()); + if (!resp.getResponseMessage().equals("Hello, S2A team!")) { + logger.info( + "Received unexpected message from the Simple Service: " + resp.getResponseMessage()); + throw new RuntimeException(); + } else { + System.out.println( + "We received this message from the Simple Service: " + resp.getResponseMessage()); + return true; + } + } finally { + channel.shutdown(); + channel.awaitTermination(1, SECONDS); + executor.shutdown(); + executor.awaitTermination(1, SECONDS); + } + } + + private static SslContext buildSslContext() throws SSLException { + SslContextBuilder sslServerContextBuilder = + SslContextBuilder.forServer( + new ByteArrayInputStream(CERT_CHAIN.getBytes(UTF_8)), + new ByteArrayInputStream(PRIVATE_KEY.getBytes(UTF_8))); + SslContext sslServerContext = + GrpcSslContexts.configure(sslServerContextBuilder, SslProvider.OPENSSL) + .protocols("TLSv1.3", "TLSv1.2") + .trustManager(new ByteArrayInputStream(ROOT_PEM.getBytes(UTF_8))) + .clientAuth(ClientAuth.REQUIRE) + .build(); + + // Enable TLS resumption. This requires using the OpenSSL provider, since the JDK provider does + // not allow a server to send session tickets. + SSLSessionContext sslSessionContext = sslServerContext.sessionContext(); + if (!(sslSessionContext instanceof OpenSslSessionContext)) { + throw new SSLException("sslSessionContext does not use OpenSSL."); + } + OpenSslSessionContext openSslSessionContext = (OpenSslSessionContext) sslSessionContext; + // Calling {@code setTicketKeys} without specifying any keys means that the SSL libraries will + // handle the generation of the resumption master secret. + openSslSessionContext.setTicketKeys(); + + return sslServerContext; + } + + public static class SimpleServiceImpl extends SimpleServiceGrpc.SimpleServiceImplBase { + @Override + public void unaryRpc(SimpleRequest request, StreamObserver observer) { + observer.onNext( + SimpleResponse.newBuilder() + .setResponseMessage("Hello, " + request.getRequestMessage() + "!") + .build()); + observer.onCompleted(); + } + } +} \ No newline at end of file diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/ProtoUtilTest.java b/s2a/src/test/java/io/grpc/s2a/handshaker/ProtoUtilTest.java new file mode 100644 index 00000000000..6d134b43f7a --- /dev/null +++ b/s2a/src/test/java/io/grpc/s2a/handshaker/ProtoUtilTest.java @@ -0,0 +1,131 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.handshaker; + +import static org.junit.Assert.assertThrows; + +import com.google.common.collect.ImmutableSet; +import com.google.common.truth.Expect; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link ProtoUtil}. */ +@RunWith(JUnit4.class) +public final class ProtoUtilTest { + @Rule public final Expect expect = Expect.create(); + + @Test + public void convertCiphersuite_success() { + expect + .that( + ProtoUtil.convertCiphersuite( + Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)) + .isEqualTo("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"); + expect + .that( + ProtoUtil.convertCiphersuite( + Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384)) + .isEqualTo("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"); + expect + .that( + ProtoUtil.convertCiphersuite( + Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256)) + .isEqualTo("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"); + expect + .that( + ProtoUtil.convertCiphersuite(Ciphersuite.CIPHERSUITE_ECDHE_RSA_WITH_AES_128_GCM_SHA256)) + .isEqualTo("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"); + expect + .that( + ProtoUtil.convertCiphersuite(Ciphersuite.CIPHERSUITE_ECDHE_RSA_WITH_AES_256_GCM_SHA384)) + .isEqualTo("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"); + expect + .that( + ProtoUtil.convertCiphersuite( + Ciphersuite.CIPHERSUITE_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256)) + .isEqualTo("TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"); + } + + @Test + public void convertCiphersuite_withUnspecifiedCiphersuite_fails() { + AssertionError expected = + assertThrows( + AssertionError.class, + () -> ProtoUtil.convertCiphersuite(Ciphersuite.CIPHERSUITE_UNSPECIFIED)); + expect.that(expected).hasMessageThat().isEqualTo("Ciphersuite 0 is not supported."); + } + + @Test + public void convertTlsProtocolVersion_success() { + expect + .that(ProtoUtil.convertTlsProtocolVersion(TLSVersion.TLS_VERSION_1_3)) + .isEqualTo("TLSv1.3"); + expect + .that(ProtoUtil.convertTlsProtocolVersion(TLSVersion.TLS_VERSION_1_2)) + .isEqualTo("TLSv1.2"); + expect + .that(ProtoUtil.convertTlsProtocolVersion(TLSVersion.TLS_VERSION_1_1)) + .isEqualTo("TLSv1.1"); + expect.that(ProtoUtil.convertTlsProtocolVersion(TLSVersion.TLS_VERSION_1_0)).isEqualTo("TLSv1"); + } + + @Test + public void convertTlsProtocolVersion_withUnknownTlsVersion_fails() { + AssertionError expected = + assertThrows( + AssertionError.class, + () -> ProtoUtil.convertTlsProtocolVersion(TLSVersion.TLS_VERSION_UNSPECIFIED)); + expect.that(expected).hasMessageThat().isEqualTo("TLS version 0 is not supported."); + } + + @Test + public void buildTlsProtocolVersionSet_success() { + expect + .that( + ProtoUtil.buildTlsProtocolVersionSet( + TLSVersion.TLS_VERSION_1_0, TLSVersion.TLS_VERSION_1_3)) + .isEqualTo(ImmutableSet.of("TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3")); + expect + .that( + ProtoUtil.buildTlsProtocolVersionSet( + TLSVersion.TLS_VERSION_1_2, TLSVersion.TLS_VERSION_1_2)) + .isEqualTo(ImmutableSet.of("TLSv1.2")); + expect + .that( + ProtoUtil.buildTlsProtocolVersionSet( + TLSVersion.TLS_VERSION_1_3, TLSVersion.TLS_VERSION_1_3)) + .isEqualTo(ImmutableSet.of("TLSv1.3")); + expect + .that( + ProtoUtil.buildTlsProtocolVersionSet( + TLSVersion.TLS_VERSION_1_3, TLSVersion.TLS_VERSION_1_2)) + .isEmpty(); + } + + @Test + public void buildTlsProtocolVersionSet_failure() { + AssertionError expected = + assertThrows( + AssertionError.class, + () -> + ProtoUtil.buildTlsProtocolVersionSet( + TLSVersion.TLS_VERSION_UNSPECIFIED, TLSVersion.TLS_VERSION_1_3)); + expect.that(expected).hasMessageThat().isEqualTo("TLS version 0 is not supported."); + } +} \ No newline at end of file diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/S2APrivateKeyMethodTest.java b/s2a/src/test/java/io/grpc/s2a/handshaker/S2APrivateKeyMethodTest.java new file mode 100644 index 00000000000..8252aa245d7 --- /dev/null +++ b/s2a/src/test/java/io/grpc/s2a/handshaker/S2APrivateKeyMethodTest.java @@ -0,0 +1,308 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.handshaker; + +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.common.truth.Expect; +import com.google.protobuf.ByteString; +import io.grpc.netty.GrpcSslContexts; +import io.grpc.s2a.handshaker.S2AIdentity; +import io.netty.handler.ssl.OpenSslPrivateKeyMethod; +import io.netty.handler.ssl.SslContextBuilder; +import java.io.ByteArrayInputStream; +import java.security.PublicKey; +import java.security.Signature; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Optional; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class S2APrivateKeyMethodTest { + @Rule public final Expect expect = Expect.create(); + private static final byte[] DATA_TO_SIGN = "random bytes for signing.".getBytes(UTF_8); + + private S2AStub stub; + private FakeWriter writer; + private S2APrivateKeyMethod keyMethod; + + private static PublicKey extractPublicKeyFromPem(String pem) throws Exception { + X509Certificate cert = + (X509Certificate) + CertificateFactory.getInstance("X.509") + .generateCertificate(new ByteArrayInputStream(pem.getBytes(UTF_8))); + return cert.getPublicKey(); + } + + private static boolean verifySignature( + byte[] dataToSign, byte[] signature, String signatureAlgorithm) throws Exception { + Signature sig = Signature.getInstance(signatureAlgorithm); + sig.initVerify(extractPublicKeyFromPem(FakeWriter.LEAF_CERT)); + sig.update(dataToSign); + return sig.verify(signature); + } + + @Before + public void setUp() { + // This is line is to ensure that JNI correctly links the necessary objects. Without this, we + // get `java.lang.UnsatisfiedLinkError` on + // `io.netty.internal.tcnative.NativeStaticallyReferencedJniMethods.sslSignRsaPkcsSha1()` + GrpcSslContexts.configure(SslContextBuilder.forClient()); + + writer = new FakeWriter(); + stub = S2AStub.newInstanceForTesting(writer); + writer.setReader(stub.getReader()); + keyMethod = S2APrivateKeyMethod.create(stub, /* localIdentity= */ Optional.empty()); + } + + @Test + public void signatureAlgorithmConversion_success() { + expect + .that( + S2APrivateKeyMethod.convertOpenSslSignAlgToS2ASignAlg( + OpenSslPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA256)) + .isEqualTo(SignatureAlgorithm.S2A_SSL_SIGN_RSA_PKCS1_SHA256); + expect + .that( + S2APrivateKeyMethod.convertOpenSslSignAlgToS2ASignAlg( + OpenSslPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA384)) + .isEqualTo(SignatureAlgorithm.S2A_SSL_SIGN_RSA_PKCS1_SHA384); + expect + .that( + S2APrivateKeyMethod.convertOpenSslSignAlgToS2ASignAlg( + OpenSslPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA512)) + .isEqualTo(SignatureAlgorithm.S2A_SSL_SIGN_RSA_PKCS1_SHA512); + expect + .that( + S2APrivateKeyMethod.convertOpenSslSignAlgToS2ASignAlg( + OpenSslPrivateKeyMethod.SSL_SIGN_ECDSA_SECP256R1_SHA256)) + .isEqualTo(SignatureAlgorithm.S2A_SSL_SIGN_ECDSA_SECP256R1_SHA256); + expect + .that( + S2APrivateKeyMethod.convertOpenSslSignAlgToS2ASignAlg( + OpenSslPrivateKeyMethod.SSL_SIGN_ECDSA_SECP384R1_SHA384)) + .isEqualTo(SignatureAlgorithm.S2A_SSL_SIGN_ECDSA_SECP384R1_SHA384); + expect + .that( + S2APrivateKeyMethod.convertOpenSslSignAlgToS2ASignAlg( + OpenSslPrivateKeyMethod.SSL_SIGN_ECDSA_SECP521R1_SHA512)) + .isEqualTo(SignatureAlgorithm.S2A_SSL_SIGN_ECDSA_SECP521R1_SHA512); + expect + .that( + S2APrivateKeyMethod.convertOpenSslSignAlgToS2ASignAlg( + OpenSslPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA256)) + .isEqualTo(SignatureAlgorithm.S2A_SSL_SIGN_RSA_PSS_RSAE_SHA256); + expect + .that( + S2APrivateKeyMethod.convertOpenSslSignAlgToS2ASignAlg( + OpenSslPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA384)) + .isEqualTo(SignatureAlgorithm.S2A_SSL_SIGN_RSA_PSS_RSAE_SHA384); + expect + .that( + S2APrivateKeyMethod.convertOpenSslSignAlgToS2ASignAlg( + OpenSslPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA512)) + .isEqualTo(SignatureAlgorithm.S2A_SSL_SIGN_RSA_PSS_RSAE_SHA512); + } + + @Test + public void signatureAlgorithmConversion_unsupportedOperation() { + UnsupportedOperationException e = + assertThrows( + UnsupportedOperationException.class, + () -> S2APrivateKeyMethod.convertOpenSslSignAlgToS2ASignAlg(-1)); + + assertThat(e).hasMessageThat().contains("Signature Algorithm -1 is not supported."); + } + + @Test + public void createOnNullStub_returnsNullPointerException() { + assertThrows( + NullPointerException.class, + () -> S2APrivateKeyMethod.create(/* stub= */ null, /* localIdentity= */ Optional.empty())); + } + + @Test + public void decrypt_unsupportedOperation() { + UnsupportedOperationException e = + assertThrows( + UnsupportedOperationException.class, + () -> keyMethod.decrypt(/* engine= */ null, DATA_TO_SIGN)); + + assertThat(e).hasMessageThat().contains("decrypt is not supported."); + } + + @Test + public void fakelocalIdentity_signWithSha256_success() throws Exception { + S2AIdentity fakeIdentity = S2AIdentity.fromSpiffeId("fake-spiffe-id"); + S2AStub mockStub = mock(S2AStub.class); + OpenSslPrivateKeyMethod keyMethodWithFakeIdentity = + S2APrivateKeyMethod.create(mockStub, Optional.of(fakeIdentity)); + SessionReq req = + SessionReq.newBuilder() + .setLocalIdentity(fakeIdentity.getIdentity()) + .setOffloadPrivateKeyOperationReq( + OffloadPrivateKeyOperationReq.newBuilder() + .setOperation(OffloadPrivateKeyOperationReq.PrivateKeyOperation.SIGN) + .setSignatureAlgorithm(SignatureAlgorithm.S2A_SSL_SIGN_ECDSA_SECP256R1_SHA256) + .setRawBytes(ByteString.copyFrom(DATA_TO_SIGN))) + .build(); + byte[] expectedOutbytes = "fake out bytes".getBytes(UTF_8); + when(mockStub.send(req)) + .thenReturn( + SessionResp.newBuilder() + .setOffloadPrivateKeyOperationResp( + OffloadPrivateKeyOperationResp.newBuilder() + .setOutBytes(ByteString.copyFrom(expectedOutbytes))) + .build()); + + byte[] signature = + keyMethodWithFakeIdentity.sign( + /* engine= */ null, + OpenSslPrivateKeyMethod.SSL_SIGN_ECDSA_SECP256R1_SHA256, + DATA_TO_SIGN); + verify(mockStub).send(req); + assertThat(signature).isEqualTo(expectedOutbytes); + } + + @Test + public void signWithSha256_success() throws Exception { + writer.initializePrivateKey().setBehavior(FakeWriter.Behavior.OK_STATUS); + + byte[] signature = + keyMethod.sign( + /* engine= */ null, + OpenSslPrivateKeyMethod.SSL_SIGN_ECDSA_SECP256R1_SHA256, + DATA_TO_SIGN); + + assertThat(signature).isNotEmpty(); + assertThat(verifySignature(DATA_TO_SIGN, signature, "SHA256withECDSA")).isTrue(); + } + + @Test + public void signWithSha384_success() throws Exception { + writer.initializePrivateKey().setBehavior(FakeWriter.Behavior.OK_STATUS); + + byte[] signature = + keyMethod.sign( + /* engine= */ null, + OpenSslPrivateKeyMethod.SSL_SIGN_ECDSA_SECP384R1_SHA384, + DATA_TO_SIGN); + + assertThat(signature).isNotEmpty(); + assertThat(verifySignature(DATA_TO_SIGN, signature, "SHA384withECDSA")).isTrue(); + } + + @Test + public void signWithSha512_success() throws Exception { + writer.initializePrivateKey().setBehavior(FakeWriter.Behavior.OK_STATUS); + + byte[] signature = + keyMethod.sign( + /* engine= */ null, + OpenSslPrivateKeyMethod.SSL_SIGN_ECDSA_SECP521R1_SHA512, + DATA_TO_SIGN); + + assertThat(signature).isNotEmpty(); + assertThat(verifySignature(DATA_TO_SIGN, signature, "SHA512withECDSA")).isTrue(); + } + + @Test + public void sign_noKeyAvailable() throws Exception { + writer.resetPrivateKey().setBehavior(FakeWriter.Behavior.OK_STATUS); + + S2AConnectionException e = + assertThrows( + S2AConnectionException.class, + () -> + keyMethod.sign( + /* engine= */ null, + OpenSslPrivateKeyMethod.SSL_SIGN_ECDSA_SECP256R1_SHA256, + DATA_TO_SIGN)); + + assertThat(e) + .hasMessageThat() + .contains( + "Error occurred in response from S2A, error code: 255, error message: \"No Private Key" + + " available.\"."); + } + + @Test + public void sign_algorithmNotSupported() throws Exception { + writer.initializePrivateKey().setBehavior(FakeWriter.Behavior.OK_STATUS); + + S2AConnectionException e = + assertThrows( + S2AConnectionException.class, + () -> + keyMethod.sign( + /* engine= */ null, + OpenSslPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA256, + DATA_TO_SIGN)); + + assertThat(e) + .hasMessageThat() + .contains( + "Error occurred in response from S2A, error code: 255, error message: \"Only ECDSA key" + + " algorithms are supported.\"."); + } + + @Test + public void sign_getsErrorResponse() throws Exception { + writer.initializePrivateKey().setBehavior(FakeWriter.Behavior.ERROR_STATUS); + + S2AConnectionException e = + assertThrows( + S2AConnectionException.class, + () -> + keyMethod.sign( + /* engine= */ null, + OpenSslPrivateKeyMethod.SSL_SIGN_ECDSA_SECP256R1_SHA256, + DATA_TO_SIGN)); + + assertThat(e) + .hasMessageThat() + .contains( + "Error occurred in response from S2A, error code: 1, error message: \"Intended ERROR" + + " Status from FakeWriter.\"."); + } + + @Test + public void sign_getsEmptyResponse() throws Exception { + writer.initializePrivateKey().setBehavior(FakeWriter.Behavior.EMPTY_RESPONSE); + + S2AConnectionException e = + assertThrows( + S2AConnectionException.class, + () -> + keyMethod.sign( + /* engine= */ null, + OpenSslPrivateKeyMethod.SSL_SIGN_ECDSA_SECP256R1_SHA256, + DATA_TO_SIGN)); + + assertThat(e).hasMessageThat().contains("No valid response received from S2A."); + } +} \ No newline at end of file diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactoryTest.java b/s2a/src/test/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactoryTest.java new file mode 100644 index 00000000000..f130e52aac7 --- /dev/null +++ b/s2a/src/test/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactoryTest.java @@ -0,0 +1,284 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.handshaker; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import com.google.common.testing.NullPointerTester; +import com.google.common.testing.NullPointerTester.Visibility; +import io.grpc.Channel; +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; +import io.grpc.ManagedChannel; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.benchmarks.Utils; +import io.grpc.internal.ObjectPool; +import io.grpc.internal.SharedResourcePool; +import io.grpc.internal.TestUtils.NoopChannelLogger; +import io.grpc.netty.GrpcHttp2ConnectionHandler; +import io.grpc.netty.InternalProtocolNegotiator; +import io.grpc.netty.InternalProtocolNegotiator.ProtocolNegotiator; +import io.grpc.s2a.channel.S2AChannelPool; +import io.grpc.s2a.channel.S2AGrpcChannelPool; +import io.grpc.s2a.channel.S2AHandshakerServiceChannel; +import io.grpc.s2a.handshaker.S2AIdentity; +import io.grpc.s2a.handshaker.S2AProtocolNegotiatorFactory.S2AProtocolNegotiator; +import io.grpc.stub.StreamObserver; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; +import io.netty.channel.embedded.EmbeddedChannel; +import io.netty.handler.codec.http2.Http2ConnectionDecoder; +import io.netty.handler.codec.http2.Http2ConnectionEncoder; +import io.netty.handler.codec.http2.Http2Settings; +import io.netty.util.AsciiString; +import java.util.Optional; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link S2AProtocolNegotiatorFactory}. */ +@RunWith(JUnit4.class) +public class S2AProtocolNegotiatorFactoryTest { + private static final S2AIdentity LOCAL_IDENTITY = S2AIdentity.fromSpiffeId("local identity"); + private final ChannelHandlerContext mockChannelHandlerContext = mock(ChannelHandlerContext.class); + private GrpcHttp2ConnectionHandler fakeConnectionHandler; + private String authority; + private int port; + private Server fakeS2AServer; + private ObjectPool channelPool; + + @Before + public void setUp() throws Exception { + port = Utils.pickUnusedPort(); + fakeS2AServer = ServerBuilder.forPort(port).addService(new S2AServiceImpl()).build(); + fakeS2AServer.start(); + channelPool = new FakeChannelPool(); + authority = "localhost:" + port; + fakeConnectionHandler = FakeConnectionHandler.create(authority); + } + + @After + public void tearDown() { + fakeS2AServer.shutdown(); + } + + @Test + public void handlerRemoved_success() throws Exception { + S2AProtocolNegotiatorFactory.BufferReadsHandler handler1 = + new S2AProtocolNegotiatorFactory.BufferReadsHandler(); + S2AProtocolNegotiatorFactory.BufferReadsHandler handler2 = + new S2AProtocolNegotiatorFactory.BufferReadsHandler(); + EmbeddedChannel channel = new EmbeddedChannel(handler1, handler2); + channel.writeInbound("message1"); + channel.writeInbound("message2"); + channel.writeInbound("message3"); + assertThat(handler1.getReads()).hasSize(3); + assertThat(handler2.getReads()).isEmpty(); + channel.pipeline().remove(handler1); + assertThat(handler2.getReads()).hasSize(3); + } + + @Test + public void createProtocolNegotiatorFactory_nullArgument() throws Exception { + NullPointerTester tester = new NullPointerTester().setDefault(Optional.class, Optional.empty()); + + tester.testStaticMethods(S2AProtocolNegotiatorFactory.class, Visibility.PUBLIC); + } + + @Test + public void createProtocolNegotiator_nullArgument() throws Exception { + S2AChannelPool pool = + S2AGrpcChannelPool.create( + SharedResourcePool.forResource( + S2AHandshakerServiceChannel.getChannelResource( + "localhost:8080", /* s2aChannelCredentials= */ Optional.empty()))); + + NullPointerTester tester = + new NullPointerTester() + .setDefault(S2AChannelPool.class, pool) + .setDefault(Optional.class, Optional.empty()); + + tester.testStaticMethods(S2AProtocolNegotiator.class, Visibility.PACKAGE); + } + + @Test + public void createProtocolNegotiatorFactory_getsDefaultPort_succeeds() throws Exception { + InternalProtocolNegotiator.ClientFactory clientFactory = + S2AProtocolNegotiatorFactory.createClientFactory(LOCAL_IDENTITY, channelPool); + + assertThat(clientFactory.getDefaultPort()).isEqualTo(S2AProtocolNegotiatorFactory.DEFAULT_PORT); + } + + @Test + public void s2aProtocolNegotiator_getHostNameOnNull_returnsNull() throws Exception { + assertThat(S2AProtocolNegotiatorFactory.S2AProtocolNegotiator.getHostNameFromAuthority(null)) + .isNull(); + } + + @Test + public void s2aProtocolNegotiator_getHostNameOnValidAuthority_returnsValidHostname() + throws Exception { + assertThat( + S2AProtocolNegotiatorFactory.S2AProtocolNegotiator.getHostNameFromAuthority( + "hostname:80")) + .isEqualTo("hostname"); + } + + @Test + public void createProtocolNegotiatorFactory_buildsAnS2AProtocolNegotiatorOnClientSide_succeeds() + throws Exception { + InternalProtocolNegotiator.ClientFactory clientFactory = + S2AProtocolNegotiatorFactory.createClientFactory(LOCAL_IDENTITY, channelPool); + + ProtocolNegotiator clientNegotiator = clientFactory.newNegotiator(); + + assertThat(clientNegotiator).isInstanceOf(S2AProtocolNegotiator.class); + assertThat(clientNegotiator.scheme()).isEqualTo(AsciiString.of("https")); + } + + @Test + public void closeProtocolNegotiator_verifyProtocolNegotiatorIsClosedOnClientSide() + throws Exception { + InternalProtocolNegotiator.ClientFactory clientFactory = + S2AProtocolNegotiatorFactory.createClientFactory(LOCAL_IDENTITY, channelPool); + ProtocolNegotiator clientNegotiator = clientFactory.newNegotiator(); + + clientNegotiator.close(); + + assertThat(((FakeChannelPool) channelPool).isChannelCached()).isFalse(); + } + + @Test + public void createChannelHandler_addHandlerToMockContext() throws Exception { + ExecutorService executor = Executors.newSingleThreadExecutor(); + ManagedChannel channel = + Grpc.newChannelBuilder(authority, InsecureChannelCredentials.create()) + .executor(executor) + .build(); + FakeS2AChannelPool fakeChannelPool = new FakeS2AChannelPool(channel); + ProtocolNegotiator clientNegotiator = + S2AProtocolNegotiatorFactory.S2AProtocolNegotiator.createForClient( + fakeChannelPool, LOCAL_IDENTITY); + + ChannelHandler channelHandler = clientNegotiator.newHandler(fakeConnectionHandler); + + ((ChannelDuplexHandler) channelHandler).userEventTriggered(mockChannelHandlerContext, "event"); + verify(mockChannelHandlerContext).fireUserEventTriggered("event"); + } + + /** A {@link S2AChannelPool} that returns the given channel. */ + private static class FakeS2AChannelPool implements S2AChannelPool { + private final Channel channel; + + FakeS2AChannelPool(Channel channel) { + this.channel = channel; + } + + @Override + public Channel getChannel() { + return channel; + } + + @Override + public void returnToPool(Channel channel) {} + + @Override + public void close() {} + } + + /** A {@code GrpcHttp2ConnectionHandler} that does nothing. */ + private static class FakeConnectionHandler extends GrpcHttp2ConnectionHandler { + private static final Http2ConnectionDecoder DECODER = mock(Http2ConnectionDecoder.class); + private static final Http2ConnectionEncoder ENCODER = mock(Http2ConnectionEncoder.class); + private static final Http2Settings SETTINGS = new Http2Settings(); + private final String authority; + + static FakeConnectionHandler create(String authority) { + return new FakeConnectionHandler(null, DECODER, ENCODER, SETTINGS, authority); + } + + private FakeConnectionHandler( + ChannelPromise channelUnused, + Http2ConnectionDecoder decoder, + Http2ConnectionEncoder encoder, + Http2Settings initialSettings, + String authority) { + super(channelUnused, decoder, encoder, initialSettings, new NoopChannelLogger()); + this.authority = authority; + } + + @Override + public String getAuthority() { + return authority; + } + } + + /** An S2A server that handles GetTlsConfiguration request. */ + private static class S2AServiceImpl extends S2AServiceGrpc.S2AServiceImplBase { + static final FakeWriter writer = new FakeWriter(); + + @Override + public StreamObserver setUpSession(StreamObserver responseObserver) { + return new StreamObserver() { + @Override + public void onNext(SessionReq req) { + responseObserver.onNext(writer.handleResponse(req)); + } + + @Override + public void onError(Throwable t) {} + + @Override + public void onCompleted() {} + }; + } + } + + private static class FakeChannelPool implements ObjectPool { + private final Channel mockChannel = mock(Channel.class); + private @Nullable Channel cachedChannel = null; + + @Override + public Channel getObject() { + if (cachedChannel == null) { + cachedChannel = mockChannel; + } + return cachedChannel; + } + + @Override + public Channel returnObject(Object object) { + assertThat(object).isSameInstanceAs(mockChannel); + cachedChannel = null; + return null; + } + + public boolean isChannelCached() { + return (cachedChannel != null); + } + } +} \ No newline at end of file diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/S2AStubTest.java b/s2a/src/test/java/io/grpc/s2a/handshaker/S2AStubTest.java new file mode 100644 index 00000000000..bb90be12b6a --- /dev/null +++ b/s2a/src/test/java/io/grpc/s2a/handshaker/S2AStubTest.java @@ -0,0 +1,260 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.handshaker; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.google.common.truth.Expect; +import io.grpc.internal.SharedResourcePool; +import io.grpc.s2a.channel.S2AChannelPool; +import io.grpc.s2a.channel.S2AGrpcChannelPool; +import io.grpc.s2a.channel.S2AHandshakerServiceChannel; +import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.util.Optional; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link S2AStub}. */ +@RunWith(JUnit4.class) +public class S2AStubTest { + @Rule public final Expect expect = Expect.create(); + private static final String S2A_ADDRESS = "localhost:8080"; + private S2AStub stub; + private FakeWriter writer; + + @Before + public void setUp() { + writer = new FakeWriter(); + stub = S2AStub.newInstanceForTesting(writer); + writer.setReader(stub.getReader()); + } + + @Test + public void send_receiveOkStatus() throws Exception { + S2AChannelPool channelPool = + S2AGrpcChannelPool.create( + SharedResourcePool.forResource( + S2AHandshakerServiceChannel.getChannelResource( + S2A_ADDRESS, /* s2aChannelCredentials= */ Optional.empty()))); + S2AServiceGrpc.S2AServiceStub serviceStub = S2AServiceGrpc.newStub(channelPool.getChannel()); + S2AStub newStub = S2AStub.newInstance(serviceStub); + + IOException expected = + assertThrows(IOException.class, () -> newStub.send(SessionReq.getDefaultInstance())); + + assertThat(expected).hasMessageThat().contains("DEADLINE_EXCEEDED"); + } + + @Test + public void send_clientTlsConfiguration_receiveOkStatus() throws Exception { + SessionReq req = + SessionReq.newBuilder() + .setGetTlsConfigurationReq( + GetTlsConfigurationReq.newBuilder() + .setConnectionSide(ConnectionSide.CONNECTION_SIDE_CLIENT)) + .build(); + + SessionResp resp = stub.send(req); + + SessionResp expected = + SessionResp.newBuilder() + .setGetTlsConfigurationResp( + GetTlsConfigurationResp.newBuilder() + .setClientTlsConfiguration( + GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() + .addCertificateChain(FakeWriter.LEAF_CERT) + .addCertificateChain(FakeWriter.INTERMEDIATE_CERT_2) + .addCertificateChain(FakeWriter.INTERMEDIATE_CERT_1) + .setMinTlsVersion(TLSVersion.TLS_VERSION_1_3) + .setMaxTlsVersion(TLSVersion.TLS_VERSION_1_3) + .addCiphersuites( + Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) + .addCiphersuites( + Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) + .addCiphersuites( + Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256))) + .build(); + assertThat(resp).ignoringRepeatedFieldOrder().isEqualTo(expected); + } + + @Test + public void send_serverTlsConfiguration_receiveErrorStatus() throws Exception { + SessionReq req = + SessionReq.newBuilder() + .setGetTlsConfigurationReq( + GetTlsConfigurationReq.newBuilder() + .setConnectionSide(ConnectionSide.CONNECTION_SIDE_SERVER)) + .build(); + + SessionResp resp = stub.send(req); + + SessionResp expected = + SessionResp.newBuilder() + .setStatus( + Status.newBuilder() + .setCode(255) + .setDetails("No TLS configuration for the server side.")) + .build(); + assertThat(resp).isEqualTo(expected); + } + + @Test + public void send_receiveErrorStatus() throws Exception { + writer.setBehavior(FakeWriter.Behavior.ERROR_STATUS); + + SessionResp resp = stub.send(SessionReq.getDefaultInstance()); + + SessionResp expected = + SessionResp.newBuilder() + .setStatus( + Status.newBuilder().setCode(1).setDetails("Intended ERROR Status from FakeWriter.")) + .build(); + assertThat(resp).isEqualTo(expected); + } + + @Test + public void send_receiveErrorResponse() throws InterruptedException { + writer.setBehavior(FakeWriter.Behavior.ERROR_RESPONSE); + + IOException expected = + assertThrows(IOException.class, () -> stub.send(SessionReq.getDefaultInstance())); + + expect.that(expected).hasCauseThat().isInstanceOf(RuntimeException.class); + expect.that(expected).hasMessageThat().contains("Intended ERROR from FakeWriter."); + } + + @Test + public void send_receiveCompleteStatus() throws Exception { + writer.setBehavior(FakeWriter.Behavior.COMPLETE_STATUS); + + ConnectionClosedException expected = + assertThrows( + ConnectionClosedException.class, () -> stub.send(SessionReq.getDefaultInstance())); + + assertThat(expected).hasMessageThat().contains("Reading from the S2A is complete."); + } + + @Test + public void send_receiveUnexpectedResponse() throws Exception { + writer.sendIoError(); + + IOException expected = + assertThrows(IOException.class, () -> stub.send(SessionReq.getDefaultInstance())); + + assertThat(expected) + .hasMessageThat() + .contains( + "Received an unexpected response from a host at the S2A's address. The S2A might be" + + " unavailable."); + } + + @Test + public void send_receiveManyUnexpectedResponse_expectResponsesEmpty() throws Exception { + writer.sendIoError(); + writer.sendIoError(); + writer.sendIoError(); + + IOException expected = + assertThrows(IOException.class, () -> stub.send(SessionReq.getDefaultInstance())); + + assertThat(expected) + .hasMessageThat() + .contains( + "Received an unexpected response from a host at the S2A's address. The S2A might be" + + " unavailable."); + + assertThat(stub.getResponses()).isEmpty(); + } + + @Test + public void send_receiveDelayedResponse() throws Exception { + writer.sendGetTlsConfigResp(); + SessionResp resp = stub.send(SessionReq.getDefaultInstance()); + SessionResp expected = + SessionResp.newBuilder() + .setGetTlsConfigurationResp( + GetTlsConfigurationResp.newBuilder() + .setClientTlsConfiguration( + GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() + .addCertificateChain(FakeWriter.LEAF_CERT) + .addCertificateChain(FakeWriter.INTERMEDIATE_CERT_2) + .addCertificateChain(FakeWriter.INTERMEDIATE_CERT_1) + .setMinTlsVersion(TLSVersion.TLS_VERSION_1_3) + .setMaxTlsVersion(TLSVersion.TLS_VERSION_1_3) + .addCiphersuites( + Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) + .addCiphersuites( + Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) + .addCiphersuites( + Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256))) + .build(); + assertThat(resp).ignoringRepeatedFieldOrder().isEqualTo(expected); + } + + @Test + public void send_afterEarlyClose_receivesClosedException() throws InterruptedException { + stub.close(); + expect.that(writer.isFakeWriterClosed()).isTrue(); + + ConnectionClosedException expected = + assertThrows( + ConnectionClosedException.class, () -> stub.send(SessionReq.getDefaultInstance())); + + assertThat(expected).hasMessageThat().contains("Stream to the S2A is closed."); + } + + @Test + public void send_failToWrite() throws Exception { + FailWriter failWriter = new FailWriter(); + stub = S2AStub.newInstanceForTesting(failWriter); + + IOException expected = + assertThrows(IOException.class, () -> stub.send(SessionReq.getDefaultInstance())); + + expect.that(expected).hasCauseThat().isInstanceOf(S2AConnectionException.class); + expect + .that(expected) + .hasCauseThat() + .hasMessageThat() + .isEqualTo("Could not send request to S2A."); + } + + /** Fails whenever a write is attempted. */ + private static class FailWriter implements StreamObserver { + @Override + public void onNext(SessionReq req) { + assertThat(req).isNotNull(); + throw new S2AConnectionException("Could not send request to S2A."); + } + + @Override + public void onError(Throwable t) { + assertThat(t).isInstanceOf(S2AConnectionException.class); + } + + @Override + public void onCompleted() { + throw new UnsupportedOperationException(); + } + } +} \ No newline at end of file diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/S2ATrustManagerTest.java b/s2a/src/test/java/io/grpc/s2a/handshaker/S2ATrustManagerTest.java new file mode 100644 index 00000000000..384e1aba5cc --- /dev/null +++ b/s2a/src/test/java/io/grpc/s2a/handshaker/S2ATrustManagerTest.java @@ -0,0 +1,262 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.handshaker; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import io.grpc.s2a.handshaker.S2AIdentity; +import java.io.ByteArrayInputStream; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Base64; +import java.util.Optional; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class S2ATrustManagerTest { + private S2AStub stub; + private FakeWriter writer; + private static final String FAKE_HOSTNAME = "Fake-Hostname"; + private static final String CLIENT_CERT_PEM = + "MIICKjCCAc+gAwIBAgIUC2GShcVO+5Zkml+7VO3OQ+B2c7EwCgYIKoZIzj0EAwIw" + + "HzEdMBsGA1UEAwwUcm9vdGNlcnQuZXhhbXBsZS5jb20wIBcNMjMwMTI2MTk0OTUx" + + "WhgPMjA1MDA2MTMxOTQ5NTFaMB8xHTAbBgNVBAMMFGxlYWZjZXJ0LmV4YW1wbGUu" + + "Y29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeciYZgFAZjxyzTrklCRIWpad" + + "8wkyCZQzJSf0IfNn9NKtfzL2V/blteULO0o9Da8e2Avaj+XCKfFTc7salMo/waOB" + + "5jCB4zAOBgNVHQ8BAf8EBAMCB4AwIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwIGCCsG" + + "AQUFBwMBMAwGA1UdEwEB/wQCMAAwYQYDVR0RBFowWIYic3BpZmZlOi8vZm9vLnBy" + + "b2QuZ29vZ2xlLmNvbS9wMS9wMoIUZm9vLnByb2Quc3BpZmZlLmdvb2eCHG1hY2hp" + + "bmUtbmFtZS5wcm9kLmdvb2dsZS5jb20wHQYDVR0OBBYEFETY6Cu/aW924nfvUrOs" + + "yXCC1hrpMB8GA1UdIwQYMBaAFJLkXGlTYKISiGd+K/Ijh4IOEpHBMAoGCCqGSM49" + + "BAMCA0kAMEYCIQCZDW472c1/4jEOHES/88X7NTqsYnLtIpTjp5PZ62z3sAIhAN1J" + + "vxvbxt9ySdFO+cW7oLBEkCwUicBhxJi5VfQeQypT"; + + @Before + public void setUp() { + writer = new FakeWriter(); + stub = S2AStub.newInstanceForTesting(writer); + writer.setReader(stub.getReader()); + } + + @Test + public void createForClient_withNullStub_throwsError() { + NullPointerException expected = + assertThrows( + NullPointerException.class, + () -> + S2ATrustManager.createForClient( + /* stub= */ null, FAKE_HOSTNAME, /* localIdentity= */ Optional.empty())); + + assertThat(expected).hasMessageThat().isNull(); + } + + @Test + public void createForClient_withNullHostname_throwsError() { + NullPointerException expected = + assertThrows( + NullPointerException.class, + () -> + S2ATrustManager.createForClient( + stub, /* hostname= */ null, /* localIdentity= */ Optional.empty())); + + assertThat(expected).hasMessageThat().isNull(); + } + + @Test + public void getAcceptedIssuers_returnsExpectedNullResult() { + S2ATrustManager trustManager = + S2ATrustManager.createForClient(stub, FAKE_HOSTNAME, /* localIdentity= */ Optional.empty()); + + assertThat(trustManager.getAcceptedIssuers()).isNull(); + } + + @Test + public void checkClientTrusted_withEmptyCertificateChain_throwsException() + throws CertificateException { + writer.setVerificationResult(FakeWriter.VerificationResult.SUCCESS); + S2ATrustManager trustManager = + S2ATrustManager.createForClient(stub, FAKE_HOSTNAME, /* localIdentity= */ Optional.empty()); + + IllegalArgumentException expected = + assertThrows( + IllegalArgumentException.class, + () -> trustManager.checkClientTrusted(new X509Certificate[] {}, /* authType= */ "")); + + assertThat(expected).hasMessageThat().contains("Certificate chain has zero certificates."); + } + + @Test + public void checkServerTrusted_withEmptyCertificateChain_throwsException() + throws CertificateException { + writer.setVerificationResult(FakeWriter.VerificationResult.SUCCESS); + S2ATrustManager trustManager = + S2ATrustManager.createForClient(stub, FAKE_HOSTNAME, /* localIdentity= */ Optional.empty()); + + IllegalArgumentException expected = + assertThrows( + IllegalArgumentException.class, + () -> trustManager.checkServerTrusted(new X509Certificate[] {}, /* authType= */ "")); + + assertThat(expected).hasMessageThat().contains("Certificate chain has zero certificates."); + } + + @Test + public void checkClientTrusted_getsSuccessResponse() throws CertificateException { + writer.setVerificationResult(FakeWriter.VerificationResult.SUCCESS); + S2ATrustManager trustManager = + S2ATrustManager.createForClient(stub, FAKE_HOSTNAME, /* localIdentity= */ Optional.empty()); + + // Expect no exception. + trustManager.checkClientTrusted(getCerts(), /* authType= */ ""); + } + + @Test + public void checkClientTrusted_withLocalIdentity_getsSuccessResponse() + throws CertificateException { + writer.setVerificationResult(FakeWriter.VerificationResult.SUCCESS); + S2ATrustManager trustManager = + S2ATrustManager.createForClient( + stub, FAKE_HOSTNAME, Optional.of(S2AIdentity.fromSpiffeId("fake-spiffe-id"))); + + // Expect no exception. + trustManager.checkClientTrusted(getCerts(), /* authType= */ ""); + } + + @Test + public void checkServerTrusted_getsSuccessResponse() throws CertificateException { + writer.setVerificationResult(FakeWriter.VerificationResult.SUCCESS); + S2ATrustManager trustManager = + S2ATrustManager.createForClient(stub, FAKE_HOSTNAME, /* localIdentity= */ Optional.empty()); + + // Expect no exception. + trustManager.checkServerTrusted(getCerts(), /* authType= */ ""); + } + + @Test + public void checkServerTrusted_withLocalIdentity_getsSuccessResponse() + throws CertificateException { + writer.setVerificationResult(FakeWriter.VerificationResult.SUCCESS); + S2ATrustManager trustManager = + S2ATrustManager.createForClient( + stub, FAKE_HOSTNAME, Optional.of(S2AIdentity.fromSpiffeId("fake-spiffe-id"))); + + // Expect no exception. + trustManager.checkServerTrusted(getCerts(), /* authType= */ ""); + } + + @Test + public void checkClientTrusted_getsIntendedFailureResponse() throws CertificateException { + writer + .setVerificationResult(FakeWriter.VerificationResult.FAILURE) + .setFailureReason("Intended failure."); + S2ATrustManager trustManager = + S2ATrustManager.createForClient(stub, FAKE_HOSTNAME, /* localIdentity= */ Optional.empty()); + + CertificateException expected = + assertThrows( + CertificateException.class, + () -> trustManager.checkClientTrusted(getCerts(), /* authType= */ "")); + + assertThat(expected).hasMessageThat().contains("Intended failure."); + } + + @Test + public void checkClientTrusted_getsIntendedFailureStatusInResponse() throws CertificateException { + writer.setBehavior(FakeWriter.Behavior.ERROR_STATUS); + S2ATrustManager trustManager = + S2ATrustManager.createForClient(stub, FAKE_HOSTNAME, /* localIdentity= */ Optional.empty()); + + CertificateException expected = + assertThrows( + CertificateException.class, + () -> trustManager.checkClientTrusted(getCerts(), /* authType= */ "")); + + assertThat(expected).hasMessageThat().contains("Error occurred in response from S2A"); + } + + @Test + public void checkClientTrusted_getsIntendedFailureFromServer() throws CertificateException { + writer.setBehavior(FakeWriter.Behavior.ERROR_RESPONSE); + S2ATrustManager trustManager = + S2ATrustManager.createForClient(stub, FAKE_HOSTNAME, /* localIdentity= */ Optional.empty()); + + CertificateException expected = + assertThrows( + CertificateException.class, + () -> trustManager.checkClientTrusted(getCerts(), /* authType= */ "")); + + assertThat(expected).hasMessageThat().isEqualTo("Failed to send request to S2A."); + } + + @Test + public void checkServerTrusted_getsIntendedFailureResponse() throws CertificateException { + writer + .setVerificationResult(FakeWriter.VerificationResult.FAILURE) + .setFailureReason("Intended failure."); + S2ATrustManager trustManager = + S2ATrustManager.createForClient(stub, FAKE_HOSTNAME, /* localIdentity= */ Optional.empty()); + + CertificateException expected = + assertThrows( + CertificateException.class, + () -> trustManager.checkServerTrusted(getCerts(), /* authType= */ "")); + + assertThat(expected).hasMessageThat().contains("Intended failure."); + } + + @Test + public void checkServerTrusted_getsIntendedFailureStatusInResponse() throws CertificateException { + writer.setBehavior(FakeWriter.Behavior.ERROR_STATUS); + S2ATrustManager trustManager = + S2ATrustManager.createForClient(stub, FAKE_HOSTNAME, /* localIdentity= */ Optional.empty()); + + CertificateException expected = + assertThrows( + CertificateException.class, + () -> trustManager.checkServerTrusted(getCerts(), /* authType= */ "")); + + assertThat(expected).hasMessageThat().contains("Error occurred in response from S2A"); + } + + @Test + public void checkServerTrusted_getsIntendedFailureFromServer() throws CertificateException { + writer.setBehavior(FakeWriter.Behavior.ERROR_RESPONSE); + S2ATrustManager trustManager = + S2ATrustManager.createForClient(stub, FAKE_HOSTNAME, /* localIdentity= */ Optional.empty()); + + CertificateException expected = + assertThrows( + CertificateException.class, + () -> trustManager.checkServerTrusted(getCerts(), /* authType= */ "")); + + assertThat(expected).hasMessageThat().isEqualTo("Failed to send request to S2A."); + } + + private X509Certificate[] getCerts() throws CertificateException { + byte[] decoded = Base64.getDecoder().decode(CLIENT_CERT_PEM); + return new X509Certificate[] { + (X509Certificate) + CertificateFactory.getInstance("X.509") + .generateCertificate(new ByteArrayInputStream(decoded)) + }; + } +} \ No newline at end of file diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/SslContextFactoryTest.java b/s2a/src/test/java/io/grpc/s2a/handshaker/SslContextFactoryTest.java new file mode 100644 index 00000000000..a2a66a7b563 --- /dev/null +++ b/s2a/src/test/java/io/grpc/s2a/handshaker/SslContextFactoryTest.java @@ -0,0 +1,177 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.handshaker; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.google.common.truth.Expect; +import io.grpc.s2a.handshaker.S2AIdentity; +import io.netty.handler.ssl.OpenSslSessionContext; +import io.netty.handler.ssl.SslContext; +import java.security.GeneralSecurityException; +import java.util.Optional; +import javax.net.ssl.SSLSessionContext; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link SslContextFactory}. */ +@RunWith(JUnit4.class) +public final class SslContextFactoryTest { + @Rule public final Expect expect = Expect.create(); + private static final String FAKE_TARGET_NAME = "fake_target_name"; + private S2AStub stub; + private FakeWriter writer; + + @Before + public void setUp() { + writer = new FakeWriter(); + stub = S2AStub.newInstanceForTesting(writer); + writer.setReader(stub.getReader()); + } + + @Test + public void createForClient_returnsValidSslContext() throws Exception { + SslContext sslContext = + SslContextFactory.createForClient( + stub, FAKE_TARGET_NAME, /* localIdentity= */ Optional.empty()); + + expect.that(sslContext).isNotNull(); + expect.that(sslContext.sessionCacheSize()).isEqualTo(1); + expect.that(sslContext.sessionTimeout()).isEqualTo(300); + expect.that(sslContext.isClient()).isTrue(); + expect.that(sslContext.applicationProtocolNegotiator().protocols()).containsExactly("h2"); + SSLSessionContext sslSessionContext = sslContext.sessionContext(); + if (sslSessionContext instanceof OpenSslSessionContext) { + OpenSslSessionContext openSslSessionContext = (OpenSslSessionContext) sslSessionContext; + expect.that(openSslSessionContext.isSessionCacheEnabled()).isFalse(); + } + } + + @Test + public void createForClient_withLocalIdentity_returnsValidSslContext() throws Exception { + SslContext sslContext = + SslContextFactory.createForClient( + stub, FAKE_TARGET_NAME, Optional.of(S2AIdentity.fromSpiffeId("fake-spiffe-id"))); + + expect.that(sslContext).isNotNull(); + expect.that(sslContext.sessionCacheSize()).isEqualTo(1); + expect.that(sslContext.sessionTimeout()).isEqualTo(300); + expect.that(sslContext.isClient()).isTrue(); + expect.that(sslContext.applicationProtocolNegotiator().protocols()).containsExactly("h2"); + SSLSessionContext sslSessionContext = sslContext.sessionContext(); + if (sslSessionContext instanceof OpenSslSessionContext) { + OpenSslSessionContext openSslSessionContext = (OpenSslSessionContext) sslSessionContext; + expect.that(openSslSessionContext.isSessionCacheEnabled()).isFalse(); + } + } + + @Test + public void createForClient_returnsEmptyResponse_error() throws Exception { + writer.setBehavior(FakeWriter.Behavior.EMPTY_RESPONSE); + + S2AConnectionException expected = + assertThrows( + S2AConnectionException.class, + () -> + SslContextFactory.createForClient( + stub, FAKE_TARGET_NAME, /* localIdentity= */ Optional.empty())); + + assertThat(expected) + .hasMessageThat() + .contains("Response from S2A server does NOT contain ClientTlsConfiguration."); + } + + @Test + public void createForClient_returnsErrorStatus_error() throws Exception { + writer.setBehavior(FakeWriter.Behavior.ERROR_STATUS); + + S2AConnectionException expected = + assertThrows( + S2AConnectionException.class, + () -> + SslContextFactory.createForClient( + stub, FAKE_TARGET_NAME, /* localIdentity= */ Optional.empty())); + + assertThat(expected).hasMessageThat().contains("Intended ERROR Status from FakeWriter."); + } + + @Test + public void createForClient_getsErrorFromServer_throwsError() throws Exception { + writer.sendIoError(); + + GeneralSecurityException expected = + assertThrows( + GeneralSecurityException.class, + () -> + SslContextFactory.createForClient( + stub, FAKE_TARGET_NAME, /* localIdentity= */ Optional.empty())); + + assertThat(expected) + .hasMessageThat() + .contains("Failed to get client TLS configuration from S2A."); + } + + @Test + public void createForClient_getsBadTlsVersionsFromServer_throwsError() throws Exception { + writer.setBehavior(FakeWriter.Behavior.BAD_TLS_VERSION_RESPONSE); + + S2AConnectionException expected = + assertThrows( + S2AConnectionException.class, + () -> + SslContextFactory.createForClient( + stub, FAKE_TARGET_NAME, /* localIdentity= */ Optional.empty())); + + assertThat(expected) + .hasMessageThat() + .contains("Set of TLS versions received from S2A server is empty."); + } + + @Test + public void createForClient_nullStub_throwsError() throws Exception { + writer.sendUnexpectedResponse(); + + NullPointerException expected = + assertThrows( + NullPointerException.class, + () -> + SslContextFactory.createForClient( + /* stub= */ null, FAKE_TARGET_NAME, /* localIdentity= */ Optional.empty())); + + assertThat(expected).hasMessageThat().isEqualTo("stub should not be null."); + } + + @Test + public void createForClient_nullTargetName_throwsError() throws Exception { + writer.sendUnexpectedResponse(); + + NullPointerException expected = + assertThrows( + NullPointerException.class, + () -> + SslContextFactory.createForClient( + stub, /* targetName= */ null, /* localIdentity= */ Optional.empty())); + + assertThat(expected) + .hasMessageThat() + .isEqualTo("targetName should not be null on client side."); + } +} \ No newline at end of file diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/tokenmanager/SingleTokenAccessTokenManagerTest.java b/s2a/src/test/java/io/grpc/s2a/handshaker/tokenmanager/SingleTokenAccessTokenManagerTest.java new file mode 100644 index 00000000000..80adba07f20 --- /dev/null +++ b/s2a/src/test/java/io/grpc/s2a/handshaker/tokenmanager/SingleTokenAccessTokenManagerTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.s2a.handshaker.tokenmanager; + +import static com.google.common.truth.Truth.assertThat; + +import io.grpc.s2a.handshaker.S2AIdentity; +import java.util.Optional; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class SingleTokenAccessTokenManagerTest { + private static final S2AIdentity IDENTITY = S2AIdentity.fromSpiffeId("spiffe_id"); + private static final String TOKEN = "token"; + + @Before + public void setUp() { + SingleTokenFetcher.setAccessToken(null); + } + + @Test + public void getDefaultToken_success() throws Exception { + SingleTokenFetcher.setAccessToken(TOKEN); + Optional manager = AccessTokenManager.create(); + assertThat(manager).isPresent(); + assertThat(manager.get().getDefaultToken()).isEqualTo(TOKEN); + } + + @Test + public void getToken_success() throws Exception { + SingleTokenFetcher.setAccessToken(TOKEN); + Optional manager = AccessTokenManager.create(); + assertThat(manager).isPresent(); + assertThat(manager.get().getToken(IDENTITY)).isEqualTo(TOKEN); + } + + @Test + public void getToken_noEnvironmentVariable() throws Exception { + assertThat(SingleTokenFetcher.create()).isEmpty(); + } + + @Test + public void create_success() throws Exception { + SingleTokenFetcher.setAccessToken(TOKEN); + Optional manager = AccessTokenManager.create(); + assertThat(manager).isPresent(); + assertThat(manager.get().getToken(IDENTITY)).isEqualTo(TOKEN); + } + + @Test + public void create_noEnvironmentVariable() throws Exception { + assertThat(AccessTokenManager.create()).isEmpty(); + } +} \ No newline at end of file diff --git a/s2a/src/test/resources/README.md b/s2a/src/test/resources/README.md new file mode 100644 index 00000000000..726b921a615 --- /dev/null +++ b/s2a/src/test/resources/README.md @@ -0,0 +1,32 @@ +# Generating certificates and keys for testing mTLS-S2A + +Content from: https://github.com/google/s2a-go/blob/main/testdata/README.md + +Create root CA + +``` +openssl req -x509 -sha256 -days 7305 -newkey rsa:2048 -keyout root_key.pem -out +root_cert.pem +``` + +Generate private keys for server and client + +``` +openssl genrsa -out server_key.pem 2048 +openssl genrsa -out client_key.pem 2048 +``` + +Generate CSRs for server and client (set Common Name to localhost, leave all +other fields blank) + +``` +openssl req -key server_key.pem -new -out server.csr -config config.cnf +openssl req -key client_key.pem -new -out client.csr -config config.cnf +``` + +Sign CSRs for server and client + +``` +openssl x509 -req -CA root_cert.pem -CAkey root_key.pem -in server.csr -out server_cert.pem -days 7305 -extfile config.cnf -extensions req_ext +openssl x509 -req -CA root_cert.pem -CAkey root_key.pem -in client.csr -out client_cert.pem -days 7305 +``` \ No newline at end of file diff --git a/s2a/src/test/resources/client.csr b/s2a/src/test/resources/client.csr new file mode 100644 index 00000000000..664f5a4cf86 --- /dev/null +++ b/s2a/src/test/resources/client.csr @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIChzCCAW8CAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAoSS3KtFgiXX4vAUNscFGIB/r2OOMgiZMKHz72dN0 +5kSxwdpQxpMIhwEoe0lhHNfOiuE7/r6VbGG9RGGIcQcoSonc3InPRfpnzfj9KohJ +i8pYkLL9EwElAEl9sWnvVKTza8jTApDP2Z/fntBEsWAMsLPpuRZT6tgN1sXe4vNG +4wufJSxuImyCVAx1fkZjRkYEKOtm1osnEDng4R0WXZ6S+q5lYzYPk1wxgbjdZu2U +fWxP6V63SphV0NFXTx0E401j2h258cIqTVj8lRX6dfl9gO0d43Rd+hSU7R4iXGEw +arixuH9g5H745AFf9H52twHPcNP9cEKBljBpSV5z3MvTkQIDAQABoC4wLAYJKoZI +hvcNAQkOMR8wHTAbBgNVHREEFDAShxAAAAAAAAAAAAAAAAAAAAAAMA0GCSqGSIb3 +DQEBCwUAA4IBAQCQHim3aIpGJs5u6JhEA07Rwm8YKyVALDEklhsHILlFhdNr2uV7 +S+3bHV79mDGjxNWvFcgK5h5ENkT60tXbhbie1gYmFT0RMCYHDsL09NGTh8G9Bbdl +UKeA9DMhRSYzE7Ks3Lo1dJvX7OAEI0qV77dGpQknufYpmHiBXuqtB9I0SpYi1c4O +9IUn/NY0yiYFPsIEsVRz/1dK97wazusLnijaMwNNhUc9bJwTyujhlr+b8ioPyADG +e+GDF97d0nQ8806DOJF4GTRKwaXD+R5zN5t4ULhZ7ERqLNeE9EnWRe4CvSGvBoNA +hIVeYaLd761Z9ZKvOnsgCr8qvMDilDFY6OfB +-----END CERTIFICATE REQUEST----- \ No newline at end of file diff --git a/s2a/src/test/resources/client_cert.pem b/s2a/src/test/resources/client_cert.pem new file mode 100644 index 00000000000..b72f6991c91 --- /dev/null +++ b/s2a/src/test/resources/client_cert.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC9DCCAdwCFB+cDXee2sIHjdlBhdNpTo+G2XAjMA0GCSqGSIb3DQEBCwUAMFkx +CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl +cm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMzEw +MTcyMzA5MDNaFw00MzEwMTcyMzA5MDNaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKEktyrRYIl1+LwFDbHBRiAf +69jjjIImTCh8+9nTdOZEscHaUMaTCIcBKHtJYRzXzorhO/6+lWxhvURhiHEHKEqJ +3NyJz0X6Z834/SqISYvKWJCy/RMBJQBJfbFp71Sk82vI0wKQz9mf357QRLFgDLCz +6bkWU+rYDdbF3uLzRuMLnyUsbiJsglQMdX5GY0ZGBCjrZtaLJxA54OEdFl2ekvqu +ZWM2D5NcMYG43WbtlH1sT+let0qYVdDRV08dBONNY9odufHCKk1Y/JUV+nX5fYDt +HeN0XfoUlO0eIlxhMGq4sbh/YOR++OQBX/R+drcBz3DT/XBCgZYwaUlec9zL05EC +AwEAATANBgkqhkiG9w0BAQsFAAOCAQEARorc1t2OJnwm1lxhf2KpTpNvNOI9FJak +iSHz/MxhMdu4BG/dQHkKkWoVC6W2Kaimx4OImBwRlGEmGf4P0bXOLSTOumk2k1np +ZUbw7Z2cJzvBmT2BLoHRXcBvbFIBW5DJUSHR37eXEKP57BeD+Og4/3XhNzehSpTX +DRd2Ix/D39JjYA462nqPHQP8HDMf6+0BFmvf9ZRYmFucccYQRCUCKDqb8+wGf9W6 +tKNRE6qPG2jpAQ9qkgO7XuucbLvpywt5xj+yDRbOIq43l40mHaz4lRp697oaxjP8 +HSVcMydW3cluoW3AVInNIaqbM1dr6931MllK62DKipFtmCycq/56XA== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/s2a/src/test/resources/client_key.pem b/s2a/src/test/resources/client_key.pem new file mode 100644 index 00000000000..dd3e2ff78f1 --- /dev/null +++ b/s2a/src/test/resources/client_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQChJLcq0WCJdfi8 +BQ2xwUYgH+vY44yCJkwofPvZ03TmRLHB2lDGkwiHASh7SWEc186K4Tv+vpVsYb1E +YYhxByhKidzcic9F+mfN+P0qiEmLyliQsv0TASUASX2xae9UpPNryNMCkM/Zn9+e +0ESxYAyws+m5FlPq2A3Wxd7i80bjC58lLG4ibIJUDHV+RmNGRgQo62bWiycQOeDh +HRZdnpL6rmVjNg+TXDGBuN1m7ZR9bE/pXrdKmFXQ0VdPHQTjTWPaHbnxwipNWPyV +Ffp1+X2A7R3jdF36FJTtHiJcYTBquLG4f2DkfvjkAV/0fna3Ac9w0/1wQoGWMGlJ +XnPcy9ORAgMBAAECggEALAUqoGDIHWUDyOEch5WDwZzWwc4PgTJTFbBm4G96fLkB +UjKAZG6gIrk3RM6b39Q4UQoMaJ/Jk+zzVi3Kpw3MfOhCVGC1JamtF8BP8IGAjdZ9 +8TFkHv/uCrEIzCFjRt00vhoDQq0qiom4/dppGYdikBbl3zDxRbM1vJkbNSY+FCGW +dA0uJ5XdMLR6lPeB5odqjUggnfUgPCOLdV/F+HkSM9NP1bzmHLiKznzwFsfat139 +7LdzJwNN5IX4Io6cxsxNlrX/NNvPkKdGv07Z6FYxWROyKCunjh48xFcQg0ltoRuq +R9P8/LwS8GYrcc1uC/uBc0e6VgM9D9fsvh+8SQtf3QKBgQDXX+z2GnsFoEs7xv9U +qN0HEX4jOkihZvFu43layUmeCeE8wlEctJ0TsM5Bd7FMoUG6e5/btwhsAIYW89Xn +l/R8OzxR6Kh952Dce4DAULuIeopiw7ASJwTZtO9lWhxw0hjM1hxXTG+xxOqQvsRX +c+d+vtvdIqyJ4ELfzg9kUtkdpwKBgQC/ig3cmej7dQdRAMn0YAwgwhuLkCqVFh4y +WIlqyPPejKf8RXubqgtaSYx/T7apP87SMMSfSLaUdrYAGjST6k+tG5cmwutPIbw/ +osL7U3hcIhjX3hfHgI69Ojcpplbd5yqTxZHpxIs6iAQCEqNuasLXIDMouqNhGF1D +YssD6qxcBwKBgQCdZqWvVrsB6ZwSG+UO4jpmqAofhMD/9FQOToCqMOF0dpP966WL +7RO/CEA06FzTPCblOuQhlyq4g8l7jMiPcSZkhIYY9oftO+Q2Pqxh4J6tp6DrfUh4 +e7u3v9wVnj2a1nD5gqFDy8D1kow7LLAhmbtdje7xNh4SxasaFWZ6U3IJkQKBgGS1 +F5i3q9IatCAZBBZjMb0/kfANevYsTPA3sPjec6q91c1EUzuDarisFx0RMn9Gt124 +mokNWEIzMHpZTO/AsOfZq92LeuF+YVYsI8y1FIGMw/csJOCWbXZ812gkt2OxGafc +p118I6BAx6q3VgrGQ2+M1JlDmIeCofa+SPPkPX+dAoGBAJrOgEJ+oyEaX/YR1g+f +33pWoPQbRCG7T4+Y0oetCCWIcMg1/IUvGUCGmRDxj5dMqB+a0vJtviQN9rjpSuNS +0EVw79AJkIjHhi6KDOfAuyBvzGhxpqxGufnQ2GU0QL65NxQfd290xkxikN0ZGtuB +SDgZoJxMOGYwf8EX5i9h27Db +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/s2a/src/test/resources/config.cnf b/s2a/src/test/resources/config.cnf new file mode 100644 index 00000000000..38d9a9ccdb0 --- /dev/null +++ b/s2a/src/test/resources/config.cnf @@ -0,0 +1,17 @@ +[req] +distinguished_name = req_distinguished_name +req_extensions = req_ext + +[req_distinguished_name] +countryName = Country Name (2 letter code) +stateOrProvinceName = State or Province Name (full name) +localityName = Locality Name (eg, city) +organizationalUnitName = Organizational Unit Name (eg, section) +commonName = Common Name (eg, your name or your server\'s hostname) +emailAddress = Email Address + +[req_ext] +subjectAltName = @alt_names + +[alt_names] +IP.1 = :: \ No newline at end of file diff --git a/s2a/src/test/resources/root_cert.pem b/s2a/src/test/resources/root_cert.pem new file mode 100644 index 00000000000..737e601691c --- /dev/null +++ b/s2a/src/test/resources/root_cert.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDkzCCAnugAwIBAgIUb7RsINwsFgKf0Q0RuzfOgp48j6UwDQYJKoZIhvcNAQEL +BQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MB4X +DTIzMTAxNzIzMDczOFoXDTQzMTAxNzIzMDczOFowWTELMAkGA1UEBhMCQVUxEzAR +BgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5 +IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAkIFnQLuhzYnm3rvmi/U7zMgEP2Tqgb3VC00frSXEV6olZcLgyC9g +0DAGdt9l9lP90DQTG5KCOtoW2BTqM/aaVpR0OaDFOCy90FIj6YyZLZ9w2PQxQcxS +GQHyEvWszTkNxeDyG1mPTj+Go8JLKqdvLg/9GUgPg6stxyAZwYhyUTGuEM4bv0sn +b3vmHRmIGJ/w6aLtd7nK8LkNHa3WVrbvRGHrzdMHfpzF/M/5fAk8GfRYugo39knf +VLKGyQCXNI8Y1iHGEmPqQZIFPTjBL6caIlbEV0VHlxoSOGB6JVxcllxAEvd6abqX +RJVJPQzzGfEnMNYp9SiZQ9bvDRUsUkWyYwIDAQABo1MwUTAdBgNVHQ4EFgQUAZMN +F9JAGHbA3jGOeu6bWFvSdWkwHwYDVR0jBBgwFoAUAZMNF9JAGHbA3jGOeu6bWFvS +dWkwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAicBli36ISJFu +lrJqOHVqTeNP6go0I35VGnP44nEEP5cBvRD3XntBFEk5D3mSNNOGt+2ncxom8VR9 +FsLuTfHAipXePJI6MSxFuBPea8V/YPBs3npk5f1FRvJ5vEgtzFvBjsKmp1dS9hH0 +KUWtWcsAkO2Anc/LVc0xxSidL8NjzYoEFqiki0TNNwCJjmd9XwnBLHW38sEb/pgy +KTyRpOyG3Zg2UDjBHiXPBrmIvVFLB6+LrPNvfr1k4HjIgVY539ZXUvVMDKytMrDY +h63EMDn4kkPpxXlufgWGybjN5D51OylyWBZLe+L1DQyWEg0Vd7GwPzb6p7bmI7MP +pooqbgbDpQ== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/s2a/src/test/resources/root_key.pem b/s2a/src/test/resources/root_key.pem new file mode 100644 index 00000000000..aae992426d7 --- /dev/null +++ b/s2a/src/test/resources/root_key.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQInmQVkXP3TFcCAggA +MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECGeCAVH1pefxBIIEyD3Nj1Dy19oy +fogU+z8YBLXuSCx8s3zncYPF9nYlegGSSo0ace/WxfPu8AEPus1P2MxlxfcCQ1A+ +5+vMihtEpgpTg9R4RlLAWs45jz4AduGiwqW05+W5zgDn6g7p7HIL0+M5FxKRkAW0 +KEH4Jy8Vc1XQxkhOm1Q4NLI8PT94rcBDE9Od03sdrW/hQgaOFz5AWOlT5jF1uUOz +glF1RQQxfJygTB6qlPTC3BAaiAnWij3NOg5L5vvUhjLa7iOZOhRQBRkf4YtHsM+2 +rFy8Z7MeHOvrqFf8LXosNy3JreQW036rLGR0Xh5myATkNrEwA8df37AgLUmwqyfz +hjZefPW77LgMAXlaN8s345AGikOX8yQKEFzPV/Nag32p6t4oiRRcUUfdB4wzKi6T +mzZ6lKcGR3qqL4V6lJSV3I2fmgkYZnUwymolyu+1+CVYDLuE53TBi5dRXwgOghi7 +npw7PqqQCian8yxHF9c1rYukD0ov0/y8ratjOu9XoJG2/wWQJNvDkAyc3mSJf+3y +6Wtu1qhLszU8pZOGW0fK6bGyHSp+wkoah/vRzB0+yFjvuMIG6py2ZDQeqhqS3ZV2 +nZHHjj0tZ45Wbdf4k17ujEK34pFXluPH//zADnd6ym2W0t6x+jtqR5tYu3poORQg +jFgpudkn2RUSq8N/gIiHDwblYBxU2dmyzEVudv1zNgVSHyetGLxsFoNB7Prn89rJ +u24a/xtuCyC2pshWo3KiL74hkkCsC8rLbEAAbADheb35b+Ca3JnMwgyUHbHL6Hqf +EiVIgm14lB/1uz651X58Boo6tDFkgrxEtGDUIZm8yk2n0tGflp7BtYbMCw+7gqhb +XN4hlhFDcCJm8peXcyCtGajOnBuNO9JJDNYor6QjptaIpQBFb7/0rc7kyO12BIUv +F9mrCHF18Hd/9AtUO93+tyDAnL64Jqq9tUv8dOVtIfbcHXZSYHf24l0XAiKByb8y +9NQLUZkIuF4aUZVHV8ZBDdHNqjzqVglKQlGHdw1XBexSal5pC9HvknOmWBgl0aza +flzeTRPX7TPrMJDE5lgSy58czGpvZzhFYwOp6cwpfjNsiqdzD78Zs0xsRbNg519s +d+cLmbiU3plWCoYCuDb68eZRRzT+o41+QJG2PoMCpzPw5wMLl6HuW7HXMRFpZKJc +tPKpeTIzb8hjhA+TwVIVpTPHvvQehtTUQD2mRujdvNM6PF8tnuC3F3sB3PTjeeJg +uzfEfs3BynRTIj/gX6y87gzwsrwWIEN6U0cCbQ6J1EcgdQCiH8vbhIgfd4DkLgLN +Kkif+fI/HgBOqaiwSw3sHmWgB6PllVQOKH6qAiejTHR/UUvJTPvgKJFLunmBiF12 +N1bRge1sSXE1eLKVdi+dP1j0o6RxhaRrbX7ie3y/wYHwCJnb8h08DEprgCqoswFs +SuNKmvlibBHAsnOdhyCTOd9I5n8XzAUUp6mT+C5WDfl7qfYvh6IHFlSrhZ9aS9b6 +RY873cnphKbqU5d7Cr8Ufx4b4SgS+hEnuP8y5IToLQ3BONGQH2lu7nmd89wjW0uo +IMRXybwf/5FnKhEy8Aw+pD6AxiXC3DZVTKl3SHmjkYBDvNElsJVgygVTKgbOa1Z+ +ovIK/D7QV7Nv3uVortH8XA== +-----END ENCRYPTED PRIVATE KEY----- \ No newline at end of file diff --git a/s2a/src/test/resources/server.csr b/s2a/src/test/resources/server.csr new file mode 100644 index 00000000000..1657b191133 --- /dev/null +++ b/s2a/src/test/resources/server.csr @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIChzCCAW8CAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAlPThqu8tfJ4hQKRiUw/vNPfo2L2LQU8NlrRL7rvV +71E345LGK1h/hM3MHp5VgEvaaIibb0hSNv/TYz3HVCQyNuPlcmkHZTJ9mB0icilU +rYWdM0LPIg46iThmIQVhMiNfpMKQLDLQ7o3Jktjm32OxnQdtYSV+7NFnw8/0pB4j +iaiBYfZIMeGzEJIOFG8GSNJG0pfCI71DyLRonIcb2XzfeDPHeWSF7lbIoMGAuKIE +2mXpwHmAjTMJzIShSgLqCvmbz7wR3ZeVMknXcgcqMmagGphy8SjizIWC5KRbrnRq +F22Ouxdat6scIevRXGp5nYawFYdpK9qo+82gEouVX3dtSQIDAQABoC4wLAYJKoZI +hvcNAQkOMR8wHTAbBgNVHREEFDAShxAAAAAAAAAAAAAAAAAAAAAAMA0GCSqGSIb3 +DQEBCwUAA4IBAQB2qU354OlNVunhZhiOFNwabovxLcgKoQz+GtJ2EzsMEza+NPvV +dttPxXzqL/U+gDghvGzSYGuh2yMfTTPO+XtZKpvMUmIWonN5jItbFwSTaWcoE8Qs +zFZokRuFJ9dy017u642mpdf6neUzjbfCjWs8+3jyFzWlkrMF3RlSTxPuksWjhXsX +dxxLNu8YWcsYRB3fODHqrlBNuDn+9kb9z8to+yq76MA0HtdDkjd/dfgghiTDJhqm +IcwhBXufwQUrOP4YiuiwM0mo7Xlhw65gnSmRcwR9ha98SV2zG5kiRYE+m+94bDbd +kGBRfhpQSzh1w09cVzmLgzkfxRShEB+bb9Ss +-----END CERTIFICATE REQUEST----- \ No newline at end of file diff --git a/s2a/src/test/resources/server_cert.pem b/s2a/src/test/resources/server_cert.pem new file mode 100644 index 00000000000..10a98cf5c21 --- /dev/null +++ b/s2a/src/test/resources/server_cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDWjCCAkKgAwIBAgIUMZkgD5gtoa39H9jdI/ijVkyxC/swDQYJKoZIhvcNAQEL +BQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MB4X +DTIzMTAxNzIzMDg1M1oXDTQzMTAxNzIzMDg1M1owFDESMBAGA1UEAwwJbG9jYWxo +b3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlPThqu8tfJ4hQKRi +Uw/vNPfo2L2LQU8NlrRL7rvV71E345LGK1h/hM3MHp5VgEvaaIibb0hSNv/TYz3H +VCQyNuPlcmkHZTJ9mB0icilUrYWdM0LPIg46iThmIQVhMiNfpMKQLDLQ7o3Jktjm +32OxnQdtYSV+7NFnw8/0pB4jiaiBYfZIMeGzEJIOFG8GSNJG0pfCI71DyLRonIcb +2XzfeDPHeWSF7lbIoMGAuKIE2mXpwHmAjTMJzIShSgLqCvmbz7wR3ZeVMknXcgcq +MmagGphy8SjizIWC5KRbrnRqF22Ouxdat6scIevRXGp5nYawFYdpK9qo+82gEouV +X3dtSQIDAQABo18wXTAbBgNVHREEFDAShxAAAAAAAAAAAAAAAAAAAAAAMB0GA1Ud +DgQWBBTKJU+NK7Q6ZPccSigRCMBCBgjkaDAfBgNVHSMEGDAWgBQBkw0X0kAYdsDe +MY567ptYW9J1aTANBgkqhkiG9w0BAQsFAAOCAQEAXuCs6MGVoND8TaJ6qaDmqtpy +wKEW2hsGclI9yv5cMS0XCVTkmKYnIoijtqv6Pdh8PfhIx5oJqJC8Ml16w4Iou4+6 +kKF0DdzdQyiM0OlNCgLYPiR4rh0ZCAFFCvOsDum1g+b9JTFZGooK4TMd9thwms4D +SqpP5v1NWf/ZLH5TYnp2CkPzBxDlnMJZphuWtPHL+78TbgQuQaKu2nMLBGBJqtFi +HDOGxckgZuwBsy0c+aC/ZwaV7FdMP42kxUZduCEx8+BDSGwPoEpz6pwVIkjiyYAm +3O8FUeEPzYzwpkANIbbEIDWV6FVH9IahKRRkE+bL3BqoQkv8SMciEA5zWsPrbA== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/s2a/src/test/resources/server_key.pem b/s2a/src/test/resources/server_key.pem new file mode 100644 index 00000000000..44f087dee94 --- /dev/null +++ b/s2a/src/test/resources/server_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCU9OGq7y18niFA +pGJTD+809+jYvYtBTw2WtEvuu9XvUTfjksYrWH+EzcwenlWAS9poiJtvSFI2/9Nj +PcdUJDI24+VyaQdlMn2YHSJyKVSthZ0zQs8iDjqJOGYhBWEyI1+kwpAsMtDujcmS +2ObfY7GdB21hJX7s0WfDz/SkHiOJqIFh9kgx4bMQkg4UbwZI0kbSl8IjvUPItGic +hxvZfN94M8d5ZIXuVsigwYC4ogTaZenAeYCNMwnMhKFKAuoK+ZvPvBHdl5UySddy +ByoyZqAamHLxKOLMhYLkpFuudGoXbY67F1q3qxwh69FcanmdhrAVh2kr2qj7zaAS +i5Vfd21JAgMBAAECggEACTBuN4hXywdKT92UP0GNZTwh/jT7QUUqNnDa+lhWI1Rk +WUK1vPjRrRSxEfZ8mdSUHbzHsf7JK6FungGyqUsuWdqHTh6SmTibLOYnONm54paK +kx38/0HXdJ2pF0Jos5ohDV3/XOqpnv3aQJfm7kMNMv3BTqvsf5mPiDHtCq7dTGGj +rGiLc0zirKZq79C6YSB1UMB01BsDl2ScflK8b3osT18uYx/BOdjLT4yZWQsU/nbB +OeF+ziWTTUAVjodGeTf+NYG7cFN/9N9PdSnAwuw8Nche3xZKbHTh2I578Zd4bsDX +H+hoMN862nzOXEvD6KyLB8xDdnEZ+p+njeDROJVmgQKBgQDQhzQEl/co1LYc5IDO +mynhCOtKJeRWBLhYEPIuaSY3qF+lrOWzqyOUNppWDx+HeKOq70X1Q+ETeSXtbaL1 +qHBkNcApQ2lStcpkR9whcVbr9NIWC8y8UQxyerEK3x3l0bZ99dfJ/z6lbxdS7prc +Hhxy6pUj8Q8AgpTZA8HfQUF1EQKBgQC23ek24kTVvWeWX2C/82H1Yfia6ITL7WHz +3aEJaZaO5JD3KmOSZgY88Ob3pkDTRYjFZND5zSB7PnM68gpo/OEDla6ZYtfwBWCX +q4QhFtv2obehobmDk+URVfvlOcBikoEP1i8oy7WdZ5CgC4gNKkkD15l68W+g5IIG +2ZOA97yUuQKBgDAzoI2TRxmUGciR9UhMy6Bt/F12ZtKPYsFQoXqi6aeh7wIP9kTS +wXWoLYLJGiOpekOv7X7lQujKbz7zweCBIAG5/wJKx9TLms4VYkgEt+/w9oMMFTZO +kc8Al14I9xNBp6p0In5Z1vRMupp79yX8e90AZpsZRLt8c8W6PZ1Kq0PRAoGBAKmD +7LzD46t/eJccs0M9CoG94Ac5pGCmHTdDLBTdnIO5vehhkwwTJ5U2e+T2aQFwY+kY +G+B1FrconQj3dk78nFoGV2Q5DJOjaHcwt7s0xZNLNj7O/HnMj3wSiP9lGcJGrP1R +P0ZCEIlph9fU2LnbiPPW2J/vT9uF+EMBTosvG9GBAoGAEVaDLLXOHj+oh1i6YY7s +0qokN2CdeKY4gG7iKjuDFb0r/l6R9uFvpUwJMhLEkF5SPQMyrzKFdnTpw3n/jnRa +AWG6GoV+D7LES+lHP5TXKKijbnHJdFjW8PtfDXHCJ6uGG91vH0TMMp1LqhcvGfTv +lcNGXkk6gUNSecxBC1uJfKE= +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 61972e30b6c..03eca809226 100644 --- a/settings.gradle +++ b/settings.gradle @@ -65,6 +65,7 @@ include ":grpc-benchmarks" include ":grpc-services" include ":grpc-servlet" include ":grpc-servlet-jakarta" +include ":grpc-s2a" include ":grpc-xds" include ":grpc-bom" include ":grpc-rls" @@ -100,6 +101,7 @@ project(':grpc-benchmarks').projectDir = "$rootDir/benchmarks" as File project(':grpc-services').projectDir = "$rootDir/services" as File project(':grpc-servlet').projectDir = "$rootDir/servlet" as File project(':grpc-servlet-jakarta').projectDir = "$rootDir/servlet/jakarta" as File +project(':grpc-s2a').projectDir = "$rootDir/s2a" as File project(':grpc-xds').projectDir = "$rootDir/xds" as File project(':grpc-bom').projectDir = "$rootDir/bom" as File project(':grpc-rls').projectDir = "$rootDir/rls" as File From 3a6be9ca1e9c8efed109b97546730852b2ab134b Mon Sep 17 00:00:00 2001 From: MV Shiva Date: Mon, 16 Sep 2024 16:32:52 +0530 Subject: [PATCH 070/103] Detect transport executors with no remaining threads (#11503) Detect misconfigured transport executors with too few threads that could further throttle the transport. Fixes #11271 --- .../io/grpc/okhttp/OkHttpClientTransport.java | 33 +++++++++++++++++++ .../okhttp/OkHttpClientTransportTest.java | 22 +++++++++++++ 2 files changed, 55 insertions(+) diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java index 29d3dbc1cdf..2f6b836dc3a 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java @@ -83,9 +83,13 @@ import java.util.Locale; import java.util.Map; import java.util.Random; +import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.CyclicBarrier; import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -499,8 +503,15 @@ public Runnable start(Listener listener) { outboundFlow = new OutboundFlowController(this, frameWriter); } final CountDownLatch latch = new CountDownLatch(1); + final CountDownLatch latchForExtraThread = new CountDownLatch(1); + // The transport needs up to two threads to function once started, + // but only needs one during handshaking. Start another thread during handshaking + // to make sure there's still a free thread available. If the number of threads is exhausted, + // it is better to kill the transport than for all the transports to hang unable to send. + CyclicBarrier barrier = new CyclicBarrier(2); // Connecting in the serializingExecutor, so that some stream operations like synStream // will be executed after connected. + serializingExecutor.execute(new Runnable() { @Override public void run() { @@ -510,8 +521,14 @@ public void run() { // initial preface. try { latch.await(); + barrier.await(1000, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); + } catch (TimeoutException | BrokenBarrierException e) { + startGoAway(0, ErrorCode.INTERNAL_ERROR, Status.UNAVAILABLE + .withDescription("Timed out waiting for second handshake thread. " + + "The transport executor pool may have run out of threads")); + return; } // Use closed source on failure so that the reader immediately shuts down. BufferedSource source = Okio.buffer(new Source() { @@ -575,6 +592,7 @@ sslSocketFactory, hostnameVerifier, sock, getOverridenHost(), getOverridenPort() return; } finally { clientFrameHandler = new ClientFrameHandler(variant.newReader(source, true)); + latchForExtraThread.countDown(); } synchronized (lock) { socket = Preconditions.checkNotNull(sock, "socket"); @@ -584,6 +602,21 @@ sslSocketFactory, hostnameVerifier, sock, getOverridenHost(), getOverridenPort() } } }); + + executor.execute(new Runnable() { + @Override + public void run() { + try { + barrier.await(1000, TimeUnit.MILLISECONDS); + latchForExtraThread.await(); + } catch (BrokenBarrierException | TimeoutException e) { + // Something bad happened, maybe too few threads available! + // This will be handled in the handshake thread. + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + }); // Schedule to send connection preface & settings before any other write. try { sendConnectionPrefaceAndSettings(); diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java index 987cc09203e..daf5073992e 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java @@ -247,6 +247,28 @@ public void testToString() throws Exception { assertTrue("Unexpected: " + s, s.contains(address.toString())); } + @Test + public void testTransportExecutorWithTooFewThreads() throws Exception { + ExecutorService fixedPoolExecutor = Executors.newFixedThreadPool(1); + channelBuilder.transportExecutor(fixedPoolExecutor); + InetSocketAddress address = InetSocketAddress.createUnresolved("hostname", 31415); + clientTransport = new OkHttpClientTransport( + channelBuilder.buildTransportFactory(), + address, + "hostname", + null, + EAG_ATTRS, + NO_PROXY, + tooManyPingsRunnable); + clientTransport.start(transportListener); + ArgumentCaptor statusCaptor = ArgumentCaptor.forClass(Status.class); + verify(transportListener, timeout(TIME_OUT_MS)).transportShutdown(statusCaptor.capture()); + Status capturedStatus = statusCaptor.getValue(); + assertEquals("Timed out waiting for second handshake thread. " + + "The transport executor pool may have run out of threads", + capturedStatus.getDescription()); + } + /** * Test logging is functioning correctly for client received Http/2 frames. Not intended to test * actual frame content being logged. From 5bec9096a2318f7b29022b8c91ea7168f0ddc177 Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Mon, 16 Sep 2024 14:43:27 -0700 Subject: [PATCH 071/103] Otel server context interceptor (#11500) Add opentelemetry tracing API, guarded by environmental variable(disabled by default). Use server interceptor to explicitly propagate span to the application thread. --- opentelemetry/build.gradle | 6 +- .../grpc/opentelemetry/GrpcOpenTelemetry.java | 26 +++ .../InternalGrpcOpenTelemetry.java | 4 + .../OpenTelemetryTracingModule.java | 93 ++++++++- .../opentelemetry/GrpcOpenTelemetryTest.java | 55 ++++++ .../OpenTelemetryTracingModuleTest.java | 181 +++++++++++++++++- 6 files changed, 357 insertions(+), 8 deletions(-) diff --git a/opentelemetry/build.gradle b/opentelemetry/build.gradle index 509960e5dbc..00d913c280d 100644 --- a/opentelemetry/build.gradle +++ b/opentelemetry/build.gradle @@ -14,8 +14,10 @@ dependencies { libraries.opentelemetry.api, libraries.auto.value.annotations - testImplementation testFixtures(project(':grpc-core')), - project(':grpc-testing'), + testImplementation project(':grpc-testing'), + project(':grpc-inprocess'), + testFixtures(project(':grpc-core')), + testFixtures(project(':grpc-api')), libraries.opentelemetry.sdk.testing, libraries.assertj.core // opentelemetry.sdk.testing uses compileOnly for assertj diff --git a/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java b/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java index 03183ef4920..6b257a18a6c 100644 --- a/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java +++ b/opentelemetry/src/main/java/io/grpc/opentelemetry/GrpcOpenTelemetry.java @@ -33,10 +33,12 @@ import io.grpc.ManagedChannelBuilder; import io.grpc.MetricSink; import io.grpc.ServerBuilder; +import io.grpc.internal.GrpcUtil; import io.grpc.opentelemetry.internal.OpenTelemetryConstants; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.api.metrics.MeterProvider; +import io.opentelemetry.api.trace.Tracer; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -61,6 +63,10 @@ public Stopwatch get() { } }; + @VisibleForTesting + static boolean ENABLE_OTEL_TRACING = GrpcUtil.getFlag("GRPC_EXPERIMENTAL_ENABLE_OTEL_TRACING", + false); + private final OpenTelemetry openTelemetrySdk; private final MeterProvider meterProvider; private final Meter meter; @@ -68,6 +74,7 @@ public Stopwatch get() { private final boolean disableDefault; private final OpenTelemetryMetricsResource resource; private final OpenTelemetryMetricsModule openTelemetryMetricsModule; + private final OpenTelemetryTracingModule openTelemetryTracingModule; private final List optionalLabels; private final MetricSink sink; @@ -88,6 +95,7 @@ private GrpcOpenTelemetry(Builder builder) { this.optionalLabels = ImmutableList.copyOf(builder.optionalLabels); this.openTelemetryMetricsModule = new OpenTelemetryMetricsModule( STOPWATCH_SUPPLIER, resource, optionalLabels, builder.plugins); + this.openTelemetryTracingModule = new OpenTelemetryTracingModule(openTelemetrySdk); this.sink = new OpenTelemetryMetricSink(meter, enableMetrics, disableDefault, optionalLabels); } @@ -125,6 +133,11 @@ MetricSink getSink() { return sink; } + @VisibleForTesting + Tracer getTracer() { + return this.openTelemetryTracingModule.getTracer(); + } + /** * Registers GrpcOpenTelemetry globally, applying its configuration to all subsequently created * gRPC channels and servers. @@ -152,6 +165,9 @@ public void configureChannelBuilder(ManagedChannelBuilder builder) { InternalManagedChannelBuilder.addMetricSink(builder, sink); InternalManagedChannelBuilder.interceptWithTarget( builder, openTelemetryMetricsModule::getClientInterceptor); + if (ENABLE_OTEL_TRACING) { + builder.intercept(openTelemetryTracingModule.getClientInterceptor()); + } } /** @@ -161,6 +177,11 @@ public void configureChannelBuilder(ManagedChannelBuilder builder) { */ public void configureServerBuilder(ServerBuilder serverBuilder) { serverBuilder.addStreamTracerFactory(openTelemetryMetricsModule.getServerTracerFactory()); + if (ENABLE_OTEL_TRACING) { + serverBuilder.addStreamTracerFactory( + openTelemetryTracingModule.getServerTracerFactory()); + serverBuilder.intercept(openTelemetryTracingModule.getServerSpanPropagationInterceptor()); + } } @VisibleForTesting @@ -342,6 +363,11 @@ public Builder disableAllMetrics() { return this; } + Builder enableTracing(boolean enable) { + ENABLE_OTEL_TRACING = enable; + return this; + } + /** * Returns a new {@link GrpcOpenTelemetry} built with the configuration of this {@link * Builder}. diff --git a/opentelemetry/src/main/java/io/grpc/opentelemetry/InternalGrpcOpenTelemetry.java b/opentelemetry/src/main/java/io/grpc/opentelemetry/InternalGrpcOpenTelemetry.java index 5d5543dddda..ea1e7ab803f 100644 --- a/opentelemetry/src/main/java/io/grpc/opentelemetry/InternalGrpcOpenTelemetry.java +++ b/opentelemetry/src/main/java/io/grpc/opentelemetry/InternalGrpcOpenTelemetry.java @@ -29,4 +29,8 @@ public static void builderPlugin( GrpcOpenTelemetry.Builder builder, InternalOpenTelemetryPlugin plugin) { builder.plugin(plugin); } + + public static void enableTracing(GrpcOpenTelemetry.Builder builder, boolean enable) { + builder.enableTracing(enable); + } } diff --git a/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryTracingModule.java b/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryTracingModule.java index 11659c87708..6f2d3268ae0 100644 --- a/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryTracingModule.java +++ b/opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryTracingModule.java @@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static io.grpc.ClientStreamTracer.NAME_RESOLUTION_DELAYED; +import static io.grpc.internal.GrpcUtil.IMPLEMENTATION_VERSION; import com.google.common.annotations.VisibleForTesting; import io.grpc.Attributes; @@ -28,15 +29,21 @@ import io.grpc.ClientStreamTracer; import io.grpc.ForwardingClientCall.SimpleForwardingClientCall; import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener; +import io.grpc.ForwardingServerCallListener; import io.grpc.Metadata; import io.grpc.MethodDescriptor; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; import io.grpc.ServerStreamTracer; +import io.grpc.opentelemetry.internal.OpenTelemetryConstants; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; import io.opentelemetry.context.propagation.ContextPropagators; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.logging.Level; @@ -50,7 +57,7 @@ final class OpenTelemetryTracingModule { private static final Logger logger = Logger.getLogger(OpenTelemetryTracingModule.class.getName()); @VisibleForTesting - static final String OTEL_TRACING_SCOPE_NAME = "grpc-java"; + final io.grpc.Context.Key otelSpan = io.grpc.Context.key("opentelemetry-span-key"); @Nullable private static final AtomicIntegerFieldUpdater callEndedUpdater; @Nullable @@ -83,13 +90,23 @@ final class OpenTelemetryTracingModule { private final MetadataGetter metadataGetter = MetadataGetter.getInstance(); private final MetadataSetter metadataSetter = MetadataSetter.getInstance(); private final TracingClientInterceptor clientInterceptor = new TracingClientInterceptor(); + private final ServerInterceptor serverSpanPropagationInterceptor = + new TracingServerSpanPropagationInterceptor(); private final ServerTracerFactory serverTracerFactory = new ServerTracerFactory(); OpenTelemetryTracingModule(OpenTelemetry openTelemetry) { - this.otelTracer = checkNotNull(openTelemetry.getTracer(OTEL_TRACING_SCOPE_NAME), "otelTracer"); + this.otelTracer = checkNotNull(openTelemetry.getTracerProvider(), "tracerProvider") + .tracerBuilder(OpenTelemetryConstants.INSTRUMENTATION_SCOPE) + .setInstrumentationVersion(IMPLEMENTATION_VERSION) + .build(); this.contextPropagators = checkNotNull(openTelemetry.getPropagators(), "contextPropagators"); } + @VisibleForTesting + Tracer getTracer() { + return otelTracer; + } + /** * Creates a {@link CallAttemptsTracerFactory} for a new call. */ @@ -112,6 +129,10 @@ ClientInterceptor getClientInterceptor() { return clientInterceptor; } + ServerInterceptor getServerSpanPropagationInterceptor() { + return serverSpanPropagationInterceptor; + } + @VisibleForTesting final class CallAttemptsTracerFactory extends ClientStreamTracer.Factory { volatile int callEnded; @@ -252,6 +273,11 @@ public void streamClosed(io.grpc.Status status) { endSpanWithStatus(span, status); } + @Override + public io.grpc.Context filterContext(io.grpc.Context context) { + return context.withValue(otelSpan, span); + } + @Override public void outboundMessageSent( int seqNo, long optionalWireSize, long optionalUncompressedSize) { @@ -293,6 +319,69 @@ public ServerStreamTracer newServerStreamTracer(String fullMethodName, Metadata } } + @VisibleForTesting + final class TracingServerSpanPropagationInterceptor implements ServerInterceptor { + @Override + public ServerCall.Listener interceptCall(ServerCall call, + Metadata headers, ServerCallHandler next) { + Span span = otelSpan.get(io.grpc.Context.current()); + if (span == null) { + logger.log(Level.FINE, "Server span not found. ServerTracerFactory for server " + + "tracing must be set."); + return next.startCall(call, headers); + } + Context serverCallContext = Context.current().with(span); + try (Scope scope = serverCallContext.makeCurrent()) { + return new ContextServerCallListener<>(next.startCall(call, headers), serverCallContext); + } + } + } + + private static class ContextServerCallListener extends + ForwardingServerCallListener.SimpleForwardingServerCallListener { + private final Context context; + + protected ContextServerCallListener(ServerCall.Listener delegate, Context context) { + super(delegate); + this.context = checkNotNull(context, "context"); + } + + @Override + public void onMessage(ReqT message) { + try (Scope scope = context.makeCurrent()) { + delegate().onMessage(message); + } + } + + @Override + public void onHalfClose() { + try (Scope scope = context.makeCurrent()) { + delegate().onHalfClose(); + } + } + + @Override + public void onCancel() { + try (Scope scope = context.makeCurrent()) { + delegate().onCancel(); + } + } + + @Override + public void onComplete() { + try (Scope scope = context.makeCurrent()) { + delegate().onComplete(); + } + } + + @Override + public void onReady() { + try (Scope scope = context.makeCurrent()) { + delegate().onReady(); + } + } + } + @VisibleForTesting final class TracingClientInterceptor implements ClientInterceptor { diff --git a/opentelemetry/src/test/java/io/grpc/opentelemetry/GrpcOpenTelemetryTest.java b/opentelemetry/src/test/java/io/grpc/opentelemetry/GrpcOpenTelemetryTest.java index e4a0fa46e8b..1ae7b755a48 100644 --- a/opentelemetry/src/test/java/io/grpc/opentelemetry/GrpcOpenTelemetryTest.java +++ b/opentelemetry/src/test/java/io/grpc/opentelemetry/GrpcOpenTelemetryTest.java @@ -17,15 +17,26 @@ package io.grpc.opentelemetry; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import com.google.common.collect.ImmutableList; +import io.grpc.ClientInterceptor; +import io.grpc.ManagedChannelBuilder; import io.grpc.MetricSink; +import io.grpc.ServerBuilder; import io.grpc.internal.GrpcUtil; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import io.opentelemetry.sdk.trace.SdkTracerProvider; import java.util.Arrays; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -35,7 +46,19 @@ public class GrpcOpenTelemetryTest { private final InMemoryMetricReader inMemoryMetricReader = InMemoryMetricReader.create(); private final SdkMeterProvider meterProvider = SdkMeterProvider.builder().registerMetricReader(inMemoryMetricReader).build(); + private final SdkTracerProvider tracerProvider = SdkTracerProvider.builder().build(); private final OpenTelemetry noopOpenTelemetry = OpenTelemetry.noop(); + private boolean originalEnableOtelTracing; + + @Before + public void setup() { + originalEnableOtelTracing = GrpcOpenTelemetry.ENABLE_OTEL_TRACING; + } + + @After + public void tearDown() { + GrpcOpenTelemetry.ENABLE_OTEL_TRACING = originalEnableOtelTracing; + } @Test public void build() { @@ -56,6 +79,31 @@ public void build() { assertThat(openTelemetryModule.getOptionalLabels()).isEqualTo(ImmutableList.of("version")); } + @Test + public void buildTracer() { + OpenTelemetrySdk sdk = + OpenTelemetrySdk.builder().setTracerProvider(tracerProvider).build(); + + GrpcOpenTelemetry grpcOpenTelemetry = GrpcOpenTelemetry.newBuilder() + .enableTracing(true) + .sdk(sdk).build(); + + assertThat(grpcOpenTelemetry.getOpenTelemetryInstance()).isSameInstanceAs(sdk); + assertThat(grpcOpenTelemetry.getTracer()).isSameInstanceAs( + tracerProvider.tracerBuilder("grpc-java") + .setInstrumentationVersion(GrpcUtil.IMPLEMENTATION_VERSION) + .build()); + ServerBuilder mockServerBuiler = mock(ServerBuilder.class); + grpcOpenTelemetry.configureServerBuilder(mockServerBuiler); + verify(mockServerBuiler, times(2)).addStreamTracerFactory(any()); + verify(mockServerBuiler).intercept(any()); + verifyNoMoreInteractions(mockServerBuiler); + + ManagedChannelBuilder mockChannelBuilder = mock(ManagedChannelBuilder.class); + grpcOpenTelemetry.configureChannelBuilder(mockChannelBuilder); + verify(mockChannelBuilder).intercept(any(ClientInterceptor.class)); + } + @Test public void builderDefaults() { GrpcOpenTelemetry module = GrpcOpenTelemetry.newBuilder().build(); @@ -73,6 +121,13 @@ public void builderDefaults() { assertThat(module.getEnableMetrics()).isEmpty(); assertThat(module.getOptionalLabels()).isEmpty(); assertThat(module.getSink()).isInstanceOf(MetricSink.class); + + assertThat(module.getTracer()).isSameInstanceAs(noopOpenTelemetry + .getTracerProvider() + .tracerBuilder("grpc-java") + .setInstrumentationVersion(GrpcUtil.IMPLEMENTATION_VERSION) + .build() + ); } @Test diff --git a/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryTracingModuleTest.java b/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryTracingModuleTest.java index 68cba17e802..89e79d55d25 100644 --- a/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryTracingModuleTest.java +++ b/opentelemetry/src/test/java/io/grpc/opentelemetry/OpenTelemetryTracingModuleTest.java @@ -17,13 +17,14 @@ package io.grpc.opentelemetry; import static io.grpc.ClientStreamTracer.NAME_RESOLUTION_DELAYED; -import static io.grpc.opentelemetry.OpenTelemetryTracingModule.OTEL_TRACING_SCOPE_NAME; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -38,14 +39,23 @@ import io.grpc.ClientInterceptor; import io.grpc.ClientInterceptors; import io.grpc.ClientStreamTracer; +import io.grpc.ManagedChannel; import io.grpc.Metadata; import io.grpc.MethodDescriptor; +import io.grpc.NoopServerCall; +import io.grpc.Server; import io.grpc.ServerCall; import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; +import io.grpc.ServerInterceptors; import io.grpc.ServerServiceDefinition; import io.grpc.ServerStreamTracer; import io.grpc.Status; +import io.grpc.inprocess.InProcessChannelBuilder; +import io.grpc.inprocess.InProcessServerBuilder; import io.grpc.opentelemetry.OpenTelemetryTracingModule.CallAttemptsTracerFactory; +import io.grpc.opentelemetry.internal.OpenTelemetryConstants; +import io.grpc.testing.GrpcCleanupRule; import io.grpc.testing.GrpcServerRule; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.trace.Span; @@ -54,6 +64,8 @@ import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.TraceId; import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.TracerBuilder; +import io.opentelemetry.api.trace.TracerProvider; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.context.propagation.ContextPropagators; @@ -130,6 +142,8 @@ public String parse(InputStream stream) { public final OpenTelemetryRule openTelemetryRule = OpenTelemetryRule.create(); @Rule public final GrpcServerRule grpcServerRule = new GrpcServerRule().directExecutor(); + @Rule + public final GrpcCleanupRule grpcCleanupRule = new GrpcCleanupRule(); private Tracer tracerRule; @Mock private Tracer mockTracer; @@ -156,8 +170,15 @@ public String parse(InputStream stream) { @Before public void setUp() { - tracerRule = openTelemetryRule.getOpenTelemetry().getTracer(OTEL_TRACING_SCOPE_NAME); - when(mockOpenTelemetry.getTracer(OTEL_TRACING_SCOPE_NAME)).thenReturn(mockTracer); + tracerRule = openTelemetryRule.getOpenTelemetry().getTracer( + OpenTelemetryConstants.INSTRUMENTATION_SCOPE); + TracerProvider mockTracerProvider = mock(TracerProvider.class); + when(mockOpenTelemetry.getTracerProvider()).thenReturn(mockTracerProvider); + TracerBuilder mockTracerBuilder = mock(TracerBuilder.class); + when(mockTracerProvider.tracerBuilder(OpenTelemetryConstants.INSTRUMENTATION_SCOPE)) + .thenReturn(mockTracerBuilder); + when(mockTracerBuilder.setInstrumentationVersion(any())).thenReturn(mockTracerBuilder); + when(mockTracerBuilder.build()).thenReturn(mockTracer); when(mockOpenTelemetry.getPropagators()).thenReturn(ContextPropagators.create(mockPropagator)); when(mockSpanBuilder.startSpan()).thenReturn(mockAttemptSpan); when(mockSpanBuilder.setParent(any())).thenReturn(mockSpanBuilder); @@ -451,7 +472,8 @@ public ClientCall interceptCall( @Test public void clientStreamNeverCreatedStillRecordTracing() { - OpenTelemetryTracingModule tracingModule = new OpenTelemetryTracingModule(mockOpenTelemetry); + OpenTelemetryTracingModule tracingModule = new OpenTelemetryTracingModule( + openTelemetryRule.getOpenTelemetry()); CallAttemptsTracerFactory callTracer = tracingModule.newClientCallTracer(mockClientSpan, method); @@ -570,6 +592,157 @@ public void grpcTraceBinPropagator() { Span.fromContext(contextArgumentCaptor.getValue()).getSpanContext()); } + @Test + public void testServerParentSpanPropagation() throws Exception { + final AtomicReference applicationSpan = new AtomicReference<>(); + OpenTelemetryTracingModule tracingModule = new OpenTelemetryTracingModule( + openTelemetryRule.getOpenTelemetry()); + ServerServiceDefinition serviceDefinition = + ServerServiceDefinition.builder("package1.service2").addMethod( + method, new ServerCallHandler() { + @Override + public ServerCall.Listener startCall( + ServerCall call, Metadata headers) { + applicationSpan.set(Span.fromContext(Context.current())); + call.sendHeaders(new Metadata()); + call.sendMessage("Hello"); + call.close( + Status.PERMISSION_DENIED.withDescription("No you don't"), new Metadata()); + return mockServerCallListener; + } + }).build(); + + Server server = InProcessServerBuilder.forName("test-server-span") + .addService( + ServerInterceptors.intercept(serviceDefinition, + tracingModule.getServerSpanPropagationInterceptor())) + .addStreamTracerFactory(tracingModule.getServerTracerFactory()) + .directExecutor().build().start(); + grpcCleanupRule.register(server); + + ManagedChannel channel = InProcessChannelBuilder.forName("test-server-span") + .directExecutor().build(); + grpcCleanupRule.register(channel); + + Span parentSpan = tracerRule.spanBuilder("test-parent-span").startSpan(); + try (Scope scope = Context.current().with(parentSpan).makeCurrent()) { + Channel interceptedChannel = + ClientInterceptors.intercept( + channel, tracingModule.getClientInterceptor()); + ClientCall call = interceptedChannel.newCall(method, CALL_OPTIONS); + Metadata headers = new Metadata(); + call.start(mockClientCallListener, headers); + + // End the call + call.halfClose(); + call.request(1); + parentSpan.end(); + } + + verify(mockClientCallListener).onClose(statusCaptor.capture(), any(Metadata.class)); + Status rpcStatus = statusCaptor.getValue(); + assertEquals(rpcStatus.getCode(), Status.Code.PERMISSION_DENIED); + assertEquals(rpcStatus.getDescription(), "No you don't"); + assertEquals(applicationSpan.get().getSpanContext().getTraceId(), + parentSpan.getSpanContext().getTraceId()); + + List spans = openTelemetryRule.getSpans(); + assertEquals(spans.size(), 4); + SpanData clientSpan = spans.get(2); + SpanData attemptSpan = spans.get(1); + + assertEquals(clientSpan.getName(), "Sent.package1.service2.method3"); + assertTrue(clientSpan.hasEnded()); + assertEquals(clientSpan.getStatus().getStatusCode(), StatusCode.ERROR); + assertEquals(clientSpan.getStatus().getDescription(), "PERMISSION_DENIED: No you don't"); + + assertEquals(attemptSpan.getName(), "Attempt.package1.service2.method3"); + assertTrue(attemptSpan.hasEnded()); + assertEquals(attemptSpan.getStatus().getStatusCode(), StatusCode.ERROR); + assertEquals(attemptSpan.getStatus().getDescription(), "PERMISSION_DENIED: No you don't"); + + SpanData serverSpan = spans.get(0); + assertEquals(serverSpan.getName(), "Recv.package1.service2.method3"); + assertTrue(serverSpan.hasEnded()); + assertEquals(serverSpan.getStatus().getStatusCode(), StatusCode.ERROR); + assertEquals(serverSpan.getStatus().getDescription(), "PERMISSION_DENIED: No you don't"); + } + + @Test + public void serverSpanPropagationInterceptor() throws Exception { + OpenTelemetryTracingModule tracingModule = new OpenTelemetryTracingModule( + openTelemetryRule.getOpenTelemetry()); + Server server = InProcessServerBuilder.forName("test-span-propagation-interceptor") + .directExecutor().build().start(); + grpcCleanupRule.register(server); + final AtomicReference callbackSpan = new AtomicReference<>(); + ServerCall.Listener getContextListener = new ServerCall.Listener() { + @Override + public void onMessage(Integer message) { + callbackSpan.set(Span.fromContext(Context.current())); + } + + @Override + public void onHalfClose() { + callbackSpan.set(Span.fromContext(Context.current())); + } + + @Override + public void onCancel() { + callbackSpan.set(Span.fromContext(Context.current())); + } + + @Override + public void onComplete() { + callbackSpan.set(Span.fromContext(Context.current())); + } + }; + ServerInterceptor interceptor = tracingModule.getServerSpanPropagationInterceptor(); + @SuppressWarnings("unchecked") + ServerCallHandler handler = mock(ServerCallHandler.class); + when(handler.startCall(any(), any())).thenReturn(getContextListener); + ServerCall call = new NoopServerCall<>(); + Metadata metadata = new Metadata(); + ServerCall.Listener listener = interceptor.interceptCall(call, metadata, handler); + verify(handler).startCall(same(call), same(metadata)); + listener.onMessage(1); + assertEquals(callbackSpan.get(), Span.getInvalid()); + listener.onReady(); + assertEquals(callbackSpan.get(), Span.getInvalid()); + listener.onCancel(); + assertEquals(callbackSpan.get(), Span.getInvalid()); + listener.onHalfClose(); + assertEquals(callbackSpan.get(), Span.getInvalid()); + listener.onComplete(); + assertEquals(callbackSpan.get(), Span.getInvalid()); + + Span parentSpan = tracerRule.spanBuilder("parent-span").startSpan(); + io.grpc.Context context = io.grpc.Context.current().withValue( + tracingModule.otelSpan, parentSpan); + io.grpc.Context previous = context.attach(); + try { + listener = interceptor.interceptCall(call, metadata, handler); + verify(handler, times(2)).startCall(same(call), same(metadata)); + listener.onMessage(1); + assertEquals(callbackSpan.get().getSpanContext().getTraceId(), + parentSpan.getSpanContext().getTraceId()); + listener.onReady(); + assertEquals(callbackSpan.get().getSpanContext().getTraceId(), + parentSpan.getSpanContext().getTraceId()); + listener.onCancel(); + assertEquals(callbackSpan.get().getSpanContext().getTraceId(), + parentSpan.getSpanContext().getTraceId()); + listener.onHalfClose(); + assertEquals(callbackSpan.get().getSpanContext().getTraceId(), + parentSpan.getSpanContext().getTraceId()); + listener.onComplete(); + assertEquals(callbackSpan.get().getSpanContext().getTraceId(), + parentSpan.getSpanContext().getTraceId()); + } finally { + context.detach(previous); + } + } + @Test public void generateTraceSpanName() { assertEquals( From ce33df4a6f646621ef03f4ba6a6d76bd0bae67e6 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Mon, 16 Sep 2024 14:54:08 -0700 Subject: [PATCH 072/103] s2a: Use new-style syntax for plugins and remove unused deps There may be more unused deps, but #11527 makes it far too painful for me to bother to clean it up more. --- s2a/build.gradle | 42 ++---------------------------------------- 1 file changed, 2 insertions(+), 40 deletions(-) diff --git a/s2a/build.gradle b/s2a/build.gradle index 403ac93552f..234f983fd5c 100644 --- a/s2a/build.gradle +++ b/s2a/build.gradle @@ -1,22 +1,15 @@ -buildscript { - dependencies { - classpath 'com.google.gradle:osdetector-gradle-plugin:1.4.0' - } -} - plugins { id "java-library" id "maven-publish" id "com.github.johnrengelman.shadow" + id "com.google.osdetector" id "com.google.protobuf" id "ru.vyarus.animalsniffer" } description = "gRPC: S2A" -apply plugin: "com.google.osdetector" - dependencies { api project(':grpc-api') @@ -24,7 +17,6 @@ dependencies { project(':grpc-protobuf'), project(':grpc-core'), libraries.protobuf.java, - libraries.conscrypt, libraries.guava.jre // JRE required by protobuf-java-util from grpclb def nettyDependency = implementation project(':grpc-netty') compileOnly libraries.javax.annotation @@ -36,12 +28,7 @@ dependencies { project(':grpc-testing'), project(':grpc-testing-proto'), testFixtures(project(':grpc-core')), - libraries.guava, - libraries.junit, - libraries.mockito.core, - libraries.truth, - libraries.conscrypt, - libraries.netty.transport.epoll + libraries.guava testImplementation 'com.google.truth:truth:1.4.2' testImplementation 'com.google.truth.extensions:truth-proto-extension:1.4.2' @@ -74,35 +61,10 @@ dependencies { classifier = "windows-x86_64" } } - testRuntimeOnly (libraries.netty.transport.epoll) { - artifact { - classifier = "linux-x86_64" - } - } signature libraries.signature.java } -tasks.named("compileJava") { - dependsOn(tasks.named("generateProto")) - //dependsOn(tasks.named("syncGeneratedSourcesmain")) -} - - -tasks.named("sourcesJar") { - dependsOn(tasks.named("generateProto")) - //dependsOn(tasks.named("syncGeneratedSourcesmain")) -} - -sourceSets { - main { - //java.srcDirs += "src/generated/main/java" - //java.srcDirs += "src/generated/main/grpc" - } -} -//println sourceSets.main.java.srcDirs -//println sourceSets.test.resources.srcDirs - configureProtoCompilation() tasks.named("javadoc").configure { From bdc0530e1db16d98702da348796ef4e71c301b13 Mon Sep 17 00:00:00 2001 From: Larry Safran Date: Tue, 17 Sep 2024 11:12:27 -0700 Subject: [PATCH 073/103] Fix slow tests that took 40 seconds to get through tearDown. (#11530) --- .../java/io/grpc/s2a/handshaker/IntegrationTest.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java b/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java index 859771a4afa..d9224f69b91 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java +++ b/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java @@ -180,14 +180,15 @@ public void setUp() throws Exception { @After public void tearDown() throws Exception { - server.awaitTermination(10, SECONDS); server.shutdown(); - s2aServer.awaitTermination(10, SECONDS); s2aServer.shutdown(); - s2aDelayServer.awaitTermination(10, SECONDS); s2aDelayServer.shutdown(); - mtlsS2AServer.awaitTermination(10, SECONDS); mtlsS2AServer.shutdown(); + + server.awaitTermination(10, SECONDS); + s2aServer.awaitTermination(10, SECONDS); + s2aDelayServer.awaitTermination(10, SECONDS); + mtlsS2AServer.awaitTermination(10, SECONDS); } @Test From 9b0c19e698f0aa28b10f1c6513b5fb51b3e21954 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Mon, 16 Sep 2024 18:16:07 -0700 Subject: [PATCH 074/103] s2a: Cleanups to IntegrationTest Move unused and unimportant fields to local variables. pickUnusedPort() is inherently racy, so avoid using it when unnecessary. The channel's default executor is fine to use, but if you don't like it directExecutor() would be an option too. But blocking stub doesn't even use the executor for unary RPCs. Thread.join() does not propagate exceptions from the Thread; it just waits for the thread to exit. --- .../grpc/s2a/handshaker/IntegrationTest.java | 86 ++++++------------- 1 file changed, 27 insertions(+), 59 deletions(-) diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java b/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java index d9224f69b91..19dda7a19e4 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java +++ b/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java @@ -44,9 +44,7 @@ import io.netty.handler.ssl.SslProvider; import java.io.ByteArrayInputStream; import java.io.File; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.logging.Level; +import java.util.concurrent.FutureTask; import java.util.logging.Logger; import javax.net.ssl.SSLException; import javax.net.ssl.SSLSessionContext; @@ -127,28 +125,21 @@ public final class IntegrationTest { + "-----END PRIVATE KEY-----"; private String s2aAddress; - private int s2aPort; private Server s2aServer; private String s2aDelayAddress; - private int s2aDelayPort; private Server s2aDelayServer; private String mtlsS2AAddress; - private int mtlsS2APort; private Server mtlsS2AServer; - private int serverPort; private String serverAddress; private Server server; @Before public void setUp() throws Exception { - s2aPort = Utils.pickUnusedPort(); + s2aServer = ServerBuilder.forPort(0).addService(new FakeS2AServer()).build().start(); + int s2aPort = s2aServer.getPort(); s2aAddress = "localhost:" + s2aPort; - s2aServer = ServerBuilder.forPort(s2aPort).addService(new FakeS2AServer()).build(); logger.info("S2A service listening on localhost:" + s2aPort); - s2aServer.start(); - mtlsS2APort = Utils.pickUnusedPort(); - mtlsS2AAddress = "localhost:" + mtlsS2APort; File s2aCert = new File("src/test/resources/server_cert.pem"); File s2aKey = new File("src/test/resources/server_key.pem"); File rootCert = new File("src/test/resources/root_cert.pem"); @@ -158,24 +149,25 @@ public void setUp() throws Exception { .trustManager(rootCert) .clientAuth(TlsServerCredentials.ClientAuth.REQUIRE) .build(); - mtlsS2AServer = - NettyServerBuilder.forPort(mtlsS2APort, s2aCreds).addService(new FakeS2AServer()).build(); - logger.info("mTLS S2A service listening on localhost:" + mtlsS2APort); + mtlsS2AServer = NettyServerBuilder.forPort(0, s2aCreds).addService(new FakeS2AServer()).build(); mtlsS2AServer.start(); + int mtlsS2APort = mtlsS2AServer.getPort(); + mtlsS2AAddress = "localhost:" + mtlsS2APort; + logger.info("mTLS S2A service listening on localhost:" + mtlsS2APort); - s2aDelayPort = Utils.pickUnusedPort(); + int s2aDelayPort = Utils.pickUnusedPort(); s2aDelayAddress = "localhost:" + s2aDelayPort; s2aDelayServer = ServerBuilder.forPort(s2aDelayPort).addService(new FakeS2AServer()).build(); - serverPort = Utils.pickUnusedPort(); - serverAddress = "localhost:" + serverPort; server = - NettyServerBuilder.forPort(serverPort) + NettyServerBuilder.forPort(0) .addService(new SimpleServiceImpl()) .sslContext(buildSslContext()) - .build(); + .build() + .start(); + int serverPort = server.getPort(); + serverAddress = "localhost:" + serverPort; logger.info("Simple Service listening on localhost:" + serverPort); - server.start(); } @After @@ -193,28 +185,23 @@ public void tearDown() throws Exception { @Test public void clientCommunicateUsingS2ACredentials_succeeds() throws Exception { - ExecutorService executor = Executors.newSingleThreadExecutor(); ChannelCredentials credentials = S2AChannelCredentials.createBuilder(s2aAddress).setLocalSpiffeId("test-spiffe-id").build(); - ManagedChannel channel = - Grpc.newChannelBuilder(serverAddress, credentials).executor(executor).build(); + ManagedChannel channel = Grpc.newChannelBuilder(serverAddress, credentials).build(); - assertThat(doUnaryRpc(executor, channel)).isTrue(); + assertThat(doUnaryRpc(channel)).isTrue(); } @Test public void clientCommunicateUsingS2ACredentialsNoLocalIdentity_succeeds() throws Exception { - ExecutorService executor = Executors.newSingleThreadExecutor(); ChannelCredentials credentials = S2AChannelCredentials.createBuilder(s2aAddress).build(); - ManagedChannel channel = - Grpc.newChannelBuilder(serverAddress, credentials).executor(executor).build(); + ManagedChannel channel = Grpc.newChannelBuilder(serverAddress, credentials).build(); - assertThat(doUnaryRpc(executor, channel)).isTrue(); + assertThat(doUnaryRpc(channel)).isTrue(); } @Test public void clientCommunicateUsingMtlsToS2ACredentials_succeeds() throws Exception { - ExecutorService executor = Executors.newSingleThreadExecutor(); ChannelCredentials credentials = MtlsToS2AChannelCredentials.createBuilder( /* s2aAddress= */ mtlsS2AAddress, @@ -224,41 +211,24 @@ public void clientCommunicateUsingMtlsToS2ACredentials_succeeds() throws Excepti .build() .setLocalSpiffeId("test-spiffe-id") .build(); - ManagedChannel channel = - Grpc.newChannelBuilder(serverAddress, credentials).executor(executor).build(); + ManagedChannel channel = Grpc.newChannelBuilder(serverAddress, credentials).build(); - assertThat(doUnaryRpc(executor, channel)).isTrue(); + assertThat(doUnaryRpc(channel)).isTrue(); } @Test public void clientCommunicateUsingS2ACredentials_s2AdelayStart_succeeds() throws Exception { - DoUnaryRpc doUnaryRpc = new DoUnaryRpc(); - doUnaryRpc.start(); + ChannelCredentials credentials = S2AChannelCredentials.createBuilder(s2aDelayAddress).build(); + ManagedChannel channel = Grpc.newChannelBuilder(serverAddress, credentials).build(); + + FutureTask rpc = new FutureTask<>(() -> doUnaryRpc(channel)); + new Thread(rpc).start(); Thread.sleep(2000); s2aDelayServer.start(); - doUnaryRpc.join(); - } - - private class DoUnaryRpc extends Thread { - @Override - public void run() { - ExecutorService executor = Executors.newSingleThreadExecutor(); - ChannelCredentials credentials = S2AChannelCredentials.createBuilder(s2aDelayAddress).build(); - ManagedChannel channel = - Grpc.newChannelBuilder(serverAddress, credentials).executor(executor).build(); - boolean result = false; - try { - result = doUnaryRpc(executor, channel); - } catch (InterruptedException e) { - logger.log(Level.SEVERE, "Failed to do unary rpc", e); - result = false; - } - assertThat(result).isTrue(); - } + assertThat(rpc.get()).isTrue(); } - public static boolean doUnaryRpc(ExecutorService executor, ManagedChannel channel) - throws InterruptedException { + public static boolean doUnaryRpc(ManagedChannel channel) throws InterruptedException { try { SimpleServiceGrpc.SimpleServiceBlockingStub stub = SimpleServiceGrpc.newBlockingStub(channel); @@ -277,8 +247,6 @@ public static boolean doUnaryRpc(ExecutorService executor, ManagedChannel channe } finally { channel.shutdown(); channel.awaitTermination(1, SECONDS); - executor.shutdown(); - executor.awaitTermination(1, SECONDS); } } @@ -318,4 +286,4 @@ public void unaryRpc(SimpleRequest request, StreamObserver obser observer.onCompleted(); } } -} \ No newline at end of file +} From 782a44ad62f84d19291b0771b56b431e6e723752 Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Thu, 19 Sep 2024 09:52:38 -0700 Subject: [PATCH 075/103] Implement ContextStorageOverride for opentelemetry context bridge (#11523) --- contextstorage/build.gradle | 28 ++++ .../grpc/override/ContextStorageOverride.java | 46 ++++++ .../override/OpenTelemetryContextStorage.java | 72 +++++++++ .../OpenTelemetryContextStorageTest.java | 144 ++++++++++++++++++ settings.gradle | 2 + 5 files changed, 292 insertions(+) create mode 100644 contextstorage/build.gradle create mode 100644 contextstorage/src/main/java/io/grpc/override/ContextStorageOverride.java create mode 100644 contextstorage/src/main/java/io/grpc/override/OpenTelemetryContextStorage.java create mode 100644 contextstorage/src/test/java/io/grpc/override/OpenTelemetryContextStorageTest.java diff --git a/contextstorage/build.gradle b/contextstorage/build.gradle new file mode 100644 index 00000000000..10c7f3a3a86 --- /dev/null +++ b/contextstorage/build.gradle @@ -0,0 +1,28 @@ +plugins { + id "java-library" + // until we are confident we like the name + //id "maven-publish" + + id "ru.vyarus.animalsniffer" +} + +description = 'gRPC: ContextStorageOverride' + +dependencies { + api project(':grpc-api') + implementation libraries.opentelemetry.api + + testImplementation libraries.junit, + libraries.opentelemetry.sdk.testing, + libraries.assertj.core + testImplementation 'junit:junit:4.13.1'// opentelemetry.sdk.testing uses compileOnly for assertj + + signature libraries.signature.java + signature libraries.signature.android +} + +tasks.named("jar").configure { + manifest { + attributes('Automatic-Module-Name': 'io.grpc.override') + } +} diff --git a/contextstorage/src/main/java/io/grpc/override/ContextStorageOverride.java b/contextstorage/src/main/java/io/grpc/override/ContextStorageOverride.java new file mode 100644 index 00000000000..41b24765de0 --- /dev/null +++ b/contextstorage/src/main/java/io/grpc/override/ContextStorageOverride.java @@ -0,0 +1,46 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.override; + +import io.grpc.Context; + +/** + * Including this class in your dependencies will override the default gRPC context storage using + * reflection. It is a bridge between {@link io.grpc.Context} and + * {@link io.opentelemetry.context.Context}, i.e. propagating io.grpc.context.Context also + * propagates io.opentelemetry.context, and propagating io.opentelemetry.context will also propagate + * io.grpc.context. + */ +public final class ContextStorageOverride extends Context.Storage { + + private final Context.Storage delegate = new OpenTelemetryContextStorage(); + + @Override + public Context doAttach(Context toAttach) { + return delegate.doAttach(toAttach); + } + + @Override + public void detach(Context toDetach, Context toRestore) { + delegate.detach(toDetach, toRestore); + } + + @Override + public Context current() { + return delegate.current(); + } +} diff --git a/contextstorage/src/main/java/io/grpc/override/OpenTelemetryContextStorage.java b/contextstorage/src/main/java/io/grpc/override/OpenTelemetryContextStorage.java new file mode 100644 index 00000000000..01356e9f406 --- /dev/null +++ b/contextstorage/src/main/java/io/grpc/override/OpenTelemetryContextStorage.java @@ -0,0 +1,72 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.override; + +import io.grpc.Context; +import io.opentelemetry.context.ContextKey; +import io.opentelemetry.context.Scope; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A Context.Storage implementation that attaches io.grpc.context to OpenTelemetry's context and + * io.opentelemetry.context is also saved in the io.grpc.context. + * Bridge between {@link io.grpc.Context} and {@link io.opentelemetry.context.Context}. + */ +final class OpenTelemetryContextStorage extends Context.Storage { + private static final Logger logger = Logger.getLogger( + OpenTelemetryContextStorage.class.getName()); + + private static final io.grpc.Context.Key OTEL_CONTEXT_OVER_GRPC + = io.grpc.Context.key("otel-context-over-grpc"); + private static final Context.Key OTEL_SCOPE = Context.key("otel-scope"); + private static final ContextKey GRPC_CONTEXT_OVER_OTEL = + ContextKey.named("grpc-context-over-otel"); + + @Override + @SuppressWarnings("MustBeClosedChecker") + public Context doAttach(Context toAttach) { + io.grpc.Context previous = current(); + io.opentelemetry.context.Context otelContext = OTEL_CONTEXT_OVER_GRPC.get(toAttach); + if (otelContext == null) { + otelContext = io.opentelemetry.context.Context.current(); + } + Scope scope = otelContext.with(GRPC_CONTEXT_OVER_OTEL, toAttach).makeCurrent(); + return previous.withValue(OTEL_SCOPE, scope); + } + + @Override + public void detach(Context toDetach, Context toRestore) { + Scope scope = OTEL_SCOPE.get(toRestore); + if (scope == null) { + logger.log( + Level.SEVERE, "Detaching context which was not attached."); + } else { + scope.close(); + } + } + + @Override + public Context current() { + io.opentelemetry.context.Context otelCurrent = io.opentelemetry.context.Context.current(); + io.grpc.Context grpcCurrent = otelCurrent.get(GRPC_CONTEXT_OVER_OTEL); + if (grpcCurrent == null) { + grpcCurrent = Context.ROOT; + } + return grpcCurrent.withValue(OTEL_CONTEXT_OVER_GRPC, otelCurrent); + } +} diff --git a/contextstorage/src/test/java/io/grpc/override/OpenTelemetryContextStorageTest.java b/contextstorage/src/test/java/io/grpc/override/OpenTelemetryContextStorageTest.java new file mode 100644 index 00000000000..3c628964342 --- /dev/null +++ b/contextstorage/src/test/java/io/grpc/override/OpenTelemetryContextStorageTest.java @@ -0,0 +1,144 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.override; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import com.google.common.util.concurrent.SettableFuture; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.ContextKey; +import io.opentelemetry.context.Scope; +import io.opentelemetry.sdk.testing.junit4.OpenTelemetryRule; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class OpenTelemetryContextStorageTest { + @Rule + public final OpenTelemetryRule openTelemetryRule = OpenTelemetryRule.create(); + private Tracer tracerRule = openTelemetryRule.getOpenTelemetry().getTracer( + "context-storage-test"); + private final io.grpc.Context.Key username = io.grpc.Context.key("username"); + private final ContextKey password = ContextKey.named("password"); + + @Test + public void grpcContextPropagation() throws Exception { + final Span parentSpan = tracerRule.spanBuilder("test-context").startSpan(); + final SettableFuture spanPropagated = SettableFuture.create(); + final SettableFuture grpcContextPropagated = SettableFuture.create(); + final SettableFuture spanDetached = SettableFuture.create(); + final SettableFuture grpcContextDetached = SettableFuture.create(); + + io.grpc.Context grpcContext; + try (Scope scope = Context.current().with(parentSpan).makeCurrent()) { + grpcContext = io.grpc.Context.current().withValue(username, "jeff"); + } + new Thread(new Runnable() { + @Override + public void run() { + io.grpc.Context previous = grpcContext.attach(); + try { + grpcContextPropagated.set(username.get(io.grpc.Context.current())); + spanPropagated.set(Span.fromContext(io.opentelemetry.context.Context.current())); + } finally { + grpcContext.detach(previous); + spanDetached.set(Span.fromContext(io.opentelemetry.context.Context.current())); + grpcContextDetached.set(username.get(io.grpc.Context.current())); + } + } + }).start(); + Assert.assertEquals(spanPropagated.get(5, TimeUnit.SECONDS), parentSpan); + Assert.assertEquals(grpcContextPropagated.get(5, TimeUnit.SECONDS), "jeff"); + Assert.assertEquals(spanDetached.get(5, TimeUnit.SECONDS), Span.getInvalid()); + Assert.assertNull(grpcContextDetached.get(5, TimeUnit.SECONDS)); + } + + @Test + public void otelContextPropagation() throws Exception { + final SettableFuture grpcPropagated = SettableFuture.create(); + final AtomicReference otelPropagation = new AtomicReference<>(); + + io.grpc.Context grpcContext = io.grpc.Context.current().withValue(username, "jeff"); + io.grpc.Context previous = grpcContext.attach(); + Context original = Context.current().with(password, "valentine"); + try { + new Thread( + () -> { + try (Scope scope = original.makeCurrent()) { + otelPropagation.set(Context.current().get(password)); + grpcPropagated.set(username.get(io.grpc.Context.current())); + } + } + ).start(); + } finally { + grpcContext.detach(previous); + } + Assert.assertEquals(grpcPropagated.get(5, TimeUnit.SECONDS), "jeff"); + Assert.assertEquals(otelPropagation.get(), "valentine"); + } + + @Test + public void grpcOtelMix() { + io.grpc.Context grpcContext = io.grpc.Context.current().withValue(username, "jeff"); + Context otelContext = Context.current().with(password, "valentine"); + Assert.assertNull(username.get(io.grpc.Context.current())); + Assert.assertNull(Context.current().get(password)); + io.grpc.Context previous = grpcContext.attach(); + try { + assertEquals(username.get(io.grpc.Context.current()), "jeff"); + try (Scope scope = otelContext.makeCurrent()) { + Assert.assertEquals(Context.current().get(password), "valentine"); + assertNull(username.get(io.grpc.Context.current())); + + io.grpc.Context grpcContext2 = io.grpc.Context.current().withValue(username, "frank"); + io.grpc.Context previous2 = grpcContext2.attach(); + try { + assertEquals(username.get(io.grpc.Context.current()), "frank"); + Assert.assertEquals(Context.current().get(password), "valentine"); + } finally { + grpcContext2.detach(previous2); + } + assertNull(username.get(io.grpc.Context.current())); + Assert.assertEquals(Context.current().get(password), "valentine"); + } + } finally { + grpcContext.detach(previous); + } + Assert.assertNull(username.get(io.grpc.Context.current())); + Assert.assertNull(Context.current().get(password)); + } + + @Test + public void grpcContextDetachError() { + io.grpc.Context grpcContext = io.grpc.Context.current().withValue(username, "jeff"); + io.grpc.Context previous = grpcContext.attach(); + try { + previous.detach(grpcContext); + assertEquals(username.get(io.grpc.Context.current()), "jeff"); + } finally { + grpcContext.detach(previous); + } + } +} diff --git a/settings.gradle b/settings.gradle index 03eca809226..b661e0f52db 100644 --- a/settings.gradle +++ b/settings.gradle @@ -77,6 +77,7 @@ include ":grpc-istio-interop-testing" include ":grpc-inprocess" include ":grpc-util" include ":grpc-opentelemetry" +include ":grpc-opentelemetry-context-storage-override" project(':grpc-api').projectDir = "$rootDir/api" as File project(':grpc-core').projectDir = "$rootDir/core" as File @@ -113,6 +114,7 @@ project(':grpc-istio-interop-testing').projectDir = "$rootDir/istio-interop-test project(':grpc-inprocess').projectDir = "$rootDir/inprocess" as File project(':grpc-util').projectDir = "$rootDir/util" as File project(':grpc-opentelemetry').projectDir = "$rootDir/opentelemetry" as File +project(':grpc-opentelemetry-context-storage-override').projectDir = "$rootDir/contextstorage" as File if (settings.hasProperty('skipCodegen') && skipCodegen.toBoolean()) { println '*** Skipping the build of codegen and compilation of proto files because skipCodegen=true' From e75a044107bc8701b2e328b7ec633c982fb3a844 Mon Sep 17 00:00:00 2001 From: Riya Mehta <55350838+rmehta19@users.noreply.github.com> Date: Fri, 20 Sep 2024 12:32:54 -0700 Subject: [PATCH 076/103] s2a,netty: S2AHandshakerServiceChannel doesn't use custom event loop. (#11539) * S2AHandshakerServiceChannel doesn't use custom event loop. * use executorPool. * log when channel not shutdown. * use a cached threadpool. * update non-executor version. --- .../netty/InternalProtocolNegotiators.java | 18 ++++++- .../channel/S2AHandshakerServiceChannel.java | 50 +++++++------------ .../S2AProtocolNegotiatorFactory.java | 7 ++- .../S2AHandshakerServiceChannelTest.java | 46 +++++++---------- 4 files changed, 56 insertions(+), 65 deletions(-) diff --git a/netty/src/main/java/io/grpc/netty/InternalProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/InternalProtocolNegotiators.java index 0d309828c6d..b9c6a77982a 100644 --- a/netty/src/main/java/io/grpc/netty/InternalProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/InternalProtocolNegotiators.java @@ -17,12 +17,14 @@ package io.grpc.netty; import io.grpc.ChannelLogger; +import io.grpc.internal.ObjectPool; import io.grpc.netty.ProtocolNegotiators.ClientTlsHandler; import io.grpc.netty.ProtocolNegotiators.GrpcNegotiationHandler; import io.grpc.netty.ProtocolNegotiators.WaitUntilActiveHandler; import io.netty.channel.ChannelHandler; import io.netty.handler.ssl.SslContext; import io.netty.util.AsciiString; +import java.util.concurrent.Executor; /** * Internal accessor for {@link ProtocolNegotiators}. @@ -35,9 +37,12 @@ private InternalProtocolNegotiators() {} * Returns a {@link ProtocolNegotiator} that ensures the pipeline is set up so that TLS will * be negotiated, the {@code handler} is added and writes to the {@link io.netty.channel.Channel} * may happen immediately, even before the TLS Handshake is complete. + * @param executorPool a dedicated {@link Executor} pool for time-consuming TLS tasks */ - public static InternalProtocolNegotiator.ProtocolNegotiator tls(SslContext sslContext) { - final io.grpc.netty.ProtocolNegotiator negotiator = ProtocolNegotiators.tls(sslContext); + public static InternalProtocolNegotiator.ProtocolNegotiator tls(SslContext sslContext, + ObjectPool executorPool) { + final io.grpc.netty.ProtocolNegotiator negotiator = ProtocolNegotiators.tls(sslContext, + executorPool); final class TlsNegotiator implements InternalProtocolNegotiator.ProtocolNegotiator { @Override @@ -58,6 +63,15 @@ public void close() { return new TlsNegotiator(); } + + /** + * Returns a {@link ProtocolNegotiator} that ensures the pipeline is set up so that TLS will + * be negotiated, the {@code handler} is added and writes to the {@link io.netty.channel.Channel} + * may happen immediately, even before the TLS Handshake is complete. + */ + public static InternalProtocolNegotiator.ProtocolNegotiator tls(SslContext sslContext) { + return tls(sslContext, null); + } /** * Returns a {@link ProtocolNegotiator} that ensures the pipeline is set up so that TLS will be diff --git a/s2a/src/main/java/io/grpc/s2a/channel/S2AHandshakerServiceChannel.java b/s2a/src/main/java/io/grpc/s2a/channel/S2AHandshakerServiceChannel.java index 75ec7347bb5..90956907bfe 100644 --- a/s2a/src/main/java/io/grpc/s2a/channel/S2AHandshakerServiceChannel.java +++ b/s2a/src/main/java/io/grpc/s2a/channel/S2AHandshakerServiceChannel.java @@ -29,13 +29,11 @@ import io.grpc.MethodDescriptor; import io.grpc.internal.SharedResourceHolder.Resource; import io.grpc.netty.NettyChannelBuilder; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.util.concurrent.DefaultThreadFactory; import java.time.Duration; import java.util.Optional; import java.util.concurrent.ConcurrentMap; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.annotation.concurrent.ThreadSafe; /** @@ -61,7 +59,6 @@ public final class S2AHandshakerServiceChannel { private static final ConcurrentMap> SHARED_RESOURCE_CHANNELS = Maps.newConcurrentMap(); - private static final Duration DELEGATE_TERMINATION_TIMEOUT = Duration.ofSeconds(2); private static final Duration CHANNEL_SHUTDOWN_TIMEOUT = Duration.ofSeconds(10); /** @@ -95,41 +92,34 @@ public ChannelResource(String targetAddress, Optional channe } /** - * Creates a {@code EventLoopHoldingChannel} instance to the service running at {@code - * targetAddress}. This channel uses a dedicated thread pool for its {@code EventLoopGroup} - * instance to avoid blocking. + * Creates a {@code HandshakerServiceChannel} instance to the service running at {@code + * targetAddress}. */ @Override public Channel create() { - EventLoopGroup eventLoopGroup = - new NioEventLoopGroup(1, new DefaultThreadFactory("S2A channel pool", true)); ManagedChannel channel = null; if (channelCredentials.isPresent()) { // Create a secure channel. channel = NettyChannelBuilder.forTarget(targetAddress, channelCredentials.get()) - .channelType(NioSocketChannel.class) .directExecutor() - .eventLoopGroup(eventLoopGroup) .build(); } else { // Create a plaintext channel. channel = NettyChannelBuilder.forTarget(targetAddress) - .channelType(NioSocketChannel.class) .directExecutor() - .eventLoopGroup(eventLoopGroup) .usePlaintext() .build(); } - return EventLoopHoldingChannel.create(channel, eventLoopGroup); + return HandshakerServiceChannel.create(channel); } - /** Destroys a {@code EventLoopHoldingChannel} instance. */ + /** Destroys a {@code HandshakerServiceChannel} instance. */ @Override public void close(Channel instanceChannel) { checkNotNull(instanceChannel); - EventLoopHoldingChannel channel = (EventLoopHoldingChannel) instanceChannel; + HandshakerServiceChannel channel = (HandshakerServiceChannel) instanceChannel; channel.close(); } @@ -140,23 +130,21 @@ public String toString() { } /** - * Manages a channel using a {@link ManagedChannel} instance that belong to the {@code - * EventLoopGroup} thread pool. + * Manages a channel using a {@link ManagedChannel} instance. */ @VisibleForTesting - static class EventLoopHoldingChannel extends Channel { + static class HandshakerServiceChannel extends Channel { + private static final Logger logger = + Logger.getLogger(S2AHandshakerServiceChannel.class.getName()); private final ManagedChannel delegate; - private final EventLoopGroup eventLoopGroup; - static EventLoopHoldingChannel create(ManagedChannel delegate, EventLoopGroup eventLoopGroup) { + static HandshakerServiceChannel create(ManagedChannel delegate) { checkNotNull(delegate); - checkNotNull(eventLoopGroup); - return new EventLoopHoldingChannel(delegate, eventLoopGroup); + return new HandshakerServiceChannel(delegate); } - private EventLoopHoldingChannel(ManagedChannel delegate, EventLoopGroup eventLoopGroup) { + private HandshakerServiceChannel(ManagedChannel delegate) { this.delegate = delegate; - this.eventLoopGroup = eventLoopGroup; } /** @@ -178,16 +166,12 @@ public ClientCall newCall( @SuppressWarnings("FutureReturnValueIgnored") public void close() { delegate.shutdownNow(); - boolean isDelegateTerminated; try { - isDelegateTerminated = - delegate.awaitTermination(DELEGATE_TERMINATION_TIMEOUT.getSeconds(), SECONDS); + delegate.awaitTermination(CHANNEL_SHUTDOWN_TIMEOUT.getSeconds(), SECONDS); } catch (InterruptedException e) { - isDelegateTerminated = false; + Thread.currentThread().interrupt(); + logger.log(Level.WARNING, "Channel to S2A was not shutdown."); } - long quietPeriodSeconds = isDelegateTerminated ? 0 : 1; - eventLoopGroup.shutdownGracefully( - quietPeriodSeconds, CHANNEL_SHUTDOWN_TIMEOUT.getSeconds(), SECONDS); } } diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactory.java b/s2a/src/main/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactory.java index 25d1e325ea8..14bdc05238d 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactory.java +++ b/s2a/src/main/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactory.java @@ -29,7 +29,9 @@ import com.google.common.util.concurrent.MoreExecutors; import com.google.errorprone.annotations.ThreadSafe; import io.grpc.Channel; +import io.grpc.internal.GrpcUtil; import io.grpc.internal.ObjectPool; +import io.grpc.internal.SharedResourcePool; import io.grpc.netty.GrpcHttp2ConnectionHandler; import io.grpc.netty.InternalProtocolNegotiator; import io.grpc.netty.InternalProtocolNegotiator.ProtocolNegotiator; @@ -227,7 +229,10 @@ protected void handlerAdded0(ChannelHandlerContext ctx) { @Override public void onSuccess(SslContext sslContext) { ChannelHandler handler = - InternalProtocolNegotiators.tls(sslContext).newHandler(grpcHandler); + InternalProtocolNegotiators.tls( + sslContext, + SharedResourcePool.forResource(GrpcUtil.SHARED_CHANNEL_EXECUTOR)) + .newHandler(grpcHandler); // Remove the bufferReads handler and delegate the rest of the handshake to the TLS // handler. diff --git a/s2a/src/test/java/io/grpc/s2a/channel/S2AHandshakerServiceChannelTest.java b/s2a/src/test/java/io/grpc/s2a/channel/S2AHandshakerServiceChannelTest.java index 57288be1b6f..dc5909442bf 100644 --- a/s2a/src/test/java/io/grpc/s2a/channel/S2AHandshakerServiceChannelTest.java +++ b/s2a/src/test/java/io/grpc/s2a/channel/S2AHandshakerServiceChannelTest.java @@ -18,11 +18,7 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; -import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.assertThrows; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; import io.grpc.CallOptions; import io.grpc.Channel; @@ -39,15 +35,13 @@ import io.grpc.benchmarks.Utils; import io.grpc.internal.SharedResourceHolder.Resource; import io.grpc.netty.NettyServerBuilder; -import io.grpc.s2a.channel.S2AHandshakerServiceChannel.EventLoopHoldingChannel; +import io.grpc.s2a.channel.S2AHandshakerServiceChannel.HandshakerServiceChannel; import io.grpc.stub.StreamObserver; import io.grpc.testing.GrpcCleanupRule; import io.grpc.testing.protobuf.SimpleRequest; import io.grpc.testing.protobuf.SimpleResponse; import io.grpc.testing.protobuf.SimpleServiceGrpc; -import io.netty.channel.EventLoopGroup; import java.io.File; -import java.time.Duration; import java.util.Optional; import java.util.concurrent.TimeUnit; import org.junit.Before; @@ -60,8 +54,6 @@ @RunWith(JUnit4.class) public final class S2AHandshakerServiceChannelTest { @ClassRule public static final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule(); - private static final Duration CHANNEL_SHUTDOWN_TIMEOUT = Duration.ofSeconds(10); - private final EventLoopGroup mockEventLoopGroup = mock(EventLoopGroup.class); private Server mtlsServer; private Server plaintextServer; @@ -191,7 +183,7 @@ public void close_mtlsSuccess() throws Exception { } /** - * Verifies that an {@code EventLoopHoldingChannel}'s {@code newCall} method can be used to + * Verifies that an {@code HandshakerServiceChannel}'s {@code newCall} method can be used to * perform a simple RPC. */ @Test @@ -201,7 +193,7 @@ public void newCall_performSimpleRpcSuccess() { "localhost:" + plaintextServer.getPort(), /* s2aChannelCredentials= */ Optional.empty()); Channel channel = resource.create(); - assertThat(channel).isInstanceOf(EventLoopHoldingChannel.class); + assertThat(channel).isInstanceOf(HandshakerServiceChannel.class); assertThat( SimpleServiceGrpc.newBlockingStub(channel).unaryRpc(SimpleRequest.getDefaultInstance())) .isEqualToDefaultInstance(); @@ -214,53 +206,49 @@ public void newCall_mtlsPerformSimpleRpcSuccess() throws Exception { S2AHandshakerServiceChannel.getChannelResource( "localhost:" + mtlsServer.getPort(), getTlsChannelCredentials()); Channel channel = resource.create(); - assertThat(channel).isInstanceOf(EventLoopHoldingChannel.class); + assertThat(channel).isInstanceOf(HandshakerServiceChannel.class); assertThat( SimpleServiceGrpc.newBlockingStub(channel).unaryRpc(SimpleRequest.getDefaultInstance())) .isEqualToDefaultInstance(); } - /** Creates a {@code EventLoopHoldingChannel} instance and verifies its authority. */ + /** Creates a {@code HandshakerServiceChannel} instance and verifies its authority. */ @Test public void authority_success() throws Exception { ManagedChannel channel = new FakeManagedChannel(true); - EventLoopHoldingChannel eventLoopHoldingChannel = - EventLoopHoldingChannel.create(channel, mockEventLoopGroup); + HandshakerServiceChannel eventLoopHoldingChannel = + HandshakerServiceChannel.create(channel); assertThat(eventLoopHoldingChannel.authority()).isEqualTo("FakeManagedChannel"); } /** - * Creates and closes a {@code EventLoopHoldingChannel} when its {@code ManagedChannel} terminates - * successfully. + * Creates and closes a {@code HandshakerServiceChannel} when its {@code ManagedChannel} + * terminates successfully. */ @Test public void close_withDelegateTerminatedSuccess() throws Exception { ManagedChannel channel = new FakeManagedChannel(true); - EventLoopHoldingChannel eventLoopHoldingChannel = - EventLoopHoldingChannel.create(channel, mockEventLoopGroup); + HandshakerServiceChannel eventLoopHoldingChannel = + HandshakerServiceChannel.create(channel); eventLoopHoldingChannel.close(); assertThat(channel.isShutdown()).isTrue(); - verify(mockEventLoopGroup, times(1)) - .shutdownGracefully(0, CHANNEL_SHUTDOWN_TIMEOUT.getSeconds(), SECONDS); } /** - * Creates and closes a {@code EventLoopHoldingChannel} when its {@code ManagedChannel} does not + * Creates and closes a {@code HandshakerServiceChannel} when its {@code ManagedChannel} does not * terminate successfully. */ @Test public void close_withDelegateTerminatedFailure() throws Exception { ManagedChannel channel = new FakeManagedChannel(false); - EventLoopHoldingChannel eventLoopHoldingChannel = - EventLoopHoldingChannel.create(channel, mockEventLoopGroup); + HandshakerServiceChannel eventLoopHoldingChannel = + HandshakerServiceChannel.create(channel); eventLoopHoldingChannel.close(); assertThat(channel.isShutdown()).isTrue(); - verify(mockEventLoopGroup, times(1)) - .shutdownGracefully(1, CHANNEL_SHUTDOWN_TIMEOUT.getSeconds(), SECONDS); } /** - * Creates and closes a {@code EventLoopHoldingChannel}, creates a new channel from the same + * Creates and closes a {@code HandshakerServiceChannel}, creates a new channel from the same * resource, and verifies that this second channel is useable. */ @Test @@ -273,7 +261,7 @@ public void create_succeedsAfterCloseIsCalledOnce() throws Exception { resource.close(channelOne); Channel channelTwo = resource.create(); - assertThat(channelTwo).isInstanceOf(EventLoopHoldingChannel.class); + assertThat(channelTwo).isInstanceOf(HandshakerServiceChannel.class); assertThat( SimpleServiceGrpc.newBlockingStub(channelTwo) .unaryRpc(SimpleRequest.getDefaultInstance())) @@ -291,7 +279,7 @@ public void create_mtlsSucceedsAfterCloseIsCalledOnce() throws Exception { resource.close(channelOne); Channel channelTwo = resource.create(); - assertThat(channelTwo).isInstanceOf(EventLoopHoldingChannel.class); + assertThat(channelTwo).isInstanceOf(HandshakerServiceChannel.class); assertThat( SimpleServiceGrpc.newBlockingStub(channelTwo) .unaryRpc(SimpleRequest.getDefaultInstance())) From d8f73e04566fa588889ca1a422e276d71724643c Mon Sep 17 00:00:00 2001 From: Riya Mehta <55350838+rmehta19@users.noreply.github.com> Date: Fri, 20 Sep 2024 15:53:14 -0700 Subject: [PATCH 077/103] s2a: Address comments on PR#11113 (#11534) * Mark S2A public APIs as experimental. * Rename S2AChannelCredentials createBuilder API to newBuilder. * Remove usage of AdvancedTls. * Use InsecureChannelCredentials.create instead of Optional. * Invoke Thread.currentThread().interrupt() in a InterruptedException block. --- .../grpc/s2a/MtlsToS2AChannelCredentials.java | 21 ++++-------- .../io/grpc/s2a/S2AChannelCredentials.java | 12 ++++--- .../channel/S2AHandshakerServiceChannel.java | 27 +++++---------- .../grpc/s2a/handshaker/S2ATrustManager.java | 3 ++ .../s2a/MtlsToS2AChannelCredentialsTest.java | 34 +++++++++---------- .../grpc/s2a/S2AChannelCredentialsTest.java | 24 ++++++------- .../S2AHandshakerServiceChannelTest.java | 25 +++++++------- .../grpc/s2a/handshaker/IntegrationTest.java | 8 ++--- .../S2AProtocolNegotiatorFactoryTest.java | 2 +- .../io/grpc/s2a/handshaker/S2AStubTest.java | 4 +-- 10 files changed, 73 insertions(+), 87 deletions(-) diff --git a/s2a/src/main/java/io/grpc/s2a/MtlsToS2AChannelCredentials.java b/s2a/src/main/java/io/grpc/s2a/MtlsToS2AChannelCredentials.java index 56f612502bf..e8eb01628ed 100644 --- a/s2a/src/main/java/io/grpc/s2a/MtlsToS2AChannelCredentials.java +++ b/s2a/src/main/java/io/grpc/s2a/MtlsToS2AChannelCredentials.java @@ -21,17 +21,16 @@ import static com.google.common.base.Strings.isNullOrEmpty; import io.grpc.ChannelCredentials; +import io.grpc.ExperimentalApi; import io.grpc.TlsChannelCredentials; -import io.grpc.util.AdvancedTlsX509KeyManager; -import io.grpc.util.AdvancedTlsX509TrustManager; import java.io.File; import java.io.IOException; -import java.security.GeneralSecurityException; /** * Configures an {@code S2AChannelCredentials.Builder} instance with credentials used to establish a * connection with the S2A to support talking to the S2A over mTLS. */ +@ExperimentalApi("https://github.com/grpc/grpc-java/issues/11533") public final class MtlsToS2AChannelCredentials { /** * Creates a {@code S2AChannelCredentials.Builder} builder, that talks to the S2A over mTLS. @@ -42,7 +41,7 @@ public final class MtlsToS2AChannelCredentials { * @param trustBundlePath the path to the trust bundle PEM. * @return a {@code MtlsToS2AChannelCredentials.Builder} instance. */ - public static Builder createBuilder( + public static Builder newBuilder( String s2aAddress, String privateKeyPath, String certChainPath, String trustBundlePath) { checkArgument(!isNullOrEmpty(s2aAddress), "S2A address must not be null or empty."); checkArgument(!isNullOrEmpty(privateKeyPath), "privateKeyPath must not be null or empty."); @@ -66,7 +65,7 @@ public static final class Builder { this.trustBundlePath = trustBundlePath; } - public S2AChannelCredentials.Builder build() throws GeneralSecurityException, IOException { + public S2AChannelCredentials.Builder build() throws IOException { checkState(!isNullOrEmpty(s2aAddress), "S2A address must not be null or empty."); checkState(!isNullOrEmpty(privateKeyPath), "privateKeyPath must not be null or empty."); checkState(!isNullOrEmpty(certChainPath), "certChainPath must not be null or empty."); @@ -75,19 +74,13 @@ public S2AChannelCredentials.Builder build() throws GeneralSecurityException, IO File certChainFile = new File(certChainPath); File trustBundleFile = new File(trustBundlePath); - AdvancedTlsX509KeyManager keyManager = new AdvancedTlsX509KeyManager(); - keyManager.updateIdentityCredentials(certChainFile, privateKeyFile); - - AdvancedTlsX509TrustManager trustManager = AdvancedTlsX509TrustManager.newBuilder().build(); - trustManager.updateTrustCredentials(trustBundleFile); - ChannelCredentials channelToS2ACredentials = TlsChannelCredentials.newBuilder() - .keyManager(keyManager) - .trustManager(trustManager) + .keyManager(certChainFile, privateKeyFile) + .trustManager(trustBundleFile) .build(); - return S2AChannelCredentials.createBuilder(s2aAddress) + return S2AChannelCredentials.newBuilder(s2aAddress) .setS2AChannelCredentials(channelToS2ACredentials); } } diff --git a/s2a/src/main/java/io/grpc/s2a/S2AChannelCredentials.java b/s2a/src/main/java/io/grpc/s2a/S2AChannelCredentials.java index 8a5f1f51350..ba0f6d72de1 100644 --- a/s2a/src/main/java/io/grpc/s2a/S2AChannelCredentials.java +++ b/s2a/src/main/java/io/grpc/s2a/S2AChannelCredentials.java @@ -24,6 +24,8 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.grpc.Channel; import io.grpc.ChannelCredentials; +import io.grpc.ExperimentalApi; +import io.grpc.InsecureChannelCredentials; import io.grpc.internal.ObjectPool; import io.grpc.internal.SharedResourcePool; import io.grpc.netty.InternalNettyChannelCredentials; @@ -31,7 +33,6 @@ import io.grpc.s2a.channel.S2AHandshakerServiceChannel; import io.grpc.s2a.handshaker.S2AIdentity; import io.grpc.s2a.handshaker.S2AProtocolNegotiatorFactory; -import java.util.Optional; import javax.annotation.concurrent.NotThreadSafe; import org.checkerframework.checker.nullness.qual.Nullable; @@ -39,6 +40,7 @@ * Configures gRPC to use S2A for transport security when establishing a secure channel. Only for * use on the client side of a gRPC connection. */ +@ExperimentalApi("https://github.com/grpc/grpc-java/issues/11533") public final class S2AChannelCredentials { /** * Creates a channel credentials builder for establishing an S2A-secured connection. @@ -46,7 +48,7 @@ public final class S2AChannelCredentials { * @param s2aAddress the address of the S2A server used to secure the connection. * @return a {@code S2AChannelCredentials.Builder} instance. */ - public static Builder createBuilder(String s2aAddress) { + public static Builder newBuilder(String s2aAddress) { checkArgument(!isNullOrEmpty(s2aAddress), "S2A address must not be null or empty."); return new Builder(s2aAddress); } @@ -56,13 +58,13 @@ public static Builder createBuilder(String s2aAddress) { public static final class Builder { private final String s2aAddress; private ObjectPool s2aChannelPool; - private Optional s2aChannelCredentials; + private ChannelCredentials s2aChannelCredentials; private @Nullable S2AIdentity localIdentity = null; Builder(String s2aAddress) { this.s2aAddress = s2aAddress; this.s2aChannelPool = null; - this.s2aChannelCredentials = Optional.empty(); + this.s2aChannelCredentials = InsecureChannelCredentials.create(); } /** @@ -107,7 +109,7 @@ public Builder setLocalUid(String localUid) { /** Sets the credentials to be used when connecting to the S2A. */ @CanIgnoreReturnValue public Builder setS2AChannelCredentials(ChannelCredentials s2aChannelCredentials) { - this.s2aChannelCredentials = Optional.of(s2aChannelCredentials); + this.s2aChannelCredentials = s2aChannelCredentials; return this; } diff --git a/s2a/src/main/java/io/grpc/s2a/channel/S2AHandshakerServiceChannel.java b/s2a/src/main/java/io/grpc/s2a/channel/S2AHandshakerServiceChannel.java index 90956907bfe..443ea553e52 100644 --- a/s2a/src/main/java/io/grpc/s2a/channel/S2AHandshakerServiceChannel.java +++ b/s2a/src/main/java/io/grpc/s2a/channel/S2AHandshakerServiceChannel.java @@ -30,7 +30,6 @@ import io.grpc.internal.SharedResourceHolder.Resource; import io.grpc.netty.NettyChannelBuilder; import java.time.Duration; -import java.util.Optional; import java.util.concurrent.ConcurrentMap; import java.util.logging.Level; import java.util.logging.Logger; @@ -71,8 +70,9 @@ public final class S2AHandshakerServiceChannel { * running at {@code s2aAddress}. */ public static Resource getChannelResource( - String s2aAddress, Optional s2aChannelCredentials) { + String s2aAddress, ChannelCredentials s2aChannelCredentials) { checkNotNull(s2aAddress); + checkNotNull(s2aChannelCredentials); return SHARED_RESOURCE_CHANNELS.computeIfAbsent( s2aAddress, channelResource -> new ChannelResource(s2aAddress, s2aChannelCredentials)); } @@ -84,9 +84,9 @@ public static Resource getChannelResource( */ private static class ChannelResource implements Resource { private final String targetAddress; - private final Optional channelCredentials; + private final ChannelCredentials channelCredentials; - public ChannelResource(String targetAddress, Optional channelCredentials) { + public ChannelResource(String targetAddress, ChannelCredentials channelCredentials) { this.targetAddress = targetAddress; this.channelCredentials = channelCredentials; } @@ -97,21 +97,10 @@ public ChannelResource(String targetAddress, Optional channe */ @Override public Channel create() { - ManagedChannel channel = null; - if (channelCredentials.isPresent()) { - // Create a secure channel. - channel = - NettyChannelBuilder.forTarget(targetAddress, channelCredentials.get()) - .directExecutor() - .build(); - } else { - // Create a plaintext channel. - channel = - NettyChannelBuilder.forTarget(targetAddress) - .directExecutor() - .usePlaintext() - .build(); - } + ManagedChannel channel = + NettyChannelBuilder.forTarget(targetAddress, channelCredentials) + .directExecutor() + .build(); return HandshakerServiceChannel.create(channel); } diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/S2ATrustManager.java b/s2a/src/main/java/io/grpc/s2a/handshaker/S2ATrustManager.java index fb113bb29cc..aafbb94c047 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/S2ATrustManager.java +++ b/s2a/src/main/java/io/grpc/s2a/handshaker/S2ATrustManager.java @@ -121,6 +121,9 @@ private void checkPeerTrusted(X509Certificate[] chain, boolean isCheckingClientC try { resp = stub.send(reqBuilder.build()); } catch (IOException | InterruptedException e) { + if (e instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } throw new CertificateException("Failed to send request to S2A.", e); } if (resp.hasStatus() && resp.getStatus().getCode() != 0) { diff --git a/s2a/src/test/java/io/grpc/s2a/MtlsToS2AChannelCredentialsTest.java b/s2a/src/test/java/io/grpc/s2a/MtlsToS2AChannelCredentialsTest.java index 5ccc522292e..0fc4ecb3268 100644 --- a/s2a/src/test/java/io/grpc/s2a/MtlsToS2AChannelCredentialsTest.java +++ b/s2a/src/test/java/io/grpc/s2a/MtlsToS2AChannelCredentialsTest.java @@ -26,11 +26,11 @@ @RunWith(JUnit4.class) public final class MtlsToS2AChannelCredentialsTest { @Test - public void createBuilder_nullAddress_throwsException() throws Exception { + public void newBuilder_nullAddress_throwsException() throws Exception { assertThrows( IllegalArgumentException.class, () -> - MtlsToS2AChannelCredentials.createBuilder( + MtlsToS2AChannelCredentials.newBuilder( /* s2aAddress= */ null, /* privateKeyPath= */ "src/test/resources/client_key.pem", /* certChainPath= */ "src/test/resources/client_cert.pem", @@ -38,11 +38,11 @@ public void createBuilder_nullAddress_throwsException() throws Exception { } @Test - public void createBuilder_nullPrivateKeyPath_throwsException() throws Exception { + public void newBuilder_nullPrivateKeyPath_throwsException() throws Exception { assertThrows( IllegalArgumentException.class, () -> - MtlsToS2AChannelCredentials.createBuilder( + MtlsToS2AChannelCredentials.newBuilder( /* s2aAddress= */ "s2a_address", /* privateKeyPath= */ null, /* certChainPath= */ "src/test/resources/client_cert.pem", @@ -50,11 +50,11 @@ public void createBuilder_nullPrivateKeyPath_throwsException() throws Exception } @Test - public void createBuilder_nullCertChainPath_throwsException() throws Exception { + public void newBuilder_nullCertChainPath_throwsException() throws Exception { assertThrows( IllegalArgumentException.class, () -> - MtlsToS2AChannelCredentials.createBuilder( + MtlsToS2AChannelCredentials.newBuilder( /* s2aAddress= */ "s2a_address", /* privateKeyPath= */ "src/test/resources/client_key.pem", /* certChainPath= */ null, @@ -62,11 +62,11 @@ public void createBuilder_nullCertChainPath_throwsException() throws Exception { } @Test - public void createBuilder_nullTrustBundlePath_throwsException() throws Exception { + public void newBuilder_nullTrustBundlePath_throwsException() throws Exception { assertThrows( IllegalArgumentException.class, () -> - MtlsToS2AChannelCredentials.createBuilder( + MtlsToS2AChannelCredentials.newBuilder( /* s2aAddress= */ "s2a_address", /* privateKeyPath= */ "src/test/resources/client_key.pem", /* certChainPath= */ "src/test/resources/client_cert.pem", @@ -74,11 +74,11 @@ public void createBuilder_nullTrustBundlePath_throwsException() throws Exception } @Test - public void createBuilder_emptyAddress_throwsException() throws Exception { + public void newBuilder_emptyAddress_throwsException() throws Exception { assertThrows( IllegalArgumentException.class, () -> - MtlsToS2AChannelCredentials.createBuilder( + MtlsToS2AChannelCredentials.newBuilder( /* s2aAddress= */ "", /* privateKeyPath= */ "src/test/resources/client_key.pem", /* certChainPath= */ "src/test/resources/client_cert.pem", @@ -86,11 +86,11 @@ public void createBuilder_emptyAddress_throwsException() throws Exception { } @Test - public void createBuilder_emptyPrivateKeyPath_throwsException() throws Exception { + public void newBuilder_emptyPrivateKeyPath_throwsException() throws Exception { assertThrows( IllegalArgumentException.class, () -> - MtlsToS2AChannelCredentials.createBuilder( + MtlsToS2AChannelCredentials.newBuilder( /* s2aAddress= */ "s2a_address", /* privateKeyPath= */ "", /* certChainPath= */ "src/test/resources/client_cert.pem", @@ -98,11 +98,11 @@ public void createBuilder_emptyPrivateKeyPath_throwsException() throws Exception } @Test - public void createBuilder_emptyCertChainPath_throwsException() throws Exception { + public void newBuilder_emptyCertChainPath_throwsException() throws Exception { assertThrows( IllegalArgumentException.class, () -> - MtlsToS2AChannelCredentials.createBuilder( + MtlsToS2AChannelCredentials.newBuilder( /* s2aAddress= */ "s2a_address", /* privateKeyPath= */ "src/test/resources/client_key.pem", /* certChainPath= */ "", @@ -110,11 +110,11 @@ public void createBuilder_emptyCertChainPath_throwsException() throws Exception } @Test - public void createBuilder_emptyTrustBundlePath_throwsException() throws Exception { + public void newBuilder_emptyTrustBundlePath_throwsException() throws Exception { assertThrows( IllegalArgumentException.class, () -> - MtlsToS2AChannelCredentials.createBuilder( + MtlsToS2AChannelCredentials.newBuilder( /* s2aAddress= */ "s2a_address", /* privateKeyPath= */ "src/test/resources/client_key.pem", /* certChainPath= */ "src/test/resources/client_cert.pem", @@ -124,7 +124,7 @@ public void createBuilder_emptyTrustBundlePath_throwsException() throws Exceptio @Test public void build_s2AChannelCredentials_success() throws Exception { assertThat( - MtlsToS2AChannelCredentials.createBuilder( + MtlsToS2AChannelCredentials.newBuilder( /* s2aAddress= */ "s2a_address", /* privateKeyPath= */ "src/test/resources/client_key.pem", /* certChainPath= */ "src/test/resources/client_cert.pem", diff --git a/s2a/src/test/java/io/grpc/s2a/S2AChannelCredentialsTest.java b/s2a/src/test/java/io/grpc/s2a/S2AChannelCredentialsTest.java index a6133ed0af8..e766aa3f145 100644 --- a/s2a/src/test/java/io/grpc/s2a/S2AChannelCredentialsTest.java +++ b/s2a/src/test/java/io/grpc/s2a/S2AChannelCredentialsTest.java @@ -30,40 +30,40 @@ @RunWith(JUnit4.class) public final class S2AChannelCredentialsTest { @Test - public void createBuilder_nullArgument_throwsException() throws Exception { - assertThrows(IllegalArgumentException.class, () -> S2AChannelCredentials.createBuilder(null)); + public void newBuilder_nullArgument_throwsException() throws Exception { + assertThrows(IllegalArgumentException.class, () -> S2AChannelCredentials.newBuilder(null)); } @Test - public void createBuilder_emptyAddress_throwsException() throws Exception { - assertThrows(IllegalArgumentException.class, () -> S2AChannelCredentials.createBuilder("")); + public void newBuilder_emptyAddress_throwsException() throws Exception { + assertThrows(IllegalArgumentException.class, () -> S2AChannelCredentials.newBuilder("")); } @Test public void setLocalSpiffeId_nullArgument_throwsException() throws Exception { assertThrows( NullPointerException.class, - () -> S2AChannelCredentials.createBuilder("s2a_address").setLocalSpiffeId(null)); + () -> S2AChannelCredentials.newBuilder("s2a_address").setLocalSpiffeId(null)); } @Test public void setLocalHostname_nullArgument_throwsException() throws Exception { assertThrows( NullPointerException.class, - () -> S2AChannelCredentials.createBuilder("s2a_address").setLocalHostname(null)); + () -> S2AChannelCredentials.newBuilder("s2a_address").setLocalHostname(null)); } @Test public void setLocalUid_nullArgument_throwsException() throws Exception { assertThrows( NullPointerException.class, - () -> S2AChannelCredentials.createBuilder("s2a_address").setLocalUid(null)); + () -> S2AChannelCredentials.newBuilder("s2a_address").setLocalUid(null)); } @Test public void build_withLocalSpiffeId_succeeds() throws Exception { assertThat( - S2AChannelCredentials.createBuilder("s2a_address") + S2AChannelCredentials.newBuilder("s2a_address") .setLocalSpiffeId("spiffe://test") .build()) .isNotNull(); @@ -72,7 +72,7 @@ public void build_withLocalSpiffeId_succeeds() throws Exception { @Test public void build_withLocalHostname_succeeds() throws Exception { assertThat( - S2AChannelCredentials.createBuilder("s2a_address") + S2AChannelCredentials.newBuilder("s2a_address") .setLocalHostname("local_hostname") .build()) .isNotNull(); @@ -80,20 +80,20 @@ public void build_withLocalHostname_succeeds() throws Exception { @Test public void build_withLocalUid_succeeds() throws Exception { - assertThat(S2AChannelCredentials.createBuilder("s2a_address").setLocalUid("local_uid").build()) + assertThat(S2AChannelCredentials.newBuilder("s2a_address").setLocalUid("local_uid").build()) .isNotNull(); } @Test public void build_withNoLocalIdentity_succeeds() throws Exception { - assertThat(S2AChannelCredentials.createBuilder("s2a_address").build()) + assertThat(S2AChannelCredentials.newBuilder("s2a_address").build()) .isNotNull(); } @Test public void build_withTlsChannelCredentials_succeeds() throws Exception { assertThat( - S2AChannelCredentials.createBuilder("s2a_address") + S2AChannelCredentials.newBuilder("s2a_address") .setLocalSpiffeId("spiffe://test") .setS2AChannelCredentials(getTlsChannelCredentials()) .build()) diff --git a/s2a/src/test/java/io/grpc/s2a/channel/S2AHandshakerServiceChannelTest.java b/s2a/src/test/java/io/grpc/s2a/channel/S2AHandshakerServiceChannelTest.java index dc5909442bf..7845e7c3bcb 100644 --- a/s2a/src/test/java/io/grpc/s2a/channel/S2AHandshakerServiceChannelTest.java +++ b/s2a/src/test/java/io/grpc/s2a/channel/S2AHandshakerServiceChannelTest.java @@ -24,6 +24,7 @@ import io.grpc.Channel; import io.grpc.ChannelCredentials; import io.grpc.ClientCall; +import io.grpc.InsecureChannelCredentials; import io.grpc.ManagedChannel; import io.grpc.MethodDescriptor; import io.grpc.Server; @@ -42,7 +43,6 @@ import io.grpc.testing.protobuf.SimpleResponse; import io.grpc.testing.protobuf.SimpleServiceGrpc; import java.io.File; -import java.util.Optional; import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.ClassRule; @@ -74,7 +74,7 @@ public void getChannelResource_success() { Resource resource = S2AHandshakerServiceChannel.getChannelResource( "localhost:" + plaintextServer.getPort(), - /* s2aChannelCredentials= */ Optional.empty()); + InsecureChannelCredentials.create()); assertThat(resource.toString()).isEqualTo("grpc-s2a-channel"); } @@ -96,11 +96,11 @@ public void getChannelResource_twoEqualChannels() { Resource resource = S2AHandshakerServiceChannel.getChannelResource( "localhost:" + plaintextServer.getPort(), - /* s2aChannelCredentials= */ Optional.empty()); + InsecureChannelCredentials.create()); Resource resourceTwo = S2AHandshakerServiceChannel.getChannelResource( "localhost:" + plaintextServer.getPort(), - /* s2aChannelCredentials= */ Optional.empty()); + InsecureChannelCredentials.create()); assertThat(resource).isEqualTo(resourceTwo); } @@ -125,10 +125,10 @@ public void getChannelResource_twoDistinctChannels() { Resource resource = S2AHandshakerServiceChannel.getChannelResource( "localhost:" + plaintextServer.getPort(), - /* s2aChannelCredentials= */ Optional.empty()); + InsecureChannelCredentials.create()); Resource resourceTwo = S2AHandshakerServiceChannel.getChannelResource( - "localhost:" + Utils.pickUnusedPort(), /* s2aChannelCredentials= */ Optional.empty()); + "localhost:" + Utils.pickUnusedPort(), InsecureChannelCredentials.create()); assertThat(resourceTwo).isNotEqualTo(resource); } @@ -153,7 +153,7 @@ public void close_success() { Resource resource = S2AHandshakerServiceChannel.getChannelResource( "localhost:" + plaintextServer.getPort(), - /* s2aChannelCredentials= */ Optional.empty()); + InsecureChannelCredentials.create()); Channel channel = resource.create(); resource.close(channel); StatusRuntimeException expected = @@ -191,7 +191,7 @@ public void newCall_performSimpleRpcSuccess() { Resource resource = S2AHandshakerServiceChannel.getChannelResource( "localhost:" + plaintextServer.getPort(), - /* s2aChannelCredentials= */ Optional.empty()); + InsecureChannelCredentials.create()); Channel channel = resource.create(); assertThat(channel).isInstanceOf(HandshakerServiceChannel.class); assertThat( @@ -256,7 +256,7 @@ public void create_succeedsAfterCloseIsCalledOnce() throws Exception { Resource resource = S2AHandshakerServiceChannel.getChannelResource( "localhost:" + plaintextServer.getPort(), - /* s2aChannelCredentials= */ Optional.empty()); + InsecureChannelCredentials.create()); Channel channelOne = resource.create(); resource.close(channelOne); @@ -308,15 +308,14 @@ private static Server createPlaintextServer() { ServerBuilder.forPort(Utils.pickUnusedPort()).addService(service).build()); } - private static Optional getTlsChannelCredentials() throws Exception { + private static ChannelCredentials getTlsChannelCredentials() throws Exception { File clientCert = new File("src/test/resources/client_cert.pem"); File clientKey = new File("src/test/resources/client_key.pem"); File rootCert = new File("src/test/resources/root_cert.pem"); - return Optional.of( - TlsChannelCredentials.newBuilder() + return TlsChannelCredentials.newBuilder() .keyManager(clientCert, clientKey) .trustManager(rootCert) - .build()); + .build(); } private static class SimpleServiceImpl extends SimpleServiceGrpc.SimpleServiceImplBase { diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java b/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java index 19dda7a19e4..bae58f2f9ec 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java +++ b/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java @@ -186,7 +186,7 @@ public void tearDown() throws Exception { @Test public void clientCommunicateUsingS2ACredentials_succeeds() throws Exception { ChannelCredentials credentials = - S2AChannelCredentials.createBuilder(s2aAddress).setLocalSpiffeId("test-spiffe-id").build(); + S2AChannelCredentials.newBuilder(s2aAddress).setLocalSpiffeId("test-spiffe-id").build(); ManagedChannel channel = Grpc.newChannelBuilder(serverAddress, credentials).build(); assertThat(doUnaryRpc(channel)).isTrue(); @@ -194,7 +194,7 @@ public void clientCommunicateUsingS2ACredentials_succeeds() throws Exception { @Test public void clientCommunicateUsingS2ACredentialsNoLocalIdentity_succeeds() throws Exception { - ChannelCredentials credentials = S2AChannelCredentials.createBuilder(s2aAddress).build(); + ChannelCredentials credentials = S2AChannelCredentials.newBuilder(s2aAddress).build(); ManagedChannel channel = Grpc.newChannelBuilder(serverAddress, credentials).build(); assertThat(doUnaryRpc(channel)).isTrue(); @@ -203,7 +203,7 @@ public void clientCommunicateUsingS2ACredentialsNoLocalIdentity_succeeds() throw @Test public void clientCommunicateUsingMtlsToS2ACredentials_succeeds() throws Exception { ChannelCredentials credentials = - MtlsToS2AChannelCredentials.createBuilder( + MtlsToS2AChannelCredentials.newBuilder( /* s2aAddress= */ mtlsS2AAddress, /* privateKeyPath= */ "src/test/resources/client_key.pem", /* certChainPath= */ "src/test/resources/client_cert.pem", @@ -218,7 +218,7 @@ public void clientCommunicateUsingMtlsToS2ACredentials_succeeds() throws Excepti @Test public void clientCommunicateUsingS2ACredentials_s2AdelayStart_succeeds() throws Exception { - ChannelCredentials credentials = S2AChannelCredentials.createBuilder(s2aDelayAddress).build(); + ChannelCredentials credentials = S2AChannelCredentials.newBuilder(s2aDelayAddress).build(); ManagedChannel channel = Grpc.newChannelBuilder(serverAddress, credentials).build(); FutureTask rpc = new FutureTask<>(() -> doUnaryRpc(channel)); diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactoryTest.java b/s2a/src/test/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactoryTest.java index f130e52aac7..404910e8be0 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactoryTest.java +++ b/s2a/src/test/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactoryTest.java @@ -115,7 +115,7 @@ public void createProtocolNegotiator_nullArgument() throws Exception { S2AGrpcChannelPool.create( SharedResourcePool.forResource( S2AHandshakerServiceChannel.getChannelResource( - "localhost:8080", /* s2aChannelCredentials= */ Optional.empty()))); + "localhost:8080", InsecureChannelCredentials.create()))); NullPointerTester tester = new NullPointerTester() diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/S2AStubTest.java b/s2a/src/test/java/io/grpc/s2a/handshaker/S2AStubTest.java index bb90be12b6a..47fd154d949 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/S2AStubTest.java +++ b/s2a/src/test/java/io/grpc/s2a/handshaker/S2AStubTest.java @@ -21,13 +21,13 @@ import static org.junit.Assert.assertThrows; import com.google.common.truth.Expect; +import io.grpc.InsecureChannelCredentials; import io.grpc.internal.SharedResourcePool; import io.grpc.s2a.channel.S2AChannelPool; import io.grpc.s2a.channel.S2AGrpcChannelPool; import io.grpc.s2a.channel.S2AHandshakerServiceChannel; import io.grpc.stub.StreamObserver; import java.io.IOException; -import java.util.Optional; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -55,7 +55,7 @@ public void send_receiveOkStatus() throws Exception { S2AGrpcChannelPool.create( SharedResourcePool.forResource( S2AHandshakerServiceChannel.getChannelResource( - S2A_ADDRESS, /* s2aChannelCredentials= */ Optional.empty()))); + S2A_ADDRESS, InsecureChannelCredentials.create()))); S2AServiceGrpc.S2AServiceStub serviceStub = S2AServiceGrpc.newStub(channelPool.getChannel()); S2AStub newStub = S2AStub.newInstance(serviceStub); From 99be6e9852817fa5b85e0634984020d766e50d1f Mon Sep 17 00:00:00 2001 From: John Cormie Date: Mon, 23 Sep 2024 20:37:09 -0700 Subject: [PATCH 078/103] Address Android 11's package visibility rules. (#11551) --- .../android-binderchannel-status-codes.md | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/documentation/android-binderchannel-status-codes.md b/documentation/android-binderchannel-status-codes.md index dda0220bf8a..28bdd8907c1 100644 --- a/documentation/android-binderchannel-status-codes.md +++ b/documentation/android-binderchannel-status-codes.md @@ -23,15 +23,23 @@ Consider the table that follows as an BinderChannel-specific addendum to the “ - 1 + 0 - Server app not installed + Server app not visible. + + bindService() returns false + +

UNIMPLEMENTED

“The operation is not implemented or is not supported / enabled in this service.” + + Give up - This is an error in the client manifest. - bindService() returns false + + + 1 -

UNIMPLEMENTED

“The operation is not implemented or is not supported / enabled in this service.” + Server app not installed - Direct the user to install/reinstall the server app. + Direct the user to install/reinstall the server app. @@ -90,6 +98,8 @@ Consider the table that follows as an BinderChannel-specific addendum to the “

PERMISSION_DENIED

“The caller does not have permission to execute the specified operation …” + Direct the user to update the server app in the hopes that a newer version fixes this error in its manifest. + 10 @@ -315,6 +325,7 @@ According to a review of the AOSP source code, there are in fact several cases: 1. The target package is not installed 2. The target package is installed but does not declare the target Service in its manifest. 3. The target package requests dangerous permissions but targets sdk <= M and therefore requires a permissions review, but the caller is not running in the foreground and so it would be inappropriate to launch the review UI. +4. The target package is not visible to the client due to [Android 11 package visibility rules](https://developer.android.com/training/package-visibility). Status code mapping: **UNIMPLEMENTED** @@ -322,6 +333,7 @@ Status code mapping: **UNIMPLEMENTED** Unfortunately `UNIMPLEMENTED` doesn’t capture (3) but none of the other canonical status codes do either and we expect this case to be extremely rare. +(4) is intentially indistinguishable from (1) by Android design so we can't handle it differently. However, as a client manifest error, it's not something reasonable apps would handle at runtime anyway. ### bindService() throws SecurityException @@ -382,4 +394,4 @@ Android’s Parcel class exposes a mechanism for marshalling certain types of `R The calling Activity or Service Context might be destroyed with a gRPC request in flight. Apps should cease operations when the Context hosting it goes away and this includes cancelling any outstanding RPCs. -Status code mapping: **CANCELLED** \ No newline at end of file +Status code mapping: **CANCELLED** From 2ff837ab6007baf41c75a04dc31bcdbb96fa1a3c Mon Sep 17 00:00:00 2001 From: "Mark S. Lewis" Date: Fri, 20 Sep 2024 16:14:59 +0100 Subject: [PATCH 079/103] Update protobuf-java to address CVE-2024-7254 Signed-off-by: Mark S. Lewis --- examples/build.gradle | 2 +- examples/example-alts/build.gradle | 2 +- examples/example-debug/build.gradle | 2 +- examples/example-debug/pom.xml | 2 +- examples/example-dualstack/build.gradle | 2 +- examples/example-dualstack/pom.xml | 2 +- examples/example-gauth/build.gradle | 2 +- examples/example-gauth/pom.xml | 2 +- examples/example-gcp-csm-observability/build.gradle | 2 +- examples/example-gcp-observability/build.gradle | 2 +- examples/example-hostname/build.gradle | 2 +- examples/example-hostname/pom.xml | 2 +- examples/example-jwt-auth/build.gradle | 2 +- examples/example-jwt-auth/pom.xml | 4 ++-- examples/example-oauth/build.gradle | 2 +- examples/example-oauth/pom.xml | 4 ++-- examples/example-opentelemetry/build.gradle | 2 +- examples/example-orca/build.gradle | 2 +- examples/example-reflection/build.gradle | 2 +- examples/example-servlet/build.gradle | 2 +- examples/example-tls/build.gradle | 2 +- examples/example-tls/pom.xml | 2 +- examples/example-xds/build.gradle | 2 +- examples/pom.xml | 4 ++-- gradle/libs.versions.toml | 2 +- 25 files changed, 28 insertions(+), 28 deletions(-) diff --git a/examples/build.gradle b/examples/build.gradle index c10b4eef46a..24f6d2b04f8 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -24,7 +24,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protobufVersion = '3.25.3' +def protobufVersion = '3.25.5' def protocVersion = protobufVersion dependencies { diff --git a/examples/example-alts/build.gradle b/examples/example-alts/build.gradle index 0d7d959de93..6b1f0ded169 100644 --- a/examples/example-alts/build.gradle +++ b/examples/example-alts/build.gradle @@ -25,7 +25,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protocVersion = '3.25.3' +def protocVersion = '3.25.5' dependencies { // grpc-alts transitively depends on grpc-netty-shaded, grpc-protobuf, and grpc-stub diff --git a/examples/example-debug/build.gradle b/examples/example-debug/build.gradle index 5565747cb19..97bca5f91b6 100644 --- a/examples/example-debug/build.gradle +++ b/examples/example-debug/build.gradle @@ -26,7 +26,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protobufVersion = '3.25.3' +def protobufVersion = '3.25.5' dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" diff --git a/examples/example-debug/pom.xml b/examples/example-debug/pom.xml index 064d989c04c..dc399a78c9d 100644 --- a/examples/example-debug/pom.xml +++ b/examples/example-debug/pom.xml @@ -13,7 +13,7 @@ UTF-8 1.68.0-SNAPSHOT - 3.25.3 + 3.25.5 1.8 1.8 diff --git a/examples/example-dualstack/build.gradle b/examples/example-dualstack/build.gradle index 554b5f758d9..0c45e6de7cf 100644 --- a/examples/example-dualstack/build.gradle +++ b/examples/example-dualstack/build.gradle @@ -26,7 +26,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protobufVersion = '3.25.3' +def protobufVersion = '3.25.5' dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" diff --git a/examples/example-dualstack/pom.xml b/examples/example-dualstack/pom.xml index dfd650cdfa4..a8e32b347e5 100644 --- a/examples/example-dualstack/pom.xml +++ b/examples/example-dualstack/pom.xml @@ -13,7 +13,7 @@ UTF-8 1.68.0-SNAPSHOT - 3.25.3 + 3.25.5 1.8 1.8 diff --git a/examples/example-gauth/build.gradle b/examples/example-gauth/build.gradle index 47e812fde15..86edd557206 100644 --- a/examples/example-gauth/build.gradle +++ b/examples/example-gauth/build.gradle @@ -25,7 +25,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protobufVersion = '3.25.3' +def protobufVersion = '3.25.5' def protocVersion = protobufVersion diff --git a/examples/example-gauth/pom.xml b/examples/example-gauth/pom.xml index d2cba1a7959..89478a2dfa6 100644 --- a/examples/example-gauth/pom.xml +++ b/examples/example-gauth/pom.xml @@ -13,7 +13,7 @@ UTF-8 1.68.0-SNAPSHOT - 3.25.3 + 3.25.5 1.8 1.8 diff --git a/examples/example-gcp-csm-observability/build.gradle b/examples/example-gcp-csm-observability/build.gradle index a392018ba25..179ab3a74d9 100644 --- a/examples/example-gcp-csm-observability/build.gradle +++ b/examples/example-gcp-csm-observability/build.gradle @@ -26,7 +26,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protocVersion = '3.25.3' +def protocVersion = '3.25.5' def openTelemetryVersion = '1.40.0' def openTelemetryPrometheusVersion = '1.40.0-alpha' diff --git a/examples/example-gcp-observability/build.gradle b/examples/example-gcp-observability/build.gradle index dcb8d420020..1a6f4719460 100644 --- a/examples/example-gcp-observability/build.gradle +++ b/examples/example-gcp-observability/build.gradle @@ -26,7 +26,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protocVersion = '3.25.3' +def protocVersion = '3.25.5' dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" diff --git a/examples/example-hostname/build.gradle b/examples/example-hostname/build.gradle index df8b0fde121..17817e2a374 100644 --- a/examples/example-hostname/build.gradle +++ b/examples/example-hostname/build.gradle @@ -24,7 +24,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protobufVersion = '3.25.3' +def protobufVersion = '3.25.5' dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" diff --git a/examples/example-hostname/pom.xml b/examples/example-hostname/pom.xml index c6d39887bac..991174af382 100644 --- a/examples/example-hostname/pom.xml +++ b/examples/example-hostname/pom.xml @@ -13,7 +13,7 @@ UTF-8 1.68.0-SNAPSHOT - 3.25.3 + 3.25.5 1.8 1.8 diff --git a/examples/example-jwt-auth/build.gradle b/examples/example-jwt-auth/build.gradle index f996282bbb0..c31486095f3 100644 --- a/examples/example-jwt-auth/build.gradle +++ b/examples/example-jwt-auth/build.gradle @@ -24,7 +24,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protobufVersion = '3.25.3' +def protobufVersion = '3.25.5' def protocVersion = protobufVersion dependencies { diff --git a/examples/example-jwt-auth/pom.xml b/examples/example-jwt-auth/pom.xml index c84f9893980..10330d955ed 100644 --- a/examples/example-jwt-auth/pom.xml +++ b/examples/example-jwt-auth/pom.xml @@ -14,8 +14,8 @@ UTF-8 1.68.0-SNAPSHOT - 3.25.3 - 3.25.3 + 3.25.5 + 3.25.5 1.8 1.8 diff --git a/examples/example-oauth/build.gradle b/examples/example-oauth/build.gradle index 7f600c2bc53..fe21efcf0db 100644 --- a/examples/example-oauth/build.gradle +++ b/examples/example-oauth/build.gradle @@ -24,7 +24,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protobufVersion = '3.25.3' +def protobufVersion = '3.25.5' def protocVersion = protobufVersion dependencies { diff --git a/examples/example-oauth/pom.xml b/examples/example-oauth/pom.xml index fa2eaa41e36..072bd957dcf 100644 --- a/examples/example-oauth/pom.xml +++ b/examples/example-oauth/pom.xml @@ -14,8 +14,8 @@ UTF-8 1.68.0-SNAPSHOT - 3.25.3 - 3.25.3 + 3.25.5 + 3.25.5 1.8 1.8 diff --git a/examples/example-opentelemetry/build.gradle b/examples/example-opentelemetry/build.gradle index 21264ffcc17..f08f9d492a5 100644 --- a/examples/example-opentelemetry/build.gradle +++ b/examples/example-opentelemetry/build.gradle @@ -25,7 +25,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protocVersion = '3.25.3' +def protocVersion = '3.25.5' def openTelemetryVersion = '1.40.0' def openTelemetryPrometheusVersion = '1.40.0-alpha' diff --git a/examples/example-orca/build.gradle b/examples/example-orca/build.gradle index d087a532aff..4ca9343f887 100644 --- a/examples/example-orca/build.gradle +++ b/examples/example-orca/build.gradle @@ -19,7 +19,7 @@ java { } def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protocVersion = '3.25.3' +def protocVersion = '3.25.5' dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" diff --git a/examples/example-reflection/build.gradle b/examples/example-reflection/build.gradle index d7d5c50b7e6..e1efc8ee050 100644 --- a/examples/example-reflection/build.gradle +++ b/examples/example-reflection/build.gradle @@ -19,7 +19,7 @@ java { } def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protocVersion = '3.25.3' +def protocVersion = '3.25.5' dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" diff --git a/examples/example-servlet/build.gradle b/examples/example-servlet/build.gradle index 995e2d0979b..ebd14674578 100644 --- a/examples/example-servlet/build.gradle +++ b/examples/example-servlet/build.gradle @@ -17,7 +17,7 @@ java { } def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protocVersion = '3.25.3' +def protocVersion = '3.25.5' dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}", diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index 8aad6b62bcb..8a16a902b72 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -25,7 +25,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protocVersion = '3.25.3' +def protocVersion = '3.25.5' dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" diff --git a/examples/example-tls/pom.xml b/examples/example-tls/pom.xml index e1d569a628c..972976ecdf1 100644 --- a/examples/example-tls/pom.xml +++ b/examples/example-tls/pom.xml @@ -13,7 +13,7 @@ UTF-8 1.68.0-SNAPSHOT - 3.25.3 + 3.25.5 1.8 1.8 diff --git a/examples/example-xds/build.gradle b/examples/example-xds/build.gradle index 8339db77e0c..a3e23a19601 100644 --- a/examples/example-xds/build.gradle +++ b/examples/example-xds/build.gradle @@ -24,7 +24,7 @@ java { // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.68.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protocVersion = '3.25.3' +def protocVersion = '3.25.5' dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" diff --git a/examples/pom.xml b/examples/pom.xml index 247df4a73ce..554c70a35ce 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -13,8 +13,8 @@ UTF-8 1.68.0-SNAPSHOT - 3.25.3 - 3.25.3 + 3.25.5 + 3.25.5 1.8 1.8 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 488ead9ad86..8d7fb3766e0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ nettytcnative = '2.0.65.Final' opencensus = "0.31.1" # Not upgrading to 4.x as it is not yet ABI compatible. # https://github.com/protocolbuffers/protobuf/issues/17247 -protobuf = "3.25.3" +protobuf = "3.25.5" [libraries] android-annotations = "com.google.android:annotations:4.1.1.4" From c92453fb14ef21674cd00194ec28bfa186f2eef2 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Tue, 24 Sep 2024 15:40:40 -0700 Subject: [PATCH 080/103] s2a: Disabling publishing until it is ready for users --- s2a/build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/s2a/build.gradle b/s2a/build.gradle index 234f983fd5c..af2c879a753 100644 --- a/s2a/build.gradle +++ b/s2a/build.gradle @@ -1,6 +1,5 @@ plugins { id "java-library" - id "maven-publish" id "com.github.johnrengelman.shadow" id "com.google.osdetector" @@ -90,6 +89,7 @@ tasks.named("shadowJar").configure { relocate 'io.netty', 'io.grpc.netty.shaded.io.netty' } +plugins.withId('maven-publish') { publishing { publications { maven(MavenPublication) { @@ -111,3 +111,4 @@ publishing { } } } +} From 3e8ef8cf0ced8f910b4a0d2ce04f8771d1219bdd Mon Sep 17 00:00:00 2001 From: Vindhya Ningegowda Date: Tue, 24 Sep 2024 16:18:34 -0700 Subject: [PATCH 081/103] xds: Check for validity of xdsClient in ClusterImplLbHelper (#11553) * Added null check for xdsClient in onSubChannelState. This avoids NPE for xdsClient when LB is shutdown and onSubChannelState is called later as part of listener callback. As shutdown is racy and eventually consistent, this check would avoid calculating locality after LB is shutdown. --- xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java index 0ea2c7dd75f..3b30b8fa036 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java @@ -236,7 +236,8 @@ public void start(SubchannelStateListener listener) { delegate().start(new SubchannelStateListener() { @Override public void onSubchannelState(ConnectivityStateInfo newState) { - if (newState.getState().equals(ConnectivityState.READY)) { + // Do nothing if LB has been shutdown + if (xdsClient != null && newState.getState().equals(ConnectivityState.READY)) { // Get locality based on the connected address attributes ClusterLocality updatedClusterLocality = createClusterLocalityFromAttributes( subchannel.getConnectedAddressAttributes()); From 5dbca0e80c03003fb72272f35bbc988f9175654b Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Wed, 25 Sep 2024 09:17:26 -0700 Subject: [PATCH 082/103] xds: Improve ClusterImpl's FakeSubchannel to verify state changes The main goal was to make sure subchannels went CONNECTING only after a connection was requested (since the test doesn't transition to CONNECTING from TF). That helps guarantee that the test is using the expected subchannel. The missing ClusterImplLB.requestConnection() doesn't actually matter much, as cluster manager doesn't propagate connection requests. --- .../io/grpc/xds/ClusterImplLoadBalancer.java | 7 ++++ .../grpc/xds/ClusterImplLoadBalancerTest.java | 33 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java index 3b30b8fa036..2a9435aa72f 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java @@ -163,6 +163,13 @@ public void handleNameResolutionError(Status error) { } } + @Override + public void requestConnection() { + if (childSwitchLb != null) { + childSwitchLb.requestConnection(); + } + } + @Override public void shutdown() { if (dropStats != null) { diff --git a/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java index aaaed9554f4..c627d60a010 100644 --- a/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java @@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -280,6 +281,7 @@ public void pick_addsLocalityLabel() { FakeLoadBalancer leafBalancer = Iterables.getOnlyElement(downstreamBalancers); leafBalancer.createSubChannel(); FakeSubchannel fakeSubchannel = helper.subchannels.poll(); + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.CONNECTING)); fakeSubchannel.setConnectedEagIndex(0); fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); assertThat(currentState).isEqualTo(ConnectivityState.READY); @@ -309,6 +311,7 @@ public void recordLoadStats() { FakeLoadBalancer leafBalancer = Iterables.getOnlyElement(downstreamBalancers); Subchannel subchannel = leafBalancer.createSubChannel(); FakeSubchannel fakeSubchannel = helper.subchannels.poll(); + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.CONNECTING)); fakeSubchannel.setConnectedEagIndex(0); fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); assertThat(currentState).isEqualTo(ConnectivityState.READY); @@ -407,6 +410,7 @@ public void pickFirstLoadReport_onUpdateAddress() { // Leaf balancer is created by Pick First. Get FakeSubchannel created to update attributes // A real subchannel would get these attributes from the connected address's EAG locality. FakeSubchannel fakeSubchannel = helper.subchannels.poll(); + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.CONNECTING)); fakeSubchannel.setConnectedEagIndex(0); fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); assertThat(currentState).isEqualTo(ConnectivityState.READY); @@ -490,6 +494,7 @@ public void dropRpcsWithRespectToLbConfigDropCategories() { .isEqualTo(endpoint.getAddresses()); leafBalancer.createSubChannel(); FakeSubchannel fakeSubchannel = helper.subchannels.poll(); + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.CONNECTING)); fakeSubchannel.setConnectedEagIndex(0); fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); @@ -571,6 +576,7 @@ private void subtest_maxConcurrentRequests_appliedByLbConfig(boolean enableCircu .isEqualTo(endpoint.getAddresses()); leafBalancer.createSubChannel(); FakeSubchannel fakeSubchannel = helper.subchannels.poll(); + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.CONNECTING)); fakeSubchannel.setConnectedEagIndex(0); fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); assertThat(currentState).isEqualTo(ConnectivityState.READY); @@ -665,6 +671,7 @@ private void subtest_maxConcurrentRequests_appliedWithDefaultValue( .isEqualTo(endpoint.getAddresses()); leafBalancer.createSubChannel(); FakeSubchannel fakeSubchannel = helper.subchannels.poll(); + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.CONNECTING)); fakeSubchannel.setConnectedEagIndex(0); fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); assertThat(currentState).isEqualTo(ConnectivityState.READY); @@ -943,6 +950,7 @@ Subchannel createSubChannel() { new FixedResultPicker(PickResult.withSubchannel(subchannel))); } }); + subchannel.requestConnection(); return subchannel; } } @@ -989,6 +997,8 @@ private static final class FakeSubchannel extends Subchannel { private final Attributes attrs; private SubchannelStateListener listener; private Attributes connectedAttributes; + private ConnectivityStateInfo state = ConnectivityStateInfo.forNonError(ConnectivityState.IDLE); + private boolean connectionRequested; private FakeSubchannel(List eags, Attributes attrs) { this.eags = eags; @@ -1006,6 +1016,9 @@ public void shutdown() { @Override public void requestConnection() { + if (state.getState() == ConnectivityState.IDLE) { + this.connectionRequested = true; + } } @Override @@ -1028,6 +1041,26 @@ public Attributes getConnectedAddressAttributes() { } public void updateState(ConnectivityStateInfo newState) { + switch (newState.getState()) { + case IDLE: + assertThat(state.getState()).isEqualTo(ConnectivityState.READY); + break; + case CONNECTING: + assertThat(state.getState()) + .isIn(Arrays.asList(ConnectivityState.IDLE, ConnectivityState.TRANSIENT_FAILURE)); + if (state.getState() == ConnectivityState.IDLE) { + assertWithMessage("Connection requested").that(this.connectionRequested).isTrue(); + this.connectionRequested = false; + } + break; + case READY: + case TRANSIENT_FAILURE: + assertThat(state.getState()).isEqualTo(ConnectivityState.CONNECTING); + break; + default: + break; + } + this.state = newState; listener.onSubchannelState(newState); } From 64e3801538624c81741798298cc2af759955d1d1 Mon Sep 17 00:00:00 2001 From: Kannan J Date: Thu, 26 Sep 2024 21:04:57 +0530 Subject: [PATCH 083/103] Update RELEASING.md (#11559) 1. Removing $ when looking for the commit 'Start of development cycle...' because it produces empty result with the $. It seems how the squash was done may influence whether $ will work or not. 2. Added an explicit git push instruction at step 5 of tagging and what base branch to use, since it will cause conflict with the default base branch used of master. --- RELEASING.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/RELEASING.md b/RELEASING.md index bb1b77d0557..0add8991746 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -65,7 +65,7 @@ would be used to create all `v1.7` tags (e.g. `v1.7.0`, `v1.7.1`). ```bash git fetch upstream git checkout -b v$MAJOR.$MINOR.x \ - $(git log --pretty=format:%H --grep "^Start $MAJOR.$((MINOR+1)).0 development cycle$" upstream/master)^ + $(git log --pretty=format:%H --grep "^Start $MAJOR.$((MINOR+1)).0 development cycle" upstream/master)^ git push upstream v$MAJOR.$MINOR.x ``` 5. Continue with Google-internal steps at go/grpc-java/releasing, but stop @@ -132,7 +132,9 @@ Tagging the Release compiler/src/test{,Lite}/golden/Test{,Deprecated}Service.java.txt ./gradlew build git commit -a -m "Bump version to $MAJOR.$MINOR.$((PATCH+1))-SNAPSHOT" + git push -u origin release-v$MAJOR.$MINOR.$PATCH ``` + Raise a PR and set the base branch of the PR to v$MAJOR.$MINOR.x of the upstream grpc-java repo. 6. Go through PR review and push the release tag and updated release branch to GitHub (DO NOT click the merge button on the GitHub page): From 1c069375ceaa86f613769505b9c234c43ca6fffc Mon Sep 17 00:00:00 2001 From: erm-g <110920239+erm-g@users.noreply.github.com> Date: Thu, 26 Sep 2024 12:01:11 -0400 Subject: [PATCH 084/103] core: SpiffeId parser (#11490) SpiffeId parser compliant with [official spec](https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md) --- .../java/io/grpc/internal/SpiffeUtil.java | 122 +++++++++++ .../java/io/grpc/internal/SpiffeUtilTest.java | 196 ++++++++++++++++++ 2 files changed, 318 insertions(+) create mode 100644 core/src/main/java/io/grpc/internal/SpiffeUtil.java create mode 100644 core/src/test/java/io/grpc/internal/SpiffeUtilTest.java diff --git a/core/src/main/java/io/grpc/internal/SpiffeUtil.java b/core/src/main/java/io/grpc/internal/SpiffeUtil.java new file mode 100644 index 00000000000..bddce3d035e --- /dev/null +++ b/core/src/main/java/io/grpc/internal/SpiffeUtil.java @@ -0,0 +1,122 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.internal; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Splitter; +import java.util.Locale; + +/** + * Helper utility to work with SPIFFE URIs. + * @see Standard + */ +public final class SpiffeUtil { + + private static final String PREFIX = "spiffe://"; + + private SpiffeUtil() {} + + /** + * Parses a URI string, applies validation rules described in SPIFFE standard, and, in case of + * success, returns parsed TrustDomain and Path. + * + * @param uri a String representing a SPIFFE ID + */ + public static SpiffeId parse(String uri) { + doInitialUriValidation(uri); + checkArgument(uri.toLowerCase(Locale.US).startsWith(PREFIX), "Spiffe Id must start with " + + PREFIX); + String domainAndPath = uri.substring(PREFIX.length()); + String trustDomain; + String path; + if (!domainAndPath.contains("/")) { + trustDomain = domainAndPath; + path = ""; + } else { + String[] parts = domainAndPath.split("/", 2); + trustDomain = parts[0]; + path = parts[1]; + checkArgument(!path.isEmpty(), "Path must not include a trailing '/'"); + } + validateTrustDomain(trustDomain); + validatePath(path); + if (!path.isEmpty()) { + path = "/" + path; + } + return new SpiffeId(trustDomain, path); + } + + private static void doInitialUriValidation(String uri) { + checkArgument(checkNotNull(uri, "uri").length() > 0, "Spiffe Id can't be empty"); + checkArgument(uri.length() <= 2048, "Spiffe Id maximum length is 2048 characters"); + checkArgument(!uri.contains("#"), "Spiffe Id must not contain query fragments"); + checkArgument(!uri.contains("?"), "Spiffe Id must not contain query parameters"); + } + + private static void validateTrustDomain(String trustDomain) { + checkArgument(!trustDomain.isEmpty(), "Trust Domain can't be empty"); + checkArgument(trustDomain.length() < 256, "Trust Domain maximum length is 255 characters"); + checkArgument(trustDomain.matches("[a-z0-9._-]+"), + "Trust Domain must contain only letters, numbers, dots, dashes, and underscores" + + " ([a-z0-9.-_])"); + } + + private static void validatePath(String path) { + if (path.isEmpty()) { + return; + } + checkArgument(!path.endsWith("/"), "Path must not include a trailing '/'"); + for (String segment : Splitter.on("/").split(path)) { + validatePathSegment(segment); + } + } + + private static void validatePathSegment(String pathSegment) { + checkArgument(!pathSegment.isEmpty(), "Individual path segments must not be empty"); + checkArgument(!(pathSegment.equals(".") || pathSegment.equals("..")), + "Individual path segments must not be relative path modifiers (i.e. ., ..)"); + checkArgument(pathSegment.matches("[a-zA-Z0-9._-]+"), + "Individual path segments must contain only letters, numbers, dots, dashes, and underscores" + + " ([a-zA-Z0-9.-_])"); + } + + /** + * Represents a SPIFFE ID as defined in the SPIFFE standard. + * @see Standard + */ + public static class SpiffeId { + + private final String trustDomain; + private final String path; + + private SpiffeId(String trustDomain, String path) { + this.trustDomain = trustDomain; + this.path = path; + } + + public String getTrustDomain() { + return trustDomain; + } + + public String getPath() { + return path; + } + } + +} diff --git a/core/src/test/java/io/grpc/internal/SpiffeUtilTest.java b/core/src/test/java/io/grpc/internal/SpiffeUtilTest.java new file mode 100644 index 00000000000..c3a98ce33e0 --- /dev/null +++ b/core/src/test/java/io/grpc/internal/SpiffeUtilTest.java @@ -0,0 +1,196 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import java.util.Arrays; +import java.util.Collection; +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + + +@RunWith(Enclosed.class) +public class SpiffeUtilTest { + + @RunWith(Parameterized.class) + public static class ParseSuccessTest { + @Parameter + public String uri; + + @Parameter(1) + public String trustDomain; + + @Parameter(2) + public String path; + + @Test + public void parseSuccessTest() { + SpiffeUtil.SpiffeId spiffeId = SpiffeUtil.parse(uri); + assertEquals(trustDomain, spiffeId.getTrustDomain()); + assertEquals(path, spiffeId.getPath()); + } + + @Parameters(name = "spiffeId={0}") + public static Collection data() { + return Arrays.asList(new String[][] { + {"spiffe://example.com", "example.com", ""}, + {"spiffe://example.com/us", "example.com", "/us"}, + {"spIFfe://qa-staging.final_check.example.com/us", "qa-staging.final_check.example.com", + "/us"}, + {"spiffe://example.com/country/us/state/FL/city/Miami", "example.com", + "/country/us/state/FL/city/Miami"}, + {"SPIFFE://example.com/Czech.Republic/region0.1/city_of-Prague", "example.com", + "/Czech.Republic/region0.1/city_of-Prague"}, + {"spiffe://trust-domain-name/path", "trust-domain-name", "/path"}, + {"spiffe://staging.example.com/payments/mysql", "staging.example.com", "/payments/mysql"}, + {"spiffe://staging.example.com/payments/web-fe", "staging.example.com", + "/payments/web-fe"}, + {"spiffe://k8s-west.example.com/ns/staging/sa/default", "k8s-west.example.com", + "/ns/staging/sa/default"}, + {"spiffe://example.com/9eebccd2-12bf-40a6-b262-65fe0487d453", "example.com", + "/9eebccd2-12bf-40a6-b262-65fe0487d453"}, + {"spiffe://trustdomain/.a..", "trustdomain", "/.a.."}, + {"spiffe://trustdomain/...", "trustdomain", "/..."}, + {"spiffe://trustdomain/abcdefghijklmnopqrstuvwxyz", "trustdomain", + "/abcdefghijklmnopqrstuvwxyz"}, + {"spiffe://trustdomain/abc0123.-_", "trustdomain", "/abc0123.-_"}, + {"spiffe://trustdomain/0123456789", "trustdomain", "/0123456789"}, + {"spiffe://trustdomain0123456789/path", "trustdomain0123456789", "/path"}, + }); + } + } + + @RunWith(Parameterized.class) + public static class ParseFailureTest { + @Parameter + public String uri; + + @Test + public void parseFailureTest() { + assertThrows(IllegalArgumentException.class, () -> SpiffeUtil.parse(uri)); + } + + @Parameters(name = "spiffeId={0}") + public static Collection data() { + return Arrays.asList( + "spiffe:///", + "spiffe://example!com", + "spiffe://exampleя.com/workload-1", + "spiffe://example.com/us/florida/miamiя", + "spiffe:/trustdomain/path", + "spiffe:///path", + "spiffe://trust%20domain/path", + "spiffe://user@trustdomain/path", + "spiffe:// /", + "", + "http://trustdomain/path", + "//trustdomain/path", + "://trustdomain/path", + "piffe://trustdomain/path", + "://", + "://trustdomain", + "spiff", + "spiffe", + "spiffe:////", + "spiffe://trust.domain/../path" + ); + } + } + + public static class ExceptionMessageTest { + + @Test + public void spiffeUriFormatTest() { + NullPointerException npe = assertThrows(NullPointerException.class, () -> + SpiffeUtil.parse(null)); + assertEquals("uri", npe.getMessage()); + + IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> + SpiffeUtil.parse("https://example.com")); + assertEquals("Spiffe Id must start with spiffe://", iae.getMessage()); + + iae = assertThrows(IllegalArgumentException.class, () -> + SpiffeUtil.parse("spiffe://example.com/workload#1")); + assertEquals("Spiffe Id must not contain query fragments", iae.getMessage()); + + iae = assertThrows(IllegalArgumentException.class, () -> + SpiffeUtil.parse("spiffe://example.com/workload-1?t=1")); + assertEquals("Spiffe Id must not contain query parameters", iae.getMessage()); + } + + @Test + public void spiffeTrustDomainFormatTest() { + IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> + SpiffeUtil.parse("spiffe://")); + assertEquals("Trust Domain can't be empty", iae.getMessage()); + + iae = assertThrows(IllegalArgumentException.class, () -> + SpiffeUtil.parse("spiffe://eXample.com")); + assertEquals( + "Trust Domain must contain only letters, numbers, dots, dashes, and underscores " + + "([a-z0-9.-_])", + iae.getMessage()); + + StringBuilder longTrustDomain = new StringBuilder("spiffe://pi.eu."); + for (int i = 0; i < 50; i++) { + longTrustDomain.append("pi.eu"); + } + iae = assertThrows(IllegalArgumentException.class, () -> + SpiffeUtil.parse(longTrustDomain.toString())); + assertEquals("Trust Domain maximum length is 255 characters", iae.getMessage()); + + StringBuilder longSpiffe = new StringBuilder(String.format("spiffe://mydomain%scom/", "%21")); + for (int i = 0; i < 405; i++) { + longSpiffe.append("qwert"); + } + iae = assertThrows(IllegalArgumentException.class, () -> + SpiffeUtil.parse(longSpiffe.toString())); + assertEquals("Spiffe Id maximum length is 2048 characters", iae.getMessage()); + } + + @Test + public void spiffePathFormatTest() { + IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> + SpiffeUtil.parse("spiffe://example.com//")); + assertEquals("Path must not include a trailing '/'", iae.getMessage()); + + iae = assertThrows(IllegalArgumentException.class, () -> + SpiffeUtil.parse("spiffe://example.com/")); + assertEquals("Path must not include a trailing '/'", iae.getMessage()); + + iae = assertThrows(IllegalArgumentException.class, () -> + SpiffeUtil.parse("spiffe://example.com/us//miami")); + assertEquals("Individual path segments must not be empty", iae.getMessage()); + + iae = assertThrows(IllegalArgumentException.class, () -> + SpiffeUtil.parse("spiffe://example.com/us/.")); + assertEquals("Individual path segments must not be relative path modifiers (i.e. ., ..)", + iae.getMessage()); + + iae = assertThrows(IllegalArgumentException.class, () -> + SpiffeUtil.parse("spiffe://example.com/us!")); + assertEquals("Individual path segments must contain only letters, numbers, dots, dashes, and " + + "underscores ([a-zA-Z0-9.-_])", iae.getMessage()); + } + } +} \ No newline at end of file From 9faa0f4eb0bbac31228938d25a15b69401dcde33 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Wed, 25 Sep 2024 09:22:07 -0700 Subject: [PATCH 085/103] xds: Update ClusterImpl test to work with PFLeafLB --- .../grpc/xds/ClusterImplLoadBalancerTest.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java index c627d60a010..7eba43ce278 100644 --- a/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java @@ -53,6 +53,7 @@ import io.grpc.SynchronizationContext; import io.grpc.internal.FakeClock; import io.grpc.internal.ObjectPool; +import io.grpc.internal.PickFirstLoadBalancerProvider; import io.grpc.internal.PickSubchannelArgsImpl; import io.grpc.protobuf.ProtoUtils; import io.grpc.testing.TestMethodDescriptors; @@ -384,9 +385,7 @@ public void recordLoadStats() { assertThat(clusterStats.upstreamLocalityStatsList()).isEmpty(); // no longer reported } - // TODO(dnvindhya): This test has been added as a fix to verify - // https://github.com/grpc/grpc-java/issues/11434. - // Once we update PickFirstLeafLoadBalancer as default LoadBalancer, update the test. + // Verifies https://github.com/grpc/grpc-java/issues/11434. @Test public void pickFirstLoadReport_onUpdateAddress() { Locality locality1 = @@ -435,8 +434,17 @@ public void pickFirstLoadReport_onUpdateAddress() { fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.CONNECTING)); // Faksubchannel mimics update address and returns different locality - fakeSubchannel.setConnectedEagIndex(1); - fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); + if (PickFirstLoadBalancerProvider.isEnabledNewPickFirst()) { + fakeSubchannel.updateState(ConnectivityStateInfo.forTransientFailure( + Status.UNAVAILABLE.withDescription("Try second address instead"))); + fakeSubchannel = helper.subchannels.poll(); + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.CONNECTING)); + fakeSubchannel.setConnectedEagIndex(0); + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); + } else { + fakeSubchannel.setConnectedEagIndex(1); + fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); + } result = currentPicker.pickSubchannel(pickSubchannelArgs); assertThat(result.getStatus().isOk()).isTrue(); ClientStreamTracer streamTracer2 = result.getStreamTracerFactory().newClientStreamTracer( From fa18fec36e8b8dda00bb2ca005938cea26180e24 Mon Sep 17 00:00:00 2001 From: Riya Mehta <55350838+rmehta19@users.noreply.github.com> Date: Fri, 27 Sep 2024 08:47:56 -0700 Subject: [PATCH 086/103] s2a: Address minor comments on PR#11113 (#11540) * Use StandardCharsets in FakeS2AServerTest. * Use add instead of offer in S2AStub. * remove dead code in ProtoUtil.java. * Mark convertTlsProtocolVersion as VisibleForTesting. * S2AStub doesn't return responses at front of queue. * Remove global SHARED_RESOURCE_CHANNELS. * Don't suppress RethrowReflectiveOperationExceptionAsLinkageError. * Update javadoc. * Make clear which certs are used in tests + add how to regenerate. --- .../channel/S2AHandshakerServiceChannel.java | 14 +- .../io/grpc/s2a/handshaker/ProtoUtil.java | 28 +-- .../java/io/grpc/s2a/handshaker/S2AStub.java | 15 +- .../tokenmanager/AccessTokenManager.java | 3 +- .../S2AHandshakerServiceChannelTest.java | 12 +- .../io/grpc/s2a/handshaker/FakeS2AServer.java | 7 +- .../s2a/handshaker/FakeS2AServerTest.java | 18 +- .../io/grpc/s2a/handshaker/FakeWriter.java | 165 ++++++++---------- .../grpc/s2a/handshaker/IntegrationTest.java | 80 +-------- .../io/grpc/s2a/handshaker/ProtoUtilTest.java | 41 ----- .../handshaker/S2APrivateKeyMethodTest.java | 5 +- .../S2AProtocolNegotiatorFactoryTest.java | 7 +- .../io/grpc/s2a/handshaker/S2AStubTest.java | 38 ++-- s2a/src/test/resources/README.md | 37 ++++ s2a/src/test/resources/cert_chain_ec.pem | 36 ++++ s2a/src/test/resources/int_cert1_.cnf | 14 ++ s2a/src/test/resources/int_cert1_ec.pem | 12 ++ s2a/src/test/resources/int_cert2.cnf | 14 ++ s2a/src/test/resources/int_cert2_ec.pem | 12 ++ s2a/src/test/resources/int_key1_ec.pem | 5 + s2a/src/test/resources/int_key2_ec.pem | 5 + s2a/src/test/resources/leaf.cnf | 14 ++ s2a/src/test/resources/leaf_cert_ec.pem | 12 ++ s2a/src/test/resources/leaf_key_ec.pem | 5 + s2a/src/test/resources/root_cert_ec.pem | 12 ++ s2a/src/test/resources/root_ec.cnf | 14 ++ s2a/src/test/resources/root_key_ec.pem | 5 + 27 files changed, 345 insertions(+), 285 deletions(-) create mode 100644 s2a/src/test/resources/cert_chain_ec.pem create mode 100644 s2a/src/test/resources/int_cert1_.cnf create mode 100644 s2a/src/test/resources/int_cert1_ec.pem create mode 100644 s2a/src/test/resources/int_cert2.cnf create mode 100644 s2a/src/test/resources/int_cert2_ec.pem create mode 100644 s2a/src/test/resources/int_key1_ec.pem create mode 100644 s2a/src/test/resources/int_key2_ec.pem create mode 100644 s2a/src/test/resources/leaf.cnf create mode 100644 s2a/src/test/resources/leaf_cert_ec.pem create mode 100644 s2a/src/test/resources/leaf_key_ec.pem create mode 100644 s2a/src/test/resources/root_cert_ec.pem create mode 100644 s2a/src/test/resources/root_ec.cnf create mode 100644 s2a/src/test/resources/root_key_ec.pem diff --git a/s2a/src/main/java/io/grpc/s2a/channel/S2AHandshakerServiceChannel.java b/s2a/src/main/java/io/grpc/s2a/channel/S2AHandshakerServiceChannel.java index 443ea553e52..9d6950ce041 100644 --- a/s2a/src/main/java/io/grpc/s2a/channel/S2AHandshakerServiceChannel.java +++ b/s2a/src/main/java/io/grpc/s2a/channel/S2AHandshakerServiceChannel.java @@ -20,7 +20,6 @@ import static java.util.concurrent.TimeUnit.SECONDS; import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.Maps; import io.grpc.CallOptions; import io.grpc.Channel; import io.grpc.ChannelCredentials; @@ -30,16 +29,15 @@ import io.grpc.internal.SharedResourceHolder.Resource; import io.grpc.netty.NettyChannelBuilder; import java.time.Duration; -import java.util.concurrent.ConcurrentMap; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.concurrent.ThreadSafe; /** - * Provides APIs for managing gRPC channels to S2A servers. Each channel is local and plaintext. If - * credentials are provided, they are used to secure the channel. + * Provides APIs for managing gRPC channels to an S2A server. Each channel is local and plaintext. + * If credentials are provided, they are used to secure the channel. * - *

This is done as follows: for each S2A server, provides an implementation of gRPC's {@link + *

This is done as follows: for an S2A server, provides an implementation of gRPC's {@link * SharedResourceHolder.Resource} interface called a {@code Resource}. A {@code * Resource} is a factory for creating gRPC channels to the S2A server at a given address, * and a channel must be returned to the {@code Resource} when it is no longer needed. @@ -56,8 +54,6 @@ */ @ThreadSafe public final class S2AHandshakerServiceChannel { - private static final ConcurrentMap> SHARED_RESOURCE_CHANNELS = - Maps.newConcurrentMap(); private static final Duration CHANNEL_SHUTDOWN_TIMEOUT = Duration.ofSeconds(10); /** @@ -72,9 +68,7 @@ public final class S2AHandshakerServiceChannel { public static Resource getChannelResource( String s2aAddress, ChannelCredentials s2aChannelCredentials) { checkNotNull(s2aAddress); - checkNotNull(s2aChannelCredentials); - return SHARED_RESOURCE_CHANNELS.computeIfAbsent( - s2aAddress, channelResource -> new ChannelResource(s2aAddress, s2aChannelCredentials)); + return new ChannelResource(s2aAddress, s2aChannelCredentials); } /** diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/ProtoUtil.java b/s2a/src/main/java/io/grpc/s2a/handshaker/ProtoUtil.java index 59e3931d9e6..129cc2d60f1 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/ProtoUtil.java +++ b/s2a/src/main/java/io/grpc/s2a/handshaker/ProtoUtil.java @@ -16,36 +16,11 @@ package io.grpc.s2a.handshaker; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableSet; /** Converts proto messages to Netty strings. */ final class ProtoUtil { - /** - * Converts {@link Ciphersuite} to its {@link String} representation. - * - * @param ciphersuite the {@link Ciphersuite} to be converted. - * @return a {@link String} representing the ciphersuite. - * @throws AssertionError if the {@link Ciphersuite} is not one of the supported ciphersuites. - */ - static String convertCiphersuite(Ciphersuite ciphersuite) { - switch (ciphersuite) { - case CIPHERSUITE_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: - return "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"; - case CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: - return "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"; - case CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: - return "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"; - case CIPHERSUITE_ECDHE_RSA_WITH_AES_128_GCM_SHA256: - return "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"; - case CIPHERSUITE_ECDHE_RSA_WITH_AES_256_GCM_SHA384: - return "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"; - case CIPHERSUITE_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: - return "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"; - default: - throw new AssertionError( - String.format("Ciphersuite %d is not supported.", ciphersuite.getNumber())); - } - } /** * Converts a {@link TLSVersion} object to its {@link String} representation. @@ -54,6 +29,7 @@ static String convertCiphersuite(Ciphersuite ciphersuite) { * @return a {@link String} representation of the TLS version. * @throws AssertionError if the {@code tlsVersion} is not one of the supported TLS versions. */ + @VisibleForTesting static String convertTlsProtocolVersion(TLSVersion tlsVersion) { switch (tlsVersion) { case TLS_VERSION_1_3: diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/S2AStub.java b/s2a/src/main/java/io/grpc/s2a/handshaker/S2AStub.java index 8249ca59d09..bf9b866ef93 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/S2AStub.java +++ b/s2a/src/main/java/io/grpc/s2a/handshaker/S2AStub.java @@ -84,6 +84,7 @@ BlockingQueue getResponses() { * @throws IOException if an unexpected response is received, or if the {@code reader} or {@code * writer} calls their {@code onError} method. */ + @SuppressWarnings("CheckReturnValue") public SessionResp send(SessionReq req) throws IOException, InterruptedException { if (doneWriting && doneReading) { logger.log(Level.INFO, "Stream to the S2A is closed."); @@ -92,9 +93,8 @@ public SessionResp send(SessionReq req) throws IOException, InterruptedException createWriterIfNull(); if (!responses.isEmpty()) { IOException exception = null; - SessionResp resp = null; try { - resp = responses.take().getResultOrThrow(); + responses.take().getResultOrThrow(); } catch (IOException e) { exception = e; } @@ -104,14 +104,15 @@ public SessionResp send(SessionReq req) throws IOException, InterruptedException "Received an unexpected response from a host at the S2A's address. The S2A might be" + " unavailable." + exception.getMessage()); + } else { + throw new IOException("Received an unexpected response from a host at the S2A's address."); } - return resp; } try { writer.onNext(req); } catch (RuntimeException e) { writer.onError(e); - responses.offer(Result.createWithThrowable(e)); + responses.add(Result.createWithThrowable(e)); } try { return responses.take().getResultOrThrow(); @@ -159,7 +160,7 @@ private class Reader implements StreamObserver { @Override public void onNext(SessionResp resp) { verify(!doneReading); - responses.offer(Result.createWithResponse(resp)); + responses.add(Result.createWithResponse(resp)); } /** @@ -169,7 +170,7 @@ public void onNext(SessionResp resp) { */ @Override public void onError(Throwable t) { - responses.offer(Result.createWithThrowable(t)); + responses.add(Result.createWithThrowable(t)); } /** @@ -180,7 +181,7 @@ public void onError(Throwable t) { public void onCompleted() { logger.log(Level.INFO, "Reading from the S2A is complete."); doneReading = true; - responses.offer( + responses.add( Result.createWithThrowable( new ConnectionClosedException("Reading from the S2A is complete."))); } diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/AccessTokenManager.java b/s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/AccessTokenManager.java index 94549d11c87..da75cf0d4dd 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/AccessTokenManager.java +++ b/s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/AccessTokenManager.java @@ -27,7 +27,6 @@ public final class AccessTokenManager { private final TokenFetcher tokenFetcher; /** Creates an {@code AccessTokenManager} based on the environment where the application runs. */ - @SuppressWarnings("RethrowReflectiveOperationExceptionAsLinkageError") public static Optional create() { Optional tokenFetcher; try { @@ -38,7 +37,7 @@ public static Optional create() { } catch (ClassNotFoundException e) { tokenFetcher = Optional.empty(); } catch (ReflectiveOperationException e) { - throw new AssertionError(e); + throw new LinkageError(e.getMessage(), e); } return tokenFetcher.isPresent() ? Optional.of(new AccessTokenManager((TokenFetcher) tokenFetcher.get())) diff --git a/s2a/src/test/java/io/grpc/s2a/channel/S2AHandshakerServiceChannelTest.java b/s2a/src/test/java/io/grpc/s2a/channel/S2AHandshakerServiceChannelTest.java index 7845e7c3bcb..7281adb9794 100644 --- a/s2a/src/test/java/io/grpc/s2a/channel/S2AHandshakerServiceChannelTest.java +++ b/s2a/src/test/java/io/grpc/s2a/channel/S2AHandshakerServiceChannelTest.java @@ -89,10 +89,10 @@ public void getChannelResource_mtlsSuccess() throws Exception { /** * Creates two {@code Resoure}s for the same target address and verifies that they are - * equal. + * distinct. */ @Test - public void getChannelResource_twoEqualChannels() { + public void getChannelResource_twoUnEqualChannels() { Resource resource = S2AHandshakerServiceChannel.getChannelResource( "localhost:" + plaintextServer.getPort(), @@ -101,19 +101,19 @@ public void getChannelResource_twoEqualChannels() { S2AHandshakerServiceChannel.getChannelResource( "localhost:" + plaintextServer.getPort(), InsecureChannelCredentials.create()); - assertThat(resource).isEqualTo(resourceTwo); + assertThat(resource).isNotEqualTo(resourceTwo); } - /** Same as getChannelResource_twoEqualChannels, but use mTLS. */ + /** Same as getChannelResource_twoUnEqualChannels, but use mTLS. */ @Test - public void getChannelResource_mtlsTwoEqualChannels() throws Exception { + public void getChannelResource_mtlsTwoUnEqualChannels() throws Exception { Resource resource = S2AHandshakerServiceChannel.getChannelResource( "localhost:" + mtlsServer.getPort(), getTlsChannelCredentials()); Resource resourceTwo = S2AHandshakerServiceChannel.getChannelResource( "localhost:" + mtlsServer.getPort(), getTlsChannelCredentials()); - assertThat(resource).isEqualTo(resourceTwo); + assertThat(resource).isNotEqualTo(resourceTwo); } /** diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServer.java b/s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServer.java index 66f636ada22..d630f57d90d 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServer.java +++ b/s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServer.java @@ -17,6 +17,7 @@ package io.grpc.s2a.handshaker; import io.grpc.stub.StreamObserver; +import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import java.util.logging.Logger; @@ -38,7 +39,11 @@ public StreamObserver setUpSession(StreamObserver respo @Override public void onNext(SessionReq req) { logger.info("Received a request from client."); - responseObserver.onNext(writer.handleResponse(req)); + try { + responseObserver.onNext(writer.handleResponse(req)); + } catch (IOException e) { + responseObserver.onError(e); + } } @Override diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServerTest.java b/s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServerTest.java index e200d119867..a8868744f80 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServerTest.java +++ b/s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServerTest.java @@ -29,6 +29,9 @@ import io.grpc.benchmarks.Utils; import io.grpc.s2a.handshaker.ValidatePeerCertificateChainReq.VerificationMode; import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -45,9 +48,7 @@ public final class FakeS2AServerTest { private static final Logger logger = Logger.getLogger(FakeS2AServerTest.class.getName()); private static final ImmutableList FAKE_CERT_DER_CHAIN = - ImmutableList.of( - ByteString.copyFrom( - new byte[] {'f', 'a', 'k', 'e', '-', 'd', 'e', 'r', '-', 'c', 'h', 'a', 'i', 'n'})); + ImmutableList.of(ByteString.copyFrom("fake-der-chain".getBytes(StandardCharsets.US_ASCII))); private int port; private String serverAddress; private SessionResp response = null; @@ -68,7 +69,7 @@ public void tearDown() { @Test public void callS2AServerOnce_getTlsConfiguration_returnsValidResult() - throws InterruptedException { + throws InterruptedException, IOException { ExecutorService executor = Executors.newSingleThreadExecutor(); logger.info("Client connecting to: " + serverAddress); ManagedChannel channel = @@ -122,9 +123,12 @@ public void onCompleted() {} GetTlsConfigurationResp.newBuilder() .setClientTlsConfiguration( GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() - .addCertificateChain(FakeWriter.LEAF_CERT) - .addCertificateChain(FakeWriter.INTERMEDIATE_CERT_2) - .addCertificateChain(FakeWriter.INTERMEDIATE_CERT_1) + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.leafCertFile.toPath()), StandardCharsets.UTF_8)) + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.cert1File.toPath()), StandardCharsets.UTF_8)) + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.cert2File.toPath()), StandardCharsets.UTF_8)) .setMinTlsVersion(TLSVersion.TLS_VERSION_1_3) .setMaxTlsVersion(TLSVersion.TLS_VERSION_1_3) .addCiphersuites( diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/FakeWriter.java b/s2a/src/test/java/io/grpc/s2a/handshaker/FakeWriter.java index 45961b81b7b..b0e84fdf962 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/FakeWriter.java +++ b/s2a/src/test/java/io/grpc/s2a/handshaker/FakeWriter.java @@ -23,7 +23,10 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.protobuf.ByteString; import io.grpc.stub.StreamObserver; +import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; @@ -50,59 +53,18 @@ enum VerificationResult { FAILURE } - public static final String LEAF_CERT = - "-----BEGIN CERTIFICATE-----\n" - + "MIICkDCCAjagAwIBAgIUSAtcrPhNNs1zxv51lIfGOVtkw6QwCgYIKoZIzj0EAwIw\n" - + "QTEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xEDAOBgNVBAsMB2NvbnRleHQxFDAS\n" - + "BgorBgEEAdZ5AggBDAQyMDIyMCAXDTIzMDcxNDIyMzYwNFoYDzIwNTAxMTI5MjIz\n" - + "NjA0WjARMQ8wDQYDVQQDDAZ1bnVzZWQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC\n" - + "AAQGFlJpLxJMh4HuUm0DKjnUF7larH3tJvroQ12xpk+pPKQepn4ILoq9lZ8Xd3jz\n" - + "U98eDRXG5f4VjnX98DDHE4Ido4IBODCCATQwDgYDVR0PAQH/BAQDAgeAMCAGA1Ud\n" - + "JQEB/wQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMIGxBgNV\n" - + "HREBAf8EgaYwgaOGSnNwaWZmZTovL3NpZ25lci1yb2xlLmNvbnRleHQuc2VjdXJp\n" - + "dHktcmVhbG0ucHJvZC5nb29nbGUuY29tL3JvbGUvbGVhZi1yb2xlgjNzaWduZXIt\n" - + "cm9sZS5jb250ZXh0LnNlY3VyaXR5LXJlYWxtLnByb2Quc3BpZmZlLmdvb2eCIGZx\n" - + "ZG4tb2YtdGhlLW5vZGUucHJvZC5nb29nbGUuY29tMB0GA1UdDgQWBBSWSd5Fw6dI\n" - + "TGpt0m1Uxwf0iKqebzAfBgNVHSMEGDAWgBRm5agVVdpWfRZKM7u6OMuzHhqPcDAK\n" - + "BggqhkjOPQQDAgNIADBFAiB0sjRPSYy2eFq8Y0vQ8QN4AZ2NMajskvxnlifu7O4U\n" - + "RwIhANTh5Fkyx2nMYFfyl+W45dY8ODTw3HnlZ4b51hTAdkWl\n" - + "-----END CERTIFICATE-----"; - public static final String INTERMEDIATE_CERT_2 = - "-----BEGIN CERTIFICATE-----\n" - + "MIICQjCCAeigAwIBAgIUKxXRDlnWXefNV5lj5CwhDuXEq7MwCgYIKoZIzj0EAwIw\n" - + "OzEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xEDAOBgNVBAsMB2NvbnRleHQxDjAM\n" - + "BgNVBAMMBTEyMzQ1MCAXDTIzMDcxNDIyMzYwNFoYDzIwNTAxMTI5MjIzNjA0WjBB\n" - + "MRcwFQYDVQQKDA5zZWN1cml0eS1yZWFsbTEQMA4GA1UECwwHY29udGV4dDEUMBIG\n" - + "CisGAQQB1nkCCAEMBDIwMjIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT/Zu7x\n" - + "UYVyg+T/vg2H+y4I6t36Kc4qxD0eqqZjRLYBVKkUQHxBqc14t0DpoROMYQCNd4DF\n" - + "pcxv/9m6DaJbRk6Ao4HBMIG+MA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG\n" - + "AQH/AgEBMFgGA1UdHgEB/wROMEygSjA1gjNzaWduZXItcm9sZS5jb250ZXh0LnNl\n" - + "Y3VyaXR5LXJlYWxtLnByb2Quc3BpZmZlLmdvb2cwEYIPcHJvZC5nb29nbGUuY29t\n" - + "MB0GA1UdDgQWBBRm5agVVdpWfRZKM7u6OMuzHhqPcDAfBgNVHSMEGDAWgBQcjNAh\n" - + "SCHTj+BW8KrzSSLo2ASEgjAKBggqhkjOPQQDAgNIADBFAiEA6KyGd9VxXDZceMZG\n" - + "IsbC40rtunFjLYI0mjZw9RcRWx8CIHCIiIHxafnDaCi+VB99NZfzAdu37g6pJptB\n" - + "gjIY71MO\n" - + "-----END CERTIFICATE-----"; - public static final String INTERMEDIATE_CERT_1 = - "-----BEGIN CERTIFICATE-----\n" - + "MIICODCCAd6gAwIBAgIUXtZECORWRSKnS9rRTJYkiALUXswwCgYIKoZIzj0EAwIw\n" - + "NzEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xDTALBgNVBAsMBHJvb3QxDTALBgNV\n" - + "BAMMBDEyMzQwIBcNMjMwNzE0MjIzNjA0WhgPMjA1MDExMjkyMjM2MDRaMDsxFzAV\n" - + "BgNVBAoMDnNlY3VyaXR5LXJlYWxtMRAwDgYDVQQLDAdjb250ZXh0MQ4wDAYDVQQD\n" - + "DAUxMjM0NTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAycVTZrjockbpD59f1a\n" - + "4l1SNL7nSyXz66Guz4eDveQqLmaMBg7vpACfO4CtiAGnolHEffuRtSkdM434m5En\n" - + "bXCjgcEwgb4wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQIwWAYD\n" - + "VR0eAQH/BE4wTKBKMDWCM3NpZ25lci1yb2xlLmNvbnRleHQuc2VjdXJpdHktcmVh\n" - + "bG0ucHJvZC5zcGlmZmUuZ29vZzARgg9wcm9kLmdvb2dsZS5jb20wHQYDVR0OBBYE\n" - + "FByM0CFIIdOP4FbwqvNJIujYBISCMB8GA1UdIwQYMBaAFMX+vebuj/lYfYEC23IA\n" - + "8HoIW0HsMAoGCCqGSM49BAMCA0gAMEUCIQCfxeXEBd7UPmeImT16SseCRu/6cHxl\n" - + "kTDsq9sKZ+eXBAIgA+oViAVOUhUQO1/6Mjlczg8NmMy2vNtG4V/7g9dMMVU=\n" - + "-----END CERTIFICATE-----"; + public static final File leafCertFile = + new File("src/test/resources/leaf_cert_ec.pem"); + public static final File cert2File = + new File("src/test/resources/int_cert2_ec.pem"); + public static final File cert1File = + new File("src/test/resources/int_cert1_ec.pem"); + // src/test/resources/leaf_key_ec.pem private static final String PRIVATE_KEY = - "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgqA2U0ld1OOHLMXWf" - + "uyN4GSaqhhudEIaKkll3rdIq0M+hRANCAAQGFlJpLxJMh4HuUm0DKjnUF7larH3t" - + "JvroQ12xpk+pPKQepn4ILoq9lZ8Xd3jzU98eDRXG5f4VjnX98DDHE4Id"; + "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgR2HBqtWTWu4NLiow" + + "ar8vh+9vAmCONE59C+jXNAb9r8ehRANCAATRM8ozcr8PTOVsZNWh+rTmJ6t+rODu" + + "g3LwWpUQq9h7AddjGlLrrTNrceOyO7nh9aEk5plKhs/h7PO8+vkEFsEx"; private static final ImmutableMap ALGORITHM_TO_SIGNATURE_INSTANCE_IDENTIFIER = ImmutableMap.of( @@ -167,24 +129,32 @@ void sendIoError() { } void sendGetTlsConfigResp() { - reader.onNext( - SessionResp.newBuilder() - .setGetTlsConfigurationResp( - GetTlsConfigurationResp.newBuilder() - .setClientTlsConfiguration( - GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() - .addCertificateChain(LEAF_CERT) - .addCertificateChain(INTERMEDIATE_CERT_2) - .addCertificateChain(INTERMEDIATE_CERT_1) - .setMinTlsVersion(TLS_VERSION_1_3) - .setMaxTlsVersion(TLS_VERSION_1_3) - .addCiphersuites( - Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) - .addCiphersuites( - Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) - .addCiphersuites( - Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256))) - .build()); + try { + reader.onNext( + SessionResp.newBuilder() + .setGetTlsConfigurationResp( + GetTlsConfigurationResp.newBuilder() + .setClientTlsConfiguration( + GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.leafCertFile.toPath()), StandardCharsets.UTF_8)) + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.cert1File.toPath()), StandardCharsets.UTF_8)) + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.cert2File.toPath()), StandardCharsets.UTF_8)) + .setMinTlsVersion(TLS_VERSION_1_3) + .setMaxTlsVersion(TLS_VERSION_1_3) + .addCiphersuites( + Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) + .addCiphersuites( + Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) + .addCiphersuites( + Ciphersuite + .CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256))) + .build()); + } catch (IOException e) { + reader.onError(e); + } } boolean isFakeWriterClosed() { @@ -195,7 +165,11 @@ boolean isFakeWriterClosed() { public void onNext(SessionReq sessionReq) { switch (behavior) { case OK_STATUS: - reader.onNext(handleResponse(sessionReq)); + try { + reader.onNext(handleResponse(sessionReq)); + } catch (IOException e) { + reader.onError(e); + } break; case EMPTY_RESPONSE: reader.onNext(SessionResp.getDefaultInstance()); @@ -216,25 +190,36 @@ public void onNext(SessionReq sessionReq) { reader.onCompleted(); break; case BAD_TLS_VERSION_RESPONSE: - reader.onNext( - SessionResp.newBuilder() - .setGetTlsConfigurationResp( - GetTlsConfigurationResp.newBuilder() - .setClientTlsConfiguration( - GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() - .addCertificateChain(LEAF_CERT) - .addCertificateChain(INTERMEDIATE_CERT_2) - .addCertificateChain(INTERMEDIATE_CERT_1) - .setMinTlsVersion(TLS_VERSION_1_3) - .setMaxTlsVersion(TLS_VERSION_1_2))) - .build()); + try { + reader.onNext( + SessionResp.newBuilder() + .setGetTlsConfigurationResp( + GetTlsConfigurationResp.newBuilder() + .setClientTlsConfiguration( + GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.leafCertFile.toPath()), StandardCharsets.UTF_8)) + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.cert1File.toPath()), StandardCharsets.UTF_8)) + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.cert2File.toPath()), StandardCharsets.UTF_8)) + .setMinTlsVersion(TLS_VERSION_1_3) + .setMaxTlsVersion(TLS_VERSION_1_2))) + .build()); + } catch (IOException e) { + reader.onError(e); + } break; default: - reader.onNext(handleResponse(sessionReq)); + try { + reader.onNext(handleResponse(sessionReq)); + } catch (IOException e) { + reader.onError(e); + } } } - SessionResp handleResponse(SessionReq sessionReq) { + SessionResp handleResponse(SessionReq sessionReq) throws IOException { if (sessionReq.hasGetTlsConfigurationReq()) { return handleGetTlsConfigurationReq(sessionReq.getGetTlsConfigurationReq()); } @@ -253,7 +238,8 @@ SessionResp handleResponse(SessionReq sessionReq) { .build(); } - private SessionResp handleGetTlsConfigurationReq(GetTlsConfigurationReq req) { + private SessionResp handleGetTlsConfigurationReq(GetTlsConfigurationReq req) + throws IOException { if (!req.getConnectionSide().equals(ConnectionSide.CONNECTION_SIDE_CLIENT)) { return SessionResp.newBuilder() .setStatus( @@ -267,9 +253,12 @@ private SessionResp handleGetTlsConfigurationReq(GetTlsConfigurationReq req) { GetTlsConfigurationResp.newBuilder() .setClientTlsConfiguration( GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() - .addCertificateChain(LEAF_CERT) - .addCertificateChain(INTERMEDIATE_CERT_2) - .addCertificateChain(INTERMEDIATE_CERT_1) + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.leafCertFile.toPath()), StandardCharsets.UTF_8)) + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.cert1File.toPath()), StandardCharsets.UTF_8)) + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.cert2File.toPath()), StandardCharsets.UTF_8)) .setMinTlsVersion(TLS_VERSION_1_3) .setMaxTlsVersion(TLS_VERSION_1_3) .addCiphersuites( diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java b/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java index bae58f2f9ec..2a2b1f246ec 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java +++ b/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java @@ -17,7 +17,6 @@ package io.grpc.s2a.handshaker; import static com.google.common.truth.Truth.assertThat; -import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.concurrent.TimeUnit.SECONDS; import io.grpc.ChannelCredentials; @@ -42,7 +41,6 @@ import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslProvider; -import java.io.ByteArrayInputStream; import java.io.File; import java.util.concurrent.FutureTask; import java.util.logging.Logger; @@ -58,72 +56,12 @@ public final class IntegrationTest { private static final Logger logger = Logger.getLogger(FakeS2AServer.class.getName()); - private static final String CERT_CHAIN = - "-----BEGIN CERTIFICATE-----\n" - + "MIICkDCCAjagAwIBAgIUSAtcrPhNNs1zxv51lIfGOVtkw6QwCgYIKoZIzj0EAwIw\n" - + "QTEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xEDAOBgNVBAsMB2NvbnRleHQxFDAS\n" - + "BgorBgEEAdZ5AggBDAQyMDIyMCAXDTIzMDcxNDIyMzYwNFoYDzIwNTAxMTI5MjIz\n" - + "NjA0WjARMQ8wDQYDVQQDDAZ1bnVzZWQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC\n" - + "AAQGFlJpLxJMh4HuUm0DKjnUF7larH3tJvroQ12xpk+pPKQepn4ILoq9lZ8Xd3jz\n" - + "U98eDRXG5f4VjnX98DDHE4Ido4IBODCCATQwDgYDVR0PAQH/BAQDAgeAMCAGA1Ud\n" - + "JQEB/wQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMIGxBgNV\n" - + "HREBAf8EgaYwgaOGSnNwaWZmZTovL3NpZ25lci1yb2xlLmNvbnRleHQuc2VjdXJp\n" - + "dHktcmVhbG0ucHJvZC5nb29nbGUuY29tL3JvbGUvbGVhZi1yb2xlgjNzaWduZXIt\n" - + "cm9sZS5jb250ZXh0LnNlY3VyaXR5LXJlYWxtLnByb2Quc3BpZmZlLmdvb2eCIGZx\n" - + "ZG4tb2YtdGhlLW5vZGUucHJvZC5nb29nbGUuY29tMB0GA1UdDgQWBBSWSd5Fw6dI\n" - + "TGpt0m1Uxwf0iKqebzAfBgNVHSMEGDAWgBRm5agVVdpWfRZKM7u6OMuzHhqPcDAK\n" - + "BggqhkjOPQQDAgNIADBFAiB0sjRPSYy2eFq8Y0vQ8QN4AZ2NMajskvxnlifu7O4U\n" - + "RwIhANTh5Fkyx2nMYFfyl+W45dY8ODTw3HnlZ4b51hTAdkWl\n" - + "-----END CERTIFICATE-----\n" - + "-----BEGIN CERTIFICATE-----\n" - + "MIICQjCCAeigAwIBAgIUKxXRDlnWXefNV5lj5CwhDuXEq7MwCgYIKoZIzj0EAwIw\n" - + "OzEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xEDAOBgNVBAsMB2NvbnRleHQxDjAM\n" - + "BgNVBAMMBTEyMzQ1MCAXDTIzMDcxNDIyMzYwNFoYDzIwNTAxMTI5MjIzNjA0WjBB\n" - + "MRcwFQYDVQQKDA5zZWN1cml0eS1yZWFsbTEQMA4GA1UECwwHY29udGV4dDEUMBIG\n" - + "CisGAQQB1nkCCAEMBDIwMjIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT/Zu7x\n" - + "UYVyg+T/vg2H+y4I6t36Kc4qxD0eqqZjRLYBVKkUQHxBqc14t0DpoROMYQCNd4DF\n" - + "pcxv/9m6DaJbRk6Ao4HBMIG+MA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG\n" - + "AQH/AgEBMFgGA1UdHgEB/wROMEygSjA1gjNzaWduZXItcm9sZS5jb250ZXh0LnNl\n" - + "Y3VyaXR5LXJlYWxtLnByb2Quc3BpZmZlLmdvb2cwEYIPcHJvZC5nb29nbGUuY29t\n" - + "MB0GA1UdDgQWBBRm5agVVdpWfRZKM7u6OMuzHhqPcDAfBgNVHSMEGDAWgBQcjNAh\n" - + "SCHTj+BW8KrzSSLo2ASEgjAKBggqhkjOPQQDAgNIADBFAiEA6KyGd9VxXDZceMZG\n" - + "IsbC40rtunFjLYI0mjZw9RcRWx8CIHCIiIHxafnDaCi+VB99NZfzAdu37g6pJptB\n" - + "gjIY71MO\n" - + "-----END CERTIFICATE-----\n" - + "-----BEGIN CERTIFICATE-----\n" - + "MIICODCCAd6gAwIBAgIUXtZECORWRSKnS9rRTJYkiALUXswwCgYIKoZIzj0EAwIw\n" - + "NzEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xDTALBgNVBAsMBHJvb3QxDTALBgNV\n" - + "BAMMBDEyMzQwIBcNMjMwNzE0MjIzNjA0WhgPMjA1MDExMjkyMjM2MDRaMDsxFzAV\n" - + "BgNVBAoMDnNlY3VyaXR5LXJlYWxtMRAwDgYDVQQLDAdjb250ZXh0MQ4wDAYDVQQD\n" - + "DAUxMjM0NTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAycVTZrjockbpD59f1a\n" - + "4l1SNL7nSyXz66Guz4eDveQqLmaMBg7vpACfO4CtiAGnolHEffuRtSkdM434m5En\n" - + "bXCjgcEwgb4wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQIwWAYD\n" - + "VR0eAQH/BE4wTKBKMDWCM3NpZ25lci1yb2xlLmNvbnRleHQuc2VjdXJpdHktcmVh\n" - + "bG0ucHJvZC5zcGlmZmUuZ29vZzARgg9wcm9kLmdvb2dsZS5jb20wHQYDVR0OBBYE\n" - + "FByM0CFIIdOP4FbwqvNJIujYBISCMB8GA1UdIwQYMBaAFMX+vebuj/lYfYEC23IA\n" - + "8HoIW0HsMAoGCCqGSM49BAMCA0gAMEUCIQCfxeXEBd7UPmeImT16SseCRu/6cHxl\n" - + "kTDsq9sKZ+eXBAIgA+oViAVOUhUQO1/6Mjlczg8NmMy2vNtG4V/7g9dMMVU=\n" - + "-----END CERTIFICATE-----"; - private static final String ROOT_PEM = - "-----BEGIN CERTIFICATE-----\n" - + "MIIBtTCCAVqgAwIBAgIUbAe+8OocndQXRBCElLBxBSdfdV8wCgYIKoZIzj0EAwIw\n" - + "NzEXMBUGA1UECgwOc2VjdXJpdHktcmVhbG0xDTALBgNVBAsMBHJvb3QxDTALBgNV\n" - + "BAMMBDEyMzQwIBcNMjMwNzE0MjIzNjA0WhgPMjA1MDExMjkyMjM2MDRaMDcxFzAV\n" - + "BgNVBAoMDnNlY3VyaXR5LXJlYWxtMQ0wCwYDVQQLDARyb290MQ0wCwYDVQQDDAQx\n" - + "MjM0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaMY2tBW5r1t0+vhayz0ZoGMF\n" - + "boX/ZmmCmIh0iTWg4madvwNOh74CMVVvDUlXZcuVqZ3vVIX/a7PTFVqUwQlKW6NC\n" - + "MEAwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMX+\n" - + "vebuj/lYfYEC23IA8HoIW0HsMAoGCCqGSM49BAMCA0kAMEYCIQDETd27nsUTXKWY\n" - + "CiOno78O09gK95NoTkPU5e2chJYMqAIhALYFAyh7PU5xgFQsN9hiqgsHUc5/pmBG\n" - + "BGjJ1iz8rWGJ\n" - + "-----END CERTIFICATE-----"; - private static final String PRIVATE_KEY = - "-----BEGIN PRIVATE KEY-----\n" - + "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgqA2U0ld1OOHLMXWf\n" - + "uyN4GSaqhhudEIaKkll3rdIq0M+hRANCAAQGFlJpLxJMh4HuUm0DKjnUF7larH3t\n" - + "JvroQ12xpk+pPKQepn4ILoq9lZ8Xd3jzU98eDRXG5f4VjnX98DDHE4Id\n" - + "-----END PRIVATE KEY-----"; - + public static final File privateKeyFile = + new File("src/test/resources/leaf_key_ec.pem"); + public static final File rootCertFile = + new File("src/test/resources/root_cert_ec.pem"); + public static final File certChainFile = + new File("src/test/resources/cert_chain_ec.pem"); private String s2aAddress; private Server s2aServer; private String s2aDelayAddress; @@ -252,13 +190,11 @@ public static boolean doUnaryRpc(ManagedChannel channel) throws InterruptedExcep private static SslContext buildSslContext() throws SSLException { SslContextBuilder sslServerContextBuilder = - SslContextBuilder.forServer( - new ByteArrayInputStream(CERT_CHAIN.getBytes(UTF_8)), - new ByteArrayInputStream(PRIVATE_KEY.getBytes(UTF_8))); + SslContextBuilder.forServer(certChainFile, privateKeyFile); SslContext sslServerContext = GrpcSslContexts.configure(sslServerContextBuilder, SslProvider.OPENSSL) .protocols("TLSv1.3", "TLSv1.2") - .trustManager(new ByteArrayInputStream(ROOT_PEM.getBytes(UTF_8))) + .trustManager(rootCertFile) .clientAuth(ClientAuth.REQUIRE) .build(); diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/ProtoUtilTest.java b/s2a/src/test/java/io/grpc/s2a/handshaker/ProtoUtilTest.java index 6d134b43f7a..f54063b9e04 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/ProtoUtilTest.java +++ b/s2a/src/test/java/io/grpc/s2a/handshaker/ProtoUtilTest.java @@ -30,47 +30,6 @@ public final class ProtoUtilTest { @Rule public final Expect expect = Expect.create(); - @Test - public void convertCiphersuite_success() { - expect - .that( - ProtoUtil.convertCiphersuite( - Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)) - .isEqualTo("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"); - expect - .that( - ProtoUtil.convertCiphersuite( - Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384)) - .isEqualTo("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"); - expect - .that( - ProtoUtil.convertCiphersuite( - Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256)) - .isEqualTo("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"); - expect - .that( - ProtoUtil.convertCiphersuite(Ciphersuite.CIPHERSUITE_ECDHE_RSA_WITH_AES_128_GCM_SHA256)) - .isEqualTo("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"); - expect - .that( - ProtoUtil.convertCiphersuite(Ciphersuite.CIPHERSUITE_ECDHE_RSA_WITH_AES_256_GCM_SHA384)) - .isEqualTo("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"); - expect - .that( - ProtoUtil.convertCiphersuite( - Ciphersuite.CIPHERSUITE_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256)) - .isEqualTo("TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"); - } - - @Test - public void convertCiphersuite_withUnspecifiedCiphersuite_fails() { - AssertionError expected = - assertThrows( - AssertionError.class, - () -> ProtoUtil.convertCiphersuite(Ciphersuite.CIPHERSUITE_UNSPECIFIED)); - expect.that(expected).hasMessageThat().isEqualTo("Ciphersuite 0 is not supported."); - } - @Test public void convertTlsProtocolVersion_success() { expect diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/S2APrivateKeyMethodTest.java b/s2a/src/test/java/io/grpc/s2a/handshaker/S2APrivateKeyMethodTest.java index 8252aa245d7..fc8d42d09c0 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/S2APrivateKeyMethodTest.java +++ b/s2a/src/test/java/io/grpc/s2a/handshaker/S2APrivateKeyMethodTest.java @@ -30,6 +30,8 @@ import io.netty.handler.ssl.OpenSslPrivateKeyMethod; import io.netty.handler.ssl.SslContextBuilder; import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.security.PublicKey; import java.security.Signature; import java.security.cert.CertificateFactory; @@ -61,7 +63,8 @@ private static PublicKey extractPublicKeyFromPem(String pem) throws Exception { private static boolean verifySignature( byte[] dataToSign, byte[] signature, String signatureAlgorithm) throws Exception { Signature sig = Signature.getInstance(signatureAlgorithm); - sig.initVerify(extractPublicKeyFromPem(FakeWriter.LEAF_CERT)); + sig.initVerify(extractPublicKeyFromPem(new String( + Files.readAllBytes(FakeWriter.leafCertFile.toPath()), StandardCharsets.UTF_8))); sig.update(dataToSign); return sig.verify(signature); } diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactoryTest.java b/s2a/src/test/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactoryTest.java index 404910e8be0..fa6c4fc858d 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactoryTest.java +++ b/s2a/src/test/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactoryTest.java @@ -50,6 +50,7 @@ import io.netty.handler.codec.http2.Http2ConnectionEncoder; import io.netty.handler.codec.http2.Http2Settings; import io.netty.util.AsciiString; +import java.io.IOException; import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -246,7 +247,11 @@ public StreamObserver setUpSession(StreamObserver respo return new StreamObserver() { @Override public void onNext(SessionReq req) { - responseObserver.onNext(writer.handleResponse(req)); + try { + responseObserver.onNext(writer.handleResponse(req)); + } catch (IOException e) { + responseObserver.onError(e); + } } @Override diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/S2AStubTest.java b/s2a/src/test/java/io/grpc/s2a/handshaker/S2AStubTest.java index 47fd154d949..a1daf9948a1 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/S2AStubTest.java +++ b/s2a/src/test/java/io/grpc/s2a/handshaker/S2AStubTest.java @@ -28,6 +28,8 @@ import io.grpc.s2a.channel.S2AHandshakerServiceChannel; import io.grpc.stub.StreamObserver; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -82,9 +84,12 @@ public void send_clientTlsConfiguration_receiveOkStatus() throws Exception { GetTlsConfigurationResp.newBuilder() .setClientTlsConfiguration( GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() - .addCertificateChain(FakeWriter.LEAF_CERT) - .addCertificateChain(FakeWriter.INTERMEDIATE_CERT_2) - .addCertificateChain(FakeWriter.INTERMEDIATE_CERT_1) + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.leafCertFile.toPath()), StandardCharsets.UTF_8)) + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.cert1File.toPath()), StandardCharsets.UTF_8)) + .addCertificateChain(new String(Files.readAllBytes( + FakeWriter.cert2File.toPath()), StandardCharsets.UTF_8)) .setMinTlsVersion(TLSVersion.TLS_VERSION_1_3) .setMaxTlsVersion(TLSVersion.TLS_VERSION_1_3) .addCiphersuites( @@ -189,26 +194,13 @@ public void send_receiveManyUnexpectedResponse_expectResponsesEmpty() throws Exc @Test public void send_receiveDelayedResponse() throws Exception { writer.sendGetTlsConfigResp(); - SessionResp resp = stub.send(SessionReq.getDefaultInstance()); - SessionResp expected = - SessionResp.newBuilder() - .setGetTlsConfigurationResp( - GetTlsConfigurationResp.newBuilder() - .setClientTlsConfiguration( - GetTlsConfigurationResp.ClientTlsConfiguration.newBuilder() - .addCertificateChain(FakeWriter.LEAF_CERT) - .addCertificateChain(FakeWriter.INTERMEDIATE_CERT_2) - .addCertificateChain(FakeWriter.INTERMEDIATE_CERT_1) - .setMinTlsVersion(TLSVersion.TLS_VERSION_1_3) - .setMaxTlsVersion(TLSVersion.TLS_VERSION_1_3) - .addCiphersuites( - Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) - .addCiphersuites( - Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) - .addCiphersuites( - Ciphersuite.CIPHERSUITE_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256))) - .build(); - assertThat(resp).ignoringRepeatedFieldOrder().isEqualTo(expected); + IOException expectedException = + assertThrows(IOException.class, () -> stub.send(SessionReq.getDefaultInstance())); + assertThat(expectedException) + .hasMessageThat() + .contains("Received an unexpected response from a host at the S2A's address."); + + assertThat(stub.getResponses()).isEmpty(); } @Test diff --git a/s2a/src/test/resources/README.md b/s2a/src/test/resources/README.md index 726b921a615..2250ffb1dec 100644 --- a/s2a/src/test/resources/README.md +++ b/s2a/src/test/resources/README.md @@ -29,4 +29,41 @@ Sign CSRs for server and client ``` openssl x509 -req -CA root_cert.pem -CAkey root_key.pem -in server.csr -out server_cert.pem -days 7305 -extfile config.cnf -extensions req_ext openssl x509 -req -CA root_cert.pem -CAkey root_key.pem -in client.csr -out client_cert.pem -days 7305 +``` + +Generate self-signed ECDSA root cert + +``` +openssl ecparam -name prime256v1 -genkey -noout -out temp.pem +openssl pkcs8 -topk8 -in temp.pem -out root_key_ec.pem -nocrypt +rm temp.pem +openssl req -x509 -days 7305 -new -key root_key_ec.pem -nodes -out root_cert_ec.pem -config root_ec.cnf -extensions 'v3_req' +``` + +Generate a chain of ECDSA certs + +``` +openssl ecparam -name prime256v1 -genkey -noout -out temp.pem +openssl pkcs8 -topk8 -in temp.pem -out int_key2_ec.pem -nocrypt +rm temp.pem +openssl req -key int_key2_ec.pem -new -out temp.csr -config int_cert2.cnf +openssl x509 -req -days 7305 -in temp.csr -CA root_cert_ec.pem -CAkey root_key_ec.pem -CAcreateserial -out int_cert2_ec.pem -extfile int_cert2.cnf -extensions 'v3_req' + + +openssl ecparam -name prime256v1 -genkey -noout -out temp.pem +openssl pkcs8 -topk8 -in temp.pem -out int_key1_ec.pem -nocrypt +rm temp.pem +openssl req -key int_key1_ec.pem -new -out temp.csr -config int_cert1.cnf +openssl x509 -req -days 7305 -in temp.csr -CA int_cert2_ec.pem -CAkey int_key2_ec.pem -CAcreateserial -out int_cert1_ec.pem -extfile int_cert1.cnf -extensions 'v3_req' + + +openssl ecparam -name prime256v1 -genkey -noout -out temp.pem +openssl pkcs8 -topk8 -in temp.pem -out leaf_key_ec.pem -nocrypt +rm temp.pem +openssl req -key leaf_key_ec.pem -new -out temp.csr -config leaf.cnf +openssl x509 -req -days 7305 -in temp.csr -CA int_cert1_ec.pem -CAkey int_key1_ec.pem -CAcreateserial -out leaf_cert_ec.pem -extfile leaf.cnf -extensions 'v3_req' +``` + +``` +cat leaf_cert_ec.pem int_cert1_ec.pem int_cert2_ec.pem > cert_chain_ec.pem ``` \ No newline at end of file diff --git a/s2a/src/test/resources/cert_chain_ec.pem b/s2a/src/test/resources/cert_chain_ec.pem new file mode 100644 index 00000000000..0e097d39bf2 --- /dev/null +++ b/s2a/src/test/resources/cert_chain_ec.pem @@ -0,0 +1,36 @@ +-----BEGIN CERTIFICATE----- +MIIB0jCCAXigAwIBAgIUBV1dftEhhEMTI83L6jpeJn2tuyQwCgYIKoZIzj0EAwIw +JjEKMAgGA1UECgwBbzELMAkGA1UECwwCb3UxCzAJBgNVBAMMAmNuMB4XDTI0MDkx +OTIzMDQwMFoXDTQ0MDkxOTIzMDQwMFowJjEKMAgGA1UECgwBbzELMAkGA1UECwwC +b3UxCzAJBgNVBAMMAmNuMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0TPKM3K/ +D0zlbGTVofq05ierfqzg7oNy8FqVEKvYewHXYxpS660za3Hjsju54fWhJOaZSobP +4ezzvPr5BBbBMaOBgzCBgDAOBgNVHQ8BAf8EBAMCB4AwIAYDVR0lAQH/BBYwFAYI +KwYBBQUHAwIGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFPvP7dnB +dg8ZoLB0w62tbvoIRRHPMB8GA1UdIwQYMBaAFHeH+MNh2fgyjNHYP9hLAv9Sl1yD +MAoGCCqGSM49BAMCA0gAMEUCIBcsImaxeFjxFXCXYNQJnde+rsEOgbeAHrAC0SZQ +NlB2AiEA4epDhw/o+6BfgDbqlZsNEHkScPrwupnBQGLQlmNJe2c= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIB1zCCAX6gAwIBAgIUW4GYHncSLeb7Tmw7FMjX/DesReIwCgYIKoZIzj0EAwIw +JjEKMAgGA1UECgwBbzELMAkGA1UECwwCb3UxCzAJBgNVBAMMAmNuMB4XDTI0MDkx +OTIzMDA0MVoXDTQ0MDkxOTIzMDA0MVowJjEKMAgGA1UECgwBbzELMAkGA1UECwwC +b3UxCzAJBgNVBAMMAmNuMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAqtg2E+h +Wfr5dnewqsCLwM0PohkB83Gh7V3i/TPFkNKF/V6pKdz5a3Z8sicG+g7uJX+eyOoD +43Z8woO7MgJl8aOBiTCBhjAOBgNVHQ8BAf8EBAMCAQYwIAYDVR0lAQH/BBYwFAYI +KwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE +FHeH+MNh2fgyjNHYP9hLAv9Sl1yDMB8GA1UdIwQYMBaAFP+PTOryxis9d7HVfqhP +MyMEgMZOMAoGCCqGSM49BAMCA0cAMEQCIHbzJvxHMIDPBRi1e047K0mqKKBSfViS +guiDSoQ5g5OuAiBT5ePqDLs4PyrK6XFkiEWoRX8Z5T9y419Go+fpLM+DaA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIB1zCCAX6gAwIBAgIUBBKkTrqFxQUist2pK2uj8/DRnKMwCgYIKoZIzj0EAwIw +JjEKMAgGA1UECgwBbzELMAkGA1UECwwCb3UxCzAJBgNVBAMMAmNuMB4XDTI0MDkx +OTIyNTYwNloXDTQ0MDkxOTIyNTYwNlowJjEKMAgGA1UECgwBbzELMAkGA1UECwwC +b3UxCzAJBgNVBAMMAmNuMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFW7/Te2z +jS8KlpF8RMMYaZtKf6EZlrZIIo5SO9j6baAKXVna9LmDCrzXnOLIeqOuZq0ODizU +i4DFALB2yd5BkaOBiTCBhjAOBgNVHQ8BAf8EBAMCAQYwIAYDVR0lAQH/BBYwFAYI +KwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYBAf8CAQIwHQYDVR0OBBYE +FP+PTOryxis9d7HVfqhPMyMEgMZOMB8GA1UdIwQYMBaAFFITbB0BULPtynN9SMki +lEarWxcKMAoGCCqGSM49BAMCA0cAMEQCIHK4cTTx4Ti7Te9hA9VVtHoMCt5fL4Cl +XnQR6D5xW4pPAiAQ+CilQdZUhVK5bU6wbrwLgcwf+40ETK/KASId5970rQ== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/s2a/src/test/resources/int_cert1_.cnf b/s2a/src/test/resources/int_cert1_.cnf new file mode 100644 index 00000000000..8eaf6570da1 --- /dev/null +++ b/s2a/src/test/resources/int_cert1_.cnf @@ -0,0 +1,14 @@ +[req] +distinguished_name = req_distinguished_name +req_extensions = v3_req +prompt = no + +[req_distinguished_name] +O = o +OU = ou +CN = cn + +[v3_req] +keyUsage = critical, keyCertSign, cRLSign +extendedKeyUsage = critical, clientAuth, serverAuth +basicConstraints = critical, CA:true, pathlen: 1 \ No newline at end of file diff --git a/s2a/src/test/resources/int_cert1_ec.pem b/s2a/src/test/resources/int_cert1_ec.pem new file mode 100644 index 00000000000..980de5aa900 --- /dev/null +++ b/s2a/src/test/resources/int_cert1_ec.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIB1zCCAX6gAwIBAgIUW4GYHncSLeb7Tmw7FMjX/DesReIwCgYIKoZIzj0EAwIw +JjEKMAgGA1UECgwBbzELMAkGA1UECwwCb3UxCzAJBgNVBAMMAmNuMB4XDTI0MDkx +OTIzMDA0MVoXDTQ0MDkxOTIzMDA0MVowJjEKMAgGA1UECgwBbzELMAkGA1UECwwC +b3UxCzAJBgNVBAMMAmNuMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAqtg2E+h +Wfr5dnewqsCLwM0PohkB83Gh7V3i/TPFkNKF/V6pKdz5a3Z8sicG+g7uJX+eyOoD +43Z8woO7MgJl8aOBiTCBhjAOBgNVHQ8BAf8EBAMCAQYwIAYDVR0lAQH/BBYwFAYI +KwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE +FHeH+MNh2fgyjNHYP9hLAv9Sl1yDMB8GA1UdIwQYMBaAFP+PTOryxis9d7HVfqhP +MyMEgMZOMAoGCCqGSM49BAMCA0cAMEQCIHbzJvxHMIDPBRi1e047K0mqKKBSfViS +guiDSoQ5g5OuAiBT5ePqDLs4PyrK6XFkiEWoRX8Z5T9y419Go+fpLM+DaA== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/s2a/src/test/resources/int_cert2.cnf b/s2a/src/test/resources/int_cert2.cnf new file mode 100644 index 00000000000..c6a2559ce10 --- /dev/null +++ b/s2a/src/test/resources/int_cert2.cnf @@ -0,0 +1,14 @@ +[req] +distinguished_name = req_distinguished_name +req_extensions = v3_req +prompt = no + +[req_distinguished_name] +O = o +OU = ou +CN = cn + +[v3_req] +keyUsage = critical, keyCertSign, cRLSign +extendedKeyUsage = critical, clientAuth, serverAuth +basicConstraints = critical, CA:true, pathlen: 2 \ No newline at end of file diff --git a/s2a/src/test/resources/int_cert2_ec.pem b/s2a/src/test/resources/int_cert2_ec.pem new file mode 100644 index 00000000000..574fa0195de --- /dev/null +++ b/s2a/src/test/resources/int_cert2_ec.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIB1zCCAX6gAwIBAgIUBBKkTrqFxQUist2pK2uj8/DRnKMwCgYIKoZIzj0EAwIw +JjEKMAgGA1UECgwBbzELMAkGA1UECwwCb3UxCzAJBgNVBAMMAmNuMB4XDTI0MDkx +OTIyNTYwNloXDTQ0MDkxOTIyNTYwNlowJjEKMAgGA1UECgwBbzELMAkGA1UECwwC +b3UxCzAJBgNVBAMMAmNuMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFW7/Te2z +jS8KlpF8RMMYaZtKf6EZlrZIIo5SO9j6baAKXVna9LmDCrzXnOLIeqOuZq0ODizU +i4DFALB2yd5BkaOBiTCBhjAOBgNVHQ8BAf8EBAMCAQYwIAYDVR0lAQH/BBYwFAYI +KwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYBAf8CAQIwHQYDVR0OBBYE +FP+PTOryxis9d7HVfqhPMyMEgMZOMB8GA1UdIwQYMBaAFFITbB0BULPtynN9SMki +lEarWxcKMAoGCCqGSM49BAMCA0cAMEQCIHK4cTTx4Ti7Te9hA9VVtHoMCt5fL4Cl +XnQR6D5xW4pPAiAQ+CilQdZUhVK5bU6wbrwLgcwf+40ETK/KASId5970rQ== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/s2a/src/test/resources/int_key1_ec.pem b/s2a/src/test/resources/int_key1_ec.pem new file mode 100644 index 00000000000..7ff3864746b --- /dev/null +++ b/s2a/src/test/resources/int_key1_ec.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgLIQUM1HkFM/LWND8 +jCZ4wHXjFZ1ZZmQolahkZB0O1VChRANCAAQCq2DYT6FZ+vl2d7CqwIvAzQ+iGQHz +caHtXeL9M8WQ0oX9Xqkp3PlrdnyyJwb6Du4lf57I6gPjdnzCg7syAmXx +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/s2a/src/test/resources/int_key2_ec.pem b/s2a/src/test/resources/int_key2_ec.pem new file mode 100644 index 00000000000..7f529ae855f --- /dev/null +++ b/s2a/src/test/resources/int_key2_ec.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgGfm6kyaAMMrmYGhS +jxprBwtcZdP6qXlU1cVIO5bOT8qhRANCAAQVbv9N7bONLwqWkXxEwxhpm0p/oRmW +tkgijlI72PptoApdWdr0uYMKvNec4sh6o65mrQ4OLNSLgMUAsHbJ3kGR +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/s2a/src/test/resources/leaf.cnf b/s2a/src/test/resources/leaf.cnf new file mode 100644 index 00000000000..d5b373cbc71 --- /dev/null +++ b/s2a/src/test/resources/leaf.cnf @@ -0,0 +1,14 @@ +[req] +distinguished_name = req_distinguished_name +req_extensions = v3_req +prompt = no + +[req_distinguished_name] +O = o +OU = ou +CN = cn + +[v3_req] +keyUsage = critical, digitalSignature +extendedKeyUsage = critical, clientAuth, serverAuth +basicConstraints = critical, CA:false \ No newline at end of file diff --git a/s2a/src/test/resources/leaf_cert_ec.pem b/s2a/src/test/resources/leaf_cert_ec.pem new file mode 100644 index 00000000000..39692b95fda --- /dev/null +++ b/s2a/src/test/resources/leaf_cert_ec.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIB0jCCAXigAwIBAgIUBV1dftEhhEMTI83L6jpeJn2tuyQwCgYIKoZIzj0EAwIw +JjEKMAgGA1UECgwBbzELMAkGA1UECwwCb3UxCzAJBgNVBAMMAmNuMB4XDTI0MDkx +OTIzMDQwMFoXDTQ0MDkxOTIzMDQwMFowJjEKMAgGA1UECgwBbzELMAkGA1UECwwC +b3UxCzAJBgNVBAMMAmNuMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0TPKM3K/ +D0zlbGTVofq05ierfqzg7oNy8FqVEKvYewHXYxpS660za3Hjsju54fWhJOaZSobP +4ezzvPr5BBbBMaOBgzCBgDAOBgNVHQ8BAf8EBAMCB4AwIAYDVR0lAQH/BBYwFAYI +KwYBBQUHAwIGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFPvP7dnB +dg8ZoLB0w62tbvoIRRHPMB8GA1UdIwQYMBaAFHeH+MNh2fgyjNHYP9hLAv9Sl1yD +MAoGCCqGSM49BAMCA0gAMEUCIBcsImaxeFjxFXCXYNQJnde+rsEOgbeAHrAC0SZQ +NlB2AiEA4epDhw/o+6BfgDbqlZsNEHkScPrwupnBQGLQlmNJe2c= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/s2a/src/test/resources/leaf_key_ec.pem b/s2a/src/test/resources/leaf_key_ec.pem new file mode 100644 index 00000000000..d90ad8f4db8 --- /dev/null +++ b/s2a/src/test/resources/leaf_key_ec.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgR2HBqtWTWu4NLiow +ar8vh+9vAmCONE59C+jXNAb9r8ehRANCAATRM8ozcr8PTOVsZNWh+rTmJ6t+rODu +g3LwWpUQq9h7AddjGlLrrTNrceOyO7nh9aEk5plKhs/h7PO8+vkEFsEx +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/s2a/src/test/resources/root_cert_ec.pem b/s2a/src/test/resources/root_cert_ec.pem new file mode 100644 index 00000000000..0dd465e8e90 --- /dev/null +++ b/s2a/src/test/resources/root_cert_ec.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBrzCCAVWgAwIBAgIUGV+9j5V61CZaa6mbrchDag5miEQwCgYIKoZIzj0EAwIw +JjEKMAgGA1UECgwBbzELMAkGA1UECwwCb3UxCzAJBgNVBAMMAmNuMB4XDTI0MDkx +OTIyNDMwOFoXDTQ0MDkxOTIyNDMwOFowJjEKMAgGA1UECgwBbzELMAkGA1UECwwC +b3UxCzAJBgNVBAMMAmNuMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDVPIq1ds +/MX52CX9YU1RdEeM89YP4o3BN8OiP2O4qcuc11k4Qu4Mo4RWeN9OJpNElTQJ0K8n +/rIvbmw8AIMquaNhMF8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdJQQWMBQGCCsGAQUF +BwMBBggrBgEFBQcDAjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRSE2wdAVCz +7cpzfUjJIpRGq1sXCjAKBggqhkjOPQQDAgNIADBFAiEA1TEfHWArDnepmtMDQ4wd +Q3uqPrV2Ye2KMO67/BHEGIQCIFu3JutXYYVU/CinwH89AJW+FJ7zokaPjCDkbiOH ++h+H +-----END CERTIFICATE----- \ No newline at end of file diff --git a/s2a/src/test/resources/root_ec.cnf b/s2a/src/test/resources/root_ec.cnf new file mode 100644 index 00000000000..bee0b80a166 --- /dev/null +++ b/s2a/src/test/resources/root_ec.cnf @@ -0,0 +1,14 @@ +[req] +distinguished_name = req_distinguished_name +req_extensions = v3_req +prompt = no + +[req_distinguished_name] +O = o +OU = ou +CN = cn + +[v3_req] +keyUsage = critical, keyCertSign, cRLSign +extendedKeyUsage = serverAuth, clientAuth +basicConstraints = critical, CA:true \ No newline at end of file diff --git a/s2a/src/test/resources/root_key_ec.pem b/s2a/src/test/resources/root_key_ec.pem new file mode 100644 index 00000000000..ef5ee1445f9 --- /dev/null +++ b/s2a/src/test/resources/root_key_ec.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgd5oZmQBOtMF0xfc3 +uRuw5EDhA1thJKKeHfrij9FMkfahRANCAAQNU8irV2z8xfnYJf1hTVF0R4zz1g/i +jcE3w6I/Y7ipy5zXWThC7gyjhFZ4304mk0SVNAnQryf+si9ubDwAgyq5 +-----END PRIVATE KEY----- \ No newline at end of file From d169a5de6fb94e974fd77ebabde52f01bc7aaca0 Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Fri, 27 Sep 2024 17:22:38 -0700 Subject: [PATCH 087/103] interop-test: add opentelemetry tracing context propagation test (#11538) --- interop-testing/build.gradle | 1 + .../OpenTelemetryContextPropagationTest.java | 191 ++++++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 interop-testing/src/test/java/io/grpc/testing/integration/OpenTelemetryContextPropagationTest.java diff --git a/interop-testing/build.gradle b/interop-testing/build.gradle index a19efb00155..a85aec97adf 100644 --- a/interop-testing/build.gradle +++ b/interop-testing/build.gradle @@ -13,6 +13,7 @@ dependencies { implementation project(path: ':grpc-alts', configuration: 'shadow'), project(':grpc-auth'), project(':grpc-census'), + project(':grpc-opentelemetry'), project(':grpc-gcp-csm-observability'), project(':grpc-netty'), project(':grpc-okhttp'), diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/OpenTelemetryContextPropagationTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/OpenTelemetryContextPropagationTest.java new file mode 100644 index 00000000000..3884d977a6e --- /dev/null +++ b/interop-testing/src/test/java/io/grpc/testing/integration/OpenTelemetryContextPropagationTest.java @@ -0,0 +1,191 @@ +/* + * Copyright 2024 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. + */ + +package io.grpc.testing.integration; + +import static org.junit.Assert.assertEquals; + +import io.grpc.ForwardingServerCallListener; +import io.grpc.InsecureServerCredentials; +import io.grpc.ManagedChannelBuilder; +import io.grpc.Metadata; +import io.grpc.ServerBuilder; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; +import io.grpc.netty.InternalNettyChannelBuilder; +import io.grpc.netty.NettyChannelBuilder; +import io.grpc.netty.NettyServerBuilder; +import io.grpc.opentelemetry.GrpcOpenTelemetry; +import io.grpc.opentelemetry.GrpcTraceBinContextPropagator; +import io.grpc.opentelemetry.InternalGrpcOpenTelemetry; +import io.grpc.testing.integration.Messages.SimpleRequest; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.Assume; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class OpenTelemetryContextPropagationTest extends AbstractInteropTest { + private final OpenTelemetrySdk openTelemetrySdk; + private final Tracer tracer; + private final GrpcOpenTelemetry grpcOpenTelemetry; + private final AtomicReference applicationSpan = new AtomicReference<>(); + private final boolean censusClient; + + @Parameterized.Parameters(name = "ContextPropagator={0}, CensusClient={1}") + public static Iterable data() { + return Arrays.asList(new Object[][] { + {W3CTraceContextPropagator.getInstance(), false}, + {GrpcTraceBinContextPropagator.defaultInstance(), false}, + {GrpcTraceBinContextPropagator.defaultInstance(), true} + }); + } + + public OpenTelemetryContextPropagationTest(TextMapPropagator textMapPropagator, + boolean isCensusClient) { + this.openTelemetrySdk = OpenTelemetrySdk.builder() + .setTracerProvider(SdkTracerProvider.builder().build()) + .setPropagators(ContextPropagators.create(TextMapPropagator.composite( + textMapPropagator + ))) + .build(); + this.tracer = openTelemetrySdk + .getTracer("grpc-java-interop-test"); + GrpcOpenTelemetry.Builder grpcOpentelemetryBuilder = GrpcOpenTelemetry.newBuilder() + .sdk(openTelemetrySdk); + InternalGrpcOpenTelemetry.enableTracing(grpcOpentelemetryBuilder, true); + grpcOpenTelemetry = grpcOpentelemetryBuilder.build(); + this.censusClient = isCensusClient; + } + + @Override + protected ServerBuilder getServerBuilder() { + NettyServerBuilder builder = NettyServerBuilder.forPort(0, InsecureServerCredentials.create()) + .maxInboundMessageSize(AbstractInteropTest.MAX_MESSAGE_SIZE); + builder.intercept(new ServerInterceptor() { + @Override + public ServerCall.Listener interceptCall(ServerCall call, + Metadata headers, ServerCallHandler next) { + ServerCall.Listener listener = next.startCall(call, headers); + return new ForwardingServerCallListener() { + @Override + protected ServerCall.Listener delegate() { + return listener; + } + + @Override + public void onMessage(ReqT request) { + applicationSpan.set(tracer.spanBuilder("InteropTest.Application.Span").startSpan()); + delegate().onMessage(request); + } + + @Override + public void onHalfClose() { + maybeCloseSpan(applicationSpan); + delegate().onHalfClose(); + } + + @Override + public void onCancel() { + maybeCloseSpan(applicationSpan); + delegate().onCancel(); + } + + @Override + public void onComplete() { + maybeCloseSpan(applicationSpan); + delegate().onComplete(); + } + }; + } + }); + // To ensure proper propagation of remote spans from gRPC to your application, this interceptor + // must be after any application interceptors that interact with spans. This allows the tracing + // information to be correctly passed along. However, it's fine for application-level onMessage + // handlers to access the span. + grpcOpenTelemetry.configureServerBuilder(builder); + return builder; + } + + private void maybeCloseSpan(AtomicReference applicationSpan) { + Span tmp = applicationSpan.get(); + if (tmp != null) { + tmp.end(); + } + } + + @Override + protected boolean metricsExpected() { + return false; + } + + @Override + protected ManagedChannelBuilder createChannelBuilder() { + NettyChannelBuilder builder = NettyChannelBuilder.forAddress(getListenAddress()) + .maxInboundMessageSize(AbstractInteropTest.MAX_MESSAGE_SIZE) + .usePlaintext(); + if (!censusClient) { + // Disabling census-tracing is necessary to avoid trace ID mismatches. + // This is because census-tracing overrides the grpc-trace-bin header with + // OpenTelemetry's GrpcTraceBinPropagator. + InternalNettyChannelBuilder.setTracingEnabled(builder, false); + grpcOpenTelemetry.configureChannelBuilder(builder); + } + return builder; + } + + @Test + public void otelSpanContextPropagation() { + Assume.assumeFalse(censusClient); + Span parentSpan = tracer.spanBuilder("Test.interopTest").startSpan(); + try (Scope scope = Context.current().with(parentSpan).makeCurrent()) { + blockingStub.unaryCall(SimpleRequest.getDefaultInstance()); + } + assertEquals(parentSpan.getSpanContext().getTraceId(), + applicationSpan.get().getSpanContext().getTraceId()); + } + + @Test + @SuppressWarnings("deprecation") + public void censusToOtelGrpcTraceBinPropagator() { + Assume.assumeTrue(censusClient); + io.opencensus.trace.Tracer censusTracer = io.opencensus.trace.Tracing.getTracer(); + io.opencensus.trace.Span parentSpan = censusTracer.spanBuilder("Test.interopTest") + .startSpan(); + io.grpc.Context context = io.opencensus.trace.unsafe.ContextUtils.withValue( + io.grpc.Context.current(), parentSpan); + io.grpc.Context previous = context.attach(); + try { + blockingStub.unaryCall(SimpleRequest.getDefaultInstance()); + assertEquals(parentSpan.getContext().getTraceId().toLowerBase16(), + applicationSpan.get().getSpanContext().getTraceId()); + } finally { + context.detach(previous); + } + } +} From a908b5e40db86c533a9f3245d14c15fdecf30378 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Fri, 27 Sep 2024 07:17:11 -0700 Subject: [PATCH 088/103] android: For UDS, use fake IP instead of localhost This avoids a DNS lookup, which can be slow and fail. Fixes #11442 --- .../src/main/java/io/grpc/android/UdsChannelBuilder.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/io/grpc/android/UdsChannelBuilder.java b/android/src/main/java/io/grpc/android/UdsChannelBuilder.java index e2dc7232378..7d41301704c 100644 --- a/android/src/main/java/io/grpc/android/UdsChannelBuilder.java +++ b/android/src/main/java/io/grpc/android/UdsChannelBuilder.java @@ -68,12 +68,15 @@ public static ManagedChannelBuilder forPath(String path, Namespace namespace) throw new UnsupportedOperationException("OkHttpChannelBuilder not found on the classpath"); } try { - // Target 'dns:///localhost' is unused, but necessary as an argument for OkHttpChannelBuilder. + // Target 'dns:///127.0.0.1' is unused, but necessary as an argument for OkHttpChannelBuilder. + // An IP address is used instead of localhost to avoid a DNS lookup (see #11442). This should + // work even if IPv4 is unavailable, as the DNS resolver doesn't need working IPv4 to parse an + // IPv4 address. Unavailable IPv4 fails when we connect(), not at resolution time. // TLS is unsupported because Conscrypt assumes the platform Socket implementation to improve // performance by using the file descriptor directly. Object o = OKHTTP_CHANNEL_BUILDER_CLASS .getMethod("forTarget", String.class, ChannelCredentials.class) - .invoke(null, "dns:///localhost", InsecureChannelCredentials.create()); + .invoke(null, "dns:///127.0.0.1", InsecureChannelCredentials.create()); ManagedChannelBuilder builder = OKHTTP_CHANNEL_BUILDER_CLASS.cast(o); OKHTTP_CHANNEL_BUILDER_CLASS .getMethod("socketFactory", SocketFactory.class) From 8c3496943c9cd5fdb74e5b6ba964aa9a37b15acf Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Sat, 27 Jul 2024 10:53:19 -0700 Subject: [PATCH 089/103] xds: Have ClusterManagerLB use child map for preserving children Instead of doing a dance of supplementing config so the later createChildAddressesMap() won't delete children, just look at the existing children and don't delete any that shouldn't be deleted. --- .../io/grpc/util/MultiChildLoadBalancer.java | 9 ++- .../grpc/xds/ClusterManagerLoadBalancer.java | 76 +++++++------------ 2 files changed, 33 insertions(+), 52 deletions(-) diff --git a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java index 626c2e1104e..b05f4d98c85 100644 --- a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java +++ b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java @@ -79,7 +79,8 @@ protected MultiChildLoadBalancer(Helper helper) { /** * Override to utilize parsing of the policy configuration or alternative helper/lb generation. - * Override this if keys are not Endpoints or if child policies have configuration. + * Override this if keys are not Endpoints or if child policies have configuration. Null map + * values preserve the child without delivering the child an update. */ protected Map createChildAddressesMap( ResolvedAddresses resolvedAddresses) { @@ -181,8 +182,10 @@ private void updateChildrenWithResolvedAddresses( childLbState = createChildLbState(entry.getKey()); childLbStates.put(entry.getKey(), childLbState); } - childLbState.setResolvedAddresses(entry.getValue()); // update child - childLbState.lb.handleResolvedAddresses(entry.getValue()); // update child LB + if (entry.getValue() != null) { + childLbState.setResolvedAddresses(entry.getValue()); // update child + childLbState.lb.handleResolvedAddresses(entry.getValue()); // update child LB + } } } diff --git a/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java index c175b847c63..2573a7293d3 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java @@ -77,57 +77,43 @@ protected ChildLbState createChildLbState(Object key) { @Override protected Map createChildAddressesMap( ResolvedAddresses resolvedAddresses) { + lastResolvedAddresses = resolvedAddresses; + ClusterManagerConfig config = (ClusterManagerConfig) resolvedAddresses.getLoadBalancingPolicyConfig(); Map childAddresses = new HashMap<>(); - if (config != null) { - for (Map.Entry childPolicy : config.childPolicies.entrySet()) { - ResolvedAddresses addresses = resolvedAddresses.toBuilder() - .setLoadBalancingPolicyConfig(childPolicy.getValue()) - .build(); - childAddresses.put(childPolicy.getKey(), addresses); + + // Reactivate children with config; deactivate children without config + for (ChildLbState rawState : getChildLbStates()) { + ClusterManagerLbState state = (ClusterManagerLbState) rawState; + if (config.childPolicies.containsKey(state.getKey())) { + // Active child + if (state.deletionTimer != null) { + state.reactivateChild(); + } + } else { + // Inactive child + if (state.deletionTimer == null) { + state.deactivateChild(); + } + if (state.deletionTimer.isPending()) { + childAddresses.put(state.getKey(), null); // Preserve child, without config update + } } } + + for (Map.Entry childPolicy : config.childPolicies.entrySet()) { + ResolvedAddresses addresses = resolvedAddresses.toBuilder() + .setLoadBalancingPolicyConfig(childPolicy.getValue()) + .build(); + childAddresses.put(childPolicy.getKey(), addresses); + } logger.log( XdsLogLevel.INFO, - "Received cluster_manager lb config: child names={0}", childAddresses.keySet()); + "Received cluster_manager lb config: child names={0}", config.childPolicies.keySet()); return childAddresses; } - /** - * This is like the parent except that it doesn't shutdown the removed children since we want that - * to be done by the timer. - */ - @Override - public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { - if (lastResolvedAddresses != null) { - // Handle deactivated children - ClusterManagerConfig config = (ClusterManagerConfig) - resolvedAddresses.getLoadBalancingPolicyConfig(); - ClusterManagerConfig lastConfig = (ClusterManagerConfig) - lastResolvedAddresses.getLoadBalancingPolicyConfig(); - Map adjChildPolicies = new HashMap<>(config.childPolicies); - for (Entry entry : lastConfig.childPolicies.entrySet()) { - ClusterManagerLbState state = (ClusterManagerLbState) getChildLbState(entry.getKey()); - if (adjChildPolicies.containsKey(entry.getKey())) { - if (state.deletionTimer != null) { - state.reactivateChild(); - } - } else if (state != null) { - adjChildPolicies.put(entry.getKey(), entry.getValue()); - if (state.deletionTimer == null) { - state.deactivateChild(); - } - } - } - config = new ClusterManagerConfig(adjChildPolicies); - resolvedAddresses = - resolvedAddresses.toBuilder().setLoadBalancingPolicyConfig(config).build(); - } - lastResolvedAddresses = resolvedAddresses; - return super.acceptResolvedAddresses(resolvedAddresses); - } - /** * Using the state of all children will calculate the current connectivity state, * update currentConnectivityState, generate a picker and then call @@ -232,14 +218,6 @@ class DeletionTask implements Runnable { @Override public void run() { - ClusterManagerConfig config = (ClusterManagerConfig) - lastResolvedAddresses.getLoadBalancingPolicyConfig(); - Map childPolicies = new HashMap<>(config.childPolicies); - Object removed = childPolicies.remove(getKey()); - assert removed != null; - config = new ClusterManagerConfig(childPolicies); - lastResolvedAddresses = - lastResolvedAddresses.toBuilder().setLoadBalancingPolicyConfig(config).build(); acceptResolvedAddresses(lastResolvedAddresses); } } From 795e2cc3ff25666b5c7d8beed985dbd55fc40804 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Sat, 31 Aug 2024 17:53:01 -0700 Subject: [PATCH 090/103] util: Simplify MultiChildLB.getChildLbState() Tests were converted to use getChildLbStateEag() if the argument was an EAG, so the instanceof was no longer necessary. --- util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java | 6 ------ .../test/java/io/grpc/xds/LeastRequestLoadBalancerTest.java | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java index b05f4d98c85..330ec9d5357 100644 --- a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java +++ b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java @@ -241,12 +241,6 @@ public final Collection getChildLbStates() { @VisibleForTesting public final ChildLbState getChildLbState(Object key) { - if (key == null) { - return null; - } - if (key instanceof EquivalentAddressGroup) { - key = new Endpoint((EquivalentAddressGroup) key); - } return childLbStates.get(key); } diff --git a/xds/src/test/java/io/grpc/xds/LeastRequestLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/LeastRequestLoadBalancerTest.java index 659bacd3626..64e18465597 100644 --- a/xds/src/test/java/io/grpc/xds/LeastRequestLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/LeastRequestLoadBalancerTest.java @@ -252,7 +252,7 @@ private List getSubchannels(LeastRequestLoadBalancer lb) { private LeastRequestLbState getChildLbState(PickResult pickResult) { EquivalentAddressGroup eag = pickResult.getSubchannel().getAddresses(); - return (LeastRequestLbState) loadBalancer.getChildLbState(eag); + return (LeastRequestLbState) loadBalancer.getChildLbStateEag(eag); } @Test From a140e1bb0cfa662bcdb7823d73320eb8d49046f1 Mon Sep 17 00:00:00 2001 From: Riya Mehta <55350838+rmehta19@users.noreply.github.com> Date: Mon, 30 Sep 2024 09:49:09 -0700 Subject: [PATCH 091/103] s2a: Combine MtlsToS2ChannelCredentials and S2AChannelCredentials. (#11544) * Combine MtlsToS2ChannelCredentials and S2AChannelCredentials. * Check if file exists. * S2AChannelCredentials API requires credentials used for client-s2a channel. * remove MtlsToS2A library in BUILD. * Don't check state twice. * Don't check for file existence in tests. --- s2a/BUILD.bazel | 13 -- .../grpc/s2a/MtlsToS2AChannelCredentials.java | 89 ------------ .../io/grpc/s2a/S2AChannelCredentials.java | 25 ++-- .../s2a/MtlsToS2AChannelCredentialsTest.java | 135 ------------------ .../grpc/s2a/S2AChannelCredentialsTest.java | 68 ++++++--- .../grpc/s2a/handshaker/IntegrationTest.java | 35 +++-- 6 files changed, 79 insertions(+), 286 deletions(-) delete mode 100644 s2a/src/main/java/io/grpc/s2a/MtlsToS2AChannelCredentials.java delete mode 100644 s2a/src/test/java/io/grpc/s2a/MtlsToS2AChannelCredentialsTest.java diff --git a/s2a/BUILD.bazel b/s2a/BUILD.bazel index 5aeaedbe358..25ce2624f57 100644 --- a/s2a/BUILD.bazel +++ b/s2a/BUILD.bazel @@ -117,19 +117,6 @@ java_library( ], ) -java_library( - name = "mtls_to_s2av2_credentials", - srcs = ["src/main/java/io/grpc/s2a/MtlsToS2AChannelCredentials.java"], - visibility = ["//visibility:public"], - deps = [ - ":s2a_channel_pool", - ":s2av2_credentials", - "//api", - "//util", - artifact("com.google.guava:guava"), - ], -) - # bazel only accepts proto import with absolute path. genrule( name = "protobuf_imports", diff --git a/s2a/src/main/java/io/grpc/s2a/MtlsToS2AChannelCredentials.java b/s2a/src/main/java/io/grpc/s2a/MtlsToS2AChannelCredentials.java deleted file mode 100644 index e8eb01628ed..00000000000 --- a/s2a/src/main/java/io/grpc/s2a/MtlsToS2AChannelCredentials.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2024 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. - */ - -package io.grpc.s2a; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.base.Strings.isNullOrEmpty; - -import io.grpc.ChannelCredentials; -import io.grpc.ExperimentalApi; -import io.grpc.TlsChannelCredentials; -import java.io.File; -import java.io.IOException; - -/** - * Configures an {@code S2AChannelCredentials.Builder} instance with credentials used to establish a - * connection with the S2A to support talking to the S2A over mTLS. - */ -@ExperimentalApi("https://github.com/grpc/grpc-java/issues/11533") -public final class MtlsToS2AChannelCredentials { - /** - * Creates a {@code S2AChannelCredentials.Builder} builder, that talks to the S2A over mTLS. - * - * @param s2aAddress the address of the S2A server used to secure the connection. - * @param privateKeyPath the path to the private key PEM to use for authenticating to the S2A. - * @param certChainPath the path to the cert chain PEM to use for authenticating to the S2A. - * @param trustBundlePath the path to the trust bundle PEM. - * @return a {@code MtlsToS2AChannelCredentials.Builder} instance. - */ - public static Builder newBuilder( - String s2aAddress, String privateKeyPath, String certChainPath, String trustBundlePath) { - checkArgument(!isNullOrEmpty(s2aAddress), "S2A address must not be null or empty."); - checkArgument(!isNullOrEmpty(privateKeyPath), "privateKeyPath must not be null or empty."); - checkArgument(!isNullOrEmpty(certChainPath), "certChainPath must not be null or empty."); - checkArgument(!isNullOrEmpty(trustBundlePath), "trustBundlePath must not be null or empty."); - return new Builder(s2aAddress, privateKeyPath, certChainPath, trustBundlePath); - } - - /** Builds an {@code MtlsToS2AChannelCredentials} instance. */ - public static final class Builder { - private final String s2aAddress; - private final String privateKeyPath; - private final String certChainPath; - private final String trustBundlePath; - - Builder( - String s2aAddress, String privateKeyPath, String certChainPath, String trustBundlePath) { - this.s2aAddress = s2aAddress; - this.privateKeyPath = privateKeyPath; - this.certChainPath = certChainPath; - this.trustBundlePath = trustBundlePath; - } - - public S2AChannelCredentials.Builder build() throws IOException { - checkState(!isNullOrEmpty(s2aAddress), "S2A address must not be null or empty."); - checkState(!isNullOrEmpty(privateKeyPath), "privateKeyPath must not be null or empty."); - checkState(!isNullOrEmpty(certChainPath), "certChainPath must not be null or empty."); - checkState(!isNullOrEmpty(trustBundlePath), "trustBundlePath must not be null or empty."); - File privateKeyFile = new File(privateKeyPath); - File certChainFile = new File(certChainPath); - File trustBundleFile = new File(trustBundlePath); - - ChannelCredentials channelToS2ACredentials = - TlsChannelCredentials.newBuilder() - .keyManager(certChainFile, privateKeyFile) - .trustManager(trustBundleFile) - .build(); - - return S2AChannelCredentials.newBuilder(s2aAddress) - .setS2AChannelCredentials(channelToS2ACredentials); - } - } - - private MtlsToS2AChannelCredentials() {} -} \ No newline at end of file diff --git a/s2a/src/main/java/io/grpc/s2a/S2AChannelCredentials.java b/s2a/src/main/java/io/grpc/s2a/S2AChannelCredentials.java index ba0f6d72de1..12963a1395c 100644 --- a/s2a/src/main/java/io/grpc/s2a/S2AChannelCredentials.java +++ b/s2a/src/main/java/io/grpc/s2a/S2AChannelCredentials.java @@ -18,14 +18,12 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Strings.isNullOrEmpty; import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.grpc.Channel; import io.grpc.ChannelCredentials; import io.grpc.ExperimentalApi; -import io.grpc.InsecureChannelCredentials; import io.grpc.internal.ObjectPool; import io.grpc.internal.SharedResourcePool; import io.grpc.netty.InternalNettyChannelCredentials; @@ -33,6 +31,7 @@ import io.grpc.s2a.channel.S2AHandshakerServiceChannel; import io.grpc.s2a.handshaker.S2AIdentity; import io.grpc.s2a.handshaker.S2AProtocolNegotiatorFactory; +import java.io.IOException; import javax.annotation.concurrent.NotThreadSafe; import org.checkerframework.checker.nullness.qual.Nullable; @@ -46,25 +45,27 @@ public final class S2AChannelCredentials { * Creates a channel credentials builder for establishing an S2A-secured connection. * * @param s2aAddress the address of the S2A server used to secure the connection. + * @param s2aChannelCredentials the credentials to be used when connecting to the S2A. * @return a {@code S2AChannelCredentials.Builder} instance. */ - public static Builder newBuilder(String s2aAddress) { + public static Builder newBuilder(String s2aAddress, ChannelCredentials s2aChannelCredentials) { checkArgument(!isNullOrEmpty(s2aAddress), "S2A address must not be null or empty."); - return new Builder(s2aAddress); + checkNotNull(s2aChannelCredentials, "S2A channel credentials must not be null"); + return new Builder(s2aAddress, s2aChannelCredentials); } /** Builds an {@code S2AChannelCredentials} instance. */ @NotThreadSafe public static final class Builder { private final String s2aAddress; + private final ChannelCredentials s2aChannelCredentials; private ObjectPool s2aChannelPool; - private ChannelCredentials s2aChannelCredentials; private @Nullable S2AIdentity localIdentity = null; - Builder(String s2aAddress) { + Builder(String s2aAddress, ChannelCredentials s2aChannelCredentials) { this.s2aAddress = s2aAddress; + this.s2aChannelCredentials = s2aChannelCredentials; this.s2aChannelPool = null; - this.s2aChannelCredentials = InsecureChannelCredentials.create(); } /** @@ -106,15 +107,7 @@ public Builder setLocalUid(String localUid) { return this; } - /** Sets the credentials to be used when connecting to the S2A. */ - @CanIgnoreReturnValue - public Builder setS2AChannelCredentials(ChannelCredentials s2aChannelCredentials) { - this.s2aChannelCredentials = s2aChannelCredentials; - return this; - } - - public ChannelCredentials build() { - checkState(!isNullOrEmpty(s2aAddress), "S2A address must not be null or empty."); + public ChannelCredentials build() throws IOException { ObjectPool s2aChannelPool = SharedResourcePool.forResource( S2AHandshakerServiceChannel.getChannelResource(s2aAddress, s2aChannelCredentials)); diff --git a/s2a/src/test/java/io/grpc/s2a/MtlsToS2AChannelCredentialsTest.java b/s2a/src/test/java/io/grpc/s2a/MtlsToS2AChannelCredentialsTest.java deleted file mode 100644 index 0fc4ecb3268..00000000000 --- a/s2a/src/test/java/io/grpc/s2a/MtlsToS2AChannelCredentialsTest.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2024 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. - */ - -package io.grpc.s2a; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertThrows; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public final class MtlsToS2AChannelCredentialsTest { - @Test - public void newBuilder_nullAddress_throwsException() throws Exception { - assertThrows( - IllegalArgumentException.class, - () -> - MtlsToS2AChannelCredentials.newBuilder( - /* s2aAddress= */ null, - /* privateKeyPath= */ "src/test/resources/client_key.pem", - /* certChainPath= */ "src/test/resources/client_cert.pem", - /* trustBundlePath= */ "src/test/resources/root_cert.pem")); - } - - @Test - public void newBuilder_nullPrivateKeyPath_throwsException() throws Exception { - assertThrows( - IllegalArgumentException.class, - () -> - MtlsToS2AChannelCredentials.newBuilder( - /* s2aAddress= */ "s2a_address", - /* privateKeyPath= */ null, - /* certChainPath= */ "src/test/resources/client_cert.pem", - /* trustBundlePath= */ "src/test/resources/root_cert.pem")); - } - - @Test - public void newBuilder_nullCertChainPath_throwsException() throws Exception { - assertThrows( - IllegalArgumentException.class, - () -> - MtlsToS2AChannelCredentials.newBuilder( - /* s2aAddress= */ "s2a_address", - /* privateKeyPath= */ "src/test/resources/client_key.pem", - /* certChainPath= */ null, - /* trustBundlePath= */ "src/test/resources/root_cert.pem")); - } - - @Test - public void newBuilder_nullTrustBundlePath_throwsException() throws Exception { - assertThrows( - IllegalArgumentException.class, - () -> - MtlsToS2AChannelCredentials.newBuilder( - /* s2aAddress= */ "s2a_address", - /* privateKeyPath= */ "src/test/resources/client_key.pem", - /* certChainPath= */ "src/test/resources/client_cert.pem", - /* trustBundlePath= */ null)); - } - - @Test - public void newBuilder_emptyAddress_throwsException() throws Exception { - assertThrows( - IllegalArgumentException.class, - () -> - MtlsToS2AChannelCredentials.newBuilder( - /* s2aAddress= */ "", - /* privateKeyPath= */ "src/test/resources/client_key.pem", - /* certChainPath= */ "src/test/resources/client_cert.pem", - /* trustBundlePath= */ "src/test/resources/root_cert.pem")); - } - - @Test - public void newBuilder_emptyPrivateKeyPath_throwsException() throws Exception { - assertThrows( - IllegalArgumentException.class, - () -> - MtlsToS2AChannelCredentials.newBuilder( - /* s2aAddress= */ "s2a_address", - /* privateKeyPath= */ "", - /* certChainPath= */ "src/test/resources/client_cert.pem", - /* trustBundlePath= */ "src/test/resources/root_cert.pem")); - } - - @Test - public void newBuilder_emptyCertChainPath_throwsException() throws Exception { - assertThrows( - IllegalArgumentException.class, - () -> - MtlsToS2AChannelCredentials.newBuilder( - /* s2aAddress= */ "s2a_address", - /* privateKeyPath= */ "src/test/resources/client_key.pem", - /* certChainPath= */ "", - /* trustBundlePath= */ "src/test/resources/root_cert.pem")); - } - - @Test - public void newBuilder_emptyTrustBundlePath_throwsException() throws Exception { - assertThrows( - IllegalArgumentException.class, - () -> - MtlsToS2AChannelCredentials.newBuilder( - /* s2aAddress= */ "s2a_address", - /* privateKeyPath= */ "src/test/resources/client_key.pem", - /* certChainPath= */ "src/test/resources/client_cert.pem", - /* trustBundlePath= */ "")); - } - - @Test - public void build_s2AChannelCredentials_success() throws Exception { - assertThat( - MtlsToS2AChannelCredentials.newBuilder( - /* s2aAddress= */ "s2a_address", - /* privateKeyPath= */ "src/test/resources/client_key.pem", - /* certChainPath= */ "src/test/resources/client_cert.pem", - /* trustBundlePath= */ "src/test/resources/root_cert.pem") - .build()) - .isInstanceOf(S2AChannelCredentials.Builder.class); - } -} \ No newline at end of file diff --git a/s2a/src/test/java/io/grpc/s2a/S2AChannelCredentialsTest.java b/s2a/src/test/java/io/grpc/s2a/S2AChannelCredentialsTest.java index e766aa3f145..fd5bfd654f3 100644 --- a/s2a/src/test/java/io/grpc/s2a/S2AChannelCredentialsTest.java +++ b/s2a/src/test/java/io/grpc/s2a/S2AChannelCredentialsTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertThrows; import io.grpc.ChannelCredentials; +import io.grpc.InsecureChannelCredentials; import io.grpc.TlsChannelCredentials; import java.io.File; import org.junit.Test; @@ -30,40 +31,51 @@ @RunWith(JUnit4.class) public final class S2AChannelCredentialsTest { @Test - public void newBuilder_nullArgument_throwsException() throws Exception { - assertThrows(IllegalArgumentException.class, () -> S2AChannelCredentials.newBuilder(null)); + public void newBuilder_nullAddress_throwsException() throws Exception { + assertThrows(IllegalArgumentException.class, () -> S2AChannelCredentials.newBuilder(null, + InsecureChannelCredentials.create())); } @Test public void newBuilder_emptyAddress_throwsException() throws Exception { - assertThrows(IllegalArgumentException.class, () -> S2AChannelCredentials.newBuilder("")); + assertThrows(IllegalArgumentException.class, () -> S2AChannelCredentials.newBuilder("", + InsecureChannelCredentials.create())); + } + + @Test + public void newBuilder_nullChannelCreds_throwsException() throws Exception { + assertThrows(NullPointerException.class, () -> S2AChannelCredentials + .newBuilder("s2a_address", null)); } @Test public void setLocalSpiffeId_nullArgument_throwsException() throws Exception { assertThrows( NullPointerException.class, - () -> S2AChannelCredentials.newBuilder("s2a_address").setLocalSpiffeId(null)); + () -> S2AChannelCredentials.newBuilder("s2a_address", + InsecureChannelCredentials.create()).setLocalSpiffeId(null)); } @Test public void setLocalHostname_nullArgument_throwsException() throws Exception { assertThrows( NullPointerException.class, - () -> S2AChannelCredentials.newBuilder("s2a_address").setLocalHostname(null)); + () -> S2AChannelCredentials.newBuilder("s2a_address", + InsecureChannelCredentials.create()).setLocalHostname(null)); } @Test public void setLocalUid_nullArgument_throwsException() throws Exception { assertThrows( NullPointerException.class, - () -> S2AChannelCredentials.newBuilder("s2a_address").setLocalUid(null)); + () -> S2AChannelCredentials.newBuilder("s2a_address", + InsecureChannelCredentials.create()).setLocalUid(null)); } @Test public void build_withLocalSpiffeId_succeeds() throws Exception { assertThat( - S2AChannelCredentials.newBuilder("s2a_address") + S2AChannelCredentials.newBuilder("s2a_address", InsecureChannelCredentials.create()) .setLocalSpiffeId("spiffe://test") .build()) .isNotNull(); @@ -72,7 +84,7 @@ public void build_withLocalSpiffeId_succeeds() throws Exception { @Test public void build_withLocalHostname_succeeds() throws Exception { assertThat( - S2AChannelCredentials.newBuilder("s2a_address") + S2AChannelCredentials.newBuilder("s2a_address", InsecureChannelCredentials.create()) .setLocalHostname("local_hostname") .build()) .isNotNull(); @@ -80,33 +92,47 @@ public void build_withLocalHostname_succeeds() throws Exception { @Test public void build_withLocalUid_succeeds() throws Exception { - assertThat(S2AChannelCredentials.newBuilder("s2a_address").setLocalUid("local_uid").build()) + assertThat(S2AChannelCredentials.newBuilder("s2a_address", + InsecureChannelCredentials.create()).setLocalUid("local_uid").build()) .isNotNull(); } @Test public void build_withNoLocalIdentity_succeeds() throws Exception { - assertThat(S2AChannelCredentials.newBuilder("s2a_address").build()) + assertThat(S2AChannelCredentials.newBuilder("s2a_address", + InsecureChannelCredentials.create()).build()) .isNotNull(); } - + @Test - public void build_withTlsChannelCredentials_succeeds() throws Exception { + public void build_withUseMtlsToS2ANoLocalIdentity_success() throws Exception { + ChannelCredentials s2aChannelCredentials = getTlsChannelCredentials(); assertThat( - S2AChannelCredentials.newBuilder("s2a_address") - .setLocalSpiffeId("spiffe://test") - .setS2AChannelCredentials(getTlsChannelCredentials()) + S2AChannelCredentials.newBuilder("s2a_address", s2aChannelCredentials) + .build()) + .isNotNull(); + } + + @Test + public void build_withUseMtlsToS2AWithLocalUid_success() throws Exception { + ChannelCredentials s2aChannelCredentials = getTlsChannelCredentials(); + assertThat( + S2AChannelCredentials.newBuilder("s2a_address", s2aChannelCredentials) + .setLocalUid("local_uid") .build()) .isNotNull(); } private static ChannelCredentials getTlsChannelCredentials() throws Exception { - File clientCert = new File("src/test/resources/client_cert.pem"); - File clientKey = new File("src/test/resources/client_key.pem"); - File rootCert = new File("src/test/resources/root_cert.pem"); + String privateKeyPath = "src/test/resources/client_key.pem"; + String certChainPath = "src/test/resources/client_cert.pem"; + String trustBundlePath = "src/test/resources/root_cert.pem"; + File privateKeyFile = new File(privateKeyPath); + File certChainFile = new File(certChainPath); + File trustBundleFile = new File(trustBundlePath); return TlsChannelCredentials.newBuilder() - .keyManager(clientCert, clientKey) - .trustManager(rootCert) - .build(); + .keyManager(certChainFile, privateKeyFile) + .trustManager(trustBundleFile) + .build(); } } \ No newline at end of file diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java b/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java index 2a2b1f246ec..0fb9b12cf95 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java +++ b/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java @@ -21,15 +21,16 @@ import io.grpc.ChannelCredentials; import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; import io.grpc.ManagedChannel; import io.grpc.Server; import io.grpc.ServerBuilder; import io.grpc.ServerCredentials; +import io.grpc.TlsChannelCredentials; import io.grpc.TlsServerCredentials; import io.grpc.benchmarks.Utils; import io.grpc.netty.GrpcSslContexts; import io.grpc.netty.NettyServerBuilder; -import io.grpc.s2a.MtlsToS2AChannelCredentials; import io.grpc.s2a.S2AChannelCredentials; import io.grpc.s2a.handshaker.FakeS2AServer; import io.grpc.stub.StreamObserver; @@ -124,7 +125,8 @@ public void tearDown() throws Exception { @Test public void clientCommunicateUsingS2ACredentials_succeeds() throws Exception { ChannelCredentials credentials = - S2AChannelCredentials.newBuilder(s2aAddress).setLocalSpiffeId("test-spiffe-id").build(); + S2AChannelCredentials.newBuilder(s2aAddress, InsecureChannelCredentials.create()) + .setLocalSpiffeId("test-spiffe-id").build(); ManagedChannel channel = Grpc.newChannelBuilder(serverAddress, credentials).build(); assertThat(doUnaryRpc(channel)).isTrue(); @@ -132,7 +134,8 @@ public void clientCommunicateUsingS2ACredentials_succeeds() throws Exception { @Test public void clientCommunicateUsingS2ACredentialsNoLocalIdentity_succeeds() throws Exception { - ChannelCredentials credentials = S2AChannelCredentials.newBuilder(s2aAddress).build(); + ChannelCredentials credentials = S2AChannelCredentials.newBuilder(s2aAddress, + InsecureChannelCredentials.create()).build(); ManagedChannel channel = Grpc.newChannelBuilder(serverAddress, credentials).build(); assertThat(doUnaryRpc(channel)).isTrue(); @@ -140,15 +143,22 @@ public void clientCommunicateUsingS2ACredentialsNoLocalIdentity_succeeds() throw @Test public void clientCommunicateUsingMtlsToS2ACredentials_succeeds() throws Exception { + String privateKeyPath = "src/test/resources/client_key.pem"; + String certChainPath = "src/test/resources/client_cert.pem"; + String trustBundlePath = "src/test/resources/root_cert.pem"; + File privateKeyFile = new File(privateKeyPath); + File certChainFile = new File(certChainPath); + File trustBundleFile = new File(trustBundlePath); + ChannelCredentials s2aChannelCredentials = + TlsChannelCredentials.newBuilder() + .keyManager(certChainFile, privateKeyFile) + .trustManager(trustBundleFile) + .build(); + ChannelCredentials credentials = - MtlsToS2AChannelCredentials.newBuilder( - /* s2aAddress= */ mtlsS2AAddress, - /* privateKeyPath= */ "src/test/resources/client_key.pem", - /* certChainPath= */ "src/test/resources/client_cert.pem", - /* trustBundlePath= */ "src/test/resources/root_cert.pem") - .build() - .setLocalSpiffeId("test-spiffe-id") - .build(); + S2AChannelCredentials.newBuilder(mtlsS2AAddress, s2aChannelCredentials) + .setLocalSpiffeId("test-spiffe-id") + .build(); ManagedChannel channel = Grpc.newChannelBuilder(serverAddress, credentials).build(); assertThat(doUnaryRpc(channel)).isTrue(); @@ -156,7 +166,8 @@ public void clientCommunicateUsingMtlsToS2ACredentials_succeeds() throws Excepti @Test public void clientCommunicateUsingS2ACredentials_s2AdelayStart_succeeds() throws Exception { - ChannelCredentials credentials = S2AChannelCredentials.newBuilder(s2aDelayAddress).build(); + ChannelCredentials credentials = S2AChannelCredentials.newBuilder(s2aDelayAddress, + InsecureChannelCredentials.create()).build(); ManagedChannel channel = Grpc.newChannelBuilder(serverAddress, credentials).build(); FutureTask rpc = new FutureTask<>(() -> doUnaryRpc(channel)); From 7b4b10930993df70916c6983c56c70a0e71a266f Mon Sep 17 00:00:00 2001 From: Riya Mehta <55350838+rmehta19@users.noreply.github.com> Date: Mon, 30 Sep 2024 12:55:42 -0700 Subject: [PATCH 092/103] s2a: remove channelPool from S2AChannelCredentials builder. (#11573) --- .../java/io/grpc/s2a/S2AChannelCredentials.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/s2a/src/main/java/io/grpc/s2a/S2AChannelCredentials.java b/s2a/src/main/java/io/grpc/s2a/S2AChannelCredentials.java index 12963a1395c..7beecdb3621 100644 --- a/s2a/src/main/java/io/grpc/s2a/S2AChannelCredentials.java +++ b/s2a/src/main/java/io/grpc/s2a/S2AChannelCredentials.java @@ -31,7 +31,6 @@ import io.grpc.s2a.channel.S2AHandshakerServiceChannel; import io.grpc.s2a.handshaker.S2AIdentity; import io.grpc.s2a.handshaker.S2AProtocolNegotiatorFactory; -import java.io.IOException; import javax.annotation.concurrent.NotThreadSafe; import org.checkerframework.checker.nullness.qual.Nullable; @@ -59,13 +58,11 @@ public static Builder newBuilder(String s2aAddress, ChannelCredentials s2aChanne public static final class Builder { private final String s2aAddress; private final ChannelCredentials s2aChannelCredentials; - private ObjectPool s2aChannelPool; private @Nullable S2AIdentity localIdentity = null; Builder(String s2aAddress, ChannelCredentials s2aChannelCredentials) { this.s2aAddress = s2aAddress; this.s2aChannelCredentials = s2aChannelCredentials; - this.s2aChannelPool = null; } /** @@ -107,16 +104,15 @@ public Builder setLocalUid(String localUid) { return this; } - public ChannelCredentials build() throws IOException { - ObjectPool s2aChannelPool = - SharedResourcePool.forResource( - S2AHandshakerServiceChannel.getChannelResource(s2aAddress, s2aChannelCredentials)); - checkNotNull(s2aChannelPool, "s2aChannelPool"); - this.s2aChannelPool = s2aChannelPool; + public ChannelCredentials build() { return InternalNettyChannelCredentials.create(buildProtocolNegotiatorFactory()); } InternalProtocolNegotiator.ClientFactory buildProtocolNegotiatorFactory() { + ObjectPool s2aChannelPool = + SharedResourcePool.forResource( + S2AHandshakerServiceChannel.getChannelResource(s2aAddress, s2aChannelCredentials)); + checkNotNull(s2aChannelPool, "s2aChannelPool"); return S2AProtocolNegotiatorFactory.createClientFactory(localIdentity, s2aChannelPool); } } From 50e442fea6bbb84dfb61c8606d05734c4b0866a7 Mon Sep 17 00:00:00 2001 From: Riya Mehta <55350838+rmehta19@users.noreply.github.com> Date: Mon, 30 Sep 2024 15:05:14 -0700 Subject: [PATCH 093/103] s2a: Include full exception in IOException --- s2a/src/main/java/io/grpc/s2a/handshaker/S2AStub.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/S2AStub.java b/s2a/src/main/java/io/grpc/s2a/handshaker/S2AStub.java index bf9b866ef93..ea4fb033a13 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/S2AStub.java +++ b/s2a/src/main/java/io/grpc/s2a/handshaker/S2AStub.java @@ -102,8 +102,7 @@ public SessionResp send(SessionReq req) throws IOException, InterruptedException if (exception != null) { throw new IOException( "Received an unexpected response from a host at the S2A's address. The S2A might be" - + " unavailable." - + exception.getMessage()); + + " unavailable.", exception); } else { throw new IOException("Received an unexpected response from a host at the S2A's address."); } From fa26a8bc5e8f5f465e1d55de922be1b6b936f47f Mon Sep 17 00:00:00 2001 From: Kannan J Date: Tue, 1 Oct 2024 11:21:08 +0530 Subject: [PATCH 094/103] Make address resolution error use the default service config (#11577) Fixes #11040. --- .../io/grpc/internal/ManagedChannelImpl.java | 10 +++- .../grpc/internal/ManagedChannelImplTest.java | 53 ++++++++++++++++++- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java index 07dcf9ee7bb..7e36086ac94 100644 --- a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java +++ b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java @@ -956,7 +956,15 @@ void updateConfigSelector(@Nullable InternalConfigSelector config) { // Must run in SynchronizationContext. void onConfigError() { if (configSelector.get() == INITIAL_PENDING_SELECTOR) { - updateConfigSelector(null); + // Apply Default Service Config if initial name resolution fails. + if (defaultServiceConfig != null) { + updateConfigSelector(defaultServiceConfig.getDefaultConfigSelector()); + lastServiceConfig = defaultServiceConfig; + channelLogger.log(ChannelLogLevel.ERROR, + "Initial Name Resolution error, using default service config"); + } else { + updateConfigSelector(null); + } } } diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java index 4d42056b689..bea14bcef47 100644 --- a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java +++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java @@ -84,6 +84,7 @@ import io.grpc.InternalChannelz; import io.grpc.InternalChannelz.ChannelStats; import io.grpc.InternalChannelz.ChannelTrace; +import io.grpc.InternalChannelz.ChannelTrace.Event.Severity; import io.grpc.InternalConfigSelector; import io.grpc.InternalInstrumented; import io.grpc.LoadBalancer; @@ -123,6 +124,7 @@ import io.grpc.internal.ManagedChannelImplBuilder.ClientTransportFactoryBuilder; import io.grpc.internal.ManagedChannelImplBuilder.FixedPortProvider; import io.grpc.internal.ManagedChannelImplBuilder.UnsupportedClientTransportFactoryBuilder; +import io.grpc.internal.ManagedChannelServiceConfig.MethodInfo; import io.grpc.internal.ServiceConfigUtil.PolicySelection; import io.grpc.internal.TestUtils.MockClientTransportInfo; import io.grpc.stub.ClientCalls; @@ -1127,6 +1129,55 @@ public void noMoreCallbackAfterLoadBalancerShutdown_configError() throws Interru verifyNoMoreInteractions(mockLoadBalancer); } + @Test + public void addressResolutionError_noPriorNameResolution_usesDefaultServiceConfig() + throws Exception { + Map rawServiceConfig = + parseConfig("{\"methodConfig\":[{" + + "\"name\":[{\"service\":\"service\"}]," + + "\"waitForReady\":true}]}"); + ManagedChannelServiceConfig managedChannelServiceConfig = + createManagedChannelServiceConfig(rawServiceConfig, null); + FakeNameResolverFactory nameResolverFactory = + new FakeNameResolverFactory.Builder(expectedUri) + .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress))) + .setResolvedAtStart(false) + .build(); + nameResolverFactory.nextConfigOrError.set( + ConfigOrError.fromConfig(managedChannelServiceConfig)); + channelBuilder.nameResolverFactory(nameResolverFactory); + Map defaultServiceConfig = + parseConfig("{\"methodConfig\":[{" + + "\"name\":[{\"service\":\"service\"}]," + + "\"waitForReady\":true}]}"); + channelBuilder.defaultServiceConfig(defaultServiceConfig); + Status resolutionError = Status.UNAVAILABLE.withDescription("Resolution failed"); + channelBuilder.maxTraceEvents(10); + createChannel(); + FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.get(0); + + resolver.listener.onError(resolutionError); + + InternalConfigSelector configSelector = channel.getConfigSelector(); + ManagedChannelServiceConfig config = + (ManagedChannelServiceConfig) configSelector.selectConfig(null).getConfig(); + MethodInfo methodConfig = config.getMethodConfig(method); + assertThat(methodConfig.waitForReady).isTrue(); + timer.forwardNanos(1234); + assertThat(getStats(channel).channelTrace.events).contains(new ChannelTrace.Event.Builder() + .setDescription("Initial Name Resolution error, using default service config") + .setSeverity(Severity.CT_ERROR) + .setTimestampNanos(0) + .build()); + + // Check that "lastServiceConfig" variable has been set above: a config resolution with the same + // config simply gets ignored and not gets reassigned. + resolver.resolved(); + timer.forwardNanos(1234); + assertThat(getStats(channel).channelTrace.events.stream().filter( + event -> event.description.equals("Service config changed")).count()).isEqualTo(0); + } + @Test public void interceptor() throws Exception { final AtomicLong atomic = new AtomicLong(); @@ -4595,7 +4646,7 @@ public void notUseDefaultImmediatelyIfEnableLookUp() throws Exception { int size = getStats(channel).channelTrace.events.size(); assertThat(getStats(channel).channelTrace.events.get(size - 1)) .isNotEqualTo(new ChannelTrace.Event.Builder() - .setDescription("Using default service config") + .setDescription("timer.forwardNanos(1234);") .setSeverity(ChannelTrace.Event.Severity.CT_INFO) .setTimestampNanos(timer.getTicker().read()) .build()); From f9ff5268857410a1e49ec4e603fcd529caf4e628 Mon Sep 17 00:00:00 2001 From: MV Shiva Date: Tue, 1 Oct 2024 20:39:24 +0530 Subject: [PATCH 095/103] Update README etc to reference 1.67.1 --- README.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index cb38ad66394..f56aebfb3ac 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,8 @@ For a guided tour, take a look at the [quick start guide](https://grpc.io/docs/languages/java/quickstart) or the more explanatory [gRPC basics](https://grpc.io/docs/languages/java/basics). -The [examples](https://github.com/grpc/grpc-java/tree/v1.66.0/examples) and the -[Android example](https://github.com/grpc/grpc-java/tree/v1.66.0/examples/android) +The [examples](https://github.com/grpc/grpc-java/tree/v1.67.1/examples) and the +[Android example](https://github.com/grpc/grpc-java/tree/v1.67.1/examples/android) are standalone projects that showcase the usage of gRPC. Download @@ -56,18 +56,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`: io.grpc grpc-netty-shaded - 1.66.0 + 1.67.1 runtime io.grpc grpc-protobuf - 1.66.0 + 1.67.1 io.grpc grpc-stub - 1.66.0 + 1.67.1 org.apache.tomcat @@ -79,18 +79,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`: Or for Gradle with non-Android, add to your dependencies: ```gradle -runtimeOnly 'io.grpc:grpc-netty-shaded:1.66.0' -implementation 'io.grpc:grpc-protobuf:1.66.0' -implementation 'io.grpc:grpc-stub:1.66.0' +runtimeOnly 'io.grpc:grpc-netty-shaded:1.67.1' +implementation 'io.grpc:grpc-protobuf:1.67.1' +implementation 'io.grpc:grpc-stub:1.67.1' compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+ ``` For Android client, use `grpc-okhttp` instead of `grpc-netty-shaded` and `grpc-protobuf-lite` instead of `grpc-protobuf`: ```gradle -implementation 'io.grpc:grpc-okhttp:1.66.0' -implementation 'io.grpc:grpc-protobuf-lite:1.66.0' -implementation 'io.grpc:grpc-stub:1.66.0' +implementation 'io.grpc:grpc-okhttp:1.67.1' +implementation 'io.grpc:grpc-protobuf-lite:1.67.1' +implementation 'io.grpc:grpc-stub:1.67.1' compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+ ``` @@ -99,7 +99,7 @@ For [Bazel](https://bazel.build), you can either (with the GAVs from above), or use `@io_grpc_grpc_java//api` et al (see below). [the JARs]: -https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.66.0 +https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.67.1 Development snapshots are available in [Sonatypes's snapshot repository](https://oss.sonatype.org/content/repositories/snapshots/). @@ -131,7 +131,7 @@ For protobuf-based codegen integrated with the Maven build system, you can use com.google.protobuf:protoc:3.25.3:exe:${os.detected.classifier} grpc-java - io.grpc:protoc-gen-grpc-java:1.66.0:exe:${os.detected.classifier} + io.grpc:protoc-gen-grpc-java:1.67.1:exe:${os.detected.classifier} @@ -161,7 +161,7 @@ protobuf { } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.66.0' + artifact = 'io.grpc:protoc-gen-grpc-java:1.67.1' } } generateProtoTasks { @@ -194,7 +194,7 @@ protobuf { } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.66.0' + artifact = 'io.grpc:protoc-gen-grpc-java:1.67.1' } } generateProtoTasks { From 927d21541dbc34be245f09c8180358a94ae5c031 Mon Sep 17 00:00:00 2001 From: Riya Mehta <55350838+rmehta19@users.noreply.github.com> Date: Tue, 1 Oct 2024 08:24:18 -0700 Subject: [PATCH 096/103] s2a: Move s2a implementation to internal package --- s2a/BUILD.bazel | 28 +++++++------- .../handshaker/S2AServiceGrpc.java | 38 +++++++++---------- .../io/grpc/s2a/S2AChannelCredentials.java | 6 +-- .../channel/S2AChannelPool.java | 2 +- .../channel/S2AGrpcChannelPool.java | 2 +- .../channel/S2AHandshakerServiceChannel.java | 2 +- .../handshaker/ConnectionClosedException.java | 2 +- .../GetAuthenticationMechanisms.java | 6 +-- .../{ => internal}/handshaker/ProtoUtil.java | 2 +- .../handshaker/S2AConnectionException.java | 2 +- .../handshaker/S2AIdentity.java | 2 +- .../handshaker/S2APrivateKeyMethod.java | 4 +- .../S2AProtocolNegotiatorFactory.java | 8 ++-- .../{ => internal}/handshaker/S2AStub.java | 2 +- .../handshaker/S2ATrustManager.java | 6 +-- .../handshaker/SslContextFactory.java | 4 +- .../tokenmanager/AccessTokenManager.java | 6 +-- .../tokenmanager/SingleTokenFetcher.java | 4 +- .../handshaker/tokenmanager/TokenFetcher.java | 4 +- s2a/src/main/proto/grpc/gcp/s2a/common.proto | 2 +- s2a/src/main/proto/grpc/gcp/s2a/s2a.proto | 2 +- .../main/proto/grpc/gcp/s2a/s2a_context.proto | 2 +- .../channel/S2AGrpcChannelPoolTest.java | 2 +- .../S2AHandshakerServiceChannelTest.java | 4 +- .../handshaker/FakeS2AServer.java | 2 +- .../handshaker/FakeS2AServerTest.java | 4 +- .../{ => internal}/handshaker/FakeWriter.java | 6 +-- .../GetAuthenticationMechanismsTest.java | 6 +-- .../handshaker/IntegrationTest.java | 4 +- .../handshaker/ProtoUtilTest.java | 2 +- .../handshaker/S2APrivateKeyMethodTest.java | 4 +- .../S2AProtocolNegotiatorFactoryTest.java | 12 +++--- .../handshaker/S2AStubTest.java | 8 ++-- .../handshaker/S2ATrustManagerTest.java | 4 +- .../handshaker/SslContextFactoryTest.java | 4 +- .../SingleTokenAccessTokenManagerTest.java | 4 +- 36 files changed, 101 insertions(+), 101 deletions(-) rename s2a/src/generated/main/grpc/io/grpc/s2a/{ => internal}/handshaker/S2AServiceGrpc.java (84%) rename s2a/src/main/java/io/grpc/s2a/{ => internal}/channel/S2AChannelPool.java (97%) rename s2a/src/main/java/io/grpc/s2a/{ => internal}/channel/S2AGrpcChannelPool.java (98%) rename s2a/src/main/java/io/grpc/s2a/{ => internal}/channel/S2AHandshakerServiceChannel.java (99%) rename s2a/src/main/java/io/grpc/s2a/{ => internal}/handshaker/ConnectionClosedException.java (95%) rename s2a/src/main/java/io/grpc/s2a/{ => internal}/handshaker/GetAuthenticationMechanisms.java (92%) rename s2a/src/main/java/io/grpc/s2a/{ => internal}/handshaker/ProtoUtil.java (98%) rename s2a/src/main/java/io/grpc/s2a/{ => internal}/handshaker/S2AConnectionException.java (95%) rename s2a/src/main/java/io/grpc/s2a/{ => internal}/handshaker/S2AIdentity.java (98%) rename s2a/src/main/java/io/grpc/s2a/{ => internal}/handshaker/S2APrivateKeyMethod.java (98%) rename s2a/src/main/java/io/grpc/s2a/{ => internal}/handshaker/S2AProtocolNegotiatorFactory.java (97%) rename s2a/src/main/java/io/grpc/s2a/{ => internal}/handshaker/S2AStub.java (99%) rename s2a/src/main/java/io/grpc/s2a/{ => internal}/handshaker/S2ATrustManager.java (97%) rename s2a/src/main/java/io/grpc/s2a/{ => internal}/handshaker/SslContextFactory.java (98%) rename s2a/src/main/java/io/grpc/s2a/{ => internal}/handshaker/tokenmanager/AccessTokenManager.java (90%) rename s2a/src/main/java/io/grpc/s2a/{ => internal}/handshaker/tokenmanager/SingleTokenFetcher.java (94%) rename s2a/src/main/java/io/grpc/s2a/{ => internal}/handshaker/tokenmanager/TokenFetcher.java (89%) rename s2a/src/test/java/io/grpc/s2a/{ => internal}/channel/S2AGrpcChannelPoolTest.java (99%) rename s2a/src/test/java/io/grpc/s2a/{ => internal}/channel/S2AHandshakerServiceChannelTest.java (99%) rename s2a/src/test/java/io/grpc/s2a/{ => internal}/handshaker/FakeS2AServer.java (97%) rename s2a/src/test/java/io/grpc/s2a/{ => internal}/handshaker/FakeS2AServerTest.java (98%) rename s2a/src/test/java/io/grpc/s2a/{ => internal}/handshaker/FakeWriter.java (98%) rename s2a/src/test/java/io/grpc/s2a/{ => internal}/handshaker/GetAuthenticationMechanismsTest.java (92%) rename s2a/src/test/java/io/grpc/s2a/{ => internal}/handshaker/IntegrationTest.java (98%) rename s2a/src/test/java/io/grpc/s2a/{ => internal}/handshaker/ProtoUtilTest.java (98%) rename s2a/src/test/java/io/grpc/s2a/{ => internal}/handshaker/S2APrivateKeyMethodTest.java (99%) rename s2a/src/test/java/io/grpc/s2a/{ => internal}/handshaker/S2AProtocolNegotiatorFactoryTest.java (96%) rename s2a/src/test/java/io/grpc/s2a/{ => internal}/handshaker/S2AStubTest.java (97%) rename s2a/src/test/java/io/grpc/s2a/{ => internal}/handshaker/S2ATrustManagerTest.java (99%) rename s2a/src/test/java/io/grpc/s2a/{ => internal}/handshaker/SslContextFactoryTest.java (98%) rename s2a/src/test/java/io/grpc/s2a/{ => internal}/handshaker/tokenmanager/SingleTokenAccessTokenManagerTest.java (95%) diff --git a/s2a/BUILD.bazel b/s2a/BUILD.bazel index 25ce2624f57..676215e1e1e 100644 --- a/s2a/BUILD.bazel +++ b/s2a/BUILD.bazel @@ -5,7 +5,7 @@ load("@rules_jvm_external//:defs.bzl", "artifact") java_library( name = "s2a_channel_pool", srcs = glob([ - "src/main/java/io/grpc/s2a/channel/*.java", + "src/main/java/io/grpc/s2a/internal/channel/*.java", ]), deps = [ "//api", @@ -23,7 +23,7 @@ java_library( java_library( name = "s2a_identity", - srcs = ["src/main/java/io/grpc/s2a/handshaker/S2AIdentity.java"], + srcs = ["src/main/java/io/grpc/s2a/internal/handshaker/S2AIdentity.java"], deps = [ ":common_java_proto", artifact("com.google.errorprone:error_prone_annotations"), @@ -33,7 +33,7 @@ java_library( java_library( name = "token_fetcher", - srcs = ["src/main/java/io/grpc/s2a/handshaker/tokenmanager/TokenFetcher.java"], + srcs = ["src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/TokenFetcher.java"], deps = [ ":s2a_identity", ], @@ -42,7 +42,7 @@ java_library( java_library( name = "access_token_manager", srcs = [ - "src/main/java/io/grpc/s2a/handshaker/tokenmanager/AccessTokenManager.java", + "src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/AccessTokenManager.java", ], deps = [ ":s2a_identity", @@ -54,7 +54,7 @@ java_library( java_library( name = "single_token_fetcher", srcs = [ - "src/main/java/io/grpc/s2a/handshaker/tokenmanager/SingleTokenFetcher.java", + "src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/SingleTokenFetcher.java", ], deps = [ ":s2a_identity", @@ -66,15 +66,15 @@ java_library( java_library( name = "s2a_handshaker", srcs = [ - "src/main/java/io/grpc/s2a/handshaker/ConnectionClosedException.java", - "src/main/java/io/grpc/s2a/handshaker/GetAuthenticationMechanisms.java", - "src/main/java/io/grpc/s2a/handshaker/ProtoUtil.java", - "src/main/java/io/grpc/s2a/handshaker/S2AConnectionException.java", - "src/main/java/io/grpc/s2a/handshaker/S2APrivateKeyMethod.java", - "src/main/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactory.java", - "src/main/java/io/grpc/s2a/handshaker/S2AStub.java", - "src/main/java/io/grpc/s2a/handshaker/S2ATrustManager.java", - "src/main/java/io/grpc/s2a/handshaker/SslContextFactory.java", + "src/main/java/io/grpc/s2a/internal/handshaker/ConnectionClosedException.java", + "src/main/java/io/grpc/s2a/internal/handshaker/GetAuthenticationMechanisms.java", + "src/main/java/io/grpc/s2a/internal/handshaker/ProtoUtil.java", + "src/main/java/io/grpc/s2a/internal/handshaker/S2AConnectionException.java", + "src/main/java/io/grpc/s2a/internal/handshaker/S2APrivateKeyMethod.java", + "src/main/java/io/grpc/s2a/internal/handshaker/S2AProtocolNegotiatorFactory.java", + "src/main/java/io/grpc/s2a/internal/handshaker/S2AStub.java", + "src/main/java/io/grpc/s2a/internal/handshaker/S2ATrustManager.java", + "src/main/java/io/grpc/s2a/internal/handshaker/SslContextFactory.java", ], deps = [ ":access_token_manager", diff --git a/s2a/src/generated/main/grpc/io/grpc/s2a/handshaker/S2AServiceGrpc.java b/s2a/src/generated/main/grpc/io/grpc/s2a/internal/handshaker/S2AServiceGrpc.java similarity index 84% rename from s2a/src/generated/main/grpc/io/grpc/s2a/handshaker/S2AServiceGrpc.java rename to s2a/src/generated/main/grpc/io/grpc/s2a/internal/handshaker/S2AServiceGrpc.java index b365954b189..d759128a4c5 100644 --- a/s2a/src/generated/main/grpc/io/grpc/s2a/handshaker/S2AServiceGrpc.java +++ b/s2a/src/generated/main/grpc/io/grpc/s2a/internal/handshaker/S2AServiceGrpc.java @@ -1,4 +1,4 @@ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static io.grpc.MethodDescriptor.generateFullMethodName; @@ -15,29 +15,29 @@ private S2AServiceGrpc() {} public static final java.lang.String SERVICE_NAME = "grpc.gcp.s2a.S2AService"; // Static method descriptors that strictly reflect the proto. - private static volatile io.grpc.MethodDescriptor getSetUpSessionMethod; + private static volatile io.grpc.MethodDescriptor getSetUpSessionMethod; @io.grpc.stub.annotations.RpcMethod( fullMethodName = SERVICE_NAME + '/' + "SetUpSession", - requestType = io.grpc.s2a.handshaker.SessionReq.class, - responseType = io.grpc.s2a.handshaker.SessionResp.class, + requestType = io.grpc.s2a.internal.handshaker.SessionReq.class, + responseType = io.grpc.s2a.internal.handshaker.SessionResp.class, methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING) - public static io.grpc.MethodDescriptor getSetUpSessionMethod() { - io.grpc.MethodDescriptor getSetUpSessionMethod; + public static io.grpc.MethodDescriptor getSetUpSessionMethod() { + io.grpc.MethodDescriptor getSetUpSessionMethod; if ((getSetUpSessionMethod = S2AServiceGrpc.getSetUpSessionMethod) == null) { synchronized (S2AServiceGrpc.class) { if ((getSetUpSessionMethod = S2AServiceGrpc.getSetUpSessionMethod) == null) { S2AServiceGrpc.getSetUpSessionMethod = getSetUpSessionMethod = - io.grpc.MethodDescriptor.newBuilder() + io.grpc.MethodDescriptor.newBuilder() .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING) .setFullMethodName(generateFullMethodName(SERVICE_NAME, "SetUpSession")) .setSampledToLocalTracing(true) .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( - io.grpc.s2a.handshaker.SessionReq.getDefaultInstance())) + io.grpc.s2a.internal.handshaker.SessionReq.getDefaultInstance())) .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( - io.grpc.s2a.handshaker.SessionResp.getDefaultInstance())) + io.grpc.s2a.internal.handshaker.SessionResp.getDefaultInstance())) .setSchemaDescriptor(new S2AServiceMethodDescriptorSupplier("SetUpSession")) .build(); } @@ -100,8 +100,8 @@ public interface AsyncService { * operations from the TLS handshake. * */ - default io.grpc.stub.StreamObserver setUpSession( - io.grpc.stub.StreamObserver responseObserver) { + default io.grpc.stub.StreamObserver setUpSession( + io.grpc.stub.StreamObserver responseObserver) { return io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall(getSetUpSessionMethod(), responseObserver); } } @@ -139,8 +139,8 @@ protected S2AServiceStub build( * operations from the TLS handshake. * */ - public io.grpc.stub.StreamObserver setUpSession( - io.grpc.stub.StreamObserver responseObserver) { + public io.grpc.stub.StreamObserver setUpSession( + io.grpc.stub.StreamObserver responseObserver) { return io.grpc.stub.ClientCalls.asyncBidiStreamingCall( getChannel().newCall(getSetUpSessionMethod(), getCallOptions()), responseObserver); } @@ -211,7 +211,7 @@ public io.grpc.stub.StreamObserver invoke( switch (methodId) { case METHODID_SET_UP_SESSION: return (io.grpc.stub.StreamObserver) serviceImpl.setUpSession( - (io.grpc.stub.StreamObserver) responseObserver); + (io.grpc.stub.StreamObserver) responseObserver); default: throw new AssertionError(); } @@ -224,8 +224,8 @@ public static final io.grpc.ServerServiceDefinition bindService(AsyncService ser getSetUpSessionMethod(), io.grpc.stub.ServerCalls.asyncBidiStreamingCall( new MethodHandlers< - io.grpc.s2a.handshaker.SessionReq, - io.grpc.s2a.handshaker.SessionResp>( + io.grpc.s2a.internal.handshaker.SessionReq, + io.grpc.s2a.internal.handshaker.SessionResp>( service, METHODID_SET_UP_SESSION))) .build(); } @@ -236,7 +236,7 @@ private static abstract class S2AServiceBaseDescriptorSupplier @java.lang.Override public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() { - return io.grpc.s2a.handshaker.S2AProto.getDescriptor(); + return io.grpc.s2a.internal.handshaker.S2AProto.getDescriptor(); } @java.lang.Override diff --git a/s2a/src/main/java/io/grpc/s2a/S2AChannelCredentials.java b/s2a/src/main/java/io/grpc/s2a/S2AChannelCredentials.java index 7beecdb3621..2e040964dfa 100644 --- a/s2a/src/main/java/io/grpc/s2a/S2AChannelCredentials.java +++ b/s2a/src/main/java/io/grpc/s2a/S2AChannelCredentials.java @@ -28,9 +28,9 @@ import io.grpc.internal.SharedResourcePool; import io.grpc.netty.InternalNettyChannelCredentials; import io.grpc.netty.InternalProtocolNegotiator; -import io.grpc.s2a.channel.S2AHandshakerServiceChannel; -import io.grpc.s2a.handshaker.S2AIdentity; -import io.grpc.s2a.handshaker.S2AProtocolNegotiatorFactory; +import io.grpc.s2a.internal.channel.S2AHandshakerServiceChannel; +import io.grpc.s2a.internal.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.S2AProtocolNegotiatorFactory; import javax.annotation.concurrent.NotThreadSafe; import org.checkerframework.checker.nullness.qual.Nullable; diff --git a/s2a/src/main/java/io/grpc/s2a/channel/S2AChannelPool.java b/s2a/src/main/java/io/grpc/s2a/internal/channel/S2AChannelPool.java similarity index 97% rename from s2a/src/main/java/io/grpc/s2a/channel/S2AChannelPool.java rename to s2a/src/main/java/io/grpc/s2a/internal/channel/S2AChannelPool.java index e5caf5e69bd..aaaa0fffd53 100644 --- a/s2a/src/main/java/io/grpc/s2a/channel/S2AChannelPool.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/channel/S2AChannelPool.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.channel; +package io.grpc.s2a.internal.channel; import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.grpc.Channel; diff --git a/s2a/src/main/java/io/grpc/s2a/channel/S2AGrpcChannelPool.java b/s2a/src/main/java/io/grpc/s2a/internal/channel/S2AGrpcChannelPool.java similarity index 98% rename from s2a/src/main/java/io/grpc/s2a/channel/S2AGrpcChannelPool.java rename to s2a/src/main/java/io/grpc/s2a/internal/channel/S2AGrpcChannelPool.java index 4794cd9ee49..af911185e6c 100644 --- a/s2a/src/main/java/io/grpc/s2a/channel/S2AGrpcChannelPool.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/channel/S2AGrpcChannelPool.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.channel; +package io.grpc.s2a.internal.channel; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; diff --git a/s2a/src/main/java/io/grpc/s2a/channel/S2AHandshakerServiceChannel.java b/s2a/src/main/java/io/grpc/s2a/internal/channel/S2AHandshakerServiceChannel.java similarity index 99% rename from s2a/src/main/java/io/grpc/s2a/channel/S2AHandshakerServiceChannel.java rename to s2a/src/main/java/io/grpc/s2a/internal/channel/S2AHandshakerServiceChannel.java index 9d6950ce041..7a2b3e70672 100644 --- a/s2a/src/main/java/io/grpc/s2a/channel/S2AHandshakerServiceChannel.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/channel/S2AHandshakerServiceChannel.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.channel; +package io.grpc.s2a.internal.channel; import static com.google.common.base.Preconditions.checkNotNull; import static java.util.concurrent.TimeUnit.SECONDS; diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/ConnectionClosedException.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/ConnectionClosedException.java similarity index 95% rename from s2a/src/main/java/io/grpc/s2a/handshaker/ConnectionClosedException.java rename to s2a/src/main/java/io/grpc/s2a/internal/handshaker/ConnectionClosedException.java index 1a7f86bda91..d6f1aa70f7c 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/ConnectionClosedException.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/ConnectionClosedException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import java.io.IOException; diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/GetAuthenticationMechanisms.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/GetAuthenticationMechanisms.java similarity index 92% rename from s2a/src/main/java/io/grpc/s2a/handshaker/GetAuthenticationMechanisms.java rename to s2a/src/main/java/io/grpc/s2a/internal/handshaker/GetAuthenticationMechanisms.java index 56d74a9b766..2d089183f91 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/GetAuthenticationMechanisms.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/GetAuthenticationMechanisms.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import com.google.errorprone.annotations.Immutable; -import io.grpc.s2a.handshaker.S2AIdentity; -import io.grpc.s2a.handshaker.tokenmanager.AccessTokenManager; +import io.grpc.s2a.internal.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.tokenmanager.AccessTokenManager; import java.util.Optional; /** Retrieves the authentication mechanism for a given local identity. */ diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/ProtoUtil.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/ProtoUtil.java similarity index 98% rename from s2a/src/main/java/io/grpc/s2a/handshaker/ProtoUtil.java rename to s2a/src/main/java/io/grpc/s2a/internal/handshaker/ProtoUtil.java index 129cc2d60f1..1d88d5a2b55 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/ProtoUtil.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/ProtoUtil.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableSet; diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/S2AConnectionException.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AConnectionException.java similarity index 95% rename from s2a/src/main/java/io/grpc/s2a/handshaker/S2AConnectionException.java rename to s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AConnectionException.java index d976308ad22..9b6c244751b 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/S2AConnectionException.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AConnectionException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; /** Exception that denotes a runtime error that was encountered when talking to the S2A server. */ @SuppressWarnings("serial") // This class is never serialized. diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/S2AIdentity.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AIdentity.java similarity index 98% rename from s2a/src/main/java/io/grpc/s2a/handshaker/S2AIdentity.java rename to s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AIdentity.java index c4fed7377ac..0b691248e91 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/S2AIdentity.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AIdentity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static com.google.common.base.Preconditions.checkNotNull; diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/S2APrivateKeyMethod.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2APrivateKeyMethod.java similarity index 98% rename from s2a/src/main/java/io/grpc/s2a/handshaker/S2APrivateKeyMethod.java rename to s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2APrivateKeyMethod.java index fb6d5761355..c7262f70ef7 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/S2APrivateKeyMethod.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2APrivateKeyMethod.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -22,7 +22,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import com.google.protobuf.ByteString; -import io.grpc.s2a.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.S2AIdentity; import io.netty.handler.ssl.OpenSslPrivateKeyMethod; import java.io.IOException; import java.util.Optional; diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactory.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AProtocolNegotiatorFactory.java similarity index 97% rename from s2a/src/main/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactory.java rename to s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AProtocolNegotiatorFactory.java index 14bdc05238d..cb02d49ce9e 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactory.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AProtocolNegotiatorFactory.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -37,9 +37,9 @@ import io.grpc.netty.InternalProtocolNegotiator.ProtocolNegotiator; import io.grpc.netty.InternalProtocolNegotiators; import io.grpc.netty.InternalProtocolNegotiators.ProtocolNegotiationHandler; -import io.grpc.s2a.channel.S2AChannelPool; -import io.grpc.s2a.channel.S2AGrpcChannelPool; -import io.grpc.s2a.handshaker.S2AIdentity; +import io.grpc.s2a.internal.channel.S2AChannelPool; +import io.grpc.s2a.internal.channel.S2AGrpcChannelPool; +import io.grpc.s2a.internal.handshaker.S2AIdentity; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/S2AStub.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AStub.java similarity index 99% rename from s2a/src/main/java/io/grpc/s2a/handshaker/S2AStub.java rename to s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AStub.java index ea4fb033a13..0bfa3b4dac2 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/S2AStub.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AStub.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/S2ATrustManager.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2ATrustManager.java similarity index 97% rename from s2a/src/main/java/io/grpc/s2a/handshaker/S2ATrustManager.java rename to s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2ATrustManager.java index aafbb94c047..2f7e5750f88 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/S2ATrustManager.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2ATrustManager.java @@ -14,15 +14,15 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.collect.ImmutableList; import com.google.protobuf.ByteString; -import io.grpc.s2a.handshaker.S2AIdentity; -import io.grpc.s2a.handshaker.ValidatePeerCertificateChainReq.VerificationMode; +import io.grpc.s2a.internal.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.ValidatePeerCertificateChainReq.VerificationMode; import java.io.IOException; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/SslContextFactory.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/SslContextFactory.java similarity index 98% rename from s2a/src/main/java/io/grpc/s2a/handshaker/SslContextFactory.java rename to s2a/src/main/java/io/grpc/s2a/internal/handshaker/SslContextFactory.java index 1ac5887ebc4..72ace2c7885 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/SslContextFactory.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/SslContextFactory.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static com.google.common.base.Preconditions.checkNotNull; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableSet; import io.grpc.netty.GrpcSslContexts; -import io.grpc.s2a.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.S2AIdentity; import io.netty.handler.ssl.OpenSslContextOption; import io.netty.handler.ssl.OpenSslSessionContext; import io.netty.handler.ssl.OpenSslX509KeyManagerFactory; diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/AccessTokenManager.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/AccessTokenManager.java similarity index 90% rename from s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/AccessTokenManager.java rename to s2a/src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/AccessTokenManager.java index da75cf0d4dd..71e55b29fcd 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/AccessTokenManager.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/AccessTokenManager.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker.tokenmanager; +package io.grpc.s2a.internal.handshaker.tokenmanager; -import io.grpc.s2a.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.S2AIdentity; import java.lang.reflect.Method; import java.util.Optional; import javax.annotation.concurrent.ThreadSafe; @@ -31,7 +31,7 @@ public static Optional create() { Optional tokenFetcher; try { Class singleTokenFetcherClass = - Class.forName("io.grpc.s2a.handshaker.tokenmanager.SingleTokenFetcher"); + Class.forName("io.grpc.s2a.internal.handshaker.tokenmanager.SingleTokenFetcher"); Method createTokenFetcher = singleTokenFetcherClass.getMethod("create"); tokenFetcher = (Optional) createTokenFetcher.invoke(null); } catch (ClassNotFoundException e) { diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/SingleTokenFetcher.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/SingleTokenFetcher.java similarity index 94% rename from s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/SingleTokenFetcher.java rename to s2a/src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/SingleTokenFetcher.java index c3dffd2b715..a5402af9db2 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/SingleTokenFetcher.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/SingleTokenFetcher.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker.tokenmanager; +package io.grpc.s2a.internal.handshaker.tokenmanager; import com.google.common.annotations.VisibleForTesting; -import io.grpc.s2a.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.S2AIdentity; import java.util.Optional; /** Fetches a single access token via an environment variable. */ diff --git a/s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/TokenFetcher.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/TokenFetcher.java similarity index 89% rename from s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/TokenFetcher.java rename to s2a/src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/TokenFetcher.java index 9eeddaad844..6827f095afe 100644 --- a/s2a/src/main/java/io/grpc/s2a/handshaker/tokenmanager/TokenFetcher.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/TokenFetcher.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker.tokenmanager; +package io.grpc.s2a.internal.handshaker.tokenmanager; -import io.grpc.s2a.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.S2AIdentity; /** Fetches tokens used to authenticate to S2A. */ interface TokenFetcher { diff --git a/s2a/src/main/proto/grpc/gcp/s2a/common.proto b/s2a/src/main/proto/grpc/gcp/s2a/common.proto index 749739553dd..1b999234669 100644 --- a/s2a/src/main/proto/grpc/gcp/s2a/common.proto +++ b/s2a/src/main/proto/grpc/gcp/s2a/common.proto @@ -21,7 +21,7 @@ package grpc.gcp.s2a; option java_multiple_files = true; option java_outer_classname = "CommonProto"; -option java_package = "io.grpc.s2a.handshaker"; +option java_package = "io.grpc.s2a.internal.handshaker"; // The TLS 1.0-1.2 ciphersuites that the application can negotiate when using // S2A. diff --git a/s2a/src/main/proto/grpc/gcp/s2a/s2a.proto b/s2a/src/main/proto/grpc/gcp/s2a/s2a.proto index 8a85e348c24..b3f153943db 100644 --- a/s2a/src/main/proto/grpc/gcp/s2a/s2a.proto +++ b/s2a/src/main/proto/grpc/gcp/s2a/s2a.proto @@ -24,7 +24,7 @@ import "grpc/gcp/s2a/s2a_context.proto"; option java_multiple_files = true; option java_outer_classname = "S2AProto"; -option java_package = "io.grpc.s2a.handshaker"; +option java_package = "io.grpc.s2a.internal.handshaker"; enum SignatureAlgorithm { S2A_SSL_SIGN_UNSPECIFIED = 0; diff --git a/s2a/src/main/proto/grpc/gcp/s2a/s2a_context.proto b/s2a/src/main/proto/grpc/gcp/s2a/s2a_context.proto index edaeaf22669..745b4d267d6 100644 --- a/s2a/src/main/proto/grpc/gcp/s2a/s2a_context.proto +++ b/s2a/src/main/proto/grpc/gcp/s2a/s2a_context.proto @@ -23,7 +23,7 @@ import "grpc/gcp/s2a/common.proto"; option java_multiple_files = true; option java_outer_classname = "S2AContextProto"; -option java_package = "io.grpc.s2a.handshaker"; +option java_package = "io.grpc.s2a.internal.handshaker"; message S2AContext { // The SPIFFE ID from the peer leaf certificate, if present. diff --git a/s2a/src/test/java/io/grpc/s2a/channel/S2AGrpcChannelPoolTest.java b/s2a/src/test/java/io/grpc/s2a/internal/channel/S2AGrpcChannelPoolTest.java similarity index 99% rename from s2a/src/test/java/io/grpc/s2a/channel/S2AGrpcChannelPoolTest.java rename to s2a/src/test/java/io/grpc/s2a/internal/channel/S2AGrpcChannelPoolTest.java index 260129f8f56..afae456abb1 100644 --- a/s2a/src/test/java/io/grpc/s2a/channel/S2AGrpcChannelPoolTest.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/channel/S2AGrpcChannelPoolTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.channel; +package io.grpc.s2a.internal.channel; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; diff --git a/s2a/src/test/java/io/grpc/s2a/channel/S2AHandshakerServiceChannelTest.java b/s2a/src/test/java/io/grpc/s2a/internal/channel/S2AHandshakerServiceChannelTest.java similarity index 99% rename from s2a/src/test/java/io/grpc/s2a/channel/S2AHandshakerServiceChannelTest.java rename to s2a/src/test/java/io/grpc/s2a/internal/channel/S2AHandshakerServiceChannelTest.java index 7281adb9794..9e09d10f7da 100644 --- a/s2a/src/test/java/io/grpc/s2a/channel/S2AHandshakerServiceChannelTest.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/channel/S2AHandshakerServiceChannelTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.channel; +package io.grpc.s2a.internal.channel; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; @@ -36,7 +36,7 @@ import io.grpc.benchmarks.Utils; import io.grpc.internal.SharedResourceHolder.Resource; import io.grpc.netty.NettyServerBuilder; -import io.grpc.s2a.channel.S2AHandshakerServiceChannel.HandshakerServiceChannel; +import io.grpc.s2a.internal.channel.S2AHandshakerServiceChannel.HandshakerServiceChannel; import io.grpc.stub.StreamObserver; import io.grpc.testing.GrpcCleanupRule; import io.grpc.testing.protobuf.SimpleRequest; diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServer.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/FakeS2AServer.java similarity index 97% rename from s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServer.java rename to s2a/src/test/java/io/grpc/s2a/internal/handshaker/FakeS2AServer.java index d630f57d90d..a1745d209d7 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServer.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/FakeS2AServer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import io.grpc.stub.StreamObserver; import java.io.IOException; diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServerTest.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/FakeS2AServerTest.java similarity index 98% rename from s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServerTest.java rename to s2a/src/test/java/io/grpc/s2a/internal/handshaker/FakeS2AServerTest.java index a8868744f80..e61f8eea1a1 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/FakeS2AServerTest.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/FakeS2AServerTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; import static java.util.concurrent.TimeUnit.SECONDS; @@ -27,7 +27,7 @@ import io.grpc.Server; import io.grpc.ServerBuilder; import io.grpc.benchmarks.Utils; -import io.grpc.s2a.handshaker.ValidatePeerCertificateChainReq.VerificationMode; +import io.grpc.s2a.internal.handshaker.ValidatePeerCertificateChainReq.VerificationMode; import io.grpc.stub.StreamObserver; import java.io.IOException; import java.nio.charset.StandardCharsets; diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/FakeWriter.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/FakeWriter.java similarity index 98% rename from s2a/src/test/java/io/grpc/s2a/handshaker/FakeWriter.java rename to s2a/src/test/java/io/grpc/s2a/internal/handshaker/FakeWriter.java index b0e84fdf962..6466a08eca9 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/FakeWriter.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/FakeWriter.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; -import static io.grpc.s2a.handshaker.TLSVersion.TLS_VERSION_1_2; -import static io.grpc.s2a.handshaker.TLSVersion.TLS_VERSION_1_3; +import static io.grpc.s2a.internal.handshaker.TLSVersion.TLS_VERSION_1_2; +import static io.grpc.s2a.internal.handshaker.TLSVersion.TLS_VERSION_1_3; import com.google.common.collect.ImmutableMap; import com.google.errorprone.annotations.CanIgnoreReturnValue; diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/GetAuthenticationMechanismsTest.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/GetAuthenticationMechanismsTest.java similarity index 92% rename from s2a/src/test/java/io/grpc/s2a/handshaker/GetAuthenticationMechanismsTest.java rename to s2a/src/test/java/io/grpc/s2a/internal/handshaker/GetAuthenticationMechanismsTest.java index 884e1ec88eb..4c00b0746fc 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/GetAuthenticationMechanismsTest.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/GetAuthenticationMechanismsTest.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import com.google.common.truth.Expect; -import io.grpc.s2a.handshaker.S2AIdentity; -import io.grpc.s2a.handshaker.tokenmanager.SingleTokenFetcher; +import io.grpc.s2a.internal.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.tokenmanager.SingleTokenFetcher; import java.util.Optional; import org.junit.BeforeClass; import org.junit.Rule; diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/IntegrationTest.java similarity index 98% rename from s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java rename to s2a/src/test/java/io/grpc/s2a/internal/handshaker/IntegrationTest.java index 0fb9b12cf95..e1ad3d278c3 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/IntegrationTest.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/IntegrationTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static com.google.common.truth.Truth.assertThat; import static java.util.concurrent.TimeUnit.SECONDS; @@ -32,7 +32,7 @@ import io.grpc.netty.GrpcSslContexts; import io.grpc.netty.NettyServerBuilder; import io.grpc.s2a.S2AChannelCredentials; -import io.grpc.s2a.handshaker.FakeS2AServer; +import io.grpc.s2a.internal.handshaker.FakeS2AServer; import io.grpc.stub.StreamObserver; import io.grpc.testing.protobuf.SimpleRequest; import io.grpc.testing.protobuf.SimpleResponse; diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/ProtoUtilTest.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/ProtoUtilTest.java similarity index 98% rename from s2a/src/test/java/io/grpc/s2a/handshaker/ProtoUtilTest.java rename to s2a/src/test/java/io/grpc/s2a/internal/handshaker/ProtoUtilTest.java index f54063b9e04..b685d0bc755 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/ProtoUtilTest.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/ProtoUtilTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static org.junit.Assert.assertThrows; diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/S2APrivateKeyMethodTest.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2APrivateKeyMethodTest.java similarity index 99% rename from s2a/src/test/java/io/grpc/s2a/handshaker/S2APrivateKeyMethodTest.java rename to s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2APrivateKeyMethodTest.java index fc8d42d09c0..c885783be99 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/S2APrivateKeyMethodTest.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2APrivateKeyMethodTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; @@ -26,7 +26,7 @@ import com.google.common.truth.Expect; import com.google.protobuf.ByteString; import io.grpc.netty.GrpcSslContexts; -import io.grpc.s2a.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.S2AIdentity; import io.netty.handler.ssl.OpenSslPrivateKeyMethod; import io.netty.handler.ssl.SslContextBuilder; import java.io.ByteArrayInputStream; diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactoryTest.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2AProtocolNegotiatorFactoryTest.java similarity index 96% rename from s2a/src/test/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactoryTest.java rename to s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2AProtocolNegotiatorFactoryTest.java index fa6c4fc858d..d469b07df0f 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/S2AProtocolNegotiatorFactoryTest.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2AProtocolNegotiatorFactoryTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; @@ -35,11 +35,11 @@ import io.grpc.netty.GrpcHttp2ConnectionHandler; import io.grpc.netty.InternalProtocolNegotiator; import io.grpc.netty.InternalProtocolNegotiator.ProtocolNegotiator; -import io.grpc.s2a.channel.S2AChannelPool; -import io.grpc.s2a.channel.S2AGrpcChannelPool; -import io.grpc.s2a.channel.S2AHandshakerServiceChannel; -import io.grpc.s2a.handshaker.S2AIdentity; -import io.grpc.s2a.handshaker.S2AProtocolNegotiatorFactory.S2AProtocolNegotiator; +import io.grpc.s2a.internal.channel.S2AChannelPool; +import io.grpc.s2a.internal.channel.S2AGrpcChannelPool; +import io.grpc.s2a.internal.channel.S2AHandshakerServiceChannel; +import io.grpc.s2a.internal.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.S2AProtocolNegotiatorFactory.S2AProtocolNegotiator; import io.grpc.stub.StreamObserver; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandler; diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/S2AStubTest.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2AStubTest.java similarity index 97% rename from s2a/src/test/java/io/grpc/s2a/handshaker/S2AStubTest.java rename to s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2AStubTest.java index a1daf9948a1..2e3bfc02879 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/S2AStubTest.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2AStubTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; @@ -23,9 +23,9 @@ import com.google.common.truth.Expect; import io.grpc.InsecureChannelCredentials; import io.grpc.internal.SharedResourcePool; -import io.grpc.s2a.channel.S2AChannelPool; -import io.grpc.s2a.channel.S2AGrpcChannelPool; -import io.grpc.s2a.channel.S2AHandshakerServiceChannel; +import io.grpc.s2a.internal.channel.S2AChannelPool; +import io.grpc.s2a.internal.channel.S2AGrpcChannelPool; +import io.grpc.s2a.internal.channel.S2AHandshakerServiceChannel; import io.grpc.stub.StreamObserver; import java.io.IOException; import java.nio.charset.StandardCharsets; diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/S2ATrustManagerTest.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2ATrustManagerTest.java similarity index 99% rename from s2a/src/test/java/io/grpc/s2a/handshaker/S2ATrustManagerTest.java rename to s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2ATrustManagerTest.java index 384e1aba5cc..198001838aa 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/S2ATrustManagerTest.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2ATrustManagerTest.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; -import io.grpc.s2a.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.S2AIdentity; import java.io.ByteArrayInputStream; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/SslContextFactoryTest.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/SslContextFactoryTest.java similarity index 98% rename from s2a/src/test/java/io/grpc/s2a/handshaker/SslContextFactoryTest.java rename to s2a/src/test/java/io/grpc/s2a/internal/handshaker/SslContextFactoryTest.java index a2a66a7b563..fc3cfb5e441 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/SslContextFactoryTest.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/SslContextFactoryTest.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker; +package io.grpc.s2a.internal.handshaker; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import com.google.common.truth.Expect; -import io.grpc.s2a.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.S2AIdentity; import io.netty.handler.ssl.OpenSslSessionContext; import io.netty.handler.ssl.SslContext; import java.security.GeneralSecurityException; diff --git a/s2a/src/test/java/io/grpc/s2a/handshaker/tokenmanager/SingleTokenAccessTokenManagerTest.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/tokenmanager/SingleTokenAccessTokenManagerTest.java similarity index 95% rename from s2a/src/test/java/io/grpc/s2a/handshaker/tokenmanager/SingleTokenAccessTokenManagerTest.java rename to s2a/src/test/java/io/grpc/s2a/internal/handshaker/tokenmanager/SingleTokenAccessTokenManagerTest.java index 80adba07f20..5bf2ce05259 100644 --- a/s2a/src/test/java/io/grpc/s2a/handshaker/tokenmanager/SingleTokenAccessTokenManagerTest.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/tokenmanager/SingleTokenAccessTokenManagerTest.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package io.grpc.s2a.handshaker.tokenmanager; +package io.grpc.s2a.internal.handshaker.tokenmanager; import static com.google.common.truth.Truth.assertThat; -import io.grpc.s2a.handshaker.S2AIdentity; +import io.grpc.s2a.internal.handshaker.S2AIdentity; import java.util.Optional; import org.junit.Before; import org.junit.Test; From 9ab35a761bf4bc9d39ade5fd15b1e29fb31f061c Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Wed, 2 Oct 2024 11:03:44 -0700 Subject: [PATCH 097/103] util: Store only a list of children in MultiChildLB A map of children is still needed, but is created temporarily on update. The order of children is currently preserved, but we could use regular HashMaps if that is not useful. --- .../io/grpc/util/MultiChildLoadBalancer.java | 61 ++++++++++--------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java index 330ec9d5357..3e56f41d038 100644 --- a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java +++ b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java @@ -24,7 +24,8 @@ import static io.grpc.ConnectivityState.TRANSIENT_FAILURE; import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableList; +import com.google.common.base.Objects; +import com.google.common.collect.Maps; import io.grpc.Attributes; import io.grpc.ConnectivityState; import io.grpc.EquivalentAddressGroup; @@ -37,12 +38,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -55,7 +53,8 @@ public abstract class MultiChildLoadBalancer extends LoadBalancer { private static final Logger logger = Logger.getLogger(MultiChildLoadBalancer.class.getName()); - private final Map childLbStates = new LinkedHashMap<>(); + // Modify by replacing the list to release memory when no longer used. + private List childLbStates = new ArrayList<>(0); private final Helper helper; // Set to true if currently in the process of handling resolved addresses. protected boolean resolvingAddresses; @@ -84,7 +83,8 @@ protected MultiChildLoadBalancer(Helper helper) { */ protected Map createChildAddressesMap( ResolvedAddresses resolvedAddresses) { - Map childAddresses = new HashMap<>(); + Map childAddresses = + Maps.newLinkedHashMapWithExpectedSize(resolvedAddresses.getAddresses().size()); for (EquivalentAddressGroup eag : resolvedAddresses.getAddresses()) { ResolvedAddresses addresses = resolvedAddresses.toBuilder() .setAddresses(Collections.singletonList(eag)) @@ -144,7 +144,7 @@ public void handleNameResolutionError(Status error) { @Override public void shutdown() { logger.log(Level.FINE, "Shutdown"); - for (ChildLbState state : childLbStates.values()) { + for (ChildLbState state : childLbStates) { state.shutdown(); } childLbStates.clear(); @@ -169,39 +169,37 @@ protected final AcceptResolvedAddrRetVal acceptResolvedAddressesInternal( return new AcceptResolvedAddrRetVal(unavailableStatus, null); } - updateChildrenWithResolvedAddresses(newChildAddresses); - - return new AcceptResolvedAddrRetVal(Status.OK, getRemovedChildren(newChildAddresses.keySet())); + List removed = updateChildrenWithResolvedAddresses(newChildAddresses); + return new AcceptResolvedAddrRetVal(Status.OK, removed); } - private void updateChildrenWithResolvedAddresses( + /** Returns removed children. */ + private List updateChildrenWithResolvedAddresses( Map newChildAddresses) { + // Create a map with the old values + Map oldStatesMap = + Maps.newLinkedHashMapWithExpectedSize(childLbStates.size()); + for (ChildLbState state : childLbStates) { + oldStatesMap.put(state.getKey(), state); + } + + // Move ChildLbStates from the map to a new list (preserving the new map's order) + List newChildLbStates = new ArrayList<>(newChildAddresses.size()); for (Map.Entry entry : newChildAddresses.entrySet()) { - ChildLbState childLbState = childLbStates.get(entry.getKey()); + ChildLbState childLbState = oldStatesMap.remove(entry.getKey()); if (childLbState == null) { childLbState = createChildLbState(entry.getKey()); - childLbStates.put(entry.getKey(), childLbState); } + newChildLbStates.add(childLbState); if (entry.getValue() != null) { childLbState.setResolvedAddresses(entry.getValue()); // update child childLbState.lb.handleResolvedAddresses(entry.getValue()); // update child LB } } - } - /** - * Identifies which children have been removed (are not part of the newChildKeys). - */ - private List getRemovedChildren(Set newChildKeys) { - List removedChildren = new ArrayList<>(); - // Do removals - for (Object key : ImmutableList.copyOf(childLbStates.keySet())) { - if (!newChildKeys.contains(key)) { - ChildLbState childLbState = childLbStates.remove(key); - removedChildren.add(childLbState); - } - } - return removedChildren; + childLbStates = newChildLbStates; + // Remaining entries in map are orphaned + return new ArrayList<>(oldStatesMap.values()); } protected final void shutdownRemoved(List removedChildren) { @@ -236,12 +234,17 @@ protected final Helper getHelper() { @VisibleForTesting public final Collection getChildLbStates() { - return childLbStates.values(); + return childLbStates; } @VisibleForTesting public final ChildLbState getChildLbState(Object key) { - return childLbStates.get(key); + for (ChildLbState state : childLbStates) { + if (Objects.equal(state.getKey(), key)) { + return state; + } + } + return null; } @VisibleForTesting From b8a0ba44af57475e43e4747607ce78a56ff08340 Mon Sep 17 00:00:00 2001 From: Riya Mehta <55350838+rmehta19@users.noreply.github.com> Date: Wed, 2 Oct 2024 12:55:21 -0700 Subject: [PATCH 098/103] s2a: clean up usage of certs (#11583) * use CertificateUtils. * Different names for each ec cert. * Generate rsa certs with ::1 IP + delete CSRs. * try. --- .../internal/handshaker/FakeS2AServer.java | 2 +- .../s2a/internal/handshaker/FakeWriter.java | 27 ++++---- s2a/src/test/resources/cert_chain_ec.pem | 63 ++++++++++--------- s2a/src/test/resources/client.csr | 16 ----- s2a/src/test/resources/client_cert.pem | 34 +++++----- s2a/src/test/resources/client_key.pem | 52 +++++++-------- s2a/src/test/resources/config.cnf | 2 +- s2a/src/test/resources/int_cert1_.cnf | 6 +- s2a/src/test/resources/int_cert1_ec.pem | 21 ++++--- s2a/src/test/resources/int_cert2.cnf | 6 +- s2a/src/test/resources/int_cert2_ec.pem | 21 ++++--- s2a/src/test/resources/int_key1_ec.pem | 6 +- s2a/src/test/resources/int_key2_ec.pem | 6 +- s2a/src/test/resources/leaf.cnf | 6 +- s2a/src/test/resources/leaf_cert_ec.pem | 21 ++++--- s2a/src/test/resources/leaf_key_ec.pem | 6 +- s2a/src/test/resources/root_cert.pem | 32 +++++----- s2a/src/test/resources/root_cert_ec.pem | 20 +++--- s2a/src/test/resources/root_ec.cnf | 6 +- s2a/src/test/resources/root_key.pem | 56 ++++++++--------- s2a/src/test/resources/root_key_ec.pem | 6 +- s2a/src/test/resources/server.csr | 16 ----- s2a/src/test/resources/server_cert.pem | 32 +++++----- s2a/src/test/resources/server_key.pem | 52 +++++++-------- 24 files changed, 246 insertions(+), 269 deletions(-) delete mode 100644 s2a/src/test/resources/client.csr delete mode 100644 s2a/src/test/resources/server.csr diff --git a/s2a/src/test/java/io/grpc/s2a/internal/handshaker/FakeS2AServer.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/FakeS2AServer.java index a1745d209d7..2d19dd122ec 100644 --- a/s2a/src/test/java/io/grpc/s2a/internal/handshaker/FakeS2AServer.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/FakeS2AServer.java @@ -28,7 +28,7 @@ public final class FakeS2AServer extends S2AServiceGrpc.S2AServiceImplBase { private final FakeWriter writer; - public FakeS2AServer() throws InvalidKeySpecException, NoSuchAlgorithmException { + public FakeS2AServer() throws InvalidKeySpecException, NoSuchAlgorithmException, IOException { this.writer = new FakeWriter(); this.writer.setVerificationResult(FakeWriter.VerificationResult.SUCCESS).initializePrivateKey(); } diff --git a/s2a/src/test/java/io/grpc/s2a/internal/handshaker/FakeWriter.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/FakeWriter.java index 6466a08eca9..4455e77b1e2 100644 --- a/s2a/src/test/java/io/grpc/s2a/internal/handshaker/FakeWriter.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/FakeWriter.java @@ -23,17 +23,18 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.protobuf.ByteString; import io.grpc.stub.StreamObserver; +import io.grpc.util.CertificateUtils; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Signature; import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.util.Base64; /** A fake Writer Class to mock the behavior of S2A server. */ final class FakeWriter implements StreamObserver { @@ -59,12 +60,8 @@ enum VerificationResult { new File("src/test/resources/int_cert2_ec.pem"); public static final File cert1File = new File("src/test/resources/int_cert1_ec.pem"); - - // src/test/resources/leaf_key_ec.pem - private static final String PRIVATE_KEY = - "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgR2HBqtWTWu4NLiow" - + "ar8vh+9vAmCONE59C+jXNAb9r8ehRANCAATRM8ozcr8PTOVsZNWh+rTmJ6t+rODu" - + "g3LwWpUQq9h7AddjGlLrrTNrceOyO7nh9aEk5plKhs/h7PO8+vkEFsEx"; + public static final File keyFile = + new File("src/test/resources/leaf_key_ec.pem"); private static final ImmutableMap ALGORITHM_TO_SIGNATURE_INSTANCE_IDENTIFIER = ImmutableMap.of( @@ -107,10 +104,14 @@ FakeWriter setFailureReason(String failureReason) { } @CanIgnoreReturnValue - FakeWriter initializePrivateKey() throws InvalidKeySpecException, NoSuchAlgorithmException { - privateKey = - KeyFactory.getInstance("EC") - .generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(PRIVATE_KEY))); + FakeWriter initializePrivateKey() throws InvalidKeySpecException, NoSuchAlgorithmException, + IOException, FileNotFoundException, UnsupportedEncodingException { + FileInputStream keyInputStream = new FileInputStream(keyFile); + try { + privateKey = CertificateUtils.getPrivateKey(keyInputStream); + } finally { + keyInputStream.close(); + } return this; } diff --git a/s2a/src/test/resources/cert_chain_ec.pem b/s2a/src/test/resources/cert_chain_ec.pem index 0e097d39bf2..a249904286c 100644 --- a/s2a/src/test/resources/cert_chain_ec.pem +++ b/s2a/src/test/resources/cert_chain_ec.pem @@ -1,36 +1,39 @@ -----BEGIN CERTIFICATE----- -MIIB0jCCAXigAwIBAgIUBV1dftEhhEMTI83L6jpeJn2tuyQwCgYIKoZIzj0EAwIw -JjEKMAgGA1UECgwBbzELMAkGA1UECwwCb3UxCzAJBgNVBAMMAmNuMB4XDTI0MDkx -OTIzMDQwMFoXDTQ0MDkxOTIzMDQwMFowJjEKMAgGA1UECgwBbzELMAkGA1UECwwC -b3UxCzAJBgNVBAMMAmNuMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0TPKM3K/ -D0zlbGTVofq05ierfqzg7oNy8FqVEKvYewHXYxpS660za3Hjsju54fWhJOaZSobP -4ezzvPr5BBbBMaOBgzCBgDAOBgNVHQ8BAf8EBAMCB4AwIAYDVR0lAQH/BBYwFAYI -KwYBBQUHAwIGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFPvP7dnB -dg8ZoLB0w62tbvoIRRHPMB8GA1UdIwQYMBaAFHeH+MNh2fgyjNHYP9hLAv9Sl1yD -MAoGCCqGSM49BAMCA0gAMEUCIBcsImaxeFjxFXCXYNQJnde+rsEOgbeAHrAC0SZQ -NlB2AiEA4epDhw/o+6BfgDbqlZsNEHkScPrwupnBQGLQlmNJe2c= +MIIB6jCCAZCgAwIBAgIUA98F2JkYZAyz9BdIkBK3P8Df7OUwCgYIKoZIzj0EAwIw +MjEOMAwGA1UECgwFaW50MU8xDzANBgNVBAsMBmludDFPVTEPMA0GA1UEAwwGaW50 +MUNOMB4XDTI0MTAwMTIxNDIwMFoXDTQ0MTAwMTIxNDIwMFowMjEOMAwGA1UECgwF +bGVhZk8xDzANBgNVBAsMBmxlYWZPVTEPMA0GA1UEAwwGbGVhZkNOMFkwEwYHKoZI +zj0CAQYIKoZIzj0DAQcDQgAEtpTTzt2VDTP6gO4uUIpg8sB63Ff4T4YPMoIGrrn3 +tU3f9j0Ysa5/xblM0LkwRImcrKKchYDiNm1wHkWo+qDImaOBgzCBgDAOBgNVHQ8B +Af8EBAMCB4AwIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMAwGA1Ud +EwEB/wQCMAAwHQYDVR0OBBYEFGzFBt/E6vDJRcH+Izy4MQ9AHycqMB8GA1UdIwQY +MBaAFBYs72Jv682/xzG3Tm8hItIFis//MAoGCCqGSM49BAMCA0gAMEUCIHUcqPTB +mQ4kXE0WoOUC8ZmzvthvfKjCNe0YogcjZgwWAiEAvapmWoQIO4qie25Ae9sYRCPq +5xAHztAquk5HLfwabow= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIB1zCCAX6gAwIBAgIUW4GYHncSLeb7Tmw7FMjX/DesReIwCgYIKoZIzj0EAwIw -JjEKMAgGA1UECgwBbzELMAkGA1UECwwCb3UxCzAJBgNVBAMMAmNuMB4XDTI0MDkx -OTIzMDA0MVoXDTQ0MDkxOTIzMDA0MVowJjEKMAgGA1UECgwBbzELMAkGA1UECwwC -b3UxCzAJBgNVBAMMAmNuMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAqtg2E+h -Wfr5dnewqsCLwM0PohkB83Gh7V3i/TPFkNKF/V6pKdz5a3Z8sicG+g7uJX+eyOoD -43Z8woO7MgJl8aOBiTCBhjAOBgNVHQ8BAf8EBAMCAQYwIAYDVR0lAQH/BBYwFAYI -KwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE -FHeH+MNh2fgyjNHYP9hLAv9Sl1yDMB8GA1UdIwQYMBaAFP+PTOryxis9d7HVfqhP -MyMEgMZOMAoGCCqGSM49BAMCA0cAMEQCIHbzJvxHMIDPBRi1e047K0mqKKBSfViS -guiDSoQ5g5OuAiBT5ePqDLs4PyrK6XFkiEWoRX8Z5T9y419Go+fpLM+DaA== +MIIB8TCCAZagAwIBAgIUEXwpznJIlU+ELO7Qgb4UUGpfbj8wCgYIKoZIzj0EAwIw +MjEOMAwGA1UECgwFaW50Mk8xDzANBgNVBAsMBmludDJPVTEPMA0GA1UEAwwGaW50 +MkNOMB4XDTI0MTAwMTIxNDIwMFoXDTQ0MTAwMTIxNDIwMFowMjEOMAwGA1UECgwF +aW50MU8xDzANBgNVBAsMBmludDFPVTEPMA0GA1UEAwwGaW50MUNOMFkwEwYHKoZI +zj0CAQYIKoZIzj0DAQcDQgAEoenicrtL6ezEW2yLSXADscDJQ/fdbr+vJEU/aieV +wA2EnPbrdpvQZaz+pXtuZzBLZY50XI9y33E+/PvBFtZob6OBiTCBhjAOBgNVHQ8B +Af8EBAMCAQYwIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1Ud +EwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFBYs72Jv682/xzG3Tm8hItIFis//MB8G +A1UdIwQYMBaAFPhN6eGgVc36Kc50rREZhMdBIkgGMAoGCCqGSM49BAMCA0kAMEYC +IQDiPcbihg1iDi0m9CUn96IbWOTh1X75RfVJYcR3Q5T78AIhAK/fxZauDeWPzk2r +2/ohCQOZFHtAi9VRpr/TqNi3SaYt -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIB1zCCAX6gAwIBAgIUBBKkTrqFxQUist2pK2uj8/DRnKMwCgYIKoZIzj0EAwIw -JjEKMAgGA1UECgwBbzELMAkGA1UECwwCb3UxCzAJBgNVBAMMAmNuMB4XDTI0MDkx -OTIyNTYwNloXDTQ0MDkxOTIyNTYwNlowJjEKMAgGA1UECgwBbzELMAkGA1UECwwC -b3UxCzAJBgNVBAMMAmNuMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFW7/Te2z -jS8KlpF8RMMYaZtKf6EZlrZIIo5SO9j6baAKXVna9LmDCrzXnOLIeqOuZq0ODizU -i4DFALB2yd5BkaOBiTCBhjAOBgNVHQ8BAf8EBAMCAQYwIAYDVR0lAQH/BBYwFAYI -KwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYBAf8CAQIwHQYDVR0OBBYE -FP+PTOryxis9d7HVfqhPMyMEgMZOMB8GA1UdIwQYMBaAFFITbB0BULPtynN9SMki -lEarWxcKMAoGCCqGSM49BAMCA0cAMEQCIHK4cTTx4Ti7Te9hA9VVtHoMCt5fL4Cl -XnQR6D5xW4pPAiAQ+CilQdZUhVK5bU6wbrwLgcwf+40ETK/KASId5970rQ== +MIIB8DCCAZagAwIBAgIUNOH4wQEoKHvaQ9Xgd36vh5TnhfUwCgYIKoZIzj0EAwIw +MjEOMAwGA1UECgwFcm9vdE8xDzANBgNVBAsMBnJvb3RPVTEPMA0GA1UEAwwGcm9v +dENOMB4XDTI0MTAwMTIxNDIwMFoXDTQ0MTAwMTIxNDIwMFowMjEOMAwGA1UECgwF +aW50Mk8xDzANBgNVBAsMBmludDJPVTEPMA0GA1UEAwwGaW50MkNOMFkwEwYHKoZI +zj0CAQYIKoZIzj0DAQcDQgAE44B/G4pzAvLpIUaPp8XNRtXuw8jeLgE40NjQMuqq +3jNs6ID/fv/jiRggLMXL3Tii1CisM4BRjg56/Owky1Fyv6OBiTCBhjAOBgNVHQ8B +Af8EBAMCAQYwIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1Ud +EwEB/wQIMAYBAf8CAQIwHQYDVR0OBBYEFPhN6eGgVc36Kc50rREZhMdBIkgGMB8G +A1UdIwQYMBaAFNHNBlllqi9koRtf7EBHjRMwVgWsMAoGCCqGSM49BAMCA0gAMEUC +IBd4bvqVeYSSUEGF1wB0KlYxn1L0Ub/LjgIUUQFAEwahAiEAgeArX63bnlI7u3dq +v/FGilvcLP3P3AvRozpHJiIZ860= -----END CERTIFICATE----- \ No newline at end of file diff --git a/s2a/src/test/resources/client.csr b/s2a/src/test/resources/client.csr deleted file mode 100644 index 664f5a4cf86..00000000000 --- a/s2a/src/test/resources/client.csr +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIChzCCAW8CAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEAoSS3KtFgiXX4vAUNscFGIB/r2OOMgiZMKHz72dN0 -5kSxwdpQxpMIhwEoe0lhHNfOiuE7/r6VbGG9RGGIcQcoSonc3InPRfpnzfj9KohJ -i8pYkLL9EwElAEl9sWnvVKTza8jTApDP2Z/fntBEsWAMsLPpuRZT6tgN1sXe4vNG -4wufJSxuImyCVAx1fkZjRkYEKOtm1osnEDng4R0WXZ6S+q5lYzYPk1wxgbjdZu2U -fWxP6V63SphV0NFXTx0E401j2h258cIqTVj8lRX6dfl9gO0d43Rd+hSU7R4iXGEw -arixuH9g5H745AFf9H52twHPcNP9cEKBljBpSV5z3MvTkQIDAQABoC4wLAYJKoZI -hvcNAQkOMR8wHTAbBgNVHREEFDAShxAAAAAAAAAAAAAAAAAAAAAAMA0GCSqGSIb3 -DQEBCwUAA4IBAQCQHim3aIpGJs5u6JhEA07Rwm8YKyVALDEklhsHILlFhdNr2uV7 -S+3bHV79mDGjxNWvFcgK5h5ENkT60tXbhbie1gYmFT0RMCYHDsL09NGTh8G9Bbdl -UKeA9DMhRSYzE7Ks3Lo1dJvX7OAEI0qV77dGpQknufYpmHiBXuqtB9I0SpYi1c4O -9IUn/NY0yiYFPsIEsVRz/1dK97wazusLnijaMwNNhUc9bJwTyujhlr+b8ioPyADG -e+GDF97d0nQ8806DOJF4GTRKwaXD+R5zN5t4ULhZ7ERqLNeE9EnWRe4CvSGvBoNA -hIVeYaLd761Z9ZKvOnsgCr8qvMDilDFY6OfB ------END CERTIFICATE REQUEST----- \ No newline at end of file diff --git a/s2a/src/test/resources/client_cert.pem b/s2a/src/test/resources/client_cert.pem index b72f6991c91..837f8bb5019 100644 --- a/s2a/src/test/resources/client_cert.pem +++ b/s2a/src/test/resources/client_cert.pem @@ -1,18 +1,20 @@ -----BEGIN CERTIFICATE----- -MIIC9DCCAdwCFB+cDXee2sIHjdlBhdNpTo+G2XAjMA0GCSqGSIb3DQEBCwUAMFkx -CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl -cm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMzEw -MTcyMzA5MDNaFw00MzEwMTcyMzA5MDNaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCC -ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKEktyrRYIl1+LwFDbHBRiAf -69jjjIImTCh8+9nTdOZEscHaUMaTCIcBKHtJYRzXzorhO/6+lWxhvURhiHEHKEqJ -3NyJz0X6Z834/SqISYvKWJCy/RMBJQBJfbFp71Sk82vI0wKQz9mf357QRLFgDLCz -6bkWU+rYDdbF3uLzRuMLnyUsbiJsglQMdX5GY0ZGBCjrZtaLJxA54OEdFl2ekvqu -ZWM2D5NcMYG43WbtlH1sT+let0qYVdDRV08dBONNY9odufHCKk1Y/JUV+nX5fYDt -HeN0XfoUlO0eIlxhMGq4sbh/YOR++OQBX/R+drcBz3DT/XBCgZYwaUlec9zL05EC -AwEAATANBgkqhkiG9w0BAQsFAAOCAQEARorc1t2OJnwm1lxhf2KpTpNvNOI9FJak -iSHz/MxhMdu4BG/dQHkKkWoVC6W2Kaimx4OImBwRlGEmGf4P0bXOLSTOumk2k1np -ZUbw7Z2cJzvBmT2BLoHRXcBvbFIBW5DJUSHR37eXEKP57BeD+Og4/3XhNzehSpTX -DRd2Ix/D39JjYA462nqPHQP8HDMf6+0BFmvf9ZRYmFucccYQRCUCKDqb8+wGf9W6 -tKNRE6qPG2jpAQ9qkgO7XuucbLvpywt5xj+yDRbOIq43l40mHaz4lRp697oaxjP8 -HSVcMydW3cluoW3AVInNIaqbM1dr6931MllK62DKipFtmCycq/56XA== +MIIDPTCCAiWgAwIBAgIUaarddwSWeE4jDC9kwxEr446ehqUwDQYJKoZIhvcNAQEL +BQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MB4X +DTI0MTAwMTIxNTk1NFoXDTQ0MTAwMTIxNTk1NFowFDESMBAGA1UEAwwJbG9jYWxo +b3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxlNsldt7yAU4KRuS +2D2/FjNIE1US5olBm4HteTr++41WaELZJqNLRPPp052jEQU3aKSYNGZvUUO6buu7 +eFpz2SBNUVMyvmzzocjVAyyf4NQvDazYHWOb+/YCeUppTRWriz4V5sn47qJTQ8cd +CGrTFeLHxUjx4nh/OiqVXP/KnF3EqPEuqph0ky7+GirnJgPRe+C5ERuGkJye8dmP +yWGA2lSS6MeDe7JZTAMi08bAn7BuNpeBkOzz1msGGI9PnUanUs7GOPWTDdcQAVY8 +KMvHCuGaNMGpb4rOR2mm8LlbAbpTPz8Pkw4QtMCLkgsrz2CzXpVwnLsU7nDXJAIO +B155lQIDAQABo0IwQDAdBgNVHQ4EFgQUSZEyIHLzkIw7AwkBaUjYfIrGVR4wHwYD +VR0jBBgwFoAUcq3dtxAVA410YWyM0B4e+4umbiwwDQYJKoZIhvcNAQELBQADggEB +AAz0bZ4ayrZLhA45xn0yvdpdqiCtiWikCRtxgE7VXHg/ziZJVMpBpAhbIGO5tIyd +lttnRXHwz5DUwKiba4/bCEFe229BshQEql5qaqcbGbFfSly11WeqqnwR1N7c8Gpv +pD9sVrx22seN0rTUk87MY/S7mzCxHqAx35zm/LTW3pWcgCTMKFHy4Gt4mpTnXkNA +WkhP2OhW5RLiu6Whi0BEdb2TGG1+ctamgijKXb+gJeef5ehlHXG8eU862KF5UlEA +NeQKBm/PpQxOMe0NdpatjN8QRoczku0Itiodng+OZ1o+2iSNG988uFRb3CUSnjtE +R/HL6ULAFzo59EpIYxruU/w= -----END CERTIFICATE----- \ No newline at end of file diff --git a/s2a/src/test/resources/client_key.pem b/s2a/src/test/resources/client_key.pem index dd3e2ff78f1..38b93eb65c4 100644 --- a/s2a/src/test/resources/client_key.pem +++ b/s2a/src/test/resources/client_key.pem @@ -1,28 +1,28 @@ -----BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQChJLcq0WCJdfi8 -BQ2xwUYgH+vY44yCJkwofPvZ03TmRLHB2lDGkwiHASh7SWEc186K4Tv+vpVsYb1E -YYhxByhKidzcic9F+mfN+P0qiEmLyliQsv0TASUASX2xae9UpPNryNMCkM/Zn9+e -0ESxYAyws+m5FlPq2A3Wxd7i80bjC58lLG4ibIJUDHV+RmNGRgQo62bWiycQOeDh -HRZdnpL6rmVjNg+TXDGBuN1m7ZR9bE/pXrdKmFXQ0VdPHQTjTWPaHbnxwipNWPyV -Ffp1+X2A7R3jdF36FJTtHiJcYTBquLG4f2DkfvjkAV/0fna3Ac9w0/1wQoGWMGlJ -XnPcy9ORAgMBAAECggEALAUqoGDIHWUDyOEch5WDwZzWwc4PgTJTFbBm4G96fLkB -UjKAZG6gIrk3RM6b39Q4UQoMaJ/Jk+zzVi3Kpw3MfOhCVGC1JamtF8BP8IGAjdZ9 -8TFkHv/uCrEIzCFjRt00vhoDQq0qiom4/dppGYdikBbl3zDxRbM1vJkbNSY+FCGW -dA0uJ5XdMLR6lPeB5odqjUggnfUgPCOLdV/F+HkSM9NP1bzmHLiKznzwFsfat139 -7LdzJwNN5IX4Io6cxsxNlrX/NNvPkKdGv07Z6FYxWROyKCunjh48xFcQg0ltoRuq -R9P8/LwS8GYrcc1uC/uBc0e6VgM9D9fsvh+8SQtf3QKBgQDXX+z2GnsFoEs7xv9U -qN0HEX4jOkihZvFu43layUmeCeE8wlEctJ0TsM5Bd7FMoUG6e5/btwhsAIYW89Xn -l/R8OzxR6Kh952Dce4DAULuIeopiw7ASJwTZtO9lWhxw0hjM1hxXTG+xxOqQvsRX -c+d+vtvdIqyJ4ELfzg9kUtkdpwKBgQC/ig3cmej7dQdRAMn0YAwgwhuLkCqVFh4y -WIlqyPPejKf8RXubqgtaSYx/T7apP87SMMSfSLaUdrYAGjST6k+tG5cmwutPIbw/ -osL7U3hcIhjX3hfHgI69Ojcpplbd5yqTxZHpxIs6iAQCEqNuasLXIDMouqNhGF1D -YssD6qxcBwKBgQCdZqWvVrsB6ZwSG+UO4jpmqAofhMD/9FQOToCqMOF0dpP966WL -7RO/CEA06FzTPCblOuQhlyq4g8l7jMiPcSZkhIYY9oftO+Q2Pqxh4J6tp6DrfUh4 -e7u3v9wVnj2a1nD5gqFDy8D1kow7LLAhmbtdje7xNh4SxasaFWZ6U3IJkQKBgGS1 -F5i3q9IatCAZBBZjMb0/kfANevYsTPA3sPjec6q91c1EUzuDarisFx0RMn9Gt124 -mokNWEIzMHpZTO/AsOfZq92LeuF+YVYsI8y1FIGMw/csJOCWbXZ812gkt2OxGafc -p118I6BAx6q3VgrGQ2+M1JlDmIeCofa+SPPkPX+dAoGBAJrOgEJ+oyEaX/YR1g+f -33pWoPQbRCG7T4+Y0oetCCWIcMg1/IUvGUCGmRDxj5dMqB+a0vJtviQN9rjpSuNS -0EVw79AJkIjHhi6KDOfAuyBvzGhxpqxGufnQ2GU0QL65NxQfd290xkxikN0ZGtuB -SDgZoJxMOGYwf8EX5i9h27Db +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDGU2yV23vIBTgp +G5LYPb8WM0gTVRLmiUGbge15Ov77jVZoQtkmo0tE8+nTnaMRBTdopJg0Zm9RQ7pu +67t4WnPZIE1RUzK+bPOhyNUDLJ/g1C8NrNgdY5v79gJ5SmlNFauLPhXmyfjuolND +xx0IatMV4sfFSPHieH86KpVc/8qcXcSo8S6qmHSTLv4aKucmA9F74LkRG4aQnJ7x +2Y/JYYDaVJLox4N7sllMAyLTxsCfsG42l4GQ7PPWawYYj0+dRqdSzsY49ZMN1xAB +Vjwoy8cK4Zo0walvis5HaabwuVsBulM/Pw+TDhC0wIuSCyvPYLNelXCcuxTucNck +Ag4HXnmVAgMBAAECggEAKuW9jXaBgiS63o1jyFkmvWcPNntG0M2sfrXuRzQfFgse +vwOCk8xrSflWQNsOe+58ayp6746ekl3LdBWSIbiy6SqG/sm3pp/LXNmjVYHv/QH4 +QYV643R5t1ihdVnGiBFhXwdpVleme/tpdjYZzgnJKak5W69o/nrgzhSK5ShAy2xM +j0XXbgdqG+4JxPb5BZmjHHfXAXUfgSORMdfArkbgFBRc9wL/6JVTXjeAMy5WX9qe +5UQsSOYkwc9P2snifC/jdIhjHQOkkx59O0FgukJEFZPoagVG1duWQbnNDr7QVHCJ +jV6dg9tIT4SXD3uPSPbgNGlRUseIakCzrhHARJuA2wKBgQD/h8zoh0KaqKyViCYw +XKOFpm1pAFnp2GiDOblxNubNFAXEWnC+FlkvO/z1s0zVuYELUqfxcYMSXJFEVelK +rfjZtoC5oxqWGqLo9iCj7pa8t+ipulYcLt2SWc7eZPD4T4lzeEf1Qz77aKcz34sa +dv9lzQkDvhR/Mv1VeEGFHiq2VwKBgQDGsLcTGH5Yxs//LRSY8TigBkQEDrH5NvXu +2jtAzZhy1Yhsoa5eiZkhnnzM6+n05ovfZLcy6s7dnwP1Y+C79vs+DKMBsodtDG5z +YpsB0VrXYa6P6pCqkcz0Bz9xdo5sOhAK3AKnX6jd29XBDdeYsw/lxHLG24wProTD +cCYFqtaj8wKBgQCaqKT68DL9zK14a8lBaDCIyexaqx3AjXzkP+Hfhi03XrEG4P5v +7rLYBeTbCUSt7vMN2V9QoTWFvYUm6SCkVJvTmcRblz6WL1T+z0l+LwAJBP7LC77m +m+77j2PH8yxt/iXhP6G97o+GNxdMLDbTM8bs5KZaH4fkXQY73uc5HMMZTQKBgEZS +7blYhf+t/ph2wD+RwVUCYrh86wkmJs2veCFro3WhlnO8lhbn5Mc9bTaqmVgQ8ZjT +8POYoDdYvPHxs+1TcYF4v4kuQziZmc5FLE/sZZauADb38tQsXrpQhmgGakpsEpmF +XXsYJJDB6lo2KATn+8x7R5SSyHQUdPEnlI2U9ft5AoGBAJw0NJiM1EzRS8xq0DmO +AvQaPjo01o2hH6wghws8gDQwrj0eHraHgVi7zo0VkaHJbO7ahKPudset3N7owJhA +CUAPPRtv5wn0amAyNz77f1dz4Gys3AkcchflqhbEaQpzKYx4kX0adclur4WJ/DVm +P7DI977SHCVB4FVMbXMEkBjN -----END PRIVATE KEY----- \ No newline at end of file diff --git a/s2a/src/test/resources/config.cnf b/s2a/src/test/resources/config.cnf index 38d9a9ccdb0..5f9a7710e92 100644 --- a/s2a/src/test/resources/config.cnf +++ b/s2a/src/test/resources/config.cnf @@ -14,4 +14,4 @@ emailAddress = Email Address subjectAltName = @alt_names [alt_names] -IP.1 = :: \ No newline at end of file +IP.1 = ::1 \ No newline at end of file diff --git a/s2a/src/test/resources/int_cert1_.cnf b/s2a/src/test/resources/int_cert1_.cnf index 8eaf6570da1..ba5a0f66a5e 100644 --- a/s2a/src/test/resources/int_cert1_.cnf +++ b/s2a/src/test/resources/int_cert1_.cnf @@ -4,9 +4,9 @@ req_extensions = v3_req prompt = no [req_distinguished_name] -O = o -OU = ou -CN = cn +O = int1O +OU = int1OU +CN = int1CN [v3_req] keyUsage = critical, keyCertSign, cRLSign diff --git a/s2a/src/test/resources/int_cert1_ec.pem b/s2a/src/test/resources/int_cert1_ec.pem index 980de5aa900..de83c2aba79 100644 --- a/s2a/src/test/resources/int_cert1_ec.pem +++ b/s2a/src/test/resources/int_cert1_ec.pem @@ -1,12 +1,13 @@ -----BEGIN CERTIFICATE----- -MIIB1zCCAX6gAwIBAgIUW4GYHncSLeb7Tmw7FMjX/DesReIwCgYIKoZIzj0EAwIw -JjEKMAgGA1UECgwBbzELMAkGA1UECwwCb3UxCzAJBgNVBAMMAmNuMB4XDTI0MDkx -OTIzMDA0MVoXDTQ0MDkxOTIzMDA0MVowJjEKMAgGA1UECgwBbzELMAkGA1UECwwC -b3UxCzAJBgNVBAMMAmNuMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAqtg2E+h -Wfr5dnewqsCLwM0PohkB83Gh7V3i/TPFkNKF/V6pKdz5a3Z8sicG+g7uJX+eyOoD -43Z8woO7MgJl8aOBiTCBhjAOBgNVHQ8BAf8EBAMCAQYwIAYDVR0lAQH/BBYwFAYI -KwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE -FHeH+MNh2fgyjNHYP9hLAv9Sl1yDMB8GA1UdIwQYMBaAFP+PTOryxis9d7HVfqhP -MyMEgMZOMAoGCCqGSM49BAMCA0cAMEQCIHbzJvxHMIDPBRi1e047K0mqKKBSfViS -guiDSoQ5g5OuAiBT5ePqDLs4PyrK6XFkiEWoRX8Z5T9y419Go+fpLM+DaA== +MIIB8TCCAZagAwIBAgIUEXwpznJIlU+ELO7Qgb4UUGpfbj8wCgYIKoZIzj0EAwIw +MjEOMAwGA1UECgwFaW50Mk8xDzANBgNVBAsMBmludDJPVTEPMA0GA1UEAwwGaW50 +MkNOMB4XDTI0MTAwMTIxNDIwMFoXDTQ0MTAwMTIxNDIwMFowMjEOMAwGA1UECgwF +aW50MU8xDzANBgNVBAsMBmludDFPVTEPMA0GA1UEAwwGaW50MUNOMFkwEwYHKoZI +zj0CAQYIKoZIzj0DAQcDQgAEoenicrtL6ezEW2yLSXADscDJQ/fdbr+vJEU/aieV +wA2EnPbrdpvQZaz+pXtuZzBLZY50XI9y33E+/PvBFtZob6OBiTCBhjAOBgNVHQ8B +Af8EBAMCAQYwIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1Ud +EwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFBYs72Jv682/xzG3Tm8hItIFis//MB8G +A1UdIwQYMBaAFPhN6eGgVc36Kc50rREZhMdBIkgGMAoGCCqGSM49BAMCA0kAMEYC +IQDiPcbihg1iDi0m9CUn96IbWOTh1X75RfVJYcR3Q5T78AIhAK/fxZauDeWPzk2r +2/ohCQOZFHtAi9VRpr/TqNi3SaYt -----END CERTIFICATE----- \ No newline at end of file diff --git a/s2a/src/test/resources/int_cert2.cnf b/s2a/src/test/resources/int_cert2.cnf index c6a2559ce10..f48524effb2 100644 --- a/s2a/src/test/resources/int_cert2.cnf +++ b/s2a/src/test/resources/int_cert2.cnf @@ -4,9 +4,9 @@ req_extensions = v3_req prompt = no [req_distinguished_name] -O = o -OU = ou -CN = cn +O = int2O +OU = int2OU +CN = int2CN [v3_req] keyUsage = critical, keyCertSign, cRLSign diff --git a/s2a/src/test/resources/int_cert2_ec.pem b/s2a/src/test/resources/int_cert2_ec.pem index 574fa0195de..4f502fda808 100644 --- a/s2a/src/test/resources/int_cert2_ec.pem +++ b/s2a/src/test/resources/int_cert2_ec.pem @@ -1,12 +1,13 @@ -----BEGIN CERTIFICATE----- -MIIB1zCCAX6gAwIBAgIUBBKkTrqFxQUist2pK2uj8/DRnKMwCgYIKoZIzj0EAwIw -JjEKMAgGA1UECgwBbzELMAkGA1UECwwCb3UxCzAJBgNVBAMMAmNuMB4XDTI0MDkx -OTIyNTYwNloXDTQ0MDkxOTIyNTYwNlowJjEKMAgGA1UECgwBbzELMAkGA1UECwwC -b3UxCzAJBgNVBAMMAmNuMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFW7/Te2z -jS8KlpF8RMMYaZtKf6EZlrZIIo5SO9j6baAKXVna9LmDCrzXnOLIeqOuZq0ODizU -i4DFALB2yd5BkaOBiTCBhjAOBgNVHQ8BAf8EBAMCAQYwIAYDVR0lAQH/BBYwFAYI -KwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYBAf8CAQIwHQYDVR0OBBYE -FP+PTOryxis9d7HVfqhPMyMEgMZOMB8GA1UdIwQYMBaAFFITbB0BULPtynN9SMki -lEarWxcKMAoGCCqGSM49BAMCA0cAMEQCIHK4cTTx4Ti7Te9hA9VVtHoMCt5fL4Cl -XnQR6D5xW4pPAiAQ+CilQdZUhVK5bU6wbrwLgcwf+40ETK/KASId5970rQ== +MIIB8DCCAZagAwIBAgIUNOH4wQEoKHvaQ9Xgd36vh5TnhfUwCgYIKoZIzj0EAwIw +MjEOMAwGA1UECgwFcm9vdE8xDzANBgNVBAsMBnJvb3RPVTEPMA0GA1UEAwwGcm9v +dENOMB4XDTI0MTAwMTIxNDIwMFoXDTQ0MTAwMTIxNDIwMFowMjEOMAwGA1UECgwF +aW50Mk8xDzANBgNVBAsMBmludDJPVTEPMA0GA1UEAwwGaW50MkNOMFkwEwYHKoZI +zj0CAQYIKoZIzj0DAQcDQgAE44B/G4pzAvLpIUaPp8XNRtXuw8jeLgE40NjQMuqq +3jNs6ID/fv/jiRggLMXL3Tii1CisM4BRjg56/Owky1Fyv6OBiTCBhjAOBgNVHQ8B +Af8EBAMCAQYwIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1Ud +EwEB/wQIMAYBAf8CAQIwHQYDVR0OBBYEFPhN6eGgVc36Kc50rREZhMdBIkgGMB8G +A1UdIwQYMBaAFNHNBlllqi9koRtf7EBHjRMwVgWsMAoGCCqGSM49BAMCA0gAMEUC +IBd4bvqVeYSSUEGF1wB0KlYxn1L0Ub/LjgIUUQFAEwahAiEAgeArX63bnlI7u3dq +v/FGilvcLP3P3AvRozpHJiIZ860= -----END CERTIFICATE----- \ No newline at end of file diff --git a/s2a/src/test/resources/int_key1_ec.pem b/s2a/src/test/resources/int_key1_ec.pem index 7ff3864746b..909c119b60c 100644 --- a/s2a/src/test/resources/int_key1_ec.pem +++ b/s2a/src/test/resources/int_key1_ec.pem @@ -1,5 +1,5 @@ -----BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgLIQUM1HkFM/LWND8 -jCZ4wHXjFZ1ZZmQolahkZB0O1VChRANCAAQCq2DYT6FZ+vl2d7CqwIvAzQ+iGQHz -caHtXeL9M8WQ0oX9Xqkp3PlrdnyyJwb6Du4lf57I6gPjdnzCg7syAmXx +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgnYGMzs4siZ7Fy3mI +rmsqBdP6We4Zt+ndtOYEGaZDj06hRANCAASh6eJyu0vp7MRbbItJcAOxwMlD991u +v68kRT9qJ5XADYSc9ut2m9BlrP6le25nMEtljnRcj3LfcT78+8EW1mhv -----END PRIVATE KEY----- \ No newline at end of file diff --git a/s2a/src/test/resources/int_key2_ec.pem b/s2a/src/test/resources/int_key2_ec.pem index 7f529ae855f..520300d2560 100644 --- a/s2a/src/test/resources/int_key2_ec.pem +++ b/s2a/src/test/resources/int_key2_ec.pem @@ -1,5 +1,5 @@ -----BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgGfm6kyaAMMrmYGhS -jxprBwtcZdP6qXlU1cVIO5bOT8qhRANCAAQVbv9N7bONLwqWkXxEwxhpm0p/oRmW -tkgijlI72PptoApdWdr0uYMKvNec4sh6o65mrQ4OLNSLgMUAsHbJ3kGR +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgzLSoAcENXIiQfBS7 +meBDCohT1rofhWSfD0m55qi8V3WhRANCAATjgH8binMC8ukhRo+nxc1G1e7DyN4u +ATjQ2NAy6qreM2zogP9+/+OJGCAsxcvdOKLUKKwzgFGODnr87CTLUXK/ -----END PRIVATE KEY----- \ No newline at end of file diff --git a/s2a/src/test/resources/leaf.cnf b/s2a/src/test/resources/leaf.cnf index d5b373cbc71..c21cee5568f 100644 --- a/s2a/src/test/resources/leaf.cnf +++ b/s2a/src/test/resources/leaf.cnf @@ -4,9 +4,9 @@ req_extensions = v3_req prompt = no [req_distinguished_name] -O = o -OU = ou -CN = cn +O = leafO +OU = leafOU +CN = leafCN [v3_req] keyUsage = critical, digitalSignature diff --git a/s2a/src/test/resources/leaf_cert_ec.pem b/s2a/src/test/resources/leaf_cert_ec.pem index 39692b95fda..ca48b821f60 100644 --- a/s2a/src/test/resources/leaf_cert_ec.pem +++ b/s2a/src/test/resources/leaf_cert_ec.pem @@ -1,12 +1,13 @@ -----BEGIN CERTIFICATE----- -MIIB0jCCAXigAwIBAgIUBV1dftEhhEMTI83L6jpeJn2tuyQwCgYIKoZIzj0EAwIw -JjEKMAgGA1UECgwBbzELMAkGA1UECwwCb3UxCzAJBgNVBAMMAmNuMB4XDTI0MDkx -OTIzMDQwMFoXDTQ0MDkxOTIzMDQwMFowJjEKMAgGA1UECgwBbzELMAkGA1UECwwC -b3UxCzAJBgNVBAMMAmNuMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0TPKM3K/ -D0zlbGTVofq05ierfqzg7oNy8FqVEKvYewHXYxpS660za3Hjsju54fWhJOaZSobP -4ezzvPr5BBbBMaOBgzCBgDAOBgNVHQ8BAf8EBAMCB4AwIAYDVR0lAQH/BBYwFAYI -KwYBBQUHAwIGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFPvP7dnB -dg8ZoLB0w62tbvoIRRHPMB8GA1UdIwQYMBaAFHeH+MNh2fgyjNHYP9hLAv9Sl1yD -MAoGCCqGSM49BAMCA0gAMEUCIBcsImaxeFjxFXCXYNQJnde+rsEOgbeAHrAC0SZQ -NlB2AiEA4epDhw/o+6BfgDbqlZsNEHkScPrwupnBQGLQlmNJe2c= +MIIB6jCCAZCgAwIBAgIUA98F2JkYZAyz9BdIkBK3P8Df7OUwCgYIKoZIzj0EAwIw +MjEOMAwGA1UECgwFaW50MU8xDzANBgNVBAsMBmludDFPVTEPMA0GA1UEAwwGaW50 +MUNOMB4XDTI0MTAwMTIxNDIwMFoXDTQ0MTAwMTIxNDIwMFowMjEOMAwGA1UECgwF +bGVhZk8xDzANBgNVBAsMBmxlYWZPVTEPMA0GA1UEAwwGbGVhZkNOMFkwEwYHKoZI +zj0CAQYIKoZIzj0DAQcDQgAEtpTTzt2VDTP6gO4uUIpg8sB63Ff4T4YPMoIGrrn3 +tU3f9j0Ysa5/xblM0LkwRImcrKKchYDiNm1wHkWo+qDImaOBgzCBgDAOBgNVHQ8B +Af8EBAMCB4AwIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMAwGA1Ud +EwEB/wQCMAAwHQYDVR0OBBYEFGzFBt/E6vDJRcH+Izy4MQ9AHycqMB8GA1UdIwQY +MBaAFBYs72Jv682/xzG3Tm8hItIFis//MAoGCCqGSM49BAMCA0gAMEUCIHUcqPTB +mQ4kXE0WoOUC8ZmzvthvfKjCNe0YogcjZgwWAiEAvapmWoQIO4qie25Ae9sYRCPq +5xAHztAquk5HLfwabow= -----END CERTIFICATE----- \ No newline at end of file diff --git a/s2a/src/test/resources/leaf_key_ec.pem b/s2a/src/test/resources/leaf_key_ec.pem index d90ad8f4db8..b92b90ba1da 100644 --- a/s2a/src/test/resources/leaf_key_ec.pem +++ b/s2a/src/test/resources/leaf_key_ec.pem @@ -1,5 +1,5 @@ -----BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgR2HBqtWTWu4NLiow -ar8vh+9vAmCONE59C+jXNAb9r8ehRANCAATRM8ozcr8PTOVsZNWh+rTmJ6t+rODu -g3LwWpUQq9h7AddjGlLrrTNrceOyO7nh9aEk5plKhs/h7PO8+vkEFsEx +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkvnGZBh3uIYfZiau +/0qN0YcQXlwwVVUh8EybjvKUlX2hRANCAAS2lNPO3ZUNM/qA7i5QimDywHrcV/hP +hg8yggauufe1Td/2PRixrn/FuUzQuTBEiZysopyFgOI2bXAeRaj6oMiZ -----END PRIVATE KEY----- \ No newline at end of file diff --git a/s2a/src/test/resources/root_cert.pem b/s2a/src/test/resources/root_cert.pem index 737e601691c..ccd0a46bc23 100644 --- a/s2a/src/test/resources/root_cert.pem +++ b/s2a/src/test/resources/root_cert.pem @@ -1,22 +1,22 @@ -----BEGIN CERTIFICATE----- -MIIDkzCCAnugAwIBAgIUb7RsINwsFgKf0Q0RuzfOgp48j6UwDQYJKoZIhvcNAQEL +MIIDkzCCAnugAwIBAgIUWemeXZdfqcqkP8/Eyj74oTJtoNQwDQYJKoZIhvcNAQEL BQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MB4X -DTIzMTAxNzIzMDczOFoXDTQzMTAxNzIzMDczOFowWTELMAkGA1UEBhMCQVUxEzAR +DTI0MTAwMTIxNTkxMVoXDTQ0MTAwMTIxNTkxMVowWTELMAkGA1UEBhMCQVUxEzAR BgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5 IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEAkIFnQLuhzYnm3rvmi/U7zMgEP2Tqgb3VC00frSXEV6olZcLgyC9g -0DAGdt9l9lP90DQTG5KCOtoW2BTqM/aaVpR0OaDFOCy90FIj6YyZLZ9w2PQxQcxS -GQHyEvWszTkNxeDyG1mPTj+Go8JLKqdvLg/9GUgPg6stxyAZwYhyUTGuEM4bv0sn -b3vmHRmIGJ/w6aLtd7nK8LkNHa3WVrbvRGHrzdMHfpzF/M/5fAk8GfRYugo39knf -VLKGyQCXNI8Y1iHGEmPqQZIFPTjBL6caIlbEV0VHlxoSOGB6JVxcllxAEvd6abqX -RJVJPQzzGfEnMNYp9SiZQ9bvDRUsUkWyYwIDAQABo1MwUTAdBgNVHQ4EFgQUAZMN -F9JAGHbA3jGOeu6bWFvSdWkwHwYDVR0jBBgwFoAUAZMNF9JAGHbA3jGOeu6bWFvS -dWkwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAicBli36ISJFu -lrJqOHVqTeNP6go0I35VGnP44nEEP5cBvRD3XntBFEk5D3mSNNOGt+2ncxom8VR9 -FsLuTfHAipXePJI6MSxFuBPea8V/YPBs3npk5f1FRvJ5vEgtzFvBjsKmp1dS9hH0 -KUWtWcsAkO2Anc/LVc0xxSidL8NjzYoEFqiki0TNNwCJjmd9XwnBLHW38sEb/pgy -KTyRpOyG3Zg2UDjBHiXPBrmIvVFLB6+LrPNvfr1k4HjIgVY539ZXUvVMDKytMrDY -h63EMDn4kkPpxXlufgWGybjN5D51OylyWBZLe+L1DQyWEg0Vd7GwPzb6p7bmI7MP -pooqbgbDpQ== +MIIBCgKCAQEAt3A04hy5lljv86Nu0LLQZ2hA+fcImHjt1p1Mxgcta/5oxfVLcerE +ZH+DAQLDtWzp9Up/vI57MM419GIL8Iszk7hnZRS/HWJ+2jewZJtz4i/g15dLr6+1 +uabMdPOWos60BwcLMxKEe6lJO1mV4z9d4NH4mAuMIHyM+ty0Klp9MfeDJtYEh0+z +AxJUHCixDTsnKJro7My7A3ZT7bvaMfXxS7XN6qlRgBfiCmXo/GKTFfmfBW/EZGkG +XOCxE2D79wYNhC41Q/ix0kwjEeOj2vgGFoiyblSdHdzvRXzsoQTEiZSM8lJDR2IT +ZbpgbBlknMU6efNWlS8P5damB9ZWXg3x4wIDAQABo1MwUTAdBgNVHQ4EFgQUcq3d +txAVA410YWyM0B4e+4umbiwwHwYDVR0jBBgwFoAUcq3dtxAVA410YWyM0B4e+4um +biwwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEApZvaI9y7vjX/ +RRdvwf2Db9KlTE9nuVQ3AsrmG9Ml0p2X6U5aTetxdYBo2PuaaYHheF03JOH8zjpL +UfFzvbi52DPbfFAaDw/6NIAenXlg492leNvUFNjGGRyJO9R5/aDfv40/fT3Em5G5 +DnR8SeGQ9tI1t6xBBT+d+/MilSiEKVu8IIF/p0SwvEyR4pKo6wFVZR0ZiIj2v/FZ +P5Qk0Xhb+slpmaR3Wtx/mPl9Wb3kpPD4CAwhWDqFkKJql9/n9FvMjdwlCQKQGB26 +ZDXY3C0UTdktK5biNWRgAUVJEWBX6Q2amrxQHIn2d9RJ8uxCME/KBAntK+VxZE78 +w0JOvQ4Dpw== -----END CERTIFICATE----- \ No newline at end of file diff --git a/s2a/src/test/resources/root_cert_ec.pem b/s2a/src/test/resources/root_cert_ec.pem index 0dd465e8e90..3d20dcfe83c 100644 --- a/s2a/src/test/resources/root_cert_ec.pem +++ b/s2a/src/test/resources/root_cert_ec.pem @@ -1,12 +1,12 @@ -----BEGIN CERTIFICATE----- -MIIBrzCCAVWgAwIBAgIUGV+9j5V61CZaa6mbrchDag5miEQwCgYIKoZIzj0EAwIw -JjEKMAgGA1UECgwBbzELMAkGA1UECwwCb3UxCzAJBgNVBAMMAmNuMB4XDTI0MDkx -OTIyNDMwOFoXDTQ0MDkxOTIyNDMwOFowJjEKMAgGA1UECgwBbzELMAkGA1UECwwC -b3UxCzAJBgNVBAMMAmNuMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDVPIq1ds -/MX52CX9YU1RdEeM89YP4o3BN8OiP2O4qcuc11k4Qu4Mo4RWeN9OJpNElTQJ0K8n -/rIvbmw8AIMquaNhMF8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdJQQWMBQGCCsGAQUF -BwMBBggrBgEFBQcDAjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRSE2wdAVCz -7cpzfUjJIpRGq1sXCjAKBggqhkjOPQQDAgNIADBFAiEA1TEfHWArDnepmtMDQ4wd -Q3uqPrV2Ye2KMO67/BHEGIQCIFu3JutXYYVU/CinwH89AJW+FJ7zokaPjCDkbiOH -+h+H +MIIBxzCCAW2gAwIBAgIUN+H7Td9dhyvMrrzZhanevAfCN34wCgYIKoZIzj0EAwIw +MjEOMAwGA1UECgwFcm9vdE8xDzANBgNVBAsMBnJvb3RPVTEPMA0GA1UEAwwGcm9v +dENOMB4XDTI0MTAwMTIxNDIwMFoXDTQ0MTAwMTIxNDIwMFowMjEOMAwGA1UECgwF +cm9vdE8xDzANBgNVBAsMBnJvb3RPVTEPMA0GA1UEAwwGcm9vdENOMFkwEwYHKoZI +zj0CAQYIKoZIzj0DAQcDQgAEGnS2gVv6Bs0GtuUAOebR9E0fqaj3zi9mD97B/dgi +MLENhtVPJQzeePv6Ccap+73O0BINRNOl8tlHX0YaXDeEHKNhMF8wDgYDVR0PAQH/ +BAQDAgEGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTRzQZZZaovZKEbX+xAR40TMFYFrDAKBggqhkjOPQQD +AgNIADBFAiEAgnIyLs7FsZNsJjFgYzlaut4h23RxrpUYVCVZt/+x1Q0CIG3U6WGz +YaEyKoCtBHH9cAy76+pP/NU2f7/QuHU9Vymd -----END CERTIFICATE----- \ No newline at end of file diff --git a/s2a/src/test/resources/root_ec.cnf b/s2a/src/test/resources/root_ec.cnf index bee0b80a166..d736865c831 100644 --- a/s2a/src/test/resources/root_ec.cnf +++ b/s2a/src/test/resources/root_ec.cnf @@ -4,9 +4,9 @@ req_extensions = v3_req prompt = no [req_distinguished_name] -O = o -OU = ou -CN = cn +O = rootO +OU = rootOU +CN = rootCN [v3_req] keyUsage = critical, keyCertSign, cRLSign diff --git a/s2a/src/test/resources/root_key.pem b/s2a/src/test/resources/root_key.pem index aae992426d7..34d0ffa61eb 100644 --- a/s2a/src/test/resources/root_key.pem +++ b/s2a/src/test/resources/root_key.pem @@ -1,30 +1,30 @@ -----BEGIN ENCRYPTED PRIVATE KEY----- -MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQInmQVkXP3TFcCAggA -MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECGeCAVH1pefxBIIEyD3Nj1Dy19oy -fogU+z8YBLXuSCx8s3zncYPF9nYlegGSSo0ace/WxfPu8AEPus1P2MxlxfcCQ1A+ -5+vMihtEpgpTg9R4RlLAWs45jz4AduGiwqW05+W5zgDn6g7p7HIL0+M5FxKRkAW0 -KEH4Jy8Vc1XQxkhOm1Q4NLI8PT94rcBDE9Od03sdrW/hQgaOFz5AWOlT5jF1uUOz -glF1RQQxfJygTB6qlPTC3BAaiAnWij3NOg5L5vvUhjLa7iOZOhRQBRkf4YtHsM+2 -rFy8Z7MeHOvrqFf8LXosNy3JreQW036rLGR0Xh5myATkNrEwA8df37AgLUmwqyfz -hjZefPW77LgMAXlaN8s345AGikOX8yQKEFzPV/Nag32p6t4oiRRcUUfdB4wzKi6T -mzZ6lKcGR3qqL4V6lJSV3I2fmgkYZnUwymolyu+1+CVYDLuE53TBi5dRXwgOghi7 -npw7PqqQCian8yxHF9c1rYukD0ov0/y8ratjOu9XoJG2/wWQJNvDkAyc3mSJf+3y -6Wtu1qhLszU8pZOGW0fK6bGyHSp+wkoah/vRzB0+yFjvuMIG6py2ZDQeqhqS3ZV2 -nZHHjj0tZ45Wbdf4k17ujEK34pFXluPH//zADnd6ym2W0t6x+jtqR5tYu3poORQg -jFgpudkn2RUSq8N/gIiHDwblYBxU2dmyzEVudv1zNgVSHyetGLxsFoNB7Prn89rJ -u24a/xtuCyC2pshWo3KiL74hkkCsC8rLbEAAbADheb35b+Ca3JnMwgyUHbHL6Hqf -EiVIgm14lB/1uz651X58Boo6tDFkgrxEtGDUIZm8yk2n0tGflp7BtYbMCw+7gqhb -XN4hlhFDcCJm8peXcyCtGajOnBuNO9JJDNYor6QjptaIpQBFb7/0rc7kyO12BIUv -F9mrCHF18Hd/9AtUO93+tyDAnL64Jqq9tUv8dOVtIfbcHXZSYHf24l0XAiKByb8y -9NQLUZkIuF4aUZVHV8ZBDdHNqjzqVglKQlGHdw1XBexSal5pC9HvknOmWBgl0aza -flzeTRPX7TPrMJDE5lgSy58czGpvZzhFYwOp6cwpfjNsiqdzD78Zs0xsRbNg519s -d+cLmbiU3plWCoYCuDb68eZRRzT+o41+QJG2PoMCpzPw5wMLl6HuW7HXMRFpZKJc -tPKpeTIzb8hjhA+TwVIVpTPHvvQehtTUQD2mRujdvNM6PF8tnuC3F3sB3PTjeeJg -uzfEfs3BynRTIj/gX6y87gzwsrwWIEN6U0cCbQ6J1EcgdQCiH8vbhIgfd4DkLgLN -Kkif+fI/HgBOqaiwSw3sHmWgB6PllVQOKH6qAiejTHR/UUvJTPvgKJFLunmBiF12 -N1bRge1sSXE1eLKVdi+dP1j0o6RxhaRrbX7ie3y/wYHwCJnb8h08DEprgCqoswFs -SuNKmvlibBHAsnOdhyCTOd9I5n8XzAUUp6mT+C5WDfl7qfYvh6IHFlSrhZ9aS9b6 -RY873cnphKbqU5d7Cr8Ufx4b4SgS+hEnuP8y5IToLQ3BONGQH2lu7nmd89wjW0uo -IMRXybwf/5FnKhEy8Aw+pD6AxiXC3DZVTKl3SHmjkYBDvNElsJVgygVTKgbOa1Z+ -ovIK/D7QV7Nv3uVortH8XA== +MIIFJDBWBgkqhkiG9w0BBQ0wSTAxBgkqhkiG9w0BBQwwJAQQJXNe391O3gaNbKLw +o60XrQICCAAwDAYIKoZIhvcNAgkFADAUBggqhkiG9w0DBwQI4pf69+BBF8IEggTI +JuQ3p67U9k/NWMuYXaR9a6lv24YZ1qR6ieL5B6keCaCDVoQMb5V22O0vBqCVePgr +EG0yWIeeAsARMzAxE7Lnil6abSe7tij+LjEI9F7mV/1QSFt03PLVI+e7OcKNI+Nr +6vISEi8CaddekP8JDRhPMpgdWderZvogo3REpJ8GNIUddQzu1e3ZgDtOPquqcgqb +MH/HuPE3vjj4/l6ZpX+6DZKIvzjwtBQ4PMzSWLumzmYLItd3kz7UryN+9hKluSZp +D2KB24aUIQFbDxe2DMTi5c0QIiyzjwkv081ecNJOy2gYX3uiucr8/Ax3o21RNZtI +oKCmSPVEfYdrkdfkwuSOioVTbWBZBcSZo3L2bmCkSXTuheGurEw/TtQWXBgew0Bn +UQjEJgZy96PVsQeu3t+NRCacARQi4vfv7PVHlQW8fcfcC6CeNw7VIZ8aS7supqym +RJxzMY9ZnLwO9cgybXLYgosVZnvI7nOokJPfO1+KqBK01C1Sgc3tg8czKhRuztHu +qDO0GCZ7l+9/ku/WIy/5NiatNvRo5dMAOGxsSrjI9a7+EmenoIfd8/KREVX19D+R +gZRALVATHq83rF6BdsyTwya1QUr/J24EIlkOc4HbCBm5WxA2ZjNdDBZ+KhivYaS7 +l1qrbkFOhmBD9kYRbseBrxlzKUWJMGhOpw3xebut3HngLqyezLcjsXQuF3Iau5Hl +9QFcmSdLj2ZlNlQvmfNJX/r6a/K2LigruXCbvHWMqVsHd7XZdWJ/8wjm2AL97iON +mYFLP+ScfYom9qrF41jNkUKZiLk/ppvSHyWBAqbze+R9Zfpcf8ArCwuAL/JlEMzv +YkBv1DWKfzJpZHYX695MxrpS3C8m0IyXNxktBL3KTVvwZaIhSNBlNS3fdb9m8toR +Tz/LS8jseWpZ5D552/+KAa0Skhav3ZFpxmAS8BEyE/nI9Dwg9niYcZLWORWHAQPp +jraG0BkE7bn5No/k7E4rjFb+2N+36QxVacJI3neC8bQXVHP0BVUvrabOWFPnGivl +Ok91Eo8q5PUAsd15ZnKjTHzlD7zv7fF6ncBgj3P4L2Xrs6P34JOZEd4wixEUZYeC +Xe+SZrFyUr6CcNC45C6R3hDYqmrz0GK1ikkis3XcKT+C5flBYb9NRx8G9wyCuS6H +oHl0Rfbpc47wQTuajicMVO2El7syMPUAxjo3EfMzvjm7uCXLTHnXRnRt3Y5AkPGa +0kFE9Vm00PReRfQ7qbSUiOOHYa9NIsw1l2ZI+knP9XbY2HikELOpjgucrMxZF+ms +zit5YGD3NGZi5xcHZFZTs9L8kaJccXn5DtjA30eEiFzKqMtMKnwlrbSL55I1JXim +co1RLpRK2KQmtJHo1br3RH6jP7fePYzgDceDds5HKWz22pYFcVtlx4DeYH5vjdEp +i3yNQZ32jD2HYhgCK325QLP5S2UYmUOPWd4sEiwZMBPpPOlt0TqCdFKYgS2GHlSN +IYVBYelPUYsz9Kg0TFtLMZLNUmwsXJ+jqnLVtmFyoV6IIvbSCqQ9jxTbZQKxThK8 +A1G+nXBO41ZW8eQZUGx8CzbCj2JvtVThgErSRqAuYbvlUt7EI4Ac8veZC8rJIG0Q +ADkueb978o4OI6vpOdTYCmdTIoHWlpup -----END ENCRYPTED PRIVATE KEY----- \ No newline at end of file diff --git a/s2a/src/test/resources/root_key_ec.pem b/s2a/src/test/resources/root_key_ec.pem index ef5ee1445f9..5560a66d414 100644 --- a/s2a/src/test/resources/root_key_ec.pem +++ b/s2a/src/test/resources/root_key_ec.pem @@ -1,5 +1,5 @@ -----BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgd5oZmQBOtMF0xfc3 -uRuw5EDhA1thJKKeHfrij9FMkfahRANCAAQNU8irV2z8xfnYJf1hTVF0R4zz1g/i -jcE3w6I/Y7ipy5zXWThC7gyjhFZ4304mk0SVNAnQryf+si9ubDwAgyq5 +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgjfTyzPIlKV0zANQP +2s1C2FhbenE34QEsf83wjpuQrZWhRANCAAQadLaBW/oGzQa25QA55tH0TR+pqPfO +L2YP3sH92CIwsQ2G1U8lDN54+/oJxqn7vc7QEg1E06Xy2UdfRhpcN4Qc -----END PRIVATE KEY----- \ No newline at end of file diff --git a/s2a/src/test/resources/server.csr b/s2a/src/test/resources/server.csr deleted file mode 100644 index 1657b191133..00000000000 --- a/s2a/src/test/resources/server.csr +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIChzCCAW8CAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEAlPThqu8tfJ4hQKRiUw/vNPfo2L2LQU8NlrRL7rvV -71E345LGK1h/hM3MHp5VgEvaaIibb0hSNv/TYz3HVCQyNuPlcmkHZTJ9mB0icilU -rYWdM0LPIg46iThmIQVhMiNfpMKQLDLQ7o3Jktjm32OxnQdtYSV+7NFnw8/0pB4j -iaiBYfZIMeGzEJIOFG8GSNJG0pfCI71DyLRonIcb2XzfeDPHeWSF7lbIoMGAuKIE -2mXpwHmAjTMJzIShSgLqCvmbz7wR3ZeVMknXcgcqMmagGphy8SjizIWC5KRbrnRq -F22Ouxdat6scIevRXGp5nYawFYdpK9qo+82gEouVX3dtSQIDAQABoC4wLAYJKoZI -hvcNAQkOMR8wHTAbBgNVHREEFDAShxAAAAAAAAAAAAAAAAAAAAAAMA0GCSqGSIb3 -DQEBCwUAA4IBAQB2qU354OlNVunhZhiOFNwabovxLcgKoQz+GtJ2EzsMEza+NPvV -dttPxXzqL/U+gDghvGzSYGuh2yMfTTPO+XtZKpvMUmIWonN5jItbFwSTaWcoE8Qs -zFZokRuFJ9dy017u642mpdf6neUzjbfCjWs8+3jyFzWlkrMF3RlSTxPuksWjhXsX -dxxLNu8YWcsYRB3fODHqrlBNuDn+9kb9z8to+yq76MA0HtdDkjd/dfgghiTDJhqm -IcwhBXufwQUrOP4YiuiwM0mo7Xlhw65gnSmRcwR9ha98SV2zG5kiRYE+m+94bDbd -kGBRfhpQSzh1w09cVzmLgzkfxRShEB+bb9Ss ------END CERTIFICATE REQUEST----- \ No newline at end of file diff --git a/s2a/src/test/resources/server_cert.pem b/s2a/src/test/resources/server_cert.pem index 10a98cf5c21..909b83aa903 100644 --- a/s2a/src/test/resources/server_cert.pem +++ b/s2a/src/test/resources/server_cert.pem @@ -1,20 +1,20 @@ -----BEGIN CERTIFICATE----- -MIIDWjCCAkKgAwIBAgIUMZkgD5gtoa39H9jdI/ijVkyxC/swDQYJKoZIhvcNAQEL +MIIDWjCCAkKgAwIBAgIUAeWzyzIEetYf+ZWHj9NzH1JkLYkwDQYJKoZIhvcNAQEL BQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MB4X -DTIzMTAxNzIzMDg1M1oXDTQzMTAxNzIzMDg1M1owFDESMBAGA1UEAwwJbG9jYWxo -b3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlPThqu8tfJ4hQKRi -Uw/vNPfo2L2LQU8NlrRL7rvV71E345LGK1h/hM3MHp5VgEvaaIibb0hSNv/TYz3H -VCQyNuPlcmkHZTJ9mB0icilUrYWdM0LPIg46iThmIQVhMiNfpMKQLDLQ7o3Jktjm -32OxnQdtYSV+7NFnw8/0pB4jiaiBYfZIMeGzEJIOFG8GSNJG0pfCI71DyLRonIcb -2XzfeDPHeWSF7lbIoMGAuKIE2mXpwHmAjTMJzIShSgLqCvmbz7wR3ZeVMknXcgcq -MmagGphy8SjizIWC5KRbrnRqF22Ouxdat6scIevRXGp5nYawFYdpK9qo+82gEouV -X3dtSQIDAQABo18wXTAbBgNVHREEFDAShxAAAAAAAAAAAAAAAAAAAAAAMB0GA1Ud -DgQWBBTKJU+NK7Q6ZPccSigRCMBCBgjkaDAfBgNVHSMEGDAWgBQBkw0X0kAYdsDe -MY567ptYW9J1aTANBgkqhkiG9w0BAQsFAAOCAQEAXuCs6MGVoND8TaJ6qaDmqtpy -wKEW2hsGclI9yv5cMS0XCVTkmKYnIoijtqv6Pdh8PfhIx5oJqJC8Ml16w4Iou4+6 -kKF0DdzdQyiM0OlNCgLYPiR4rh0ZCAFFCvOsDum1g+b9JTFZGooK4TMd9thwms4D -SqpP5v1NWf/ZLH5TYnp2CkPzBxDlnMJZphuWtPHL+78TbgQuQaKu2nMLBGBJqtFi -HDOGxckgZuwBsy0c+aC/ZwaV7FdMP42kxUZduCEx8+BDSGwPoEpz6pwVIkjiyYAm -3O8FUeEPzYzwpkANIbbEIDWV6FVH9IahKRRkE+bL3BqoQkv8SMciEA5zWsPrbA== +DTI0MTAwMTIxNTk0NloXDTQ0MTAwMTIxNTk0NlowFDESMBAGA1UEAwwJbG9jYWxo +b3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1qnW7Pb06MgRNLzt +icv/ydl8W/lpPRjrJJb04/TtXbJ1hjnp7i796TfNGrJgHqEZnaR8q83lO0L38B2X +sJ04b3R+y+6HhH8+MbHejM7ybrTZRNQXip/Kxu4QLHBTQEsplycWLf42/R3cIk/X +vgxq5NsCsbk4xI4xwlcqC8FM1AHU0VrKxzHWVhZEM+/KovBAr/hRYln9CukeKjOf +UiVq58uuDAlJRC3yH2Rd/sqCDELvqRv17J6eYx2nJ3mSN5aBa0FwVjg6vr5Obddj +AWWIkgrlAr+a+OraxOrWElFfChBSvr/qHdJFWHeCdq/SAhow5uRhC69ScJf+7lrX +hsj1sQIDAQABo18wXTAbBgNVHREEFDAShxAAAAAAAAAAAAAAAAAAAAABMB0GA1Ud +DgQWBBRdDRg6GuDj8Sujmz4/rqfP0jZHbTAfBgNVHSMEGDAWgBRyrd23EBUDjXRh +bIzQHh77i6ZuLDANBgkqhkiG9w0BAQsFAAOCAQEAAEUS27+6p88CWYemMOY0iu0e +mp4YqG0XQSilbSnxrqnJb3N8pR3Yh6JJKnblQ6xdexfzrXlBA/v7nx+f8e9HS2QZ +KLtEIaEvNKL51JdOS6ebEzLVvhk98r2kpKM3wpT++/18HPlPK5W3rMQNsLOyAdvP +UX6TakhIfflRjz1DYXQ1ERvJOFw2HEmw6K6r2VwBhZKfwwzxmAHpVwniWXGbgyRF +79hG6rO1tv1K5LHAPIRs0h2Lh/VPxm2XiaNkdGyarUy5/NM+GoHErgxOBmYltn5Q +vAlZrgF2/mSXcUb7EHoXvoC9L4M7U/dRQD4Q1fQRJ/KjrhbDAC3gfZ4zorKoaQ== -----END CERTIFICATE----- \ No newline at end of file diff --git a/s2a/src/test/resources/server_key.pem b/s2a/src/test/resources/server_key.pem index 44f087dee94..edc37cb3855 100644 --- a/s2a/src/test/resources/server_key.pem +++ b/s2a/src/test/resources/server_key.pem @@ -1,28 +1,28 @@ -----BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCU9OGq7y18niFA -pGJTD+809+jYvYtBTw2WtEvuu9XvUTfjksYrWH+EzcwenlWAS9poiJtvSFI2/9Nj -PcdUJDI24+VyaQdlMn2YHSJyKVSthZ0zQs8iDjqJOGYhBWEyI1+kwpAsMtDujcmS -2ObfY7GdB21hJX7s0WfDz/SkHiOJqIFh9kgx4bMQkg4UbwZI0kbSl8IjvUPItGic -hxvZfN94M8d5ZIXuVsigwYC4ogTaZenAeYCNMwnMhKFKAuoK+ZvPvBHdl5UySddy -ByoyZqAamHLxKOLMhYLkpFuudGoXbY67F1q3qxwh69FcanmdhrAVh2kr2qj7zaAS -i5Vfd21JAgMBAAECggEACTBuN4hXywdKT92UP0GNZTwh/jT7QUUqNnDa+lhWI1Rk -WUK1vPjRrRSxEfZ8mdSUHbzHsf7JK6FungGyqUsuWdqHTh6SmTibLOYnONm54paK -kx38/0HXdJ2pF0Jos5ohDV3/XOqpnv3aQJfm7kMNMv3BTqvsf5mPiDHtCq7dTGGj -rGiLc0zirKZq79C6YSB1UMB01BsDl2ScflK8b3osT18uYx/BOdjLT4yZWQsU/nbB -OeF+ziWTTUAVjodGeTf+NYG7cFN/9N9PdSnAwuw8Nche3xZKbHTh2I578Zd4bsDX -H+hoMN862nzOXEvD6KyLB8xDdnEZ+p+njeDROJVmgQKBgQDQhzQEl/co1LYc5IDO -mynhCOtKJeRWBLhYEPIuaSY3qF+lrOWzqyOUNppWDx+HeKOq70X1Q+ETeSXtbaL1 -qHBkNcApQ2lStcpkR9whcVbr9NIWC8y8UQxyerEK3x3l0bZ99dfJ/z6lbxdS7prc -Hhxy6pUj8Q8AgpTZA8HfQUF1EQKBgQC23ek24kTVvWeWX2C/82H1Yfia6ITL7WHz -3aEJaZaO5JD3KmOSZgY88Ob3pkDTRYjFZND5zSB7PnM68gpo/OEDla6ZYtfwBWCX -q4QhFtv2obehobmDk+URVfvlOcBikoEP1i8oy7WdZ5CgC4gNKkkD15l68W+g5IIG -2ZOA97yUuQKBgDAzoI2TRxmUGciR9UhMy6Bt/F12ZtKPYsFQoXqi6aeh7wIP9kTS -wXWoLYLJGiOpekOv7X7lQujKbz7zweCBIAG5/wJKx9TLms4VYkgEt+/w9oMMFTZO -kc8Al14I9xNBp6p0In5Z1vRMupp79yX8e90AZpsZRLt8c8W6PZ1Kq0PRAoGBAKmD -7LzD46t/eJccs0M9CoG94Ac5pGCmHTdDLBTdnIO5vehhkwwTJ5U2e+T2aQFwY+kY -G+B1FrconQj3dk78nFoGV2Q5DJOjaHcwt7s0xZNLNj7O/HnMj3wSiP9lGcJGrP1R -P0ZCEIlph9fU2LnbiPPW2J/vT9uF+EMBTosvG9GBAoGAEVaDLLXOHj+oh1i6YY7s -0qokN2CdeKY4gG7iKjuDFb0r/l6R9uFvpUwJMhLEkF5SPQMyrzKFdnTpw3n/jnRa -AWG6GoV+D7LES+lHP5TXKKijbnHJdFjW8PtfDXHCJ6uGG91vH0TMMp1LqhcvGfTv -lcNGXkk6gUNSecxBC1uJfKE= +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDWqdbs9vToyBE0 +vO2Jy//J2Xxb+Wk9GOsklvTj9O1dsnWGOenuLv3pN80asmAeoRmdpHyrzeU7Qvfw +HZewnThvdH7L7oeEfz4xsd6MzvJutNlE1BeKn8rG7hAscFNASymXJxYt/jb9Hdwi +T9e+DGrk2wKxuTjEjjHCVyoLwUzUAdTRWsrHMdZWFkQz78qi8ECv+FFiWf0K6R4q +M59SJWrny64MCUlELfIfZF3+yoIMQu+pG/Xsnp5jHacneZI3loFrQXBWODq+vk5t +12MBZYiSCuUCv5r46trE6tYSUV8KEFK+v+od0kVYd4J2r9ICGjDm5GELr1Jwl/7u +WteGyPWxAgMBAAECggEAFEAgcOlZME6TZPS/ueSfRET6mNieB2/+2sxM3OZhsBmi +QZ/cBCa1uFcVx8N1Et6iwn7ebfy199G4/xNjmHs0dDs6rPVbHnI8hUag1oq9TxlL +d9VERUUOxZZ2uyJ7kBCnI0XCL2OQf29eMXRzx093lBBfIDH3e39ojUtYwZQiMcuw +EPry0k4fVhymhKg9Wnmt5lMg4Mdc1TpPfmNFuTR0PZ1nAaVQglvH66qNKGVoWEhZ +paNLaKC4H2Jfa1AfAWl6Efy5JDMOfHF0ww0cDUrTzAeQ7jEh0UGyL1lX8W6kKRDa +0quUqxOJz9aQ8cyd27s2OQMlRtbXi/jhhVp7WLIrWQKBgQD9gKG5CgBO/L8nIj5o +EhHFhtfjEhdeXTAlenmxoBxUN7Pwkc2OvhNef7+T0+euwl50ieopWLoRxLZ2yY8l +E2b2+7EM6/8/wgt1bCVh5NCWrE63tLCx+wdht1oqciDXvuv5bJTf73sipgDTYYSV +gE+DHXq96mxVJXo1TLtQQpXMVQKBgQDYx0AbO0KP2TTNY5ChqVwthaETHjWs6z9p +U5WRgNYeXbUKg3l7JJk6zq72ZIBeqEr3d9mJqrk6HFKTh4c+LyjKyLjmY5wkmfHh +s6s1lCEgEoXKT3Fa+DxlsXltyxrJLzuf1h276jeL5bB6BmJNKLODcEoCx/ubrwOj +prdUSWqf7QKBgQCO/sg7AJE7/QY2pPJe8hJkQbP1unbEG/zUp0mOEKrqNqGhyh0R +r9ZtL9J5KMc/pRRy2Hjl6c7LxxLF3tyIJXGnUEKG73iEFokwK1jK569hzsB4j8w8 +GUYIsMyDtO0hxeiGQeGYkBX9bXZ5xkBrtH0lkLNz/ZAuV32gIzBmDalCIQKBgDGT +f+m6Z8KWHilKt+0A2n/eq7O/mO7u7hWcc/xOxqkzLRA2eTXcbN6yHfljiqgbPOnT +kwCU9r9/crMir59dEasutHqcFT2Zp2PCv0kFk33OPqLCAF6ZntZy/B5L8NhJ4Qzw +3uP28LUh1nZRt3GF+Wf56jMwoS49nEt0+UBhee0RAoGAS9YsJkbjBg2p3Gxvo5c0 +IjfZdcyS2ndTjXv+hFvkjMw0ULFT3dqpk+0asaCh5nrDUbVQyan+D8LgwSwNZy89 +e99bl//oliv/Om7lVFCKtBOhe+fIWHlrR0e2bemsQi/pgTURjYFuvjhR50dcKx96 +jLHvG4mTfStHaJ1gKGWvgWA= -----END PRIVATE KEY----- \ No newline at end of file From 959060a824945c835fde434fcfe3a9789e3f81e5 Mon Sep 17 00:00:00 2001 From: Riya Mehta <55350838+rmehta19@users.noreply.github.com> Date: Wed, 2 Oct 2024 16:01:23 -0700 Subject: [PATCH 099/103] s2a: Address comments on S2A channel + stub (#11584) * delete HandshakerServiceChannel. * remove usage of S2AGrpcChannelPool + avoid creating Channel ref per conn. --- .../s2a/internal/channel/S2AChannelPool.java | 43 ------ .../internal/channel/S2AGrpcChannelPool.java | 109 --------------- .../channel/S2AHandshakerServiceChannel.java | 72 +++------- .../S2AProtocolNegotiatorFactory.java | 31 +++-- .../channel/S2AGrpcChannelPoolTest.java | 125 ------------------ .../S2AHandshakerServiceChannelTest.java | 125 +----------------- .../S2AProtocolNegotiatorFactoryTest.java | 41 +----- .../s2a/internal/handshaker/S2AStubTest.java | 15 +-- 8 files changed, 44 insertions(+), 517 deletions(-) delete mode 100644 s2a/src/main/java/io/grpc/s2a/internal/channel/S2AChannelPool.java delete mode 100644 s2a/src/main/java/io/grpc/s2a/internal/channel/S2AGrpcChannelPool.java delete mode 100644 s2a/src/test/java/io/grpc/s2a/internal/channel/S2AGrpcChannelPoolTest.java diff --git a/s2a/src/main/java/io/grpc/s2a/internal/channel/S2AChannelPool.java b/s2a/src/main/java/io/grpc/s2a/internal/channel/S2AChannelPool.java deleted file mode 100644 index aaaa0fffd53..00000000000 --- a/s2a/src/main/java/io/grpc/s2a/internal/channel/S2AChannelPool.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2024 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. - */ - -package io.grpc.s2a.internal.channel; - -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import io.grpc.Channel; -import javax.annotation.concurrent.ThreadSafe; - -/** Manages a channel pool to be used for communication with the S2A. */ -@ThreadSafe -public interface S2AChannelPool extends AutoCloseable { - /** - * Retrieves an open channel to the S2A from the channel pool. - * - * @throws IllegalStateException if no channel is available. - */ - @CanIgnoreReturnValue - Channel getChannel(); - - /** Returns a channel to the channel pool. */ - void returnToPool(Channel channel); - - /** - * Returns all channels to the channel pool and closes the pool so that no new channels can be - * retrieved from the pool. - */ - @Override - void close(); -} \ No newline at end of file diff --git a/s2a/src/main/java/io/grpc/s2a/internal/channel/S2AGrpcChannelPool.java b/s2a/src/main/java/io/grpc/s2a/internal/channel/S2AGrpcChannelPool.java deleted file mode 100644 index af911185e6c..00000000000 --- a/s2a/src/main/java/io/grpc/s2a/internal/channel/S2AGrpcChannelPool.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2024 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. - */ - -package io.grpc.s2a.internal.channel; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; - -import com.google.errorprone.annotations.concurrent.GuardedBy; -import io.grpc.Channel; -import io.grpc.internal.ObjectPool; -import javax.annotation.concurrent.ThreadSafe; -import org.checkerframework.checker.nullness.qual.Nullable; - -/** - * Manages a gRPC channel pool and a cached gRPC channel to be used for communication with the S2A. - */ -@ThreadSafe -public final class S2AGrpcChannelPool implements S2AChannelPool { - private static final int MAX_NUMBER_USERS_OF_CACHED_CHANNEL = 100000; - private final ObjectPool channelPool; - - @GuardedBy("this") - private @Nullable Channel cachedChannel; - - @GuardedBy("this") - private int numberOfUsersOfCachedChannel = 0; - - private enum State { - OPEN, - CLOSED, - } - - ; - - @GuardedBy("this") - private State state = State.OPEN; - - public static S2AChannelPool create(ObjectPool channelPool) { - checkNotNull(channelPool, "Channel pool should not be null."); - return new S2AGrpcChannelPool(channelPool); - } - - private S2AGrpcChannelPool(ObjectPool channelPool) { - this.channelPool = channelPool; - } - - /** - * Retrieves a channel from {@code channelPool} if {@code channel} is null, and returns {@code - * channel} otherwise. - * - * @return a {@link Channel} obtained from the channel pool. - */ - @Override - public synchronized Channel getChannel() { - checkState(state.equals(State.OPEN), "Channel pool is not open."); - checkState( - numberOfUsersOfCachedChannel < MAX_NUMBER_USERS_OF_CACHED_CHANNEL, - "Max number of channels have been retrieved from the channel pool."); - if (cachedChannel == null) { - cachedChannel = channelPool.getObject(); - } - numberOfUsersOfCachedChannel += 1; - return cachedChannel; - } - - /** - * Returns {@code channel} to {@code channelPool}. - * - *

The caller must ensure that {@code channel} was retrieved from this channel pool. - */ - @Override - public synchronized void returnToPool(Channel channel) { - checkState(state.equals(State.OPEN), "Channel pool is not open."); - checkArgument( - cachedChannel != null && numberOfUsersOfCachedChannel > 0 && cachedChannel.equals(channel), - "Cannot return the channel to channel pool because the channel was not obtained from" - + " channel pool."); - numberOfUsersOfCachedChannel -= 1; - if (numberOfUsersOfCachedChannel == 0) { - channelPool.returnObject(channel); - cachedChannel = null; - } - } - - @Override - public synchronized void close() { - state = State.CLOSED; - numberOfUsersOfCachedChannel = 0; - if (cachedChannel != null) { - channelPool.returnObject(cachedChannel); - cachedChannel = null; - } - } -} \ No newline at end of file diff --git a/s2a/src/main/java/io/grpc/s2a/internal/channel/S2AHandshakerServiceChannel.java b/s2a/src/main/java/io/grpc/s2a/internal/channel/S2AHandshakerServiceChannel.java index 7a2b3e70672..b1ba88d1886 100644 --- a/s2a/src/main/java/io/grpc/s2a/internal/channel/S2AHandshakerServiceChannel.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/channel/S2AHandshakerServiceChannel.java @@ -19,13 +19,9 @@ import static com.google.common.base.Preconditions.checkNotNull; import static java.util.concurrent.TimeUnit.SECONDS; -import com.google.common.annotations.VisibleForTesting; -import io.grpc.CallOptions; import io.grpc.Channel; import io.grpc.ChannelCredentials; -import io.grpc.ClientCall; import io.grpc.ManagedChannel; -import io.grpc.MethodDescriptor; import io.grpc.internal.SharedResourceHolder.Resource; import io.grpc.netty.NettyChannelBuilder; import java.time.Duration; @@ -55,6 +51,8 @@ @ThreadSafe public final class S2AHandshakerServiceChannel { private static final Duration CHANNEL_SHUTDOWN_TIMEOUT = Duration.ofSeconds(10); + private static final Logger logger = + Logger.getLogger(S2AHandshakerServiceChannel.class.getName()); /** * Returns a {@link SharedResourceHolder.Resource} instance for managing channels to an S2A server @@ -86,75 +84,35 @@ public ChannelResource(String targetAddress, ChannelCredentials channelCredentia } /** - * Creates a {@code HandshakerServiceChannel} instance to the service running at {@code + * Creates a {@code ManagedChannel} instance to the service running at {@code * targetAddress}. */ @Override public Channel create() { - ManagedChannel channel = - NettyChannelBuilder.forTarget(targetAddress, channelCredentials) + return NettyChannelBuilder.forTarget(targetAddress, channelCredentials) .directExecutor() + .idleTimeout(5, SECONDS) .build(); - return HandshakerServiceChannel.create(channel); } - /** Destroys a {@code HandshakerServiceChannel} instance. */ + /** Destroys a {@code ManagedChannel} instance. */ @Override public void close(Channel instanceChannel) { checkNotNull(instanceChannel); - HandshakerServiceChannel channel = (HandshakerServiceChannel) instanceChannel; - channel.close(); - } - - @Override - public String toString() { - return "grpc-s2a-channel"; - } - } - - /** - * Manages a channel using a {@link ManagedChannel} instance. - */ - @VisibleForTesting - static class HandshakerServiceChannel extends Channel { - private static final Logger logger = - Logger.getLogger(S2AHandshakerServiceChannel.class.getName()); - private final ManagedChannel delegate; - - static HandshakerServiceChannel create(ManagedChannel delegate) { - checkNotNull(delegate); - return new HandshakerServiceChannel(delegate); - } - - private HandshakerServiceChannel(ManagedChannel delegate) { - this.delegate = delegate; - } - - /** - * Returns the address of the service to which the {@code delegate} channel connects, which is - * typically of the form {@code host:port}. - */ - @Override - public String authority() { - return delegate.authority(); - } - - /** Creates a {@link ClientCall} that invokes the operations in {@link MethodDescriptor}. */ - @Override - public ClientCall newCall( - MethodDescriptor methodDescriptor, CallOptions options) { - return delegate.newCall(methodDescriptor, options); - } - - @SuppressWarnings("FutureReturnValueIgnored") - public void close() { - delegate.shutdownNow(); + ManagedChannel channel = (ManagedChannel) instanceChannel; + channel.shutdownNow(); try { - delegate.awaitTermination(CHANNEL_SHUTDOWN_TIMEOUT.getSeconds(), SECONDS); + channel.awaitTermination(CHANNEL_SHUTDOWN_TIMEOUT.getSeconds(), SECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); logger.log(Level.WARNING, "Channel to S2A was not shutdown."); } + + } + + @Override + public String toString() { + return "grpc-s2a-channel"; } } diff --git a/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AProtocolNegotiatorFactory.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AProtocolNegotiatorFactory.java index cb02d49ce9e..188faf63435 100644 --- a/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AProtocolNegotiatorFactory.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AProtocolNegotiatorFactory.java @@ -37,8 +37,6 @@ import io.grpc.netty.InternalProtocolNegotiator.ProtocolNegotiator; import io.grpc.netty.InternalProtocolNegotiators; import io.grpc.netty.InternalProtocolNegotiators.ProtocolNegotiationHandler; -import io.grpc.s2a.internal.channel.S2AChannelPool; -import io.grpc.s2a.internal.channel.S2AGrpcChannelPool; import io.grpc.s2a.internal.handshaker.S2AIdentity; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerAdapter; @@ -70,17 +68,16 @@ public final class S2AProtocolNegotiatorFactory { public static InternalProtocolNegotiator.ClientFactory createClientFactory( @Nullable S2AIdentity localIdentity, ObjectPool s2aChannelPool) { checkNotNull(s2aChannelPool, "S2A channel pool should not be null."); - S2AChannelPool channelPool = S2AGrpcChannelPool.create(s2aChannelPool); - return new S2AClientProtocolNegotiatorFactory(localIdentity, channelPool); + return new S2AClientProtocolNegotiatorFactory(localIdentity, s2aChannelPool); } static final class S2AClientProtocolNegotiatorFactory implements InternalProtocolNegotiator.ClientFactory { private final @Nullable S2AIdentity localIdentity; - private final S2AChannelPool channelPool; + private final ObjectPool channelPool; S2AClientProtocolNegotiatorFactory( - @Nullable S2AIdentity localIdentity, S2AChannelPool channelPool) { + @Nullable S2AIdentity localIdentity, ObjectPool channelPool) { this.localIdentity = localIdentity; this.channelPool = channelPool; } @@ -100,13 +97,14 @@ public int getDefaultPort() { @VisibleForTesting static final class S2AProtocolNegotiator implements ProtocolNegotiator { - private final S2AChannelPool channelPool; + private final ObjectPool channelPool; + private final Channel channel; private final Optional localIdentity; private final ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(1)); static S2AProtocolNegotiator createForClient( - S2AChannelPool channelPool, @Nullable S2AIdentity localIdentity) { + ObjectPool channelPool, @Nullable S2AIdentity localIdentity) { checkNotNull(channelPool, "Channel pool should not be null."); if (localIdentity == null) { return new S2AProtocolNegotiator(channelPool, Optional.empty()); @@ -123,9 +121,11 @@ static S2AProtocolNegotiator createForClient( return HostAndPort.fromString(authority).getHost(); } - private S2AProtocolNegotiator(S2AChannelPool channelPool, Optional localIdentity) { + private S2AProtocolNegotiator(ObjectPool channelPool, + Optional localIdentity) { this.channelPool = channelPool; this.localIdentity = localIdentity; + this.channel = channelPool.getObject(); } @Override @@ -139,13 +139,13 @@ public ChannelHandler newHandler(GrpcHttp2ConnectionHandler grpcHandler) { String hostname = getHostNameFromAuthority(grpcHandler.getAuthority()); checkArgument(!isNullOrEmpty(hostname), "hostname should not be null or empty."); return new S2AProtocolNegotiationHandler( - grpcHandler, channelPool, localIdentity, hostname, service); + grpcHandler, channel, localIdentity, hostname, service); } @Override public void close() { service.shutdown(); - channelPool.close(); + channelPool.returnObject(channel); } } @@ -180,7 +180,7 @@ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { } private static final class S2AProtocolNegotiationHandler extends ProtocolNegotiationHandler { - private final S2AChannelPool channelPool; + private final Channel channel; private final Optional localIdentity; private final String hostname; private final GrpcHttp2ConnectionHandler grpcHandler; @@ -188,7 +188,7 @@ private static final class S2AProtocolNegotiationHandler extends ProtocolNegotia private S2AProtocolNegotiationHandler( GrpcHttp2ConnectionHandler grpcHandler, - S2AChannelPool channelPool, + Channel channel, Optional localIdentity, String hostname, ListeningExecutorService service) { @@ -204,7 +204,7 @@ public void handlerAdded(ChannelHandlerContext ctx) { }, grpcHandler.getNegotiationLogger()); this.grpcHandler = grpcHandler; - this.channelPool = channelPool; + this.channel = channel; this.localIdentity = localIdentity; this.hostname = hostname; checkNotNull(service, "service should not be null."); @@ -217,8 +217,7 @@ protected void handlerAdded0(ChannelHandlerContext ctx) { BufferReadsHandler bufferReads = new BufferReadsHandler(); ctx.pipeline().addBefore(ctx.name(), /* name= */ null, bufferReads); - Channel ch = channelPool.getChannel(); - S2AServiceGrpc.S2AServiceStub stub = S2AServiceGrpc.newStub(ch); + S2AServiceGrpc.S2AServiceStub stub = S2AServiceGrpc.newStub(channel); S2AStub s2aStub = S2AStub.newInstance(stub); ListenableFuture sslContextFuture = diff --git a/s2a/src/test/java/io/grpc/s2a/internal/channel/S2AGrpcChannelPoolTest.java b/s2a/src/test/java/io/grpc/s2a/internal/channel/S2AGrpcChannelPoolTest.java deleted file mode 100644 index afae456abb1..00000000000 --- a/s2a/src/test/java/io/grpc/s2a/internal/channel/S2AGrpcChannelPoolTest.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2024 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. - */ - -package io.grpc.s2a.internal.channel; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertThrows; -import static org.mockito.Mockito.mock; - -import io.grpc.Channel; -import io.grpc.internal.ObjectPool; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -/** Unit tests for {@link S2AGrpcChannelPool}. */ -@RunWith(JUnit4.class) -public final class S2AGrpcChannelPoolTest { - @Test - public void getChannel_success() throws Exception { - FakeChannelPool fakeChannelPool = new FakeChannelPool(); - S2AChannelPool s2aChannelPool = S2AGrpcChannelPool.create(fakeChannelPool); - - Channel channel = s2aChannelPool.getChannel(); - - assertThat(channel).isNotNull(); - assertThat(fakeChannelPool.isChannelCached()).isTrue(); - assertThat(s2aChannelPool.getChannel()).isEqualTo(channel); - } - - @Test - public void returnToPool_success() throws Exception { - FakeChannelPool fakeChannelPool = new FakeChannelPool(); - S2AChannelPool s2aChannelPool = S2AGrpcChannelPool.create(fakeChannelPool); - - s2aChannelPool.returnToPool(s2aChannelPool.getChannel()); - - assertThat(fakeChannelPool.isChannelCached()).isFalse(); - } - - @Test - public void returnToPool_channelStillCachedBecauseMultipleChannelsRetrieved() throws Exception { - FakeChannelPool fakeChannelPool = new FakeChannelPool(); - S2AChannelPool s2aChannelPool = S2AGrpcChannelPool.create(fakeChannelPool); - - s2aChannelPool.getChannel(); - s2aChannelPool.returnToPool(s2aChannelPool.getChannel()); - - assertThat(fakeChannelPool.isChannelCached()).isTrue(); - } - - @Test - public void returnToPool_failureBecauseChannelWasNotFromPool() throws Exception { - S2AChannelPool s2aChannelPool = S2AGrpcChannelPool.create(new FakeChannelPool()); - - IllegalArgumentException expected = - assertThrows( - IllegalArgumentException.class, - () -> s2aChannelPool.returnToPool(mock(Channel.class))); - assertThat(expected) - .hasMessageThat() - .isEqualTo( - "Cannot return the channel to channel pool because the channel was not obtained from" - + " channel pool."); - } - - @Test - public void close_success() throws Exception { - FakeChannelPool fakeChannelPool = new FakeChannelPool(); - try (S2AChannelPool s2aChannelPool = S2AGrpcChannelPool.create(fakeChannelPool)) { - s2aChannelPool.getChannel(); - } - - assertThat(fakeChannelPool.isChannelCached()).isFalse(); - } - - @Test - public void close_poolIsUnusable() throws Exception { - S2AChannelPool s2aChannelPool = S2AGrpcChannelPool.create(new FakeChannelPool()); - s2aChannelPool.close(); - - IllegalStateException expected = - assertThrows(IllegalStateException.class, s2aChannelPool::getChannel); - - assertThat(expected).hasMessageThat().isEqualTo("Channel pool is not open."); - } - - private static class FakeChannelPool implements ObjectPool { - private final Channel mockChannel = mock(Channel.class); - private @Nullable Channel cachedChannel = null; - - @Override - public Channel getObject() { - if (cachedChannel == null) { - cachedChannel = mockChannel; - } - return cachedChannel; - } - - @Override - public Channel returnObject(Object object) { - assertThat(object).isSameInstanceAs(mockChannel); - cachedChannel = null; - return null; - } - - public boolean isChannelCached() { - return (cachedChannel != null); - } - } -} \ No newline at end of file diff --git a/s2a/src/test/java/io/grpc/s2a/internal/channel/S2AHandshakerServiceChannelTest.java b/s2a/src/test/java/io/grpc/s2a/internal/channel/S2AHandshakerServiceChannelTest.java index 9e09d10f7da..16409721ff5 100644 --- a/s2a/src/test/java/io/grpc/s2a/internal/channel/S2AHandshakerServiceChannelTest.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/channel/S2AHandshakerServiceChannelTest.java @@ -20,13 +20,10 @@ import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; import static org.junit.Assert.assertThrows; -import io.grpc.CallOptions; import io.grpc.Channel; import io.grpc.ChannelCredentials; -import io.grpc.ClientCall; import io.grpc.InsecureChannelCredentials; import io.grpc.ManagedChannel; -import io.grpc.MethodDescriptor; import io.grpc.Server; import io.grpc.ServerBuilder; import io.grpc.ServerCredentials; @@ -36,14 +33,12 @@ import io.grpc.benchmarks.Utils; import io.grpc.internal.SharedResourceHolder.Resource; import io.grpc.netty.NettyServerBuilder; -import io.grpc.s2a.internal.channel.S2AHandshakerServiceChannel.HandshakerServiceChannel; import io.grpc.stub.StreamObserver; import io.grpc.testing.GrpcCleanupRule; import io.grpc.testing.protobuf.SimpleRequest; import io.grpc.testing.protobuf.SimpleResponse; import io.grpc.testing.protobuf.SimpleServiceGrpc; import java.io.File; -import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; @@ -183,72 +178,7 @@ public void close_mtlsSuccess() throws Exception { } /** - * Verifies that an {@code HandshakerServiceChannel}'s {@code newCall} method can be used to - * perform a simple RPC. - */ - @Test - public void newCall_performSimpleRpcSuccess() { - Resource resource = - S2AHandshakerServiceChannel.getChannelResource( - "localhost:" + plaintextServer.getPort(), - InsecureChannelCredentials.create()); - Channel channel = resource.create(); - assertThat(channel).isInstanceOf(HandshakerServiceChannel.class); - assertThat( - SimpleServiceGrpc.newBlockingStub(channel).unaryRpc(SimpleRequest.getDefaultInstance())) - .isEqualToDefaultInstance(); - } - - /** Same as newCall_performSimpleRpcSuccess, but use mTLS. */ - @Test - public void newCall_mtlsPerformSimpleRpcSuccess() throws Exception { - Resource resource = - S2AHandshakerServiceChannel.getChannelResource( - "localhost:" + mtlsServer.getPort(), getTlsChannelCredentials()); - Channel channel = resource.create(); - assertThat(channel).isInstanceOf(HandshakerServiceChannel.class); - assertThat( - SimpleServiceGrpc.newBlockingStub(channel).unaryRpc(SimpleRequest.getDefaultInstance())) - .isEqualToDefaultInstance(); - } - - /** Creates a {@code HandshakerServiceChannel} instance and verifies its authority. */ - @Test - public void authority_success() throws Exception { - ManagedChannel channel = new FakeManagedChannel(true); - HandshakerServiceChannel eventLoopHoldingChannel = - HandshakerServiceChannel.create(channel); - assertThat(eventLoopHoldingChannel.authority()).isEqualTo("FakeManagedChannel"); - } - - /** - * Creates and closes a {@code HandshakerServiceChannel} when its {@code ManagedChannel} - * terminates successfully. - */ - @Test - public void close_withDelegateTerminatedSuccess() throws Exception { - ManagedChannel channel = new FakeManagedChannel(true); - HandshakerServiceChannel eventLoopHoldingChannel = - HandshakerServiceChannel.create(channel); - eventLoopHoldingChannel.close(); - assertThat(channel.isShutdown()).isTrue(); - } - - /** - * Creates and closes a {@code HandshakerServiceChannel} when its {@code ManagedChannel} does not - * terminate successfully. - */ - @Test - public void close_withDelegateTerminatedFailure() throws Exception { - ManagedChannel channel = new FakeManagedChannel(false); - HandshakerServiceChannel eventLoopHoldingChannel = - HandshakerServiceChannel.create(channel); - eventLoopHoldingChannel.close(); - assertThat(channel.isShutdown()).isTrue(); - } - - /** - * Creates and closes a {@code HandshakerServiceChannel}, creates a new channel from the same + * Creates and closes a {@code ManagedChannel}, creates a new channel from the same * resource, and verifies that this second channel is useable. */ @Test @@ -261,7 +191,7 @@ public void create_succeedsAfterCloseIsCalledOnce() throws Exception { resource.close(channelOne); Channel channelTwo = resource.create(); - assertThat(channelTwo).isInstanceOf(HandshakerServiceChannel.class); + assertThat(channelTwo).isInstanceOf(ManagedChannel.class); assertThat( SimpleServiceGrpc.newBlockingStub(channelTwo) .unaryRpc(SimpleRequest.getDefaultInstance())) @@ -279,7 +209,7 @@ public void create_mtlsSucceedsAfterCloseIsCalledOnce() throws Exception { resource.close(channelOne); Channel channelTwo = resource.create(); - assertThat(channelTwo).isInstanceOf(HandshakerServiceChannel.class); + assertThat(channelTwo).isInstanceOf(ManagedChannel.class); assertThat( SimpleServiceGrpc.newBlockingStub(channelTwo) .unaryRpc(SimpleRequest.getDefaultInstance())) @@ -325,53 +255,4 @@ public void unaryRpc(SimpleRequest request, StreamObserver strea streamObserver.onCompleted(); } } - - private static class FakeManagedChannel extends ManagedChannel { - private final boolean isDelegateTerminatedSuccess; - private boolean isShutdown = false; - - FakeManagedChannel(boolean isDelegateTerminatedSuccess) { - this.isDelegateTerminatedSuccess = isDelegateTerminatedSuccess; - } - - @Override - public String authority() { - return "FakeManagedChannel"; - } - - @Override - public ClientCall newCall( - MethodDescriptor methodDescriptor, CallOptions options) { - throw new UnsupportedOperationException("This method should not be called."); - } - - @Override - public ManagedChannel shutdown() { - throw new UnsupportedOperationException("This method should not be called."); - } - - @Override - public boolean isShutdown() { - return isShutdown; - } - - @Override - public boolean isTerminated() { - throw new UnsupportedOperationException("This method should not be called."); - } - - @Override - public ManagedChannel shutdownNow() { - isShutdown = true; - return null; - } - - @Override - public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { - if (isDelegateTerminatedSuccess) { - return true; - } - throw new InterruptedException("Await termination was interrupted."); - } - } } diff --git a/s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2AProtocolNegotiatorFactoryTest.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2AProtocolNegotiatorFactoryTest.java index d469b07df0f..48c512c4e5c 100644 --- a/s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2AProtocolNegotiatorFactoryTest.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2AProtocolNegotiatorFactoryTest.java @@ -23,9 +23,7 @@ import com.google.common.testing.NullPointerTester; import com.google.common.testing.NullPointerTester.Visibility; import io.grpc.Channel; -import io.grpc.Grpc; import io.grpc.InsecureChannelCredentials; -import io.grpc.ManagedChannel; import io.grpc.Server; import io.grpc.ServerBuilder; import io.grpc.benchmarks.Utils; @@ -35,8 +33,6 @@ import io.grpc.netty.GrpcHttp2ConnectionHandler; import io.grpc.netty.InternalProtocolNegotiator; import io.grpc.netty.InternalProtocolNegotiator.ProtocolNegotiator; -import io.grpc.s2a.internal.channel.S2AChannelPool; -import io.grpc.s2a.internal.channel.S2AGrpcChannelPool; import io.grpc.s2a.internal.channel.S2AHandshakerServiceChannel; import io.grpc.s2a.internal.handshaker.S2AIdentity; import io.grpc.s2a.internal.handshaker.S2AProtocolNegotiatorFactory.S2AProtocolNegotiator; @@ -52,8 +48,6 @@ import io.netty.util.AsciiString; import java.io.IOException; import java.util.Optional; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.After; import org.junit.Before; @@ -112,15 +106,14 @@ public void createProtocolNegotiatorFactory_nullArgument() throws Exception { @Test public void createProtocolNegotiator_nullArgument() throws Exception { - S2AChannelPool pool = - S2AGrpcChannelPool.create( + ObjectPool pool = SharedResourcePool.forResource( S2AHandshakerServiceChannel.getChannelResource( - "localhost:8080", InsecureChannelCredentials.create()))); + "localhost:8080", InsecureChannelCredentials.create())); NullPointerTester tester = new NullPointerTester() - .setDefault(S2AChannelPool.class, pool) + .setDefault(ObjectPool.class, pool) .setDefault(Optional.class, Optional.empty()); tester.testStaticMethods(S2AProtocolNegotiator.class, Visibility.PACKAGE); @@ -175,15 +168,9 @@ public void closeProtocolNegotiator_verifyProtocolNegotiatorIsClosedOnClientSide @Test public void createChannelHandler_addHandlerToMockContext() throws Exception { - ExecutorService executor = Executors.newSingleThreadExecutor(); - ManagedChannel channel = - Grpc.newChannelBuilder(authority, InsecureChannelCredentials.create()) - .executor(executor) - .build(); - FakeS2AChannelPool fakeChannelPool = new FakeS2AChannelPool(channel); ProtocolNegotiator clientNegotiator = S2AProtocolNegotiatorFactory.S2AProtocolNegotiator.createForClient( - fakeChannelPool, LOCAL_IDENTITY); + channelPool, LOCAL_IDENTITY); ChannelHandler channelHandler = clientNegotiator.newHandler(fakeConnectionHandler); @@ -191,26 +178,6 @@ public void createChannelHandler_addHandlerToMockContext() throws Exception { verify(mockChannelHandlerContext).fireUserEventTriggered("event"); } - /** A {@link S2AChannelPool} that returns the given channel. */ - private static class FakeS2AChannelPool implements S2AChannelPool { - private final Channel channel; - - FakeS2AChannelPool(Channel channel) { - this.channel = channel; - } - - @Override - public Channel getChannel() { - return channel; - } - - @Override - public void returnToPool(Channel channel) {} - - @Override - public void close() {} - } - /** A {@code GrpcHttp2ConnectionHandler} that does nothing. */ private static class FakeConnectionHandler extends GrpcHttp2ConnectionHandler { private static final Http2ConnectionDecoder DECODER = mock(Http2ConnectionDecoder.class); diff --git a/s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2AStubTest.java b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2AStubTest.java index 2e3bfc02879..bf99ef3f944 100644 --- a/s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2AStubTest.java +++ b/s2a/src/test/java/io/grpc/s2a/internal/handshaker/S2AStubTest.java @@ -21,10 +21,10 @@ import static org.junit.Assert.assertThrows; import com.google.common.truth.Expect; +import io.grpc.Channel; import io.grpc.InsecureChannelCredentials; +import io.grpc.internal.ObjectPool; import io.grpc.internal.SharedResourcePool; -import io.grpc.s2a.internal.channel.S2AChannelPool; -import io.grpc.s2a.internal.channel.S2AGrpcChannelPool; import io.grpc.s2a.internal.channel.S2AHandshakerServiceChannel; import io.grpc.stub.StreamObserver; import java.io.IOException; @@ -53,12 +53,11 @@ public void setUp() { @Test public void send_receiveOkStatus() throws Exception { - S2AChannelPool channelPool = - S2AGrpcChannelPool.create( - SharedResourcePool.forResource( - S2AHandshakerServiceChannel.getChannelResource( - S2A_ADDRESS, InsecureChannelCredentials.create()))); - S2AServiceGrpc.S2AServiceStub serviceStub = S2AServiceGrpc.newStub(channelPool.getChannel()); + ObjectPool channelPool = + SharedResourcePool.forResource( + S2AHandshakerServiceChannel.getChannelResource( + S2A_ADDRESS, InsecureChannelCredentials.create())); + S2AServiceGrpc.S2AServiceStub serviceStub = S2AServiceGrpc.newStub(channelPool.getObject()); S2AStub newStub = S2AStub.newInstance(serviceStub); IOException expected = From 6f3542297c2fb46a38cab65d510fc2ce76200045 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Fri, 27 Sep 2024 07:06:17 -0700 Subject: [PATCH 100/103] okhttp: Don't warn about missing Conscrypt When running on the JDK, it is quite normal for Conscrypt not to be present. We'll end up using the JDK 9 ALPN API and everything will be fine. On Android, it would be extremely rare for someone to completely remove the default Android security providers, so the warning was almost never going to trigger on that platform anyway. --- .../okhttp/main/java/io/grpc/okhttp/internal/Platform.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Platform.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Platform.java index 6ed3bc50b81..29ea8055b26 100644 --- a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Platform.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Platform.java @@ -283,7 +283,7 @@ private static boolean isAtLeastAndroid41() { /** * Select the first recognized security provider according to the preference order returned by - * {@link Security#getProviders}. If a recognized provider is not found then warn but continue. + * {@link Security#getProviders}. */ private static Provider getAndroidSecurityProvider() { Provider[] providers = Security.getProviders(); @@ -295,7 +295,6 @@ private static Provider getAndroidSecurityProvider() { } } } - logger.log(Level.WARNING, "Unable to find Conscrypt"); return null; } From 9bb06af9633ea7e0cd4e90eadb037abaa423f2fd Mon Sep 17 00:00:00 2001 From: Larry Safran Date: Wed, 2 Oct 2024 17:03:47 -0700 Subject: [PATCH 101/103] Change PickFirstLeafLoadBalancer to only have 1 subchannel at a time (#11520) * Change PickFirstLeafLoadBalancer to only have 1 subchannel at a time if environment variable GRPC_SERIALIZE_RETRIES == true. Cache serializingRetries value so that it doesn't have to look up the flag every time. Clear the correct task when READY in processSubchannelState and move the logic to cancelScheduledTasks Cleanup based on PR review remove unneeded checks for shutdown. * Fix previously broken tests * Shutdown previous subchannel when run off end of index. * Provide option to disable subchannel retries to let PFLeafLB take control of retries. * InternalSubchannel internally goes to IDLE when sees TF when reconnect is disabled. Remove an extra index.increment in LeafLB --- api/src/main/java/io/grpc/LoadBalancer.java | 6 + .../io/grpc/internal/InternalSubchannel.java | 30 ++-- .../io/grpc/internal/ManagedChannelImpl.java | 4 +- .../internal/PickFirstLeafLoadBalancer.java | 96 +++++++++++-- .../grpc/internal/InternalSubchannelTest.java | 65 ++++++++- .../PickFirstLeafLoadBalancerTest.java | 134 ++++++++++++++---- .../io/grpc/xds/RingHashLoadBalancerTest.java | 10 +- 7 files changed, 292 insertions(+), 53 deletions(-) diff --git a/api/src/main/java/io/grpc/LoadBalancer.java b/api/src/main/java/io/grpc/LoadBalancer.java index 0fbce5fa5be..6d74006b396 100644 --- a/api/src/main/java/io/grpc/LoadBalancer.java +++ b/api/src/main/java/io/grpc/LoadBalancer.java @@ -121,6 +121,12 @@ public abstract class LoadBalancer { HEALTH_CONSUMER_LISTENER_ARG_KEY = LoadBalancer.CreateSubchannelArgs.Key.create("internal:health-check-consumer-listener"); + @Internal + public static final LoadBalancer.CreateSubchannelArgs.Key + DISABLE_SUBCHANNEL_RECONNECT_KEY = + LoadBalancer.CreateSubchannelArgs.Key.createWithDefault( + "internal:disable-subchannel-reconnect", Boolean.FALSE); + @Internal public static final Attributes.Key HAS_HEALTH_PRODUCER_LISTENER_KEY = diff --git a/core/src/main/java/io/grpc/internal/InternalSubchannel.java b/core/src/main/java/io/grpc/internal/InternalSubchannel.java index 70e42e2f5f1..27a80f7c191 100644 --- a/core/src/main/java/io/grpc/internal/InternalSubchannel.java +++ b/core/src/main/java/io/grpc/internal/InternalSubchannel.java @@ -45,6 +45,7 @@ import io.grpc.InternalInstrumented; import io.grpc.InternalLogId; import io.grpc.InternalWithLogId; +import io.grpc.LoadBalancer; import io.grpc.Metadata; import io.grpc.MethodDescriptor; import io.grpc.Status; @@ -77,6 +78,7 @@ final class InternalSubchannel implements InternalInstrumented, Tr private final CallTracer callsTracer; private final ChannelTracer channelTracer; private final ChannelLogger channelLogger; + private final boolean reconnectDisabled; private final List transportFilters; @@ -159,13 +161,15 @@ protected void handleNotInUse() { private volatile Attributes connectedAddressAttributes; - InternalSubchannel(List addressGroups, String authority, String userAgent, - BackoffPolicy.Provider backoffPolicyProvider, - ClientTransportFactory transportFactory, ScheduledExecutorService scheduledExecutor, - Supplier stopwatchSupplier, SynchronizationContext syncContext, Callback callback, - InternalChannelz channelz, CallTracer callsTracer, ChannelTracer channelTracer, - InternalLogId logId, ChannelLogger channelLogger, - List transportFilters) { + InternalSubchannel(LoadBalancer.CreateSubchannelArgs args, String authority, String userAgent, + BackoffPolicy.Provider backoffPolicyProvider, + ClientTransportFactory transportFactory, + ScheduledExecutorService scheduledExecutor, + Supplier stopwatchSupplier, SynchronizationContext syncContext, + Callback callback, InternalChannelz channelz, CallTracer callsTracer, + ChannelTracer channelTracer, InternalLogId logId, + ChannelLogger channelLogger, List transportFilters) { + List addressGroups = args.getAddresses(); Preconditions.checkNotNull(addressGroups, "addressGroups"); Preconditions.checkArgument(!addressGroups.isEmpty(), "addressGroups is empty"); checkListHasNoNulls(addressGroups, "addressGroups contains null entry"); @@ -187,6 +191,7 @@ protected void handleNotInUse() { this.logId = Preconditions.checkNotNull(logId, "logId"); this.channelLogger = Preconditions.checkNotNull(channelLogger, "channelLogger"); this.transportFilters = transportFilters; + this.reconnectDisabled = args.getOption(LoadBalancer.DISABLE_SUBCHANNEL_RECONNECT_KEY); } ChannelLogger getChannelLogger() { @@ -289,6 +294,11 @@ public void run() { } gotoState(ConnectivityStateInfo.forTransientFailure(status)); + + if (reconnectDisabled) { + return; + } + if (reconnectPolicy == null) { reconnectPolicy = backoffPolicyProvider.get(); } @@ -337,7 +347,11 @@ private void gotoState(final ConnectivityStateInfo newState) { if (state.getState() != newState.getState()) { Preconditions.checkState(state.getState() != SHUTDOWN, "Cannot transition out of SHUTDOWN to " + newState); - state = newState; + if (reconnectDisabled && newState.getState() == TRANSIENT_FAILURE) { + state = ConnectivityStateInfo.forNonError(IDLE); + } else { + state = newState; + } callback.onStateChange(InternalSubchannel.this, newState); } } diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java index 7e36086ac94..36d79f4011b 100644 --- a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java +++ b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java @@ -1483,7 +1483,7 @@ void onStateChange(InternalSubchannel is, ConnectivityStateInfo newState) { } final InternalSubchannel internalSubchannel = new InternalSubchannel( - addressGroup, + CreateSubchannelArgs.newBuilder().setAddresses(addressGroup).build(), authority, userAgent, backoffPolicyProvider, oobTransportFactory, oobTransportFactory.getScheduledExecutorService(), stopwatchSupplier, syncContext, // All callback methods are run from syncContext @@ -1915,7 +1915,7 @@ void onNotInUse(InternalSubchannel is) { } final InternalSubchannel internalSubchannel = new InternalSubchannel( - args.getAddresses(), + args, authority(), userAgent, backoffPolicyProvider, diff --git a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java index bfa462e16e1..6f4794fdd46 100644 --- a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java +++ b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java @@ -64,17 +64,26 @@ final class PickFirstLeafLoadBalancer extends LoadBalancer { private int numTf = 0; private boolean firstPass = true; @Nullable - private ScheduledHandle scheduleConnectionTask; + private ScheduledHandle scheduleConnectionTask = null; private ConnectivityState rawConnectivityState = IDLE; private ConnectivityState concludedState = IDLE; - private final boolean enableHappyEyeballs = - PickFirstLoadBalancerProvider.isEnabledHappyEyeballs(); + private final boolean enableHappyEyeballs = !isSerializingRetries() + && PickFirstLoadBalancerProvider.isEnabledHappyEyeballs(); private boolean notAPetiolePolicy = true; // means not under a petiole policy + private final BackoffPolicy.Provider bkoffPolProvider = new ExponentialBackoffPolicy.Provider(); + private BackoffPolicy reconnectPolicy; + @Nullable + private ScheduledHandle reconnectTask = null; + private final boolean serializingRetries = isSerializingRetries(); PickFirstLeafLoadBalancer(Helper helper) { this.helper = checkNotNull(helper, "helper"); } + static boolean isSerializingRetries() { + return GrpcUtil.getFlag("GRPC_SERIALIZE_RETRIES", false); + } + @Override public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { if (rawConnectivityState == SHUTDOWN) { @@ -225,9 +234,10 @@ void processSubchannelState(SubchannelData subchannelData, ConnectivityStateInfo return; } - if (newState == IDLE) { + if (newState == IDLE && subchannelData.state == READY) { helper.refreshNameResolution(); } + // If we are transitioning from a TRANSIENT_FAILURE to CONNECTING or IDLE we ignore this state // transition and still keep the LB in TRANSIENT_FAILURE state. This is referred to as "sticky // transient failure". Only a subchannel state change to READY will get the LB out of @@ -277,6 +287,8 @@ void processSubchannelState(SubchannelData subchannelData, ConnectivityStateInfo if (addressIndex.increment()) { cancelScheduleTask(); requestConnection(); // is recursive so might hit the end of the addresses + } else { + scheduleBackoff(); } } @@ -304,6 +316,39 @@ void processSubchannelState(SubchannelData subchannelData, ConnectivityStateInfo } } + /** + * Only called after all addresses attempted and failed (TRANSIENT_FAILURE). + */ + private void scheduleBackoff() { + if (!serializingRetries) { + return; + } + + class EndOfCurrentBackoff implements Runnable { + @Override + public void run() { + reconnectTask = null; + addressIndex.reset(); + requestConnection(); + } + } + + // Just allow the previous one to trigger when ready if we're already in backoff + if (reconnectTask != null) { + return; + } + + if (reconnectPolicy == null) { + reconnectPolicy = bkoffPolProvider.get(); + } + long delayNanos = reconnectPolicy.nextBackoffNanos(); + reconnectTask = helper.getSynchronizationContext().schedule( + new EndOfCurrentBackoff(), + delayNanos, + TimeUnit.NANOSECONDS, + helper.getScheduledExecutorService()); + } + private void updateHealthCheckedState(SubchannelData subchannelData) { if (subchannelData.state != READY) { return; @@ -337,6 +382,11 @@ public void shutdown() { rawConnectivityState = SHUTDOWN; concludedState = SHUTDOWN; cancelScheduleTask(); + if (reconnectTask != null) { + reconnectTask.cancel(); + reconnectTask = null; + } + reconnectPolicy = null; for (SubchannelData subchannelData : subchannels.values()) { subchannelData.getSubchannel().shutdown(); @@ -350,6 +400,12 @@ public void shutdown() { * that all other subchannels must be shutdown. */ private void shutdownRemaining(SubchannelData activeSubchannelData) { + if (reconnectTask != null) { + reconnectTask.cancel(); + reconnectTask = null; + } + reconnectPolicy = null; + cancelScheduleTask(); for (SubchannelData subchannelData : subchannels.values()) { if (!subchannelData.getSubchannel().equals(activeSubchannelData.subchannel)) { @@ -391,8 +447,17 @@ public void requestConnection() { scheduleNextConnection(); break; case TRANSIENT_FAILURE: - addressIndex.increment(); - requestConnection(); + if (!serializingRetries) { + addressIndex.increment(); + requestConnection(); + } else { + if (!addressIndex.isValid()) { + scheduleBackoff(); + } else { + subchannelData.subchannel.requestConnection(); + subchannelData.updateState(CONNECTING); + } + } break; default: // Wait for current subchannel to change state @@ -438,9 +503,10 @@ private SubchannelData createNewSubchannel(SocketAddress addr, Attributes attrs) HealthListener hcListener = new HealthListener(); final Subchannel subchannel = helper.createSubchannel( CreateSubchannelArgs.newBuilder() - .setAddresses(Lists.newArrayList( - new EquivalentAddressGroup(addr, attrs))) - .addOption(HEALTH_CONSUMER_LISTENER_ARG_KEY, hcListener) + .setAddresses(Lists.newArrayList( + new EquivalentAddressGroup(addr, attrs))) + .addOption(HEALTH_CONSUMER_LISTENER_ARG_KEY, hcListener) + .addOption(LoadBalancer.DISABLE_SUBCHANNEL_RECONNECT_KEY, serializingRetries) .build()); if (subchannel == null) { log.warning("Was not able to create subchannel for " + addr); @@ -458,7 +524,7 @@ private SubchannelData createNewSubchannel(SocketAddress addr, Attributes attrs) } private boolean isPassComplete() { - if (addressIndex.isValid() || subchannels.size() < addressIndex.size()) { + if (subchannels.size() < addressIndex.size()) { return false; } for (SubchannelData sc : subchannels.values()) { @@ -646,6 +712,16 @@ public int size() { } } + @VisibleForTesting + int getGroupIndex() { + return addressIndex.groupIndex; + } + + @VisibleForTesting + boolean isIndexValid() { + return addressIndex.isValid(); + } + private static final class SubchannelData { private final Subchannel subchannel; private ConnectivityState state; diff --git a/core/src/test/java/io/grpc/internal/InternalSubchannelTest.java b/core/src/test/java/io/grpc/internal/InternalSubchannelTest.java index e4d9f27ed46..b75fd43a743 100644 --- a/core/src/test/java/io/grpc/internal/InternalSubchannelTest.java +++ b/core/src/test/java/io/grpc/internal/InternalSubchannelTest.java @@ -46,6 +46,7 @@ import io.grpc.InternalChannelz; import io.grpc.InternalLogId; import io.grpc.InternalWithLogId; +import io.grpc.LoadBalancer; import io.grpc.Status; import io.grpc.SynchronizationContext; import io.grpc.internal.InternalSubchannel.CallTracingTransport; @@ -309,10 +310,57 @@ public void constructor_eagListWithNull_throws() { verify(mockBackoffPolicy2, times(backoff2Consulted)).nextBackoffNanos(); } + @Test public void twoAddressesReconnectDisabled() { + SocketAddress addr1 = mock(SocketAddress.class); + SocketAddress addr2 = mock(SocketAddress.class); + createInternalSubchannel(true, + new EquivalentAddressGroup(Arrays.asList(addr1, addr2))); + assertEquals(IDLE, internalSubchannel.getState()); + + assertNull(internalSubchannel.obtainActiveTransport()); + assertExactCallbackInvokes("onStateChange:CONNECTING"); + assertEquals(CONNECTING, internalSubchannel.getState()); + verify(mockTransportFactory).newClientTransport(eq(addr1), any(), any()); + // Let this one fail without success + transports.poll().listener.transportShutdown(Status.UNAVAILABLE); + // Still in CONNECTING + assertNull(internalSubchannel.obtainActiveTransport()); + assertNoCallbackInvoke(); + assertEquals(CONNECTING, internalSubchannel.getState()); + + // Second attempt will start immediately. Still no back-off policy. + verify(mockBackoffPolicyProvider, times(0)).get(); + verify(mockTransportFactory, times(1)) + .newClientTransport( + eq(addr2), + eq(createClientTransportOptions()), + isA(TransportLogger.class)); + assertNull(internalSubchannel.obtainActiveTransport()); + // Fail this one too + assertNoCallbackInvoke(); + transports.poll().listener.transportShutdown(Status.UNAVAILABLE); + // All addresses have failed, but we aren't controlling retries. + assertEquals(IDLE, internalSubchannel.getState()); + assertExactCallbackInvokes("onStateChange:" + UNAVAILABLE_STATE); + // Backoff reset and first back-off interval begins + verify(mockBackoffPolicy1, never()).nextBackoffNanos(); + verify(mockBackoffPolicyProvider, never()).get(); + assertTrue("Nothing should have been scheduled", fakeClock.getPendingTasks().isEmpty()); + + // Should follow orders and create an active transport. + internalSubchannel.obtainActiveTransport(); + assertExactCallbackInvokes("onStateChange:CONNECTING"); + assertEquals(CONNECTING, internalSubchannel.getState()); + + // Shouldn't have anything scheduled, so shouldn't do anything + assertTrue("Nothing should have been scheduled 2", fakeClock.getPendingTasks().isEmpty()); + } + @Test public void twoAddressesReconnect() { SocketAddress addr1 = mock(SocketAddress.class); SocketAddress addr2 = mock(SocketAddress.class); - createInternalSubchannel(addr1, addr2); + createInternalSubchannel(false, + new EquivalentAddressGroup(Arrays.asList(addr1, addr2))); assertEquals(IDLE, internalSubchannel.getState()); // Invocation counters int transportsAddr1 = 0; @@ -1377,11 +1425,24 @@ private void createInternalSubchannel(SocketAddress ... addrs) { } private void createInternalSubchannel(EquivalentAddressGroup ... addrs) { + createInternalSubchannel(false, addrs); + } + + private void createInternalSubchannel(boolean reconnectDisabled, + EquivalentAddressGroup ... addrs) { List addressGroups = Arrays.asList(addrs); InternalLogId logId = InternalLogId.allocate("Subchannel", /*details=*/ AUTHORITY); ChannelTracer subchannelTracer = new ChannelTracer(logId, 10, fakeClock.getTimeProvider().currentTimeNanos(), "Subchannel"); - internalSubchannel = new InternalSubchannel(addressGroups, AUTHORITY, USER_AGENT, + LoadBalancer.CreateSubchannelArgs.Builder argBuilder = + LoadBalancer.CreateSubchannelArgs.newBuilder().setAddresses(addressGroups); + if (reconnectDisabled) { + argBuilder.addOption(LoadBalancer.DISABLE_SUBCHANNEL_RECONNECT_KEY, reconnectDisabled); + } + LoadBalancer.CreateSubchannelArgs createSubchannelArgs = argBuilder.build(); + internalSubchannel = new InternalSubchannel( + createSubchannelArgs, + AUTHORITY, USER_AGENT, mockBackoffPolicyProvider, mockTransportFactory, fakeClock.getScheduledExecutorService(), fakeClock.getStopwatchSupplier(), syncContext, mockInternalSubchannelCallback, channelz, CallTracer.getDefaultFactory().create(), diff --git a/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java b/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java index 63915bddc99..f0031a6ae62 100644 --- a/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java +++ b/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java @@ -27,10 +27,12 @@ import static io.grpc.LoadBalancer.HEALTH_CONSUMER_LISTENER_ARG_KEY; import static io.grpc.LoadBalancer.IS_PETIOLE_POLICY; import static io.grpc.internal.PickFirstLeafLoadBalancer.CONNECTION_DELAY_INTERVAL_MS; +import static io.grpc.internal.PickFirstLeafLoadBalancer.isSerializingRetries; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assume.assumeTrue; import static org.mockito.AdditionalAnswers.delegatesTo; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -73,7 +75,6 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.junit.After; -import org.junit.Assume; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -92,14 +93,22 @@ public class PickFirstLeafLoadBalancerTest { public static final Status CONNECTION_ERROR = Status.UNAVAILABLE.withDescription("Simulated connection error"); - - @Parameterized.Parameters(name = "{0}") - public static List enableHappyEyeballs() { - return Arrays.asList(true, false); + public static final String GRPC_SERIALIZE_RETRIES = "GRPC_SERIALIZE_RETRIES"; + + @Parameterized.Parameters(name = "{0}-{1}") + public static List data() { + return Arrays.asList(new Object[][] { + {false, false}, + {false, true}, + {true, false}}); } - @Parameterized.Parameter + @Parameterized.Parameter(value = 0) + public boolean serializeRetries; + + @Parameterized.Parameter(value = 1) public boolean enableHappyEyeballs; + private PickFirstLeafLoadBalancer loadBalancer; private final List servers = Lists.newArrayList(); private static final Attributes.Key FOO = Attributes.Key.create("foo"); @@ -137,13 +146,22 @@ public void uncaughtException(Thread t, Throwable e) { private PickSubchannelArgs mockArgs; private String originalHappyEyeballsEnabledValue; + private String originalSerializeRetriesValue; + + private long backoffMillis; @Before public void setUp() { + assumeTrue(!serializeRetries || !enableHappyEyeballs); // they are not compatible + + backoffMillis = TimeUnit.SECONDS.toMillis(1); + originalSerializeRetriesValue = System.getProperty(GRPC_SERIALIZE_RETRIES); + System.setProperty(GRPC_SERIALIZE_RETRIES, Boolean.toString(serializeRetries)); + originalHappyEyeballsEnabledValue = System.getProperty(PickFirstLoadBalancerProvider.GRPC_PF_USE_HAPPY_EYEBALLS); System.setProperty(PickFirstLoadBalancerProvider.GRPC_PF_USE_HAPPY_EYEBALLS, - enableHappyEyeballs ? "true" : "false"); + Boolean.toString(enableHappyEyeballs)); for (int i = 1; i <= 5; i++) { SocketAddress addr = new FakeSocketAddress("server" + i); @@ -176,6 +194,11 @@ public void setUp() { @After public void tearDown() { + if (originalSerializeRetriesValue == null) { + System.clearProperty(GRPC_SERIALIZE_RETRIES); + } else { + System.setProperty(GRPC_SERIALIZE_RETRIES, originalSerializeRetriesValue); + } if (originalHappyEyeballsEnabledValue == null) { System.clearProperty(PickFirstLoadBalancerProvider.GRPC_PF_USE_HAPPY_EYEBALLS); } else { @@ -498,6 +521,9 @@ public void healthCheckFlow() { inOrder.verify(mockHelper).updateBalancingState(eq(READY), pickerCaptor.capture()); assertThat(pickerCaptor.getValue().pickSubchannel(mockArgs) .getSubchannel()).isSameInstanceAs(mockSubchannel1); + verify(mockHelper, atLeast(0)).getSynchronizationContext(); + verify(mockHelper, atLeast(0)).getScheduledExecutorService(); + verifyNoMoreInteractions(mockHelper); healthListener2.onSubchannelState(ConnectivityStateInfo.forNonError(READY)); verifyNoMoreInteractions(mockHelper); @@ -520,20 +546,7 @@ public void pickAfterStateChangeAfterResolution() { inOrder.verify(mockSubchannel1).start(stateListenerCaptor.capture()); stateListeners[0] = stateListenerCaptor.getValue(); - if (enableHappyEyeballs) { - forwardTimeByConnectionDelay(); - inOrder.verify(mockSubchannel2).start(stateListenerCaptor.capture()); - stateListeners[1] = stateListenerCaptor.getValue(); - forwardTimeByConnectionDelay(); - inOrder.verify(mockSubchannel3).start(stateListenerCaptor.capture()); - stateListeners[2] = stateListenerCaptor.getValue(); - forwardTimeByConnectionDelay(); - inOrder.verify(mockSubchannel4).start(stateListenerCaptor.capture()); - stateListeners[3] = stateListenerCaptor.getValue(); - } - - reset(mockHelper); - + stateListeners[0].onSubchannelState(ConnectivityStateInfo.forNonError(READY)); stateListeners[0].onSubchannelState(ConnectivityStateInfo.forNonError(IDLE)); inOrder.verify(mockHelper).refreshNameResolution(); inOrder.verify(mockHelper).updateBalancingState(eq(IDLE), pickerCaptor.capture()); @@ -543,11 +556,23 @@ public void pickAfterStateChangeAfterResolution() { stateListeners[0].onSubchannelState(ConnectivityStateInfo.forNonError(CONNECTING)); Status error = Status.UNAVAILABLE.withDescription("boom!"); + reset(mockHelper); if (enableHappyEyeballs) { - for (SubchannelStateListener listener : stateListeners) { - listener.onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); - } + stateListeners[0].onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); + forwardTimeByConnectionDelay(); + inOrder.verify(mockSubchannel2).start(stateListenerCaptor.capture()); + stateListeners[1] = stateListenerCaptor.getValue(); + stateListeners[1].onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); + forwardTimeByConnectionDelay(); + inOrder.verify(mockSubchannel3).start(stateListenerCaptor.capture()); + stateListeners[2] = stateListenerCaptor.getValue(); + stateListeners[2].onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); + forwardTimeByConnectionDelay(); + inOrder.verify(mockSubchannel4).start(stateListenerCaptor.capture()); + stateListeners[3] = stateListenerCaptor.getValue(); + stateListeners[3].onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); + forwardTimeByConnectionDelay(); } else { stateListeners[0].onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); for (int i = 1; i < stateListeners.length; i++) { @@ -589,6 +614,8 @@ public void pickAfterResolutionAfterTransientValue() { // Transition from TRANSIENT_ERROR to CONNECTING should also be ignored. stateListener.onSubchannelState(ConnectivityStateInfo.forNonError(CONNECTING)); + verify(mockHelper, atLeast(0)).getSynchronizationContext(); + verify(mockHelper, atLeast(0)).getScheduledExecutorService(); verifyNoMoreInteractions(mockHelper); assertEquals(error, pickerCaptor.getValue().pickSubchannel(mockArgs).getStatus()); } @@ -619,6 +646,8 @@ public void pickWithDupAddressesUpDownUp() { // Transition from TRANSIENT_ERROR to CONNECTING should also be ignored. stateListener.onSubchannelState(ConnectivityStateInfo.forNonError(CONNECTING)); + verify(mockHelper, atLeast(0)).getSynchronizationContext(); + verify(mockHelper, atLeast(0)).getScheduledExecutorService(); verifyNoMoreInteractions(mockHelper); assertEquals(error, pickerCaptor.getValue().pickSubchannel(mockArgs).getStatus()); @@ -651,6 +680,8 @@ public void pickWithDupEagsUpDownUp() { // Transition from TRANSIENT_ERROR to CONNECTING should also be ignored. stateListener.onSubchannelState(ConnectivityStateInfo.forNonError(CONNECTING)); + verify(mockHelper, atLeast(0)).getSynchronizationContext(); + verify(mockHelper, atLeast(0)).getScheduledExecutorService(); verifyNoMoreInteractions(mockHelper); assertEquals(error, pickerCaptor.getValue().pickSubchannel(mockArgs).getStatus()); @@ -1518,6 +1549,8 @@ public void updateAddresses_intersecting_ready() { @Test public void updateAddresses_intersecting_transient_failure() { + assumeTrue(!isSerializingRetries()); + // Starting first connection attempt InOrder inOrder = inOrder(mockHelper, mockSubchannel1, mockSubchannel2, mockSubchannel3, mockSubchannel4); // captor: captures @@ -1782,6 +1815,8 @@ public void updateAddresses_identical_ready() { @Test public void updateAddresses_identical_transient_failure() { + assumeTrue(!isSerializingRetries()); + InOrder inOrder = inOrder(mockHelper, mockSubchannel1, mockSubchannel2, mockSubchannel3, mockSubchannel4); // Creating first set of endpoints/addresses @@ -2295,7 +2330,7 @@ public void ready_then_transient_failure_again() { @Test public void happy_eyeballs_trigger_connection_delay() { - Assume.assumeTrue(enableHappyEyeballs); // This test is only for happy eyeballs + assumeTrue(enableHappyEyeballs); // This test is only for happy eyeballs // Starting first connection attempt InOrder inOrder = inOrder(mockHelper, mockSubchannel1, mockSubchannel2, mockSubchannel3, mockSubchannel4); @@ -2340,7 +2375,7 @@ public void happy_eyeballs_trigger_connection_delay() { @Test public void happy_eyeballs_connection_results_happen_after_get_to_end() { - Assume.assumeTrue(enableHappyEyeballs); // This test is only for happy eyeballs + assumeTrue(enableHappyEyeballs); // This test is only for happy eyeballs InOrder inOrder = inOrder(mockHelper, mockSubchannel1, mockSubchannel2, mockSubchannel3); Status error = Status.UNAUTHENTICATED.withDescription("simulated failure"); @@ -2393,7 +2428,7 @@ public void happy_eyeballs_connection_results_happen_after_get_to_end() { @Test public void happy_eyeballs_pick_pushes_index_over_end() { - Assume.assumeTrue(enableHappyEyeballs); // This test is only for happy eyeballs + assumeTrue(enableHappyEyeballs); // This test is only for happy eyeballs InOrder inOrder = inOrder(mockHelper, mockSubchannel1, mockSubchannel2, mockSubchannel3, mockSubchannel2n2, mockSubchannel3n2); @@ -2471,7 +2506,7 @@ public void happy_eyeballs_pick_pushes_index_over_end() { @Test public void happy_eyeballs_fail_then_trigger_connection_delay() { - Assume.assumeTrue(enableHappyEyeballs); // This test is only for happy eyeballs + assumeTrue(enableHappyEyeballs); // This test is only for happy eyeballs // Starting first connection attempt InOrder inOrder = inOrder(mockHelper, mockSubchannel1, mockSubchannel2, mockSubchannel3); assertEquals(IDLE, loadBalancer.getConcludedConnectivityState()); @@ -2550,6 +2585,44 @@ public void advance_index_then_request_connection() { loadBalancer.requestConnection(); // should be handled without throwing exception } + @Test + public void serialized_retries_two_passes() { + assumeTrue(serializeRetries); // This test is only for serialized retries + + InOrder inOrder = inOrder(mockHelper, mockSubchannel1, mockSubchannel2, mockSubchannel3); + Status error = Status.UNAUTHENTICATED.withDescription("simulated failure"); + + List addrs = + Lists.newArrayList(servers.get(0), servers.get(1), servers.get(2)); + Subchannel[] subchannels = new Subchannel[]{mockSubchannel1, mockSubchannel2, mockSubchannel3}; + SubchannelStateListener[] listeners = new SubchannelStateListener[subchannels.length]; + loadBalancer.acceptResolvedAddresses( + ResolvedAddresses.newBuilder().setAddresses(addrs).build()); + forwardTimeByConnectionDelay(2); + for (int i = 0; i < subchannels.length; i++) { + inOrder.verify(subchannels[i]).start(stateListenerCaptor.capture()); + inOrder.verify(subchannels[i]).requestConnection(); + listeners[i] = stateListenerCaptor.getValue(); + listeners[i].onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); + } + assertEquals(TRANSIENT_FAILURE, loadBalancer.getConcludedConnectivityState()); + assertFalse("Index should be at end", loadBalancer.isIndexValid()); + + forwardTimeByBackoffDelay(); // should trigger retry + for (int i = 0; i < subchannels.length; i++) { + inOrder.verify(subchannels[i]).requestConnection(); + listeners[i].onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); // cascade + } + inOrder.verify(subchannels[0], never()).requestConnection(); // should wait for backoff delay + + forwardTimeByBackoffDelay(); // should trigger retry again + for (int i = 0; i < subchannels.length; i++) { + inOrder.verify(subchannels[i]).requestConnection(); + assertEquals(i, loadBalancer.getGroupIndex()); + listeners[i].onSubchannelState(ConnectivityStateInfo.forTransientFailure(error)); // cascade + } + } + @Test public void index_looping() { Attributes.Key key = Attributes.Key.create("some-key"); @@ -2689,6 +2762,11 @@ private void forwardTimeByConnectionDelay(int times) { } } + private void forwardTimeByBackoffDelay() { + backoffMillis = (long) (backoffMillis * 1.8); // backoff factor default is 1.6 with Jitter .2 + fakeClock.forwardTime(backoffMillis, TimeUnit.MILLISECONDS); + } + private void acceptXSubchannels(int num) { List newServers = new ArrayList<>(); for (int i = 0; i < num; i++) { diff --git a/xds/src/test/java/io/grpc/xds/RingHashLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/RingHashLoadBalancerTest.java index 047ba71bbe0..dd7de3691a7 100644 --- a/xds/src/test/java/io/grpc/xds/RingHashLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/RingHashLoadBalancerTest.java @@ -150,7 +150,8 @@ public void subchannelLazyConnectUntilPicked() { assertThat(result.getStatus().isOk()).isTrue(); assertThat(result.getSubchannel()).isNull(); Subchannel subchannel = Iterables.getOnlyElement(subchannels.values()); - int expectedTimes = PickFirstLoadBalancerProvider.isEnabledHappyEyeballs() ? 1 : 2; + int expectedTimes = PickFirstLoadBalancerProvider.isEnabledNewPickFirst() + && !PickFirstLoadBalancerProvider.isEnabledHappyEyeballs() ? 1 : 2; verify(subchannel, times(expectedTimes)).requestConnection(); verify(helper).updateBalancingState(eq(CONNECTING), any(SubchannelPicker.class)); verify(helper).createSubchannel(any(CreateSubchannelArgs.class)); @@ -184,7 +185,8 @@ public void subchannelNotAutoReconnectAfterReenteringIdle() { pickerCaptor.getValue().pickSubchannel(args); Subchannel subchannel = subchannels.get(Collections.singletonList(childLbState.getEag())); InOrder inOrder = Mockito.inOrder(helper, subchannel); - int expectedTimes = PickFirstLoadBalancerProvider.isEnabledHappyEyeballs() ? 1 : 2; + int expectedTimes = PickFirstLoadBalancerProvider.isEnabledHappyEyeballs() + || !PickFirstLoadBalancerProvider.isEnabledNewPickFirst() ? 2 : 1; inOrder.verify(subchannel, times(expectedTimes)).requestConnection(); deliverSubchannelState(subchannel, CSI_READY); inOrder.verify(helper).updateBalancingState(eq(READY), any(SubchannelPicker.class)); @@ -443,8 +445,10 @@ public void skipFailingHosts_pickNextNonFailingHost() { PickResult result = pickerCaptor.getValue().pickSubchannel(args); assertThat(result.getStatus().isOk()).isTrue(); assertThat(result.getSubchannel()).isNull(); // buffer request - int expectedTimes = PickFirstLoadBalancerProvider.isEnabledHappyEyeballs() ? 1 : 2; // verify kicked off connection to server2 + int expectedTimes = PickFirstLoadBalancerProvider.isEnabledHappyEyeballs() + || !PickFirstLoadBalancerProvider.isEnabledNewPickFirst() ? 2 : 1; + verify(getSubChannel(servers.get(1)), times(expectedTimes)).requestConnection(); assertThat(subchannels.size()).isEqualTo(2); // no excessive connection From 35f0d56894d50c0105ab60eb121fe8363c386333 Mon Sep 17 00:00:00 2001 From: Riya Mehta <55350838+rmehta19@users.noreply.github.com> Date: Thu, 3 Oct 2024 14:45:01 -0700 Subject: [PATCH 102/103] s2a: don't use reflection to load token manager (#11590) --- s2a/BUILD.bazel | 31 +++---------------- .../tokenmanager/AccessTokenManager.java | 15 ++------- 2 files changed, 7 insertions(+), 39 deletions(-) diff --git a/s2a/BUILD.bazel b/s2a/BUILD.bazel index 676215e1e1e..283828a9f7e 100644 --- a/s2a/BUILD.bazel +++ b/s2a/BUILD.bazel @@ -32,33 +32,13 @@ java_library( ) java_library( - name = "token_fetcher", - srcs = ["src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/TokenFetcher.java"], - deps = [ - ":s2a_identity", - ], -) - -java_library( - name = "access_token_manager", - srcs = [ - "src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/AccessTokenManager.java", - ], + name = "token_manager", + srcs = glob([ + "src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/*.java", + ]), deps = [ ":s2a_identity", - ":token_fetcher", artifact("com.google.code.findbugs:jsr305"), - ], -) - -java_library( - name = "single_token_fetcher", - srcs = [ - "src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/SingleTokenFetcher.java", - ], - deps = [ - ":s2a_identity", - ":token_fetcher", artifact("com.google.guava:guava"), ], ) @@ -77,13 +57,12 @@ java_library( "src/main/java/io/grpc/s2a/internal/handshaker/SslContextFactory.java", ], deps = [ - ":access_token_manager", + ":token_manager", ":common_java_proto", ":s2a_channel_pool", ":s2a_identity", ":s2a_java_proto", ":s2a_java_grpc_proto", - ":single_token_fetcher", "//api", "//core:internal", "//netty", diff --git a/s2a/src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/AccessTokenManager.java b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/AccessTokenManager.java index 71e55b29fcd..65fca46bbb2 100644 --- a/s2a/src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/AccessTokenManager.java +++ b/s2a/src/main/java/io/grpc/s2a/internal/handshaker/tokenmanager/AccessTokenManager.java @@ -17,7 +17,6 @@ package io.grpc.s2a.internal.handshaker.tokenmanager; import io.grpc.s2a.internal.handshaker.S2AIdentity; -import java.lang.reflect.Method; import java.util.Optional; import javax.annotation.concurrent.ThreadSafe; @@ -28,19 +27,9 @@ public final class AccessTokenManager { /** Creates an {@code AccessTokenManager} based on the environment where the application runs. */ public static Optional create() { - Optional tokenFetcher; - try { - Class singleTokenFetcherClass = - Class.forName("io.grpc.s2a.internal.handshaker.tokenmanager.SingleTokenFetcher"); - Method createTokenFetcher = singleTokenFetcherClass.getMethod("create"); - tokenFetcher = (Optional) createTokenFetcher.invoke(null); - } catch (ClassNotFoundException e) { - tokenFetcher = Optional.empty(); - } catch (ReflectiveOperationException e) { - throw new LinkageError(e.getMessage(), e); - } + Optional tokenFetcher = SingleTokenFetcher.create(); return tokenFetcher.isPresent() - ? Optional.of(new AccessTokenManager((TokenFetcher) tokenFetcher.get())) + ? Optional.of(new AccessTokenManager(tokenFetcher.get())) : Optional.empty(); } From 94a0a0d1c7af25aaf46f6f36353afc16e45f6b2b Mon Sep 17 00:00:00 2001 From: vinodhabib <47808007+vinodhabib@users.noreply.github.com> Date: Fri, 4 Oct 2024 11:42:25 +0530 Subject: [PATCH 103/103] example-gauth: Use application default creds instead of file argument (#11595) Also removed unnecessary refreshAccessToken() and fixed the reference to README.md. Fixes #5677 --- examples/example-gauth/README.md | 21 +++++++-------- .../examples/googleAuth/GoogleAuthClient.java | 26 +++++++++---------- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/examples/example-gauth/README.md b/examples/example-gauth/README.md index 622c14cb57b..b49d346a9be 100644 --- a/examples/example-gauth/README.md +++ b/examples/example-gauth/README.md @@ -43,13 +43,13 @@ gcloud pubsub topics create Topic1 5. You will now need to set up [authentication](https://cloud.google.com/docs/authentication/) and a [service account](https://cloud.google.com/docs/authentication/#service_accounts) in order to access Pub/Sub via gRPC APIs as described [here](https://cloud.google.com/iam/docs/creating-managing-service-accounts). -Assign the [role](https://cloud.google.com/iam/docs/granting-roles-to-service-accounts) `Project -> Owner` +(**Note:** This step is unnecessary on Google platforms (Google App Engine / Google Cloud Shell / Google Compute Engine) as it will +automatically use the in-built Google credentials). Assign the [role](https://cloud.google.com/iam/docs/granting-roles-to-service-accounts) `Project -> Owner` and for Key type select JSON. Once you click `Create`, a JSON file containing your key is downloaded to your computer. Note down the path of this file or copy this file to the computer and file system where you will be running the example application as described later. Assume this JSON file is available at -`/path/to/JSON/file`. You can also use the `gcloud` shell commands to -[create the service account](https://cloud.google.com/iam/docs/creating-managing-service-accounts#iam-service-accounts-create-gcloud) -and [the JSON file](https://cloud.google.com/iam/docs/creating-managing-service-account-keys#iam-service-account-keys-create-gcloud). +`/path/to/JSON/file` Set the value of the environment variable GOOGLE_APPLICATION_CREDENTIALS to this file path. You can also use the `gcloud` shell commands to +[create the service account](https://cloud.google.com/iam/docs/creating-managing-service-accounts#iam-service-accounts-create-gcloud). #### To build the examples @@ -62,19 +62,18 @@ $ ../gradlew installDist #### How to run the example: -`google-auth-client` requires two command line arguments for the location of the JSON file and the project ID: +`google-auth-client` requires one command line argument for the project ID: ```text -USAGE: GoogleAuthClient +USAGE: GoogleAuthClient ``` -The first argument is the location of the JSON file you created in step 5 above. -The second argument is the project ID in the form "projects/xyz123" where "xyz123" is +The first argument is the project ID in the form "projects/xyz123" where "xyz123" is the project ID of the project you created (or used) in step 2 above. ```bash # Run the client -./build/install/example-gauth/bin/google-auth-client /path/to/JSON/file projects/xyz123 +./build/install/example-gauth/bin/google-auth-client projects/xyz123 ``` That's it! The client will show the list of Pub/Sub topics for the project as follows: @@ -93,7 +92,7 @@ the project ID of the project you created (or used) in step 2 above. ``` $ mvn verify $ # Run the client - $ mvn exec:java -Dexec.mainClass=io.grpc.examples.googleAuth.GoogleAuthClient -Dexec.args="/path/to/JSON/file projects/xyz123" + $ mvn exec:java -Dexec.mainClass=io.grpc.examples.googleAuth.GoogleAuthClient -Dexec.args="projects/xyz123" ``` ## Bazel @@ -101,5 +100,5 @@ the project ID of the project you created (or used) in step 2 above. ``` $ bazel build :google-auth-client $ # Run the client - $ ../bazel-bin/google-auth-client /path/to/JSON/file projects/xyz123 + $ ../bazel-bin/google-auth-client projects/xyz123 ``` \ No newline at end of file diff --git a/examples/example-gauth/src/main/java/io/grpc/examples/googleAuth/GoogleAuthClient.java b/examples/example-gauth/src/main/java/io/grpc/examples/googleAuth/GoogleAuthClient.java index 4d3dd044376..eb0d9feedfc 100644 --- a/examples/example-gauth/src/main/java/io/grpc/examples/googleAuth/GoogleAuthClient.java +++ b/examples/example-gauth/src/main/java/io/grpc/examples/googleAuth/GoogleAuthClient.java @@ -33,7 +33,7 @@ /** * Example to illustrate use of Google credentials as described in - * @see Google Auth Example README + * @see Google Auth Example README * * Also @see Google Cloud Pubsub via gRPC */ @@ -52,7 +52,7 @@ public class GoogleAuthClient { * * @param host host to connect to - typically "pubsub.googleapis.com" * @param port port to connect to - typically 443 - the TLS port - * @param callCredentials the Google call credentials created from a JSON file + * @param callCredentials the Google call credentials */ public GoogleAuthClient(String host, int port, CallCredentials callCredentials) { // Google API invocation requires a secure channel. Channels are secure by default (SSL/TLS) @@ -63,7 +63,7 @@ public GoogleAuthClient(String host, int port, CallCredentials callCredentials) * Construct our gRPC client that connects to the pubsub server using an existing channel. * * @param channel channel that has been built already - * @param callCredentials the Google call credentials created from a JSON file + * @param callCredentials the Google call credentials */ GoogleAuthClient(ManagedChannel channel, CallCredentials callCredentials) { this.channel = channel; @@ -101,32 +101,30 @@ public void getTopics(String projectID) { /** * The app requires 2 arguments as described in - * @see Google Auth Example README + * @see Google Auth Example README * - * arg0 = location of the JSON file for the service account you created in the GCP console - * arg1 = project name in the form "projects/balmy-cirrus-225307" where "balmy-cirrus-225307" is + * arg0 = project name in the form "projects/balmy-cirrus-225307" where "balmy-cirrus-225307" is * the project ID for the project you created. * + * On non-Google platforms, the GOOGLE_APPLICATION_CREDENTIALS env variable should be set to the + * location of the JSON file for the service account you created in the GCP console. */ public static void main(String[] args) throws Exception { - if (args.length < 2) { - logger.severe("Usage: please pass 2 arguments:\n" + - "arg0 = location of the JSON file for the service account you created in the GCP console\n" + - "arg1 = project name in the form \"projects/xyz\" where \"xyz\" is the project ID of the project you created.\n"); + if (args.length < 1) { + logger.severe("Usage: please pass 1 argument:\n" + + "arg0 = project name in the form \"projects/xyz\" where \"xyz\" is the project ID of the project you created.\n"); System.exit(1); } - GoogleCredentials credentials = GoogleCredentials.fromStream(new FileInputStream(args[0])); + GoogleCredentials credentials = GoogleCredentials.getApplicationDefault(); // We need to create appropriate scope as per https://cloud.google.com/storage/docs/authentication#oauth-scopes credentials = credentials.createScoped(Arrays.asList("https://www.googleapis.com/auth/cloud-platform")); - // credentials must be refreshed before the access token is available - credentials.refreshAccessToken(); GoogleAuthClient client = new GoogleAuthClient("pubsub.googleapis.com", 443, MoreCallCredentials.from(credentials)); try { - client.getTopics(args[1]); + client.getTopics(args[0]); } finally { client.shutdown(); }