From 01aff58178150c451413191696733fad2a930490 Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Wed, 10 Aug 2022 08:17:59 -0700 Subject: [PATCH 01/72] Start 1.50.0 development cycle (#9433) --- build.gradle | 2 +- .../src/test/golden/TestDeprecatedService.java.txt | 2 +- compiler/src/test/golden/TestService.java.txt | 2 +- .../src/testLite/golden/TestDeprecatedService.java.txt | 2 +- compiler/src/testLite/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-gauth/build.gradle | 2 +- examples/example-gauth/pom.xml | 4 ++-- 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-orca/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 ++-- 23 files changed, 41 insertions(+), 41 deletions(-) diff --git a/build.gradle b/build.gradle index a63b9cd5a6a..a5243b0d573 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ subprojects { apply plugin: "net.ltgt.errorprone" group = "io.grpc" - version = "1.49.0-SNAPSHOT" // CURRENT_GRPC_VERSION + version = "1.50.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 4f21c75f7d0..9fddc958d81 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.49.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.50.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 4fc8b282a0a..0bbe6f65ddc 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.49.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.50.0-SNAPSHOT)", comments = "Source: grpc/testing/compiler/test.proto") @io.grpc.stub.annotations.GrpcGenerated public final class TestServiceGrpc { diff --git a/compiler/src/testLite/golden/TestDeprecatedService.java.txt b/compiler/src/testLite/golden/TestDeprecatedService.java.txt index 5ae3fdd746b..b13295c37b4 100644 --- a/compiler/src/testLite/golden/TestDeprecatedService.java.txt +++ b/compiler/src/testLite/golden/TestDeprecatedService.java.txt @@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.49.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.50.0-SNAPSHOT)", comments = "Source: grpc/testing/compiler/test.proto") @io.grpc.stub.annotations.GrpcGenerated @java.lang.Deprecated diff --git a/compiler/src/testLite/golden/TestService.java.txt b/compiler/src/testLite/golden/TestService.java.txt index ed26c986f45..d2f9c12e923 100644 --- a/compiler/src/testLite/golden/TestService.java.txt +++ b/compiler/src/testLite/golden/TestService.java.txt @@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.49.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.50.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 605fabc0cc2..263ecd0118d 100644 --- a/core/src/main/java/io/grpc/internal/GrpcUtil.java +++ b/core/src/main/java/io/grpc/internal/GrpcUtil.java @@ -203,7 +203,7 @@ public byte[] parseAsciiString(byte[] serialized) { public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults(); - private static final String IMPLEMENTATION_VERSION = "1.49.0-SNAPSHOT"; // CURRENT_GRPC_VERSION + private static final String IMPLEMENTATION_VERSION = "1.50.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 6f0d10891a9..78cbdd305e0 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.21.1' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.49.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -54,12 +54,12 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.0.2' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.49.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.49.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.49.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' testImplementation 'junit:junit:4.12' testImplementation 'com.google.truth:truth:1.0.1' - testImplementation 'io.grpc:grpc-testing:1.49.0-SNAPSHOT' // CURRENT_GRPC_VERSION + testImplementation 'io.grpc:grpc-testing:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION } diff --git a/examples/android/helloworld/app/build.gradle b/examples/android/helloworld/app/build.gradle index 5a38cff5271..f30a1110f12 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.21.1' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.49.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -52,8 +52,8 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.0.2' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.49.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.49.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.49.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.50.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 19c8d7a27a7..5c861238223 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.21.1' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.49.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -52,8 +52,8 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.0.2' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.49.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.49.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.49.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.50.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 3f6f2d0eb0d..788c015823a 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.21.1' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.49.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -53,8 +53,8 @@ dependencies { implementation 'com.android.support:appcompat-v7:28.0.0' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.49.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.49.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.49.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.50.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 4b02ae122b1..9f779ff819a 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -22,7 +22,7 @@ targetCompatibility = 1.8 // 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.49.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.21.1' def protocVersion = protobufVersion diff --git a/examples/example-alts/build.gradle b/examples/example-alts/build.gradle index 0f22d8ab6bc..4d44a130d4b 100644 --- a/examples/example-alts/build.gradle +++ b/examples/example-alts/build.gradle @@ -23,7 +23,7 @@ targetCompatibility = 1.8 // 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.49.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.21.1' dependencies { diff --git a/examples/example-gauth/build.gradle b/examples/example-gauth/build.gradle index ed0a08564bc..64b870daf28 100644 --- a/examples/example-gauth/build.gradle +++ b/examples/example-gauth/build.gradle @@ -23,7 +23,7 @@ targetCompatibility = 1.8 // 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.49.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.21.1' def protocVersion = protobufVersion diff --git a/examples/example-gauth/pom.xml b/examples/example-gauth/pom.xml index 79d2dec996a..8c9c8673d3f 100644 --- a/examples/example-gauth/pom.xml +++ b/examples/example-gauth/pom.xml @@ -6,13 +6,13 @@ jar - 1.49.0-SNAPSHOT + 1.50.0-SNAPSHOT example-gauth https://github.com/grpc/grpc-java UTF-8 - 1.49.0-SNAPSHOT + 1.50.0-SNAPSHOT 3.21.1 1.7 diff --git a/examples/example-hostname/build.gradle b/examples/example-hostname/build.gradle index ec212f27aee..e9db5ef7c61 100644 --- a/examples/example-hostname/build.gradle +++ b/examples/example-hostname/build.gradle @@ -21,7 +21,7 @@ targetCompatibility = 1.8 // 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.49.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.21.1' dependencies { diff --git a/examples/example-hostname/pom.xml b/examples/example-hostname/pom.xml index 5ac4cfd4e5c..9ca5bfbacda 100644 --- a/examples/example-hostname/pom.xml +++ b/examples/example-hostname/pom.xml @@ -6,13 +6,13 @@ jar - 1.49.0-SNAPSHOT + 1.50.0-SNAPSHOT example-hostname https://github.com/grpc/grpc-java UTF-8 - 1.49.0-SNAPSHOT + 1.50.0-SNAPSHOT 3.21.1 1.7 diff --git a/examples/example-jwt-auth/build.gradle b/examples/example-jwt-auth/build.gradle index 924d8da8966..1d892bfb0d7 100644 --- a/examples/example-jwt-auth/build.gradle +++ b/examples/example-jwt-auth/build.gradle @@ -22,7 +22,7 @@ targetCompatibility = 1.8 // 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.49.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.21.1' def protocVersion = protobufVersion diff --git a/examples/example-jwt-auth/pom.xml b/examples/example-jwt-auth/pom.xml index 9e4d78ba820..b57eb8a801b 100644 --- a/examples/example-jwt-auth/pom.xml +++ b/examples/example-jwt-auth/pom.xml @@ -7,13 +7,13 @@ jar - 1.49.0-SNAPSHOT + 1.50.0-SNAPSHOT example-jwt-auth https://github.com/grpc/grpc-java UTF-8 - 1.49.0-SNAPSHOT + 1.50.0-SNAPSHOT 3.21.1 3.21.1 diff --git a/examples/example-orca/build.gradle b/examples/example-orca/build.gradle index ad53634bd21..2bbfc97e834 100644 --- a/examples/example-orca/build.gradle +++ b/examples/example-orca/build.gradle @@ -17,7 +17,7 @@ repositories { sourceCompatibility = 1.8 targetCompatibility = 1.8 -def grpcVersion = '1.49.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.21.1' dependencies { diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index 62329b2e3c4..895d6c98a7c 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -23,7 +23,7 @@ targetCompatibility = 1.8 // 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.49.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.21.1' dependencies { diff --git a/examples/example-tls/pom.xml b/examples/example-tls/pom.xml index 2557cd51843..3c9fe14d4fd 100644 --- a/examples/example-tls/pom.xml +++ b/examples/example-tls/pom.xml @@ -6,13 +6,13 @@ jar - 1.49.0-SNAPSHOT + 1.50.0-SNAPSHOT example-tls https://github.com/grpc/grpc-java UTF-8 - 1.49.0-SNAPSHOT + 1.50.0-SNAPSHOT 3.21.1 2.0.53.Final diff --git a/examples/example-xds/build.gradle b/examples/example-xds/build.gradle index 851937aa4a9..5f48215e876 100644 --- a/examples/example-xds/build.gradle +++ b/examples/example-xds/build.gradle @@ -22,7 +22,7 @@ targetCompatibility = 1.8 // 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.49.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION def nettyTcNativeVersion = '2.0.31.Final' def protocVersion = '3.21.1' diff --git a/examples/pom.xml b/examples/pom.xml index 386b4bddd3e..7aa05b7db95 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -6,13 +6,13 @@ jar - 1.49.0-SNAPSHOT + 1.50.0-SNAPSHOT examples https://github.com/grpc/grpc-java UTF-8 - 1.49.0-SNAPSHOT + 1.50.0-SNAPSHOT 3.21.1 3.21.1 From 2b50e405b19c99ca06b9d660945123a7780daa5f Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Fri, 5 Aug 2022 17:03:05 -0700 Subject: [PATCH 02/72] core: Remove LB refreshNameResolver check It's been 17 months since the check was introduced, which is plenty for the migration. Leaving ignoreRefreshNameResolutionCheck() in-place to let users delete their call sites. We'll remove the method after a few releases. Fixes #9409 --- api/src/main/java/io/grpc/LoadBalancer.java | 10 +- .../io/grpc/internal/ManagedChannelImpl.java | 18 --- .../util/ForwardingLoadBalancerHelper.java | 1 + .../grpc/internal/ManagedChannelImplTest.java | 117 ------------------ .../grpc/xds/ClusterResolverLoadBalancer.java | 2 - .../xds/ClusterResolverLoadBalancerTest.java | 2 - 6 files changed, 10 insertions(+), 140 deletions(-) diff --git a/api/src/main/java/io/grpc/LoadBalancer.java b/api/src/main/java/io/grpc/LoadBalancer.java index 8d74216b36f..3cef6190087 100644 --- a/api/src/main/java/io/grpc/LoadBalancer.java +++ b/api/src/main/java/io/grpc/LoadBalancer.java @@ -1073,8 +1073,10 @@ public void refreshNameResolution() { * that need to be updated for the new expected behavior. * * @since 1.38.0 + * @deprecated Warning has been removed */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/8088") + @Deprecated public void ignoreRefreshNameResolutionCheck() { // no-op } @@ -1356,13 +1358,19 @@ public interface SubchannelStateListener { * unnecessary delays of RPCs. Please refer to {@link PickResult#withSubchannel * PickResult.withSubchannel()}'s javadoc for more information. * + *

When a subchannel's state is IDLE or TRANSIENT_FAILURE and the address for the subchannel + * was received in {@link LoadBalancer#handleResolvedAddresses}, load balancers should call + * {@link Helper#refreshNameResolution} to inform polling name resolvers that it is an + * appropriate time to refresh the addresses. Without the refresh, changes to the addresses may + * never be detected. + * *

SHUTDOWN can only happen in two cases. One is that LoadBalancer called {@link * Subchannel#shutdown} earlier, thus it should have already discarded this Subchannel. The * other is that Channel is doing a {@link ManagedChannel#shutdownNow forced shutdown} or has * already terminated, thus there won't be further requests to LoadBalancer. Therefore, the * LoadBalancer usually don't need to react to a SHUTDOWN state. - * @param newState the new state * + * @param newState the new state * @since 1.22.0 */ void onSubchannelState(ConnectivityStateInfo newState); diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java index 3e2e3ec17fc..f7b82d1db17 100644 --- a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java +++ b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java @@ -1453,8 +1453,6 @@ void remove(RetriableStream retriableStream) { private final class LbHelperImpl extends LoadBalancer.Helper { AutoConfiguredLoadBalancer lb; - boolean nsRefreshedByLb; - boolean ignoreRefreshNsCheck; @Override public AbstractSubchannel createSubchannel(CreateSubchannelArgs args) { @@ -1493,7 +1491,6 @@ public void run() { @Override public void refreshNameResolution() { syncContext.throwIfNotInThisSynchronizationContext(); - nsRefreshedByLb = true; final class LoadBalancerRefreshNameResolution implements Runnable { @Override public void run() { @@ -1504,11 +1501,6 @@ public void run() { syncContext.execute(new LoadBalancerRefreshNameResolution()); } - @Override - public void ignoreRefreshNameResolutionCheck() { - ignoreRefreshNsCheck = true; - } - @Override public ManagedChannel createOobChannel(EquivalentAddressGroup addressGroup, String authority) { return createOobChannel(Collections.singletonList(addressGroup), authority); @@ -1979,16 +1971,6 @@ void onTerminated(InternalSubchannel is) { void onStateChange(InternalSubchannel is, ConnectivityStateInfo newState) { checkState(listener != null, "listener is null"); listener.onSubchannelState(newState); - if (newState.getState() == TRANSIENT_FAILURE || newState.getState() == IDLE) { - if (!helper.ignoreRefreshNsCheck && !helper.nsRefreshedByLb) { - logger.log(Level.WARNING, - "LoadBalancer should call Helper.refreshNameResolution() to refresh name " - + "resolution if subchannel state becomes TRANSIENT_FAILURE or IDLE. " - + "This will no longer happen automatically in the future releases"); - refreshAndResetNameResolution(); - helper.nsRefreshedByLb = true; - } - } } @Override diff --git a/core/src/main/java/io/grpc/util/ForwardingLoadBalancerHelper.java b/core/src/main/java/io/grpc/util/ForwardingLoadBalancerHelper.java index 05db207806d..f8e901205ee 100644 --- a/core/src/main/java/io/grpc/util/ForwardingLoadBalancerHelper.java +++ b/core/src/main/java/io/grpc/util/ForwardingLoadBalancerHelper.java @@ -95,6 +95,7 @@ public void refreshNameResolution() { } @Override + @Deprecated public void ignoreRefreshNameResolutionCheck() { delegate().ignoreRefreshNameResolutionCheck(); } diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java index b5c8ca0c98d..e293cb942ea 100644 --- a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java +++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java @@ -140,9 +140,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.LogRecord; import javax.annotation.Nullable; import org.junit.After; import org.junit.Assert; @@ -284,7 +281,6 @@ public String getPolicyName() { private boolean requestConnection = true; private BlockingQueue transports; private boolean panicExpected; - private final List logs = new ArrayList<>(); @Captor private ArgumentCaptor resolvedAddressCaptor; @@ -336,22 +332,6 @@ public void setUp() throws Exception { when(executorPool.getObject()).thenReturn(executor.getScheduledExecutorService()); when(balancerRpcExecutorPool.getObject()) .thenReturn(balancerRpcExecutor.getScheduledExecutorService()); - Handler handler = new Handler() { - @Override - public void publish(LogRecord record) { - logs.add(record); - } - - @Override - public void flush() { - } - - @Override - public void close() throws SecurityException { - } - }; - ManagedChannelImpl.logger.addHandler(handler); - ManagedChannelImpl.logger.setLevel(Level.ALL); channelBuilder = new ManagedChannelImplBuilder(TARGET, new UnsupportedClientTransportFactoryBuilder(), new FixedPortProvider(DEFAULT_PORT)); @@ -1591,103 +1571,6 @@ public void run() { timer.forwardTime(ManagedChannelImpl.SUBCHANNEL_SHUTDOWN_DELAY_SECONDS, TimeUnit.SECONDS); } - @Test - public void subchannelConnectionBroken_noLbRefreshingResolver_logWarningAndTriggeRefresh() { - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri) - .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress))) - .build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - createChannel(); - FakeNameResolverFactory.FakeNameResolver resolver = - Iterables.getOnlyElement(nameResolverFactory.resolvers); - assertThat(resolver.refreshCalled).isEqualTo(0); - - Subchannel subchannel = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - InternalSubchannel internalSubchannel = - (InternalSubchannel) subchannel.getInternalSubchannel(); - internalSubchannel.obtainActiveTransport(); - MockClientTransportInfo transportInfo = transports.poll(); - - // Break subchannel connection - transportInfo.listener.transportShutdown(Status.UNAVAILABLE.withDescription("unreachable")); - LogRecord log = Iterables.getOnlyElement(logs); - assertThat(log.getLevel()).isEqualTo(Level.WARNING); - assertThat(log.getMessage()).isEqualTo( - "LoadBalancer should call Helper.refreshNameResolution() to refresh name resolution if " - + "subchannel state becomes TRANSIENT_FAILURE or IDLE. This will no longer happen " - + "automatically in the future releases"); - assertThat(resolver.refreshCalled).isEqualTo(1); - } - - @Test - public void subchannelConnectionBroken_ResolverRefreshedByLb() { - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri) - .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress))) - .build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - createChannel(); - FakeNameResolverFactory.FakeNameResolver resolver = - Iterables.getOnlyElement(nameResolverFactory.resolvers); - assertThat(resolver.refreshCalled).isEqualTo(0); - ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(Helper.class); - verify(mockLoadBalancerProvider).newLoadBalancer(helperCaptor.capture()); - helper = helperCaptor.getValue(); - - SubchannelStateListener listener = new SubchannelStateListener() { - @Override - public void onSubchannelState(ConnectivityStateInfo newState) { - // Normal LoadBalancer should refresh name resolution when some subchannel enters - // TRANSIENT_FAILURE or IDLE - if (newState.getState() == TRANSIENT_FAILURE || newState.getState() == IDLE) { - helper.refreshNameResolution(); - } - } - }; - Subchannel subchannel = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, listener); - InternalSubchannel internalSubchannel = - (InternalSubchannel) subchannel.getInternalSubchannel(); - internalSubchannel.obtainActiveTransport(); - MockClientTransportInfo transportInfo = transports.poll(); - - // Break subchannel connection and simulate load balancer refreshing name resolution - transportInfo.listener.transportShutdown(Status.UNAVAILABLE.withDescription("unreachable")); - assertThat(logs).isEmpty(); - assertThat(resolver.refreshCalled).isEqualTo(1); - } - - @Test - public void subchannelConnectionBroken_ignoreRefreshNameResolutionCheck_noRefresh() { - FakeNameResolverFactory nameResolverFactory = - new FakeNameResolverFactory.Builder(expectedUri) - .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress))) - .build(); - channelBuilder.nameResolverFactory(nameResolverFactory); - createChannel(); - FakeNameResolverFactory.FakeNameResolver resolver = - Iterables.getOnlyElement(nameResolverFactory.resolvers); - assertThat(resolver.refreshCalled).isEqualTo(0); - ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(Helper.class); - verify(mockLoadBalancerProvider).newLoadBalancer(helperCaptor.capture()); - helper = helperCaptor.getValue(); - helper.ignoreRefreshNameResolutionCheck(); - - Subchannel subchannel = - createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); - InternalSubchannel internalSubchannel = - (InternalSubchannel) subchannel.getInternalSubchannel(); - internalSubchannel.obtainActiveTransport(); - MockClientTransportInfo transportInfo = transports.poll(); - - // Break subchannel connection - transportInfo.listener.transportShutdown(Status.UNAVAILABLE.withDescription("unreachable")); - assertThat(logs).isEmpty(); - assertThat(resolver.refreshCalled).isEqualTo(0); - } - @Test public void subchannelStringableBeforeStart() { createChannel(); diff --git a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java index 108f406ade8..91da43eb24a 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java @@ -285,10 +285,8 @@ private void handleEndpointResolutionError() { private final class RefreshableHelper extends ForwardingLoadBalancerHelper { private final Helper delegate; - @SuppressWarnings("deprecation") private RefreshableHelper(Helper delegate) { this.delegate = checkNotNull(delegate, "delegate"); - delegate.ignoreRefreshNameResolutionCheck(); } @Override diff --git a/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java index 087b64b55e9..22c54bbb94a 100644 --- a/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java @@ -679,7 +679,6 @@ public void onlyLogicalDnsCluster_handleRefreshNameResolution() { EquivalentAddressGroup endpoint2 = makeAddress("endpoint-addr-2"); resolver.deliverEndpointAddresses(Arrays.asList(endpoint1, endpoint2)); assertThat(resolver.refreshCount).isEqualTo(0); - verify(helper).ignoreRefreshNameResolutionCheck(); FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers); childBalancer.helper.refreshNameResolution(); assertThat(resolver.refreshCount).isEqualTo(1); @@ -745,7 +744,6 @@ public void onlyLogicalDnsCluster_refreshNameResolutionRaceWithResolutionError() FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers); assertAddressesEqual(Collections.singletonList(endpoint), childBalancer.addresses); assertThat(resolver.refreshCount).isEqualTo(0); - verify(helper).ignoreRefreshNameResolutionCheck(); childBalancer.helper.refreshNameResolution(); assertThat(resolver.refreshCount).isEqualTo(1); From 61f19d707a4c6fd4f5e327b8fceb09658b9cb452 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Wed, 10 Aug 2022 12:41:57 -0700 Subject: [PATCH 03/72] Swap Animalsniffer to Java 8 and Android 19 Also added missing signatures. Swapping to version catalog will make this process easier in the future. --- alts/build.gradle | 2 +- api/build.gradle | 4 ++-- auth/build.gradle | 4 ++-- authz/build.gradle | 2 +- benchmarks/build.gradle | 3 +++ census/build.gradle | 5 +++++ core/build.gradle | 4 ++-- gae-interop-testing/gae-jdk8/build.gradle | 3 +++ gcp-observability/build.gradle | 2 +- googleapis/build.gradle | 2 +- gradle/libs.versions.toml | 2 ++ grpclb/build.gradle | 3 +++ interop-testing/build.gradle | 4 ++++ istio-interop-testing/build.gradle | 3 +++ netty/build.gradle | 3 ++- netty/shaded/build.gradle | 3 +++ okhttp/build.gradle | 4 ++-- protobuf-lite/build.gradle | 4 ++-- protobuf/build.gradle | 3 ++- rls/build.gradle | 2 +- services/build.gradle | 2 +- stub/build.gradle | 4 ++-- testing-proto/build.gradle | 2 ++ testing/build.gradle | 4 ++++ xds/build.gradle | 2 +- 25 files changed, 55 insertions(+), 21 deletions(-) diff --git a/alts/build.gradle b/alts/build.gradle index 90d2f0516de..926a3d4993b 100644 --- a/alts/build.gradle +++ b/alts/build.gradle @@ -45,7 +45,7 @@ dependencies { classifier = "linux-x86_64" } } - signature 'org.codehaus.mojo.signature:java17:1.0@signature' + signature libraries.signature.java } configureProtoCompilation() diff --git a/api/build.gradle b/api/build.gradle index f38bbd38d6c..05cd80674c3 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -24,8 +24,8 @@ dependencies { } jmh project(':grpc-core') - signature "org.codehaus.mojo.signature:java17:1.0@signature" - signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature" + signature libraries.signature.java + signature libraries.signature.android } tasks.named("javadoc").configure { diff --git a/auth/build.gradle b/auth/build.gradle index e9c9d0b4aad..73c108d5203 100644 --- a/auth/build.gradle +++ b/auth/build.gradle @@ -13,6 +13,6 @@ dependencies { implementation libraries.guava testImplementation project(':grpc-testing'), libraries.google.auth.oauth2Http - signature "org.codehaus.mojo.signature:java17:1.0@signature" - signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature" + signature libraries.signature.java + signature libraries.signature.android } diff --git a/authz/build.gradle b/authz/build.gradle index d0126922aca..ce327f14e14 100644 --- a/authz/build.gradle +++ b/authz/build.gradle @@ -26,7 +26,7 @@ dependencies { shadow configurations.implementation.getDependencies().minus([xdsDependency]) shadow project(path: ':grpc-xds', configuration: 'shadow') - signature "org.codehaus.mojo.signature:java17:1.0@signature" + signature libraries.signature.java } tasks.named("jar").configure { diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index 17977692ecb..90251e6692f 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -5,6 +5,7 @@ plugins { id "com.google.protobuf" id "me.champeau.jmh" + id "ru.vyarus.animalsniffer" } description = "grpc Benchmarks" @@ -39,6 +40,8 @@ dependencies { testImplementation libraries.junit, libraries.mockito.core + + signature libraries.signature.java } import net.ltgt.gradle.errorprone.CheckSeverity diff --git a/census/build.gradle b/census/build.gradle index d09d0b18996..02b7e6395b1 100644 --- a/census/build.gradle +++ b/census/build.gradle @@ -1,6 +1,8 @@ plugins { id "java-library" id "maven-publish" + + id "ru.vyarus.animalsniffer" } description = 'gRPC: Census' @@ -18,6 +20,9 @@ dependencies { project(':grpc-core').sourceSets.test.output, project(':grpc-testing'), libraries.opencensus.impl + + signature libraries.signature.java + signature libraries.signature.android } tasks.named("javadoc").configure { diff --git a/core/build.gradle b/core/build.gradle index d242802dfcf..9778c035f5c 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -42,8 +42,8 @@ dependencies { jmh project(':grpc-testing') - signature "org.codehaus.mojo.signature:java17:1.0@signature" - signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature" + signature libraries.signature.java + signature libraries.signature.android } tasks.named("javadoc").configure { diff --git a/gae-interop-testing/gae-jdk8/build.gradle b/gae-interop-testing/gae-jdk8/build.gradle index 6000c092353..03d603bd3cf 100644 --- a/gae-interop-testing/gae-jdk8/build.gradle +++ b/gae-interop-testing/gae-jdk8/build.gradle @@ -27,6 +27,8 @@ buildscript { plugins { id "java" id "war" + + id "ru.vyarus.animalsniffer" } description = 'gRPC: gae interop testing (jdk8)' @@ -53,6 +55,7 @@ dependencies { implementation libraries.junit implementation libraries.protobuf.java runtimeOnly libraries.netty.tcnative, libraries.netty.tcnative.classes + signature libraries.signature.java } tasks.named("compileJava").configure { diff --git a/gcp-observability/build.gradle b/gcp-observability/build.gradle index c4de44ed44f..f03d6f9620a 100644 --- a/gcp-observability/build.gradle +++ b/gcp-observability/build.gradle @@ -50,7 +50,7 @@ dependencies { exclude group: 'junit', module: 'junit' } - signature "org.codehaus.mojo.signature:java18:1.0@signature" + signature libraries.signature.java } configureProtoCompilation() diff --git a/googleapis/build.gradle b/googleapis/build.gradle index 69449e8a02b..d829b1d28e9 100644 --- a/googleapis/build.gradle +++ b/googleapis/build.gradle @@ -15,7 +15,7 @@ dependencies { libraries.guava testImplementation project(':grpc-core').sourceSets.test.output - signature "org.codehaus.mojo.signature:java17:1.0@signature" + signature libraries.signature.java } publishing { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 809bb230b75..c053c9d1bcc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -68,4 +68,6 @@ protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version. protobuf-protoc = { module = "com.google.protobuf:protoc", version.ref = "protobuf" } re2j = "com.google.re2j:re2j:1.6" robolectric = "org.robolectric:robolectric:4.8.1" +signature-android = "net.sf.androidscents.signature:android-api-level-19:4.4.2_r4" +signature-java = "org.codehaus.mojo.signature:java18:1.0" truth = "com.google.truth:truth:1.0.1" diff --git a/grpclb/build.gradle b/grpclb/build.gradle index 4ae38180aa9..6fd64c84299 100644 --- a/grpclb/build.gradle +++ b/grpclb/build.gradle @@ -4,6 +4,7 @@ plugins { id "com.google.protobuf" id "me.champeau.gradle.japicmp" + id "ru.vyarus.animalsniffer" } description = "gRPC: GRPCLB LoadBalancer plugin" @@ -21,6 +22,8 @@ dependencies { compileOnly libraries.javax.annotation testImplementation libraries.truth, project(':grpc-core').sourceSets.test.output + + signature libraries.signature.java } configureProtoCompilation() diff --git a/interop-testing/build.gradle b/interop-testing/build.gradle index 04f3c658fd3..f225eee95e0 100644 --- a/interop-testing/build.gradle +++ b/interop-testing/build.gradle @@ -5,6 +5,7 @@ plugins { id "com.github.johnrengelman.shadow" id "com.google.protobuf" + id "ru.vyarus.animalsniffer" } description = "gRPC: Integration Testing" @@ -56,6 +57,9 @@ dependencies { libraries.okhttp alpnagent libraries.jetty.alpn.agent shadow configurations.implementation.getDependencies().minus(xdsDependency) + + signature libraries.signature.java + signature libraries.signature.android } configureProtoCompilation() diff --git a/istio-interop-testing/build.gradle b/istio-interop-testing/build.gradle index d53e9b5c52e..7ba342c6884 100644 --- a/istio-interop-testing/build.gradle +++ b/istio-interop-testing/build.gradle @@ -4,6 +4,7 @@ plugins { id "com.google.protobuf" id 'com.google.cloud.tools.jib' + id "ru.vyarus.animalsniffer" } description = "gRPC: Istio Interop testing" @@ -34,6 +35,8 @@ dependencies { libraries.junit, libraries.truth alpnagent libraries.jetty.alpn.agent + + signature libraries.signature.java } sourceSets { diff --git a/netty/build.gradle b/netty/build.gradle index caee6ef3075..013d962ed37 100644 --- a/netty/build.gradle +++ b/netty/build.gradle @@ -63,7 +63,8 @@ dependencies { classifier = "linux-x86_64" } } - signature "org.codehaus.mojo.signature:java17:1.0@signature" + signature libraries.signature.java + signature libraries.signature.android alpnagent libraries.jetty.alpn.agent } diff --git a/netty/shaded/build.gradle b/netty/shaded/build.gradle index 17ff297f844..0a9be812d7b 100644 --- a/netty/shaded/build.gradle +++ b/netty/shaded/build.gradle @@ -9,6 +9,7 @@ plugins { id "maven-publish" id "com.github.johnrengelman.shadow" + id "ru.vyarus.animalsniffer" } description = "gRPC: Netty Shaded" @@ -61,6 +62,8 @@ dependencies { shadow project(':grpc-netty').configurations.runtimeClasspath.allDependencies.matching { it.group != 'io.netty' } + signature libraries.signature.java + signature libraries.signature.android } tasks.named("jar").configure { diff --git a/okhttp/build.gradle b/okhttp/build.gradle index 994e4ce2b45..a044503510d 100644 --- a/okhttp/build.gradle +++ b/okhttp/build.gradle @@ -23,8 +23,8 @@ dependencies { project(':grpc-testing'), libraries.netty.codec.http2, libraries.okhttp - signature "org.codehaus.mojo.signature:java17:1.0@signature" - signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature" + signature libraries.signature.java + signature libraries.signature.android } project.sourceSets { diff --git a/protobuf-lite/build.gradle b/protobuf-lite/build.gradle index aeafb86a37d..0ada691913c 100644 --- a/protobuf-lite/build.gradle +++ b/protobuf-lite/build.gradle @@ -17,8 +17,8 @@ dependencies { testImplementation project(':grpc-core') - signature "org.codehaus.mojo.signature:java17:1.0@signature" - signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature" + signature libraries.signature.java + signature libraries.signature.android } tasks.named("compileTestJava").configure { diff --git a/protobuf/build.gradle b/protobuf/build.gradle index a49ba75d741..93b379daf43 100644 --- a/protobuf/build.gradle +++ b/protobuf/build.gradle @@ -25,7 +25,8 @@ dependencies { exclude group: 'com.google.protobuf', module: 'protobuf-javalite' } - signature "org.codehaus.mojo.signature:java17:1.0@signature" + signature libraries.signature.java + signature libraries.signature.android } tasks.named("javadoc").configure { diff --git a/rls/build.gradle b/rls/build.gradle index 8ce80f413ec..ddd8cb65870 100644 --- a/rls/build.gradle +++ b/rls/build.gradle @@ -23,7 +23,7 @@ dependencies { project(':grpc-testing'), project(':grpc-testing-proto'), project(':grpc-core').sourceSets.test.output // for FakeClock - signature "org.codehaus.mojo.signature:java17:1.0@signature" + signature libraries.signature.java } tasks.named("compileJava").configure { diff --git a/services/build.gradle b/services/build.gradle index 5def836954a..fdc0f35322b 100644 --- a/services/build.gradle +++ b/services/build.gradle @@ -32,7 +32,7 @@ dependencies { libraries.netty.transport.epoll, // for DomainSocketAddress project(':grpc-core').sourceSets.test.output // for FakeClock testCompileOnly libraries.javax.annotation - signature "org.codehaus.mojo.signature:java17:1.0@signature" + signature libraries.signature.java } configureProtoCompilation() diff --git a/stub/build.gradle b/stub/build.gradle index 9b6f50e1849..16a9ca2d995 100644 --- a/stub/build.gradle +++ b/stub/build.gradle @@ -13,8 +13,8 @@ dependencies { implementation libraries.errorprone.annotations testImplementation libraries.truth, project(':grpc-testing') - signature "org.codehaus.mojo.signature:java17:1.0@signature" - signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature" + signature libraries.signature.java + signature libraries.signature.android } tasks.named("javadoc").configure { diff --git a/testing-proto/build.gradle b/testing-proto/build.gradle index 5ee06fec597..168c059e66c 100644 --- a/testing-proto/build.gradle +++ b/testing-proto/build.gradle @@ -3,6 +3,7 @@ plugins { id "maven-publish" id "com.google.protobuf" + id "ru.vyarus.animalsniffer" } description = "gRPC: Testing Protos" @@ -13,6 +14,7 @@ dependencies { compileOnly libraries.javax.annotation testImplementation libraries.truth testRuntimeOnly libraries.javax.annotation + signature libraries.signature.java } configureProtoCompilation() diff --git a/testing/build.gradle b/testing/build.gradle index a2b6cec5c1f..35da61f11fc 100644 --- a/testing/build.gradle +++ b/testing/build.gradle @@ -3,6 +3,7 @@ plugins { id "maven-publish" id "me.champeau.gradle.japicmp" + id "ru.vyarus.animalsniffer" } description = "gRPC: Testing" @@ -24,6 +25,9 @@ dependencies { testImplementation project(':grpc-testing-proto'), project(':grpc-core').sourceSets.test.output + + signature libraries.signature.java + signature libraries.signature.android } tasks.named("javadoc").configure { exclude 'io/grpc/internal/**' } diff --git a/xds/build.gradle b/xds/build.gradle index 04613218a66..764dc530d97 100644 --- a/xds/build.gradle +++ b/xds/build.gradle @@ -77,7 +77,7 @@ dependencies { shadow configurations.implementation.getDependencies().minus([nettyDependency]) shadow project(path: ':grpc-netty-shaded', configuration: 'shadow') - signature "org.codehaus.mojo.signature:java17:1.0@signature" + signature libraries.signature.java testRuntimeOnly libraries.netty.tcnative, libraries.netty.tcnative.classes testRuntimeOnly (libraries.netty.tcnative) { From 91aada316a6fd79db243d798f8155dc301b02ca0 Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Wed, 10 Aug 2022 14:19:27 -0700 Subject: [PATCH 04/72] gcp-observability: implement exclusion of cloud backend RPCs for all 3 signals (#9427) * gcp-observability: implement exclusion of cloud backend RPCs for all 3 signals by using a ConditionalClientInterceptor that conditionally delegates --- .../gcp/observability/GcpObservability.java | 19 +++- .../ConditionalClientInterceptor.java | 52 +++++++++++ .../gcp/observability/logging/GcpLogSink.java | 16 ++-- .../observability/GcpObservabilityTest.java | 65 +++++++++++++- .../grpc/gcp/observability/LoggingTest.java | 5 +- .../ConditionalClientInterceptorTest.java | 89 +++++++++++++++++++ .../observability/logging/GcpLogSinkTest.java | 22 +++-- 7 files changed, 250 insertions(+), 18 deletions(-) create mode 100644 gcp-observability/src/main/java/io/grpc/gcp/observability/interceptors/ConditionalClientInterceptor.java create mode 100644 gcp-observability/src/test/java/io/grpc/gcp/observability/interceptors/ConditionalClientInterceptorTest.java diff --git a/gcp-observability/src/main/java/io/grpc/gcp/observability/GcpObservability.java b/gcp-observability/src/main/java/io/grpc/gcp/observability/GcpObservability.java index e077e12a2a6..83601582ef5 100644 --- a/gcp-observability/src/main/java/io/grpc/gcp/observability/GcpObservability.java +++ b/gcp-observability/src/main/java/io/grpc/gcp/observability/GcpObservability.java @@ -19,6 +19,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableSet; import io.grpc.ClientInterceptor; import io.grpc.ExperimentalApi; import io.grpc.InternalGlobalInterceptors; @@ -27,6 +28,7 @@ import io.grpc.ServerStreamTracer; import io.grpc.census.InternalCensusStatsAccessor; import io.grpc.census.InternalCensusTracingAccessor; +import io.grpc.gcp.observability.interceptors.ConditionalClientInterceptor; import io.grpc.gcp.observability.interceptors.ConfigFilterHelper; import io.grpc.gcp.observability.interceptors.InternalLoggingChannelInterceptor; import io.grpc.gcp.observability.interceptors.InternalLoggingServerInterceptor; @@ -57,6 +59,9 @@ public final class GcpObservability implements AutoCloseable { private static final Logger logger = Logger.getLogger(GcpObservability.class.getName()); private static final int METRICS_EXPORT_INTERVAL = 30; + private static final ImmutableSet SERVICES_TO_EXCLUDE = ImmutableSet.of( + "google.logging.v2.LoggingServiceV2", "google.monitoring.v3.MetricService", + "google.devtools.cloudtrace.v2.TraceService"); private static GcpObservability instance = null; private final Sink sink; private final ObservabilityConfig config; @@ -77,7 +82,7 @@ public static synchronized GcpObservability grpcInit() throws IOException { ObservabilityConfigImpl observabilityConfig = ObservabilityConfigImpl.getInstance(); Sink sink = new GcpLogSink(observabilityConfig.getDestinationProjectId(), globalLocationTags.getLocationTags(), observabilityConfig.getCustomTags(), - observabilityConfig.getFlushMessageCount()); + observabilityConfig.getFlushMessageCount(), SERVICES_TO_EXCLUDE); LogHelper helper = new LogHelper(sink, TimeProvider.SYSTEM_TIME_PROVIDER); ConfigFilterHelper configFilterHelper = ConfigFilterHelper.factory(observabilityConfig); instance = grpcInit(sink, observabilityConfig, @@ -126,13 +131,14 @@ private void setProducer( serverInterceptors.add(serverInterceptorFactory.create()); } if (config.isEnableCloudMonitoring()) { - clientInterceptors.add( - InternalCensusStatsAccessor.getClientInterceptor(true, true, true, true)); + clientInterceptors.add(getConditionalInterceptor( + InternalCensusStatsAccessor.getClientInterceptor(true, true, true, true))); tracerFactories.add( InternalCensusStatsAccessor.getServerStreamTracerFactory(true, true, true)); } if (config.isEnableCloudTracing()) { - clientInterceptors.add(InternalCensusTracingAccessor.getClientInterceptor()); + clientInterceptors.add( + getConditionalInterceptor(InternalCensusTracingAccessor.getClientInterceptor())); tracerFactories.add(InternalCensusTracingAccessor.getServerStreamTracerFactory()); } @@ -140,6 +146,11 @@ private void setProducer( clientInterceptors, serverInterceptors, tracerFactories); } + static ConditionalClientInterceptor getConditionalInterceptor(ClientInterceptor interceptor) { + return new ConditionalClientInterceptor(interceptor, + (m, c) -> !SERVICES_TO_EXCLUDE.contains(m.getServiceName())); + } + @VisibleForTesting void registerStackDriverExporter(String projectId, Map customTags) throws IOException { diff --git a/gcp-observability/src/main/java/io/grpc/gcp/observability/interceptors/ConditionalClientInterceptor.java b/gcp-observability/src/main/java/io/grpc/gcp/observability/interceptors/ConditionalClientInterceptor.java new file mode 100644 index 00000000000..5051453ce0e --- /dev/null +++ b/gcp-observability/src/main/java/io/grpc/gcp/observability/interceptors/ConditionalClientInterceptor.java @@ -0,0 +1,52 @@ +/* + * Copyright 2022 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.gcp.observability.interceptors; + +import static com.google.common.base.Preconditions.checkNotNull; + +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.Internal; +import io.grpc.MethodDescriptor; +import java.util.function.BiPredicate; + +/** + * A client interceptor that conditionally calls a delegated interceptor. + */ +@Internal +public final class ConditionalClientInterceptor implements ClientInterceptor { + + private final ClientInterceptor delegate; + private final BiPredicate, CallOptions> predicate; + + public ConditionalClientInterceptor(ClientInterceptor delegate, + BiPredicate, CallOptions> predicate) { + this.delegate = checkNotNull(delegate, "delegate"); + this.predicate = checkNotNull(predicate, "predicate"); + } + + @Override + public ClientCall interceptCall(MethodDescriptor method, + CallOptions callOptions, Channel next) { + if (!predicate.test(method, callOptions)) { + return next.newCall(method, callOptions); + } + return delegate.interceptCall(method, callOptions, next); + } +} diff --git a/gcp-observability/src/main/java/io/grpc/gcp/observability/logging/GcpLogSink.java b/gcp-observability/src/main/java/io/grpc/gcp/observability/logging/GcpLogSink.java index 9b198250afb..8275b1ec048 100644 --- a/gcp-observability/src/main/java/io/grpc/gcp/observability/logging/GcpLogSink.java +++ b/gcp-observability/src/main/java/io/grpc/gcp/observability/logging/GcpLogSink.java @@ -16,6 +16,8 @@ package io.grpc.gcp.observability.logging; +import static com.google.common.base.Preconditions.checkNotNull; + import com.google.cloud.MonitoredResource; import com.google.cloud.logging.LogEntry; import com.google.cloud.logging.Logging; @@ -31,6 +33,7 @@ import io.grpc.internal.JsonParser; import io.grpc.observabilitylog.v1.GrpcLogRecord; import java.io.IOException; +import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Objects; @@ -45,8 +48,6 @@ public class GcpLogSink implements Sink { private final Logger logger = Logger.getLogger(GcpLogSink.class.getName()); - // TODO(DNVindhya): Make cloud logging service a configurable value - private static final String SERVICE_TO_EXCLUDE = "google.logging.v2.LoggingServiceV2"; private static final String DEFAULT_LOG_NAME = "microservices.googleapis.com%2Fobservability%2Fgrpc"; private static final String K8S_MONITORED_RESOURCE_TYPE = "k8s_container"; @@ -62,11 +63,12 @@ public class GcpLogSink implements Sink { * logging APIs also uses gRPC. */ private volatile Logging gcpLoggingClient; private long flushCounter; + private final Collection servicesToExclude; @VisibleForTesting GcpLogSink(Logging loggingClient, String destinationProjectId, Map locationTags, - Map customTags, Long flushLimit) { - this(destinationProjectId, locationTags, customTags, flushLimit); + Map customTags, Long flushLimit, Collection servicesToExclude) { + this(destinationProjectId, locationTags, customTags, flushLimit, servicesToExclude); this.gcpLoggingClient = loggingClient; } @@ -74,14 +76,16 @@ public class GcpLogSink implements Sink { * Retrieves a single instance of GcpLogSink. * * @param destinationProjectId cloud project id to write logs + * @param servicesToExclude service names for which log entries should not be generated */ public GcpLogSink(String destinationProjectId, Map locationTags, - Map customTags, Long flushLimit) { + Map customTags, Long flushLimit, Collection servicesToExclude) { this.projectId = destinationProjectId; this.customTags = getCustomTags(customTags, locationTags, destinationProjectId); this.kubernetesResource = getResource(locationTags); this.flushLimit = flushLimit != null ? flushLimit : FALLBACK_FLUSH_LIMIT; this.flushCounter = 0L; + this.servicesToExclude = checkNotNull(servicesToExclude, "servicesToExclude"); } /** @@ -98,7 +102,7 @@ public void write(GrpcLogRecord logProto) { } } } - if (SERVICE_TO_EXCLUDE.equals(logProto.getServiceName())) { + if (servicesToExclude.contains(logProto.getServiceName())) { return; } try { diff --git a/gcp-observability/src/test/java/io/grpc/gcp/observability/GcpObservabilityTest.java b/gcp-observability/src/test/java/io/grpc/gcp/observability/GcpObservabilityTest.java index 97e9031631b..b18fe741ae2 100644 --- a/gcp-observability/src/test/java/io/grpc/gcp/observability/GcpObservabilityTest.java +++ b/gcp-observability/src/test/java/io/grpc/gcp/observability/GcpObservabilityTest.java @@ -19,8 +19,11 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import static org.mockito.AdditionalAnswers.delegatesTo; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import io.grpc.CallOptions; @@ -34,11 +37,13 @@ import io.grpc.ServerCallHandler; import io.grpc.ServerInterceptor; import io.grpc.StaticTestingClassLoader; +import io.grpc.gcp.observability.interceptors.ConditionalClientInterceptor; import io.grpc.gcp.observability.interceptors.InternalLoggingChannelInterceptor; import io.grpc.gcp.observability.interceptors.InternalLoggingServerInterceptor; import io.grpc.gcp.observability.logging.Sink; import io.opencensus.trace.samplers.Samplers; import java.io.IOException; +import java.util.List; import java.util.regex.Pattern; import org.junit.Test; import org.junit.runner.RunWith; @@ -77,6 +82,61 @@ public void disableObservability() throws Exception { ((Runnable) runnable.getDeclaredConstructor().newInstance()).run(); } + @Test + @SuppressWarnings("unchecked") + public void conditionalInterceptor() { + ClientInterceptor delegate = mock(ClientInterceptor.class); + Channel channel = mock(Channel.class); + ClientCall returnedCall = mock(ClientCall.class); + + ConditionalClientInterceptor conditionalClientInterceptor + = GcpObservability.getConditionalInterceptor( + delegate); + MethodDescriptor method = MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName("google.logging.v2.LoggingServiceV2/method") + .setRequestMarshaller(mock(MethodDescriptor.Marshaller.class)) + .setResponseMarshaller(mock(MethodDescriptor.Marshaller.class)) + .build(); + doReturn(returnedCall).when(channel).newCall(method, CallOptions.DEFAULT); + ClientCall clientCall = conditionalClientInterceptor.interceptCall(method, + CallOptions.DEFAULT, channel); + verifyNoInteractions(delegate); + assertThat(clientCall).isSameInstanceAs(returnedCall); + + method = MethodDescriptor.newBuilder().setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName("google.monitoring.v3.MetricService/method2") + .setRequestMarshaller(mock(MethodDescriptor.Marshaller.class)) + .setResponseMarshaller(mock(MethodDescriptor.Marshaller.class)) + .build(); + doReturn(returnedCall).when(channel).newCall(method, CallOptions.DEFAULT); + clientCall = conditionalClientInterceptor.interceptCall(method, CallOptions.DEFAULT, channel); + verifyNoInteractions(delegate); + assertThat(clientCall).isSameInstanceAs(returnedCall); + + method = MethodDescriptor.newBuilder().setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName("google.devtools.cloudtrace.v2.TraceService/method3") + .setRequestMarshaller(mock(MethodDescriptor.Marshaller.class)) + .setResponseMarshaller(mock(MethodDescriptor.Marshaller.class)) + .build(); + doReturn(returnedCall).when(channel).newCall(method, CallOptions.DEFAULT); + clientCall = conditionalClientInterceptor.interceptCall(method, CallOptions.DEFAULT, channel); + verifyNoInteractions(delegate); + assertThat(clientCall).isSameInstanceAs(returnedCall); + + reset(channel); + ClientCall interceptedCall = mock(ClientCall.class); + method = MethodDescriptor.newBuilder().setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName("some.other.random.service/method4") + .setRequestMarshaller(mock(MethodDescriptor.Marshaller.class)) + .setResponseMarshaller(mock(MethodDescriptor.Marshaller.class)) + .build(); + doReturn(interceptedCall).when(delegate).interceptCall(method, CallOptions.DEFAULT, channel); + clientCall = conditionalClientInterceptor.interceptCall(method, CallOptions.DEFAULT, channel); + verifyNoInteractions(channel); + assertThat(clientCall).isSameInstanceAs(interceptedCall); + } + // UsedReflectively public static final class StaticTestingClassInitFinish implements Runnable { @@ -137,7 +197,10 @@ public void run() { try (GcpObservability unused = GcpObservability.grpcInit( sink, config, channelInterceptorFactory, serverInterceptorFactory)) { - assertThat(InternalGlobalInterceptors.getClientInterceptors()).hasSize(3); + List list = InternalGlobalInterceptors.getClientInterceptors(); + assertThat(list).hasSize(3); + assertThat(list.get(1)).isInstanceOf(ConditionalClientInterceptor.class); + assertThat(list.get(2)).isInstanceOf(ConditionalClientInterceptor.class); assertThat(InternalGlobalInterceptors.getServerInterceptors()).hasSize(1); assertThat(InternalGlobalInterceptors.getServerStreamTracerFactories()).hasSize(2); } catch (Exception e) { diff --git a/gcp-observability/src/test/java/io/grpc/gcp/observability/LoggingTest.java b/gcp-observability/src/test/java/io/grpc/gcp/observability/LoggingTest.java index b4dea62c047..aa6c2d55d8b 100644 --- a/gcp-observability/src/test/java/io/grpc/gcp/observability/LoggingTest.java +++ b/gcp-observability/src/test/java/io/grpc/gcp/observability/LoggingTest.java @@ -42,6 +42,7 @@ import io.grpc.testing.GrpcCleanupRule; import io.grpc.testing.protobuf.SimpleServiceGrpc; import java.io.IOException; +import java.util.Collections; import java.util.regex.Pattern; import org.junit.ClassRule; import org.junit.Ignore; @@ -110,7 +111,9 @@ public static final class StaticTestingClassEndtoEndLogging implements Runnable @Override public void run() { - Sink sink = new GcpLogSink(PROJECT_ID, LOCATION_TAGS, CUSTOM_TAGS, FLUSH_LIMIT); + Sink sink = + new GcpLogSink( + PROJECT_ID, LOCATION_TAGS, CUSTOM_TAGS, FLUSH_LIMIT, Collections.emptySet()); ObservabilityConfig config = mock(ObservabilityConfig.class); LogHelper spyLogHelper = spy(new LogHelper(sink, TimeProvider.SYSTEM_TIME_PROVIDER)); ConfigFilterHelper mockFilterHelper = mock(ConfigFilterHelper.class); diff --git a/gcp-observability/src/test/java/io/grpc/gcp/observability/interceptors/ConditionalClientInterceptorTest.java b/gcp-observability/src/test/java/io/grpc/gcp/observability/interceptors/ConditionalClientInterceptorTest.java new file mode 100644 index 00000000000..5fdd2e185bd --- /dev/null +++ b/gcp-observability/src/test/java/io/grpc/gcp/observability/interceptors/ConditionalClientInterceptorTest.java @@ -0,0 +1,89 @@ +/* + * Copyright 2022 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.gcp.observability.interceptors; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.MethodDescriptor; +import io.grpc.MethodDescriptor.MethodType; +import java.util.function.BiPredicate; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link ConditionalClientInterceptor}. + */ +@RunWith(JUnit4.class) +public class ConditionalClientInterceptorTest { + + private ConditionalClientInterceptor conditionalClientInterceptor; + @Mock private ClientInterceptor delegate; + @Mock private BiPredicate, CallOptions> predicate; + @Mock private Channel channel; + @Mock private ClientCall returnedCall; + private MethodDescriptor method; + + @Before + @SuppressWarnings("unchecked") + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + conditionalClientInterceptor = new ConditionalClientInterceptor( + delegate, predicate); + method = MethodDescriptor.newBuilder().setType(MethodType.UNARY) + .setFullMethodName("service/method") + .setRequestMarshaller(mock(MethodDescriptor.Marshaller.class)) + .setResponseMarshaller(mock(MethodDescriptor.Marshaller.class)) + .build(); + } + + @Test + @SuppressWarnings("unchecked") + public void predicateFalse() { + when(predicate.test(any(MethodDescriptor.class), any(CallOptions.class))).thenReturn(false); + doReturn(returnedCall).when(channel).newCall(method, CallOptions.DEFAULT); + ClientCall clientCall = conditionalClientInterceptor.interceptCall(method, + CallOptions.DEFAULT, channel); + assertThat(clientCall).isSameInstanceAs(returnedCall); + verify(delegate, never()).interceptCall(any(MethodDescriptor.class), any(CallOptions.class), + any(Channel.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void predicateTrue() { + when(predicate.test(any(MethodDescriptor.class), any(CallOptions.class))).thenReturn(true); + doReturn(returnedCall).when(delegate).interceptCall(method, CallOptions.DEFAULT, channel); + ClientCall clientCall = conditionalClientInterceptor.interceptCall(method, + CallOptions.DEFAULT, channel); + assertThat(clientCall).isSameInstanceAs(returnedCall); + verify(channel, never()).newCall(any(MethodDescriptor.class), any(CallOptions.class)); + } +} diff --git a/gcp-observability/src/test/java/io/grpc/gcp/observability/logging/GcpLogSinkTest.java b/gcp-observability/src/test/java/io/grpc/gcp/observability/logging/GcpLogSinkTest.java index 031313c7304..cea081b9b55 100644 --- a/gcp-observability/src/test/java/io/grpc/gcp/observability/logging/GcpLogSinkTest.java +++ b/gcp-observability/src/test/java/io/grpc/gcp/observability/logging/GcpLogSinkTest.java @@ -21,6 +21,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; import com.google.cloud.MonitoredResource; @@ -35,6 +36,7 @@ import io.grpc.observabilitylog.v1.GrpcLogRecord.EventLogger; import io.grpc.observabilitylog.v1.GrpcLogRecord.EventType; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -105,7 +107,7 @@ public class GcpLogSinkTest { @SuppressWarnings("unchecked") public void verifyWrite() throws Exception { GcpLogSink sink = new GcpLogSink(mockLogging, DEST_PROJECT_NAME, LOCATION_TAGS, - CUSTOM_TAGS, FLUSH_LIMIT); + CUSTOM_TAGS, FLUSH_LIMIT, Collections.emptySet()); sink.write(LOG_PROTO); ArgumentCaptor> logEntrySetCaptor = ArgumentCaptor.forClass( @@ -123,7 +125,7 @@ public void verifyWrite() throws Exception { @SuppressWarnings("unchecked") public void verifyWriteWithTags() { GcpLogSink sink = new GcpLogSink(mockLogging, DEST_PROJECT_NAME, LOCATION_TAGS, - CUSTOM_TAGS, FLUSH_LIMIT); + CUSTOM_TAGS, FLUSH_LIMIT, Collections.emptySet()); MonitoredResource expectedMonitoredResource = GcpLogSink.getResource(LOCATION_TAGS); sink.write(LOG_PROTO); @@ -147,7 +149,7 @@ public void emptyCustomTags_labelsNotSet() { Map emptyCustomTags = null; Map expectedEmptyLabels = new HashMap<>(); GcpLogSink sink = new GcpLogSink(mockLogging, DEST_PROJECT_NAME, LOCATION_TAGS, - emptyCustomTags, FLUSH_LIMIT); + emptyCustomTags, FLUSH_LIMIT, Collections.emptySet()); sink.write(LOG_PROTO); ArgumentCaptor> logEntrySetCaptor = ArgumentCaptor.forClass( @@ -168,7 +170,7 @@ public void emptyCustomTags_setSourceProject() { Map expectedLabels = GcpLogSink.getCustomTags(emptyCustomTags, LOCATION_TAGS, destinationProjectId); GcpLogSink sink = new GcpLogSink(mockLogging, destinationProjectId, LOCATION_TAGS, - emptyCustomTags, FLUSH_LIMIT); + emptyCustomTags, FLUSH_LIMIT, Collections.emptySet()); sink.write(LOG_PROTO); ArgumentCaptor> logEntrySetCaptor = ArgumentCaptor.forClass( @@ -185,7 +187,7 @@ public void emptyCustomTags_setSourceProject() { public void verifyFlush() { long lowerFlushLimit = 2L; GcpLogSink sink = new GcpLogSink(mockLogging, DEST_PROJECT_NAME, LOCATION_TAGS, - CUSTOM_TAGS, lowerFlushLimit); + CUSTOM_TAGS, lowerFlushLimit, Collections.emptySet()); sink.write(LOG_PROTO); verify(mockLogging, never()).flush(); sink.write(LOG_PROTO); @@ -198,11 +200,19 @@ public void verifyFlush() { @Test public void verifyClose() throws Exception { GcpLogSink sink = new GcpLogSink(mockLogging, DEST_PROJECT_NAME, LOCATION_TAGS, - CUSTOM_TAGS, FLUSH_LIMIT); + CUSTOM_TAGS, FLUSH_LIMIT, Collections.emptySet()); sink.write(LOG_PROTO); verify(mockLogging, times(1)).write(anyIterable()); sink.close(); verify(mockLogging).close(); verifyNoMoreInteractions(mockLogging); } + + @Test + public void verifyExclude() throws Exception { + Sink mockSink = new GcpLogSink(mockLogging, DEST_PROJECT_NAME, LOCATION_TAGS, + CUSTOM_TAGS, FLUSH_LIMIT, Collections.singleton("service")); + mockSink.write(LOG_PROTO); + verifyNoInteractions(mockLogging); + } } From 7169b174e600c42b65ab3b670f978fc7890e1d43 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Thu, 11 Aug 2022 13:20:51 -0700 Subject: [PATCH 05/72] auth: Copy quota project id when creating Self Signed JWT creds from Service Account Creds (#9438) --- .../java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java | 5 +++++ .../io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java b/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java index 4b95a6c7f4d..d01109249a4 100644 --- a/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java +++ b/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java @@ -298,6 +298,11 @@ public JwtHelper(Class rawServiceAccountClass, ClassLoader loader) Method setter = builderClass.getMethod("setPrivateKeyId", getter.getReturnType()); methodPairs.add(new MethodPair(getter, setter)); } + { + Method getter = serviceAccountClass.getMethod("getQuotaProjectId"); + Method setter = builderClass.getMethod("setQuotaProjectId", getter.getReturnType()); + methodPairs.add(new MethodPair(getter, setter)); + } } /** diff --git a/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java b/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java index ee5713bfd27..cbb2afdc3d0 100644 --- a/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java +++ b/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java @@ -379,6 +379,7 @@ public void jwtAccessCredentialsInRequestMetadata() throws Exception { .setClientEmail("test-email@example.com") .setPrivateKey(pair.getPrivate()) .setPrivateKeyId("test-private-key-id") + .setQuotaProjectId("test-quota-project-id") .build(); GoogleAuthLibraryCallCredentials callCredentials = new GoogleAuthLibraryCallCredentials(credentials); @@ -401,6 +402,10 @@ public void jwtAccessCredentialsInRequestMetadata() throws Exception { || "https://example.com:123/a.service".equals(payload.get("aud"))); assertEquals("test-email@example.com", payload.get("iss")); assertEquals("test-email@example.com", payload.get("sub")); + + Metadata.Key quotaProject = Metadata.Key + .of("X-Goog-User-Project", Metadata.ASCII_STRING_MARSHALLER); + assertEquals("test-quota-project-id", Iterables.getOnlyElement(headers.getAll(quotaProject))); } private int runPendingRunnables() { From 03430c786aa8556178236dfb5cf6cf41d24c6b18 Mon Sep 17 00:00:00 2001 From: DNVindhya Date: Fri, 12 Aug 2022 12:20:43 -0700 Subject: [PATCH 06/72] remove unregistering stackdriver exporters (#9442) --- .../gcp/observability/GcpObservability.java | 30 ------------------- .../gcp/observability/logging/GcpLogSink.java | 1 + 2 files changed, 1 insertion(+), 30 deletions(-) diff --git a/gcp-observability/src/main/java/io/grpc/gcp/observability/GcpObservability.java b/gcp-observability/src/main/java/io/grpc/gcp/observability/GcpObservability.java index 83601582ef5..5337a933fed 100644 --- a/gcp-observability/src/main/java/io/grpc/gcp/observability/GcpObservability.java +++ b/gcp-observability/src/main/java/io/grpc/gcp/observability/GcpObservability.java @@ -50,14 +50,11 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; import java.util.stream.Collectors; /** The main class for gRPC Google Cloud Platform Observability features. */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/8869") public final class GcpObservability implements AutoCloseable { - private static final Logger logger = Logger.getLogger(GcpObservability.class.getName()); private static final int METRICS_EXPORT_INTERVAL = 30; private static final ImmutableSet SERVICES_TO_EXCLUDE = ImmutableSet.of( "google.logging.v2.LoggingServiceV2", "google.monitoring.v3.MetricService", @@ -68,8 +65,6 @@ public final class GcpObservability implements AutoCloseable { private final ArrayList clientInterceptors = new ArrayList<>(); private final ArrayList serverInterceptors = new ArrayList<>(); private final ArrayList tracerFactories = new ArrayList<>(); - private boolean metricsEnabled; - private boolean tracesEnabled; /** * Initialize grpc-observability. @@ -115,7 +110,6 @@ public void close() { if (instance == null) { throw new IllegalStateException("GcpObservability already closed!"); } - unRegisterStackDriverExporter(); sink.close(); instance = null; } @@ -169,7 +163,6 @@ void registerStackDriverExporter(String projectId, Map customTag } statsConfigurationBuilder.setExportInterval(Duration.create(METRICS_EXPORT_INTERVAL, 0)); StackdriverStatsExporter.createAndRegister(statsConfigurationBuilder.build()); - metricsEnabled = true; } if (config.isEnableCloudTracing()) { @@ -188,29 +181,6 @@ void registerStackDriverExporter(String projectId, Map customTag traceConfigurationBuilder.setFixedAttributes(fixedAttributes); } StackdriverTraceExporter.createAndRegister(traceConfigurationBuilder.build()); - tracesEnabled = true; - } - } - - private void unRegisterStackDriverExporter() { - if (metricsEnabled) { - try { - StackdriverStatsExporter.unregister(); - } catch (IllegalStateException e) { - logger.log( - Level.SEVERE, "Failed to unregister Stackdriver stats exporter, " + e.getMessage()); - } - metricsEnabled = false; - } - - if (tracesEnabled) { - try { - StackdriverTraceExporter.unregister(); - } catch (IllegalStateException e) { - logger.log( - Level.SEVERE, "Failed to unregister Stackdriver trace exporter, " + e.getMessage()); - } - tracesEnabled = false; } } diff --git a/gcp-observability/src/main/java/io/grpc/gcp/observability/logging/GcpLogSink.java b/gcp-observability/src/main/java/io/grpc/gcp/observability/logging/GcpLogSink.java index 8275b1ec048..0209677aae9 100644 --- a/gcp-observability/src/main/java/io/grpc/gcp/observability/logging/GcpLogSink.java +++ b/gcp-observability/src/main/java/io/grpc/gcp/observability/logging/GcpLogSink.java @@ -148,6 +148,7 @@ static Map getCustomTags(Map customTags, ImmutableMap.Builder tagsBuilder = ImmutableMap.builder(); String sourceProjectId = locationTags.get("project_id"); if (!Strings.isNullOrEmpty(destinationProjectId) + && !Strings.isNullOrEmpty(sourceProjectId) && !Objects.equals(sourceProjectId, destinationProjectId)) { tagsBuilder.put("source_project_id", sourceProjectId); } From 01d5bd47cb0bf19965d31abb05dc5bab84a028c8 Mon Sep 17 00:00:00 2001 From: Larry Safran <107004254+larry-safran@users.noreply.github.com> Date: Mon, 15 Aug 2022 11:06:31 -0700 Subject: [PATCH 07/72] Cleanup some of the warnings across the code base (#9445) No logic changes, just cleans up warnings to make spotting real problems easier. Remove "public" declarations on interfaces Remove duplicate semicolons (Java lines ending in ";;") Remove unneeded import Change non-javadoc comment to not start with "/**" Remove unneeded explicit type declarations from generics Fix broken javadoc links --- .../main/java/io/grpc/alts/AltsContext.java | 1 - .../alts/internal/AltsHandshakerStub.java | 12 +- .../io/grpc/alts/internal/TsiHandshaker.java | 2 +- .../main/java/io/grpc/InternalMetadata.java | 3 +- api/src/main/java/io/grpc/Metadata.java | 2 +- .../io/grpc/census/CensusStatsModule.java | 6 +- .../io/grpc/census/CensusTracingModule.java | 2 +- .../java/io/grpc/internal/BackoffPolicy.java | 2 +- .../io/grpc/internal/DelayedClientCall.java | 1 + .../java/io/grpc/internal/InternalServer.java | 4 +- .../PickFirstLoadBalancerProvider.java | 1 + .../java/io/grpc/internal/StreamListener.java | 2 +- .../grpc/util/AdvancedTlsX509KeyManager.java | 3 +- .../util/AdvancedTlsX509TrustManager.java | 3 +- .../ServiceConfigErrorHandlingTest.java | 6 +- .../observability/GcpObservabilityTest.java | 2 +- .../io/grpc/netty/AbstractNettyHandler.java | 2 +- .../io/grpc/netty/NettyClientTransport.java | 2 +- .../WriteBufferingAndExceptionHandler.java | 2 +- .../java/io/grpc/xds/XdsNameResolver.java | 4 +- .../io/grpc/xds/internal/sds/Closeable.java | 2 +- .../java/io/grpc/xds/XdsNameResolverTest.java | 250 +++++++++--------- 22 files changed, 162 insertions(+), 152 deletions(-) diff --git a/alts/src/main/java/io/grpc/alts/AltsContext.java b/alts/src/main/java/io/grpc/alts/AltsContext.java index f264ad112d7..7680de4160e 100644 --- a/alts/src/main/java/io/grpc/alts/AltsContext.java +++ b/alts/src/main/java/io/grpc/alts/AltsContext.java @@ -20,7 +20,6 @@ import io.grpc.alts.internal.AltsInternalContext; import io.grpc.alts.internal.HandshakerResult; import io.grpc.alts.internal.Identity; -import io.grpc.alts.internal.SecurityLevel; /** {@code AltsContext} contains security-related information on the ALTS channel. */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/7864") diff --git a/alts/src/main/java/io/grpc/alts/internal/AltsHandshakerStub.java b/alts/src/main/java/io/grpc/alts/internal/AltsHandshakerStub.java index 61d9fd2f894..bb2ff9dbc4d 100644 --- a/alts/src/main/java/io/grpc/alts/internal/AltsHandshakerStub.java +++ b/alts/src/main/java/io/grpc/alts/internal/AltsHandshakerStub.java @@ -64,12 +64,18 @@ public HandshakerResp send(HandshakerReq req) throws InterruptedException, IOExc if (!responseQueue.isEmpty()) { throw new IOException("Received an unexpected response."); } + writer.onNext(req); Optional result = responseQueue.take(); - if (!result.isPresent()) { - maybeThrowIoException(); + if (result.isPresent()) { + return result.get(); + } + + if (exceptionMessage.get() != null) { + throw new IOException(exceptionMessage.get()); + } else { + throw new IOException("No handshaker response received"); } - return result.get(); } /** Create a new writer if the writer is null. */ diff --git a/alts/src/main/java/io/grpc/alts/internal/TsiHandshaker.java b/alts/src/main/java/io/grpc/alts/internal/TsiHandshaker.java index 35b945770d2..c6da647168f 100644 --- a/alts/src/main/java/io/grpc/alts/internal/TsiHandshaker.java +++ b/alts/src/main/java/io/grpc/alts/internal/TsiHandshaker.java @@ -86,7 +86,7 @@ public interface TsiHandshaker { * * @return the extracted peer. */ - public Object extractPeerObject() throws GeneralSecurityException; + Object extractPeerObject() throws GeneralSecurityException; /** * Creates a frame protector from a completed handshake. No other methods may be called after the diff --git a/api/src/main/java/io/grpc/InternalMetadata.java b/api/src/main/java/io/grpc/InternalMetadata.java index 2823882952f..cbf6b72aaf0 100644 --- a/api/src/main/java/io/grpc/InternalMetadata.java +++ b/api/src/main/java/io/grpc/InternalMetadata.java @@ -19,6 +19,7 @@ import com.google.common.io.BaseEncoding; import io.grpc.Metadata.AsciiMarshaller; import io.grpc.Metadata.BinaryStreamMarshaller; +import java.io.InputStream; import java.nio.charset.Charset; /** @@ -100,7 +101,7 @@ public static Object[] serializePartial(Metadata md) { /** * Creates a holder for a pre-parsed value read by the transport. * - * @param marshaller The {@link Metadata#BinaryStreamMarshaller} associated with this value. + * @param marshaller The {@link Metadata.BinaryStreamMarshaller} associated with this value. * @param value The value to store. * @return an object holding the pre-parsed value for this key. */ diff --git a/api/src/main/java/io/grpc/Metadata.java b/api/src/main/java/io/grpc/Metadata.java index 9c2a2227f8c..6311762601c 100644 --- a/api/src/main/java/io/grpc/Metadata.java +++ b/api/src/main/java/io/grpc/Metadata.java @@ -211,8 +211,8 @@ private int len() { return size * 2; } + /** checks when {@link #namesAndValues} is null or has no elements. */ private boolean isEmpty() { - /** checks when {@link #namesAndValues} is null or has no elements */ return size == 0; } diff --git a/census/src/main/java/io/grpc/census/CensusStatsModule.java b/census/src/main/java/io/grpc/census/CensusStatsModule.java index 366be55de68..1d016abc89d 100644 --- a/census/src/main/java/io/grpc/census/CensusStatsModule.java +++ b/census/src/main/java/io/grpc/census/CensusStatsModule.java @@ -188,7 +188,7 @@ private static final class ClientTracer extends ClientStreamTracer { @Nullable private static final AtomicLongFieldUpdater inboundUncompressedSizeUpdater; - /** + /* * 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. @@ -268,7 +268,7 @@ public void streamCreated(Attributes transportAttrs, Metadata headers) { } @Override - @SuppressWarnings("NonAtomicVolatileUpdate") + @SuppressWarnings({"NonAtomicVolatileUpdate", "NonAtomicOperationOnVolatileField"}) public void outboundWireSize(long bytes) { if (outboundWireSizeUpdater != null) { outboundWireSizeUpdater.getAndAdd(this, bytes); @@ -562,7 +562,7 @@ private static final class ServerTracer extends ServerStreamTracer { @Nullable private static final AtomicLongFieldUpdater inboundUncompressedSizeUpdater; - /** + /* * 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. diff --git a/census/src/main/java/io/grpc/census/CensusTracingModule.java b/census/src/main/java/io/grpc/census/CensusTracingModule.java index b507db10c64..ab56feff045 100644 --- a/census/src/main/java/io/grpc/census/CensusTracingModule.java +++ b/census/src/main/java/io/grpc/census/CensusTracingModule.java @@ -65,7 +65,7 @@ final class CensusTracingModule { @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. diff --git a/core/src/main/java/io/grpc/internal/BackoffPolicy.java b/core/src/main/java/io/grpc/internal/BackoffPolicy.java index cdca4a22606..c80ef9e1f9d 100644 --- a/core/src/main/java/io/grpc/internal/BackoffPolicy.java +++ b/core/src/main/java/io/grpc/internal/BackoffPolicy.java @@ -20,7 +20,7 @@ * Determines how long to wait before doing some action (typically a retry, or a reconnect). */ public interface BackoffPolicy { - public interface Provider { + interface Provider { BackoffPolicy get(); } diff --git a/core/src/main/java/io/grpc/internal/DelayedClientCall.java b/core/src/main/java/io/grpc/internal/DelayedClientCall.java index df56fc0c5f7..f529b0d0a0f 100644 --- a/core/src/main/java/io/grpc/internal/DelayedClientCall.java +++ b/core/src/main/java/io/grpc/internal/DelayedClientCall.java @@ -121,6 +121,7 @@ private ScheduledFuture scheduleDeadlineIfNeeded( buf.append(seconds); buf.append(String.format(Locale.US, ".%09d", nanos)); buf.append("s. "); + /** Cancels the call if deadline exceeded prior to the real call being set. */ class DeadlineExceededRunnable implements Runnable { @Override diff --git a/core/src/main/java/io/grpc/internal/InternalServer.java b/core/src/main/java/io/grpc/internal/InternalServer.java index 0445ae3dfab..a6079081233 100644 --- a/core/src/main/java/io/grpc/internal/InternalServer.java +++ b/core/src/main/java/io/grpc/internal/InternalServer.java @@ -50,7 +50,7 @@ public interface InternalServer { void shutdown(); /** - * Returns the first listening socket address. May change after {@link start(ServerListener)} is + * Returns the first listening socket address. May change after {@link #start(ServerListener)} is * called. */ SocketAddress getListenSocketAddress(); @@ -61,7 +61,7 @@ public interface InternalServer { @Nullable InternalInstrumented getListenSocketStats(); /** - * Returns a list of listening socket addresses. May change after {@link start(ServerListener)} + * Returns a list of listening socket addresses. May change after {@link #start(ServerListener)} * is called. */ List getListenSocketAddresses(); diff --git a/core/src/main/java/io/grpc/internal/PickFirstLoadBalancerProvider.java b/core/src/main/java/io/grpc/internal/PickFirstLoadBalancerProvider.java index 0b11af94e2d..7f7b366564e 100644 --- a/core/src/main/java/io/grpc/internal/PickFirstLoadBalancerProvider.java +++ b/core/src/main/java/io/grpc/internal/PickFirstLoadBalancerProvider.java @@ -18,6 +18,7 @@ import io.grpc.LoadBalancer; import io.grpc.LoadBalancerProvider; +import io.grpc.NameResolver; import io.grpc.NameResolver.ConfigOrError; import java.util.Map; diff --git a/core/src/main/java/io/grpc/internal/StreamListener.java b/core/src/main/java/io/grpc/internal/StreamListener.java index 090c0555b0a..a893a9c84b6 100644 --- a/core/src/main/java/io/grpc/internal/StreamListener.java +++ b/core/src/main/java/io/grpc/internal/StreamListener.java @@ -59,6 +59,6 @@ interface MessageProducer { * messages until the producer returns null, at which point the producer may be discarded. */ @Nullable - public InputStream next(); + InputStream next(); } } diff --git a/core/src/main/java/io/grpc/util/AdvancedTlsX509KeyManager.java b/core/src/main/java/io/grpc/util/AdvancedTlsX509KeyManager.java index 9c9102b12cb..1530834d609 100644 --- a/core/src/main/java/io/grpc/util/AdvancedTlsX509KeyManager.java +++ b/core/src/main/java/io/grpc/util/AdvancedTlsX509KeyManager.java @@ -246,7 +246,8 @@ private UpdateResult readAndUpdate(File keyFile, File certFile, long oldKeyTime, * Mainly used to avoid throwing IO Exceptions in java.io.Closeable. */ public interface Closeable extends java.io.Closeable { - @Override public void close(); + @Override + void close(); } } diff --git a/core/src/main/java/io/grpc/util/AdvancedTlsX509TrustManager.java b/core/src/main/java/io/grpc/util/AdvancedTlsX509TrustManager.java index 51bf57aeb34..7465e632104 100644 --- a/core/src/main/java/io/grpc/util/AdvancedTlsX509TrustManager.java +++ b/core/src/main/java/io/grpc/util/AdvancedTlsX509TrustManager.java @@ -295,7 +295,8 @@ private long readAndUpdate(File trustCertFile, long oldTime) // Mainly used to avoid throwing IO Exceptions in java.io.Closeable. public interface Closeable extends java.io.Closeable { - @Override public void close(); + @Override + void close(); } public static Builder newBuilder() { diff --git a/core/src/test/java/io/grpc/internal/ServiceConfigErrorHandlingTest.java b/core/src/test/java/io/grpc/internal/ServiceConfigErrorHandlingTest.java index 16c6f3bf302..6abb477878a 100644 --- a/core/src/test/java/io/grpc/internal/ServiceConfigErrorHandlingTest.java +++ b/core/src/test/java/io/grpc/internal/ServiceConfigErrorHandlingTest.java @@ -236,7 +236,7 @@ public void cleanUp() { public void emptyAddresses_validConfig_firstResolution_lbNeedsAddress() throws Exception { FakeNameResolverFactory nameResolverFactory = new FakeNameResolverFactory.Builder(expectedUri) - .setServers(Collections.emptyList()) + .setServers(Collections.emptyList()) .build(); channelBuilder.nameResolverFactory(nameResolverFactory); @@ -299,7 +299,7 @@ public void emptyAddresses_validConfig_2ndResolution_lbNeedsAddress() throws Exc public void emptyAddresses_validConfig_lbDoesNotNeedAddress() throws Exception { FakeNameResolverFactory nameResolverFactory = new FakeNameResolverFactory.Builder(expectedUri) - .setServers(Collections.emptyList()) + .setServers(Collections.emptyList()) .build(); channelBuilder.nameResolverFactory(nameResolverFactory); when(mockLoadBalancer.canHandleEmptyAddressListFromNameResolution()).thenReturn(true); @@ -316,7 +316,7 @@ public void emptyAddresses_validConfig_lbDoesNotNeedAddress() throws Exception { ResolvedAddresses resolvedAddresses = resultCaptor.getValue(); assertThat(resolvedAddresses.getAddresses()).isEmpty(); - assertThat(resolvedAddresses.getLoadBalancingPolicyConfig()).isEqualTo("val");; + assertThat(resolvedAddresses.getLoadBalancingPolicyConfig()).isEqualTo("val"); verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); assertThat(channel.getState(false)).isNotEqualTo(ConnectivityState.TRANSIENT_FAILURE); diff --git a/gcp-observability/src/test/java/io/grpc/gcp/observability/GcpObservabilityTest.java b/gcp-observability/src/test/java/io/grpc/gcp/observability/GcpObservabilityTest.java index b18fe741ae2..c42d7b65c08 100644 --- a/gcp-observability/src/test/java/io/grpc/gcp/observability/GcpObservabilityTest.java +++ b/gcp-observability/src/test/java/io/grpc/gcp/observability/GcpObservabilityTest.java @@ -223,7 +223,7 @@ public void run() { InternalLoggingChannelInterceptor.Factory channelInterceptorFactory = mock(InternalLoggingChannelInterceptor.Factory.class); InternalLoggingServerInterceptor.Factory serverInterceptorFactory = - mock(InternalLoggingServerInterceptor.Factory.class);; + mock(InternalLoggingServerInterceptor.Factory.class); try (GcpObservability unused = GcpObservability.grpcInit( diff --git a/netty/src/main/java/io/grpc/netty/AbstractNettyHandler.java b/netty/src/main/java/io/grpc/netty/AbstractNettyHandler.java index ab66472105a..c94c05ffafd 100644 --- a/netty/src/main/java/io/grpc/netty/AbstractNettyHandler.java +++ b/netty/src/main/java/io/grpc/netty/AbstractNettyHandler.java @@ -238,7 +238,7 @@ void setDataSizeAndSincePing(int dataSize) { /** Controls whether PINGs like those for BDP are permitted to be sent at the current time. */ public interface PingLimiter { - public boolean isPingAllowed(); + boolean isPingAllowed(); } private static final class AllowPingLimiter implements PingLimiter { diff --git a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java index a7a1044059c..dbfa8cf7cab 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java @@ -251,7 +251,7 @@ public Runnable start(Listener transportListener) { ChannelHandler bufferingHandler = new WriteBufferingAndExceptionHandler(negotiationHandler); - /** + /* * We don't use a ChannelInitializer in the client bootstrap because its "initChannel" method * is executed in the event loop and we need this handler to be in the pipeline immediately so * that it may begin buffering writes. diff --git a/netty/src/main/java/io/grpc/netty/WriteBufferingAndExceptionHandler.java b/netty/src/main/java/io/grpc/netty/WriteBufferingAndExceptionHandler.java index 100367625fa..2799dfccb61 100644 --- a/netty/src/main/java/io/grpc/netty/WriteBufferingAndExceptionHandler.java +++ b/netty/src/main/java/io/grpc/netty/WriteBufferingAndExceptionHandler.java @@ -184,7 +184,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { */ @Override public void flush(ChannelHandlerContext ctx) { - /** + /* * Swallowing any flushes is not only an optimization but also required * for the SslHandler to work correctly. If the SslHandler receives multiple * flushes while the handshake is still ongoing, then the handshake "randomly" diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java index ff9e4a10eb1..d29132d1b25 100644 --- a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java +++ b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java @@ -822,7 +822,7 @@ private void updateRoutes(List virtualHosts, long httpMaxStreamDura existingClusters == null ? clusters : Sets.difference(clusters, existingClusters); Set deletedClusters = existingClusters == null - ? Collections.emptySet() : Sets.difference(existingClusters, clusters); + ? Collections.emptySet() : Sets.difference(existingClusters, clusters); existingClusters = clusters; for (String cluster : addedClusters) { if (clusterRefs.containsKey(cluster)) { @@ -982,7 +982,7 @@ private static class RoutingConfig { final Map virtualHostOverrideConfig; private static RoutingConfig empty = new RoutingConfig( - 0L, Collections.emptyList(), null, Collections.emptyMap()); + 0, Collections.emptyList(), null, Collections.emptyMap()); private RoutingConfig( long fallbackTimeoutNano, List routes, @Nullable List filterChain, diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/Closeable.java b/xds/src/main/java/io/grpc/xds/internal/sds/Closeable.java index c3695cecaf3..3ac8734894a 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/Closeable.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/Closeable.java @@ -19,5 +19,5 @@ public interface Closeable extends java.io.Closeable { @Override - public void close(); + void close(); } diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java index 25bd866424d..8f4a2cf1774 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java @@ -274,7 +274,7 @@ public void resolving_targetAuthorityInAuthoritiesMap() { .authorities( ImmutableMap.of(targetAuthority, AuthorityInfo.create( "xdstp://" + targetAuthority + "/envoy.config.listener.v3.Listener/%s?foo=1&bar=2", - ImmutableList.of(ServerInfo.create( + ImmutableList.of(ServerInfo.create( "td.googleapis.com", InsecureChannelCredentials.create(), true))))) .build(); expectedLdsResourceName = "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/" @@ -299,12 +299,12 @@ public void resolving_ldsResourceNotFound() { public void resolving_ldsResourceUpdateRdsName() { Route route1 = Route.forAction(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( - cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), - ImmutableMap.of()); + cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), + ImmutableMap.of()); Route route2 = Route.forAction(RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), RouteAction.forCluster( - cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(20L), null), - ImmutableMap.of()); + cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(20L), null), + ImmutableMap.of()); resolver.start(mockListener); FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); @@ -313,7 +313,7 @@ public void resolving_ldsResourceUpdateRdsName() { VirtualHost virtualHost = VirtualHost.create("virtualhost", Collections.singletonList(AUTHORITY), Collections.singletonList(route1), - ImmutableMap.of()); + ImmutableMap.of()); xdsClient.deliverRdsUpdate(RDS_RESOURCE_NAME, Collections.singletonList(virtualHost)); verify(mockListener).onResult(resolutionResultCaptor.capture()); assertServiceConfigForLoadBalancingConfig( @@ -329,7 +329,7 @@ public void resolving_ldsResourceUpdateRdsName() { virtualHost = VirtualHost.create("virtualhost-alter", Collections.singletonList(AUTHORITY), Collections.singletonList(route2), - ImmutableMap.of()); + ImmutableMap.of()); xdsClient.deliverRdsUpdate(alternativeRdsResource, Collections.singletonList(virtualHost)); // Two new service config updates triggered: // - with load balancing config being able to select cluster1 and cluster2 @@ -357,8 +357,8 @@ public void resolving_rdsResourceNotFound() { public void resolving_ldsResourceRevokedAndAddedBack() { Route route = Route.forAction(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( - cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), - ImmutableMap.of()); + cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), + ImmutableMap.of()); resolver.start(mockListener); FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); @@ -367,7 +367,7 @@ public void resolving_ldsResourceRevokedAndAddedBack() { VirtualHost virtualHost = VirtualHost.create("virtualhost", Collections.singletonList(AUTHORITY), Collections.singletonList(route), - ImmutableMap.of()); + ImmutableMap.of()); xdsClient.deliverRdsUpdate(RDS_RESOURCE_NAME, Collections.singletonList(virtualHost)); verify(mockListener).onResult(resolutionResultCaptor.capture()); assertServiceConfigForLoadBalancingConfig( @@ -396,8 +396,8 @@ public void resolving_ldsResourceRevokedAndAddedBack() { public void resolving_rdsResourceRevokedAndAddedBack() { Route route = Route.forAction(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( - cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), - ImmutableMap.of()); + cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), + ImmutableMap.of()); resolver.start(mockListener); FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); @@ -406,7 +406,7 @@ public void resolving_rdsResourceRevokedAndAddedBack() { VirtualHost virtualHost = VirtualHost.create("virtualhost", Collections.singletonList(AUTHORITY), Collections.singletonList(route), - ImmutableMap.of()); + ImmutableMap.of()); xdsClient.deliverRdsUpdate(RDS_RESOURCE_NAME, Collections.singletonList(virtualHost)); verify(mockListener).onResult(resolutionResultCaptor.capture()); assertServiceConfigForLoadBalancingConfig( @@ -473,12 +473,12 @@ public void resolving_encounterErrorLdsAndRdsWatchers() { public void resolving_matchingVirtualHostNotFound_matchingOverrideAuthority() { Route route = Route.forAction(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( - cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), - ImmutableMap.of()); + cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), + ImmutableMap.of()); VirtualHost virtualHost = VirtualHost.create("virtualhost", Collections.singletonList("random"), Collections.singletonList(route), - ImmutableMap.of()); + ImmutableMap.of()); resolver = new XdsNameResolver(null, AUTHORITY, "random", serviceConfigParser, syncContext, scheduler, @@ -496,12 +496,12 @@ public void resolving_matchingVirtualHostNotFound_matchingOverrideAuthority() { public void resolving_matchingVirtualHostNotFound_notMatchingOverrideAuthority() { Route route = Route.forAction(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( - cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), - ImmutableMap.of()); + cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), + ImmutableMap.of()); VirtualHost virtualHost = VirtualHost.create("virtualhost", Collections.singletonList(AUTHORITY), Collections.singletonList(route), - ImmutableMap.of()); + ImmutableMap.of()); resolver = new XdsNameResolver(null, AUTHORITY, "random", serviceConfigParser, syncContext, scheduler, @@ -543,19 +543,19 @@ public void resolving_matchingVirtualHostNotFoundInRdsResource() { private List buildUnmatchedVirtualHosts() { Route route1 = Route.forAction(RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), RouteAction.forCluster( - cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), - ImmutableMap.of()); + cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), + ImmutableMap.of()); Route route2 = Route.forAction(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( - cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), - ImmutableMap.of()); + cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), + ImmutableMap.of()); return Arrays.asList( VirtualHost.create("virtualhost-foo", Collections.singletonList("hello.googleapis.com"), Collections.singletonList(route1), - ImmutableMap.of()), + ImmutableMap.of()), VirtualHost.create("virtualhost-bar", Collections.singletonList("hi.googleapis.com"), Collections.singletonList(route2), - ImmutableMap.of())); + ImmutableMap.of())); } @Test @@ -564,11 +564,11 @@ public void resolved_noTimeout() { FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); Route route = Route.forAction(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( - cluster1, Collections.emptyList(), null, null), // per-route timeout unset - ImmutableMap.of()); + cluster1, Collections.emptyList(), null, null), // per-route timeout unset + ImmutableMap.of()); VirtualHost virtualHost = VirtualHost.create("does not matter", Collections.singletonList(AUTHORITY), Collections.singletonList(route), - ImmutableMap.of()); + ImmutableMap.of()); xdsClient.deliverLdsUpdate(0L, Collections.singletonList(virtualHost)); verify(mockListener).onResult(resolutionResultCaptor.capture()); ResolutionResult result = resolutionResultCaptor.getValue(); @@ -582,11 +582,11 @@ public void resolved_fallbackToHttpMaxStreamDurationAsTimeout() { FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); Route route = Route.forAction(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( - cluster1, Collections.emptyList(), null, null), // per-route timeout unset - ImmutableMap.of()); + cluster1, Collections.emptyList(), null, null), // per-route timeout unset + ImmutableMap.of()); VirtualHost virtualHost = VirtualHost.create("does not matter", Collections.singletonList(AUTHORITY), Collections.singletonList(route), - ImmutableMap.of()); + ImmutableMap.of()); xdsClient.deliverLdsUpdate(TimeUnit.SECONDS.toNanos(5L), Collections.singletonList(virtualHost)); verify(mockListener).onResult(resolutionResultCaptor.capture()); @@ -612,10 +612,10 @@ public void retryPolicyInPerMethodConfigGeneratedByResolverIsValid() { RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( cluster1, - Collections.emptyList(), + Collections.emptyList(), null, retryPolicy), - ImmutableMap.of()))); + ImmutableMap.of()))); verify(mockListener).onResult(resolutionResultCaptor.capture()); ResolutionResult result = resolutionResultCaptor.getValue(); InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY); @@ -668,12 +668,12 @@ public void resolved_simpleCallFailedToRoute_routeWithNonForwardingAction() { Arrays.asList( Route.forNonForwardingAction( RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), - ImmutableMap.of()), + ImmutableMap.of()), Route.forAction( RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), - RouteAction.forCluster(cluster2, Collections.emptyList(), + RouteAction.forCluster(cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), - ImmutableMap.of()))); + ImmutableMap.of()))); verify(mockListener).onResult(resolutionResultCaptor.capture()); ResolutionResult result = resolutionResultCaptor.getValue(); assertThat(result.getAddresses()).isEmpty(); @@ -709,7 +709,7 @@ public void resolved_rpcHashingByHeader_withoutSubstitution() { HashPolicy.forHeader(false, "custom-key", null, null)), null, null), - ImmutableMap.of()))); + ImmutableMap.of()))); verify(mockListener).onResult(resolutionResultCaptor.capture()); InternalConfigSelector configSelector = resolutionResultCaptor.getValue().getAttributes().get(InternalConfigSelector.KEY); @@ -743,7 +743,7 @@ public void resolved_rpcHashingByHeader_withSubstitution() { HashPolicy.forHeader(false, "custom-key", Pattern.compile("value"), "val")), null, null), - ImmutableMap.of()))); + ImmutableMap.of()))); verify(mockListener).onResult(resolutionResultCaptor.capture()); InternalConfigSelector configSelector = resolutionResultCaptor.getValue().getAttributes().get(InternalConfigSelector.KEY); @@ -782,7 +782,7 @@ public void resolved_rpcHashingByChannelId() { Collections.singletonList(HashPolicy.forChannelId(false)), null, null), - ImmutableMap.of()))); + ImmutableMap.of()))); verify(mockListener).onResult(resolutionResultCaptor.capture()); InternalConfigSelector configSelector = resolutionResultCaptor.getValue().getAttributes().get(InternalConfigSelector.KEY); @@ -795,7 +795,7 @@ public void resolved_rpcHashingByChannelId() { // Second call, with no custom header. startNewCall(TestMethodDescriptors.voidMethod(), configSelector, - Collections.emptyMap(), + Collections.emptyMap(), CallOptions.DEFAULT); long hash2 = testCall.callOptions.getOption(XdsNameResolver.RPC_HASH_KEY); @@ -817,14 +817,14 @@ public void resolved_rpcHashingByChannelId() { Collections.singletonList(HashPolicy.forChannelId(false)), null, null), - ImmutableMap.of()))); + ImmutableMap.of()))); verify(mockListener).onResult(resolutionResultCaptor.capture()); configSelector = resolutionResultCaptor.getValue().getAttributes().get( InternalConfigSelector.KEY); // Third call, with no custom header. startNewCall(TestMethodDescriptors.voidMethod(), configSelector, - Collections.emptyMap(), + Collections.emptyMap(), CallOptions.DEFAULT); long hash3 = testCall.callOptions.getOption(XdsNameResolver.RPC_HASH_KEY); @@ -846,15 +846,15 @@ public void resolved_resourceUpdateAfterCallStarted() { Route.forAction( RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( - "another-cluster", Collections.emptyList(), + "another-cluster", Collections.emptyList(), TimeUnit.SECONDS.toNanos(20L), null), - ImmutableMap.of()), + ImmutableMap.of()), Route.forAction( RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), RouteAction.forCluster( - cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), + cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), - ImmutableMap.of()))); + ImmutableMap.of()))); verify(mockListener).onResult(resolutionResultCaptor.capture()); ResolutionResult result = resolutionResultCaptor.getValue(); // Updated service config still contains cluster1 while it is removed resource. New calls no @@ -886,15 +886,15 @@ public void resolved_resourceUpdatedBeforeCallStarted() { Route.forAction( RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( - "another-cluster", Collections.emptyList(), + "another-cluster", Collections.emptyList(), TimeUnit.SECONDS.toNanos(20L), null), - ImmutableMap.of()), + ImmutableMap.of()), Route.forAction( RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), RouteAction.forCluster( - cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), + cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), - ImmutableMap.of()))); + ImmutableMap.of()))); // Two consecutive service config updates: one for removing clcuster1, // one for adding "another=cluster". verify(mockListener, times(2)).onResult(resolutionResultCaptor.capture()); @@ -922,15 +922,15 @@ public void resolved_raceBetweenCallAndRepeatedResourceUpdate() { Route.forAction( RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( - "another-cluster", Collections.emptyList(), + "another-cluster", Collections.emptyList(), TimeUnit.SECONDS.toNanos(20L), null), - ImmutableMap.of()), + ImmutableMap.of()), Route.forAction( RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), RouteAction.forCluster( - cluster2, Collections.emptyList(), + cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), - ImmutableMap.of()))); + ImmutableMap.of()))); verify(mockListener).onResult(resolutionResultCaptor.capture()); ResolutionResult result = resolutionResultCaptor.getValue(); @@ -943,15 +943,15 @@ public void resolved_raceBetweenCallAndRepeatedResourceUpdate() { Route.forAction( RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( - "another-cluster", Collections.emptyList(), + "another-cluster", Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), - ImmutableMap.of()), + ImmutableMap.of()), Route.forAction( RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), RouteAction.forCluster( - cluster2, Collections.emptyList(), + cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), - ImmutableMap.of()))); + ImmutableMap.of()))); verifyNoMoreInteractions(mockListener); // no cluster added/deleted assertCallSelectClusterResult(call1, configSelector, "another-cluster", 15.0); } @@ -966,23 +966,23 @@ public void resolved_raceBetweenClusterReleasedAndResourceUpdateAddBackAgain() { Route.forAction( RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), RouteAction.forCluster( - cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), + cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), - ImmutableMap.of()))); + ImmutableMap.of()))); xdsClient.deliverLdsUpdate( Arrays.asList( Route.forAction( RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( - cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), + cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), - ImmutableMap.of()), + ImmutableMap.of()), Route.forAction( RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), RouteAction.forCluster( - cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), + cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), - ImmutableMap.of()))); + ImmutableMap.of()))); testCall.deliverErrorStatus(); verifyNoMoreInteractions(mockListener); } @@ -999,13 +999,13 @@ public void resolved_simpleCallSucceeds_routeToWeightedCluster() { RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forWeightedClusters( Arrays.asList( - ClusterWeight.create(cluster1, 20, ImmutableMap.of()), + ClusterWeight.create(cluster1, 20, ImmutableMap.of()), ClusterWeight.create( - cluster2, 80, ImmutableMap.of())), - Collections.emptyList(), + cluster2, 80, ImmutableMap.of())), + Collections.emptyList(), TimeUnit.SECONDS.toNanos(20L), null), - ImmutableMap.of()))); + ImmutableMap.of()))); verify(mockListener).onResult(resolutionResultCaptor.capture()); ResolutionResult result = resolutionResultCaptor.getValue(); assertThat(result.getAddresses()).isEmpty(); @@ -1031,10 +1031,10 @@ public void resolved_simpleCallSucceeds_routeToRls() { "rls-plugin-foo", RlsPluginConfig.create( ImmutableMap.of("lookupService", "rls-cbt.googleapis.com"))), - Collections.emptyList(), + Collections.emptyList(), TimeUnit.SECONDS.toNanos(20L), null), - ImmutableMap.of()))); + ImmutableMap.of()))); verify(mockListener).onResult(resolutionResultCaptor.capture()); ResolutionResult result = resolutionResultCaptor.getValue(); assertThat(result.getAddresses()).isEmpty(); @@ -1078,11 +1078,11 @@ public void resolved_simpleCallSucceeds_routeToRls() { RlsPluginConfig.create( // changed ImmutableMap.of("lookupService", "rls-cbt-2.googleapis.com"))), - Collections.emptyList(), + Collections.emptyList(), // changed TimeUnit.SECONDS.toNanos(30L), null), - ImmutableMap.of()))); + ImmutableMap.of()))); verify(mockListener, times(2)).onResult(resolutionResultCaptor.capture()); ResolutionResult result2 = resolutionResultCaptor.getValue(); @SuppressWarnings("unchecked") @@ -1136,7 +1136,7 @@ private void assertCallSelectClusterResult( ClientInterceptor interceptor = result.getInterceptor(); ClientCall clientCall = interceptor.interceptCall( call.methodDescriptor, CallOptions.DEFAULT, channel); - clientCall.start(new NoopClientCallListener(), new Metadata()); + clientCall.start(new NoopClientCallListener<>(), new Metadata()); assertThat(testCall.callOptions.getOption(XdsNameResolver.CLUSTER_SELECTION_KEY)) .isEqualTo("cluster:" + expectedCluster); @SuppressWarnings("unchecked") @@ -1164,7 +1164,7 @@ private void assertCallSelectRlsPluginResult( ClientInterceptor interceptor = result.getInterceptor(); ClientCall clientCall = interceptor.interceptCall( call.methodDescriptor, CallOptions.DEFAULT, channel); - clientCall.start(new NoopClientCallListener(), new Metadata()); + clientCall.start(new NoopClientCallListener<>(), new Metadata()); assertThat(testCall.callOptions.getOption(XdsNameResolver.CLUSTER_SELECTION_KEY)) .isEqualTo("cluster_specifier_plugin:" + expectedPluginName); @SuppressWarnings("unchecked") @@ -1186,15 +1186,15 @@ private InternalConfigSelector resolveToClusters() { Route.forAction( RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteAction.forCluster( - cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), + cluster1, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), - ImmutableMap.of()), + ImmutableMap.of()), Route.forAction( RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), RouteAction.forCluster( - cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), + cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), - ImmutableMap.of()))); + ImmutableMap.of()))); verify(mockListener).onResult(resolutionResultCaptor.capture()); ResolutionResult result = resolutionResultCaptor.getValue(); assertThat(result.getAddresses()).isEmpty(); @@ -1329,7 +1329,7 @@ public void generateServiceConfig_forPerMethodConfig() throws IOException { 4, ImmutableList.of(Code.UNAVAILABLE, Code.CANCELLED), Durations.fromMillis(100), Durations.fromMillis(200), null); RetryPolicy retryPolicyWithEmptyStatusCodes = RetryPolicy.create( - 4, ImmutableList.of(), Durations.fromMillis(100), Durations.fromMillis(200), null); + 4, ImmutableList.of(), Durations.fromMillis(100), Durations.fromMillis(200), null); // timeout only String expectedServiceConfigJson = "{\n" @@ -1451,13 +1451,13 @@ public void findVirtualHostForHostName_exactMatchFirst() { List routes = Collections.emptyList(); VirtualHost vHost1 = VirtualHost.create("virtualhost01.googleapis.com", Arrays.asList("a.googleapis.com", "b.googleapis.com"), routes, - ImmutableMap.of()); + ImmutableMap.of()); VirtualHost vHost2 = VirtualHost.create("virtualhost02.googleapis.com", Collections.singletonList("*.googleapis.com"), routes, - ImmutableMap.of()); + ImmutableMap.of()); VirtualHost vHost3 = VirtualHost.create("virtualhost03.googleapis.com", Collections.singletonList("*"), routes, - ImmutableMap.of()); + ImmutableMap.of()); List virtualHosts = Arrays.asList(vHost1, vHost2, vHost3); assertThat(XdsNameResolver.findVirtualHostForHostName(virtualHosts, hostname)) .isEqualTo(vHost1); @@ -1469,13 +1469,13 @@ public void findVirtualHostForHostName_preferSuffixDomainOverPrefixDomain() { List routes = Collections.emptyList(); VirtualHost vHost1 = VirtualHost.create("virtualhost01.googleapis.com", Arrays.asList("*.googleapis.com", "b.googleapis.com"), routes, - ImmutableMap.of()); + ImmutableMap.of()); VirtualHost vHost2 = VirtualHost.create("virtualhost02.googleapis.com", Collections.singletonList("a.googleapis.*"), routes, - ImmutableMap.of()); + ImmutableMap.of()); VirtualHost vHost3 = VirtualHost.create("virtualhost03.googleapis.com", Collections.singletonList("*"), routes, - ImmutableMap.of()); + ImmutableMap.of()); List virtualHosts = Arrays.asList(vHost1, vHost2, vHost3); assertThat(XdsNameResolver.findVirtualHostForHostName(virtualHosts, hostname)) .isEqualTo(vHost1); @@ -1487,13 +1487,13 @@ public void findVirtualHostForHostName_asteriskMatchAnyDomain() { List routes = Collections.emptyList(); VirtualHost vHost1 = VirtualHost.create("virtualhost01.googleapis.com", Collections.singletonList("*"), routes, - ImmutableMap.of()); + ImmutableMap.of()); VirtualHost vHost2 = VirtualHost.create("virtualhost02.googleapis.com", Collections.singletonList("b.googleapis.com"), routes, - ImmutableMap.of()); + ImmutableMap.of()); List virtualHosts = Arrays.asList(vHost1, vHost2); assertThat(XdsNameResolver.findVirtualHostForHostName(virtualHosts, hostname)) - .isEqualTo(vHost1);; + .isEqualTo(vHost1); } @Test @@ -1513,7 +1513,7 @@ public void resolved_faultAbortInLdsUpdate() { InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY); // no header abort key provided in metadata, rpc should succeed ClientCall.Listener observer = startNewCall(TestMethodDescriptors.voidMethod(), - configSelector, Collections.emptyMap(), CallOptions.DEFAULT); + configSelector, Collections.emptyMap(), CallOptions.DEFAULT); verifyRpcSucceeded(observer); // header abort http status key provided, rpc should fail observer = startNewCall(TestMethodDescriptors.voidMethod(), configSelector, @@ -1582,7 +1582,7 @@ public void resolved_faultAbortInLdsUpdate() { result = resolutionResultCaptor.getValue(); configSelector = result.getAttributes().get(InternalConfigSelector.KEY); observer = startNewCall(TestMethodDescriptors.voidMethod(), configSelector, - Collections.emptyMap(), CallOptions.DEFAULT); + Collections.emptyMap(), CallOptions.DEFAULT); verifyRpcFailed( observer, Status.UNAUTHENTICATED.withDescription( @@ -1600,7 +1600,7 @@ public void resolved_faultAbortInLdsUpdate() { result = resolutionResultCaptor.getValue(); configSelector = result.getAttributes().get(InternalConfigSelector.KEY); observer = startNewCall(TestMethodDescriptors.voidMethod(), configSelector, - Collections.emptyMap(), CallOptions.DEFAULT); + Collections.emptyMap(), CallOptions.DEFAULT); verifyRpcSucceeded(observer); } @@ -1619,7 +1619,7 @@ public void resolved_faultDelayInLdsUpdate() { InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY); // no header delay key provided in metadata, rpc should succeed immediately ClientCall.Listener observer = startNewCall(TestMethodDescriptors.voidMethod(), - configSelector, Collections.emptyMap(), CallOptions.DEFAULT); + configSelector, Collections.emptyMap(), CallOptions.DEFAULT); verifyRpcSucceeded(observer); // header delay key provided, rpc should be delayed observer = startNewCall(TestMethodDescriptors.voidMethod(), configSelector, @@ -1659,7 +1659,7 @@ public void resolved_faultDelayInLdsUpdate() { result = resolutionResultCaptor.getValue(); configSelector = result.getAttributes().get(InternalConfigSelector.KEY); observer = startNewCall(TestMethodDescriptors.voidMethod(), configSelector, - Collections.emptyMap(), CallOptions.DEFAULT); + Collections.emptyMap(), CallOptions.DEFAULT); verifyRpcDelayed(observer, 5000L); // fixed delay, fix rate = 40% @@ -1672,7 +1672,7 @@ public void resolved_faultDelayInLdsUpdate() { result = resolutionResultCaptor.getValue(); configSelector = result.getAttributes().get(InternalConfigSelector.KEY); observer = startNewCall(TestMethodDescriptors.voidMethod(), configSelector, - Collections.emptyMap(), CallOptions.DEFAULT); + Collections.emptyMap(), CallOptions.DEFAULT); verifyRpcSucceeded(observer); } @@ -1694,15 +1694,15 @@ public void resolved_faultDelayWithMaxActiveStreamsInLdsUpdate() { // Send two calls, then the first call should delayed and the second call should not be delayed // because maxActiveFaults is exceeded. ClientCall.Listener observer1 = startNewCall(TestMethodDescriptors.voidMethod(), - configSelector, Collections.emptyMap(), CallOptions.DEFAULT); + configSelector, Collections.emptyMap(), CallOptions.DEFAULT); assertThat(testCall).isNull(); ClientCall.Listener observer2 = startNewCall(TestMethodDescriptors.voidMethod(), - configSelector, Collections.emptyMap(), CallOptions.DEFAULT); + configSelector, Collections.emptyMap(), CallOptions.DEFAULT); verifyRpcSucceeded(observer2); verifyRpcDelayed(observer1, 5000L); // Once all calls are finished, new call should be delayed. ClientCall.Listener observer3 = startNewCall(TestMethodDescriptors.voidMethod(), - configSelector, Collections.emptyMap(), CallOptions.DEFAULT); + configSelector, Collections.emptyMap(), CallOptions.DEFAULT); verifyRpcDelayed(observer3, 5000L); } @@ -1728,7 +1728,7 @@ public long nanoTime() { } }; ClientCall.Listener observer = startNewCall(TestMethodDescriptors.voidMethod(), - configSelector, Collections.emptyMap(), CallOptions.DEFAULT.withDeadline( + configSelector, Collections.emptyMap(), CallOptions.DEFAULT.withDeadline( Deadline.after(4000, TimeUnit.NANOSECONDS, fakeTicker))); assertThat(testCall).isNull(); verifyRpcDelayedThenAborted(observer, 4000L, Status.DEADLINE_EXCEEDED.withDescription( @@ -1753,7 +1753,7 @@ public void resolved_faultAbortAndDelayInLdsUpdateInLdsUpdate() { ResolutionResult result = resolutionResultCaptor.getValue(); InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY); ClientCall.Listener observer = startNewCall(TestMethodDescriptors.voidMethod(), - configSelector, Collections.emptyMap(), CallOptions.DEFAULT); + configSelector, Collections.emptyMap(), CallOptions.DEFAULT); verifyRpcDelayedThenAborted( observer, 5000L, Status.UNAUTHENTICATED.withDescription( @@ -1782,7 +1782,7 @@ public void resolved_faultConfigOverrideInLdsUpdate() { ResolutionResult result = resolutionResultCaptor.getValue(); InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY); ClientCall.Listener observer = startNewCall(TestMethodDescriptors.voidMethod(), - configSelector, Collections.emptyMap(), CallOptions.DEFAULT); + configSelector, Collections.emptyMap(), CallOptions.DEFAULT); verifyRpcFailed( observer, Status.INTERNAL.withDescription("RPC terminated due to fault injection")); @@ -1797,7 +1797,7 @@ public void resolved_faultConfigOverrideInLdsUpdate() { result = resolutionResultCaptor.getValue(); configSelector = result.getAttributes().get(InternalConfigSelector.KEY); observer = startNewCall(TestMethodDescriptors.voidMethod(), configSelector, - Collections.emptyMap(), CallOptions.DEFAULT); + Collections.emptyMap(), CallOptions.DEFAULT); verifyRpcFailed( observer, Status.UNKNOWN.withDescription("RPC terminated due to fault injection")); @@ -1814,7 +1814,7 @@ public void resolved_faultConfigOverrideInLdsUpdate() { result = resolutionResultCaptor.getValue(); configSelector = result.getAttributes().get(InternalConfigSelector.KEY); observer = startNewCall(TestMethodDescriptors.voidMethod(), configSelector, - Collections.emptyMap(), CallOptions.DEFAULT); + Collections.emptyMap(), CallOptions.DEFAULT); verifyRpcFailed( observer, Status.UNAVAILABLE.withDescription("RPC terminated due to fault injection")); } @@ -1843,7 +1843,7 @@ public void resolved_faultConfigOverrideInLdsAndInRdsUpdate() { ResolutionResult result = resolutionResultCaptor.getValue(); InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY); ClientCall.Listener observer = startNewCall(TestMethodDescriptors.voidMethod(), - configSelector, Collections.emptyMap(), CallOptions.DEFAULT);; + configSelector, Collections.emptyMap(), CallOptions.DEFAULT); verifyRpcFailed( observer, Status.UNKNOWN.withDescription("RPC terminated due to fault injection")); } @@ -1904,7 +1904,7 @@ public void routeMatching_pathOnly() { RouteMatch routeMatch1 = RouteMatch.create( PathMatcher.fromPath("/FooService/barMethod", true), - Collections.emptyList(), null); + Collections.emptyList(), null); assertThat(XdsNameResolver.matchRoute(routeMatch1, "/FooService/barMethod", headers, random)) .isTrue(); assertThat(XdsNameResolver.matchRoute(routeMatch1, "/FooService/bazMethod", headers, random)) @@ -1913,7 +1913,7 @@ public void routeMatching_pathOnly() { RouteMatch routeMatch2 = RouteMatch.create( PathMatcher.fromPrefix("/FooService/", true), - Collections.emptyList(), null); + Collections.emptyList(), null); assertThat(XdsNameResolver.matchRoute(routeMatch2, "/FooService/barMethod", headers, random)) .isTrue(); assertThat(XdsNameResolver.matchRoute(routeMatch2, "/FooService/bazMethod", headers, random)) @@ -1924,7 +1924,7 @@ public void routeMatching_pathOnly() { RouteMatch routeMatch3 = RouteMatch.create( PathMatcher.fromRegEx(Pattern.compile(".*Foo.*")), - Collections.emptyList(), null); + Collections.emptyList(), null); assertThat(XdsNameResolver.matchRoute(routeMatch3, "/FooService/barMethod", headers, random)) .isTrue(); } @@ -1937,14 +1937,14 @@ public void routeMatching_pathOnly_caseInsensitive() { RouteMatch routeMatch1 = RouteMatch.create( PathMatcher.fromPath("/FooService/barMethod", false), - Collections.emptyList(), null); + Collections.emptyList(), null); assertThat(XdsNameResolver.matchRoute(routeMatch1, "/fooservice/barmethod", headers, random)) .isTrue(); RouteMatch routeMatch2 = RouteMatch.create( PathMatcher.fromPrefix("/FooService", false), - Collections.emptyList(), null); + Collections.emptyList(), null); assertThat(XdsNameResolver.matchRoute(routeMatch2, "/fooservice/barmethod", headers, random)) .isTrue(); } @@ -2122,7 +2122,7 @@ void deliverLdsUpdate(final List routes) { VirtualHost virtualHost = VirtualHost.create( "virtual-host", Collections.singletonList(expectedLdsResourceName), routes, - ImmutableMap.of()); + ImmutableMap.of()); ldsWatcher.onChanged(LdsUpdate.forApiListener(HttpConnectionManager.forVirtualHosts( 0L, Collections.singletonList(virtualHost), null))); } @@ -2140,28 +2140,28 @@ void deliverLdsUpdateWithFaultInjection( new NamedFilterConfig(FAULT_FILTER_INSTANCE_NAME, httpFilterFaultConfig), new NamedFilterConfig(ROUTER_FILTER_INSTANCE_NAME, RouterFilter.ROUTER_CONFIG)); ImmutableMap overrideConfig = weightedClusterFaultConfig == null - ? ImmutableMap.of() - : ImmutableMap.of( + ? ImmutableMap.of() + : ImmutableMap.of( FAULT_FILTER_INSTANCE_NAME, weightedClusterFaultConfig); ClusterWeight clusterWeight = ClusterWeight.create( cluster, 100, overrideConfig); overrideConfig = routeFaultConfig == null - ? ImmutableMap.of() - : ImmutableMap.of(FAULT_FILTER_INSTANCE_NAME, routeFaultConfig); + ? ImmutableMap.of() + : ImmutableMap.of(FAULT_FILTER_INSTANCE_NAME, routeFaultConfig); Route route = Route.forAction( RouteMatch.create( - PathMatcher.fromPrefix("/", false), Collections.emptyList(), null), + PathMatcher.fromPrefix("/", false), Collections.emptyList(), null), RouteAction.forWeightedClusters( Collections.singletonList(clusterWeight), - Collections.emptyList(), + Collections.emptyList(), null, null), overrideConfig); overrideConfig = virtualHostFaultConfig == null - ? ImmutableMap.of() - : ImmutableMap.of( + ? ImmutableMap.of() + : ImmutableMap.of( FAULT_FILTER_INSTANCE_NAME, virtualHostFaultConfig); VirtualHost virtualHost = VirtualHost.create( "virtual-host", @@ -2201,26 +2201,26 @@ void deliverRdsUpdateWithFaultInjection( return; } ImmutableMap overrideConfig = weightedClusterFaultConfig == null - ? ImmutableMap.of() - : ImmutableMap.of( + ? ImmutableMap.of() + : ImmutableMap.of( FAULT_FILTER_INSTANCE_NAME, weightedClusterFaultConfig); ClusterWeight clusterWeight = ClusterWeight.create(cluster1, 100, overrideConfig); overrideConfig = routFaultConfig == null - ? ImmutableMap.of() - : ImmutableMap.of(FAULT_FILTER_INSTANCE_NAME, routFaultConfig); + ? ImmutableMap.of() + : ImmutableMap.of(FAULT_FILTER_INSTANCE_NAME, routFaultConfig); Route route = Route.forAction( RouteMatch.create( - PathMatcher.fromPrefix("/", false), Collections.emptyList(), null), + PathMatcher.fromPrefix("/", false), Collections.emptyList(), null), RouteAction.forWeightedClusters( Collections.singletonList(clusterWeight), - Collections.emptyList(), + Collections.emptyList(), null, null), overrideConfig); overrideConfig = virtualHostFaultConfig == null - ? ImmutableMap.of() - : ImmutableMap.of( + ? ImmutableMap.of() + : ImmutableMap.of( FAULT_FILTER_INSTANCE_NAME, virtualHostFaultConfig); VirtualHost virtualHost = VirtualHost.create( "virtual-host", From 778098b911d1fabc6360fa99cebe0d71779524cb Mon Sep 17 00:00:00 2001 From: Larry Safran <107004254+larry-safran@users.noreply.github.com> Date: Mon, 15 Aug 2022 11:10:10 -0700 Subject: [PATCH 08/72] rls: fix RLS policy to not propagate status from control plane RPC to data plane RPC (#9413) rls: Avoid library returning the status codes which the status spec document says that the library will never return when talking to RLS server. Instead, always return UNAVAILABLE on errors. * Provide context around error message from RLS server --- api/src/main/java/io/grpc/Status.java | 4 ++ .../java/io/grpc/rls/CachingRlsLbClient.java | 19 +++++++- .../io/grpc/rls/CachingRlsLbClientTest.java | 6 ++- .../java/io/grpc/rls/RlsLoadBalancerTest.java | 45 ++++++++++++++++--- 4 files changed, 65 insertions(+), 9 deletions(-) diff --git a/api/src/main/java/io/grpc/Status.java b/api/src/main/java/io/grpc/Status.java index 1ad5abc0539..f6e88eab8dd 100644 --- a/api/src/main/java/io/grpc/Status.java +++ b/api/src/main/java/io/grpc/Status.java @@ -50,6 +50,10 @@ * *

Utility functions are provided to convert a status to an exception and to extract them * back out. + * + *

Extended descriptions, including a list of codes that should not be generated by the library, + * can be found at + * doc/statuscodes.md */ @Immutable @CheckReturnValue diff --git a/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java b/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java index 6c777f45146..fcac2a37ce3 100644 --- a/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java +++ b/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java @@ -176,6 +176,19 @@ private CachingRlsLbClient(Builder builder) { logger.log(ChannelLogLevel.DEBUG, "CachingRlsLbClient created"); } + /** + * Convert the status to UNAVAILBLE and enhance the error message. + * @param status status as provided by server + * @param serverName Used for error description + * @return Transformed status + */ + static Status convertRlsServerStatus(Status status, String serverName) { + return Status.UNAVAILABLE.withCause(status.getCause()).withDescription( + String.format("Unable to retrieve RLS targets from RLS server %s. " + + "RLS server returned: %s: %s", + serverName, status.getCode(), status.getDescription())); + } + @CheckReturnValue private ListenableFuture asyncRlsCall(RouteLookupRequest request) { final SettableFuture response = SettableFuture.create(); @@ -931,12 +944,15 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { if (picker == null) { return PickResult.withNoResult(); } + // Happy path return picker.pickSubchannel(args); } else if (response.hasError()) { if (hasFallback) { return useFallback(args); } - return PickResult.withError(response.getStatus()); + return PickResult.withError( + convertRlsServerStatus(response.getStatus(), + lbPolicyConfig.getRouteLookupConfig().lookupService())); } else { return PickResult.withNoResult(); } @@ -980,4 +996,5 @@ public String toString() { .toString(); } } + } diff --git a/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java b/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java index 69aa27af182..9ac27532ad4 100644 --- a/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java +++ b/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static io.grpc.rls.CachingRlsLbClient.RLS_DATA_KEY; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -386,7 +387,7 @@ public void get_updatesLbState() throws Exception { headers, CallOptions.DEFAULT)); assertThat(pickResult.getStatus().getCode()).isEqualTo(Code.UNAVAILABLE); - assertThat(pickResult.getStatus().getDescription()).isEqualTo("fallback not available"); + assertThat(pickResult.getStatus().getDescription()).contains("fallback not available"); } @Test @@ -412,6 +413,7 @@ public void get_childPolicyWrapper_reusedForSameTarget() throws Exception { assertThat(resp.getHeaderData()).isEqualTo("header"); ChildPolicyWrapper childPolicyWrapper = resp.getChildPolicyWrapper(); + assertNotNull(childPolicyWrapper); assertThat(childPolicyWrapper.getTarget()).isEqualTo("target"); assertThat(childPolicyWrapper.getPicker()).isNotInstanceOf(RlsPicker.class); @@ -575,7 +577,7 @@ public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { @Override public PickResult pickSubchannel(PickSubchannelArgs args) { return PickResult.withError( - Status.UNAVAILABLE.withDescription("fallback not available")); + Status.UNAVAILABLE.withDescription("fallback not available")); } }); } else { diff --git a/rls/src/test/java/io/grpc/rls/RlsLoadBalancerTest.java b/rls/src/test/java/io/grpc/rls/RlsLoadBalancerTest.java index def99eb6ae9..b469ee6fe32 100644 --- a/rls/src/test/java/io/grpc/rls/RlsLoadBalancerTest.java +++ b/rls/src/test/java/io/grpc/rls/RlsLoadBalancerTest.java @@ -169,6 +169,36 @@ public void tearDown() { rlsLb.shutdown(); } + @Test + public void lb_serverStatusCodeConversion() throws Exception { + deliverResolvedAddresses(); + InOrder inOrder = inOrder(helper); + inOrder.verify(helper) + .updateBalancingState(eq(ConnectivityState.CONNECTING), pickerCaptor.capture()); + SubchannelPicker picker = pickerCaptor.getValue(); + Metadata headers = new Metadata(); + PickSubchannelArgsImpl fakeSearchMethodArgs = + new PickSubchannelArgsImpl(fakeSearchMethod, headers, CallOptions.DEFAULT); + PickResult res = picker.pickSubchannel(fakeSearchMethodArgs); + FakeSubchannel subchannel = (FakeSubchannel) res.getSubchannel(); + assertThat(subchannel).isNotNull(); + + // Ensure happy path is unaffected + subchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); + res = picker.pickSubchannel(fakeSearchMethodArgs); + assertThat(res.getStatus().getCode()).isEqualTo(Status.Code.OK); + + // Check on conversion + Throwable cause = new Throwable("cause"); + Status aborted = Status.ABORTED.withCause(cause).withDescription("base desc"); + Status serverStatus = CachingRlsLbClient.convertRlsServerStatus(aborted, "conv.test"); + assertThat(serverStatus.getCode()).isEqualTo(Status.Code.UNAVAILABLE); + assertThat(serverStatus.getCause()).isEqualTo(cause); + assertThat(serverStatus.getDescription()).contains("RLS server returned: "); + assertThat(serverStatus.getDescription()).endsWith("ABORTED: base desc"); + assertThat(serverStatus.getDescription()).contains("RLS server conv.test"); + } + @Test public void lb_working_withDefaultTarget_rlsResponding() throws Exception { deliverResolvedAddresses(); @@ -210,7 +240,7 @@ public void lb_working_withDefaultTarget_rlsResponding() throws Exception { FakeSubchannel rescueSubchannel = subchannels.getLast(); // search subchannel is down, rescue subchannel is connecting - searchSubchannel.updateState(ConnectivityStateInfo.forTransientFailure(Status.NOT_FOUND)); + searchSubchannel.updateState(ConnectivityStateInfo.forTransientFailure(Status.UNAVAILABLE)); inOrder.verify(helper) .updateBalancingState(eq(ConnectivityState.CONNECTING), pickerCaptor.capture()); @@ -223,7 +253,7 @@ public void lb_working_withDefaultTarget_rlsResponding() throws Exception { // subchannel is in failure mode res = picker.pickSubchannel( new PickSubchannelArgsImpl(fakeSearchMethod, headers, CallOptions.DEFAULT)); - assertThat(res.getStatus().getCode()).isEqualTo(Status.Code.NOT_FOUND); + assertThat(res.getStatus().getCode()).isEqualTo(Status.Code.UNAVAILABLE); assertThat(subchannelIsReady(res.getSubchannel())).isFalse(); } @@ -243,6 +273,7 @@ public void lb_working_withDefaultTarget_noRlsResponse() throws Exception { res = picker.pickSubchannel( new PickSubchannelArgsImpl(fakeSearchMethod, headers, CallOptions.DEFAULT)); FakeSubchannel fallbackSubchannel = (FakeSubchannel) res.getSubchannel(); + assertThat(fallbackSubchannel).isNotNull(); assertThat(res.getStatus().getCode()).isEqualTo(Status.Code.OK); assertThat(subchannelIsReady(res.getSubchannel())).isFalse(); @@ -270,6 +301,7 @@ public void lb_working_withDefaultTarget_noRlsResponse() throws Exception { new PickSubchannelArgsImpl(fakeSearchMethod, headers, CallOptions.DEFAULT)); assertThat(res.getSubchannel()).isNotSameInstanceAs(fallbackSubchannel); FakeSubchannel searchSubchannel = (FakeSubchannel) res.getSubchannel(); + assertThat(searchSubchannel).isNotNull(); searchSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); // create rescue subchannel @@ -278,16 +310,17 @@ public void lb_working_withDefaultTarget_noRlsResponse() throws Exception { assertThat(res.getSubchannel()).isNotSameInstanceAs(fallbackSubchannel); assertThat(res.getSubchannel()).isNotSameInstanceAs(searchSubchannel); FakeSubchannel rescueSubchannel = (FakeSubchannel) res.getSubchannel(); + assertThat(rescueSubchannel).isNotNull(); rescueSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY)); // all channels are failed - rescueSubchannel.updateState(ConnectivityStateInfo.forTransientFailure(Status.NOT_FOUND)); - searchSubchannel.updateState(ConnectivityStateInfo.forTransientFailure(Status.NOT_FOUND)); - fallbackSubchannel.updateState(ConnectivityStateInfo.forTransientFailure(Status.NOT_FOUND)); + rescueSubchannel.updateState(ConnectivityStateInfo.forTransientFailure(Status.UNAVAILABLE)); + searchSubchannel.updateState(ConnectivityStateInfo.forTransientFailure(Status.UNAVAILABLE)); + fallbackSubchannel.updateState(ConnectivityStateInfo.forTransientFailure(Status.UNAVAILABLE)); res = picker.pickSubchannel( new PickSubchannelArgsImpl(fakeSearchMethod, headers, CallOptions.DEFAULT)); - assertThat(res.getStatus().getCode()).isEqualTo(Status.Code.NOT_FOUND); + assertThat(res.getStatus().getCode()).isEqualTo(Status.Code.UNAVAILABLE); assertThat(res.getSubchannel()).isNull(); } From 128688ae4d7e082240a7df1c95df1c2a3e8515e3 Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Thu, 18 Aug 2022 08:46:30 -0700 Subject: [PATCH 09/72] Outlier detection load balancer (#9447) New outlier detection load balancer. Tracks all RPC results to the addresses it is configured with and periodically attempts to detect outlier. It wraps a child load balancer from which it hides any addresses that are deemed outliers. As specified in gRFC A50: gRPC xDS Outlier Detection Support: https://github.com/grpc/proposal/blob/master/A50-xds-outlier-detection.md --- .../java/io/grpc/SynchronizationContext.java | 32 + .../io/grpc/LoadBalancerRegistryTest.java | 8 +- .../util/OutlierDetectionLoadBalancer.java | 1067 ++++++++++++++++ .../OutlierDetectionLoadBalancerProvider.java | 158 +++ .../services/io.grpc.LoadBalancerProvider | 1 + .../test/java/io/grpc/internal/FakeClock.java | 24 +- ...lierDetectionLoadBalancerProviderTest.java | 141 +++ .../OutlierDetectionLoadBalancerTest.java | 1077 +++++++++++++++++ 8 files changed, 2505 insertions(+), 3 deletions(-) create mode 100644 core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java create mode 100644 core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancerProvider.java create mode 100644 core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerProviderTest.java create mode 100644 core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java diff --git a/api/src/main/java/io/grpc/SynchronizationContext.java b/api/src/main/java/io/grpc/SynchronizationContext.java index 03d26b55f0a..fe4243ec227 100644 --- a/api/src/main/java/io/grpc/SynchronizationContext.java +++ b/api/src/main/java/io/grpc/SynchronizationContext.java @@ -163,6 +163,38 @@ public String toString() { return new ScheduledHandle(runnable, future); } + /** + * Schedules a task to be added and run via {@link #execute} after an inital delay and then + * repeated after the delay until cancelled. + * + * @param task the task being scheduled + * @param initialDelay the delay before the first run + * @param delay the delay after the first run. + * @param unit the time unit for the delay + * @param timerService the {@code ScheduledExecutorService} that provides delayed execution + * + * @return an object for checking the status and/or cancel the scheduled task + */ + public final ScheduledHandle scheduleWithFixedDelay( + final Runnable task, long initialDelay, long delay, TimeUnit unit, + ScheduledExecutorService timerService) { + final ManagedRunnable runnable = new ManagedRunnable(task); + ScheduledFuture future = timerService.scheduleWithFixedDelay(new Runnable() { + @Override + public void run() { + execute(runnable); + } + + @Override + public String toString() { + return task.toString() + "(scheduled in SynchronizationContext with delay of " + delay + + ")"; + } + }, initialDelay, delay, unit); + return new ScheduledHandle(runnable, future); + } + + private static class ManagedRunnable implements Runnable { final Runnable task; boolean isCancelled; diff --git a/api/src/test/java/io/grpc/LoadBalancerRegistryTest.java b/api/src/test/java/io/grpc/LoadBalancerRegistryTest.java index e8588e5e8d8..3debc871121 100644 --- a/api/src/test/java/io/grpc/LoadBalancerRegistryTest.java +++ b/api/src/test/java/io/grpc/LoadBalancerRegistryTest.java @@ -41,7 +41,7 @@ public void getClassesViaHardcoded_classesPresent() throws Exception { @Test public void stockProviders() { LoadBalancerRegistry defaultRegistry = LoadBalancerRegistry.getDefaultRegistry(); - assertThat(defaultRegistry.providers()).hasSize(3); + assertThat(defaultRegistry.providers()).hasSize(4); LoadBalancerProvider pickFirst = defaultRegistry.getProvider("pick_first"); assertThat(pickFirst).isInstanceOf(PickFirstLoadBalancerProvider.class); @@ -52,6 +52,12 @@ public void stockProviders() { "io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider"); assertThat(roundRobin.getPriority()).isEqualTo(5); + LoadBalancerProvider outlierDetection = defaultRegistry.getProvider( + "outlier_detection_experimental"); + assertThat(outlierDetection.getClass().getName()).isEqualTo( + "io.grpc.util.OutlierDetectionLoadBalancerProvider"); + assertThat(roundRobin.getPriority()).isEqualTo(5); + LoadBalancerProvider grpclb = defaultRegistry.getProvider("grpclb"); assertThat(grpclb).isInstanceOf(GrpclbLoadBalancerProvider.class); assertThat(grpclb.getPriority()).isEqualTo(5); diff --git a/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java new file mode 100644 index 00000000000..3fb32fdb078 --- /dev/null +++ b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java @@ -0,0 +1,1067 @@ +/* + * Copyright 2022 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.util; + +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 java.util.concurrent.TimeUnit.NANOSECONDS; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ForwardingMap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import io.grpc.Attributes; +import io.grpc.ClientStreamTracer; +import io.grpc.ClientStreamTracer.StreamInfo; +import io.grpc.ConnectivityState; +import io.grpc.ConnectivityStateInfo; +import io.grpc.EquivalentAddressGroup; +import io.grpc.LoadBalancer; +import io.grpc.Metadata; +import io.grpc.Status; +import io.grpc.SynchronizationContext; +import io.grpc.SynchronizationContext.ScheduledHandle; +import io.grpc.internal.ServiceConfigUtil.PolicySelection; +import io.grpc.internal.TimeProvider; +import java.net.SocketAddress; +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; +import java.util.concurrent.atomic.AtomicLong; +import javax.annotation.Nullable; + +/** + * Wraps a child {@code LoadBalancer} while monitoring for outlier backends and removing them from + * the use of the child LB. + * + *

This implements the outlier detection gRFC: + * https://github.com/grpc/proposal/blob/master/A50-xds-outlier-detection.md + */ +public class OutlierDetectionLoadBalancer extends LoadBalancer { + + @VisibleForTesting + final AddressTrackerMap trackerMap; + + private final SynchronizationContext syncContext; + private final Helper childHelper; + private final GracefulSwitchLoadBalancer switchLb; + private TimeProvider timeProvider; + private final ScheduledExecutorService timeService; + private ScheduledHandle detectionTimerHandle; + private Long detectionTimerStartNanos; + + private static final Attributes.Key ADDRESS_TRACKER_ATTR_KEY + = Attributes.Key.create("addressTrackerKey"); + + /** + * Creates a new instance of {@link OutlierDetectionLoadBalancer}. + */ + public OutlierDetectionLoadBalancer(Helper helper, TimeProvider timeProvider) { + childHelper = new ChildHelper(checkNotNull(helper, "helper")); + switchLb = new GracefulSwitchLoadBalancer(childHelper); + trackerMap = new AddressTrackerMap(); + this.syncContext = checkNotNull(helper.getSynchronizationContext(), "syncContext"); + this.timeService = checkNotNull(helper.getScheduledExecutorService(), "timeService"); + this.timeProvider = timeProvider; + } + + @Override + public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { + OutlierDetectionLoadBalancerConfig config + = (OutlierDetectionLoadBalancerConfig) resolvedAddresses.getLoadBalancingPolicyConfig(); + + // The map should only retain entries for addresses in this latest update. + ArrayList addresses = new ArrayList<>(); + for (EquivalentAddressGroup addressGroup : resolvedAddresses.getAddresses()) { + addresses.addAll(addressGroup.getAddresses()); + } + trackerMap.keySet().retainAll(addresses); + + trackerMap.updateTrackerConfigs(config); + + // Add any new ones. + trackerMap.putNewTrackers(config, addresses); + + switchLb.switchTo(config.childPolicy.getProvider()); + + // If outlier detection is actually configured, start a timer that will periodically try to + // detect outliers. + if (config.outlierDetectionEnabled()) { + Long initialDelayNanos; + + if (detectionTimerStartNanos == null) { + // On the first go we use the configured interval. + initialDelayNanos = config.intervalNanos; + } else { + // If a timer has started earlier we cancel it and use the difference between the start + // time and now as the interval. + initialDelayNanos = Math.max(0L, + config.intervalNanos - (timeProvider.currentTimeNanos() - detectionTimerStartNanos)); + } + + // If a timer has been previously created we need to cancel it and reset all the call counters + // for a fresh start. + if (detectionTimerHandle != null) { + detectionTimerHandle.cancel(); + trackerMap.resetCallCounters(); + } + + detectionTimerHandle = syncContext.scheduleWithFixedDelay(new DetectionTimer(config), + initialDelayNanos, config.intervalNanos, NANOSECONDS, timeService); + } else if (detectionTimerHandle != null) { + // Outlier detection is not configured, but we have a lingering timer. Let's cancel it and + // uneject any addresses we may have ejected. + detectionTimerHandle.cancel(); + detectionTimerStartNanos = null; + trackerMap.cancelTracking(); + } + + switchLb.handleResolvedAddresses(resolvedAddresses); + } + + @Override + public void handleNameResolutionError(Status error) { + switchLb.handleNameResolutionError(error); + } + + @Override + public void shutdown() { + switchLb.shutdown(); + } + + /** + * This timer will be invoked periodically, according to configuration, and it will look for any + * outlier subchannels. + */ + class DetectionTimer implements Runnable { + + OutlierDetectionLoadBalancerConfig config; + + DetectionTimer(OutlierDetectionLoadBalancerConfig config) { + this.config = config; + } + + @Override + public void run() { + detectionTimerStartNanos = timeProvider.currentTimeNanos(); + + trackerMap.swapCounters(); + + for (OutlierEjectionAlgorithm algo : OutlierEjectionAlgorithm.forConfig(config)) { + algo.ejectOutliers(trackerMap, detectionTimerStartNanos); + } + + trackerMap.maybeUnejectOutliers(detectionTimerStartNanos); + } + } + + /** + * This child helper wraps the provided helper so that it can hand out wrapped {@link + * OutlierDetectionSubchannel}s and manage the address info map. + */ + class ChildHelper extends ForwardingLoadBalancerHelper { + + private Helper delegate; + + ChildHelper(Helper delegate) { + this.delegate = delegate; + } + + @Override + protected Helper delegate() { + return delegate; + } + + @Override + public Subchannel createSubchannel(CreateSubchannelArgs args) { + // Subchannels are wrapped so that we can monitor call results and to trigger failures when + // we decide to eject the subchannel. + OutlierDetectionSubchannel subchannel = new OutlierDetectionSubchannel( + delegate.createSubchannel(args)); + + // If the subchannel is associated with a single address that is also already in the map + // the subchannel will be added to the map and be included in outlier detection. + List addressGroups = subchannel.getAllAddresses(); + if (hasSingleAddress(addressGroups) + && trackerMap.containsKey(addressGroups.get(0).getAddresses().get(0))) { + AddressTracker tracker = trackerMap.get(addressGroups.get(0).getAddresses().get(0)); + tracker.addSubchannel(subchannel); + + // If this address has already been ejected, we need to immediately eject this Subchannel. + if (tracker.ejectionTimeNanos != null) { + subchannel.eject(); + } + } + + return subchannel; + } + + @Override + public void updateBalancingState(ConnectivityState newState, SubchannelPicker newPicker) { + delegate.updateBalancingState(newState, new OutlierDetectionPicker(newPicker)); + } + } + + class OutlierDetectionSubchannel extends ForwardingSubchannel { + + private final Subchannel delegate; + private AddressTracker addressTracker; + private boolean ejected; + private ConnectivityStateInfo lastSubchannelState; + private OutlierDetectionSubchannelStateListener subchannelStateListener; + + OutlierDetectionSubchannel(Subchannel delegate) { + this.delegate = delegate; + } + + @Override + public void start(SubchannelStateListener listener) { + subchannelStateListener = new OutlierDetectionSubchannelStateListener(listener); + super.start(subchannelStateListener); + } + + @Override + public Attributes getAttributes() { + if (addressTracker != null) { + return delegate.getAttributes().toBuilder().set(ADDRESS_TRACKER_ATTR_KEY, addressTracker) + .build(); + } else { + return delegate.getAttributes(); + } + } + + @Override + public void updateAddresses(List addressGroups) { + // Outlier detection only supports subchannels with a single address, but the list of + // addressGroups associated with a subchannel can change at any time, so we need to react to + // changes in the address list plurality. + + // No change in address plurality, we replace the single one with a new one. + if (hasSingleAddress(getAllAddresses()) && hasSingleAddress(addressGroups)) { + // Remove the current subchannel from the old address it is associated with in the map. + if (trackerMap.containsValue(addressTracker)) { + addressTracker.removeSubchannel(this); + } + + // If the map has an entry for the new address, we associate this subchannel with it. + SocketAddress address = addressGroups.get(0).getAddresses().get(0); + if (trackerMap.containsKey(address)) { + trackerMap.get(address).addSubchannel(this); + } + } else if (hasSingleAddress(getAllAddresses()) && !hasSingleAddress(addressGroups)) { + // We go from a single address to having multiple, making this subchannel uneligible for + // outlier detection. Remove it from all trackers and reset the call counters of all the + // associated trackers. + // Remove the current subchannel from the old address it is associated with in the map. + if (trackerMap.containsKey(getAddresses().getAddresses().get(0))) { + AddressTracker tracker = trackerMap.get(getAddresses().getAddresses().get(0)); + tracker.removeSubchannel(this); + tracker.resetCallCounters(); + } + } else if (!hasSingleAddress(getAllAddresses()) && hasSingleAddress(addressGroups)) { + // We go from, previously uneligble, multiple address mode to a single address. If the map + // has an entry for the new address, we associate this subchannel with it. + SocketAddress address = addressGroups.get(0).getAddresses().get(0); + if (trackerMap.containsKey(address)) { + AddressTracker tracker = trackerMap.get(address); + tracker.addSubchannel(this); + } + } + + // We could also have multiple addressGroups and get an update for multiple new ones. This is + // a no-op as we will just continue to ignore multiple address subchannels. + + delegate.updateAddresses(addressGroups); + } + + /** + * If the {@link Subchannel} is considered for outlier detection the associated {@link + * AddressTracker} should be set. + */ + void setAddressTracker(AddressTracker addressTracker) { + this.addressTracker = addressTracker; + } + + void clearAddressTracker() { + this.addressTracker = null; + } + + void eject() { + ejected = true; + subchannelStateListener.onSubchannelState( + ConnectivityStateInfo.forTransientFailure(Status.UNAVAILABLE)); + } + + void uneject() { + ejected = false; + if (lastSubchannelState != null) { + subchannelStateListener.onSubchannelState(lastSubchannelState); + } + } + + boolean isEjected() { + return ejected; + } + + @Override + protected Subchannel delegate() { + return delegate; + } + + /** + * Wraps the actual listener so that state changes from the actual one can be intercepted. + */ + class OutlierDetectionSubchannelStateListener implements SubchannelStateListener { + + private final SubchannelStateListener delegate; + + OutlierDetectionSubchannelStateListener(SubchannelStateListener delegate) { + this.delegate = delegate; + } + + @Override + public void onSubchannelState(ConnectivityStateInfo newState) { + lastSubchannelState = newState; + if (!ejected) { + delegate.onSubchannelState(newState); + } + } + } + } + + + /** + * This picker delegates the actual picking logic to a wrapped delegate, but associates a {@link + * ClientStreamTracer} with each pick to track the results of each subchannel stream. + */ + class OutlierDetectionPicker extends SubchannelPicker { + + private final SubchannelPicker delegate; + + OutlierDetectionPicker(SubchannelPicker delegate) { + this.delegate = delegate; + } + + @Override + public PickResult pickSubchannel(PickSubchannelArgs args) { + PickResult pickResult = delegate.pickSubchannel(args); + + Subchannel subchannel = pickResult.getSubchannel(); + if (subchannel != null) { + return PickResult.withSubchannel(subchannel, + new ResultCountingClientStreamTracerFactory( + subchannel.getAttributes().get(ADDRESS_TRACKER_ATTR_KEY))); + } + + return pickResult; + } + + /** + * Builds instances of {@link ResultCountingClientStreamTracer}. + */ + class ResultCountingClientStreamTracerFactory extends ClientStreamTracer.Factory { + + private final AddressTracker tracker; + + ResultCountingClientStreamTracerFactory(AddressTracker tracker) { + this.tracker = tracker; + } + + @Override + public ClientStreamTracer newClientStreamTracer(StreamInfo info, Metadata headers) { + return new ResultCountingClientStreamTracer(tracker); + } + } + + /** + * Counts the results (successful/unsuccessful) of a particular {@link + * OutlierDetectionSubchannel}s streams and increments the counter in the associated {@link + * AddressTracker}. + */ + class ResultCountingClientStreamTracer extends ClientStreamTracer { + + AddressTracker tracker; + + public ResultCountingClientStreamTracer(AddressTracker tracker) { + this.tracker = tracker; + } + + @Override + public void streamClosed(Status status) { + tracker.incrementCallCount(status.isOk()); + } + } + } + + /** + * Tracks additional information about a set of equivalent addresses needed for outlier + * detection. + */ + static class AddressTracker { + + private OutlierDetectionLoadBalancerConfig config; + // Marked as volatile to assure that when the inactive counter is swapped in as the new active + // one, all threads see the change and don't hold on to a reference to the now inactive counter. + private volatile CallCounter activeCallCounter = new CallCounter(); + private CallCounter inactiveCallCounter = new CallCounter(); + private Long ejectionTimeNanos; + private int ejectionTimeMultiplier; + private final Set subchannels = new HashSet<>(); + + AddressTracker(OutlierDetectionLoadBalancerConfig config) { + this.config = config; + } + + void setConfig(OutlierDetectionLoadBalancerConfig config) { + this.config = config; + } + + /** + * Adds a subchannel to the tracker, while assuring that the subchannel ejection status is + * updated to match the tracker's if needed. + */ + boolean addSubchannel(OutlierDetectionSubchannel subchannel) { + // Make sure that the subchannel is in the same ejection state as the new tracker it is + // associated with. + if (subchannelsEjected() && !subchannel.isEjected()) { + subchannel.eject(); + } else if (!subchannelsEjected() && subchannel.isEjected()) { + subchannel.uneject(); + } + subchannel.setAddressTracker(this); + return subchannels.add(subchannel); + } + + boolean removeSubchannel(OutlierDetectionSubchannel subchannel) { + subchannel.clearAddressTracker(); + return subchannels.remove(subchannel); + } + + boolean containsSubchannel(OutlierDetectionSubchannel subchannel) { + return subchannels.contains(subchannel); + } + + @VisibleForTesting + Set getSubchannels() { + return ImmutableSet.copyOf(subchannels); + } + + void incrementCallCount(boolean success) { + // If neither algorithm is configured, no point in incrementing counters. + if (config.successRateEjection == null && config.failurePercentageEjection == null) { + return; + } + + if (success) { + activeCallCounter.successCount.getAndIncrement(); + } else { + activeCallCounter.failureCount.getAndIncrement(); + } + } + + @VisibleForTesting + long activeVolume() { + return activeCallCounter.successCount.get() + activeCallCounter.failureCount.get(); + } + + long inactiveVolume() { + return inactiveCallCounter.successCount.get() + inactiveCallCounter.failureCount.get(); + } + + double successRate() { + return ((double) inactiveCallCounter.successCount.get()) / inactiveVolume(); + } + + double failureRate() { + return ((double)inactiveCallCounter.failureCount.get()) / inactiveVolume(); + } + + void resetCallCounters() { + activeCallCounter.reset(); + inactiveCallCounter.reset(); + } + + void decrementEjectionTimeMultiplier() { + // The multiplier should not go negative. + ejectionTimeMultiplier = ejectionTimeMultiplier == 0 ? 0 : ejectionTimeMultiplier - 1; + } + + void resetEjectionTimeMultiplier() { + ejectionTimeMultiplier = 0; + } + + /** + * Swaps the active and inactive counters. + * + *

Note that this method is not thread safe as the swap is not done atomically. This is + * expected to only be called from the timer that is scheduled at a fixed delay, assuring that + * only one timer is active at a time. + */ + void swapCounters() { + inactiveCallCounter.reset(); + CallCounter tempCounter = activeCallCounter; + activeCallCounter = inactiveCallCounter; + inactiveCallCounter = tempCounter; + } + + void ejectSubchannels(long ejectionTimeNanos) { + this.ejectionTimeNanos = ejectionTimeNanos; + ejectionTimeMultiplier++; + for (OutlierDetectionSubchannel subchannel : subchannels) { + subchannel.eject(); + } + } + + /** + * Uneject a currently ejected address. + */ + void unejectSubchannels() { + checkState(ejectionTimeNanos != null, "not currently ejected"); + ejectionTimeNanos = null; + for (OutlierDetectionSubchannel subchannel : subchannels) { + subchannel.uneject(); + } + } + + boolean subchannelsEjected() { + return ejectionTimeNanos != null; + } + + public boolean maxEjectionTimeElapsed(long currentTimeNanos) { + // The instant in time beyond which the address should no longer be ejected. Also making sure + // we honor any maximum ejection time setting. + long maxEjectionDurationSecs + = Math.max(config.baseEjectionTimeNanos, config.maxEjectionTimeNanos); + long maxEjectionTimeNanos = + ejectionTimeNanos + Math.min( + config.baseEjectionTimeNanos * ejectionTimeMultiplier, + maxEjectionDurationSecs); + + return currentTimeNanos > maxEjectionTimeNanos; + } + + /** Tracks both successful and failed call counts. */ + private static class CallCounter { + AtomicLong successCount = new AtomicLong(); + AtomicLong failureCount = new AtomicLong(); + + void reset() { + successCount.set(0); + failureCount.set(0); + } + } + } + + /** + * Maintains a mapping from addresses to their trackers. + */ + static class AddressTrackerMap extends ForwardingMap { + private final Map trackerMap; + + AddressTrackerMap() { + trackerMap = new HashMap<>(); + } + + @Override + protected Map delegate() { + return trackerMap; + } + + void updateTrackerConfigs(OutlierDetectionLoadBalancerConfig config) { + for (AddressTracker tracker: trackerMap.values()) { + tracker.setConfig(config); + } + } + + /** Adds a new tracker for every given address. */ + void putNewTrackers(OutlierDetectionLoadBalancerConfig config, + Collection addresses) { + for (SocketAddress address : addresses) { + if (!trackerMap.containsKey(address)) { + trackerMap.put(address, new AddressTracker(config)); + } + } + } + + /** Resets the call counters for all the trackers in the map. */ + void resetCallCounters() { + for (AddressTracker tracker : trackerMap.values()) { + tracker.resetCallCounters(); + } + } + + /** + * When OD gets disabled we need to uneject any subchannels that may have been ejected and + * to reset the ejection time multiplier. + */ + void cancelTracking() { + for (AddressTracker tracker : trackerMap.values()) { + if (tracker.subchannelsEjected()) { + tracker.unejectSubchannels(); + } + tracker.resetEjectionTimeMultiplier(); + } + } + + /** Swaps the active and inactive counters for each tracker. */ + void swapCounters() { + for (AddressTracker tracker : trackerMap.values()) { + tracker.swapCounters(); + } + } + + /** + * At the end of a timer run we need to decrement the ejection time multiplier for trackers + * that don't have ejected subchannels and uneject ones that have spent the maximum ejection + * time allowed. + */ + void maybeUnejectOutliers(Long detectionTimerStartNanos) { + for (AddressTracker tracker : trackerMap.values()) { + if (!tracker.subchannelsEjected()) { + tracker.decrementEjectionTimeMultiplier(); + } + + if (tracker.subchannelsEjected() && tracker.maxEjectionTimeElapsed( + detectionTimerStartNanos)) { + tracker.unejectSubchannels(); + } + } + } + + /** + * How many percent of the addresses would have their subchannels ejected if we proceeded + * with the next ejection. + */ + double nextEjectionPercentage() { + if (trackerMap.isEmpty()) { + return 0; + } + int totalAddresses = 0; + int ejectedAddresses = 0; + for (AddressTracker tracker : trackerMap.values()) { + totalAddresses++; + if (tracker.subchannelsEjected()) { + ejectedAddresses++; + } + } + return ((double)(ejectedAddresses + 1) / totalAddresses) * 100; + } + } + + + /** + * Implementations provide different ways of ejecting outlier addresses.. + */ + interface OutlierEjectionAlgorithm { + + /** Eject any outlier addresses. */ + void ejectOutliers(AddressTrackerMap trackerMap, long ejectionTimeNanos); + + /** Builds a list of algorithms that are enabled in the given config. */ + @Nullable + static List forConfig(OutlierDetectionLoadBalancerConfig config) { + ImmutableList.Builder algoListBuilder = ImmutableList.builder(); + if (config.successRateEjection != null) { + algoListBuilder.add(new SuccessRateOutlierEjectionAlgorithm(config)); + } + if (config.failurePercentageEjection != null) { + algoListBuilder.add(new FailurePercentageOutlierEjectionAlgorithm(config)); + } + return algoListBuilder.build(); + } + } + + /** + * This algorithm ejects addresses that don't maintain a required rate of successful calls. The + * required rate is not fixed, but is based on the mean and standard deviation of the success + * rates of all of the addresses. + */ + static class SuccessRateOutlierEjectionAlgorithm implements OutlierEjectionAlgorithm { + + private final OutlierDetectionLoadBalancerConfig config; + + SuccessRateOutlierEjectionAlgorithm(OutlierDetectionLoadBalancerConfig config) { + checkArgument(config.successRateEjection != null, "success rate ejection config is null"); + this.config = config; + } + + @Override + public void ejectOutliers(AddressTrackerMap trackerMap, long ejectionTimeNanos) { + + // Only consider addresses that have the minimum request volume specified in the config. + List trackersWithVolume = trackersWithVolume(trackerMap, config); + // If we don't have enough addresses with significant volume then there's nothing to do. + if (trackersWithVolume.size() < config.successRateEjection.minimumHosts + || trackersWithVolume.size() == 0) { + return; + } + + // Calculate mean and standard deviation of the fractions of successful calls. + List successRates = new ArrayList<>(); + for (AddressTracker tracker : trackersWithVolume) { + successRates.add(tracker.successRate()); + } + double mean = mean(successRates); + double stdev = standardDeviation(successRates, mean); + + double requiredSuccessRate = + mean - stdev * (config.successRateEjection.stdevFactor / 1000f); + + for (AddressTracker tracker : trackersWithVolume) { + // If an ejection now would take us past the max configured ejection percentage, stop here. + if (trackerMap.nextEjectionPercentage() > config.maxEjectionPercent) { + return; + } + + // If success rate is below the threshold, eject the address. + if (tracker.successRate() < requiredSuccessRate) { + // Only eject some addresses based on the enforcement percentage. + if (new Random().nextInt(100) < config.successRateEjection.enforcementPercentage) { + tracker.ejectSubchannels(ejectionTimeNanos); + } + } + } + } + + /** Returns only the trackers that have the minimum configured volume to be considered. */ + private List trackersWithVolume(AddressTrackerMap trackerMap, + OutlierDetectionLoadBalancerConfig config) { + List trackersWithVolume = new ArrayList<>(); + for (AddressTracker tracker : trackerMap.values()) { + if (tracker.inactiveVolume() >= config.successRateEjection.requestVolume) { + trackersWithVolume.add(tracker); + } + } + return trackersWithVolume; + } + + /** Calculates the mean of the given values. */ + @VisibleForTesting + static double mean(Collection values) { + double totalValue = 0; + for (double value : values) { + totalValue += value; + } + + return totalValue / values.size(); + } + + /** Calculates the standard deviation for the given values and their mean. */ + @VisibleForTesting + static double standardDeviation(Collection values, double mean) { + double squaredDifferenceSum = 0; + for (double value : values) { + double difference = value - mean; + squaredDifferenceSum += difference * difference; + } + double variance = squaredDifferenceSum / values.size(); + + return Math.sqrt(variance); + } + } + + static class FailurePercentageOutlierEjectionAlgorithm implements OutlierEjectionAlgorithm { + + private final OutlierDetectionLoadBalancerConfig config; + + FailurePercentageOutlierEjectionAlgorithm(OutlierDetectionLoadBalancerConfig config) { + this.config = config; + } + + @Override + public void ejectOutliers(AddressTrackerMap trackerMap, long ejectionTimeNanos) { + + // If we don't have the minimum amount of addresses the config calls for, then return. + if (trackerMap.size() < config.failurePercentageEjection.minimumHosts) { + return; + } + + // If this address does not have enough volume to be considered, skip to the next one. + for (AddressTracker tracker : trackerMap.values()) { + // If an ejection now would take us past the max configured ejection percentage stop here. + if (trackerMap.nextEjectionPercentage() > config.maxEjectionPercent) { + return; + } + + if (tracker.inactiveVolume() < config.failurePercentageEjection.requestVolume) { + continue; + } + + // If the failure rate is above the threshold, we should eject... + double maxFailureRate = ((double)config.failurePercentageEjection.threshold) / 100; + if (tracker.failureRate() > maxFailureRate) { + // ...but only enforce this based on the enforcement percentage. + if (new Random().nextInt(100) < config.failurePercentageEjection.enforcementPercentage) { + tracker.ejectSubchannels(ejectionTimeNanos); + } + } + } + } + } + + /** Counts how many addresses are in a given address group. */ + private static boolean hasSingleAddress(List addressGroups) { + int addressCount = 0; + for (EquivalentAddressGroup addressGroup : addressGroups) { + addressCount += addressGroup.getAddresses().size(); + if (addressCount > 1) { + return false; + } + } + return true; + } + + /** + * The configuration for {@link OutlierDetectionLoadBalancer}. + */ + public static final class OutlierDetectionLoadBalancerConfig { + + final Long intervalNanos; + final Long baseEjectionTimeNanos; + final Long maxEjectionTimeNanos; + final Integer maxEjectionPercent; + final SuccessRateEjection successRateEjection; + final FailurePercentageEjection failurePercentageEjection; + final PolicySelection childPolicy; + + private OutlierDetectionLoadBalancerConfig(Long intervalNanos, + Long baseEjectionTimeNanos, + Long maxEjectionTimeNanos, + Integer maxEjectionPercent, + SuccessRateEjection successRateEjection, + FailurePercentageEjection failurePercentageEjection, + PolicySelection childPolicy) { + this.intervalNanos = intervalNanos; + this.baseEjectionTimeNanos = baseEjectionTimeNanos; + this.maxEjectionTimeNanos = maxEjectionTimeNanos; + this.maxEjectionPercent = maxEjectionPercent; + this.successRateEjection = successRateEjection; + this.failurePercentageEjection = failurePercentageEjection; + this.childPolicy = childPolicy; + } + + /** Builds a new {@link OutlierDetectionLoadBalancerConfig}. */ + public static class Builder { + Long intervalNanos = 10_000_000_000L; // 10s + Long baseEjectionTimeNanos = 30_000_000_000L; // 30s + Long maxEjectionTimeNanos = 30_000_000_000L; // 30s + Integer maxEjectionPercent = 10; + SuccessRateEjection successRateEjection; + FailurePercentageEjection failurePercentageEjection; + PolicySelection childPolicy; + + /** The interval between outlier detection sweeps. */ + public Builder setIntervalNanos(Long intervalNanos) { + checkArgument(intervalNanos != null); + this.intervalNanos = intervalNanos; + return this; + } + + /** The base time an address is ejected for. */ + public Builder setBaseEjectionTimeNanos(Long baseEjectionTimeNanos) { + checkArgument(baseEjectionTimeNanos != null); + this.baseEjectionTimeNanos = baseEjectionTimeNanos; + return this; + } + + /** The longest time an address can be ejected. */ + public Builder setMaxEjectionTimeNanos(Long maxEjectionTimeNanos) { + checkArgument(maxEjectionTimeNanos != null); + this.maxEjectionTimeNanos = maxEjectionTimeNanos; + return this; + } + + /** The algorithm agnostic maximum percentage of addresses that can be ejected. */ + public Builder setMaxEjectionPercent(Integer maxEjectionPercent) { + checkArgument(maxEjectionPercent != null); + this.maxEjectionPercent = maxEjectionPercent; + return this; + } + + /** Set to enable success rate ejection. */ + public Builder setSuccessRateEjection( + SuccessRateEjection successRateEjection) { + this.successRateEjection = successRateEjection; + return this; + } + + /** Set to enable failure percentage ejection. */ + public Builder setFailurePercentageEjection( + FailurePercentageEjection failurePercentageEjection) { + this.failurePercentageEjection = failurePercentageEjection; + return this; + } + + /** Sets the child policy the {@link OutlierDetectionLoadBalancer} delegates to. */ + public Builder setChildPolicy(PolicySelection childPolicy) { + checkState(childPolicy != null); + this.childPolicy = childPolicy; + return this; + } + + /** Builds a new instance of {@link OutlierDetectionLoadBalancerConfig}. */ + public OutlierDetectionLoadBalancerConfig build() { + checkState(childPolicy != null); + return new OutlierDetectionLoadBalancerConfig(intervalNanos, baseEjectionTimeNanos, + maxEjectionTimeNanos, maxEjectionPercent, successRateEjection, + failurePercentageEjection, childPolicy); + } + } + + /** The configuration for success rate ejection. */ + public static class SuccessRateEjection { + + final Integer stdevFactor; + final Integer enforcementPercentage; + final Integer minimumHosts; + final Integer requestVolume; + + SuccessRateEjection(Integer stdevFactor, Integer enforcementPercentage, Integer minimumHosts, + Integer requestVolume) { + this.stdevFactor = stdevFactor; + this.enforcementPercentage = enforcementPercentage; + this.minimumHosts = minimumHosts; + this.requestVolume = requestVolume; + } + + /** Builds new instances of {@link SuccessRateEjection}. */ + public static final class Builder { + + Integer stdevFactor = 1900; + Integer enforcementPercentage = 100; + Integer minimumHosts = 5; + Integer requestVolume = 100; + + /** The product of this and the standard deviation of success rates determine the ejection + * threshold. + */ + public Builder setStdevFactor(Integer stdevFactor) { + checkArgument(stdevFactor != null); + this.stdevFactor = stdevFactor; + return this; + } + + /** Only eject this percentage of outliers. */ + public Builder setEnforcementPercentage(Integer enforcementPercentage) { + checkArgument(enforcementPercentage != null); + checkArgument(enforcementPercentage >= 0 && enforcementPercentage <= 100); + this.enforcementPercentage = enforcementPercentage; + return this; + } + + /** The minimum amount of hosts needed for success rate ejection. */ + public Builder setMinimumHosts(Integer minimumHosts) { + checkArgument(minimumHosts != null); + checkArgument(minimumHosts >= 0); + this.minimumHosts = minimumHosts; + return this; + } + + /** The minimum address request volume to be considered for success rate ejection. */ + public Builder setRequestVolume(Integer requestVolume) { + checkArgument(requestVolume != null); + checkArgument(requestVolume >= 0); + this.requestVolume = requestVolume; + return this; + } + + /** Builds a new instance of {@link SuccessRateEjection}. */ + public SuccessRateEjection build() { + return new SuccessRateEjection(stdevFactor, enforcementPercentage, minimumHosts, + requestVolume); + } + } + } + + /** The configuration for failure percentage ejection. */ + public static class FailurePercentageEjection { + final Integer threshold; + final Integer enforcementPercentage; + final Integer minimumHosts; + final Integer requestVolume; + + FailurePercentageEjection(Integer threshold, Integer enforcementPercentage, + Integer minimumHosts, Integer requestVolume) { + this.threshold = threshold; + this.enforcementPercentage = enforcementPercentage; + this.minimumHosts = minimumHosts; + this.requestVolume = requestVolume; + } + + /** For building new {@link FailurePercentageEjection} instances. */ + public static class Builder { + Integer threshold = 85; + Integer enforcementPercentage = 100; + Integer minimumHosts = 5; + Integer requestVolume = 50; + + /** The failure percentage that will result in an address being considered an outlier. */ + public Builder setThreshold(Integer threshold) { + checkArgument(threshold != null); + checkArgument(threshold >= 0 && threshold <= 100); + this.threshold = threshold; + return this; + } + + /** Only eject this percentage of outliers. */ + public Builder setEnforcementPercentage(Integer enforcementPercentage) { + checkArgument(enforcementPercentage != null); + checkArgument(enforcementPercentage >= 0 && enforcementPercentage <= 100); + this.enforcementPercentage = enforcementPercentage; + return this; + } + + /** The minimum amount of host for failure percentage ejection to be enabled. */ + public Builder setMinimumHosts(Integer minimumHosts) { + checkArgument(minimumHosts != null); + checkArgument(minimumHosts >= 0); + this.minimumHosts = minimumHosts; + return this; + } + + /** + * The request volume required for an address to be considered for failure percentage + * ejection. + */ + public Builder setRequestVolume(Integer requestVolume) { + checkArgument(requestVolume != null); + checkArgument(requestVolume >= 0); + this.requestVolume = requestVolume; + return this; + } + + /** Builds a new instance of {@link FailurePercentageEjection}. */ + public FailurePercentageEjection build() { + return new FailurePercentageEjection(threshold, enforcementPercentage, minimumHosts, + requestVolume); + } + } + } + + /** Determine if any outlier detection algorithms are enabled in the config. */ + boolean outlierDetectionEnabled() { + return successRateEjection != null || failurePercentageEjection != null; + } + } +} diff --git a/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancerProvider.java b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancerProvider.java new file mode 100644 index 00000000000..a92f49bd1d2 --- /dev/null +++ b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancerProvider.java @@ -0,0 +1,158 @@ +/* + * Copyright 2022 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.util; + +import io.grpc.LoadBalancer; +import io.grpc.LoadBalancer.Helper; +import io.grpc.LoadBalancerProvider; +import io.grpc.LoadBalancerRegistry; +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.internal.TimeProvider; +import io.grpc.util.OutlierDetectionLoadBalancer.OutlierDetectionLoadBalancerConfig; +import io.grpc.util.OutlierDetectionLoadBalancer.OutlierDetectionLoadBalancerConfig.FailurePercentageEjection; +import io.grpc.util.OutlierDetectionLoadBalancer.OutlierDetectionLoadBalancerConfig.SuccessRateEjection; +import java.util.List; +import java.util.Map; + +public final class OutlierDetectionLoadBalancerProvider extends LoadBalancerProvider { + + @Override + public LoadBalancer newLoadBalancer(Helper helper) { + return new OutlierDetectionLoadBalancer(helper, TimeProvider.SYSTEM_TIME_PROVIDER); + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public int getPriority() { + return 5; + } + + @Override + public String getPolicyName() { + return "outlier_detection_experimental"; + } + + @Override + public ConfigOrError parseLoadBalancingPolicyConfig(Map rawConfig) { + // Common configuration. + Long intervalNanos = JsonUtil.getStringAsDuration(rawConfig, "interval"); + Long baseEjectionTimeNanos = JsonUtil.getStringAsDuration(rawConfig, "baseEjectionTime"); + Long maxEjectionTimeNanos = JsonUtil.getStringAsDuration(rawConfig, "maxEjectionTime"); + Integer maxEjectionPercentage = JsonUtil.getNumberAsInteger(rawConfig, + "maxEjectionPercentage"); + + OutlierDetectionLoadBalancerConfig.Builder configBuilder + = new OutlierDetectionLoadBalancerConfig.Builder(); + if (intervalNanos != null) { + configBuilder.setIntervalNanos(intervalNanos); + } + if (baseEjectionTimeNanos != null) { + configBuilder.setBaseEjectionTimeNanos(baseEjectionTimeNanos); + } + if (maxEjectionTimeNanos != null) { + configBuilder.setMaxEjectionTimeNanos(maxEjectionTimeNanos); + } + if (maxEjectionPercentage != null) { + configBuilder.setMaxEjectionPercent(maxEjectionPercentage); + } + + // Success rate ejection specific configuration. + Map rawSuccessRateEjection = JsonUtil.getObject(rawConfig, "successRateEjection"); + if (rawSuccessRateEjection != null) { + SuccessRateEjection.Builder successRateEjectionBuilder = new SuccessRateEjection.Builder(); + + Integer stdevFactor = JsonUtil.getNumberAsInteger(rawSuccessRateEjection, "stdevFactor"); + Integer enforcementPercentage = JsonUtil.getNumberAsInteger(rawSuccessRateEjection, + "enforcementPercentage"); + Integer minimumHosts = JsonUtil.getNumberAsInteger(rawSuccessRateEjection, "minimumHosts"); + Integer requestVolume = JsonUtil.getNumberAsInteger(rawSuccessRateEjection, "requestVolume"); + + if (stdevFactor != null) { + successRateEjectionBuilder.setStdevFactor(stdevFactor); + } + if (enforcementPercentage != null) { + successRateEjectionBuilder.setEnforcementPercentage(enforcementPercentage); + } + if (minimumHosts != null) { + successRateEjectionBuilder.setMinimumHosts(minimumHosts); + } + if (requestVolume != null) { + successRateEjectionBuilder.setRequestVolume(requestVolume); + } + + configBuilder.setSuccessRateEjection(successRateEjectionBuilder.build()); + } + + // Failure percentage ejection specific configuration. + Map rawFailurePercentageEjection = JsonUtil.getObject(rawConfig, + "failurePercentageEjection"); + if (rawFailurePercentageEjection != null) { + FailurePercentageEjection.Builder failurePercentageEjectionBuilder + = new FailurePercentageEjection.Builder(); + + Integer threshold = JsonUtil.getNumberAsInteger(rawFailurePercentageEjection, "threshold"); + Integer enforcementPercentage = JsonUtil.getNumberAsInteger(rawFailurePercentageEjection, + "enforcementPercentage"); + Integer minimumHosts = JsonUtil.getNumberAsInteger(rawFailurePercentageEjection, + "minimumHosts"); + Integer requestVolume = JsonUtil.getNumberAsInteger(rawFailurePercentageEjection, + "requestVolume"); + + if (threshold != null) { + failurePercentageEjectionBuilder.setThreshold(threshold); + } + if (enforcementPercentage != null) { + failurePercentageEjectionBuilder.setEnforcementPercentage(enforcementPercentage); + } + if (minimumHosts != null) { + failurePercentageEjectionBuilder.setMinimumHosts(minimumHosts); + } + if (requestVolume != null) { + failurePercentageEjectionBuilder.setRequestVolume(requestVolume); + } + + configBuilder.setFailurePercentageEjection(failurePercentageEjectionBuilder.build()); + } + + // Child load balancer configuration. + List childConfigCandidates = ServiceConfigUtil.unwrapLoadBalancingConfigList( + JsonUtil.getListOfObjects(rawConfig, "childPolicy")); + if (childConfigCandidates == null || childConfigCandidates.isEmpty()) { + return ConfigOrError.fromError(Status.INTERNAL.withDescription( + "No child policy in outlier_detection_experimental LB policy: " + + rawConfig)); + } + ConfigOrError selectedConfig = + ServiceConfigUtil.selectLbPolicyFromList(childConfigCandidates, + LoadBalancerRegistry.getDefaultRegistry()); + if (selectedConfig.getError() != null) { + return selectedConfig; + } + configBuilder.setChildPolicy((PolicySelection) selectedConfig.getConfig()); + + return ConfigOrError.fromConfig(configBuilder.build()); + } +} diff --git a/core/src/main/resources/META-INF/services/io.grpc.LoadBalancerProvider b/core/src/main/resources/META-INF/services/io.grpc.LoadBalancerProvider index cb200d5f044..d68a57c4eb3 100644 --- a/core/src/main/resources/META-INF/services/io.grpc.LoadBalancerProvider +++ b/core/src/main/resources/META-INF/services/io.grpc.LoadBalancerProvider @@ -1,2 +1,3 @@ io.grpc.internal.PickFirstLoadBalancerProvider io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider +io.grpc.util.OutlierDetectionLoadBalancerProvider diff --git a/core/src/test/java/io/grpc/internal/FakeClock.java b/core/src/test/java/io/grpc/internal/FakeClock.java index f5d22651271..9cc9178f1ff 100644 --- a/core/src/test/java/io/grpc/internal/FakeClock.java +++ b/core/src/test/java/io/grpc/internal/FakeClock.java @@ -159,8 +159,10 @@ private void schedule(ScheduledTask task, long delay, TimeUnit unit) { } @Override public ScheduledFuture scheduleWithFixedDelay( - Runnable command, long initialDelay, long delay, TimeUnit unit) { - throw new UnsupportedOperationException(); + Runnable cmd, long initialDelay, long delay, TimeUnit unit) { + ScheduledTask task = new ScheduleWithFixedDelayTask(cmd, delay, unit); + schedule(task, initialDelay, unit); + return task; } @Override public boolean awaitTermination(long timeout, TimeUnit unit) { @@ -234,6 +236,24 @@ public ScheduleAtFixedRateTask(Runnable command, long period, TimeUnit unit) { } } } + + class ScheduleWithFixedDelayTask extends ScheduledTask { + + final long delayNanos; + + ScheduleWithFixedDelayTask(Runnable command, long delay, TimeUnit unit) { + super(command); + this.delayNanos = unit.toNanos(delay); + } + + @Override + void run() { + command.run(); + if (!isCancelled()) { + schedule(this, delayNanos, TimeUnit.NANOSECONDS); + } + } + } } /** diff --git a/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerProviderTest.java b/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerProviderTest.java new file mode 100644 index 00000000000..5a27e6f176f --- /dev/null +++ b/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerProviderTest.java @@ -0,0 +1,141 @@ +/* + * Copyright 2022 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.util; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import io.grpc.InternalServiceProviders; +import io.grpc.LoadBalancer.Helper; +import io.grpc.LoadBalancerProvider; +import io.grpc.NameResolver.ConfigOrError; +import io.grpc.SynchronizationContext; +import io.grpc.internal.JsonParser; +import io.grpc.util.OutlierDetectionLoadBalancer.OutlierDetectionLoadBalancerConfig; +import java.io.IOException; +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests for {@link OutlierDetectionLoadBalancerProvider}. + */ +@RunWith(JUnit4.class) +public class OutlierDetectionLoadBalancerProviderTest { + + private final SynchronizationContext syncContext = new SynchronizationContext( + new UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + throw new AssertionError(e); + } + }); + private final OutlierDetectionLoadBalancerProvider provider + = new OutlierDetectionLoadBalancerProvider(); + + @Test + public void provided() { + for (LoadBalancerProvider current : InternalServiceProviders.getCandidatesViaServiceLoader( + LoadBalancerProvider.class, getClass().getClassLoader())) { + if (current instanceof OutlierDetectionLoadBalancerProvider) { + return; + } + } + fail("OutlierDetectionLoadBalancerProvider not registered"); + } + + @Test + public void providesLoadBalancer() { + Helper helper = mock(Helper.class); + when(helper.getSynchronizationContext()).thenReturn(syncContext); + when(helper.getScheduledExecutorService()).thenReturn(mock(ScheduledExecutorService.class)); + assertThat(provider.newLoadBalancer(helper)) + .isInstanceOf(OutlierDetectionLoadBalancer.class); + } + + @Test + public void parseLoadBalancingConfig_defaults() throws IOException { + String lbConfig = + "{ \"successRateEjection\" : {}, " + + "\"failurePercentageEjection\" : {}, " + + "\"childPolicy\" : [{\"round_robin\" : {}}]}"; + ConfigOrError configOrError = + provider.parseLoadBalancingPolicyConfig(parseJsonObject(lbConfig)); + assertThat(configOrError.getConfig()).isNotNull(); + OutlierDetectionLoadBalancerConfig config + = (OutlierDetectionLoadBalancerConfig) configOrError.getConfig(); + assertThat(config.successRateEjection).isNotNull(); + assertThat(config.failurePercentageEjection).isNotNull(); + assertThat(config.childPolicy.getProvider().getPolicyName()).isEqualTo("round_robin"); + } + + @Test + public void parseLoadBalancingConfig_valuesSet() throws IOException { + String lbConfig = + "{\"interval\" : \"100s\"," + + " \"baseEjectionTime\" : \"100s\"," + + " \"maxEjectionTime\" : \"100s\"," + + " \"maxEjectionPercentage\" : 100," + + " \"successRateEjection\" : {" + + " \"stdevFactor\" : 100," + + " \"enforcementPercentage\" : 100," + + " \"minimumHosts\" : 100," + + " \"requestVolume\" : 100" + + " }," + + " \"failurePercentageEjection\" : {" + + " \"threshold\" : 100," + + " \"enforcementPercentage\" : 100," + + " \"minimumHosts\" : 100," + + " \"requestVolume\" : 100" + + " }," + + "\"childPolicy\" : [{\"round_robin\" : {}}]}"; + ConfigOrError configOrError = + provider.parseLoadBalancingPolicyConfig(parseJsonObject(lbConfig)); + assertThat(configOrError.getConfig()).isNotNull(); + OutlierDetectionLoadBalancerConfig config + = (OutlierDetectionLoadBalancerConfig) configOrError.getConfig(); + + assertThat(config.intervalNanos).isEqualTo(100_000_000_000L); + assertThat(config.baseEjectionTimeNanos).isEqualTo(100_000_000_000L); + assertThat(config.maxEjectionTimeNanos).isEqualTo(100_000_000_000L); + assertThat(config.maxEjectionPercent).isEqualTo(100); + + assertThat(config.successRateEjection).isNotNull(); + assertThat(config.successRateEjection.stdevFactor).isEqualTo(100); + assertThat(config.successRateEjection.enforcementPercentage).isEqualTo(100); + assertThat(config.successRateEjection.minimumHosts).isEqualTo(100); + assertThat(config.successRateEjection.requestVolume).isEqualTo(100); + + assertThat(config.failurePercentageEjection).isNotNull(); + assertThat(config.failurePercentageEjection.threshold).isEqualTo(100); + assertThat(config.failurePercentageEjection.enforcementPercentage).isEqualTo(100); + assertThat(config.failurePercentageEjection.minimumHosts).isEqualTo(100); + assertThat(config.failurePercentageEjection.requestVolume).isEqualTo(100); + + assertThat(config.childPolicy.getProvider().getPolicyName()).isEqualTo("round_robin"); + } + + @SuppressWarnings("unchecked") + private static Map parseJsonObject(String json) throws IOException { + return (Map) JsonParser.parse(json); + } +} diff --git a/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java b/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java new file mode 100644 index 00000000000..5b73edb4c2c --- /dev/null +++ b/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java @@ -0,0 +1,1077 @@ +/* + * Copyright 2022 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.util; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; +import static io.grpc.ConnectivityState.READY; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import io.grpc.ClientStreamTracer; +import io.grpc.ConnectivityState; +import io.grpc.ConnectivityStateInfo; +import io.grpc.EquivalentAddressGroup; +import io.grpc.LoadBalancer; +import io.grpc.LoadBalancer.CreateSubchannelArgs; +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; +import io.grpc.LoadBalancer.SubchannelPicker; +import io.grpc.LoadBalancer.SubchannelStateListener; +import io.grpc.LoadBalancerProvider; +import io.grpc.Status; +import io.grpc.SynchronizationContext; +import io.grpc.internal.FakeClock; +import io.grpc.internal.FakeClock.ScheduledTask; +import io.grpc.internal.ServiceConfigUtil.PolicySelection; +import io.grpc.internal.TestUtils.StandardLoadBalancerProvider; +import io.grpc.util.OutlierDetectionLoadBalancer.AddressTracker; +import io.grpc.util.OutlierDetectionLoadBalancer.OutlierDetectionLoadBalancerConfig; +import io.grpc.util.OutlierDetectionLoadBalancer.OutlierDetectionLoadBalancerConfig.FailurePercentageEjection; +import io.grpc.util.OutlierDetectionLoadBalancer.OutlierDetectionLoadBalancerConfig.SuccessRateEjection; +import io.grpc.util.OutlierDetectionLoadBalancer.OutlierDetectionSubchannel; +import io.grpc.util.OutlierDetectionLoadBalancer.SuccessRateOutlierEjectionAlgorithm; +import java.net.SocketAddress; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import org.junit.Before; +import org.junit.Ignore; +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.Mock; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.mockito.stubbing.Answer; + +/** + * Unit tests for {@link OutlierDetectionLoadBalancer}. + */ +@RunWith(JUnit4.class) +public class OutlierDetectionLoadBalancerTest { + + @Rule + public final MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock + private LoadBalancer mockChildLb; + @Mock + private Helper mockHelper; + @Mock + private SocketAddress mockSocketAddress; + + @Captor + private ArgumentCaptor connectivityStateCaptor; + @Captor + private ArgumentCaptor errorPickerCaptor; + @Captor + private ArgumentCaptor pickerCaptor; + @Captor + private ArgumentCaptor stateCaptor; + + private final LoadBalancerProvider mockChildLbProvider = new StandardLoadBalancerProvider( + "foo_policy") { + @Override + public LoadBalancer newLoadBalancer(Helper helper) { + return mockChildLb; + } + }; + private final LoadBalancerProvider roundRobinLbProvider = new StandardLoadBalancerProvider( + "round_robin") { + @Override + public LoadBalancer newLoadBalancer(Helper helper) { + return new RoundRobinLoadBalancer(helper); + } + }; + + private final FakeClock fakeClock = new FakeClock(); + private final SynchronizationContext syncContext = new SynchronizationContext( + new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + throw new AssertionError(e); + } + }); + private OutlierDetectionLoadBalancer loadBalancer; + + private final List servers = Lists.newArrayList(); + private final Map, Subchannel> subchannels = Maps.newLinkedHashMap(); + private final Map subchannelStateListeners + = Maps.newLinkedHashMap(); + + private Subchannel subchannel1; + private Subchannel subchannel2; + private Subchannel subchannel3; + private Subchannel subchannel4; + private Subchannel subchannel5; + + @Before + public void setUp() { + for (int i = 0; i < 5; i++) { + SocketAddress addr = new FakeSocketAddress("server" + i); + EquivalentAddressGroup eag = new EquivalentAddressGroup(addr); + servers.add(eag); + Subchannel sc = mock(Subchannel.class); + subchannels.put(Arrays.asList(eag), sc); + } + + Iterator subchannelIterator = subchannels.values().iterator(); + subchannel1 = subchannelIterator.next(); + subchannel2 = subchannelIterator.next(); + subchannel3 = subchannelIterator.next(); + subchannel4 = subchannelIterator.next(); + subchannel5 = subchannelIterator.next(); + + when(mockHelper.getSynchronizationContext()).thenReturn(syncContext); + when(mockHelper.getScheduledExecutorService()).thenReturn( + fakeClock.getScheduledExecutorService()); + when(mockHelper.createSubchannel(any(CreateSubchannelArgs.class))).then( + new Answer() { + @Override + public Subchannel answer(InvocationOnMock invocation) throws Throwable { + CreateSubchannelArgs args = (CreateSubchannelArgs) invocation.getArguments()[0]; + final Subchannel subchannel = subchannels.get(args.getAddresses()); + when(subchannel.getAllAddresses()).thenReturn(args.getAddresses()); + when(subchannel.getAttributes()).thenReturn(args.getAttributes()); + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + subchannelStateListeners.put(subchannel, + (SubchannelStateListener) invocation.getArguments()[0]); + return null; + } + }).when(subchannel).start(any(SubchannelStateListener.class)); + return subchannel; + } + }); + + loadBalancer = new OutlierDetectionLoadBalancer(mockHelper, fakeClock.getTimeProvider()); + } + + @Test + public void handleNameResolutionError_noChildLb() { + loadBalancer.handleNameResolutionError(Status.DEADLINE_EXCEEDED); + + verify(mockHelper).updateBalancingState(connectivityStateCaptor.capture(), + errorPickerCaptor.capture()); + assertThat(connectivityStateCaptor.getValue()).isEqualTo(ConnectivityState.TRANSIENT_FAILURE); + } + + @Test + public void handleNameResolutionError_withChildLb() { + loadBalancer.handleResolvedAddresses(buildResolvedAddress( + new OutlierDetectionLoadBalancerConfig.Builder() + .setSuccessRateEjection(new SuccessRateEjection.Builder().build()) + .setChildPolicy(new PolicySelection(mockChildLbProvider, null)).build(), + new EquivalentAddressGroup(mockSocketAddress))); + loadBalancer.handleNameResolutionError(Status.DEADLINE_EXCEEDED); + + verify(mockChildLb).handleNameResolutionError(Status.DEADLINE_EXCEEDED); + } + + /** + * {@code shutdown()} is simply delegated. + */ + @Test + public void shutdown() { + loadBalancer.handleResolvedAddresses(buildResolvedAddress( + new OutlierDetectionLoadBalancerConfig.Builder() + .setSuccessRateEjection(new SuccessRateEjection.Builder().build()) + .setChildPolicy(new PolicySelection(mockChildLbProvider, null)).build(), + new EquivalentAddressGroup(mockSocketAddress))); + loadBalancer.shutdown(); + verify(mockChildLb).shutdown(); + } + + /** + * Base case for accepting new resolved addresses. + */ + @Test + public void handleResolvedAddresses() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setSuccessRateEjection(new SuccessRateEjection.Builder().build()) + .setChildPolicy(new PolicySelection(mockChildLbProvider, null)).build(); + ResolvedAddresses resolvedAddresses = buildResolvedAddress(config, + new EquivalentAddressGroup(mockSocketAddress)); + + loadBalancer.handleResolvedAddresses(resolvedAddresses); + + // Handling of resolved addresses is delegated + verify(mockChildLb).handleResolvedAddresses(resolvedAddresses); + + // There is a single pending task to run the outlier detection algorithm + assertThat(fakeClock.getPendingTasks()).hasSize(1); + + // The task is scheduled to run after a delay set in the config. + ScheduledTask task = fakeClock.getPendingTasks().iterator().next(); + assertThat(task.getDelay(TimeUnit.NANOSECONDS)).isEqualTo(config.intervalNanos); + } + + /** + * Outlier detection first enabled, then removed. + */ + @Test + public void handleResolvedAddresses_outlierDetectionDisabled() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setSuccessRateEjection(new SuccessRateEjection.Builder().build()) + .setChildPolicy(new PolicySelection(mockChildLbProvider, null)).build(); + ResolvedAddresses resolvedAddresses = buildResolvedAddress(config, + new EquivalentAddressGroup(mockSocketAddress)); + + loadBalancer.handleResolvedAddresses(resolvedAddresses); + + fakeClock.forwardTime(15, TimeUnit.SECONDS); + + // There is a single pending task to run the outlier detection algorithm + assertThat(fakeClock.getPendingTasks()).hasSize(1); + + config = new OutlierDetectionLoadBalancerConfig.Builder().setChildPolicy( + new PolicySelection(mockChildLbProvider, null)).build(); + loadBalancer.handleResolvedAddresses( + buildResolvedAddress(config, new EquivalentAddressGroup(mockSocketAddress))); + + // Pending task should be gone since OD is disabled. + assertThat(fakeClock.getPendingTasks()).isEmpty(); + + } + + /** + * Tests different scenarios when the timer interval in the config changes. + */ + @Test + public void handleResolvedAddresses_intervalUpdate() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setSuccessRateEjection(new SuccessRateEjection.Builder().build()) + .setChildPolicy(new PolicySelection(mockChildLbProvider, null)).build(); + ResolvedAddresses resolvedAddresses = buildResolvedAddress(config, + new EquivalentAddressGroup(mockSocketAddress)); + + loadBalancer.handleResolvedAddresses(resolvedAddresses); + + // Config update has doubled the interval + config = new OutlierDetectionLoadBalancerConfig.Builder() + .setIntervalNanos(config.intervalNanos * 2) + .setSuccessRateEjection(new SuccessRateEjection.Builder().build()) + .setChildPolicy(new PolicySelection(mockChildLbProvider, null)).build(); + + loadBalancer.handleResolvedAddresses( + buildResolvedAddress(config, new EquivalentAddressGroup(mockSocketAddress))); + + // If the timer has not run yet the task is just rescheduled to run after the new delay. + assertThat(fakeClock.getPendingTasks()).hasSize(1); + ScheduledTask task = fakeClock.getPendingTasks().iterator().next(); + assertThat(task.getDelay(TimeUnit.NANOSECONDS)).isEqualTo(config.intervalNanos); + assertThat(task.dueTimeNanos).isEqualTo(config.intervalNanos); + + // The new interval time has passed. The next task due time should have been pushed back another + // interval. + forwardTime(config); + assertThat(fakeClock.getPendingTasks()).hasSize(1); + task = fakeClock.getPendingTasks().iterator().next(); + assertThat(task.dueTimeNanos).isEqualTo(config.intervalNanos + config.intervalNanos + 1); + + // Some time passes and a second update comes down, but now the timer has had a chance to run, + // the new delay to timer start should consider when the timer last ran and if the interval is + // not changing in the config, the next task due time should remain unchanged. + fakeClock.forwardTime(4, TimeUnit.SECONDS); + task = fakeClock.getPendingTasks().iterator().next(); + loadBalancer.handleResolvedAddresses( + buildResolvedAddress(config, new EquivalentAddressGroup(mockSocketAddress))); + assertThat(task.dueTimeNanos).isEqualTo(config.intervalNanos + config.intervalNanos + 1); + } + + /** + * Confirm basic picking works by delegating to round_robin. + */ + @Test + public void delegatePick() throws Exception { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setSuccessRateEjection(new SuccessRateEjection.Builder().build()) + .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); + + loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers.get(0))); + + // Make one of the subchannels READY. + final Subchannel readySubchannel = subchannels.values().iterator().next(); + deliverSubchannelState(readySubchannel, ConnectivityStateInfo.forNonError(READY)); + + verify(mockHelper, times(3)).updateBalancingState(stateCaptor.capture(), + pickerCaptor.capture()); + + // Make sure that we can pick the single READY subchannel. + SubchannelPicker picker = pickerCaptor.getAllValues().get(2); + PickResult pickResult = picker.pickSubchannel(mock(PickSubchannelArgs.class)); + assertThat(((OutlierDetectionSubchannel) pickResult.getSubchannel()).delegate()).isEqualTo( + readySubchannel); + } + + /** + * The success rate algorithm leaves a healthy set of addresses alone. + */ + @Test + public void successRateNoOutliers() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setMaxEjectionPercent(50) + .setSuccessRateEjection( + new SuccessRateEjection.Builder().setMinimumHosts(3).setRequestVolume(10).build()) + .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); + + loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of()); + + // Move forward in time to a point where the detection timer has fired. + forwardTime(config); + + // No outliers, no ejections. + assertEjectedSubchannels(ImmutableSet.of()); + } + + /** + * The success rate algorithm ejects the outlier. + */ + @Test + public void successRateOneOutlier() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setMaxEjectionPercent(50) + .setSuccessRateEjection( + new SuccessRateEjection.Builder() + .setMinimumHosts(3) + .setRequestVolume(10).build()) + .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); + + loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED)); + + // Move forward in time to a point where the detection timer has fired. + forwardTime(config); + + // The one subchannel that was returning errors should be ejected. + assertEjectedSubchannels(ImmutableSet.of(servers.get(0).getAddresses().get(0))); + } + + /** + * The success rate algorithm ejects the outlier, but then the config changes so that similar + * behavior no longer gets ejected. + */ + @Test + public void successRateOneOutlier_configChange() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setMaxEjectionPercent(50) + .setSuccessRateEjection( + new SuccessRateEjection.Builder() + .setMinimumHosts(3) + .setRequestVolume(10).build()) + .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); + + loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED)); + + // Move forward in time to a point where the detection timer has fired. + forwardTime(config); + + // The one subchannel that was returning errors should be ejected. + assertEjectedSubchannels(ImmutableSet.of(servers.get(0).getAddresses().get(0))); + + // New config sets enforcement percentage to 0. + config = new OutlierDetectionLoadBalancerConfig.Builder() + .setMaxEjectionPercent(50) + .setSuccessRateEjection( + new SuccessRateEjection.Builder() + .setMinimumHosts(3) + .setRequestVolume(10) + .setEnforcementPercentage(0).build()) + .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); + + loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(subchannel2, Status.DEADLINE_EXCEEDED)); + + // Move forward in time to a point where the detection timer has fired. + forwardTime(config); + + // Since we brought enforcement percentage to 0, no additional ejection should have happened. + assertEjectedSubchannels(ImmutableSet.of(servers.get(0).getAddresses().get(0))); + } + + /** + * The success rate algorithm ejects the outlier but after some time it should get unejected + * if it stops being an outlier.. + */ + @Test + public void successRateOneOutlier_unejected() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setMaxEjectionPercent(50) + .setSuccessRateEjection( + new SuccessRateEjection.Builder() + .setMinimumHosts(3) + .setRequestVolume(10).build()) + .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); + + loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED)); + + // Move forward in time to a point where the detection timer has fired. + fakeClock.forwardTime(config.intervalNanos + 1, TimeUnit.NANOSECONDS); + + // The one subchannel that was returning errors should be ejected. + assertEjectedSubchannels(ImmutableSet.of(servers.get(0).getAddresses().get(0))); + + // Now we produce more load, but the subchannel start working and is no longer an outlier. + generateLoad(ImmutableMap.of()); + + // Move forward in time to a point where the detection timer has fired. + fakeClock.forwardTime(config.maxEjectionTimeNanos + 1, TimeUnit.NANOSECONDS); + + // No subchannels should remain ejected. + assertEjectedSubchannels(ImmutableSet.of()); + } + + /** + * The success rate algorithm ignores addresses without enough volume. + */ + @Test + public void successRateOneOutlier_notEnoughVolume() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setMaxEjectionPercent(50) + .setSuccessRateEjection( + new SuccessRateEjection.Builder() + .setMinimumHosts(3) + .setRequestVolume(20).build()) + .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); + + loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + + // We produce an outlier, but don't give it enough calls to reach the minimum volume. + generateLoad( + ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), + ImmutableMap.of(subchannel1, 19)); + + // Move forward in time to a point where the detection timer has fired. + forwardTime(config); + + // The address should not have been ejected. + assertEjectedSubchannels(ImmutableSet.of()); + } + + /** + * The success rate algorithm does not apply if enough addresses have the required volume. + */ + @Test + public void successRateOneOutlier_notEnoughAddressesWithVolume() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setMaxEjectionPercent(50) + .setSuccessRateEjection( + new SuccessRateEjection.Builder() + .setMinimumHosts(6) // We don't have this many hosts... + .setRequestVolume(10).build()) + .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); + + loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED)); + + // Move forward in time to a point where the detection timer has fired. + forwardTime(config); + + // No subchannels should have been ejected. + assertEjectedSubchannels(ImmutableSet.of()); + } + + /** + * The enforcementPercentage configuration should be honored. + */ + @Test + public void successRateOneOutlier_enforcementPercentage() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setMaxEjectionPercent(50) + .setSuccessRateEjection( + new SuccessRateEjection.Builder() + .setMinimumHosts(3) + .setRequestVolume(10) + .setEnforcementPercentage(0) + .build()) + .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); + + loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED)); + + // Move forward in time to a point where the detection timer has fired. + forwardTime(config); + + // There is one outlier, but because enforcementPercentage is 0, nothing should be ejected. + assertEjectedSubchannels(ImmutableSet.of()); + } + + /** + * Two outliers get ejected. + */ + @Test + public void successRateTwoOutliers() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setMaxEjectionPercent(50) + .setSuccessRateEjection( + new SuccessRateEjection.Builder() + .setMinimumHosts(3) + .setRequestVolume(10) + .setStdevFactor(1).build()) + .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); + + loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of( + subchannel1, Status.DEADLINE_EXCEEDED, + subchannel2, Status.DEADLINE_EXCEEDED)); + + // Move forward in time to a point where the detection timer has fired. + forwardTime(config); + + // The one subchannel that was returning errors should be ejected. + assertEjectedSubchannels(ImmutableSet.of(servers.get(0).getAddresses().get(0), + servers.get(1).getAddresses().get(0))); + } + + /** + * Two outliers. but only one gets ejected because we have reached the max ejection percentage. + */ + @Test + public void successRateTwoOutliers_maxEjectionPercentage() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setMaxEjectionPercent(20) + .setSuccessRateEjection( + new SuccessRateEjection.Builder() + .setMinimumHosts(3) + .setRequestVolume(10) + .setStdevFactor(1).build()) + .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); + + loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of( + subchannel1, Status.DEADLINE_EXCEEDED, + subchannel2, Status.DEADLINE_EXCEEDED)); + + // Move forward in time to a point where the detection timer has fired. + forwardTime(config); + + int totalEjected = 0; + for (EquivalentAddressGroup addressGroup: servers) { + totalEjected += + loadBalancer.trackerMap.get(addressGroup.getAddresses().get(0)).subchannelsEjected() ? 1 + : 0; + } + + // Even if all subchannels were failing, we should have not ejected more than the configured + // maximum percentage. + assertThat((double) totalEjected / servers.size()).isAtMost( + (double) config.maxEjectionPercent / 100); + } + + + /** + * The success rate algorithm leaves a healthy set of addresses alone. + */ + @Test + public void failurePercentageNoOutliers() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setMaxEjectionPercent(50) + .setFailurePercentageEjection( + new FailurePercentageEjection.Builder() + .setMinimumHosts(3) + .setRequestVolume(10).build()) + .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); + + loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + + // By default all calls will return OK. + generateLoad(ImmutableMap.of()); + + // Move forward in time to a point where the detection timer has fired. + forwardTime(config); + + // No outliers, no ejections. + assertEjectedSubchannels(ImmutableSet.of()); + } + + /** + * The success rate algorithm ejects the outlier. + */ + @Test + public void failurePercentageOneOutlier() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setMaxEjectionPercent(50) + .setFailurePercentageEjection( + new FailurePercentageEjection.Builder() + .setMinimumHosts(3) + .setRequestVolume(10).build()) + .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); + + loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED)); + + // Move forward in time to a point where the detection timer has fired. + forwardTime(config); + + // The one subchannel that was returning errors should be ejected. + assertEjectedSubchannels(ImmutableSet.of(servers.get(0).getAddresses().get(0))); + } + + /** + * The failure percentage algorithm ignores addresses without enough volume.. + */ + @Test + public void failurePercentageOneOutlier_notEnoughVolume() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setMaxEjectionPercent(50) + .setFailurePercentageEjection( + new FailurePercentageEjection.Builder() + .setMinimumHosts(3) + .setRequestVolume(100).build()) // We won't produce this much volume... + .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); + + loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED)); + + // Move forward in time to a point where the detection timer has fired. + forwardTime(config); + + // We should see no ejections. + assertEjectedSubchannels(ImmutableSet.of()); + } + + /** + * The enforcementPercentage configuration should be honored. + */ + @Test + public void failurePercentageOneOutlier_enforcementPercentage() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setMaxEjectionPercent(50) + .setFailurePercentageEjection( + new FailurePercentageEjection.Builder() + .setMinimumHosts(3) + .setRequestVolume(10) + .setEnforcementPercentage(0) + .build()) + .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); + + loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED)); + + // Move forward in time to a point where the detection timer has fired. + forwardTime(config); + + // There is one outlier, but because enforcementPercentage is 0, nothing should be ejected. + assertEjectedSubchannels(ImmutableSet.of()); + } + + /** Success rate detects two outliers and error percentage three. */ + @Test + public void successRateAndFailurePercentageThreeOutliers() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setMaxEjectionPercent(100) + .setSuccessRateEjection( + new SuccessRateEjection.Builder() + .setMinimumHosts(3) + .setRequestVolume(10) + .setStdevFactor(1).build()) + .setFailurePercentageEjection( + new FailurePercentageEjection.Builder() + .setThreshold(0) + .setMinimumHosts(3) + .setRequestVolume(1) + .build()) + .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); + + loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + + // Three subchannels with problems, but one only has a single call that failed. + // This is not enough for success rate to catch, but failure percentage is + // configured with a 0 tolerance threshold. + generateLoad( + ImmutableMap.of( + subchannel1, Status.DEADLINE_EXCEEDED, + subchannel2, Status.DEADLINE_EXCEEDED, + subchannel3, Status.DEADLINE_EXCEEDED), + ImmutableMap.of(subchannel3, 1)); + + // Move forward in time to a point where the detection timer has fired. + forwardTime(config); + + // Should see thee ejected, success rate cathes the first two, error percentage the + // same two plus the subchannel with the single failure. + assertEjectedSubchannels(ImmutableSet.of( + servers.get(0).getAddresses().get(0), + servers.get(1).getAddresses().get(0), + servers.get(2).getAddresses().get(0))); + } + + /** + * When the address a subchannel is associated with changes it should get tracked under the new + * address and its ejection state should match what the address has. + */ + @Test + public void subchannelUpdateAddress_singleReplaced() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setMaxEjectionPercent(50) + .setFailurePercentageEjection( + new FailurePercentageEjection.Builder() + .setMinimumHosts(3) + .setRequestVolume(10).build()) + .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); + + loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED)); + + // Move forward in time to a point where the detection timer has fired. + forwardTime(config); + + EquivalentAddressGroup oldAddressGroup = servers.get(0); + AddressTracker oldAddressTracker = loadBalancer.trackerMap.get( + oldAddressGroup.getAddresses().get(0)); + EquivalentAddressGroup newAddressGroup = servers.get(1); + AddressTracker newAddressTracker = loadBalancer.trackerMap.get( + newAddressGroup.getAddresses().get(0)); + + // The one subchannel that was returning errors should be ejected. + assertEjectedSubchannels(ImmutableSet.of(oldAddressGroup.getAddresses().get(0))); + + // The ejected subchannel gets updated with another address in the map that is not ejected + OutlierDetectionSubchannel subchannel = oldAddressTracker.getSubchannels() + .iterator().next(); + subchannel.updateAddresses(ImmutableList.of(newAddressGroup)); + + // The replaced address should no longer have the subchannel associated with it. + assertThat(oldAddressTracker.getSubchannels()).doesNotContain(subchannel); + + // The new address should instead have the subchannel. + assertThat(newAddressTracker.getSubchannels()).contains(subchannel); + + // Since the new address is not ejected, the ejected subchannel moving over to it should also + // become unejected. + assertThat(subchannel.isEjected()).isFalse(); + } + + /** + * If a single address gets replaced by multiple, the subchannel becomes uneligible for outlier + * detection. + */ + @Test + public void subchannelUpdateAddress_singleReplacedWithMultiple() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setMaxEjectionPercent(50) + .setFailurePercentageEjection( + new FailurePercentageEjection.Builder() + .setMinimumHosts(3) + .setRequestVolume(10).build()) + .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); + + loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of()); + + // Move forward in time to a point where the detection timer has fired. + forwardTime(config); + + EquivalentAddressGroup oldAddressGroup = servers.get(0); + AddressTracker oldAddressTracker = loadBalancer.trackerMap.get( + oldAddressGroup.getAddresses().get(0)); + EquivalentAddressGroup newAddress1 = servers.get(1); + EquivalentAddressGroup newAddress2 = servers.get(2); + + OutlierDetectionSubchannel subchannel = oldAddressTracker.getSubchannels() + .iterator().next(); + + // The subchannel gets updated with two new addresses + ImmutableList addressUpdate + = ImmutableList.of(newAddress1, newAddress2); + subchannel.updateAddresses(addressUpdate); + when(subchannel1.getAllAddresses()).thenReturn(addressUpdate); + + // The replaced address should no longer be tracked. + assertThat(oldAddressTracker.getSubchannels()).doesNotContain(subchannel); + + // The old tracker should also have its call counters cleared. + assertThat(oldAddressTracker.activeVolume()).isEqualTo(0); + assertThat(oldAddressTracker.inactiveVolume()).isEqualTo(0); + } + + /** + * A subchannel with multiple addresses will again become eligible for outlier detection if it + * receives an update with a single address. + * + *

TODO: Figure out how to test this scenario, round_robin does not support multiple addresses + * and fails the transition from multiple addresses to single. + */ + @Ignore + public void subchannelUpdateAddress_multipleReplacedWithSingle() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setMaxEjectionPercent(50) + .setFailurePercentageEjection( + new FailurePercentageEjection.Builder() + .setMinimumHosts(3) + .setRequestVolume(10).build()) + .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); + + loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED)); + + // Move forward in time to a point where the detection timer has fired. + forwardTime(config); + + EquivalentAddressGroup oldAddressGroup = servers.get(0); + AddressTracker oldAddressTracker = loadBalancer.trackerMap.get( + oldAddressGroup.getAddresses().get(0)); + EquivalentAddressGroup newAddressGroup1 = servers.get(1); + AddressTracker newAddressTracker1 = loadBalancer.trackerMap.get( + newAddressGroup1.getAddresses().get(0)); + EquivalentAddressGroup newAddressGroup2 = servers.get(2); + + // The old subchannel was returning errors and should be ejected. + assertEjectedSubchannels(ImmutableSet.of(oldAddressGroup.getAddresses().get(0))); + + OutlierDetectionSubchannel subchannel = oldAddressTracker.getSubchannels() + .iterator().next(); + + // The subchannel gets updated with two new addresses + ImmutableList addressUpdate + = ImmutableList.of(newAddressGroup1, newAddressGroup2); + subchannel.updateAddresses(addressUpdate); + when(subchannel1.getAllAddresses()).thenReturn(addressUpdate); + + // The replaced address should no longer be tracked. + assertThat(oldAddressTracker.getSubchannels()).doesNotContain(subchannel); + + // The old tracker should also have its call counters cleared. + assertThat(oldAddressTracker.activeVolume()).isEqualTo(0); + assertThat(oldAddressTracker.inactiveVolume()).isEqualTo(0); + + // Another update takes the subchannel back to a single address. + addressUpdate = ImmutableList.of(newAddressGroup1); + subchannel.updateAddresses(addressUpdate); + when(subchannel1.getAllAddresses()).thenReturn(addressUpdate); + + // The subchannel is now associated with the single new address. + assertThat(newAddressTracker1.getSubchannels()).contains(subchannel); + + // The previously ejected subchannel should become unejected as it is now associated with an + // unejected address. + assertThat(subchannel.isEjected()).isFalse(); + } + + /** Both algorithms configured, but no outliers. */ + @Test + public void successRateAndFailurePercentage_noOutliers() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setMaxEjectionPercent(50) + .setSuccessRateEjection( + new SuccessRateEjection.Builder() + .setMinimumHosts(3) + .setRequestVolume(10).build()) + .setFailurePercentageEjection( + new FailurePercentageEjection.Builder() + .setMinimumHosts(3) + .setRequestVolume(10).build()) + .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); + + loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of()); + + // Move forward in time to a point where the detection timer has fired. + forwardTime(config); + + // No outliers, no ejections. + assertEjectedSubchannels(ImmutableSet.of()); + } + + /** Both algorithms configured, success rate detects an outlier. */ + @Test + public void successRateAndFailurePercentage_successRateOutlier() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setMaxEjectionPercent(50) + .setSuccessRateEjection( + new SuccessRateEjection.Builder() + .setMinimumHosts(3) + .setRequestVolume(10).build()) + .setFailurePercentageEjection( + new FailurePercentageEjection.Builder() + .setMinimumHosts(3) + .setRequestVolume(10) + .setEnforcementPercentage(0).build()) // Configured, but not enforcing. + .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); + + loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED)); + + // Move forward in time to a point where the detection timer has fired. + forwardTime(config); + + // The one subchannel that was returning errors should be ejected. + assertEjectedSubchannels(ImmutableSet.of(servers.get(0).getAddresses().get(0))); + } + + /** Both algorithms configured, error percentage detects an outlier. */ + @Test + public void successRateAndFailurePercentage_errorPercentageOutlier() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setMaxEjectionPercent(50) + .setSuccessRateEjection( + new SuccessRateEjection.Builder() + .setMinimumHosts(3) + .setRequestVolume(10) + .setEnforcementPercentage(0).build()) + .setFailurePercentageEjection( + new FailurePercentageEjection.Builder() + .setMinimumHosts(3) + .setRequestVolume(10).build()) // Configured, but not enforcing. + .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); + + loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED)); + + // Move forward in time to a point where the detection timer has fired. + forwardTime(config); + + // The one subchannel that was returning errors should be ejected. + assertEjectedSubchannels(ImmutableSet.of(servers.get(0).getAddresses().get(0))); + } + + @Test + public void mathChecksOut() { + ImmutableList values = ImmutableList.of(600d, 470d, 170d, 430d, 300d); + double mean = SuccessRateOutlierEjectionAlgorithm.mean(values); + double stdev = SuccessRateOutlierEjectionAlgorithm.standardDeviation(values, mean); + + assertThat(mean).isEqualTo(394); + assertThat(stdev).isEqualTo(147.32277488562318); + } + + private static class FakeSocketAddress extends SocketAddress { + + final String name; + + FakeSocketAddress(String name) { + this.name = name; + } + + @Override + public String toString() { + return "FakeSocketAddress-" + name; + } + } + + private ResolvedAddresses buildResolvedAddress(OutlierDetectionLoadBalancerConfig config, + EquivalentAddressGroup... servers) { + return ResolvedAddresses.newBuilder().setAddresses(ImmutableList.copyOf(servers)) + .setLoadBalancingPolicyConfig(config).build(); + } + + private ResolvedAddresses buildResolvedAddress(OutlierDetectionLoadBalancerConfig config, + List servers) { + return ResolvedAddresses.newBuilder().setAddresses(ImmutableList.copyOf(servers)) + .setLoadBalancingPolicyConfig(config).build(); + } + + private void deliverSubchannelState(Subchannel subchannel, ConnectivityStateInfo newState) { + subchannelStateListeners.get(subchannel).onSubchannelState(newState); + } + + private void generateLoad(Map statusMap) { + generateLoad(statusMap, null); + } + + // Generates 100 calls, 20 each across the subchannels. Default status is OK. + private void generateLoad(Map statusMap, + Map maxCallsMap) { + deliverSubchannelState(subchannel1, ConnectivityStateInfo.forNonError(READY)); + deliverSubchannelState(subchannel2, ConnectivityStateInfo.forNonError(READY)); + deliverSubchannelState(subchannel3, ConnectivityStateInfo.forNonError(READY)); + deliverSubchannelState(subchannel4, ConnectivityStateInfo.forNonError(READY)); + deliverSubchannelState(subchannel5, ConnectivityStateInfo.forNonError(READY)); + + verify(mockHelper, times(7)).updateBalancingState(stateCaptor.capture(), + pickerCaptor.capture()); + SubchannelPicker picker = pickerCaptor.getAllValues().get(6); + + HashMap callCountMap = new HashMap<>(); + for (int i = 0; i < 100; i++) { + PickResult pickResult = picker + .pickSubchannel(mock(PickSubchannelArgs.class)); + ClientStreamTracer clientStreamTracer = pickResult.getStreamTracerFactory() + .newClientStreamTracer(null, null); + + Subchannel subchannel = ((OutlierDetectionSubchannel) pickResult.getSubchannel()).delegate(); + + int maxCalls = + maxCallsMap != null && maxCallsMap.containsKey(subchannel) + ? maxCallsMap.get(subchannel) : Integer.MAX_VALUE; + int calls = callCountMap.containsKey(subchannel) ? callCountMap.get(subchannel) : 0; + if (calls < maxCalls) { + callCountMap.put(subchannel, ++calls); + clientStreamTracer.streamClosed( + statusMap.containsKey(subchannel) ? statusMap.get(subchannel) : Status.OK); + } + } + } + + // Forwards time past the moment when the timer will fire. + private void forwardTime(OutlierDetectionLoadBalancerConfig config) { + fakeClock.forwardTime(config.intervalNanos + 1, TimeUnit.NANOSECONDS); + } + + // Asserts that the given addresses are ejected and the rest are not. + void assertEjectedSubchannels(Set addresses) { + for (Entry entry : loadBalancer.trackerMap.entrySet()) { + assertWithMessage("not ejected: " + entry.getKey()) + .that(entry.getValue().subchannelsEjected()) + .isEqualTo(addresses.contains(entry.getKey())); + } + } +} From 81abb21e7f06788a89a6089730e1d75f175f3fb2 Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Thu, 18 Aug 2022 16:06:17 -0700 Subject: [PATCH 10/72] xds: Configure outlier detection. (#9456) Enables the new OutlierDetectionLoadBalancer when outlier detection is enabled in the xDS cluster configuration. --- .../util/OutlierDetectionLoadBalancer.java | 34 +- .../OutlierDetectionLoadBalancerProvider.java | 2 + .../java/io/grpc/xds/CdsLoadBalancer2.java | 2 +- .../java/io/grpc/xds/ClientXdsClient.java | 78 ++++- .../grpc/xds/ClusterResolverLoadBalancer.java | 115 ++++++- .../ClusterResolverLoadBalancerProvider.java | 14 +- .../io/grpc/xds/EnvoyServerProtoData.java | 145 +++++++++ xds/src/main/java/io/grpc/xds/XdsClient.java | 16 +- .../io/grpc/xds/CdsLoadBalancer2Test.java | 82 ++--- .../io/grpc/xds/ClientXdsClientTestBase.java | 292 ++++++++++++++++-- .../io/grpc/xds/ClientXdsClientV2Test.java | 12 +- .../io/grpc/xds/ClientXdsClientV3Test.java | 12 +- .../xds/ClusterResolverLoadBalancerTest.java | 102 +++++- 13 files changed, 789 insertions(+), 117 deletions(-) diff --git a/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java index 3fb32fdb078..66380b9631b 100644 --- a/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java +++ b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java @@ -31,6 +31,7 @@ import io.grpc.ConnectivityState; import io.grpc.ConnectivityStateInfo; import io.grpc.EquivalentAddressGroup; +import io.grpc.Internal; import io.grpc.LoadBalancer; import io.grpc.Metadata; import io.grpc.Status; @@ -58,7 +59,8 @@ *

This implements the outlier detection gRFC: * https://github.com/grpc/proposal/blob/master/A50-xds-outlier-detection.md */ -public class OutlierDetectionLoadBalancer extends LoadBalancer { +@Internal +public final class OutlierDetectionLoadBalancer extends LoadBalancer { @VisibleForTesting final AddressTrackerMap trackerMap; @@ -837,13 +839,13 @@ private static boolean hasSingleAddress(List addressGrou */ public static final class OutlierDetectionLoadBalancerConfig { - final Long intervalNanos; - final Long baseEjectionTimeNanos; - final Long maxEjectionTimeNanos; - final Integer maxEjectionPercent; - final SuccessRateEjection successRateEjection; - final FailurePercentageEjection failurePercentageEjection; - final PolicySelection childPolicy; + public final Long intervalNanos; + public final Long baseEjectionTimeNanos; + public final Long maxEjectionTimeNanos; + public final Integer maxEjectionPercent; + public final SuccessRateEjection successRateEjection; + public final FailurePercentageEjection failurePercentageEjection; + public final PolicySelection childPolicy; private OutlierDetectionLoadBalancerConfig(Long intervalNanos, Long baseEjectionTimeNanos, @@ -932,10 +934,10 @@ public OutlierDetectionLoadBalancerConfig build() { /** The configuration for success rate ejection. */ public static class SuccessRateEjection { - final Integer stdevFactor; - final Integer enforcementPercentage; - final Integer minimumHosts; - final Integer requestVolume; + public final Integer stdevFactor; + public final Integer enforcementPercentage; + public final Integer minimumHosts; + public final Integer requestVolume; SuccessRateEjection(Integer stdevFactor, Integer enforcementPercentage, Integer minimumHosts, Integer requestVolume) { @@ -996,10 +998,10 @@ public SuccessRateEjection build() { /** The configuration for failure percentage ejection. */ public static class FailurePercentageEjection { - final Integer threshold; - final Integer enforcementPercentage; - final Integer minimumHosts; - final Integer requestVolume; + public final Integer threshold; + public final Integer enforcementPercentage; + public final Integer minimumHosts; + public final Integer requestVolume; FailurePercentageEjection(Integer threshold, Integer enforcementPercentage, Integer minimumHosts, Integer requestVolume) { diff --git a/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancerProvider.java b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancerProvider.java index a92f49bd1d2..e52c7414653 100644 --- a/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancerProvider.java +++ b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancerProvider.java @@ -16,6 +16,7 @@ package io.grpc.util; +import io.grpc.Internal; import io.grpc.LoadBalancer; import io.grpc.LoadBalancer.Helper; import io.grpc.LoadBalancerProvider; @@ -33,6 +34,7 @@ import java.util.List; import java.util.Map; +@Internal public final class OutlierDetectionLoadBalancerProvider extends LoadBalancerProvider { @Override diff --git a/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java b/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java index 4a599ef232c..4fa701c3c42 100644 --- a/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java +++ b/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java @@ -159,7 +159,7 @@ private void handleClusterDiscovered() { instance = DiscoveryMechanism.forEds( clusterState.name, clusterState.result.edsServiceName(), clusterState.result.lrsServerInfo(), clusterState.result.maxConcurrentRequests(), - clusterState.result.upstreamTlsContext()); + clusterState.result.upstreamTlsContext(), clusterState.result.outlierDetection()); } else { // logical DNS instance = DiscoveryMechanism.forLogicalDns( clusterState.name, clusterState.result.dnsHostName(), diff --git a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java index 9a4c366f84f..208b9a2852f 100644 --- a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java +++ b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java @@ -90,6 +90,7 @@ import io.grpc.xds.EnvoyServerProtoData.ConnectionSourceType; import io.grpc.xds.EnvoyServerProtoData.FilterChain; import io.grpc.xds.EnvoyServerProtoData.FilterChainMatch; +import io.grpc.xds.EnvoyServerProtoData.OutlierDetection; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.Filter.ClientInterceptorBuilder; import io.grpc.xds.Filter.FilterConfig; @@ -166,6 +167,10 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res static boolean enableCustomLbConfig = Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_XDS_CUSTOM_LB_CONFIG")) || Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_XDS_CUSTOM_LB_CONFIG")); + @VisibleForTesting + static boolean enableOutlierDetection = + !Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_XDS_OUTLIER_DETECTION")) + || Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_XDS_OUTLIER_DETECTION")); private static final String TYPE_URL_HTTP_CONNECTION_MANAGER_V2 = "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2" + ".HttpConnectionManager"; @@ -632,6 +637,65 @@ static void validateCommonTlsContext( } } + static io.envoyproxy.envoy.config.cluster.v3.OutlierDetection validateOutlierDetection( + io.envoyproxy.envoy.config.cluster.v3.OutlierDetection outlierDetection) + throws ResourceInvalidException { + if (outlierDetection.hasInterval()) { + if (!Durations.isValid(outlierDetection.getInterval())) { + throw new ResourceInvalidException("outlier_detection interval is not a valid Duration"); + } + if (hasNegativeValues(outlierDetection.getInterval())) { + throw new ResourceInvalidException("outlier_detection interval has a negative value"); + } + } + if (outlierDetection.hasBaseEjectionTime()) { + if (!Durations.isValid(outlierDetection.getBaseEjectionTime())) { + throw new ResourceInvalidException( + "outlier_detection base_ejection_time is not a valid Duration"); + } + if (hasNegativeValues(outlierDetection.getBaseEjectionTime())) { + throw new ResourceInvalidException( + "outlier_detection base_ejection_time has a negative value"); + } + } + if (outlierDetection.hasMaxEjectionTime()) { + if (!Durations.isValid(outlierDetection.getMaxEjectionTime())) { + throw new ResourceInvalidException( + "outlier_detection max_ejection_time is not a valid Duration"); + } + if (hasNegativeValues(outlierDetection.getMaxEjectionTime())) { + throw new ResourceInvalidException( + "outlier_detection max_ejection_time has a negative value"); + } + } + if (outlierDetection.hasMaxEjectionPercent() + && outlierDetection.getMaxEjectionPercent().getValue() > 100) { + throw new ResourceInvalidException( + "outlier_detection max_ejection_percent is > 100"); + } + if (outlierDetection.hasEnforcingSuccessRate() + && outlierDetection.getEnforcingSuccessRate().getValue() > 100) { + throw new ResourceInvalidException( + "outlier_detection enforcing_success_rate is > 100"); + } + if (outlierDetection.hasFailurePercentageThreshold() + && outlierDetection.getFailurePercentageThreshold().getValue() > 100) { + throw new ResourceInvalidException( + "outlier_detection failure_percentage_threshold is > 100"); + } + if (outlierDetection.hasEnforcingFailurePercentage() + && outlierDetection.getEnforcingFailurePercentage().getValue() > 100) { + throw new ResourceInvalidException( + "outlier_detection enforcing_failure_percentage is > 100"); + } + + return outlierDetection; + } + + static boolean hasNegativeValues(Duration duration) { + return duration.getSeconds() < 0 || duration.getNanos() < 0; + } + private static String getIdentityCertInstanceName(CommonTlsContext commonTlsContext) { if (commonTlsContext.hasTlsCertificateProviderInstance()) { return commonTlsContext.getTlsCertificateProviderInstance().getInstanceName(); @@ -1704,6 +1768,7 @@ private static StructOrError parseNonAggregateCluster( ServerInfo lrsServerInfo = null; Long maxConcurrentRequests = null; UpstreamTlsContext upstreamTlsContext = null; + OutlierDetection outlierDetection = null; if (cluster.hasLrsServer()) { if (!cluster.getLrsServer().hasSelf()) { return StructOrError.fromError( @@ -1743,6 +1808,16 @@ private static StructOrError parseNonAggregateCluster( "Cluster " + clusterName + ": malformed UpstreamTlsContext: " + e); } } + if (cluster.hasOutlierDetection() && enableOutlierDetection) { + try { + outlierDetection = OutlierDetection.fromEnvoyOutlierDetection( + validateOutlierDetection(cluster.getOutlierDetection())); + } catch (ResourceInvalidException e) { + return StructOrError.fromError( + "Cluster " + clusterName + ": malformed outlier_detection: " + e); + } + } + DiscoveryType type = cluster.getType(); if (type == DiscoveryType.EDS) { @@ -1763,7 +1838,8 @@ private static StructOrError parseNonAggregateCluster( edsResources.add(clusterName); } return StructOrError.fromStruct(CdsUpdate.forEds( - clusterName, edsServiceName, lrsServerInfo, maxConcurrentRequests, upstreamTlsContext)); + clusterName, edsServiceName, lrsServerInfo, maxConcurrentRequests, upstreamTlsContext, + outlierDetection)); } else if (type.equals(DiscoveryType.LOGICAL_DNS)) { if (!cluster.hasLoadAssignment()) { return StructOrError.fromError( diff --git a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java index 91da43eb24a..96e2cf441a0 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java @@ -38,6 +38,7 @@ import io.grpc.internal.ServiceConfigUtil.PolicySelection; import io.grpc.util.ForwardingLoadBalancerHelper; import io.grpc.util.GracefulSwitchLoadBalancer; +import io.grpc.util.OutlierDetectionLoadBalancer.OutlierDetectionLoadBalancerConfig; import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig; import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig; @@ -45,6 +46,9 @@ import io.grpc.xds.Endpoints.DropOverload; import io.grpc.xds.Endpoints.LbEndpoint; import io.grpc.xds.Endpoints.LocalityLbEndpoints; +import io.grpc.xds.EnvoyServerProtoData.FailurePercentageEjection; +import io.grpc.xds.EnvoyServerProtoData.OutlierDetection; +import io.grpc.xds.EnvoyServerProtoData.SuccessRateEjection; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig; import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig.PriorityChildConfig; @@ -176,7 +180,8 @@ public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { ClusterState state; if (instance.type == DiscoveryMechanism.Type.EDS) { state = new EdsClusterState(instance.cluster, instance.edsServiceName, - instance.lrsServerInfo, instance.maxConcurrentRequests, instance.tlsContext); + instance.lrsServerInfo, instance.maxConcurrentRequests, instance.tlsContext, + instance.outlierDetection); } else { // logical DNS state = new LogicalDnsClusterState(instance.cluster, instance.dnsHostName, instance.lrsServerInfo, instance.maxConcurrentRequests, instance.tlsContext); @@ -316,6 +321,8 @@ private abstract class ClusterState { protected final Long maxConcurrentRequests; @Nullable protected final UpstreamTlsContext tlsContext; + @Nullable + protected final OutlierDetection outlierDetection; // Resolution status, may contain most recent error encountered. protected Status status = Status.OK; // True if has received resolution result. @@ -327,11 +334,13 @@ private abstract class ClusterState { protected boolean shutdown; private ClusterState(String name, @Nullable ServerInfo lrsServerInfo, - @Nullable Long maxConcurrentRequests, @Nullable UpstreamTlsContext tlsContext) { + @Nullable Long maxConcurrentRequests, @Nullable UpstreamTlsContext tlsContext, + @Nullable OutlierDetection outlierDetection) { this.name = name; this.lrsServerInfo = lrsServerInfo; this.maxConcurrentRequests = maxConcurrentRequests; this.tlsContext = tlsContext; + this.outlierDetection = outlierDetection; } abstract void start(); @@ -349,8 +358,8 @@ private final class EdsClusterState extends ClusterState implements EdsResourceW private EdsClusterState(String name, @Nullable String edsServiceName, @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, - @Nullable UpstreamTlsContext tlsContext) { - super(name, lrsServerInfo, maxConcurrentRequests, tlsContext); + @Nullable UpstreamTlsContext tlsContext, @Nullable OutlierDetection outlierDetection) { + super(name, lrsServerInfo, maxConcurrentRequests, tlsContext, outlierDetection); this.edsServiceName = edsServiceName; } @@ -434,7 +443,8 @@ public void run() { Map priorityChildConfigs = generateEdsBasedPriorityChildConfigs( name, edsServiceName, lrsServerInfo, maxConcurrentRequests, tlsContext, - endpointLbPolicy, lbRegistry, prioritizedLocalityWeights, dropOverloads); + outlierDetection, endpointLbPolicy, lbRegistry, prioritizedLocalityWeights, + dropOverloads); status = Status.OK; resolved = true; result = new ClusterResolutionResult(addresses, priorityChildConfigs, @@ -530,7 +540,7 @@ private final class LogicalDnsClusterState extends ClusterState { private LogicalDnsClusterState(String name, String dnsHostName, @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, @Nullable UpstreamTlsContext tlsContext) { - super(name, lrsServerInfo, maxConcurrentRequests, tlsContext); + super(name, lrsServerInfo, maxConcurrentRequests, tlsContext, null); this.dnsHostName = checkNotNull(dnsHostName, "dnsHostName"); nameResolverFactory = checkNotNull(helper.getNameResolverRegistry().asFactory(), "nameResolverFactory"); @@ -730,9 +740,9 @@ private static PriorityChildConfig generateDnsBasedPriorityChildConfig( private static Map generateEdsBasedPriorityChildConfigs( String cluster, @Nullable String edsServiceName, @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, @Nullable UpstreamTlsContext tlsContext, - PolicySelection endpointLbPolicy, LoadBalancerRegistry lbRegistry, - Map> prioritizedLocalityWeights, - List dropOverloads) { + @Nullable OutlierDetection outlierDetection, PolicySelection endpointLbPolicy, + LoadBalancerRegistry lbRegistry, Map> prioritizedLocalityWeights, List dropOverloads) { Map configs = new HashMap<>(); for (String priority : prioritizedLocalityWeights.keySet()) { ClusterImplConfig clusterImplConfig = @@ -740,15 +750,98 @@ private static Map generateEdsBasedPriorityChildCon dropOverloads, endpointLbPolicy, tlsContext); LoadBalancerProvider clusterImplLbProvider = lbRegistry.getProvider(XdsLbPolicies.CLUSTER_IMPL_POLICY_NAME); - PolicySelection clusterImplPolicy = + PolicySelection priorityChildPolicy = new PolicySelection(clusterImplLbProvider, clusterImplConfig); + + // If outlier detection has been configured we wrap the child policy in the outlier detection + // load balancer. + if (outlierDetection != null) { + LoadBalancerProvider outlierDetectionProvider = lbRegistry.getProvider( + "outlier_detection_experimental"); + priorityChildPolicy = new PolicySelection(outlierDetectionProvider, + buildOutlierDetectionLbConfig(outlierDetection, priorityChildPolicy)); + } + PriorityChildConfig priorityChildConfig = - new PriorityChildConfig(clusterImplPolicy, true /* ignoreReresolution */); + new PriorityChildConfig(priorityChildPolicy, true /* ignoreReresolution */); configs.put(priority, priorityChildConfig); } return configs; } + /** + * Converts {@link OutlierDetection} that represents the xDS configuration to {@link + * OutlierDetectionLoadBalancerConfig} that the {@link io.grpc.util.OutlierDetectionLoadBalancer} + * understands. + */ + private static OutlierDetectionLoadBalancerConfig buildOutlierDetectionLbConfig( + OutlierDetection outlierDetection, PolicySelection childPolicy) { + OutlierDetectionLoadBalancerConfig.Builder configBuilder + = new OutlierDetectionLoadBalancerConfig.Builder(); + + configBuilder.setChildPolicy(childPolicy); + + if (outlierDetection.intervalNanos() != null) { + configBuilder.setIntervalNanos(outlierDetection.intervalNanos()); + } + if (outlierDetection.baseEjectionTimeNanos() != null) { + configBuilder.setBaseEjectionTimeNanos(outlierDetection.baseEjectionTimeNanos()); + } + if (outlierDetection.maxEjectionTimeNanos() != null) { + configBuilder.setMaxEjectionTimeNanos(outlierDetection.maxEjectionTimeNanos()); + } + if (outlierDetection.maxEjectionPercent() != null) { + configBuilder.setMaxEjectionPercent(outlierDetection.maxEjectionPercent()); + } + + SuccessRateEjection successRate = outlierDetection.successRateEjection(); + if (successRate != null) { + OutlierDetectionLoadBalancerConfig.SuccessRateEjection.Builder + successRateConfigBuilder = new OutlierDetectionLoadBalancerConfig + .SuccessRateEjection.Builder(); + + if (successRate.stdevFactor() != null) { + successRateConfigBuilder.setStdevFactor(successRate.stdevFactor()); + } + if (successRate.enforcementPercentage() != null) { + successRateConfigBuilder.setEnforcementPercentage(successRate.enforcementPercentage()); + } + if (successRate.minimumHosts() != null) { + successRateConfigBuilder.setMinimumHosts(successRate.minimumHosts()); + } + if (successRate.requestVolume() != null) { + successRateConfigBuilder.setRequestVolume(successRate.requestVolume()); + } + + configBuilder.setSuccessRateEjection(successRateConfigBuilder.build()); + } + + FailurePercentageEjection failurePercentage = outlierDetection.failurePercentageEjection(); + if (failurePercentage != null) { + OutlierDetectionLoadBalancerConfig.FailurePercentageEjection.Builder + failurePercentageConfigBuilder = new OutlierDetectionLoadBalancerConfig + .FailurePercentageEjection.Builder(); + + if (failurePercentage.threshold() != null) { + failurePercentageConfigBuilder.setThreshold(failurePercentage.threshold()); + } + if (failurePercentage.enforcementPercentage() != null) { + failurePercentageConfigBuilder.setEnforcementPercentage( + failurePercentage.enforcementPercentage()); + } + if (failurePercentage.minimumHosts() != null) { + failurePercentageConfigBuilder.setMinimumHosts(failurePercentage.minimumHosts()); + } + if (failurePercentage.requestVolume() != null) { + failurePercentageConfigBuilder.setRequestVolume(failurePercentage.requestVolume()); + } + + configBuilder.setFailurePercentageEjection(failurePercentageConfigBuilder.build()); + } + + return configBuilder.build(); + } + /** * Generates a string that represents the priority in the LB policy config. The string is unique * across priorities in all clusters and priorityName(c, p1) < priorityName(c, p2) iff p1 < p2. diff --git a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancerProvider.java b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancerProvider.java index 6f6f887e925..38da1f465c1 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancerProvider.java +++ b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancerProvider.java @@ -26,6 +26,7 @@ import io.grpc.NameResolver.ConfigOrError; import io.grpc.internal.ServiceConfigUtil.PolicySelection; import io.grpc.xds.Bootstrapper.ServerInfo; +import io.grpc.xds.EnvoyServerProtoData.OutlierDetection; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import java.util.List; import java.util.Map; @@ -124,6 +125,8 @@ static final class DiscoveryMechanism { // Hostname for resolving endpoints via DNS. Only valid for LOGICAL_DNS clusters. @Nullable final String dnsHostName; + @Nullable + final OutlierDetection outlierDetection; enum Type { EDS, @@ -132,7 +135,8 @@ enum Type { private DiscoveryMechanism(String cluster, Type type, @Nullable String edsServiceName, @Nullable String dnsHostName, @Nullable ServerInfo lrsServerInfo, - @Nullable Long maxConcurrentRequests, @Nullable UpstreamTlsContext tlsContext) { + @Nullable Long maxConcurrentRequests, @Nullable UpstreamTlsContext tlsContext, + @Nullable OutlierDetection outlierDetection) { this.cluster = checkNotNull(cluster, "cluster"); this.type = checkNotNull(type, "type"); this.edsServiceName = edsServiceName; @@ -140,20 +144,22 @@ private DiscoveryMechanism(String cluster, Type type, @Nullable String edsServic this.lrsServerInfo = lrsServerInfo; this.maxConcurrentRequests = maxConcurrentRequests; this.tlsContext = tlsContext; + this.outlierDetection = outlierDetection; } static DiscoveryMechanism forEds(String cluster, @Nullable String edsServiceName, @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, - @Nullable UpstreamTlsContext tlsContext) { + @Nullable UpstreamTlsContext tlsContext, + OutlierDetection outlierDetection) { return new DiscoveryMechanism(cluster, Type.EDS, edsServiceName, null, lrsServerInfo, - maxConcurrentRequests, tlsContext); + maxConcurrentRequests, tlsContext, outlierDetection); } static DiscoveryMechanism forLogicalDns(String cluster, String dnsHostName, @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, @Nullable UpstreamTlsContext tlsContext) { return new DiscoveryMechanism(cluster, Type.LOGICAL_DNS, null, dnsHostName, - lrsServerInfo, maxConcurrentRequests, tlsContext); + lrsServerInfo, maxConcurrentRequests, tlsContext, null); } @Override diff --git a/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java b/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java index e53439755be..fad5700f5b1 100644 --- a/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java +++ b/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java @@ -19,6 +19,7 @@ import com.google.auto.value.AutoValue; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; +import com.google.protobuf.util.Durations; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; import io.grpc.Internal; import io.grpc.xds.internal.sds.SslContextProviderSupplier; @@ -254,4 +255,148 @@ static Listener create( defaultFilterChain); } } + + /** + * Corresponds to Envoy proto message {@link + * io.envoyproxy.envoy.config.cluster.v3.OutlierDetection}. Only the fields supported by gRPC are + * included. + * + *

Protobuf Duration fields are represented in their string format (e.g. "10s"). + */ + @AutoValue + abstract static class OutlierDetection { + + @Nullable + abstract Long intervalNanos(); + + @Nullable + abstract Long baseEjectionTimeNanos(); + + @Nullable + abstract Long maxEjectionTimeNanos(); + + @Nullable + abstract Integer maxEjectionPercent(); + + @Nullable + abstract SuccessRateEjection successRateEjection(); + + @Nullable + abstract FailurePercentageEjection failurePercentageEjection(); + + static OutlierDetection create( + @Nullable Long intervalNanos, + @Nullable Long baseEjectionTimeNanos, + @Nullable Long maxEjectionTimeNanos, + @Nullable Integer maxEjectionPercentage, + @Nullable SuccessRateEjection successRateEjection, + @Nullable FailurePercentageEjection failurePercentageEjection) { + return new AutoValue_EnvoyServerProtoData_OutlierDetection(intervalNanos, + baseEjectionTimeNanos, maxEjectionTimeNanos, maxEjectionPercentage, successRateEjection, + failurePercentageEjection); + } + + static OutlierDetection fromEnvoyOutlierDetection( + io.envoyproxy.envoy.config.cluster.v3.OutlierDetection envoyOutlierDetection) { + + Long intervalNanos = envoyOutlierDetection.hasInterval() + ? Durations.toNanos(envoyOutlierDetection.getInterval()) : null; + Long baseEjectionTimeNanos = envoyOutlierDetection.hasBaseEjectionTime() + ? Durations.toNanos(envoyOutlierDetection.getBaseEjectionTime()) : null; + Long maxEjectionTimeNanos = envoyOutlierDetection.hasMaxEjectionTime() + ? Durations.toNanos(envoyOutlierDetection.getMaxEjectionTime()) : null; + Integer maxEjectionPercentage = envoyOutlierDetection.hasMaxEjectionPercent() + ? envoyOutlierDetection.getMaxEjectionPercent().getValue() : null; + + SuccessRateEjection successRateEjection; + // If success rate enforcement has been turned completely off, don't configure this ejection. + if (envoyOutlierDetection.hasEnforcingSuccessRate() + && envoyOutlierDetection.getEnforcingSuccessRate().getValue() == 0) { + successRateEjection = null; + } else { + Integer stdevFactor = envoyOutlierDetection.hasSuccessRateStdevFactor() + ? envoyOutlierDetection.getSuccessRateStdevFactor().getValue() : null; + Integer enforcementPercentage = envoyOutlierDetection.hasEnforcingSuccessRate() + ? envoyOutlierDetection.getEnforcingSuccessRate().getValue() : null; + Integer minimumHosts = envoyOutlierDetection.hasSuccessRateMinimumHosts() + ? envoyOutlierDetection.getSuccessRateMinimumHosts().getValue() : null; + Integer requestVolume = envoyOutlierDetection.hasSuccessRateRequestVolume() + ? envoyOutlierDetection.getSuccessRateMinimumHosts().getValue() : null; + + successRateEjection = SuccessRateEjection.create(stdevFactor, enforcementPercentage, + minimumHosts, requestVolume); + } + + FailurePercentageEjection failurePercentageEjection; + if (envoyOutlierDetection.hasEnforcingFailurePercentage() + && envoyOutlierDetection.getEnforcingFailurePercentage().getValue() == 0) { + failurePercentageEjection = null; + } else { + Integer threshold = envoyOutlierDetection.hasFailurePercentageThreshold() + ? envoyOutlierDetection.getFailurePercentageThreshold().getValue() : null; + Integer enforcementPercentage = envoyOutlierDetection.hasEnforcingFailurePercentage() + ? envoyOutlierDetection.getEnforcingFailurePercentage().getValue() : null; + Integer minimumHosts = envoyOutlierDetection.hasFailurePercentageMinimumHosts() + ? envoyOutlierDetection.getFailurePercentageMinimumHosts().getValue() : null; + Integer requestVolume = envoyOutlierDetection.hasFailurePercentageRequestVolume() + ? envoyOutlierDetection.getFailurePercentageRequestVolume().getValue() : null; + + failurePercentageEjection = FailurePercentageEjection.create(threshold, + enforcementPercentage, minimumHosts, requestVolume); + } + + return create(intervalNanos, baseEjectionTimeNanos, maxEjectionTimeNanos, + maxEjectionPercentage, successRateEjection, failurePercentageEjection); + } + } + + @AutoValue + abstract static class SuccessRateEjection { + + @Nullable + abstract Integer stdevFactor(); + + @Nullable + abstract Integer enforcementPercentage(); + + @Nullable + abstract Integer minimumHosts(); + + @Nullable + abstract Integer requestVolume(); + + static SuccessRateEjection create( + @Nullable Integer stdevFactor, + @Nullable Integer enforcementPercentage, + @Nullable Integer minimumHosts, + @Nullable Integer requestVolume) { + return new AutoValue_EnvoyServerProtoData_SuccessRateEjection(stdevFactor, + enforcementPercentage, minimumHosts, requestVolume); + } + } + + @AutoValue + abstract static class FailurePercentageEjection { + + @Nullable + abstract Integer threshold(); + + @Nullable + abstract Integer enforcementPercentage(); + + @Nullable + abstract Integer minimumHosts(); + + @Nullable + abstract Integer requestVolume(); + + static FailurePercentageEjection create( + @Nullable Integer threshold, + @Nullable Integer enforcementPercentage, + @Nullable Integer minimumHosts, + @Nullable Integer requestVolume) { + return new AutoValue_EnvoyServerProtoData_FailurePercentageEjection(threshold, + enforcementPercentage, minimumHosts, requestVolume); + } + } } diff --git a/xds/src/main/java/io/grpc/xds/XdsClient.java b/xds/src/main/java/io/grpc/xds/XdsClient.java index bdffc361193..028ae155ba6 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClient.java +++ b/xds/src/main/java/io/grpc/xds/XdsClient.java @@ -34,6 +34,7 @@ import io.grpc.xds.Endpoints.DropOverload; import io.grpc.xds.Endpoints.LocalityLbEndpoints; import io.grpc.xds.EnvoyServerProtoData.Listener; +import io.grpc.xds.EnvoyServerProtoData.OutlierDetection; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.LoadStatsManager2.ClusterDropStats; import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats; @@ -221,6 +222,10 @@ abstract static class CdsUpdate implements ResourceUpdate { @Nullable abstract ImmutableList prioritizedClusterNames(); + // Outlier detection configuration. + @Nullable + abstract OutlierDetection outlierDetection(); + static Builder forAggregate(String clusterName, List prioritizedClusterNames) { checkNotNull(prioritizedClusterNames, "prioritizedClusterNames"); return new AutoValue_XdsClient_CdsUpdate.Builder() @@ -234,7 +239,8 @@ static Builder forAggregate(String clusterName, List prioritizedClusterN static Builder forEds(String clusterName, @Nullable String edsServiceName, @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, - @Nullable UpstreamTlsContext upstreamTlsContext) { + @Nullable UpstreamTlsContext upstreamTlsContext, + @Nullable OutlierDetection outlierDetection) { return new AutoValue_XdsClient_CdsUpdate.Builder() .clusterName(clusterName) .clusterType(ClusterType.EDS) @@ -244,7 +250,8 @@ static Builder forEds(String clusterName, @Nullable String edsServiceName, .edsServiceName(edsServiceName) .lrsServerInfo(lrsServerInfo) .maxConcurrentRequests(maxConcurrentRequests) - .upstreamTlsContext(upstreamTlsContext); + .upstreamTlsContext(upstreamTlsContext) + .outlierDetection(outlierDetection); } static Builder forLogicalDns(String clusterName, String dnsHostName, @@ -284,8 +291,9 @@ public final String toString() { .add("dnsHostName", dnsHostName()) .add("lrsServerInfo", lrsServerInfo()) .add("maxConcurrentRequests", maxConcurrentRequests()) - // Exclude upstreamTlsContext as its string representation is cumbersome. .add("prioritizedClusterNames", prioritizedClusterNames()) + // Exclude upstreamTlsContext and outlierDetection as their string representations are + // cumbersome. .toString(); } @@ -341,6 +349,8 @@ Builder leastRequestLbPolicy(Integer choiceCount) { // Private, use CdsUpdate.forAggregate() instead. protected abstract Builder prioritizedClusterNames(List prioritizedClusterNames); + protected abstract Builder outlierDetection(OutlierDetection outlierDetection); + abstract CdsUpdate build(); } } diff --git a/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java b/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java index a90df303e04..128d7bd395e 100644 --- a/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java +++ b/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java @@ -50,6 +50,8 @@ import io.grpc.xds.CdsLoadBalancerProvider.CdsConfig; import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig; import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig.DiscoveryMechanism; +import io.grpc.xds.EnvoyServerProtoData.OutlierDetection; +import io.grpc.xds.EnvoyServerProtoData.SuccessRateEjection; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.LeastRequestLoadBalancer.LeastRequestConfig; import io.grpc.xds.RingHashLoadBalancer.RingHashConfig; @@ -85,6 +87,9 @@ public class CdsLoadBalancer2Test { ServerInfo.create("lrs.googleapis.com", InsecureChannelCredentials.create(), true); private final UpstreamTlsContext upstreamTlsContext = CommonTlsContextTestsUtil.buildUpstreamTlsContext("google_cloud_private_spiffe", true); + private final OutlierDetection outlierDetection = OutlierDetection.create( + null, null, null, null, SuccessRateEjection.create(null, null, null, null), null); + private final SynchronizationContext syncContext = new SynchronizationContext( new Thread.UncaughtExceptionHandler() { @@ -154,7 +159,8 @@ public void tearDown() { @Test public void discoverTopLevelEdsCluster() { CdsUpdate update = - CdsUpdate.forEds(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext) + CdsUpdate.forEds(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext, + outlierDetection) .roundRobinLbPolicy().build(); xdsClient.deliverCdsUpdate(CLUSTER, update); assertThat(childBalancers).hasSize(1); @@ -164,7 +170,7 @@ public void discoverTopLevelEdsCluster() { assertThat(childLbConfig.discoveryMechanisms).hasSize(1); DiscoveryMechanism instance = Iterables.getOnlyElement(childLbConfig.discoveryMechanisms); assertDiscoveryMechanism(instance, CLUSTER, DiscoveryMechanism.Type.EDS, EDS_SERVICE_NAME, - null, LRS_SERVER_INFO, 100L, upstreamTlsContext); + null, LRS_SERVER_INFO, 100L, upstreamTlsContext, outlierDetection); assertThat(childLbConfig.lbPolicy.getProvider().getPolicyName()).isEqualTo("round_robin"); } @@ -181,7 +187,7 @@ public void discoverTopLevelLogicalDnsCluster() { assertThat(childLbConfig.discoveryMechanisms).hasSize(1); DiscoveryMechanism instance = Iterables.getOnlyElement(childLbConfig.discoveryMechanisms); assertDiscoveryMechanism(instance, CLUSTER, DiscoveryMechanism.Type.LOGICAL_DNS, null, - DNS_HOST_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext); + DNS_HOST_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext, null); assertThat(childLbConfig.lbPolicy.getProvider().getPolicyName()) .isEqualTo("least_request_experimental"); assertThat(((LeastRequestConfig) childLbConfig.lbPolicy.getConfig()).choiceCount).isEqualTo(3); @@ -201,7 +207,7 @@ public void nonAggregateCluster_resourceNotExist_returnErrorPicker() { @Test public void nonAggregateCluster_resourceUpdate() { CdsUpdate update = - CdsUpdate.forEds(CLUSTER, null, null, 100L, upstreamTlsContext) + CdsUpdate.forEds(CLUSTER, null, null, 100L, upstreamTlsContext, outlierDetection) .roundRobinLbPolicy().build(); xdsClient.deliverCdsUpdate(CLUSTER, update); assertThat(childBalancers).hasSize(1); @@ -209,15 +215,15 @@ public void nonAggregateCluster_resourceUpdate() { ClusterResolverConfig childLbConfig = (ClusterResolverConfig) childBalancer.config; DiscoveryMechanism instance = Iterables.getOnlyElement(childLbConfig.discoveryMechanisms); assertDiscoveryMechanism(instance, CLUSTER, DiscoveryMechanism.Type.EDS, null, null, null, - 100L, upstreamTlsContext); + 100L, upstreamTlsContext, outlierDetection); - update = CdsUpdate.forEds(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, 200L, null) - .roundRobinLbPolicy().build(); + update = CdsUpdate.forEds(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, 200L, null, + outlierDetection).roundRobinLbPolicy().build(); xdsClient.deliverCdsUpdate(CLUSTER, update); childLbConfig = (ClusterResolverConfig) childBalancer.config; instance = Iterables.getOnlyElement(childLbConfig.discoveryMechanisms); assertDiscoveryMechanism(instance, CLUSTER, DiscoveryMechanism.Type.EDS, EDS_SERVICE_NAME, - null, LRS_SERVER_INFO, 200L, null); + null, LRS_SERVER_INFO, 200L, null, outlierDetection); } @Test @@ -231,7 +237,7 @@ public void nonAggregateCluster_resourceRevoked() { ClusterResolverConfig childLbConfig = (ClusterResolverConfig) childBalancer.config; DiscoveryMechanism instance = Iterables.getOnlyElement(childLbConfig.discoveryMechanisms); assertDiscoveryMechanism(instance, CLUSTER, DiscoveryMechanism.Type.LOGICAL_DNS, null, - DNS_HOST_NAME, null, 100L, upstreamTlsContext); + DNS_HOST_NAME, null, 100L, upstreamTlsContext, null); xdsClient.deliverResourceNotExist(CLUSTER); assertThat(childBalancer.shutdown).isTrue(); @@ -265,9 +271,8 @@ public void discoverAggregateCluster() { assertThat(xdsClient.watchers.keySet()).containsExactly( CLUSTER, cluster1, cluster2, cluster3, cluster4); assertThat(childBalancers).isEmpty(); - CdsUpdate update3 = - CdsUpdate.forEds(cluster3, EDS_SERVICE_NAME, LRS_SERVER_INFO, 200L, upstreamTlsContext) - .roundRobinLbPolicy().build(); + CdsUpdate update3 = CdsUpdate.forEds(cluster3, EDS_SERVICE_NAME, LRS_SERVER_INFO, 200L, + upstreamTlsContext, outlierDetection).roundRobinLbPolicy().build(); xdsClient.deliverCdsUpdate(cluster3, update3); assertThat(childBalancers).isEmpty(); CdsUpdate update2 = @@ -276,7 +281,7 @@ public void discoverAggregateCluster() { xdsClient.deliverCdsUpdate(cluster2, update2); assertThat(childBalancers).isEmpty(); CdsUpdate update4 = - CdsUpdate.forEds(cluster4, null, LRS_SERVER_INFO, 300L, null) + CdsUpdate.forEds(cluster4, null, LRS_SERVER_INFO, 300L, null, outlierDetection) .roundRobinLbPolicy().build(); xdsClient.deliverCdsUpdate(cluster4, update4); assertThat(childBalancers).hasSize(1); // all non-aggregate clusters discovered @@ -286,12 +291,12 @@ public void discoverAggregateCluster() { assertThat(childLbConfig.discoveryMechanisms).hasSize(3); // Clusters on higher level has higher priority: [cluster2, cluster3, cluster4] assertDiscoveryMechanism(childLbConfig.discoveryMechanisms.get(0), cluster2, - DiscoveryMechanism.Type.LOGICAL_DNS, null, DNS_HOST_NAME, null, 100L, null); + DiscoveryMechanism.Type.LOGICAL_DNS, null, DNS_HOST_NAME, null, 100L, null, null); assertDiscoveryMechanism(childLbConfig.discoveryMechanisms.get(1), cluster3, DiscoveryMechanism.Type.EDS, EDS_SERVICE_NAME, null, LRS_SERVER_INFO, 200L, - upstreamTlsContext); + upstreamTlsContext, outlierDetection); assertDiscoveryMechanism(childLbConfig.discoveryMechanisms.get(2), cluster4, - DiscoveryMechanism.Type.EDS, null, null, LRS_SERVER_INFO, 300L, null); + DiscoveryMechanism.Type.EDS, null, null, LRS_SERVER_INFO, 300L, null, outlierDetection); assertThat(childLbConfig.lbPolicy.getProvider().getPolicyName()) .isEqualTo("ring_hash_experimental"); // dominated by top-level cluster's config assertThat(((RingHashConfig) childLbConfig.lbPolicy.getConfig()).minRingSize).isEqualTo(100L); @@ -326,9 +331,8 @@ public void aggregateCluster_descendantClustersRevoked() { .roundRobinLbPolicy().build(); xdsClient.deliverCdsUpdate(CLUSTER, update); assertThat(xdsClient.watchers.keySet()).containsExactly(CLUSTER, cluster1, cluster2); - CdsUpdate update1 = - CdsUpdate.forEds(cluster1, EDS_SERVICE_NAME, LRS_SERVER_INFO, 200L, upstreamTlsContext) - .roundRobinLbPolicy().build(); + CdsUpdate update1 = CdsUpdate.forEds(cluster1, EDS_SERVICE_NAME, LRS_SERVER_INFO, 200L, + upstreamTlsContext, outlierDetection).roundRobinLbPolicy().build(); xdsClient.deliverCdsUpdate(cluster1, update1); CdsUpdate update2 = CdsUpdate.forLogicalDns(cluster2, DNS_HOST_NAME, LRS_SERVER_INFO, 100L, null) @@ -339,9 +343,10 @@ public void aggregateCluster_descendantClustersRevoked() { assertThat(childLbConfig.discoveryMechanisms).hasSize(2); assertDiscoveryMechanism(childLbConfig.discoveryMechanisms.get(0), cluster1, DiscoveryMechanism.Type.EDS, EDS_SERVICE_NAME, null, LRS_SERVER_INFO, 200L, - upstreamTlsContext); + upstreamTlsContext, outlierDetection); assertDiscoveryMechanism(childLbConfig.discoveryMechanisms.get(1), cluster2, - DiscoveryMechanism.Type.LOGICAL_DNS, null, DNS_HOST_NAME, LRS_SERVER_INFO, 100L, null); + DiscoveryMechanism.Type.LOGICAL_DNS, null, DNS_HOST_NAME, LRS_SERVER_INFO, 100L, null, + null); // Revoke cluster1, should still be able to proceed with cluster2. xdsClient.deliverResourceNotExist(cluster1); @@ -349,7 +354,8 @@ public void aggregateCluster_descendantClustersRevoked() { childLbConfig = (ClusterResolverConfig) childBalancer.config; assertThat(childLbConfig.discoveryMechanisms).hasSize(1); assertDiscoveryMechanism(Iterables.getOnlyElement(childLbConfig.discoveryMechanisms), cluster2, - DiscoveryMechanism.Type.LOGICAL_DNS, null, DNS_HOST_NAME, LRS_SERVER_INFO, 100L, null); + DiscoveryMechanism.Type.LOGICAL_DNS, null, DNS_HOST_NAME, LRS_SERVER_INFO, 100L, null, + null); verify(helper, never()).updateBalancingState( eq(ConnectivityState.TRANSIENT_FAILURE), any(SubchannelPicker.class)); @@ -374,9 +380,8 @@ public void aggregateCluster_rootClusterRevoked() { .roundRobinLbPolicy().build(); xdsClient.deliverCdsUpdate(CLUSTER, update); assertThat(xdsClient.watchers.keySet()).containsExactly(CLUSTER, cluster1, cluster2); - CdsUpdate update1 = - CdsUpdate.forEds(cluster1, EDS_SERVICE_NAME, LRS_SERVER_INFO, 200L, upstreamTlsContext) - .roundRobinLbPolicy().build(); + CdsUpdate update1 = CdsUpdate.forEds(cluster1, EDS_SERVICE_NAME, LRS_SERVER_INFO, 200L, + upstreamTlsContext, outlierDetection).roundRobinLbPolicy().build(); xdsClient.deliverCdsUpdate(cluster1, update1); CdsUpdate update2 = CdsUpdate.forLogicalDns(cluster2, DNS_HOST_NAME, LRS_SERVER_INFO, 100L, null) @@ -387,9 +392,10 @@ public void aggregateCluster_rootClusterRevoked() { assertThat(childLbConfig.discoveryMechanisms).hasSize(2); assertDiscoveryMechanism(childLbConfig.discoveryMechanisms.get(0), cluster1, DiscoveryMechanism.Type.EDS, EDS_SERVICE_NAME, null, LRS_SERVER_INFO, 200L, - upstreamTlsContext); + upstreamTlsContext, outlierDetection); assertDiscoveryMechanism(childLbConfig.discoveryMechanisms.get(1), cluster2, - DiscoveryMechanism.Type.LOGICAL_DNS, null, DNS_HOST_NAME, LRS_SERVER_INFO, 100L, null); + DiscoveryMechanism.Type.LOGICAL_DNS, null, DNS_HOST_NAME, LRS_SERVER_INFO, 100L, null, + null); xdsClient.deliverResourceNotExist(CLUSTER); assertThat(xdsClient.watchers.keySet()) @@ -428,16 +434,15 @@ public void aggregateCluster_intermediateClusterChanges() { .roundRobinLbPolicy().build(); xdsClient.deliverCdsUpdate(cluster2, update2); assertThat(xdsClient.watchers.keySet()).containsExactly(CLUSTER, cluster2, cluster3); - CdsUpdate update3 = - CdsUpdate.forEds(cluster3, EDS_SERVICE_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext) - .roundRobinLbPolicy().build(); + CdsUpdate update3 = CdsUpdate.forEds(cluster3, EDS_SERVICE_NAME, LRS_SERVER_INFO, 100L, + upstreamTlsContext, outlierDetection).roundRobinLbPolicy().build(); xdsClient.deliverCdsUpdate(cluster3, update3); FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers); ClusterResolverConfig childLbConfig = (ClusterResolverConfig) childBalancer.config; assertThat(childLbConfig.discoveryMechanisms).hasSize(1); DiscoveryMechanism instance = Iterables.getOnlyElement(childLbConfig.discoveryMechanisms); assertDiscoveryMechanism(instance, cluster3, DiscoveryMechanism.Type.EDS, EDS_SERVICE_NAME, - null, LRS_SERVER_INFO, 100L, upstreamTlsContext); + null, LRS_SERVER_INFO, 100L, upstreamTlsContext, outlierDetection); // cluster2 revoked xdsClient.deliverResourceNotExist(cluster2); @@ -506,9 +511,8 @@ public void handleNameResolutionErrorFromUpstream_beforeChildLbCreated_returnErr @Test public void handleNameResolutionErrorFromUpstream_afterChildLbCreated_fallThrough() { - CdsUpdate update = - CdsUpdate.forEds(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext) - .roundRobinLbPolicy().build(); + CdsUpdate update = CdsUpdate.forEds(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, 100L, + upstreamTlsContext, outlierDetection).roundRobinLbPolicy().build(); xdsClient.deliverCdsUpdate(CLUSTER, update); FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers); assertThat(childBalancer.shutdown).isFalse(); @@ -523,7 +527,8 @@ public void handleNameResolutionErrorFromUpstream_afterChildLbCreated_fallThroug public void unknownLbProvider() { try { xdsClient.deliverCdsUpdate(CLUSTER, - CdsUpdate.forEds(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext) + CdsUpdate.forEds(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext, + outlierDetection) .lbPolicyConfig(ImmutableMap.of("unknown", ImmutableMap.of("foo", "bar"))).build()); } catch (Exception e) { assertThat(e).hasCauseThat().hasMessageThat().contains("No provider available"); @@ -536,8 +541,8 @@ public void unknownLbProvider() { public void invalidLbConfig() { try { xdsClient.deliverCdsUpdate(CLUSTER, - CdsUpdate.forEds(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext) - .lbPolicyConfig( + CdsUpdate.forEds(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext, + outlierDetection).lbPolicyConfig( ImmutableMap.of("ring_hash_experimental", ImmutableMap.of("minRingSize", "-1"))) .build()); } catch (Exception e) { @@ -561,7 +566,7 @@ private static void assertPicker(SubchannelPicker picker, Status expectedStatus, private static void assertDiscoveryMechanism(DiscoveryMechanism instance, String name, DiscoveryMechanism.Type type, @Nullable String edsServiceName, @Nullable String dnsHostName, @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, - @Nullable UpstreamTlsContext tlsContext) { + @Nullable UpstreamTlsContext tlsContext, @Nullable OutlierDetection outlierDetection) { assertThat(instance.cluster).isEqualTo(name); assertThat(instance.type).isEqualTo(type); assertThat(instance.edsServiceName).isEqualTo(edsServiceName); @@ -569,6 +574,7 @@ private static void assertDiscoveryMechanism(DiscoveryMechanism instance, String assertThat(instance.lrsServerInfo).isEqualTo(lrsServerInfo); assertThat(instance.maxConcurrentRequests).isEqualTo(maxConcurrentRequests); assertThat(instance.tlsContext).isEqualTo(tlsContext); + assertThat(instance.outlierDetection).isEqualTo(outlierDetection); } private final class FakeLoadBalancerProvider extends LoadBalancerProvider { diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java index 53b4027768f..97f892a0c27 100644 --- a/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java +++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java @@ -37,8 +37,12 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.protobuf.Any; +import com.google.protobuf.Duration; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; +import com.google.protobuf.UInt32Value; +import com.google.protobuf.util.Durations; +import io.envoyproxy.envoy.config.cluster.v3.OutlierDetection; import io.envoyproxy.envoy.config.route.v3.FilterConfig; import io.envoyproxy.envoy.extensions.filters.http.router.v3.Router; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateProviderPluginInstance; @@ -66,12 +70,15 @@ import io.grpc.xds.Bootstrapper.AuthorityInfo; import io.grpc.xds.Bootstrapper.CertificateProviderInfo; import io.grpc.xds.Bootstrapper.ServerInfo; +import io.grpc.xds.ClientXdsClient.ResourceInvalidException; import io.grpc.xds.ClientXdsClient.XdsChannelFactory; import io.grpc.xds.Endpoints.DropOverload; import io.grpc.xds.Endpoints.LbEndpoint; import io.grpc.xds.Endpoints.LocalityLbEndpoints; import io.grpc.xds.EnvoyProtoData.Node; +import io.grpc.xds.EnvoyServerProtoData.FailurePercentageEjection; import io.grpc.xds.EnvoyServerProtoData.FilterChain; +import io.grpc.xds.EnvoyServerProtoData.SuccessRateEjection; import io.grpc.xds.FaultConfig.FractionalPercent.DenominatorType; import io.grpc.xds.LoadStatsManager2.ClusterDropStats; import io.grpc.xds.XdsClient.CdsResourceWatcher; @@ -211,7 +218,7 @@ public long currentTimeNanos() { // CDS test resources. private final Any testClusterRoundRobin = Any.pack(mf.buildEdsCluster(CDS_RESOURCE, null, "round_robin", null, - null, false, null, "envoy.transport_sockets.tls", null + null, false, null, "envoy.transport_sockets.tls", null, null )); // EDS test resources. @@ -1021,7 +1028,7 @@ public void cdsResourceUpdated_withXdstpResourceName_withWrongType() { "xdstp://authority.xds.com/envoy.config.listener.v3.Listener/cluster1"; Any testClusterConfig = Any.pack(mf.buildEdsCluster( cdsResourceNameWithWrongType, null, "round_robin", null, null, false, null, - "envoy.transport_sockets.tls", null)); + "envoy.transport_sockets.tls", null, null)); call.sendResponse(CDS, testClusterConfig, VERSION_1, "0000"); call.verifyRequestNack( CDS, cdsResourceName, "", "0000", NODE, @@ -1615,9 +1622,9 @@ public void cdsResourceNotFound() { List clusters = ImmutableList.of( Any.pack(mf.buildEdsCluster("cluster-bar.googleapis.com", null, "round_robin", null, - null, false, null, "envoy.transport_sockets.tls", null)), + null, false, null, "envoy.transport_sockets.tls", null, null)), Any.pack(mf.buildEdsCluster("cluster-baz.googleapis.com", null, "round_robin", null, - null, false, null, "envoy.transport_sockets.tls", null))); + null, false, null, "envoy.transport_sockets.tls", null, null))); call.sendResponse(CDS, clusters, VERSION_1, "0000"); // Client sent an ACK CDS request. @@ -1692,13 +1699,13 @@ public void cdsResponseErrorHandling_subscribedResourceInvalid() { // CDS -> {A, B, C}, version 1 ImmutableMap resourcesV1 = ImmutableMap.of( "A", Any.pack(mf.buildEdsCluster("A", "A.1", "round_robin", null, null, false, null, - "envoy.transport_sockets.tls", null + "envoy.transport_sockets.tls", null, null )), "B", Any.pack(mf.buildEdsCluster("B", "B.1", "round_robin", null, null, false, null, - "envoy.transport_sockets.tls", null + "envoy.transport_sockets.tls", null, null )), "C", Any.pack(mf.buildEdsCluster("C", "C.1", "round_robin", null, null, false, null, - "envoy.transport_sockets.tls", null + "envoy.transport_sockets.tls", null, null ))); call.sendResponse(CDS, resourcesV1.values().asList(), VERSION_1, "0000"); // {A, B, C} -> ACK, version 1 @@ -1711,7 +1718,7 @@ public void cdsResponseErrorHandling_subscribedResourceInvalid() { // Failed to parse endpoint B ImmutableMap resourcesV2 = ImmutableMap.of( "A", Any.pack(mf.buildEdsCluster("A", "A.2", "round_robin", null, null, false, null, - "envoy.transport_sockets.tls", null + "envoy.transport_sockets.tls", null, null )), "B", Any.pack(mf.buildClusterInvalid("B"))); call.sendResponse(CDS, resourcesV2.values().asList(), VERSION_2, "0001"); @@ -1733,10 +1740,10 @@ public void cdsResponseErrorHandling_subscribedResourceInvalid() { // CDS -> {B, C} version 3 ImmutableMap resourcesV3 = ImmutableMap.of( "B", Any.pack(mf.buildEdsCluster("B", "B.3", "round_robin", null, null, false, null, - "envoy.transport_sockets.tls", null + "envoy.transport_sockets.tls", null, null )), "C", Any.pack(mf.buildEdsCluster("C", "C.3", "round_robin", null, null, false, null, - "envoy.transport_sockets.tls", null + "envoy.transport_sockets.tls", null, null ))); call.sendResponse(CDS, resourcesV3.values().asList(), VERSION_3, "0002"); // {A} -> does not exit @@ -1774,13 +1781,13 @@ public void cdsResponseErrorHandling_subscribedResourceInvalid_withEdsSubscripti // CDS -> {A, B, C}, version 1 ImmutableMap resourcesV1 = ImmutableMap.of( "A", Any.pack(mf.buildEdsCluster("A", "A.1", "round_robin", null, null, false, null, - "envoy.transport_sockets.tls", null + "envoy.transport_sockets.tls", null, null )), "B", Any.pack(mf.buildEdsCluster("B", "B.1", "round_robin", null, null, false, null, - "envoy.transport_sockets.tls", null + "envoy.transport_sockets.tls", null, null )), "C", Any.pack(mf.buildEdsCluster("C", "C.1", "round_robin", null, null, false, null, - "envoy.transport_sockets.tls", null + "envoy.transport_sockets.tls", null, null ))); call.sendResponse(CDS, resourcesV1.values().asList(), VERSION_1, "0000"); // {A, B, C} -> ACK, version 1 @@ -1806,7 +1813,7 @@ public void cdsResponseErrorHandling_subscribedResourceInvalid_withEdsSubscripti // Failed to parse endpoint B ImmutableMap resourcesV2 = ImmutableMap.of( "A", Any.pack(mf.buildEdsCluster("A", "A.2", "round_robin", null, null, false, null, - "envoy.transport_sockets.tls", null + "envoy.transport_sockets.tls", null, null )), "B", Any.pack(mf.buildClusterInvalid("B"))); call.sendResponse(CDS, resourcesV2.values().asList(), VERSION_2, "0001"); @@ -1876,7 +1883,7 @@ public void cdsResourceFound_leastRequestLbPolicy() { Message leastRequestConfig = mf.buildLeastRequestLbConfig(3); Any clusterRingHash = Any.pack( mf.buildEdsCluster(CDS_RESOURCE, null, "least_request_experimental", null, - leastRequestConfig, false, null, "envoy.transport_sockets.tls", null + leastRequestConfig, false, null, "envoy.transport_sockets.tls", null, null )); call.sendResponse(ResourceType.CDS, clusterRingHash, VERSION_1, "0000"); @@ -1907,7 +1914,7 @@ public void cdsResourceFound_ringHashLbPolicy() { Message ringHashConfig = mf.buildRingHashLbConfig("xx_hash", 10L, 100L); Any clusterRingHash = Any.pack( mf.buildEdsCluster(CDS_RESOURCE, null, "ring_hash_experimental", ringHashConfig, null, - false, null, "envoy.transport_sockets.tls", null + false, null, "envoy.transport_sockets.tls", null, null )); call.sendResponse(ResourceType.CDS, clusterRingHash, VERSION_1, "0000"); @@ -1962,7 +1969,7 @@ public void cdsResponseWithCircuitBreakers() { DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); Any clusterCircuitBreakers = Any.pack( mf.buildEdsCluster(CDS_RESOURCE, null, "round_robin", null, null, false, null, - "envoy.transport_sockets.tls", mf.buildCircuitBreakers(50, 200))); + "envoy.transport_sockets.tls", mf.buildCircuitBreakers(50, 200), null)); call.sendResponse(CDS, clusterCircuitBreakers, VERSION_1, "0000"); // Client sent an ACK CDS request. @@ -1999,13 +2006,13 @@ public void cdsResponseWithUpstreamTlsContext() { Any.pack(mf.buildEdsCluster(CDS_RESOURCE, "eds-cluster-foo.googleapis.com", "round_robin", null, null, true, mf.buildUpstreamTlsContext("cert-instance-name", "cert1"), - "envoy.transport_sockets.tls", null)); + "envoy.transport_sockets.tls", null, null)); List clusters = ImmutableList.of( Any.pack(mf.buildLogicalDnsCluster("cluster-bar.googleapis.com", "dns-service-bar.googleapis.com", 443, "round_robin", null, null,false, null, null)), clusterEds, Any.pack(mf.buildEdsCluster("cluster-baz.googleapis.com", null, "round_robin", null, null, - false, null, "envoy.transport_sockets.tls", null))); + false, null, "envoy.transport_sockets.tls", null, null))); call.sendResponse(CDS, clusters, VERSION_1, "0000"); // Client sent an ACK CDS request. @@ -2035,13 +2042,13 @@ public void cdsResponseWithNewUpstreamTlsContext() { Any.pack(mf.buildEdsCluster(CDS_RESOURCE, "eds-cluster-foo.googleapis.com", "round_robin", null, null,true, mf.buildNewUpstreamTlsContext("cert-instance-name", "cert1"), - "envoy.transport_sockets.tls", null)); + "envoy.transport_sockets.tls", null, null)); List clusters = ImmutableList.of( Any.pack(mf.buildLogicalDnsCluster("cluster-bar.googleapis.com", "dns-service-bar.googleapis.com", 443, "round_robin", null, null, false, null, null)), clusterEds, Any.pack(mf.buildEdsCluster("cluster-baz.googleapis.com", null, "round_robin", null, null, - false, null, "envoy.transport_sockets.tls", null))); + false, null, "envoy.transport_sockets.tls", null, null))); call.sendResponse(CDS, clusters, VERSION_1, "0000"); // Client sent an ACK CDS request. @@ -2069,7 +2076,7 @@ public void cdsResponseErrorHandling_badUpstreamTlsContext() { List clusters = ImmutableList.of(Any .pack(mf.buildEdsCluster(CDS_RESOURCE, "eds-cluster-foo.googleapis.com", "round_robin", null, null, true, - mf.buildUpstreamTlsContext(null, null), "envoy.transport_sockets.tls", null))); + mf.buildUpstreamTlsContext(null, null), "envoy.transport_sockets.tls", null, null))); call.sendResponse(CDS, clusters, VERSION_1, "0000"); // The response NACKed with errors indicating indices of the failed resources. @@ -2082,6 +2089,224 @@ public void cdsResponseErrorHandling_badUpstreamTlsContext() { verifyStatusWithNodeId(errorCaptor.getValue(), Code.UNAVAILABLE, errorMsg); } + /** + * CDS response containing OutlierDetection for a cluster. + */ + @Test + @SuppressWarnings("deprecation") + public void cdsResponseWithOutlierDetection() { + Assume.assumeTrue(useProtocolV3()); + ClientXdsClient.enableOutlierDetection = true; + + DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + + OutlierDetection outlierDetectionXds = OutlierDetection.newBuilder() + .setInterval(Durations.fromNanos(100)) + .setBaseEjectionTime(Durations.fromNanos(100)) + .setMaxEjectionTime(Durations.fromNanos(100)) + .setMaxEjectionPercent(UInt32Value.of(100)) + .setSuccessRateStdevFactor(UInt32Value.of(100)) + .setEnforcingSuccessRate(UInt32Value.of(100)) + .setSuccessRateMinimumHosts(UInt32Value.of(100)) + .setSuccessRateRequestVolume(UInt32Value.of(100)) + .setFailurePercentageThreshold(UInt32Value.of(100)) + .setEnforcingFailurePercentage(UInt32Value.of(100)) + .setFailurePercentageMinimumHosts(UInt32Value.of(100)) + .setFailurePercentageRequestVolume(UInt32Value.of(100)).build(); + + // Management server sends back CDS response with UpstreamTlsContext. + Any clusterEds = + Any.pack(mf.buildEdsCluster(CDS_RESOURCE, "eds-cluster-foo.googleapis.com", "round_robin", + null, null, true, + mf.buildUpstreamTlsContext("cert-instance-name", "cert1"), + "envoy.transport_sockets.tls", null, outlierDetectionXds)); + List clusters = ImmutableList.of( + Any.pack(mf.buildLogicalDnsCluster("cluster-bar.googleapis.com", + "dns-service-bar.googleapis.com", 443, "round_robin", null, null,false, null, null)), + clusterEds, + Any.pack(mf.buildEdsCluster("cluster-baz.googleapis.com", null, "round_robin", null, null, + false, null, "envoy.transport_sockets.tls", null, outlierDetectionXds))); + call.sendResponse(CDS, clusters, VERSION_1, "0000"); + + // Client sent an ACK CDS request. + call.verifyRequest(CDS, CDS_RESOURCE, VERSION_1, "0000", NODE); + verify(cdsResourceWatcher, times(1)).onChanged(cdsUpdateCaptor.capture()); + CdsUpdate cdsUpdate = cdsUpdateCaptor.getValue(); + + // The outlier detection config in CdsUpdate should match what we get from xDS. + EnvoyServerProtoData.OutlierDetection outlierDetection = cdsUpdate.outlierDetection(); + assertThat(outlierDetection).isNotNull(); + assertThat(outlierDetection.intervalNanos()).isEqualTo(100); + assertThat(outlierDetection.baseEjectionTimeNanos()).isEqualTo(100); + assertThat(outlierDetection.maxEjectionTimeNanos()).isEqualTo(100); + assertThat(outlierDetection.maxEjectionPercent()).isEqualTo(100); + + SuccessRateEjection successRateEjection = outlierDetection.successRateEjection(); + assertThat(successRateEjection).isNotNull(); + assertThat(successRateEjection.stdevFactor()).isEqualTo(100); + assertThat(successRateEjection.enforcementPercentage()).isEqualTo(100); + assertThat(successRateEjection.minimumHosts()).isEqualTo(100); + assertThat(successRateEjection.requestVolume()).isEqualTo(100); + + FailurePercentageEjection failurePercentageEjection + = outlierDetection.failurePercentageEjection(); + assertThat(failurePercentageEjection).isNotNull(); + assertThat(failurePercentageEjection.threshold()).isEqualTo(100); + assertThat(failurePercentageEjection.enforcementPercentage()).isEqualTo(100); + assertThat(failurePercentageEjection.minimumHosts()).isEqualTo(100); + assertThat(failurePercentageEjection.requestVolume()).isEqualTo(100); + + verifyResourceMetadataAcked(CDS, CDS_RESOURCE, clusterEds, VERSION_1, TIME_INCREMENT); + verifySubscribedResourcesMetadataSizes(0, 1, 0, 0); + } + + /** + * CDS response containing OutlierDetection for a cluster, but support has not been enabled. + */ + @Test + @SuppressWarnings("deprecation") + public void cdsResponseWithOutlierDetection_supportDisabled() { + Assume.assumeTrue(useProtocolV3()); + ClientXdsClient.enableOutlierDetection = false; + + DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + + OutlierDetection outlierDetectionXds = OutlierDetection.newBuilder() + .setInterval(Durations.fromNanos(100)).build(); + + // Management server sends back CDS response with UpstreamTlsContext. + Any clusterEds = + Any.pack(mf.buildEdsCluster(CDS_RESOURCE, "eds-cluster-foo.googleapis.com", "round_robin", + null, null, true, + mf.buildUpstreamTlsContext("cert-instance-name", "cert1"), + "envoy.transport_sockets.tls", null, outlierDetectionXds)); + List clusters = ImmutableList.of( + Any.pack(mf.buildLogicalDnsCluster("cluster-bar.googleapis.com", + "dns-service-bar.googleapis.com", 443, "round_robin", null, null,false, null, null)), + clusterEds, + Any.pack(mf.buildEdsCluster("cluster-baz.googleapis.com", null, "round_robin", null, null, + false, null, "envoy.transport_sockets.tls", null, outlierDetectionXds))); + call.sendResponse(CDS, clusters, VERSION_1, "0000"); + + // Client sent an ACK CDS request. + call.verifyRequest(CDS, CDS_RESOURCE, VERSION_1, "0000", NODE); + verify(cdsResourceWatcher, times(1)).onChanged(cdsUpdateCaptor.capture()); + CdsUpdate cdsUpdate = cdsUpdateCaptor.getValue(); + + assertThat(cdsUpdate.outlierDetection()).isNull(); + + verifyResourceMetadataAcked(CDS, CDS_RESOURCE, clusterEds, VERSION_1, TIME_INCREMENT); + verifySubscribedResourcesMetadataSizes(0, 1, 0, 0); + } + + /** + * CDS response containing OutlierDetection for a cluster. + */ + @Test + @SuppressWarnings("deprecation") + public void cdsResponseWithInvalidOutlierDetectionNacks() { + Assume.assumeTrue(useProtocolV3()); + ClientXdsClient.enableOutlierDetection = true; + + DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + + OutlierDetection outlierDetectionXds = OutlierDetection.newBuilder() + .setMaxEjectionPercent(UInt32Value.of(101)).build(); + + // Management server sends back CDS response with UpstreamTlsContext. + Any clusterEds = + Any.pack(mf.buildEdsCluster(CDS_RESOURCE, "eds-cluster-foo.googleapis.com", "round_robin", + null, null, true, + mf.buildUpstreamTlsContext("cert-instance-name", "cert1"), + "envoy.transport_sockets.tls", null, outlierDetectionXds)); + List clusters = ImmutableList.of( + Any.pack(mf.buildLogicalDnsCluster("cluster-bar.googleapis.com", + "dns-service-bar.googleapis.com", 443, "round_robin", null, null,false, null, null)), + clusterEds, + Any.pack(mf.buildEdsCluster("cluster-baz.googleapis.com", null, "round_robin", null, null, + false, null, "envoy.transport_sockets.tls", null, outlierDetectionXds))); + call.sendResponse(CDS, clusters, VERSION_1, "0000"); + + String errorMsg = "CDS response Cluster 'cluster.googleapis.com' validation error: " + + "Cluster cluster.googleapis.com: malformed outlier_detection: " + + "io.grpc.xds.ClientXdsClient$ResourceInvalidException: outlier_detection " + + "max_ejection_percent is > 100"; + call.verifyRequestNack(CDS, CDS_RESOURCE, "", "0000", NODE, ImmutableList.of(errorMsg)); + verify(cdsResourceWatcher).onError(errorCaptor.capture()); + verifyStatusWithNodeId(errorCaptor.getValue(), Code.UNAVAILABLE, errorMsg); + } + + @Test(expected = ResourceInvalidException.class) + public void validateOutlierDetection_invalidInterval() throws ResourceInvalidException { + ClientXdsClient.validateOutlierDetection( + OutlierDetection.newBuilder().setInterval(Duration.newBuilder().setSeconds(Long.MAX_VALUE)) + .build()); + } + + @Test(expected = ResourceInvalidException.class) + public void validateOutlierDetection_negativeInterval() throws ResourceInvalidException { + ClientXdsClient.validateOutlierDetection( + OutlierDetection.newBuilder().setInterval(Duration.newBuilder().setSeconds(-1)) + .build()); + } + + @Test(expected = ResourceInvalidException.class) + public void validateOutlierDetection_invalidBaseEjectionTime() throws ResourceInvalidException { + ClientXdsClient.validateOutlierDetection( + OutlierDetection.newBuilder() + .setBaseEjectionTime(Duration.newBuilder().setSeconds(Long.MAX_VALUE)) + .build()); + } + + @Test(expected = ResourceInvalidException.class) + public void validateOutlierDetection_negativeBaseEjectionTime() throws ResourceInvalidException { + ClientXdsClient.validateOutlierDetection( + OutlierDetection.newBuilder().setBaseEjectionTime(Duration.newBuilder().setSeconds(-1)) + .build()); + } + + @Test(expected = ResourceInvalidException.class) + public void validateOutlierDetection_invalidMaxEjectionTime() throws ResourceInvalidException { + ClientXdsClient.validateOutlierDetection( + OutlierDetection.newBuilder() + .setMaxEjectionTime(Duration.newBuilder().setSeconds(Long.MAX_VALUE)) + .build()); + } + + @Test(expected = ResourceInvalidException.class) + public void validateOutlierDetection_negativeMaxEjectionTime() throws ResourceInvalidException { + ClientXdsClient.validateOutlierDetection( + OutlierDetection.newBuilder().setMaxEjectionTime(Duration.newBuilder().setSeconds(-1)) + .build()); + } + + @Test(expected = ResourceInvalidException.class) + public void validateOutlierDetection_maxEjectionPercentTooHigh() throws ResourceInvalidException { + ClientXdsClient.validateOutlierDetection( + OutlierDetection.newBuilder().setMaxEjectionPercent(UInt32Value.of(101)).build()); + } + + @Test(expected = ResourceInvalidException.class) + public void validateOutlierDetection_enforcingSuccessRateTooHigh() + throws ResourceInvalidException { + ClientXdsClient.validateOutlierDetection( + OutlierDetection.newBuilder().setEnforcingSuccessRate(UInt32Value.of(101)).build()); + } + + @Test(expected = ResourceInvalidException.class) + public void validateOutlierDetection_failurePercentageThresholdTooHigh() + throws ResourceInvalidException { + ClientXdsClient.validateOutlierDetection( + OutlierDetection.newBuilder().setFailurePercentageThreshold(UInt32Value.of(101)).build()); + } + + @Test(expected = ResourceInvalidException.class) + public void validateOutlierDetection_enforcingFailurePercentageTooHigh() + throws ResourceInvalidException { + ClientXdsClient.validateOutlierDetection( + OutlierDetection.newBuilder().setEnforcingFailurePercentage(UInt32Value.of(101)).build()); + } + /** * CDS response containing UpstreamTlsContext with bad transportSocketName for a cluster. */ @@ -2094,7 +2319,8 @@ public void cdsResponseErrorHandling_badTransportSocketName() { List clusters = ImmutableList.of(Any .pack(mf.buildEdsCluster(CDS_RESOURCE, "eds-cluster-foo.googleapis.com", "round_robin", null, null, true, - mf.buildUpstreamTlsContext("secret1", "cert1"), "envoy.transport_sockets.bad", null))); + mf.buildUpstreamTlsContext("secret1", "cert1"), "envoy.transport_sockets.bad", null, + null))); call.sendResponse(CDS, clusters, VERSION_1, "0000"); // The response NACKed with errors indicating indices of the failed resources. @@ -2169,7 +2395,7 @@ public void cdsResourceUpdated() { String edsService = "eds-service-bar.googleapis.com"; Any clusterEds = Any.pack( mf.buildEdsCluster(CDS_RESOURCE, edsService, "round_robin", null, null, true, null, - "envoy.transport_sockets.tls", null + "envoy.transport_sockets.tls", null, null )); call.sendResponse(CDS, clusterEds, VERSION_2, "0001"); call.verifyRequest(CDS, CDS_RESOURCE, VERSION_2, "0001", NODE); @@ -2199,17 +2425,17 @@ public void cdsResourceUpdatedWithDuplicate() { String transportSocketName = "envoy.transport_sockets.tls"; Any roundRobinConfig = Any.pack( mf.buildEdsCluster(CDS_RESOURCE, edsService, "round_robin", null, null, true, null, - transportSocketName, null + transportSocketName, null, null )); Any ringHashConfig = Any.pack( mf.buildEdsCluster(CDS_RESOURCE, edsService, "ring_hash_experimental", mf.buildRingHashLbConfig("xx_hash", 1, 2), null, true, null, - transportSocketName, null + transportSocketName, null, null )); Any leastRequestConfig = Any.pack( mf.buildEdsCluster(CDS_RESOURCE, edsService, "least_request_experimental", null, mf.buildLeastRequestLbConfig(2), true, null, - transportSocketName, null + transportSocketName, null, null )); // Configure with round robin, the update should be sent to the watcher. @@ -2332,7 +2558,7 @@ public void multipleCdsWatchers() { Any.pack(mf.buildLogicalDnsCluster(CDS_RESOURCE, dnsHostAddr, dnsHostPort, "round_robin", null, null, false, null, null)), Any.pack(mf.buildEdsCluster(cdsResourceTwo, edsService, "round_robin", null, null, true, - null, "envoy.transport_sockets.tls", null))); + null, "envoy.transport_sockets.tls", null, null))); call.sendResponse(CDS, clusters, VERSION_1, "0000"); verify(cdsResourceWatcher).onChanged(cdsUpdateCaptor.capture()); CdsUpdate cdsUpdate = cdsUpdateCaptor.getValue(); @@ -2643,10 +2869,10 @@ public void edsResourceDeletedByCds() { DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); List clusters = ImmutableList.of( Any.pack(mf.buildEdsCluster(resource, null, "round_robin", null, null, true, null, - "envoy.transport_sockets.tls", null + "envoy.transport_sockets.tls", null, null )), Any.pack(mf.buildEdsCluster(CDS_RESOURCE, EDS_RESOURCE, "round_robin", null, null, false, - null, "envoy.transport_sockets.tls", null))); + null, "envoy.transport_sockets.tls", null, null))); call.sendResponse(CDS, clusters, VERSION_1, "0000"); verify(cdsWatcher).onChanged(cdsUpdateCaptor.capture()); CdsUpdate cdsUpdate = cdsUpdateCaptor.getValue(); @@ -2692,9 +2918,9 @@ public void edsResourceDeletedByCds() { clusters = ImmutableList.of( Any.pack(mf.buildEdsCluster(resource, null, "round_robin", null, null, true, null, - "envoy.transport_sockets.tls", null)), // no change + "envoy.transport_sockets.tls", null, null)), // no change Any.pack(mf.buildEdsCluster(CDS_RESOURCE, null, "round_robin", null, null, false, null, - "envoy.transport_sockets.tls", null + "envoy.transport_sockets.tls", null, null ))); call.sendResponse(CDS, clusters, VERSION_2, "0001"); verify(cdsResourceWatcher, times(2)).onChanged(cdsUpdateCaptor.capture()); @@ -3256,7 +3482,7 @@ protected abstract Message buildVirtualHost( protected abstract Message buildEdsCluster(String clusterName, @Nullable String edsServiceName, String lbPolicy, @Nullable Message ringHashLbConfig, @Nullable Message leastRequestLbConfig, boolean enableLrs, @Nullable Message upstreamTlsContext, String transportSocketName, - @Nullable Message circuitBreakers); + @Nullable Message circuitBreakers, @Nullable Message outlierDetection); protected abstract Message buildLogicalDnsCluster(String clusterName, String dnsHostAddr, int dnsHostPort, String lbPolicy, @Nullable Message ringHashLbConfig, diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientV2Test.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientV2Test.java index b302b56dbb8..3e2cafd58fd 100644 --- a/xds/src/test/java/io/grpc/xds/ClientXdsClientV2Test.java +++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientV2Test.java @@ -52,6 +52,7 @@ import io.envoyproxy.envoy.api.v2.auth.UpstreamTlsContext; import io.envoyproxy.envoy.api.v2.cluster.CircuitBreakers; import io.envoyproxy.envoy.api.v2.cluster.CircuitBreakers.Thresholds; +import io.envoyproxy.envoy.api.v2.cluster.OutlierDetection; import io.envoyproxy.envoy.api.v2.core.Address; import io.envoyproxy.envoy.api.v2.core.AggregatedConfigSource; import io.envoyproxy.envoy.api.v2.core.ApiConfigSource; @@ -420,10 +421,10 @@ protected Message buildEdsCluster(String clusterName, @Nullable String edsServic String lbPolicy, @Nullable Message ringHashLbConfig, @Nullable Message leastRequestLbConfig, boolean enableLrs, @Nullable Message upstreamTlsContext, String transportSocketName, - @Nullable Message circuitBreakers) { + @Nullable Message circuitBreakers, @Nullable Message outlierDetection) { Cluster.Builder builder = initClusterBuilder( clusterName, lbPolicy, ringHashLbConfig, leastRequestLbConfig, - enableLrs, upstreamTlsContext, circuitBreakers); + enableLrs, upstreamTlsContext, circuitBreakers, outlierDetection); builder.setType(DiscoveryType.EDS); EdsClusterConfig.Builder edsClusterConfigBuilder = EdsClusterConfig.newBuilder(); edsClusterConfigBuilder.setEdsConfig( @@ -442,7 +443,7 @@ protected Message buildLogicalDnsCluster(String clusterName, String dnsHostAddr, @Nullable Message upstreamTlsContext, @Nullable Message circuitBreakers) { Cluster.Builder builder = initClusterBuilder( clusterName, lbPolicy, ringHashLbConfig, leastRequestLbConfig, - enableLrs, upstreamTlsContext, circuitBreakers); + enableLrs, upstreamTlsContext, circuitBreakers, null); builder.setType(DiscoveryType.LOGICAL_DNS); builder.setLoadAssignment( ClusterLoadAssignment.newBuilder().addEndpoints( @@ -483,7 +484,7 @@ protected Message buildAggregateCluster(String clusterName, String lbPolicy, private Cluster.Builder initClusterBuilder(String clusterName, String lbPolicy, @Nullable Message ringHashLbConfig, @Nullable Message leastRequestLbConfig, boolean enableLrs, @Nullable Message upstreamTlsContext, - @Nullable Message circuitBreakers) { + @Nullable Message circuitBreakers, @Nullable Message outlierDetection) { Cluster.Builder builder = Cluster.newBuilder(); builder.setName(clusterName); if (lbPolicy.equals("round_robin")) { @@ -511,6 +512,9 @@ private Cluster.Builder initClusterBuilder(String clusterName, String lbPolicy, if (circuitBreakers != null) { builder.setCircuitBreakers((CircuitBreakers) circuitBreakers); } + if (outlierDetection != null) { + builder.setOutlierDetection((OutlierDetection) outlierDetection); + } return builder; } diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientV3Test.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientV3Test.java index 4f86b178d2d..b051643c0bf 100644 --- a/xds/src/test/java/io/grpc/xds/ClientXdsClientV3Test.java +++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientV3Test.java @@ -41,6 +41,7 @@ import io.envoyproxy.envoy.config.cluster.v3.Cluster.LeastRequestLbConfig; import io.envoyproxy.envoy.config.cluster.v3.Cluster.RingHashLbConfig; import io.envoyproxy.envoy.config.cluster.v3.Cluster.RingHashLbConfig.HashFunction; +import io.envoyproxy.envoy.config.cluster.v3.OutlierDetection; import io.envoyproxy.envoy.config.core.v3.Address; import io.envoyproxy.envoy.config.core.v3.AggregatedConfigSource; import io.envoyproxy.envoy.config.core.v3.ConfigSource; @@ -476,10 +477,10 @@ protected Message buildEdsCluster(String clusterName, @Nullable String edsServic String lbPolicy, @Nullable Message ringHashLbConfig, @Nullable Message leastRequestLbConfig, boolean enableLrs, @Nullable Message upstreamTlsContext, String transportSocketName, - @Nullable Message circuitBreakers) { + @Nullable Message circuitBreakers, @Nullable Message outlierDetection) { Cluster.Builder builder = initClusterBuilder( clusterName, lbPolicy, ringHashLbConfig, leastRequestLbConfig, - enableLrs, upstreamTlsContext, transportSocketName, circuitBreakers); + enableLrs, upstreamTlsContext, transportSocketName, circuitBreakers, outlierDetection); builder.setType(DiscoveryType.EDS); EdsClusterConfig.Builder edsClusterConfigBuilder = EdsClusterConfig.newBuilder(); edsClusterConfigBuilder.setEdsConfig( @@ -498,7 +499,7 @@ protected Message buildLogicalDnsCluster(String clusterName, String dnsHostAddr, @Nullable Message upstreamTlsContext, @Nullable Message circuitBreakers) { Cluster.Builder builder = initClusterBuilder( clusterName, lbPolicy, ringHashLbConfig, leastRequestLbConfig, - enableLrs, upstreamTlsContext, "envoy.transport_sockets.tls", circuitBreakers); + enableLrs, upstreamTlsContext, "envoy.transport_sockets.tls", circuitBreakers, null); builder.setType(DiscoveryType.LOGICAL_DNS); builder.setLoadAssignment( ClusterLoadAssignment.newBuilder().addEndpoints( @@ -539,7 +540,7 @@ protected Message buildAggregateCluster(String clusterName, String lbPolicy, private Cluster.Builder initClusterBuilder(String clusterName, String lbPolicy, @Nullable Message ringHashLbConfig, @Nullable Message leastRequestLbConfig, boolean enableLrs, @Nullable Message upstreamTlsContext, String transportSocketName, - @Nullable Message circuitBreakers) { + @Nullable Message circuitBreakers, @Nullable Message outlierDetection) { Cluster.Builder builder = Cluster.newBuilder(); builder.setName(clusterName); if (lbPolicy.equals("round_robin")) { @@ -567,6 +568,9 @@ private Cluster.Builder initClusterBuilder(String clusterName, String lbPolicy, if (circuitBreakers != null) { builder.setCircuitBreakers((CircuitBreakers) circuitBreakers); } + if (outlierDetection != null) { + builder.setOutlierDetection((OutlierDetection) outlierDetection); + } return builder; } diff --git a/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java index 22c54bbb94a..2a8b95260fc 100644 --- a/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java @@ -58,6 +58,8 @@ import io.grpc.internal.GrpcUtil; import io.grpc.internal.ObjectPool; import io.grpc.internal.ServiceConfigUtil.PolicySelection; +import io.grpc.util.OutlierDetectionLoadBalancer.OutlierDetectionLoadBalancerConfig; +import io.grpc.util.OutlierDetectionLoadBalancerProvider; import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig; import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig; @@ -65,6 +67,9 @@ import io.grpc.xds.Endpoints.DropOverload; import io.grpc.xds.Endpoints.LbEndpoint; import io.grpc.xds.Endpoints.LocalityLbEndpoints; +import io.grpc.xds.EnvoyServerProtoData.FailurePercentageEjection; +import io.grpc.xds.EnvoyServerProtoData.OutlierDetection; +import io.grpc.xds.EnvoyServerProtoData.SuccessRateEjection; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.LeastRequestLoadBalancer.LeastRequestConfig; import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig; @@ -116,10 +121,18 @@ public class ClusterResolverLoadBalancerTest { Locality.create("test-region-3", "test-zone-3", "test-subzone-3"); private final UpstreamTlsContext tlsContext = CommonTlsContextTestsUtil.buildUpstreamTlsContext("google_cloud_private_spiffe", true); + private final OutlierDetection outlierDetection = OutlierDetection.create( + 100L, 100L, 100L, 100, SuccessRateEjection.create(100, 100, 100, 100), + FailurePercentageEjection.create(100, 100, 100, 100)); private final DiscoveryMechanism edsDiscoveryMechanism1 = - DiscoveryMechanism.forEds(CLUSTER1, EDS_SERVICE_NAME1, LRS_SERVER_INFO, 100L, tlsContext); + DiscoveryMechanism.forEds(CLUSTER1, EDS_SERVICE_NAME1, LRS_SERVER_INFO, 100L, tlsContext, + null); private final DiscoveryMechanism edsDiscoveryMechanism2 = - DiscoveryMechanism.forEds(CLUSTER2, EDS_SERVICE_NAME2, LRS_SERVER_INFO, 200L, tlsContext); + DiscoveryMechanism.forEds(CLUSTER2, EDS_SERVICE_NAME2, LRS_SERVER_INFO, 200L, tlsContext, + null); + private final DiscoveryMechanism edsDiscoveryMechanismWithOutlierDetection = + DiscoveryMechanism.forEds(CLUSTER1, EDS_SERVICE_NAME1, LRS_SERVER_INFO, 100L, tlsContext, + outlierDetection); private final DiscoveryMechanism logicalDnsDiscoveryMechanism = DiscoveryMechanism.forLogicalDns(CLUSTER_DNS, DNS_HOST_NAME, LRS_SERVER_INFO, 300L, null); @@ -182,6 +195,7 @@ public void setUp() throws URISyntaxException { lbRegistry.register(new FakeLoadBalancerProvider(WEIGHTED_TARGET_POLICY_NAME)); lbRegistry.register( new FakeLoadBalancerProvider("pick_first")); // needed by logical_dns + lbRegistry.register(new OutlierDetectionLoadBalancerProvider()); NameResolver.Args args = NameResolver.Args.newBuilder() .setDefaultPort(8080) .setProxyDetector(GrpcUtil.NOOP_PROXY_DETECTOR) @@ -317,6 +331,90 @@ public void edsClustersWithLeastRequestEndpointLbPolicy() { locality1, 100); } + @Test + public void edsClustersWithOutlierDetection() { + ClusterResolverConfig config = new ClusterResolverConfig( + Collections.singletonList(edsDiscoveryMechanismWithOutlierDetection), leastRequest); + deliverLbConfig(config); + assertThat(xdsClient.watchers.keySet()).containsExactly(EDS_SERVICE_NAME1); + assertThat(childBalancers).isEmpty(); + + // Simple case with one priority and one locality + EquivalentAddressGroup endpoint = makeAddress("endpoint-addr-1"); + LocalityLbEndpoints localityLbEndpoints = + LocalityLbEndpoints.create( + Arrays.asList( + LbEndpoint.create(endpoint, 0 /* loadBalancingWeight */, true)), + 100 /* localityWeight */, 1 /* priority */); + xdsClient.deliverClusterLoadAssignment( + EDS_SERVICE_NAME1, + ImmutableMap.of(locality1, localityLbEndpoints)); + assertThat(childBalancers).hasSize(1); + FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers); + assertThat(childBalancer.addresses).hasSize(1); + EquivalentAddressGroup addr = childBalancer.addresses.get(0); + assertThat(addr.getAddresses()).isEqualTo(endpoint.getAddresses()); + assertThat(childBalancer.name).isEqualTo(PRIORITY_POLICY_NAME); + PriorityLbConfig priorityLbConfig = (PriorityLbConfig) childBalancer.config; + assertThat(priorityLbConfig.priorities).containsExactly(CLUSTER1 + "[child1]"); + PriorityChildConfig priorityChildConfig = + Iterables.getOnlyElement(priorityLbConfig.childConfigs.values()); + + // The child config for priority should be outlier detection. + assertThat(priorityChildConfig.policySelection.getProvider().getPolicyName()) + .isEqualTo("outlier_detection_experimental"); + OutlierDetectionLoadBalancerConfig outlierDetectionConfig = + (OutlierDetectionLoadBalancerConfig) priorityChildConfig.policySelection.getConfig(); + + // The outlier detection config should faithfully represent what came down from xDS. + assertThat(outlierDetectionConfig.intervalNanos).isEqualTo(outlierDetection.intervalNanos()); + assertThat(outlierDetectionConfig.baseEjectionTimeNanos).isEqualTo( + outlierDetection.baseEjectionTimeNanos()); + assertThat(outlierDetectionConfig.baseEjectionTimeNanos).isEqualTo( + outlierDetection.baseEjectionTimeNanos()); + assertThat(outlierDetectionConfig.maxEjectionTimeNanos).isEqualTo( + outlierDetection.maxEjectionTimeNanos()); + assertThat(outlierDetectionConfig.maxEjectionPercent).isEqualTo( + outlierDetection.maxEjectionPercent()); + + OutlierDetectionLoadBalancerConfig.SuccessRateEjection successRateEjection + = outlierDetectionConfig.successRateEjection; + assertThat(successRateEjection.stdevFactor).isEqualTo( + outlierDetection.successRateEjection().stdevFactor()); + assertThat(successRateEjection.enforcementPercentage).isEqualTo( + outlierDetection.successRateEjection().enforcementPercentage()); + assertThat(successRateEjection.minimumHosts).isEqualTo( + outlierDetection.successRateEjection().minimumHosts()); + assertThat(successRateEjection.requestVolume).isEqualTo( + outlierDetection.successRateEjection().requestVolume()); + + OutlierDetectionLoadBalancerConfig.FailurePercentageEjection failurePercentageEjection + = outlierDetectionConfig.failurePercentageEjection; + assertThat(failurePercentageEjection.threshold).isEqualTo( + outlierDetection.failurePercentageEjection().threshold()); + assertThat(failurePercentageEjection.enforcementPercentage).isEqualTo( + outlierDetection.failurePercentageEjection().enforcementPercentage()); + assertThat(failurePercentageEjection.minimumHosts).isEqualTo( + outlierDetection.failurePercentageEjection().minimumHosts()); + assertThat(failurePercentageEjection.requestVolume).isEqualTo( + outlierDetection.failurePercentageEjection().requestVolume()); + + // The wrapped configuration should not have been tampered with. + ClusterImplConfig clusterImplConfig = + (ClusterImplConfig) outlierDetectionConfig.childPolicy.getConfig(); + assertClusterImplConfig(clusterImplConfig, CLUSTER1, EDS_SERVICE_NAME1, LRS_SERVER_INFO, 100L, + tlsContext, Collections.emptyList(), WRR_LOCALITY_POLICY_NAME); + WrrLocalityConfig wrrLocalityConfig = + (WrrLocalityConfig) clusterImplConfig.childPolicy.getConfig(); + assertThat(wrrLocalityConfig.childPolicy.getProvider().getPolicyName()).isEqualTo( + "least_request_experimental"); + + assertThat( + childBalancer.attributes.get(InternalXdsAttributes.ATTR_LOCALITY_WEIGHTS)).containsEntry( + locality1, 100); + } + + @Test public void onlyEdsClusters_receivedEndpoints() { ClusterResolverConfig config = new ClusterResolverConfig( From 618a4de705189ce01179434e85f91f197a36a6b4 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 18 Aug 2022 17:27:15 -0700 Subject: [PATCH 11/72] xds: CHANNEL_ID hash should be random for the channel The log id is an incrementing value starting from 0. That means the same binary on two different machines will have the same hashes for each consecutive Channel. That was not at all the intension of CHANNEL_ID. From gRFC A42: > This can be used in similar situations to where Envoy uses > connection_properties to hash on the source IP address. --- xds/src/main/java/io/grpc/xds/XdsNameResolver.java | 4 +++- xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java index d29132d1b25..0d51d3afa80 100644 --- a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java +++ b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java @@ -123,6 +123,7 @@ final class XdsNameResolver extends NameResolver { // put()/remove() must be called in SyncContext, and get() can be called in any thread. private final ConcurrentMap clusterRefs = new ConcurrentHashMap<>(); private final ConfigSelector configSelector = new ConfigSelector(); + private final long randomChannelId; private volatile RoutingConfig routingConfig = RoutingConfig.empty; private Listener2 listener; @@ -162,6 +163,7 @@ final class XdsNameResolver extends NameResolver { this.xdsClientPoolFactory.setBootstrapOverride(bootstrapOverride); this.random = checkNotNull(random, "random"); this.filterRegistry = checkNotNull(filterRegistry, "filterRegistry"); + randomChannelId = random.nextLong(); logId = InternalLogId.allocate("xds-resolver", name); logger = XdsLogger.withLogId(logId); logger.log(XdsLogLevel.INFO, "Created resolver for {0}", name); @@ -586,7 +588,7 @@ private long generateHash(List hashPolicies, Metadata headers) { newHash = hashFunc.hashAsciiString(value); } } else if (policy.type() == HashPolicy.Type.CHANNEL_ID) { - newHash = hashFunc.hashLong(logId.getId()); + newHash = hashFunc.hashLong(randomChannelId); } if (newHash != null ) { // Rotating the old value prevents duplicate hash rules from cancelling each other out diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java index 8f4a2cf1774..d96fa2b30a8 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java @@ -802,6 +802,7 @@ public void resolved_rpcHashingByChannelId() { // A different resolver/Channel. resolver.shutdown(); reset(mockListener); + when(mockRandom.nextLong()).thenReturn(123L); resolver = new XdsNameResolver(null, AUTHORITY, null, serviceConfigParser, syncContext, scheduler, xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); From b66250e9e5b6cf111ffac0785b00bcf0412629ce Mon Sep 17 00:00:00 2001 From: Larry Safran <107004254+larry-safran@users.noreply.github.com> Date: Fri, 19 Aug 2022 13:31:05 -0700 Subject: [PATCH 12/72] Rls spec sync (#9437) rls: Update implementation to match spec. * Cleanup cache if exceeds max size when add an entry. Make cache entry size calculations more accurate * Trigger pending RPC processing if unexpired backoff entries were removed from the cache by triggering helper to call it's parent updateBalancingState with the same state and picker * Introduce minimum time before eviction (5 seconds) * Change default accept ratio for AdaptiveThrottler from 1.2 -> 2.0 * Configuration validation * When checking key names for duplicates also look at headers * Check extra keys for duplicates See analysis of implementation versus spec at https://docs.google.com/spreadsheets/d/18w5s1TEebRumWzk1pvWnjiHFGKc6MW-vt8tRLY4eNs0/ --- .../RpcBehaviorLoadBalancerProvider.java | 2 +- .../java/io/grpc/rls/AdaptiveThrottler.java | 2 +- .../java/io/grpc/rls/CachingRlsLbClient.java | 75 +++++++++++++++---- .../java/io/grpc/rls/LinkedHashLruCache.java | 38 ++++++++-- .../java/io/grpc/rls/RlsProtoConverters.java | 26 +++++++ .../java/io/grpc/rls/RlsRequestFactory.java | 4 +- 6 files changed, 122 insertions(+), 25 deletions(-) diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/RpcBehaviorLoadBalancerProvider.java b/interop-testing/src/main/java/io/grpc/testing/integration/RpcBehaviorLoadBalancerProvider.java index 212ea7bed11..83c416765ec 100644 --- a/interop-testing/src/main/java/io/grpc/testing/integration/RpcBehaviorLoadBalancerProvider.java +++ b/interop-testing/src/main/java/io/grpc/testing/integration/RpcBehaviorLoadBalancerProvider.java @@ -38,7 +38,7 @@ * looks for an "rpc_behavior" field in its configuration and includes the value in the * "rpc-behavior" metadata entry that is sent to the server. This will cause the test server to * behave in a predefined way. Endpoint picking logic is delegated to the - * {@link RoundRobinLoadBalancer}. + * io.grpc.util.RoundRobinLoadBalancer. * *

Initial use case is to prove that a custom load balancer can be configured by the control * plane via xDS. An interop test will configure this LB and then verify it has been correctly diff --git a/rls/src/main/java/io/grpc/rls/AdaptiveThrottler.java b/rls/src/main/java/io/grpc/rls/AdaptiveThrottler.java index ff5789c517d..576b234b0c3 100644 --- a/rls/src/main/java/io/grpc/rls/AdaptiveThrottler.java +++ b/rls/src/main/java/io/grpc/rls/AdaptiveThrottler.java @@ -44,7 +44,7 @@ final class AdaptiveThrottler implements Throttler { private static final int DEFAULT_HISTORY_SECONDS = 30; private static final int DEFAULT_REQUEST_PADDING = 8; - private static final float DEFAULT_RATIO_FOR_ACCEPT = 1.2f; + private static final float DEFAULT_RATIO_FOR_ACCEPT = 2.0f; /** * The duration of history of calls used by Adaptive Throttler. diff --git a/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java b/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java index fcac2a37ce3..80d36b34b02 100644 --- a/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java +++ b/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java @@ -81,12 +81,17 @@ final class CachingRlsLbClient { REQUEST_CONVERTER = new RlsProtoConverters.RouteLookupRequestConverter().reverse(); private static final Converter RESPONSE_CONVERTER = new RouteLookupResponseConverter().reverse(); + public static final long MIN_EVICTION_TIME_DELTA_NANOS = TimeUnit.SECONDS.toNanos(5); + public static final int BYTES_PER_CHAR = 2; + public static final int STRING_OVERHEAD_BYTES = 38; + /** Minimum bytes for a Java Object. */ + public static final int OBJ_OVERHEAD_B = 16; // All cache status changes (pending, backoff, success) must be under this lock private final Object lock = new Object(); // LRU cache based on access order (BACKOFF and actual data will be here) @GuardedBy("lock") - private final LinkedHashLruCache linkedHashLruCache; + private final RlsAsyncLruCache linkedHashLruCache; // any RPC on the fly will cached in this map @GuardedBy("lock") private final Map pendingCallCache = new HashMap<>(); @@ -287,12 +292,12 @@ private CachedRouteLookupResponse handleNewRequest(RouteLookupRequest request) { try { RouteLookupResponse response = asyncCall.get(); DataCacheEntry dataEntry = new DataCacheEntry(request, response); - linkedHashLruCache.cache(request, dataEntry); + linkedHashLruCache.cacheAndClean(request, dataEntry); return CachedRouteLookupResponse.dataEntry(dataEntry); } catch (Exception e) { BackoffCacheEntry backoffEntry = new BackoffCacheEntry(request, Status.fromThrowable(e), backoffProvider.get()); - linkedHashLruCache.cache(request, backoffEntry); + linkedHashLruCache.cacheAndClean(request, backoffEntry); return CachedRouteLookupResponse.backoffEntry(backoffEntry); } } @@ -336,6 +341,10 @@ public void run() { } }); } + + void triggerPendingRpcProcessing() { + super.updateBalancingState(state, picker); + } } /** @@ -488,14 +497,15 @@ private void transitionToDataEntry(RouteLookupResponse routeLookupResponse) { ChannelLogLevel.DEBUG, "Transition to data cache: routeLookupResponse={0}", routeLookupResponse); - linkedHashLruCache.cache(request, new DataCacheEntry(request, routeLookupResponse)); + linkedHashLruCache.cacheAndClean(request, new DataCacheEntry(request, routeLookupResponse)); } } private void transitionToBackOff(Status status) { synchronized (lock) { logger.log(ChannelLogLevel.DEBUG, "Transition to back off: status={0}", status); - linkedHashLruCache.cache(request, new BackoffCacheEntry(request, status, backoffPolicy)); + linkedHashLruCache.cacheAndClean(request, + new BackoffCacheEntry(request, status, backoffPolicy)); } } @@ -525,11 +535,20 @@ final boolean isExpired() { abstract boolean isExpired(long now); abstract void cleanup(); + + protected long getMinEvictionTime() { + return 0L; + } + + protected void triggerPendingRpcProcessing() { + helper.triggerPendingRpcProcessing(); + } } /** Implementation of {@link CacheEntry} contains valid data. */ final class DataCacheEntry extends CacheEntry { private final RouteLookupResponse response; + private final long minEvictionTime; private final long expireTime; private final long staleTime; private final List childPolicyWrappers; @@ -543,6 +562,7 @@ final class DataCacheEntry extends CacheEntry { refCountedChildPolicyWrapperFactory .createOrGet(response.targets()); long now = ticker.read(); + minEvictionTime = now + MIN_EVICTION_TIME_DELTA_NANOS; expireTime = now + maxAgeNanos; staleTime = now + staleAgeNanos; } @@ -574,13 +594,13 @@ void maybeRefresh() { // async call returned finished future is most likely throttled try { RouteLookupResponse response = asyncCall.get(); - linkedHashLruCache.cache(request, new DataCacheEntry(request, response)); + linkedHashLruCache.cacheAndClean(request, new DataCacheEntry(request, response)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (Exception e) { BackoffCacheEntry backoffEntry = new BackoffCacheEntry(request, Status.fromThrowable(e), backoffProvider.get()); - linkedHashLruCache.cache(request, backoffEntry); + linkedHashLruCache.cacheAndClean(request, backoffEntry); } } } @@ -611,11 +631,19 @@ String getHeaderData() { return response.getHeaderData(); } + // Assume UTF-16 (2 bytes) and overhead of a String object is 38 bytes + int calcStringSize(String target) { + return target.length() * BYTES_PER_CHAR + STRING_OVERHEAD_BYTES; + } + @Override int getSizeBytes() { - // size of strings and java object overhead, actual memory usage is more than this. - return - (response.targets().get(0).length() + response.getHeaderData().length()) * 2 + 38 * 2; + int targetSize = 0; + for (String target : response.targets()) { + targetSize += calcStringSize(target); + } + return targetSize + calcStringSize(response.getHeaderData()) + OBJ_OVERHEAD_B // response size + + Long.SIZE * 2 + OBJ_OVERHEAD_B; // Other fields } @Override @@ -627,6 +655,11 @@ boolean isStaled(long now) { return staleTime - now <= 0; } + @Override + protected long getMinEvictionTime() { + return minEvictionTime; + } + @Override void cleanup() { synchronized (lock) { @@ -700,11 +733,11 @@ private void transitionToPending() { } else { try { RouteLookupResponse response = call.get(); - linkedHashLruCache.cache(request, new DataCacheEntry(request, response)); + linkedHashLruCache.cacheAndClean(request, new DataCacheEntry(request, response)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (Exception e) { - linkedHashLruCache.cache( + linkedHashLruCache.cacheAndClean( request, new BackoffCacheEntry(request, Status.fromThrowable(e), backoffPolicy)); } @@ -718,7 +751,7 @@ Status getStatus() { @Override int getSizeBytes() { - return 0; + return OBJ_OVERHEAD_B * 3 + Long.SIZE + 8; // 3 java objects, 1 long and a boolean } @Override @@ -876,8 +909,22 @@ protected int estimateSizeOf(RouteLookupRequest key, CacheEntry value) { @Override protected boolean shouldInvalidateEldestEntry( RouteLookupRequest eldestKey, CacheEntry eldestValue) { + if (eldestValue.getMinEvictionTime() > now()) { + return false; + } + // eldest entry should be evicted if size limit exceeded - return true; + return this.estimatedSizeBytes() > this.estimatedMaxSizeBytes(); + } + + public CacheEntry cacheAndClean(RouteLookupRequest key, CacheEntry value) { + CacheEntry newEntry = cache(key, value); + + // force cleanup if new entry pushed cache over max size (in bytes) + if (fitToLimit()) { + value.triggerPendingRpcProcessing(); + } + return newEntry; } } diff --git a/rls/src/main/java/io/grpc/rls/LinkedHashLruCache.java b/rls/src/main/java/io/grpc/rls/LinkedHashLruCache.java index dde3381a1f4..77376a374f4 100644 --- a/rls/src/main/java/io/grpc/rls/LinkedHashLruCache.java +++ b/rls/src/main/java/io/grpc/rls/LinkedHashLruCache.java @@ -118,6 +118,10 @@ protected int estimateSizeOf(K key, V value) { return 1; } + protected long estimatedMaxSizeBytes() { + return estimatedMaxSizeBytes; + } + /** Updates size for given key if entry exists. It is useful if the cache value is mutated. */ public void updateEntrySize(K key) { synchronized (lock) { @@ -233,30 +237,50 @@ public final List values() { } } + protected long now() { + return ticker.read(); + } + /** - * Resizes cache. If new size is smaller than current estimated size, it will free up space by + * Cleans up cache if needed to fit into max size bytes by * removing expired entries and removing oldest entries by LRU order. + * Returns TRUE if any unexpired entries were removed */ - public final void resize(int newSizeBytes) { - long now = ticker.read(); + protected final boolean fitToLimit() { + boolean removedAnyUnexpired = false; synchronized (lock) { - this.estimatedMaxSizeBytes = newSizeBytes; - if (estimatedSizeBytes.get() <= newSizeBytes) { + if (estimatedSizeBytes.get() <= estimatedMaxSizeBytes) { // new size is larger no need to do cleanup - return; + return false; } // cleanup expired entries - cleanupExpiredEntries(now); + cleanupExpiredEntries(now()); // cleanup eldest entry until new size limit Iterator> lruIter = delegate.entrySet().iterator(); while (lruIter.hasNext() && estimatedMaxSizeBytes < this.estimatedSizeBytes.get()) { Map.Entry entry = lruIter.next(); + if (!shouldInvalidateEldestEntry(entry.getKey(), entry.getValue().value)) { + break; // Violates some constraint like minimum age so stop our cleanup + } lruIter.remove(); // eviction listener will update the estimatedSizeBytes evictionListener.onEviction(entry.getKey(), entry.getValue(), EvictionType.SIZE); + removedAnyUnexpired = true; } } + return removedAnyUnexpired; + } + + /** + * Resizes cache. If new size is smaller than current estimated size, it will free up space by + * removing expired entries and removing oldest entries by LRU order. + */ + public final void resize(long newSizeBytes) { + synchronized (lock) { + this.estimatedMaxSizeBytes = newSizeBytes; + fitToLimit(); + } } @Override diff --git a/rls/src/main/java/io/grpc/rls/RlsProtoConverters.java b/rls/src/main/java/io/grpc/rls/RlsProtoConverters.java index efc516919dd..cd164f5e2a7 100644 --- a/rls/src/main/java/io/grpc/rls/RlsProtoConverters.java +++ b/rls/src/main/java/io/grpc/rls/RlsProtoConverters.java @@ -112,13 +112,28 @@ protected RouteLookupConfig doForward(Map json) { ImmutableList grpcKeybuilders = GrpcKeyBuilderConverter.covertAll( checkNotNull(JsonUtil.getListOfObjects(json, "grpcKeybuilders"), "grpcKeybuilders")); + + // Validate grpc_keybuilders checkArgument(!grpcKeybuilders.isEmpty(), "must have at least one GrpcKeyBuilder"); Set names = new HashSet<>(); for (GrpcKeyBuilder keyBuilder : grpcKeybuilders) { for (Name name : keyBuilder.names()) { checkArgument(names.add(name), "duplicate names in grpc_keybuilders: " + name); } + + Set keys = new HashSet<>(); + for (NameMatcher header : keyBuilder.headers()) { + checkKeys(keys, header.key(), "header"); + } + for (String key : keyBuilder.constantKeys().keySet()) { + checkKeys(keys, key, "constant"); + } + String extraKeyStr = keyToString(keyBuilder.extraKeys()); + checkArgument(keys.add(extraKeyStr), + "duplicate extra key in grpc_keybuilders: " + extraKeyStr); } + + // Validate lookup_service String lookupService = JsonUtil.getString(json, "lookupService"); checkArgument(!Strings.isNullOrEmpty(lookupService), "lookupService must not be empty"); try { @@ -157,6 +172,11 @@ protected RouteLookupConfig doForward(Map json) { .build(); } + private static String keyToString(ExtraKeys extraKeys) { + return String.format("host: %s, service: %s, method: %s", + extraKeys.host(), extraKeys.service(), extraKeys.method()); + } + private static T orDefault(@Nullable T value, T defaultValue) { if (value == null) { return checkNotNull(defaultValue, "defaultValue"); @@ -170,6 +190,12 @@ protected Map doBackward(RouteLookupConfig routeLookupConfig) { } } + private static void checkKeys(Set keys, String key, String keyType) { + checkArgument(key != null, "unset " + keyType + " key"); + checkArgument(!key.isEmpty(), "Empty string for " + keyType + " key"); + checkArgument(keys.add(key), "duplicate " + keyType + " key in grpc_keybuilders: " + key); + } + private static final class GrpcKeyBuilderConverter { public static ImmutableList covertAll(List> keyBuilders) { ImmutableList.Builder keyBuilderList = ImmutableList.builder(); diff --git a/rls/src/main/java/io/grpc/rls/RlsRequestFactory.java b/rls/src/main/java/io/grpc/rls/RlsRequestFactory.java index f47ed91a81b..a6ca0137ff1 100644 --- a/rls/src/main/java/io/grpc/rls/RlsRequestFactory.java +++ b/rls/src/main/java/io/grpc/rls/RlsRequestFactory.java @@ -52,8 +52,8 @@ private static Map createKeyBuilderTable( Map table = new HashMap<>(); for (GrpcKeyBuilder grpcKeyBuilder : config.grpcKeybuilders()) { for (Name name : grpcKeyBuilder.names()) { - boolean hasMethod = name.method() == null || name.method().isEmpty(); - String method = hasMethod ? "*" : name.method(); + boolean noMethod = name.method() == null || name.method().isEmpty(); + String method = noMethod ? "*" : name.method(); String path = "/" + name.service() + "/" + method; table.put(path, grpcKeyBuilder); } From 2a364838beb08f96a013c4e0934e0e9540f4412e Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Fri, 4 Mar 2022 12:50:17 -0800 Subject: [PATCH 13/72] googleapis: Stabilize google-c2p resolver Preserve google-c2p-experimental support for the moment to ease testing migration. --- ...oProdExperimentalNameResolverProvider.java | 52 +++++++++++++++++++ ...GoogleCloudToProdNameResolverProvider.java | 17 ++++-- .../services/io.grpc.NameResolverProvider | 1 + ...leCloudToProdNameResolverProviderTest.java | 19 +++++++ 4 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdExperimentalNameResolverProvider.java diff --git a/googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdExperimentalNameResolverProvider.java b/googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdExperimentalNameResolverProvider.java new file mode 100644 index 00000000000..349e1c94380 --- /dev/null +++ b/googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdExperimentalNameResolverProvider.java @@ -0,0 +1,52 @@ +/* + * Copyright 2022 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.googleapis; + +import io.grpc.Internal; +import io.grpc.NameResolver; +import io.grpc.NameResolver.Args; +import io.grpc.NameResolverProvider; +import java.net.URI; + +/** + * A provider for {@link GoogleCloudToProdNameResolver}, with experimental scheme. + */ +@Internal +public final class GoogleCloudToProdExperimentalNameResolverProvider extends NameResolverProvider { + private final GoogleCloudToProdNameResolverProvider delegate = + new GoogleCloudToProdNameResolverProvider("google-c2p-experimental"); + + @Override + public NameResolver newNameResolver(URI targetUri, Args args) { + return delegate.newNameResolver(targetUri, args); + } + + @Override + public String getDefaultScheme() { + return delegate.getDefaultScheme(); + } + + @Override + protected boolean isAvailable() { + return delegate.isAvailable(); + } + + @Override + protected int priority() { + return delegate.priority(); + } +} diff --git a/googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdNameResolverProvider.java b/googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdNameResolverProvider.java index b431cba4d27..ce833d5c4e0 100644 --- a/googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdNameResolverProvider.java +++ b/googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdNameResolverProvider.java @@ -16,6 +16,7 @@ package io.grpc.googleapis; +import com.google.common.base.Preconditions; import io.grpc.Internal; import io.grpc.NameResolver; import io.grpc.NameResolver.Args; @@ -35,11 +36,21 @@ @Internal public final class GoogleCloudToProdNameResolverProvider extends NameResolverProvider { - private static final String SCHEME = "google-c2p-experimental"; + private static final String SCHEME = "google-c2p"; + + private final String scheme; + + public GoogleCloudToProdNameResolverProvider() { + this(SCHEME); + } + + GoogleCloudToProdNameResolverProvider(String scheme) { + this.scheme = Preconditions.checkNotNull(scheme, "scheme"); + } @Override public NameResolver newNameResolver(URI targetUri, Args args) { - if (SCHEME.equals(targetUri.getScheme())) { + if (scheme.equals(targetUri.getScheme())) { return new GoogleCloudToProdNameResolver( targetUri, args, GrpcUtil.SHARED_CHANNEL_EXECUTOR, new SharedXdsClientPoolProviderBootstrapSetter()); @@ -49,7 +60,7 @@ public NameResolver newNameResolver(URI targetUri, Args args) { @Override public String getDefaultScheme() { - return SCHEME; + return scheme; } @Override diff --git a/googleapis/src/main/resources/META-INF/services/io.grpc.NameResolverProvider b/googleapis/src/main/resources/META-INF/services/io.grpc.NameResolverProvider index 553e5409180..1d08ff2bb0b 100644 --- a/googleapis/src/main/resources/META-INF/services/io.grpc.NameResolverProvider +++ b/googleapis/src/main/resources/META-INF/services/io.grpc.NameResolverProvider @@ -1 +1,2 @@ +io.grpc.googleapis.GoogleCloudToProdExperimentalNameResolverProvider io.grpc.googleapis.GoogleCloudToProdNameResolverProvider diff --git a/googleapis/src/test/java/io/grpc/googleapis/GoogleCloudToProdNameResolverProviderTest.java b/googleapis/src/test/java/io/grpc/googleapis/GoogleCloudToProdNameResolverProviderTest.java index e6ef1696dc0..447b102c8c7 100644 --- a/googleapis/src/test/java/io/grpc/googleapis/GoogleCloudToProdNameResolverProviderTest.java +++ b/googleapis/src/test/java/io/grpc/googleapis/GoogleCloudToProdNameResolverProviderTest.java @@ -71,9 +71,28 @@ NameResolverProvider.class, getClass().getClassLoader())) { fail("GoogleCloudToProdNameResolverProvider not registered"); } + @Test + public void experimentalProvided() { + for (NameResolverProvider current + : InternalServiceProviders.getCandidatesViaServiceLoader( + NameResolverProvider.class, getClass().getClassLoader())) { + if (current instanceof GoogleCloudToProdExperimentalNameResolverProvider) { + return; + } + } + fail("GoogleCloudToProdExperimentalNameResolverProvider not registered"); + } + @Test public void newNameResolver() { assertThat(provider + .newNameResolver(URI.create("google-c2p:///foo.googleapis.com"), args)) + .isInstanceOf(GoogleCloudToProdNameResolver.class); + } + + @Test + public void experimentalNewNameResolver() { + assertThat(new GoogleCloudToProdExperimentalNameResolverProvider() .newNameResolver(URI.create("google-c2p-experimental:///foo.googleapis.com"), args)) .isInstanceOf(GoogleCloudToProdNameResolver.class); } From 20ab369f3a4ef6d105b2bb42240405f97a390ec3 Mon Sep 17 00:00:00 2001 From: DNVindhya Date: Fri, 19 Aug 2022 17:09:17 -0700 Subject: [PATCH 14/72] gcp-observability: update configuration variable to enable cloud tracing (#9463) --- .../gcp/observability/ObservabilityConfigImpl.java | 2 +- .../gcp/observability/ObservabilityConfigImplTest.java | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) 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 bcad0851562..1d0505e2818 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 @@ -82,7 +82,7 @@ private void parseConfig(Map config) { if (value != null) { enableCloudMonitoring = value; } - value = JsonUtil.getBoolean(config, "enable_cloud_tracing"); + value = JsonUtil.getBoolean(config, "enable_cloud_trace"); if (value != null) { enableCloudTracing = value; } 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 e065d7f29a4..821dcd43ee4 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 @@ -80,26 +80,26 @@ public class ObservabilityConfigImplTest { private static final String ENABLE_CLOUD_MONITORING_AND_TRACING = "{\n" + " \"enable_cloud_monitoring\": true,\n" - + " \"enable_cloud_tracing\": true\n" + + " \"enable_cloud_trace\": true\n" + "}"; private static final String GLOBAL_TRACING_ALWAYS_SAMPLER = "{\n" - + " \"enable_cloud_tracing\": true,\n" + + " \"enable_cloud_trace\": true,\n" + " \"global_trace_sampling_rate\": 1.00\n" + "}"; private static final String GLOBAL_TRACING_NEVER_SAMPLER = "{\n" - + " \"enable_cloud_tracing\": true,\n" + + " \"enable_cloud_trace\": true,\n" + " \"global_trace_sampling_rate\": 0.00\n" + "}"; private static final String GLOBAL_TRACING_PROBABILISTIC_SAMPLER = "{\n" - + " \"enable_cloud_tracing\": true,\n" + + " \"enable_cloud_trace\": true,\n" + " \"global_trace_sampling_rate\": 0.75\n" + "}"; private static final String GLOBAL_TRACING_DEFAULT_SAMPLER = "{\n" - + " \"enable_cloud_tracing\": true\n" + + " \"enable_cloud_trace\": true\n" + "}"; private static final String GLOBAL_TRACING_BADPROBABILISTIC_SAMPLER = "{\n" From 9ed5a1bbbf53077110167261edc1d4e4c5864e35 Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Sun, 21 Aug 2022 16:13:31 -0700 Subject: [PATCH 15/72] xds: Fix outlier detection env flag name. (#9462) --- xds/src/main/java/io/grpc/xds/ClientXdsClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java index 208b9a2852f..d63e3b3c2aa 100644 --- a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java +++ b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java @@ -169,8 +169,8 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res || Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_XDS_CUSTOM_LB_CONFIG")); @VisibleForTesting static boolean enableOutlierDetection = - !Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_XDS_OUTLIER_DETECTION")) - || Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_XDS_OUTLIER_DETECTION")); + !Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION")) + || Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION")); private static final String TYPE_URL_HTTP_CONNECTION_MANAGER_V2 = "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2" + ".HttpConnectionManager"; From f5670b39c4d59f8110d6d075a7066ef58c2e34c4 Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Mon, 22 Aug 2022 10:29:57 -0700 Subject: [PATCH 16/72] core: OutlierDetectionLoadBalancer to pass child LB config. (#9467) --- .../main/java/io/grpc/util/OutlierDetectionLoadBalancer.java | 4 +++- .../java/io/grpc/util/OutlierDetectionLoadBalancerTest.java | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java index 66380b9631b..5b3fb6ee0b8 100644 --- a/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java +++ b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java @@ -139,7 +139,9 @@ public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { trackerMap.cancelTracking(); } - switchLb.handleResolvedAddresses(resolvedAddresses); + switchLb.handleResolvedAddresses( + resolvedAddresses.toBuilder().setLoadBalancingPolicyConfig(config.childPolicy.getConfig()) + .build()); } @Override diff --git a/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java b/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java index 5b73edb4c2c..1bc541586cc 100644 --- a/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java +++ b/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java @@ -233,7 +233,9 @@ public void handleResolvedAddresses() { loadBalancer.handleResolvedAddresses(resolvedAddresses); // Handling of resolved addresses is delegated - verify(mockChildLb).handleResolvedAddresses(resolvedAddresses); + verify(mockChildLb).handleResolvedAddresses( + resolvedAddresses.toBuilder().setLoadBalancingPolicyConfig(config.childPolicy.getConfig()) + .build()); // There is a single pending task to run the outlier detection algorithm assertThat(fakeClock.getPendingTasks()).hasSize(1); From fbefdf5ea7dcbbf690828b1e6df86deb07f8b885 Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Mon, 22 Aug 2022 11:12:51 -0700 Subject: [PATCH 17/72] core: [outlier detection] Get addresses from subchannel args. (#9468) --- .../main/java/io/grpc/util/OutlierDetectionLoadBalancer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java index 5b3fb6ee0b8..3473dd174cd 100644 --- a/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java +++ b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java @@ -206,7 +206,7 @@ public Subchannel createSubchannel(CreateSubchannelArgs args) { // If the subchannel is associated with a single address that is also already in the map // the subchannel will be added to the map and be included in outlier detection. - List addressGroups = subchannel.getAllAddresses(); + List addressGroups = args.getAddresses(); if (hasSingleAddress(addressGroups) && trackerMap.containsKey(addressGroups.get(0).getAddresses().get(0))) { AddressTracker tracker = trackerMap.get(addressGroups.get(0).getAddresses().get(0)); From b16e0e34a43a8d4434e147a0dfa33d282ec103c9 Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Mon, 22 Aug 2022 11:34:50 -0700 Subject: [PATCH 18/72] core: [outlier detection] eject/uneject states to delegate listener. (#9469) --- .../util/OutlierDetectionLoadBalancer.java | 6 +- .../OutlierDetectionLoadBalancerTest.java | 55 ++++++++++--------- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java index 3473dd174cd..068bfb4de6d 100644 --- a/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java +++ b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java @@ -233,7 +233,7 @@ class OutlierDetectionSubchannel extends ForwardingSubchannel { private AddressTracker addressTracker; private boolean ejected; private ConnectivityStateInfo lastSubchannelState; - private OutlierDetectionSubchannelStateListener subchannelStateListener; + private SubchannelStateListener subchannelStateListener; OutlierDetectionSubchannel(Subchannel delegate) { this.delegate = delegate; @@ -241,8 +241,8 @@ class OutlierDetectionSubchannel extends ForwardingSubchannel { @Override public void start(SubchannelStateListener listener) { - subchannelStateListener = new OutlierDetectionSubchannelStateListener(listener); - super.start(subchannelStateListener); + subchannelStateListener = listener; + super.start(new OutlierDetectionSubchannelStateListener(listener)); } @Override diff --git a/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java b/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java index 1bc541586cc..cbd32c91904 100644 --- a/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java +++ b/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java @@ -356,7 +356,7 @@ public void successRateNoOutliers() { loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); - generateLoad(ImmutableMap.of()); + generateLoad(ImmutableMap.of(), 7); // Move forward in time to a point where the detection timer has fired. forwardTime(config); @@ -380,7 +380,7 @@ public void successRateOneOutlier() { loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); - generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED)); + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); // Move forward in time to a point where the detection timer has fired. forwardTime(config); @@ -405,7 +405,7 @@ public void successRateOneOutlier_configChange() { loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); - generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED)); + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); // Move forward in time to a point where the detection timer has fired. forwardTime(config); @@ -425,7 +425,7 @@ public void successRateOneOutlier_configChange() { loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); - generateLoad(ImmutableMap.of(subchannel2, Status.DEADLINE_EXCEEDED)); + generateLoad(ImmutableMap.of(subchannel2, Status.DEADLINE_EXCEEDED), 8); // Move forward in time to a point where the detection timer has fired. forwardTime(config); @@ -450,7 +450,7 @@ public void successRateOneOutlier_unejected() { loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); - generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED)); + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); // Move forward in time to a point where the detection timer has fired. fakeClock.forwardTime(config.intervalNanos + 1, TimeUnit.NANOSECONDS); @@ -459,7 +459,7 @@ public void successRateOneOutlier_unejected() { assertEjectedSubchannels(ImmutableSet.of(servers.get(0).getAddresses().get(0))); // Now we produce more load, but the subchannel start working and is no longer an outlier. - generateLoad(ImmutableMap.of()); + generateLoad(ImmutableMap.of(), 8); // Move forward in time to a point where the detection timer has fired. fakeClock.forwardTime(config.maxEjectionTimeNanos + 1, TimeUnit.NANOSECONDS); @@ -486,7 +486,7 @@ public void successRateOneOutlier_notEnoughVolume() { // We produce an outlier, but don't give it enough calls to reach the minimum volume. generateLoad( ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), - ImmutableMap.of(subchannel1, 19)); + ImmutableMap.of(subchannel1, 19), 7); // Move forward in time to a point where the detection timer has fired. forwardTime(config); @@ -510,7 +510,7 @@ public void successRateOneOutlier_notEnoughAddressesWithVolume() { loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); - generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED)); + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); // Move forward in time to a point where the detection timer has fired. forwardTime(config); @@ -536,7 +536,7 @@ public void successRateOneOutlier_enforcementPercentage() { loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); - generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED)); + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); // Move forward in time to a point where the detection timer has fired. forwardTime(config); @@ -563,7 +563,7 @@ public void successRateTwoOutliers() { generateLoad(ImmutableMap.of( subchannel1, Status.DEADLINE_EXCEEDED, - subchannel2, Status.DEADLINE_EXCEEDED)); + subchannel2, Status.DEADLINE_EXCEEDED), 7); // Move forward in time to a point where the detection timer has fired. forwardTime(config); @@ -591,7 +591,7 @@ public void successRateTwoOutliers_maxEjectionPercentage() { generateLoad(ImmutableMap.of( subchannel1, Status.DEADLINE_EXCEEDED, - subchannel2, Status.DEADLINE_EXCEEDED)); + subchannel2, Status.DEADLINE_EXCEEDED), 7); // Move forward in time to a point where the detection timer has fired. forwardTime(config); @@ -626,7 +626,7 @@ public void failurePercentageNoOutliers() { loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); // By default all calls will return OK. - generateLoad(ImmutableMap.of()); + generateLoad(ImmutableMap.of(), 7); // Move forward in time to a point where the detection timer has fired. forwardTime(config); @@ -650,7 +650,7 @@ public void failurePercentageOneOutlier() { loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); - generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED)); + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); // Move forward in time to a point where the detection timer has fired. forwardTime(config); @@ -674,7 +674,7 @@ public void failurePercentageOneOutlier_notEnoughVolume() { loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); - generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED)); + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); // Move forward in time to a point where the detection timer has fired. forwardTime(config); @@ -700,7 +700,7 @@ public void failurePercentageOneOutlier_enforcementPercentage() { loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); - generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED)); + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); // Move forward in time to a point where the detection timer has fired. forwardTime(config); @@ -737,7 +737,7 @@ public void successRateAndFailurePercentageThreeOutliers() { subchannel1, Status.DEADLINE_EXCEEDED, subchannel2, Status.DEADLINE_EXCEEDED, subchannel3, Status.DEADLINE_EXCEEDED), - ImmutableMap.of(subchannel3, 1)); + ImmutableMap.of(subchannel3, 1), 7); // Move forward in time to a point where the detection timer has fired. forwardTime(config); @@ -766,7 +766,7 @@ public void subchannelUpdateAddress_singleReplaced() { loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); - generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED)); + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); // Move forward in time to a point where the detection timer has fired. forwardTime(config); @@ -813,7 +813,7 @@ public void subchannelUpdateAddress_singleReplacedWithMultiple() { loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); - generateLoad(ImmutableMap.of()); + generateLoad(ImmutableMap.of(), 7); // Move forward in time to a point where the detection timer has fired. forwardTime(config); @@ -860,7 +860,7 @@ public void subchannelUpdateAddress_multipleReplacedWithSingle() { loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); - generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED)); + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); // Move forward in time to a point where the detection timer has fired. forwardTime(config); @@ -922,7 +922,7 @@ public void successRateAndFailurePercentage_noOutliers() { loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); - generateLoad(ImmutableMap.of()); + generateLoad(ImmutableMap.of(), 7); // Move forward in time to a point where the detection timer has fired. forwardTime(config); @@ -949,7 +949,7 @@ public void successRateAndFailurePercentage_successRateOutlier() { loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); - generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED)); + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); // Move forward in time to a point where the detection timer has fired. forwardTime(config); @@ -976,7 +976,7 @@ public void successRateAndFailurePercentage_errorPercentageOutlier() { loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); - generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED)); + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); // Move forward in time to a point where the detection timer has fired. forwardTime(config); @@ -1025,22 +1025,23 @@ private void deliverSubchannelState(Subchannel subchannel, ConnectivityStateInfo subchannelStateListeners.get(subchannel).onSubchannelState(newState); } - private void generateLoad(Map statusMap) { - generateLoad(statusMap, null); + private void generateLoad(Map statusMap, int expectedStateChanges) { + generateLoad(statusMap, null, expectedStateChanges); } // Generates 100 calls, 20 each across the subchannels. Default status is OK. private void generateLoad(Map statusMap, - Map maxCallsMap) { + Map maxCallsMap, int expectedStateChanges) { deliverSubchannelState(subchannel1, ConnectivityStateInfo.forNonError(READY)); deliverSubchannelState(subchannel2, ConnectivityStateInfo.forNonError(READY)); deliverSubchannelState(subchannel3, ConnectivityStateInfo.forNonError(READY)); deliverSubchannelState(subchannel4, ConnectivityStateInfo.forNonError(READY)); deliverSubchannelState(subchannel5, ConnectivityStateInfo.forNonError(READY)); - verify(mockHelper, times(7)).updateBalancingState(stateCaptor.capture(), + verify(mockHelper, times(expectedStateChanges)).updateBalancingState(stateCaptor.capture(), pickerCaptor.capture()); - SubchannelPicker picker = pickerCaptor.getAllValues().get(6); + SubchannelPicker picker = pickerCaptor.getAllValues() + .get(pickerCaptor.getAllValues().size() - 1); HashMap callCountMap = new HashMap<>(); for (int i = 0; i < 100; i++) { From 1dd764a3a18ea7e91dfccd03b87a92f79fc67032 Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Mon, 22 Aug 2022 14:27:20 -0700 Subject: [PATCH 19/72] buildscripts: Add outlier_detection_test interop test. (#9461) --- buildscripts/kokoro/xds_k8s_lb.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildscripts/kokoro/xds_k8s_lb.sh b/buildscripts/kokoro/xds_k8s_lb.sh index d9e575bb652..295c4d5c7bc 100755 --- a/buildscripts/kokoro/xds_k8s_lb.sh +++ b/buildscripts/kokoro/xds_k8s_lb.sh @@ -172,7 +172,7 @@ main() { # Run tests cd "${TEST_DRIVER_FULL_DIR}" local failed_tests=0 - test_suites=("api_listener_test" "change_backend_service_test" "failover_test" "remove_neg_test" "round_robin_test" "affinity_test") + test_suites=("api_listener_test" "change_backend_service_test" "failover_test" "remove_neg_test" "round_robin_test" "affinity_test" "outlier_detection_test") for test in "${test_suites[@]}"; do run_test $test || (( failed_tests++ )) done From d4cd926c96c8ae1102ec493b403e9814b06eacfd Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Tue, 23 Aug 2022 11:32:10 -0700 Subject: [PATCH 20/72] core: Enable outlier detection by default. (#9479) --- xds/src/main/java/io/grpc/xds/ClientXdsClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java index d63e3b3c2aa..55593eb99a9 100644 --- a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java +++ b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java @@ -169,7 +169,7 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res || Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_XDS_CUSTOM_LB_CONFIG")); @VisibleForTesting static boolean enableOutlierDetection = - !Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION")) + Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION")) || Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION")); private static final String TYPE_URL_HTTP_CONNECTION_MANAGER_V2 = "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2" From 95508e19dfe799ed2c77570ab764f984aa7a27bb Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Tue, 23 Aug 2022 10:40:41 -0700 Subject: [PATCH 21/72] binder: Exclude internal classes from javadoc --- binder/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/binder/build.gradle b/binder/build.gradle index 24dd0d9015d..d18fe5e6b20 100644 --- a/binder/build.gradle +++ b/binder/build.gradle @@ -96,6 +96,8 @@ tasks.withType(JavaCompile).configureEach { tasks.register("javadocs", Javadoc) { source = android.sourceSets.main.java.srcDirs + exclude 'io/grpc/binder/internal/**' + exclude 'io/grpc/binder/Internal*' classpath += files(android.getBootClasspath()) classpath += files({ android.libraryVariants.collect { variant -> From e1ddc3553f0fbb5721b785011558c7aefb730d9c Mon Sep 17 00:00:00 2001 From: Sergii Tkachenko Date: Tue, 23 Aug 2022 14:56:03 -0700 Subject: [PATCH 22/72] example-android: Add android:exported="true" tag (#9482) Add android:exported="true" tag to activities/services/receivers that specify an intent filter. Apps targeting Android 12 and higher are required to specify an explicit value for `android:exported` when the corresponding component has an intent filter defined. Future versions of the manifest merger, as well as the Android platform and the Playstore enforce this. --- examples/android/helloworld/app/src/main/AndroidManifest.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/android/helloworld/app/src/main/AndroidManifest.xml b/examples/android/helloworld/app/src/main/AndroidManifest.xml index eee4057cd0c..0beff2dc840 100644 --- a/examples/android/helloworld/app/src/main/AndroidManifest.xml +++ b/examples/android/helloworld/app/src/main/AndroidManifest.xml @@ -11,7 +11,8 @@ android:theme="@style/Base.V7.Theme.AppCompat.Light" > + android:label="@string/app_name" + android:exported="true"> From 0cf2d8bc48c86678aba35ba76bde82ead717cb5d Mon Sep 17 00:00:00 2001 From: Sergii Tkachenko Date: Wed, 24 Aug 2022 10:17:12 -0700 Subject: [PATCH 23/72] context: Skip storageReturnsNullTest for JDK >= 11 (#9484) JDK-8210522 changes the behaviour of Java reflection to filter out security-sensitive fields in the java.lang.reflect.Field. This prohibits Field.class.getDeclaredFields("modifiers") call we rely on in this test. Until we have a good solution for setting a custom storage for testing purposes, we'll have to skip this test for JDK >= 11. Ref https://bugs.openjdk.org/browse/JDK-8210522 --- context/src/test/java/io/grpc/ContextTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/context/src/test/java/io/grpc/ContextTest.java b/context/src/test/java/io/grpc/ContextTest.java index 5171d4839a9..c2f36f41d71 100644 --- a/context/src/test/java/io/grpc/ContextTest.java +++ b/context/src/test/java/io/grpc/ContextTest.java @@ -16,6 +16,7 @@ package io.grpc; +import static com.google.common.truth.TruthJUnit.assume; import static io.grpc.Context.cancellableAncestor; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsInstanceOf.instanceOf; @@ -872,6 +873,20 @@ public String call() { @Test public void storageReturnsNullTest() throws Exception { + // TODO(sergiitk): JDK-8210522 changes the behaviour of Java reflection to filter out + // security-sensitive fields in the java.lang.reflect.Field. This prohibits + // Field.class.getDeclaredFields("modifiers") call we rely on in this test. + // Until we have a good solution for setting a custom storage for testing purposes, + // we'll have to skip this test for JDK >= 11. Ref https://bugs.openjdk.org/browse/JDK-8210522 + double javaVersion; + // Graceful version check. Run the test if the version undetermined. + try { + javaVersion = Double.parseDouble(System.getProperty("java.specification.version", "0")); + } catch (NumberFormatException e) { + javaVersion = 0; + } + assume().that(javaVersion).isLessThan(11); + Class lazyStorageClass = Class.forName("io.grpc.Context$LazyStorage"); Field storage = lazyStorageClass.getDeclaredField("storage"); assertTrue(Modifier.isFinal(storage.getModifiers())); From bd33af2ddee2acab6eafb87a725f784e3afba843 Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Wed, 24 Aug 2022 12:11:08 -0700 Subject: [PATCH 24/72] Update README etc to reference 1.49.0 (#9487) --- README.md | 30 ++++++++++++------------ cronet/README.md | 2 +- documentation/android-channel-builder.md | 4 ++-- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index f906b351ec7..9290082d96b 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,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.48.1/examples) and the -[Android example](https://github.com/grpc/grpc-java/tree/v1.48.1/examples/android) +The [examples](https://github.com/grpc/grpc-java/tree/v1.49.0/examples) and the +[Android example](https://github.com/grpc/grpc-java/tree/v1.49.0/examples/android) are standalone projects that showcase the usage of gRPC. Download @@ -43,18 +43,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`: io.grpc grpc-netty-shaded - 1.48.1 + 1.49.0 runtime io.grpc grpc-protobuf - 1.48.1 + 1.49.0 io.grpc grpc-stub - 1.48.1 + 1.49.0 org.apache.tomcat @@ -66,23 +66,23 @@ 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.48.1' -implementation 'io.grpc:grpc-protobuf:1.48.1' -implementation 'io.grpc:grpc-stub:1.48.1' +runtimeOnly 'io.grpc:grpc-netty-shaded:1.49.0' +implementation 'io.grpc:grpc-protobuf:1.49.0' +implementation 'io.grpc:grpc-stub:1.49.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.48.1' -implementation 'io.grpc:grpc-protobuf-lite:1.48.1' -implementation 'io.grpc:grpc-stub:1.48.1' +implementation 'io.grpc:grpc-okhttp:1.49.0' +implementation 'io.grpc:grpc-protobuf-lite:1.49.0' +implementation 'io.grpc:grpc-stub:1.49.0' compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+ ``` [the JARs]: -https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.48.1 +https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.49.0 Development snapshots are available in [Sonatypes's snapshot repository](https://oss.sonatype.org/content/repositories/snapshots/). @@ -114,7 +114,7 @@ For protobuf-based codegen integrated with the Maven build system, you can use com.google.protobuf:protoc:3.21.1:exe:${os.detected.classifier} grpc-java - io.grpc:protoc-gen-grpc-java:1.48.1:exe:${os.detected.classifier} + io.grpc:protoc-gen-grpc-java:1.49.0:exe:${os.detected.classifier} @@ -144,7 +144,7 @@ protobuf { } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.48.1' + artifact = 'io.grpc:protoc-gen-grpc-java:1.49.0' } } generateProtoTasks { @@ -177,7 +177,7 @@ protobuf { } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.48.1' + artifact = 'io.grpc:protoc-gen-grpc-java:1.49.0' } } generateProtoTasks { diff --git a/cronet/README.md b/cronet/README.md index 0878c251cab..3c767c4e972 100644 --- a/cronet/README.md +++ b/cronet/README.md @@ -26,7 +26,7 @@ In your app module's `build.gradle` file, include a dependency on both `grpc-cro Google Play Services Client Library for Cronet ``` -implementation 'io.grpc:grpc-cronet:1.48.1' +implementation 'io.grpc:grpc-cronet:1.49.0' implementation 'com.google.android.gms:play-services-cronet:16.0.0' ``` diff --git a/documentation/android-channel-builder.md b/documentation/android-channel-builder.md index c32db5f4d76..70149fc9b2c 100644 --- a/documentation/android-channel-builder.md +++ b/documentation/android-channel-builder.md @@ -36,8 +36,8 @@ In your `build.gradle` file, include a dependency on both `grpc-android` and `grpc-okhttp`: ``` -implementation 'io.grpc:grpc-android:1.48.1' -implementation 'io.grpc:grpc-okhttp:1.48.1' +implementation 'io.grpc:grpc-android:1.49.0' +implementation 'io.grpc:grpc-okhttp:1.49.0' ``` You also need permission to access the device's network state in your From a74f82ac26b61fcd3fd3cd0a07ca7d3f5b791b5a Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Wed, 24 Aug 2022 14:44:28 -0700 Subject: [PATCH 25/72] core: Update outlier detection max ejection logic. (#9489) Instead of strictly enforcing the max ejection percentage setting, allow one additional ejection past the maximum. This quarantees at least one ejection and matches Envoy proxy behavior. --- .../util/OutlierDetectionLoadBalancer.java | 19 +++++++++++-------- .../OutlierDetectionLoadBalancerTest.java | 13 ++++++------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java index 068bfb4de6d..12d71ba2152 100644 --- a/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java +++ b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java @@ -654,10 +654,9 @@ void maybeUnejectOutliers(Long detectionTimerStartNanos) { } /** - * How many percent of the addresses would have their subchannels ejected if we proceeded - * with the next ejection. + * How many percent of the addresses have been ejected. */ - double nextEjectionPercentage() { + double ejectionPercentage() { if (trackerMap.isEmpty()) { return 0; } @@ -669,7 +668,7 @@ void maybeUnejectOutliers(Long detectionTimerStartNanos) { ejectedAddresses++; } } - return ((double)(ejectedAddresses + 1) / totalAddresses) * 100; + return ((double)ejectedAddresses / totalAddresses) * 100; } } @@ -733,8 +732,10 @@ public void ejectOutliers(AddressTrackerMap trackerMap, long ejectionTimeNanos) mean - stdev * (config.successRateEjection.stdevFactor / 1000f); for (AddressTracker tracker : trackersWithVolume) { - // If an ejection now would take us past the max configured ejection percentage, stop here. - if (trackerMap.nextEjectionPercentage() > config.maxEjectionPercent) { + // If we are above the max ejection percentage, don't eject any more. This will allow the + // total ejections to go one above the max, but at the same time it assures at least one + // ejection, which the spec calls for. This behavior matches what Envoy proxy does. + if (trackerMap.ejectionPercentage() > config.maxEjectionPercent) { return; } @@ -803,8 +804,10 @@ public void ejectOutliers(AddressTrackerMap trackerMap, long ejectionTimeNanos) // If this address does not have enough volume to be considered, skip to the next one. for (AddressTracker tracker : trackerMap.values()) { - // If an ejection now would take us past the max configured ejection percentage stop here. - if (trackerMap.nextEjectionPercentage() > config.maxEjectionPercent) { + // If we are above the max ejection percentage, don't eject any more. This will allow the + // total ejections to go one above the max, but at the same time it assures at least one + // ejection, which the spec calls for. This behavior matches what Envoy proxy does. + if (trackerMap.ejectionPercentage() > config.maxEjectionPercent) { return; } diff --git a/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java b/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java index cbd32c91904..94426d2c9de 100644 --- a/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java +++ b/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java @@ -574,10 +574,11 @@ public void successRateTwoOutliers() { } /** - * Two outliers. but only one gets ejected because we have reached the max ejection percentage. + * Three outliers, second one ejected even if ejecting it goes above the max ejection percentage, + * as this matches Envoy behavior. The third one should not get ejected. */ @Test - public void successRateTwoOutliers_maxEjectionPercentage() { + public void successRateThreeOutliers_maxEjectionPercentage() { OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() .setMaxEjectionPercent(20) .setSuccessRateEjection( @@ -591,7 +592,8 @@ public void successRateTwoOutliers_maxEjectionPercentage() { generateLoad(ImmutableMap.of( subchannel1, Status.DEADLINE_EXCEEDED, - subchannel2, Status.DEADLINE_EXCEEDED), 7); + subchannel2, Status.DEADLINE_EXCEEDED, + subchannel3, Status.DEADLINE_EXCEEDED), 7); // Move forward in time to a point where the detection timer has fired. forwardTime(config); @@ -603,10 +605,7 @@ public void successRateTwoOutliers_maxEjectionPercentage() { : 0; } - // Even if all subchannels were failing, we should have not ejected more than the configured - // maximum percentage. - assertThat((double) totalEjected / servers.size()).isAtMost( - (double) config.maxEjectionPercent / 100); + assertThat(totalEjected).isEqualTo(2); } From 70bc7470c60bd8b8dcbcd563bafbec7c06dda18a Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Wed, 24 Aug 2022 15:49:01 -0700 Subject: [PATCH 26/72] core: outlier detection to min host request volume (#9490) When doing the failure percentage algorithm, ejections should be skipped if there are not enough addresses that meet the minimum volume requirement. --- .../util/OutlierDetectionLoadBalancer.java | 37 +++++++++------- .../OutlierDetectionLoadBalancerTest.java | 42 +++++++++++++++++-- 2 files changed, 59 insertions(+), 20 deletions(-) diff --git a/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java index 12d71ba2152..d6ada1dbd1d 100644 --- a/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java +++ b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java @@ -713,7 +713,8 @@ static class SuccessRateOutlierEjectionAlgorithm implements OutlierEjectionAlgor public void ejectOutliers(AddressTrackerMap trackerMap, long ejectionTimeNanos) { // Only consider addresses that have the minimum request volume specified in the config. - List trackersWithVolume = trackersWithVolume(trackerMap, config); + List trackersWithVolume = trackersWithVolume(trackerMap, + config.successRateEjection.requestVolume); // If we don't have enough addresses with significant volume then there's nothing to do. if (trackersWithVolume.size() < config.successRateEjection.minimumHosts || trackersWithVolume.size() == 0) { @@ -749,18 +750,6 @@ public void ejectOutliers(AddressTrackerMap trackerMap, long ejectionTimeNanos) } } - /** Returns only the trackers that have the minimum configured volume to be considered. */ - private List trackersWithVolume(AddressTrackerMap trackerMap, - OutlierDetectionLoadBalancerConfig config) { - List trackersWithVolume = new ArrayList<>(); - for (AddressTracker tracker : trackerMap.values()) { - if (tracker.inactiveVolume() >= config.successRateEjection.requestVolume) { - trackersWithVolume.add(tracker); - } - } - return trackersWithVolume; - } - /** Calculates the mean of the given values. */ @VisibleForTesting static double mean(Collection values) { @@ -797,13 +786,17 @@ static class FailurePercentageOutlierEjectionAlgorithm implements OutlierEjectio @Override public void ejectOutliers(AddressTrackerMap trackerMap, long ejectionTimeNanos) { - // If we don't have the minimum amount of addresses the config calls for, then return. - if (trackerMap.size() < config.failurePercentageEjection.minimumHosts) { + // Only consider addresses that have the minimum request volume specified in the config. + List trackersWithVolume = trackersWithVolume(trackerMap, + config.failurePercentageEjection.requestVolume); + // If we don't have enough addresses with significant volume then there's nothing to do. + if (trackersWithVolume.size() < config.failurePercentageEjection.minimumHosts + || trackersWithVolume.size() == 0) { return; } // If this address does not have enough volume to be considered, skip to the next one. - for (AddressTracker tracker : trackerMap.values()) { + for (AddressTracker tracker : trackersWithVolume) { // If we are above the max ejection percentage, don't eject any more. This will allow the // total ejections to go one above the max, but at the same time it assures at least one // ejection, which the spec calls for. This behavior matches what Envoy proxy does. @@ -827,6 +820,18 @@ public void ejectOutliers(AddressTrackerMap trackerMap, long ejectionTimeNanos) } } + /** Returns only the trackers that have the minimum configured volume to be considered. */ + private static List trackersWithVolume(AddressTrackerMap trackerMap, + int volume) { + List trackersWithVolume = new ArrayList<>(); + for (AddressTracker tracker : trackerMap.values()) { + if (tracker.inactiveVolume() >= volume) { + trackersWithVolume.add(tracker); + } + } + return trackersWithVolume; + } + /** Counts how many addresses are in a given address group. */ private static boolean hasSingleAddress(List addressGroups) { int addressCount = 0; diff --git a/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java b/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java index 94426d2c9de..b60c685fa5d 100644 --- a/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java +++ b/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java @@ -496,7 +496,8 @@ public void successRateOneOutlier_notEnoughVolume() { } /** - * The success rate algorithm does not apply if enough addresses have the required volume. + * The success rate algorithm does not apply if we don't have enough addresses that have the + * required volume. */ @Test public void successRateOneOutlier_notEnoughAddressesWithVolume() { @@ -504,13 +505,17 @@ public void successRateOneOutlier_notEnoughAddressesWithVolume() { .setMaxEjectionPercent(50) .setSuccessRateEjection( new SuccessRateEjection.Builder() - .setMinimumHosts(6) // We don't have this many hosts... - .setRequestVolume(10).build()) + .setMinimumHosts(5) + .setRequestVolume(20).build()) .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); - generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); + generateLoad( + ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), + // subchannel2 has only 19 calls which results in success rate not triggering. + ImmutableMap.of(subchannel2, 19), + 7); // Move forward in time to a point where the detection timer has fired. forwardTime(config); @@ -682,6 +687,35 @@ public void failurePercentageOneOutlier_notEnoughVolume() { assertEjectedSubchannels(ImmutableSet.of()); } + /** + * The failure percentage algorithm does not apply if we don't have enough addresses that have the + * required volume. + */ + @Test + public void failurePercentageOneOutlier_notEnoughAddressesWithVolume() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setMaxEjectionPercent(50) + .setFailurePercentageEjection( + new FailurePercentageEjection.Builder() + .setMinimumHosts(5) + .setRequestVolume(20).build()) + .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); + + loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad( + ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), + // subchannel2 has only 19 calls which results in failure percentage not triggering. + ImmutableMap.of(subchannel2, 19), + 7); + + // Move forward in time to a point where the detection timer has fired. + forwardTime(config); + + // No subchannels should have been ejected. + assertEjectedSubchannels(ImmutableSet.of()); + } + /** * The enforcementPercentage configuration should be honored. */ From d3331d953d48071778455dd182c1fff8d6d8845c Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Thu, 25 Aug 2022 10:48:28 -0700 Subject: [PATCH 27/72] core: outlier detection max ejection logic update (#9492) Stop further ejection if the ejection percentage is lesser than or equal to the maximum ejection percentage. Previously this was only done when the current ejection percentage was lesser than the maximum. --- .../util/OutlierDetectionLoadBalancer.java | 18 ++++++++++-------- .../util/OutlierDetectionLoadBalancerTest.java | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java index d6ada1dbd1d..87f9bdce2b3 100644 --- a/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java +++ b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java @@ -733,10 +733,11 @@ public void ejectOutliers(AddressTrackerMap trackerMap, long ejectionTimeNanos) mean - stdev * (config.successRateEjection.stdevFactor / 1000f); for (AddressTracker tracker : trackersWithVolume) { - // If we are above the max ejection percentage, don't eject any more. This will allow the - // total ejections to go one above the max, but at the same time it assures at least one - // ejection, which the spec calls for. This behavior matches what Envoy proxy does. - if (trackerMap.ejectionPercentage() > config.maxEjectionPercent) { + // If we are above or equal to the max ejection percentage, don't eject any more. This will + // allow the total ejections to go one above the max, but at the same time it assures at + // least one ejection, which the spec calls for. This behavior matches what Envoy proxy + // does. + if (trackerMap.ejectionPercentage() >= config.maxEjectionPercent) { return; } @@ -797,10 +798,11 @@ public void ejectOutliers(AddressTrackerMap trackerMap, long ejectionTimeNanos) // If this address does not have enough volume to be considered, skip to the next one. for (AddressTracker tracker : trackersWithVolume) { - // If we are above the max ejection percentage, don't eject any more. This will allow the - // total ejections to go one above the max, but at the same time it assures at least one - // ejection, which the spec calls for. This behavior matches what Envoy proxy does. - if (trackerMap.ejectionPercentage() > config.maxEjectionPercent) { + // If we are above or equal to the max ejection percentage, don't eject any more. This will + // allow the total ejections to go one above the max, but at the same time it assures at + // least one ejection, which the spec calls for. This behavior matches what Envoy proxy + // does. + if (trackerMap.ejectionPercentage() >= config.maxEjectionPercent) { return; } diff --git a/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java b/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java index b60c685fa5d..c11e6328f34 100644 --- a/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java +++ b/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java @@ -585,7 +585,7 @@ public void successRateTwoOutliers() { @Test public void successRateThreeOutliers_maxEjectionPercentage() { OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() - .setMaxEjectionPercent(20) + .setMaxEjectionPercent(30) .setSuccessRateEjection( new SuccessRateEjection.Builder() .setMinimumHosts(3) From 221ee494d97190953e451c341a506efdf30ca7e7 Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Tue, 23 Aug 2022 11:51:30 -0700 Subject: [PATCH 28/72] move max connection idle manager to core --- .../internal}/MaxConnectionIdleManager.java | 39 ++++++++--------- .../MaxConnectionIdleManagerTest.java | 42 ++++++------------- .../io/grpc/netty/NettyServerHandler.java | 23 +++++----- 3 files changed, 42 insertions(+), 62 deletions(-) rename {netty/src/main/java/io/grpc/netty => core/src/main/java/io/grpc/internal}/MaxConnectionIdleManager.java (78%) rename {netty/src/test/java/io/grpc/netty => core/src/test/java/io/grpc/internal}/MaxConnectionIdleManagerTest.java (62%) diff --git a/netty/src/main/java/io/grpc/netty/MaxConnectionIdleManager.java b/core/src/main/java/io/grpc/internal/MaxConnectionIdleManager.java similarity index 78% rename from netty/src/main/java/io/grpc/netty/MaxConnectionIdleManager.java rename to core/src/main/java/io/grpc/internal/MaxConnectionIdleManager.java index 964ae44b178..a6e2a13913b 100644 --- a/netty/src/main/java/io/grpc/netty/MaxConnectionIdleManager.java +++ b/core/src/main/java/io/grpc/internal/MaxConnectionIdleManager.java @@ -14,20 +14,19 @@ * limitations under the License. */ -package io.grpc.netty; +package io.grpc.internal; import com.google.common.annotations.VisibleForTesting; -import io.grpc.internal.LogExceptionRunnable; -import io.netty.channel.ChannelHandlerContext; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; + import javax.annotation.CheckForNull; /** * Monitors connection idle time; shutdowns the connection if the max connection idle is reached. */ -abstract class MaxConnectionIdleManager { +public class MaxConnectionIdleManager { private static final Ticker systemTicker = new Ticker() { @Override public long nanoTime() { @@ -46,23 +45,24 @@ public long nanoTime() { private boolean shutdownDelayed; private boolean isActive; - MaxConnectionIdleManager(long maxConnectionIdleInNanos) { + public MaxConnectionIdleManager(long maxConnectionIdleInNanos) { this(maxConnectionIdleInNanos, systemTicker); } @VisibleForTesting - MaxConnectionIdleManager(long maxConnectionIdleInNanos, Ticker ticker) { + public MaxConnectionIdleManager(long maxConnectionIdleInNanos, Ticker ticker) { this.maxConnectionIdleInNanos = maxConnectionIdleInNanos; this.ticker = ticker; } - /** A {@link NettyServerHandler} was added to the transport. */ - void start(ChannelHandlerContext ctx) { - start(ctx, ctx.executor()); - } - + /** + * Start the initial scheduled shutdown given the transport status reaches max connection idle. + * + * @param closeJob Closes the connection by sending GO_AWAY with status code NO_ERROR and ASCII + * debug data max_idle and then doing the graceful connection termination. + */ @VisibleForTesting - void start(final ChannelHandlerContext ctx, final ScheduledExecutorService scheduler) { + public void start(final Runnable closeJob, final ScheduledExecutorService scheduler) { this.scheduler = scheduler; nextIdleMonitorTime = ticker.nanoTime() + maxConnectionIdleInNanos; @@ -78,7 +78,7 @@ public void run() { } // if isActive, exit. Will schedule a new shutdownFuture once onTransportIdle } else { - close(ctx); + closeJob.run(); shutdownFuture = null; } } @@ -88,20 +88,15 @@ public void run() { scheduler.schedule(shutdownTask, maxConnectionIdleInNanos, TimeUnit.NANOSECONDS); } - /** - * Closes the connection by sending GO_AWAY with status code NO_ERROR and ASCII debug data - * max_idle and then doing the graceful connection termination. - */ - abstract void close(ChannelHandlerContext ctx); /** There are outstanding RPCs on the transport. */ - void onTransportActive() { + public void onTransportActive() { isActive = true; shutdownDelayed = true; } /** There are no outstanding RPCs on the transport. */ - void onTransportIdle() { + public void onTransportIdle() { isActive = false; if (shutdownFuture == null) { return; @@ -116,7 +111,7 @@ void onTransportIdle() { } /** Transport is being terminated. */ - void onTransportTermination() { + public void onTransportTermination() { if (shutdownFuture != null) { shutdownFuture.cancel(false); shutdownFuture = null; @@ -124,7 +119,7 @@ void onTransportTermination() { } @VisibleForTesting - interface Ticker { + public interface Ticker { long nanoTime(); } } diff --git a/netty/src/test/java/io/grpc/netty/MaxConnectionIdleManagerTest.java b/core/src/test/java/io/grpc/internal/MaxConnectionIdleManagerTest.java similarity index 62% rename from netty/src/test/java/io/grpc/netty/MaxConnectionIdleManagerTest.java rename to core/src/test/java/io/grpc/internal/MaxConnectionIdleManagerTest.java index d2ae98980d0..53566054a64 100644 --- a/netty/src/test/java/io/grpc/netty/MaxConnectionIdleManagerTest.java +++ b/core/src/test/java/io/grpc/internal/MaxConnectionIdleManagerTest.java @@ -14,17 +14,11 @@ * limitations under the License. */ -package io.grpc.netty; +package io.grpc.internal; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -import io.grpc.internal.FakeClock; -import io.grpc.netty.MaxConnectionIdleManager.Ticker; -import io.netty.channel.ChannelHandlerContext; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -36,7 +30,7 @@ @RunWith(JUnit4.class) public class MaxConnectionIdleManagerTest { private final FakeClock fakeClock = new FakeClock(); - private final Ticker ticker = new Ticker() { + private final MaxConnectionIdleManager.Ticker ticker = new MaxConnectionIdleManager.Ticker() { @Override public long nanoTime() { return fakeClock.getTicker().read(); @@ -44,7 +38,7 @@ public long nanoTime() { }; @Mock - private ChannelHandlerContext ctx; + private Runnable closure; @Before public void setUp() { @@ -54,21 +48,21 @@ public void setUp() { @Test public void maxIdleReached() { MaxConnectionIdleManager maxConnectionIdleManager = - spy(new TestMaxConnectionIdleManager(123L, ticker)); + new MaxConnectionIdleManager(123L, ticker); - maxConnectionIdleManager.start(ctx, fakeClock.getScheduledExecutorService()); + maxConnectionIdleManager.start(closure, fakeClock.getScheduledExecutorService()); maxConnectionIdleManager.onTransportIdle(); fakeClock.forwardNanos(123L); - verify(maxConnectionIdleManager).close(eq(ctx)); + verify(closure).run(); } @Test public void maxIdleNotReachedAndReached() { MaxConnectionIdleManager maxConnectionIdleManager = - spy(new TestMaxConnectionIdleManager(123L, ticker)); + new MaxConnectionIdleManager(123L, ticker); - maxConnectionIdleManager.start(ctx, fakeClock.getScheduledExecutorService()); + maxConnectionIdleManager.start(closure, fakeClock.getScheduledExecutorService()); maxConnectionIdleManager.onTransportIdle(); fakeClock.forwardNanos(100L); // max idle not reached @@ -79,35 +73,25 @@ public void maxIdleNotReachedAndReached() { maxConnectionIdleManager.onTransportActive(); fakeClock.forwardNanos(100L); - verify(maxConnectionIdleManager, never()).close(any(ChannelHandlerContext.class)); + verify(closure, never()).run(); // max idle reached maxConnectionIdleManager.onTransportIdle(); fakeClock.forwardNanos(123L); - verify(maxConnectionIdleManager).close(eq(ctx)); + verify(closure).run(); } @Test public void shutdownThenMaxIdleReached() { MaxConnectionIdleManager maxConnectionIdleManager = - spy(new TestMaxConnectionIdleManager(123L, ticker)); + new MaxConnectionIdleManager(123L, ticker); - maxConnectionIdleManager.start(ctx, fakeClock.getScheduledExecutorService()); + maxConnectionIdleManager.start(closure, fakeClock.getScheduledExecutorService()); maxConnectionIdleManager.onTransportIdle(); maxConnectionIdleManager.onTransportTermination(); fakeClock.forwardNanos(123L); - verify(maxConnectionIdleManager, never()).close(any(ChannelHandlerContext.class)); - } - - private static class TestMaxConnectionIdleManager extends MaxConnectionIdleManager { - TestMaxConnectionIdleManager(long maxConnectionIdleInNanos, Ticker ticker) { - super(maxConnectionIdleInNanos, ticker); - } - - @Override - void close(ChannelHandlerContext ctx) { - } + verify(closure, never()).run(); } } diff --git a/netty/src/main/java/io/grpc/netty/NettyServerHandler.java b/netty/src/main/java/io/grpc/netty/NettyServerHandler.java index 1dc73fa5e0e..fa21221a0ae 100644 --- a/netty/src/main/java/io/grpc/netty/NettyServerHandler.java +++ b/netty/src/main/java/io/grpc/netty/NettyServerHandler.java @@ -46,6 +46,7 @@ import io.grpc.internal.GrpcUtil; import io.grpc.internal.KeepAliveManager; import io.grpc.internal.LogExceptionRunnable; +import io.grpc.internal.MaxConnectionIdleManager; import io.grpc.internal.ServerTransportListener; import io.grpc.internal.StatsTraceContext; import io.grpc.internal.TransportTracer; @@ -284,16 +285,7 @@ private NettyServerHandler( if (maxConnectionIdleInNanos == MAX_CONNECTION_IDLE_NANOS_DISABLED) { maxConnectionIdleManager = null; } else { - maxConnectionIdleManager = new MaxConnectionIdleManager(maxConnectionIdleInNanos) { - @Override - void close(ChannelHandlerContext ctx) { - if (gracefulShutdown == null) { - gracefulShutdown = new GracefulShutdown("max_idle", null); - gracefulShutdown.start(ctx); - ctx.flush(); - } - } - }; + maxConnectionIdleManager = new MaxConnectionIdleManager(maxConnectionIdleInNanos); } connection.addListener(new Http2ConnectionAdapter() { @@ -364,7 +356,16 @@ public void run() { } if (maxConnectionIdleManager != null) { - maxConnectionIdleManager.start(ctx); + maxConnectionIdleManager.start(new Runnable() { + @Override + public void run() { + if (gracefulShutdown == null) { + gracefulShutdown = new GracefulShutdown("max_idle", null); + gracefulShutdown.start(ctx); + ctx.flush(); + } + } + }, ctx.executor()); } if (keepAliveTimeInNanos != SERVER_KEEPALIVE_TIME_NANOS_DISABLED) { From 96b9fc1be4054a6df8539c4ae4523ae61d148480 Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Thu, 25 Aug 2022 10:47:38 -0700 Subject: [PATCH 29/72] fix --- .../main/java/io/grpc/internal/MaxConnectionIdleManager.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/MaxConnectionIdleManager.java b/core/src/main/java/io/grpc/internal/MaxConnectionIdleManager.java index a6e2a13913b..4d4a36dda01 100644 --- a/core/src/main/java/io/grpc/internal/MaxConnectionIdleManager.java +++ b/core/src/main/java/io/grpc/internal/MaxConnectionIdleManager.java @@ -20,13 +20,12 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; - import javax.annotation.CheckForNull; /** * Monitors connection idle time; shutdowns the connection if the max connection idle is reached. */ -public class MaxConnectionIdleManager { +public final class MaxConnectionIdleManager { private static final Ticker systemTicker = new Ticker() { @Override public long nanoTime() { @@ -61,7 +60,6 @@ public MaxConnectionIdleManager(long maxConnectionIdleInNanos, Ticker ticker) { * @param closeJob Closes the connection by sending GO_AWAY with status code NO_ERROR and ASCII * debug data max_idle and then doing the graceful connection termination. */ - @VisibleForTesting public void start(final Runnable closeJob, final ScheduledExecutorService scheduler) { this.scheduler = scheduler; nextIdleMonitorTime = ticker.nanoTime() + maxConnectionIdleInNanos; From 6131a85196b9281b6699e1307051ac6850cdfbb8 Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Fri, 26 Aug 2022 16:39:54 -0700 Subject: [PATCH 30/72] census,observability: suppress message-events in traces when used by observability (#9485) --- .../io/grpc/census/CensusTracingModule.java | 13 +- .../census/InternalCensusTracingAccessor.java | 24 ++- .../io/grpc/census/CensusModulesTest.java | 4 +- .../CensusTracingNoMessageEventTest.java | 200 ++++++++++++++++++ .../gcp/observability/GcpObservability.java | 4 +- 5 files changed, 236 insertions(+), 9 deletions(-) create mode 100644 census/src/test/java/io/grpc/census/CensusTracingNoMessageEventTest.java diff --git a/census/src/main/java/io/grpc/census/CensusTracingModule.java b/census/src/main/java/io/grpc/census/CensusTracingModule.java index ab56feff045..f413da94388 100644 --- a/census/src/main/java/io/grpc/census/CensusTracingModule.java +++ b/census/src/main/java/io/grpc/census/CensusTracingModule.java @@ -92,9 +92,12 @@ final class CensusTracingModule { final Metadata.Key tracingHeader; private final TracingClientInterceptor clientInterceptor = new TracingClientInterceptor(); private final ServerTracerFactory serverTracerFactory = new ServerTracerFactory(); + private final boolean addMessageEvents; CensusTracingModule( - Tracer censusTracer, final BinaryFormat censusPropagationBinaryFormat) { + Tracer censusTracer, + final BinaryFormat censusPropagationBinaryFormat, + boolean addMessageEvents) { this.censusTracer = checkNotNull(censusTracer, "censusTracer"); checkNotNull(censusPropagationBinaryFormat, "censusPropagationBinaryFormat"); this.tracingHeader = @@ -114,6 +117,7 @@ public SpanContext parseBytes(byte[] serialized) { } } }); + this.addMessageEvents = addMessageEvents; } /** @@ -211,9 +215,12 @@ private static EndSpanOptions createEndSpanOptions( .build(); } - private static void recordMessageEvent( + private void recordMessageEvent( Span span, MessageEvent.Type type, int seqNo, long optionalWireSize, long optionalUncompressedSize) { + if (!addMessageEvents) { + return; + } MessageEvent.Builder eventBuilder = MessageEvent.builder(type, seqNo); if (optionalUncompressedSize != -1) { eventBuilder.setUncompressedMessageSize(optionalUncompressedSize); @@ -282,7 +289,7 @@ void callEnded(io.grpc.Status status) { } } - private static final class ClientTracer extends ClientStreamTracer { + private final class ClientTracer extends ClientStreamTracer { private final Span span; final Metadata.Key tracingHeader; final boolean isSampledToLocalTracing; diff --git a/census/src/main/java/io/grpc/census/InternalCensusTracingAccessor.java b/census/src/main/java/io/grpc/census/InternalCensusTracingAccessor.java index 2df6c5fb4bd..5709d3a6e49 100644 --- a/census/src/main/java/io/grpc/census/InternalCensusTracingAccessor.java +++ b/census/src/main/java/io/grpc/census/InternalCensusTracingAccessor.java @@ -36,10 +36,21 @@ private InternalCensusTracingAccessor() { * Returns a {@link ClientInterceptor} with default tracing implementation. */ public static ClientInterceptor getClientInterceptor() { + return getClientInterceptor(true); + } + + /** + * @param addMessageEvents add message events to Spans + * + * @return a {@link ClientInterceptor} with default tracing implementation. + */ + public static ClientInterceptor getClientInterceptor( + boolean addMessageEvents) { CensusTracingModule censusTracing = new CensusTracingModule( Tracing.getTracer(), - Tracing.getPropagationComponent().getBinaryFormat()); + Tracing.getPropagationComponent().getBinaryFormat(), + addMessageEvents); return censusTracing.getClientInterceptor(); } @@ -47,10 +58,19 @@ public static ClientInterceptor getClientInterceptor() { * Returns a {@link ServerStreamTracer.Factory} with default stats implementation. */ public static ServerStreamTracer.Factory getServerStreamTracerFactory() { + return getServerStreamTracerFactory(true); + } + + /** + * Returns a {@link ServerStreamTracer.Factory} with default stats implementation. + */ + public static ServerStreamTracer.Factory getServerStreamTracerFactory( + boolean addMessageEvents) { CensusTracingModule censusTracing = new CensusTracingModule( Tracing.getTracer(), - Tracing.getPropagationComponent().getBinaryFormat()); + Tracing.getPropagationComponent().getBinaryFormat(), + addMessageEvents); return censusTracing.getServerTracerFactory(); } } diff --git a/census/src/test/java/io/grpc/census/CensusModulesTest.java b/census/src/test/java/io/grpc/census/CensusModulesTest.java index 9382ab84c12..891a4e5e680 100644 --- a/census/src/test/java/io/grpc/census/CensusModulesTest.java +++ b/census/src/test/java/io/grpc/census/CensusModulesTest.java @@ -225,7 +225,7 @@ public void setUp() throws Exception { new CensusStatsModule( tagger, tagCtxSerializer, statsRecorder, fakeClock.getStopwatchSupplier(), true, true, true, false /* real-time */, true); - censusTracing = new CensusTracingModule(tracer, mockTracingPropagationHandler); + censusTracing = new CensusTracingModule(tracer, mockTracingPropagationHandler, true); } @After @@ -1514,7 +1514,7 @@ public Long apply(AggregationData arg) { }); } - private static class CallInfo extends ServerCallInfo { + static class CallInfo extends ServerCallInfo { private final MethodDescriptor methodDescriptor; private final Attributes attributes; private final String authority; diff --git a/census/src/test/java/io/grpc/census/CensusTracingNoMessageEventTest.java b/census/src/test/java/io/grpc/census/CensusTracingNoMessageEventTest.java new file mode 100644 index 00000000000..1bdefe4e749 --- /dev/null +++ b/census/src/test/java/io/grpc/census/CensusTracingNoMessageEventTest.java @@ -0,0 +1,200 @@ +/* + * Copyright 2022 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.census; + +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; + +import io.grpc.Attributes; +import io.grpc.ClientStreamTracer; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.ServerStreamTracer; +import io.grpc.Status; +import io.grpc.census.CensusTracingModule.CallAttemptsTracerFactory; +import io.grpc.internal.testing.StatsTestUtils.FakeStatsRecorder; +import io.grpc.internal.testing.StatsTestUtils.MockableSpan; +import io.grpc.testing.GrpcServerRule; +import io.opencensus.trace.MessageEvent; +import io.opencensus.trace.Span; +import io.opencensus.trace.SpanBuilder; +import io.opencensus.trace.SpanContext; +import io.opencensus.trace.Tracer; +import io.opencensus.trace.propagation.BinaryFormat; +import java.io.InputStream; +import java.util.Random; +import org.junit.After; +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.ArgumentMatchers; +import org.mockito.Captor; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +/** + * Test for {@link CensusTracingModule}. + */ +@RunWith(JUnit4.class) +public class CensusTracingNoMessageEventTest { + private static final ClientStreamTracer.StreamInfo STREAM_INFO = + ClientStreamTracer.StreamInfo.newBuilder().build(); + + 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; + } + }; + + @Rule + public final MockitoRule mocks = MockitoJUnit.rule(); + + private final MethodDescriptor method = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNKNOWN) + .setRequestMarshaller(MARSHALLER) + .setResponseMarshaller(MARSHALLER) + .setFullMethodName("package1.service2/method3") + .build(); + + private final FakeStatsRecorder statsRecorder = new FakeStatsRecorder(); + private final Random random = new Random(1234); + private final Span spyClientSpan = spy(MockableSpan.generateRandomSpan(random)); + private final Span spyAttemptSpan = spy(MockableSpan.generateRandomSpan(random)); + private final SpanContext fakeAttemptSpanContext = spyAttemptSpan.getContext(); + private final Span spyServerSpan = spy(MockableSpan.generateRandomSpan(random)); + private final byte[] binarySpanContext = new byte[]{3, 1, 5}; + private final SpanBuilder spyClientSpanBuilder = spy(new MockableSpan.Builder()); + private final SpanBuilder spyAttemptSpanBuilder = spy(new MockableSpan.Builder()); + private final SpanBuilder spyServerSpanBuilder = spy(new MockableSpan.Builder()); + + @Rule + public final GrpcServerRule grpcServerRule = new GrpcServerRule().directExecutor(); + + @Mock + private Tracer tracer; + @Mock + private BinaryFormat mockTracingPropagationHandler; + + @Captor + private ArgumentCaptor messageEventCaptor; + + private CensusTracingModule censusTracing; + + @Before + public void setUp() throws Exception { + when(spyClientSpanBuilder.startSpan()).thenReturn(spyClientSpan); + when(spyAttemptSpanBuilder.startSpan()).thenReturn(spyAttemptSpan); + when(tracer.spanBuilderWithExplicitParent( + eq("Sent.package1.service2.method3"), ArgumentMatchers.any())) + .thenReturn(spyClientSpanBuilder); + when(tracer.spanBuilderWithExplicitParent( + eq("Attempt.package1.service2.method3"), ArgumentMatchers.any())) + .thenReturn(spyAttemptSpanBuilder); + when(spyServerSpanBuilder.startSpan()).thenReturn(spyServerSpan); + when(tracer.spanBuilderWithRemoteParent(anyString(), ArgumentMatchers.any())) + .thenReturn(spyServerSpanBuilder); + when(mockTracingPropagationHandler.toByteArray(any(SpanContext.class))) + .thenReturn(binarySpanContext); + when(mockTracingPropagationHandler.fromByteArray(any(byte[].class))) + .thenReturn(fakeAttemptSpanContext); + + censusTracing = new CensusTracingModule(tracer, mockTracingPropagationHandler, false); + } + + @After + public void wrapUp() { + assertNull(statsRecorder.pollRecord()); + } + + @Test + public void clientBasicTracingNoMessageEvents() { + CallAttemptsTracerFactory callTracer = + censusTracing.newClientCallTracer(null, method); + Metadata headers = new Metadata(); + ClientStreamTracer clientStreamTracer = callTracer.newClientStreamTracer(STREAM_INFO, headers); + 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, 90); + + clientStreamTracer.streamClosed(Status.OK); + callTracer.callEnded(Status.OK); + + InOrder inOrder = inOrder(spyClientSpan, spyAttemptSpan); + inOrder.verify(spyAttemptSpan, times(0)).addMessageEvent(messageEventCaptor.capture()); + } + + @Test + public void serverBasicTracingNoMessageEvents() { + ServerStreamTracer.Factory tracerFactory = censusTracing.getServerTracerFactory(); + ServerStreamTracer serverStreamTracer = + tracerFactory.newServerStreamTracer(method.getFullMethodName(), new Metadata()); + + serverStreamTracer.serverCallStarted( + new CensusModulesTest.CallInfo<>(method, Attributes.EMPTY, null)); + + serverStreamTracer.outboundMessage(0); + serverStreamTracer.outboundMessageSent(0, 882, -1); + serverStreamTracer.inboundMessage(0); + serverStreamTracer.outboundMessage(1); + serverStreamTracer.outboundMessageSent(1, -1, 27); + serverStreamTracer.inboundMessageRead(0, 255, 90); + + serverStreamTracer.streamClosed(Status.CANCELLED); + + InOrder inOrder = inOrder(spyServerSpan); + inOrder.verify(spyServerSpan, times(0)).addMessageEvent(messageEventCaptor.capture()); + } +} diff --git a/gcp-observability/src/main/java/io/grpc/gcp/observability/GcpObservability.java b/gcp-observability/src/main/java/io/grpc/gcp/observability/GcpObservability.java index 5337a933fed..751ac058cd8 100644 --- a/gcp-observability/src/main/java/io/grpc/gcp/observability/GcpObservability.java +++ b/gcp-observability/src/main/java/io/grpc/gcp/observability/GcpObservability.java @@ -132,8 +132,8 @@ private void setProducer( } if (config.isEnableCloudTracing()) { clientInterceptors.add( - getConditionalInterceptor(InternalCensusTracingAccessor.getClientInterceptor())); - tracerFactories.add(InternalCensusTracingAccessor.getServerStreamTracerFactory()); + getConditionalInterceptor(InternalCensusTracingAccessor.getClientInterceptor(false))); + tracerFactories.add(InternalCensusTracingAccessor.getServerStreamTracerFactory(false)); } InternalGlobalInterceptors.setInterceptorsTracers( From db05295fde7aba58221c2912b4a4fd3c9eee23f5 Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Mon, 29 Aug 2022 15:23:41 -0700 Subject: [PATCH 31/72] core: Enable outlier detection unit test (#9493) Implements a fake load balancer with round robin like behavior in order to test an outlier detection scenario where a subchannel goes from having multiple associated addresses to one. --- .../OutlierDetectionLoadBalancerTest.java | 63 ++++++++++++++++--- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java b/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java index c11e6328f34..4e94b0089fd 100644 --- a/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java +++ b/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java @@ -58,6 +58,7 @@ import io.grpc.util.OutlierDetectionLoadBalancer.OutlierDetectionSubchannel; import io.grpc.util.OutlierDetectionLoadBalancer.SuccessRateOutlierEjectionAlgorithm; import java.net.SocketAddress; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; @@ -67,7 +68,6 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -112,6 +112,13 @@ public LoadBalancer newLoadBalancer(Helper helper) { return mockChildLb; } }; + private final LoadBalancerProvider fakeLbProvider = new StandardLoadBalancerProvider( + "fake_policy") { + @Override + public LoadBalancer newLoadBalancer(Helper helper) { + return new FakeLoadBalancer(helper); + } + }; private final LoadBalancerProvider roundRobinLbProvider = new StandardLoadBalancerProvider( "round_robin") { @Override @@ -877,11 +884,8 @@ public void subchannelUpdateAddress_singleReplacedWithMultiple() { /** * A subchannel with multiple addresses will again become eligible for outlier detection if it * receives an update with a single address. - * - *

TODO: Figure out how to test this scenario, round_robin does not support multiple addresses - * and fails the transition from multiple addresses to single. */ - @Ignore + @Test public void subchannelUpdateAddress_multipleReplacedWithSingle() { OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() .setMaxEjectionPercent(50) @@ -889,11 +893,11 @@ public void subchannelUpdateAddress_multipleReplacedWithSingle() { new FailurePercentageEjection.Builder() .setMinimumHosts(3) .setRequestVolume(10).build()) - .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); + .setChildPolicy(new PolicySelection(fakeLbProvider, null)).build(); loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); - generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 6); // Move forward in time to a point where the detection timer has fired. forwardTime(config); @@ -1110,4 +1114,49 @@ void assertEjectedSubchannels(Set addresses) { .isEqualTo(addresses.contains(entry.getKey())); } } + + /** Round robin like fake load balancer. */ + private static final class FakeLoadBalancer extends LoadBalancer { + private final Helper helper; + + List subchannelList; + int lastPickIndex = -1; + + FakeLoadBalancer(Helper helper) { + this.helper = helper; + } + + @Override + public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { + subchannelList = new ArrayList<>(); + for (EquivalentAddressGroup eag: resolvedAddresses.getAddresses()) { + Subchannel subchannel = helper.createSubchannel(CreateSubchannelArgs.newBuilder() + .setAddresses(eag).build()); + subchannelList.add(subchannel); + subchannel.start(mock(SubchannelStateListener.class)); + deliverSubchannelState(READY); + } + } + + @Override + public void handleNameResolutionError(Status error) { + } + + @Override + public void shutdown() { + } + + void deliverSubchannelState(ConnectivityState state) { + SubchannelPicker picker = new SubchannelPicker() { + @Override + public PickResult pickSubchannel(PickSubchannelArgs args) { + if (lastPickIndex < 0 || lastPickIndex > subchannelList.size() - 1) { + lastPickIndex = 0; + } + return PickResult.withSubchannel(subchannelList.get(lastPickIndex++)); + } + }; + helper.updateBalancingState(state, picker); + } + } } From 7291ad44c6d5d2b614762d4f549008c14eeff312 Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Mon, 29 Aug 2022 16:25:58 -0700 Subject: [PATCH 32/72] okhttp: add max connection idle at OkHttpServer (#9494) --- .../io/grpc/okhttp/OkHttpServerBuilder.java | 28 +++++++ .../io/grpc/okhttp/OkHttpServerTransport.java | 23 ++++++ .../okhttp/OkHttpServerTransportTest.java | 74 +++++++++++++++++-- 3 files changed, 119 insertions(+), 6 deletions(-) diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java index 5928a2e38c1..0e1273c7f23 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java @@ -16,6 +16,8 @@ package io.grpc.okhttp; +import static com.google.common.base.Preconditions.checkArgument; + import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.errorprone.annotations.DoNotCall; @@ -62,6 +64,10 @@ public final class OkHttpServerBuilder extends ForwardingServerBuilder { private static final Logger log = Logger.getLogger(OkHttpServerBuilder.class.getName()); private static final int DEFAULT_FLOW_CONTROL_WINDOW = 65535; + + static final long MAX_CONNECTION_IDLE_NANOS_DISABLED = Long.MAX_VALUE; + private static final long MIN_MAX_CONNECTION_IDLE_NANO = TimeUnit.SECONDS.toNanos(1L); + private static final long AS_LARGE_AS_INFINITE = TimeUnit.DAYS.toNanos(1000L); private static final ObjectPool DEFAULT_TRANSPORT_EXECUTOR_POOL = OkHttpChannelBuilder.DEFAULT_TRANSPORT_EXECUTOR_POOL; @@ -110,6 +116,7 @@ public static OkHttpServerBuilder forPort(SocketAddress address, ServerCredentia int flowControlWindow = DEFAULT_FLOW_CONTROL_WINDOW; int maxInboundMetadataSize = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE; int maxInboundMessageSize = GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE; + long maxConnectionIdleInNanos = MAX_CONNECTION_IDLE_NANOS_DISABLED; @VisibleForTesting OkHttpServerBuilder( @@ -178,6 +185,27 @@ public OkHttpServerBuilder keepAliveTime(long keepAliveTime, TimeUnit timeUnit) return this; } + /** + * Sets a custom max connection idle time, connection being idle for longer than which will be + * gracefully terminated. Idleness duration is defined since the most recent time the number of + * outstanding RPCs became zero or the connection establishment. An unreasonably small value might + * be increased. {@code Long.MAX_VALUE} nano seconds or an unreasonably large value will disable + * max connection idle. + */ + @Override + public OkHttpServerBuilder maxConnectionIdle(long maxConnectionIdle, TimeUnit timeUnit) { + checkArgument(maxConnectionIdle > 0L, "max connection idle must be positive: %s", + maxConnectionIdle); + maxConnectionIdleInNanos = timeUnit.toNanos(maxConnectionIdle); + if (maxConnectionIdleInNanos >= AS_LARGE_AS_INFINITE) { + maxConnectionIdleInNanos = MAX_CONNECTION_IDLE_NANOS_DISABLED; + } + if (maxConnectionIdleInNanos < MIN_MAX_CONNECTION_IDLE_NANO) { + maxConnectionIdleInNanos = MIN_MAX_CONNECTION_IDLE_NANO; + } + return this; + } + /** * Sets a time waiting for read activity after sending a keepalive ping. If the time expires * without any read activity on the connection, the connection is considered dead. An unreasonably diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerTransport.java index 3a4ed86f5da..b2b581d7155 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerTransport.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerTransport.java @@ -16,6 +16,8 @@ package io.grpc.okhttp; +import static io.grpc.okhttp.OkHttpServerBuilder.MAX_CONNECTION_IDLE_NANOS_DISABLED; + import com.google.common.base.Preconditions; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -28,6 +30,7 @@ import io.grpc.Status; import io.grpc.internal.GrpcUtil; import io.grpc.internal.KeepAliveManager; +import io.grpc.internal.MaxConnectionIdleManager; import io.grpc.internal.ObjectPool; import io.grpc.internal.SerializingExecutor; import io.grpc.internal.ServerTransport; @@ -91,6 +94,7 @@ final class OkHttpServerTransport implements ServerTransport, private ScheduledExecutorService scheduledExecutorService; private Attributes attributes; private KeepAliveManager keepAliveManager; + private MaxConnectionIdleManager maxConnectionIdleManager; private final Object lock = new Object(); @GuardedBy("lock") @@ -189,6 +193,11 @@ private void startIo(SerializingExecutor serializingExecutor) { keepAliveManager.onTransportStarted(); } + if (config.maxConnectionIdleNanos != MAX_CONNECTION_IDLE_NANOS_DISABLED) { + maxConnectionIdleManager = new MaxConnectionIdleManager(config.maxConnectionIdleNanos); + maxConnectionIdleManager.start(this::shutdown, scheduledExecutorService); + } + transportExecutor.execute( new FrameHandler(variant.newReader(Okio.buffer(Okio.source(socket)), false))); } catch (Error | IOException | RuntimeException ex) { @@ -311,6 +320,9 @@ private void terminated() { if (keepAliveManager != null) { keepAliveManager.onTransportTermination(); } + if (maxConnectionIdleManager != null) { + maxConnectionIdleManager.onTransportTermination(); + } transportExecutor = config.transportExecutorPool.returnObject(transportExecutor); scheduledExecutorService = config.scheduledExecutorServicePool.returnObject(scheduledExecutorService); @@ -369,6 +381,9 @@ public OutboundFlowController.StreamState[] getActiveStreams() { void streamClosed(int streamId, boolean flush) { synchronized (lock) { streams.remove(streamId); + if (maxConnectionIdleManager != null && streams.isEmpty()) { + maxConnectionIdleManager.onTransportIdle(); + } if (gracefulShutdown && streams.isEmpty()) { frameWriter.close(); } else { @@ -433,6 +448,7 @@ static final class Config { final int flowControlWindow; final int maxInboundMessageSize; final int maxInboundMetadataSize; + final long maxConnectionIdleNanos; public Config( OkHttpServerBuilder builder, @@ -452,6 +468,7 @@ public Config( flowControlWindow = builder.flowControlWindow; maxInboundMessageSize = builder.maxInboundMessageSize; maxInboundMetadataSize = builder.maxInboundMetadataSize; + maxConnectionIdleNanos = builder.maxConnectionIdleInNanos; } } @@ -697,6 +714,9 @@ public void headers(boolean outFinished, authority == null ? null : asciiString(authority), statsTraceCtx, tracer); + if (maxConnectionIdleManager != null && streams.isEmpty()) { + maxConnectionIdleManager.onTransportActive(); + } streams.put(streamId, stream); listener.streamCreated(streamForApp, method, metadata); stream.onStreamAllocated(); @@ -953,6 +973,9 @@ private void respondWithHttpError( synchronized (lock) { Http2ErrorStreamState stream = new Http2ErrorStreamState(streamId, lock, outboundFlow, config.flowControlWindow); + if (maxConnectionIdleManager != null && streams.isEmpty()) { + maxConnectionIdleManager.onTransportActive(); + } streams.put(streamId, stream); if (inFinished) { stream.inboundDataReceived(new Buffer(), 0, true); diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java index 201829d38ab..93e39583fb9 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java @@ -90,6 +90,7 @@ public class OkHttpServerTransportTest { private static final int TIME_OUT_MS = 2000; private static final int INITIAL_WINDOW_SIZE = 65535; + private static final long MAX_CONNECTION_IDLE = TimeUnit.SECONDS.toNanos(1); private MockServerTransportListener mockTransportListener = new MockServerTransportListener(); private ServerTransportListener transportListener @@ -105,10 +106,11 @@ public class OkHttpServerTransportTest { private ExecutorService threadPool = Executors.newCachedThreadPool(); private HandshakerSocketFactory handshakerSocketFactory = mock(HandshakerSocketFactory.class, delegatesTo(new PlaintextHandshakerSocketFactory())); + private final FakeClock fakeClock = new FakeClock(); private OkHttpServerBuilder serverBuilder = new OkHttpServerBuilder(new InetSocketAddress(1234), handshakerSocketFactory) .executor(new FakeClock().getScheduledExecutorService()) // Executor unused - .scheduledExecutorService(new FakeClock().getScheduledExecutorService()) + .scheduledExecutorService(fakeClock.getScheduledExecutorService()) .transportExecutor(new Executor() { @Override public void execute(Runnable runnable) { if (runnable instanceof OkHttpServerTransport.FrameHandler) { @@ -119,7 +121,8 @@ public class OkHttpServerTransportTest { } } }) - .flowControlWindow(INITIAL_WINDOW_SIZE); + .flowControlWindow(INITIAL_WINDOW_SIZE) + .maxConnectionIdle(MAX_CONNECTION_IDLE, TimeUnit.NANOSECONDS); @Rule public final Timeout globalTimeout = Timeout.seconds(10); @@ -146,6 +149,63 @@ public void startThenShutdown() throws Exception { shutdownAndTerminate(/*lastStreamId=*/ 0); } + @Test + public void maxConnectionIdleTimer() throws Exception { + initTransport(); + handshake(); + clientFrameWriter.headers(1, Arrays.asList( + HTTP_SCHEME_HEADER, + METHOD_HEADER, + new Header(Header.TARGET_AUTHORITY, "example.com:80"), + new Header(Header.TARGET_PATH, "/com.example/SimpleService.doit"), + CONTENT_TYPE_HEADER, + TE_HEADER)); + clientFrameWriter.synStream(true, false, 1, -1, Arrays.asList( + new Header("some-client-sent-trailer", "trailer-value"))); + pingPong(); + + MockStreamListener streamListener = mockTransportListener.newStreams.pop(); + assertThat(streamListener.messages.peek()).isNull(); + assertThat(streamListener.halfClosedCalled).isTrue(); + + streamListener.stream.close(Status.OK, new Metadata()); + + List

responseTrailers = Arrays.asList( + new Header(":status", "200"), + CONTENT_TYPE_HEADER, + new Header("grpc-status", "0")); + assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue(); + verify(clientFramesRead) + .headers(false, true, 1, -1, responseTrailers, HeadersMode.HTTP_20_HEADERS); + + fakeClock.forwardNanos(MAX_CONNECTION_IDLE); + fakeClock.forwardNanos(MAX_CONNECTION_IDLE); + verifyGracefulShutdown(1); + } + + @Test + public void maxConnectionIdleTimer_respondWithError() throws Exception { + initTransport(); + handshake(); + + clientFrameWriter.headers(1, Arrays.asList( + HTTP_SCHEME_HEADER, + METHOD_HEADER, + new Header(Header.TARGET_PATH, "/com.example/SimpleService.doit"), + CONTENT_TYPE_HEADER, + TE_HEADER, + new Header("host", "example.com:80"), + new Header("host", "example.com:80"))); + clientFrameWriter.flush(); + + verifyHttpError( + 1, 400, Status.Code.INTERNAL, "Multiple host headers disallowed. RFC7230 section 5.4"); + + fakeClock.forwardNanos(MAX_CONNECTION_IDLE); + fakeClock.forwardNanos(MAX_CONNECTION_IDLE); + verifyGracefulShutdown(1); + } + @Test public void startThenShutdownTwice() throws Exception { initTransport(); @@ -316,7 +376,8 @@ public void activeRpc_delaysShutdownTermination() throws Exception { clientFrameWriter.data(true, 1, requestMessageFrame, (int) requestMessageFrame.size()); pingPong(); - shutdownAndVerifyGraceful(1); + serverTransport.shutdown(); + verifyGracefulShutdown(1); verify(transportListener, never()).transportTerminated(); MockStreamListener streamListener = mockTransportListener.newStreams.pop(); @@ -1038,8 +1099,8 @@ private Metadata metadata(String... keysAndValues) { return metadata; } - private void shutdownAndVerifyGraceful(int lastStreamId) throws IOException { - serverTransport.shutdown(); + private void verifyGracefulShutdown(int lastStreamId) + throws IOException { assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue(); verify(clientFramesRead).goAway(2147483647, ErrorCode.NO_ERROR, ByteString.EMPTY); assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue(); @@ -1052,7 +1113,8 @@ private void shutdownAndVerifyGraceful(int lastStreamId) throws IOException { private void shutdownAndTerminate(int lastStreamId) throws IOException { assertThat(serverTransport.getActiveStreams().length).isEqualTo(0); - shutdownAndVerifyGraceful(lastStreamId); + serverTransport.shutdown(); + verifyGracefulShutdown(lastStreamId); assertThat(clientFrameReader.nextFrame(clientFramesRead)).isFalse(); verify(transportListener, timeout(TIME_OUT_MS)).transportTerminated(); } From b778947ca63893c726795de2553c54b967348cc7 Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Tue, 30 Aug 2022 09:32:52 -0700 Subject: [PATCH 33/72] istio-interop-testing: increase deadline to 5 sec to avoid deadline issue on aarch64 (#9497) --- .../src/test/java/io/grpc/testing/istio/EchoTestServerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/istio-interop-testing/src/test/java/io/grpc/testing/istio/EchoTestServerTest.java b/istio-interop-testing/src/test/java/io/grpc/testing/istio/EchoTestServerTest.java index bc882d15f49..091a300b874 100644 --- a/istio-interop-testing/src/test/java/io/grpc/testing/istio/EchoTestServerTest.java +++ b/istio-interop-testing/src/test/java/io/grpc/testing/istio/EchoTestServerTest.java @@ -206,7 +206,7 @@ public void forwardEchoTest() throws IOException, InterruptedException { ForwardEchoRequest.newBuilder() .setCount(COUNT_OF_REQUESTS_TO_FORWARD) .setQps(100) - .setTimeoutMicros(2000_000L) // 2000 millis + .setTimeoutMicros(5000_000L) // 5000 millis .setUrl("grpc://localhost:" + port2) .addHeaders( Header.newBuilder().setKey("test-key1").setValue("test-value1").build()) From 8dbff5ba6a9d5be331297388a5c28b70be4d9d7d Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Tue, 30 Aug 2022 13:22:07 -0700 Subject: [PATCH 34/72] core: DelayedClientCall should propagate context This was already being done for the Listener, but it was missed for the ClientCall draining itself. That's not too surprising, since very few things should be looking at the context in that path. We don't care too much about this client call case, but if the context _does_ end up mattering it could be painful to debug. Fixes #9478 --- .../io/grpc/internal/DelayedClientCall.java | 4 +-- .../grpc/internal/DelayedClientCallTest.java | 32 +++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/DelayedClientCall.java b/core/src/main/java/io/grpc/internal/DelayedClientCall.java index f529b0d0a0f..42cedddd0e7 100644 --- a/core/src/main/java/io/grpc/internal/DelayedClientCall.java +++ b/core/src/main/java/io/grpc/internal/DelayedClientCall.java @@ -152,9 +152,9 @@ public final Runnable setCall(ClientCall call) { } setRealCall(checkNotNull(call, "call")); } - return new Runnable() { + return new ContextRunnable(context) { @Override - public void run() { + public void runInContext() { drainPendingCalls(); } }; diff --git a/core/src/test/java/io/grpc/internal/DelayedClientCallTest.java b/core/src/test/java/io/grpc/internal/DelayedClientCallTest.java index 7b653f82132..4bed3891788 100644 --- a/core/src/test/java/io/grpc/internal/DelayedClientCallTest.java +++ b/core/src/test/java/io/grpc/internal/DelayedClientCallTest.java @@ -27,7 +27,9 @@ import com.google.common.util.concurrent.MoreExecutors; import io.grpc.ClientCall; import io.grpc.ClientCall.Listener; +import io.grpc.Context; import io.grpc.Deadline; +import io.grpc.ForwardingClientCall.SimpleForwardingClientCall; import io.grpc.ForwardingTestUtil; import io.grpc.Metadata; import io.grpc.Status; @@ -36,6 +38,7 @@ import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicReference; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -191,6 +194,35 @@ public void setCallThenCancel() { verify(listener).onClose(Status.CANCELLED, null); } + @Test + public void delayedCallsRunUnderContext() throws Exception { + Context.Key contextKey = Context.key("foo"); + Object goldenValue = new Object(); + DelayedClientCall delayedClientCall = + Context.current().withValue(contextKey, goldenValue).call(() -> + new DelayedClientCall<>(callExecutor, fakeClock.getScheduledExecutorService(), null)); + AtomicReference readyContext = new AtomicReference<>(); + delayedClientCall.start(new ClientCall.Listener() { + @Override public void onReady() { + readyContext.set(Context.current()); + } + }, new Metadata()); + AtomicReference startContext = new AtomicReference<>(); + Runnable r = delayedClientCall.setCall(new SimpleForwardingClientCall( + mockRealCall) { + @Override public void start(Listener listener, Metadata metadata) { + startContext.set(Context.current()); + listener.onReady(); // Delayed until call finishes draining + assertThat(readyContext.get()).isNull(); + super.start(listener, metadata); + } + }); + assertThat(r).isNotNull(); + r.run(); + assertThat(contextKey.get(startContext.get())).isEqualTo(goldenValue); + assertThat(contextKey.get(readyContext.get())).isEqualTo(goldenValue); + } + private void callMeMaybe(Runnable r) { if (r != null) { r.run(); From 4b4cb0bd3be693cfd74208f53e1baecbb2b858d7 Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Wed, 31 Aug 2022 08:36:50 -0700 Subject: [PATCH 35/72] api,core: Add LoadBalancer.acceptResolvedAddresses() (#9498) Introduces a new acceptResolvedAddresses() method in LoadBalancer that is to ultimately replace the existing handleResolvedAddresses(). The new method allows the LoadBalancer implementation to reject the given addresses by returning false from the method. The long deprecated handleResolvedAddressGroups is also removed. --- api/src/main/java/io/grpc/LoadBalancer.java | 49 ++++---- .../test/java/io/grpc/LoadBalancerTest.java | 54 +++++---- .../AutoConfiguredLoadBalancerFactory.java | 42 +++---- .../io/grpc/internal/ManagedChannelImpl.java | 9 +- .../io/grpc/util/ForwardingLoadBalancer.java | 15 +-- ...AutoConfiguredLoadBalancerFactoryTest.java | 108 +++++++++--------- .../ManagedChannelImplIdlenessTest.java | 3 +- .../grpc/internal/ManagedChannelImplTest.java | 46 +++----- .../ServiceConfigErrorHandlingTest.java | 49 ++++---- .../io/grpc/rls/CachingRlsLbClientTest.java | 4 +- 10 files changed, 183 insertions(+), 196 deletions(-) diff --git a/api/src/main/java/io/grpc/LoadBalancer.java b/api/src/main/java/io/grpc/LoadBalancer.java index 3cef6190087..6469e33b907 100644 --- a/api/src/main/java/io/grpc/LoadBalancer.java +++ b/api/src/main/java/io/grpc/LoadBalancer.java @@ -124,39 +124,46 @@ public abstract class LoadBalancer { * *

Implementations should not modify the given {@code servers}. * - * @param servers the resolved server addresses, never empty. - * @param attributes extra information from naming system. - * @deprecated override {@link #handleResolvedAddresses(ResolvedAddresses) instead} - * @since 1.2.0 + * @param resolvedAddresses the resolved server addresses, attributes, and config. + * @since 1.21.0 */ - @Deprecated - public void handleResolvedAddressGroups( - List servers, - @NameResolver.ResolutionResultAttr Attributes attributes) { + public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { if (recursionCount++ == 0) { - handleResolvedAddresses( - ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(attributes).build()); + // Note that the information about the addresses actually being accepted will be lost + // if you rely on this method for backward compatibility. + acceptResolvedAddresses(resolvedAddresses); } recursionCount = 0; } /** - * Handles newly resolved server groups and metadata attributes from name resolution system. - * {@code servers} contained in {@link EquivalentAddressGroup} should be considered equivalent - * but may be flattened into a single list if needed. + * Accepts newly resolved addresses from the name resolution system. The {@link + * EquivalentAddressGroup} addresses should be considered equivalent but may be flattened into a + * single list if needed. * - *

Implementations should not modify the given {@code servers}. + *

Implementations can choose to reject the given addresses by returning {@code false}. + * + *

Implementations should not modify the given {@code addresses}. * * @param resolvedAddresses the resolved server addresses, attributes, and config. - * @since 1.21.0 + * @return {@code true} if the resolved addresses were accepted. {@code false} if rejected. + * @since 1.49.0 */ - @SuppressWarnings("deprecation") - public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { - if (recursionCount++ == 0) { - handleResolvedAddressGroups( - resolvedAddresses.getAddresses(), resolvedAddresses.getAttributes()); + public boolean acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { + if (resolvedAddresses.getAddresses().isEmpty() + && !canHandleEmptyAddressListFromNameResolution()) { + handleNameResolutionError(Status.UNAVAILABLE.withDescription( + "NameResolver returned no usable address. addrs=" + resolvedAddresses.getAddresses() + + ", attrs=" + resolvedAddresses.getAttributes())); + return false; + } else { + if (recursionCount++ == 0) { + handleResolvedAddresses(resolvedAddresses); + } + recursionCount = 0; + + return true; } - recursionCount = 0; } /** diff --git a/api/src/test/java/io/grpc/LoadBalancerTest.java b/api/src/test/java/io/grpc/LoadBalancerTest.java index be3d10ba2ae..beaf3335e2c 100644 --- a/api/src/test/java/io/grpc/LoadBalancerTest.java +++ b/api/src/test/java/io/grpc/LoadBalancerTest.java @@ -235,13 +235,14 @@ public void createSubchannelArgs_toString() { @Deprecated @Test - public void handleResolvedAddressGroups_delegatesToHandleResolvedAddresses() { + public void handleResolvedAddresses_delegatesToAcceptResolvedAddresses() { final AtomicReference resultCapture = new AtomicReference<>(); LoadBalancer balancer = new LoadBalancer() { @Override - public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { + public boolean acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { resultCapture.set(resolvedAddresses); + return true; } @Override @@ -260,23 +261,22 @@ public void shutdown() { List servers = Arrays.asList( new EquivalentAddressGroup(new SocketAddress(){}), new EquivalentAddressGroup(new SocketAddress(){})); - balancer.handleResolvedAddressGroups(servers, attrs); + ResolvedAddresses addresses = ResolvedAddresses.newBuilder().setAddresses(servers) + .setAttributes(attrs).build(); + balancer.handleResolvedAddresses(addresses); assertThat(resultCapture.get()).isEqualTo( ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(attrs).build()); } @Deprecated @Test - public void handleResolvedAddresses_delegatesToHandleResolvedAddressGroups() { - final AtomicReference> serversCapture = new AtomicReference<>(); - final AtomicReference attrsCapture = new AtomicReference<>(); + public void acceptResolvedAddresses_delegatesToHandleResolvedAddressGroups() { + final AtomicReference addressesCapture = new AtomicReference<>(); LoadBalancer balancer = new LoadBalancer() { @Override - public void handleResolvedAddressGroups( - List servers, Attributes attrs) { - serversCapture.set(servers); - attrsCapture.set(attrs); + public void handleResolvedAddresses(ResolvedAddresses addresses) { + addressesCapture.set(addresses); } @Override @@ -295,25 +295,23 @@ public void shutdown() { List servers = Arrays.asList( new EquivalentAddressGroup(new SocketAddress(){}), new EquivalentAddressGroup(new SocketAddress(){})); - balancer.handleResolvedAddresses( - ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(attrs).build()); - assertThat(serversCapture.get()).isEqualTo(servers); - assertThat(attrsCapture.get()).isEqualTo(attrs); + ResolvedAddresses addresses = ResolvedAddresses.newBuilder().setAddresses(servers) + .setAttributes(attrs).build(); + balancer.handleResolvedAddresses(addresses); + assertThat(addressesCapture.get().getAddresses()).isEqualTo(servers); + assertThat(addressesCapture.get().getAttributes()).isEqualTo(attrs); } @Deprecated @Test - public void handleResolvedAddresses_noInfiniteLoop() { - final List> serversCapture = new ArrayList<>(); - final List attrsCapture = new ArrayList<>(); + public void acceptResolvedAddresses_noInfiniteLoop() { + final List addressesCapture = new ArrayList<>(); LoadBalancer balancer = new LoadBalancer() { @Override - public void handleResolvedAddressGroups( - List servers, Attributes attrs) { - serversCapture.add(servers); - attrsCapture.add(attrs); - super.handleResolvedAddressGroups(servers, attrs); + public void handleResolvedAddresses(ResolvedAddresses addresses) { + addressesCapture.add(addresses); + super.handleResolvedAddresses(addresses); } @Override @@ -328,12 +326,12 @@ public void shutdown() { List servers = Arrays.asList( new EquivalentAddressGroup(new SocketAddress(){}), new EquivalentAddressGroup(new SocketAddress(){})); - balancer.handleResolvedAddresses( - ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(attrs).build()); - assertThat(serversCapture).hasSize(1); - assertThat(attrsCapture).hasSize(1); - assertThat(serversCapture.get(0)).isEqualTo(servers); - assertThat(attrsCapture.get(0)).isEqualTo(attrs); + ResolvedAddresses addresses = ResolvedAddresses.newBuilder().setAddresses(servers) + .setAttributes(attrs).build(); + balancer.handleResolvedAddresses(addresses); + assertThat(addressesCapture).hasSize(1); + assertThat(addressesCapture.get(0).getAddresses()).isEqualTo(servers); + assertThat(addressesCapture.get(0).getAttributes()).isEqualTo(attrs); } private static class NoopHelper extends LoadBalancer.Helper { diff --git a/core/src/main/java/io/grpc/internal/AutoConfiguredLoadBalancerFactory.java b/core/src/main/java/io/grpc/internal/AutoConfiguredLoadBalancerFactory.java index 01c48b9efcf..b8c9cab7459 100644 --- a/core/src/main/java/io/grpc/internal/AutoConfiguredLoadBalancerFactory.java +++ b/core/src/main/java/io/grpc/internal/AutoConfiguredLoadBalancerFactory.java @@ -20,11 +20,9 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; -import io.grpc.Attributes; import io.grpc.ChannelLogger.ChannelLogLevel; import io.grpc.ConnectivityState; import io.grpc.ConnectivityStateInfo; -import io.grpc.EquivalentAddressGroup; import io.grpc.LoadBalancer; import io.grpc.LoadBalancer.Helper; import io.grpc.LoadBalancer.PickResult; @@ -67,10 +65,14 @@ private static final class NoopLoadBalancer extends LoadBalancer { @Override @Deprecated - public void handleResolvedAddressGroups(List s, Attributes a) {} + @SuppressWarnings("InlineMeSuggester") + public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { + } @Override - public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) {} + public boolean acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { + return true; + } @Override public void handleNameResolutionError(Status error) {} @@ -97,14 +99,10 @@ public final class AutoConfiguredLoadBalancer { } /** - * Returns non-OK status if resolvedAddresses is empty and delegate lb requires address ({@link - * LoadBalancer#canHandleEmptyAddressListFromNameResolution()} returns {@code false}). {@code - * AutoConfiguredLoadBalancer} doesn't expose {@code - * canHandleEmptyAddressListFromNameResolution} because it depends on the delegated LB. + * Returns non-OK status if the delegate rejects the resolvedAddresses (e.g. if it does not + * support an empty list). */ - Status tryHandleResolvedAddresses(ResolvedAddresses resolvedAddresses) { - List servers = resolvedAddresses.getAddresses(); - Attributes attributes = resolvedAddresses.getAttributes(); + boolean tryAcceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { PolicySelection policySelection = (PolicySelection) resolvedAddresses.getLoadBalancingPolicyConfig(); @@ -118,7 +116,7 @@ Status tryHandleResolvedAddresses(ResolvedAddresses resolvedAddresses) { delegate.shutdown(); delegateProvider = null; delegate = new NoopLoadBalancer(); - return Status.OK; + return true; } policySelection = new PolicySelection(defaultProvider, /* config= */ null); @@ -141,20 +139,12 @@ Status tryHandleResolvedAddresses(ResolvedAddresses resolvedAddresses) { ChannelLogLevel.DEBUG, "Load-balancing config: {0}", policySelection.config); } - LoadBalancer delegate = getDelegate(); - if (resolvedAddresses.getAddresses().isEmpty() - && !delegate.canHandleEmptyAddressListFromNameResolution()) { - return Status.UNAVAILABLE.withDescription( - "NameResolver returned no usable address. addrs=" + servers + ", attrs=" + attributes); - } else { - delegate.handleResolvedAddresses( - ResolvedAddresses.newBuilder() - .setAddresses(resolvedAddresses.getAddresses()) - .setAttributes(attributes) - .setLoadBalancingPolicyConfig(lbConfig) - .build()); - return Status.OK; - } + return getDelegate().acceptResolvedAddresses( + ResolvedAddresses.newBuilder() + .setAddresses(resolvedAddresses.getAddresses()) + .setAttributes(resolvedAddresses.getAttributes()) + .setLoadBalancingPolicyConfig(lbConfig) + .build()); } void handleNameResolutionError(Status error) { diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java index f7b82d1db17..6e80eb39255 100644 --- a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java +++ b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java @@ -1852,16 +1852,17 @@ public void run() { .set(LoadBalancer.ATTR_HEALTH_CHECKING_CONFIG, healthCheckingConfig) .build(); } + Attributes attributes = attrBuilder.build(); - Status handleResult = helper.lb.tryHandleResolvedAddresses( + boolean addressesAccepted = helper.lb.tryAcceptResolvedAddresses( ResolvedAddresses.newBuilder() .setAddresses(servers) - .setAttributes(attrBuilder.build()) + .setAttributes(attributes) .setLoadBalancingPolicyConfig(effectiveServiceConfig.getLoadBalancingConfig()) .build()); - if (!handleResult.isOk()) { - handleErrorInSyncContext(handleResult.augmentDescription(resolver + " was used")); + if (!addressesAccepted) { + scheduleExponentialBackOffInSyncContext(); } } } diff --git a/core/src/main/java/io/grpc/util/ForwardingLoadBalancer.java b/core/src/main/java/io/grpc/util/ForwardingLoadBalancer.java index 628eda3b71b..516488f0d1a 100644 --- a/core/src/main/java/io/grpc/util/ForwardingLoadBalancer.java +++ b/core/src/main/java/io/grpc/util/ForwardingLoadBalancer.java @@ -17,14 +17,10 @@ package io.grpc.util; import com.google.common.base.MoreObjects; -import io.grpc.Attributes; import io.grpc.ConnectivityStateInfo; -import io.grpc.EquivalentAddressGroup; import io.grpc.ExperimentalApi; import io.grpc.LoadBalancer; -import io.grpc.NameResolver; import io.grpc.Status; -import java.util.List; @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771") public abstract class ForwardingLoadBalancer extends LoadBalancer { @@ -34,16 +30,13 @@ public abstract class ForwardingLoadBalancer extends LoadBalancer { protected abstract LoadBalancer delegate(); @Override - @Deprecated - public void handleResolvedAddressGroups( - List servers, - @NameResolver.ResolutionResultAttr Attributes attributes) { - delegate().handleResolvedAddressGroups(servers, attributes); + public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { + delegate().handleResolvedAddresses(resolvedAddresses); } @Override - public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { - delegate().handleResolvedAddresses(resolvedAddresses); + public boolean acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { + return delegate().acceptResolvedAddresses(resolvedAddresses); } @Override diff --git a/core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest.java b/core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest.java index bf7c63818cf..ad886c31142 100644 --- a/core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest.java +++ b/core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest.java @@ -21,8 +21,8 @@ import static org.mockito.AdditionalAnswers.delegatesTo; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; import static org.mockito.ArgumentMatchers.same; -import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -97,9 +97,8 @@ public class AutoConfiguredLoadBalancerFactoryTest { @Before public void setUp() { - when(testLbBalancer.canHandleEmptyAddressListFromNameResolution()).thenCallRealMethod(); - assertThat(testLbBalancer.canHandleEmptyAddressListFromNameResolution()).isFalse(); - when(testLbBalancer2.canHandleEmptyAddressListFromNameResolution()).thenReturn(true); + when(testLbBalancer.acceptResolvedAddresses(isA(ResolvedAddresses.class))).thenReturn(true); + when(testLbBalancer2.acceptResolvedAddresses(isA(ResolvedAddresses.class))).thenReturn(true); defaultRegistry.register(testLbBalancerProvider); defaultRegistry.register(testLbBalancerProvider2); } @@ -171,7 +170,7 @@ public void shutdown() { } @Test - public void handleResolvedAddressGroups_keepOldBalancer() { + public void acceptResolvedAddresses_keepOldBalancer() { final List servers = Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); Helper helper = new TestHelper() { @@ -184,19 +183,19 @@ public Subchannel createSubchannel(CreateSubchannelArgs args) { AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(helper); LoadBalancer oldDelegate = lb.getDelegate(); - Status handleResult = lb.tryHandleResolvedAddresses( + boolean addressesAccepted = lb.tryAcceptResolvedAddresses( ResolvedAddresses.newBuilder() .setAddresses(servers) .setAttributes(Attributes.EMPTY) .setLoadBalancingPolicyConfig(null) .build()); - assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); + assertThat(addressesAccepted).isTrue(); assertThat(lb.getDelegate()).isSameInstanceAs(oldDelegate); } @Test - public void handleResolvedAddressGroups_shutsDownOldBalancer() throws Exception { + public void acceptResolvedAddresses_shutsDownOldBalancer() throws Exception { Map serviceConfig = parseConfig("{\"loadBalancingConfig\": [ {\"round_robin\": { } } ] }"); ConfigOrError lbConfigs = lbf.parseLoadBalancerPolicy(serviceConfig); @@ -226,13 +225,13 @@ public void shutdown() { }; lb.setDelegate(testlb); - Status handleResult = lb.tryHandleResolvedAddresses( + boolean addressesAccepted = lb.tryAcceptResolvedAddresses( ResolvedAddresses.newBuilder() .setAddresses(servers) .setLoadBalancingPolicyConfig(lbConfigs.getConfig()) .build()); - assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); + assertThat(addressesAccepted).isTrue(); assertThat(lb.getDelegateProvider().getClass().getName()).isEqualTo( "io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider"); assertTrue(shutdown.get()); @@ -240,7 +239,7 @@ public void shutdown() { @Test @SuppressWarnings("unchecked") - public void handleResolvedAddressGroups_propagateLbConfigToDelegate() throws Exception { + public void acceptResolvedAddresses_propagateLbConfigToDelegate() throws Exception { Map rawServiceConfig = parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { \"setting1\": \"high\" } } ] }"); ConfigOrError lbConfigs = lbf.parseLoadBalancerPolicy(rawServiceConfig); @@ -251,20 +250,19 @@ public void handleResolvedAddressGroups_propagateLbConfigToDelegate() throws Exc Helper helper = new TestHelper(); AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(helper); - Status handleResult = lb.tryHandleResolvedAddresses( + boolean addressesAccepted = lb.tryAcceptResolvedAddresses( ResolvedAddresses.newBuilder() .setAddresses(servers) .setLoadBalancingPolicyConfig(lbConfigs.getConfig()) .build()); verify(testLbBalancerProvider).newLoadBalancer(same(helper)); - assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); + assertThat(addressesAccepted).isTrue(); assertThat(lb.getDelegate()).isSameInstanceAs(testLbBalancer); ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(testLbBalancer).handleResolvedAddresses(resultCaptor.capture()); + verify(testLbBalancer).acceptResolvedAddresses(resultCaptor.capture()); assertThat(resultCaptor.getValue().getAddresses()).containsExactlyElementsIn(servers).inOrder(); - verify(testLbBalancer, atLeast(0)).canHandleEmptyAddressListFromNameResolution(); ArgumentCaptor> lbConfigCaptor = ArgumentCaptor.forClass(Map.class); verify(testLbBalancerProvider).parseLoadBalancingPolicyConfig(lbConfigCaptor.capture()); assertThat(lbConfigCaptor.getValue()).containsExactly("setting1", "high"); @@ -274,7 +272,7 @@ public void handleResolvedAddressGroups_propagateLbConfigToDelegate() throws Exc parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { \"setting1\": \"low\" } } ] }"); lbConfigs = lbf.parseLoadBalancerPolicy(rawServiceConfig); - handleResult = lb.tryHandleResolvedAddresses( + addressesAccepted = lb.tryAcceptResolvedAddresses( ResolvedAddresses.newBuilder() .setAddresses(servers) .setLoadBalancingPolicyConfig(lbConfigs.getConfig()) @@ -282,8 +280,8 @@ public void handleResolvedAddressGroups_propagateLbConfigToDelegate() throws Exc resultCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(testLbBalancer, times(2)).handleResolvedAddresses(resultCaptor.capture()); - assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); + verify(testLbBalancer, times(2)).acceptResolvedAddresses(resultCaptor.capture()); + assertThat(addressesAccepted).isTrue(); assertThat(resultCaptor.getValue().getAddresses()).containsExactlyElementsIn(servers).inOrder(); verify(testLbBalancerProvider, times(2)) .parseLoadBalancingPolicyConfig(lbConfigCaptor.capture()); @@ -294,7 +292,7 @@ public void handleResolvedAddressGroups_propagateLbConfigToDelegate() throws Exc } @Test - public void handleResolvedAddressGroups_propagateAddrsToDelegate() throws Exception { + public void acceptResolvedAddresses_propagateAddrsToDelegate() throws Exception { Map rawServiceConfig = parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { \"setting1\": \"high\" } } ] }"); ConfigOrError lbConfigs = lbf.parseLoadBalancerPolicy(rawServiceConfig); @@ -305,56 +303,58 @@ public void handleResolvedAddressGroups_propagateAddrsToDelegate() throws Except List servers = Collections.singletonList(new EquivalentAddressGroup(new InetSocketAddress(8080){})); - Status handleResult = lb.tryHandleResolvedAddresses( + boolean addressesAccepted = lb.tryAcceptResolvedAddresses( ResolvedAddresses.newBuilder() .setAddresses(servers) .setLoadBalancingPolicyConfig(lbConfigs.getConfig()) .build()); verify(testLbBalancerProvider).newLoadBalancer(same(helper)); - assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); + assertThat(addressesAccepted).isTrue(); assertThat(lb.getDelegate()).isSameInstanceAs(testLbBalancer); ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(testLbBalancer).handleResolvedAddresses(resultCaptor.capture()); + verify(testLbBalancer).acceptResolvedAddresses(resultCaptor.capture()); assertThat(resultCaptor.getValue().getAddresses()).containsExactlyElementsIn(servers).inOrder(); servers = Collections.singletonList(new EquivalentAddressGroup(new InetSocketAddress(9090){})); - handleResult = lb.tryHandleResolvedAddresses( + addressesAccepted = lb.tryAcceptResolvedAddresses( ResolvedAddresses.newBuilder() .setAddresses(servers) .setLoadBalancingPolicyConfig(lbConfigs.getConfig()) .build()); - assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); - verify(testLbBalancer, times(2)).handleResolvedAddresses(resultCaptor.capture()); + assertThat(addressesAccepted).isTrue(); + verify(testLbBalancer, times(2)).acceptResolvedAddresses(resultCaptor.capture()); assertThat(resultCaptor.getValue().getAddresses()).containsExactlyElementsIn(servers).inOrder(); } @Test - public void handleResolvedAddressGroups_delegateDoNotAcceptEmptyAddressList_nothing() + public void acceptResolvedAddresses_delegateDoNotAcceptEmptyAddressList_nothing() throws Exception { + + // The test LB will NOT accept the addresses we give them. + when(testLbBalancer.acceptResolvedAddresses(isA(ResolvedAddresses.class))).thenReturn(false); + Helper helper = new TestHelper(); AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(helper); Map serviceConfig = parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { \"setting1\": \"high\" } } ] }"); ConfigOrError lbConfig = lbf.parseLoadBalancerPolicy(serviceConfig); - Status handleResult = lb.tryHandleResolvedAddresses( + boolean addressesAccepted = lb.tryAcceptResolvedAddresses( ResolvedAddresses.newBuilder() .setAddresses(Collections.emptyList()) .setLoadBalancingPolicyConfig(lbConfig.getConfig()) .build()); - assertThat(testLbBalancer.canHandleEmptyAddressListFromNameResolution()).isFalse(); - assertThat(handleResult.getCode()).isEqualTo(Status.Code.UNAVAILABLE); - assertThat(handleResult.getDescription()).startsWith("NameResolver returned no usable address"); + assertThat(addressesAccepted).isFalse(); assertThat(lb.getDelegate()).isSameInstanceAs(testLbBalancer); } @Test - public void handleResolvedAddressGroups_delegateAcceptsEmptyAddressList() + public void acceptResolvedAddresses_delegateAcceptsEmptyAddressList() throws Exception { Helper helper = new TestHelper(); AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(helper); @@ -363,25 +363,24 @@ public void handleResolvedAddressGroups_delegateAcceptsEmptyAddressList() parseConfig("{\"loadBalancingConfig\": [ {\"test_lb2\": { \"setting1\": \"high\" } } ] }"); ConfigOrError lbConfigs = lbf.parseLoadBalancerPolicy(rawServiceConfig); - Status handleResult = lb.tryHandleResolvedAddresses( + boolean addressesAccepted = lb.tryAcceptResolvedAddresses( ResolvedAddresses.newBuilder() .setAddresses(Collections.emptyList()) .setLoadBalancingPolicyConfig(lbConfigs.getConfig()) .build()); - assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); + assertThat(addressesAccepted).isTrue(); assertThat(lb.getDelegate()).isSameInstanceAs(testLbBalancer2); - assertThat(testLbBalancer2.canHandleEmptyAddressListFromNameResolution()).isTrue(); ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(testLbBalancer2).handleResolvedAddresses(resultCaptor.capture()); + verify(testLbBalancer2).acceptResolvedAddresses(resultCaptor.capture()); assertThat(resultCaptor.getValue().getAddresses()).isEmpty(); assertThat(resultCaptor.getValue().getLoadBalancingPolicyConfig()) .isEqualTo(nextParsedConfigOrError2.get().getConfig()); } @Test - public void handleResolvedAddressGroups_useSelectedLbPolicy() throws Exception { + public void acceptResolvedAddresses_useSelectedLbPolicy() throws Exception { Map rawServiceConfig = parseConfig("{\"loadBalancingConfig\": [{\"round_robin\": {}}]}"); ConfigOrError lbConfigs = lbf.parseLoadBalancerPolicy(rawServiceConfig); @@ -399,18 +398,18 @@ public Subchannel createSubchannel(CreateSubchannelArgs args) { } }; AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(helper); - Status handleResult = lb.tryHandleResolvedAddresses( + boolean addressesAccepted = lb.tryAcceptResolvedAddresses( ResolvedAddresses.newBuilder() .setAddresses(servers) .setLoadBalancingPolicyConfig(lbConfigs.getConfig()) .build()); - assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); + assertThat(addressesAccepted).isTrue(); assertThat(lb.getDelegate().getClass().getName()) .isEqualTo("io.grpc.util.RoundRobinLoadBalancer"); } @Test - public void handleResolvedAddressGroups_noLbPolicySelected_defaultToPickFirst() { + public void acceptResolvedAddresses_noLbPolicySelected_defaultToPickFirst() { final List servers = Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); Helper helper = new TestHelper() { @@ -421,27 +420,27 @@ public Subchannel createSubchannel(CreateSubchannelArgs args) { } }; AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(helper); - Status handleResult = lb.tryHandleResolvedAddresses( + boolean addressesAccepted = lb.tryAcceptResolvedAddresses( ResolvedAddresses.newBuilder() .setAddresses(servers) .setLoadBalancingPolicyConfig(null) .build()); - assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); + assertThat(addressesAccepted).isTrue(); assertThat(lb.getDelegate()).isInstanceOf(PickFirstLoadBalancer.class); } @Test - public void handleResolvedAddressGroups_noLbPolicySelected_defaultToCustomDefault() { + public void acceptResolvedAddresses_noLbPolicySelected_defaultToCustomDefault() { AutoConfiguredLoadBalancer lb = new AutoConfiguredLoadBalancerFactory("test_lb") .newLoadBalancer(new TestHelper()); List servers = Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){})); - Status handleResult = lb.tryHandleResolvedAddresses( + boolean addressesAccepted = lb.tryAcceptResolvedAddresses( ResolvedAddresses.newBuilder() .setAddresses(servers) .setLoadBalancingPolicyConfig(null) .build()); - assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); + assertThat(addressesAccepted).isTrue(); assertThat(lb.getDelegate()).isSameInstanceAs(testLbBalancer); } @@ -458,13 +457,13 @@ public Subchannel createSubchannel(CreateSubchannelArgs args) { AutoConfiguredLoadBalancer lb = new AutoConfiguredLoadBalancerFactory(GrpcUtil.DEFAULT_LB_POLICY).newLoadBalancer(helper); - Status handleResult = lb.tryHandleResolvedAddresses( + boolean addressesAccepted = lb.tryAcceptResolvedAddresses( ResolvedAddresses.newBuilder() .setAddresses(servers) .setAttributes(Attributes.EMPTY) .build()); - assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); + assertThat(addressesAccepted).isTrue(); verifyNoMoreInteractions(channelLogger); ConfigOrError testLbParsedConfig = ConfigOrError.fromConfig("foo"); @@ -472,13 +471,13 @@ public Subchannel createSubchannel(CreateSubchannelArgs args) { Map serviceConfig = parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { } } ] }"); ConfigOrError lbConfigs = lbf.parseLoadBalancerPolicy(serviceConfig); - handleResult = lb.tryHandleResolvedAddresses( + addressesAccepted = lb.tryAcceptResolvedAddresses( ResolvedAddresses.newBuilder() .setAddresses(servers) .setLoadBalancingPolicyConfig(lbConfigs.getConfig()) .build()); - assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); + assertThat(addressesAccepted).isTrue(); verify(channelLogger).log( eq(ChannelLogLevel.INFO), eq("Load balancer changed from {0} to {1}"), @@ -495,12 +494,12 @@ public Subchannel createSubchannel(CreateSubchannelArgs args) { nextParsedConfigOrError.set(testLbParsedConfig); serviceConfig = parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { } } ] }"); lbConfigs = lbf.parseLoadBalancerPolicy(serviceConfig); - handleResult = lb.tryHandleResolvedAddresses( + addressesAccepted = lb.tryAcceptResolvedAddresses( ResolvedAddresses.newBuilder() .setAddresses(servers) .setLoadBalancingPolicyConfig(lbConfigs.getConfig()) .build()); - assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK); + assertThat(addressesAccepted).isTrue(); verify(channelLogger).log( eq(ChannelLogLevel.DEBUG), eq("Load-balancing config: {0}"), @@ -643,14 +642,13 @@ protected LoadBalancer delegate() { @Override @Deprecated - public void handleResolvedAddressGroups( - List servers, Attributes attributes) { - delegate().handleResolvedAddressGroups(servers, attributes); + public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { + delegate().acceptResolvedAddresses(resolvedAddresses); } @Override - public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { - delegate().handleResolvedAddresses(resolvedAddresses); + public boolean acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { + return delegate().acceptResolvedAddresses(resolvedAddresses); } @Override diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java index 30e137cba22..90d2d2d8d12 100644 --- a/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java +++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java @@ -154,6 +154,7 @@ public String getPolicyName() { @Before @SuppressWarnings("deprecation") // For NameResolver.Listener public void setUp() { + when(mockLoadBalancer.acceptResolvedAddresses(isA(ResolvedAddresses.class))).thenReturn(true); LoadBalancerRegistry.getDefaultRegistry().register(mockLoadBalancerProvider); when(mockNameResolver.getServiceAuthority()).thenReturn(AUTHORITY); when(mockNameResolverFactory @@ -220,7 +221,7 @@ public void newCallExitsIdleness() throws Exception { ArgumentCaptor resolvedAddressCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer).handleResolvedAddresses(resolvedAddressCaptor.capture()); + verify(mockLoadBalancer).acceptResolvedAddresses(resolvedAddressCaptor.capture()); assertThat(resolvedAddressCaptor.getValue().getAddresses()) .containsExactlyElementsIn(servers); } diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java index e293cb942ea..09be9a9718d 100644 --- a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java +++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java @@ -323,7 +323,7 @@ public void run() { @Before public void setUp() throws Exception { - when(mockLoadBalancer.canHandleEmptyAddressListFromNameResolution()).thenCallRealMethod(); + when(mockLoadBalancer.acceptResolvedAddresses(isA(ResolvedAddresses.class))).thenReturn(true); LoadBalancerRegistry.getDefaultRegistry().register(mockLoadBalancerProvider); expectedUri = new URI(TARGET); transports = TestUtils.captureTransports(mockTransportFactory); @@ -928,7 +928,7 @@ public void noMoreCallbackAfterLoadBalancerShutdown() { FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.get(0); verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class)); - verify(mockLoadBalancer).handleResolvedAddresses(resolvedAddressCaptor.capture()); + verify(mockLoadBalancer).acceptResolvedAddresses(resolvedAddressCaptor.capture()); assertThat(resolvedAddressCaptor.getValue().getAddresses()).containsExactly(addressGroup); SubchannelStateListener stateListener1 = mock(SubchannelStateListener.class); @@ -1143,8 +1143,9 @@ public void nameResolutionFailed_delayedTransportShutdownCancelsBackoff() { } @Test - public void nameResolverReturnsEmptySubLists_becomeErrorByDefault() throws Exception { - String errorDescription = "NameResolver returned no usable address"; + public void nameResolverReturnsEmptySubLists_resolutionRetry() throws Exception { + // The mock LB is set to reject the addresses. + when(mockLoadBalancer.acceptResolvedAddresses(isA(ResolvedAddresses.class))).thenReturn(false); // Pass a FakeNameResolverFactory with an empty list and LB config FakeNameResolverFactory nameResolverFactory = @@ -1157,21 +1158,12 @@ public void nameResolverReturnsEmptySubLists_becomeErrorByDefault() throws Excep channelBuilder.nameResolverFactory(nameResolverFactory); createChannel(); - // LoadBalancer received the error - verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class)); - verify(mockLoadBalancer).handleNameResolutionError(statusCaptor.capture()); - Status status = statusCaptor.getValue(); - assertSame(Status.Code.UNAVAILABLE, status.getCode()); - assertThat(status.getDescription()).startsWith(errorDescription); - // A resolution retry has been scheduled assertEquals(1, timer.numPendingTasks(NAME_RESOLVER_REFRESH_TASK_FILTER)); } @Test public void nameResolverReturnsEmptySubLists_optionallyAllowed() throws Exception { - when(mockLoadBalancer.canHandleEmptyAddressListFromNameResolution()).thenReturn(true); - // Pass a FakeNameResolverFactory with an empty list and LB config FakeNameResolverFactory nameResolverFactory = new FakeNameResolverFactory.Builder(expectedUri).build(); @@ -1193,7 +1185,7 @@ public void nameResolverReturnsEmptySubLists_optionallyAllowed() throws Exceptio verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class)); ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); + verify(mockLoadBalancer).acceptResolvedAddresses(resultCaptor.capture()); assertThat(resultCaptor.getValue().getAddresses()).isEmpty(); assertThat(resultCaptor.getValue().getLoadBalancingPolicyConfig()).isEqualTo(parsedLbConfig); @@ -1214,7 +1206,7 @@ public void loadBalancerThrowsInHandleResolvedAddresses() { createChannel(); verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class)); - doThrow(ex).when(mockLoadBalancer).handleResolvedAddresses(any(ResolvedAddresses.class)); + doThrow(ex).when(mockLoadBalancer).acceptResolvedAddresses(any(ResolvedAddresses.class)); // NameResolver returns addresses. nameResolverFactory.allResolved(); @@ -1276,7 +1268,7 @@ public void firstResolvedServerFailedToConnect() throws Exception { // Simulate name resolution results EquivalentAddressGroup addressGroup = new EquivalentAddressGroup(resolvedAddrs); - inOrder.verify(mockLoadBalancer).handleResolvedAddresses(resolvedAddressCaptor.capture()); + inOrder.verify(mockLoadBalancer).acceptResolvedAddresses(resolvedAddressCaptor.capture()); assertThat(resolvedAddressCaptor.getValue().getAddresses()).containsExactly(addressGroup); Subchannel subchannel = createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); @@ -1426,7 +1418,7 @@ public void allServersFailedToConnect() throws Exception { // Simulate name resolution results EquivalentAddressGroup addressGroup = new EquivalentAddressGroup(resolvedAddrs); - inOrder.verify(mockLoadBalancer).handleResolvedAddresses(resolvedAddressCaptor.capture()); + inOrder.verify(mockLoadBalancer).acceptResolvedAddresses(resolvedAddressCaptor.capture()); assertThat(resolvedAddressCaptor.getValue().getAddresses()).containsExactly(addressGroup); Subchannel subchannel = @@ -3655,7 +3647,7 @@ public double nextDouble() { ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(Helper.class); verify(mockLoadBalancerProvider).newLoadBalancer(helperCaptor.capture()); helper = helperCaptor.getValue(); - verify(mockLoadBalancer).handleResolvedAddresses( + verify(mockLoadBalancer).acceptResolvedAddresses( ResolvedAddresses.newBuilder() .setAddresses(nameResolverFactory.servers) .build()); @@ -3761,7 +3753,7 @@ public void hedgingScheduledThenChannelShutdown_hedgeShouldStillHappen_newCallSh ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(Helper.class); verify(mockLoadBalancerProvider).newLoadBalancer(helperCaptor.capture()); helper = helperCaptor.getValue(); - verify(mockLoadBalancer).handleResolvedAddresses( + verify(mockLoadBalancer).acceptResolvedAddresses( ResolvedAddresses.newBuilder() .setAddresses(nameResolverFactory.servers) .build()); @@ -4088,7 +4080,7 @@ public void disableServiceConfigLookUp_noDefaultConfig() throws Exception { ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); + verify(mockLoadBalancer).acceptResolvedAddresses(resultCaptor.capture()); assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup); assertThat(resultCaptor.getValue().getAttributes().get(InternalConfigSelector.KEY)).isNull(); verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); @@ -4126,7 +4118,7 @@ public void disableServiceConfigLookUp_withDefaultConfig() throws Exception { ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); + verify(mockLoadBalancer).acceptResolvedAddresses(resultCaptor.capture()); assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup); assertThat(resultCaptor.getValue().getAttributes().get(InternalConfigSelector.KEY)).isNull(); verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); @@ -4156,7 +4148,7 @@ public void enableServiceConfigLookUp_noDefaultConfig() throws Exception { createChannel(); ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); + verify(mockLoadBalancer).acceptResolvedAddresses(resultCaptor.capture()); assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup); verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); @@ -4172,7 +4164,7 @@ public void enableServiceConfigLookUp_noDefaultConfig() throws Exception { nameResolverFactory.allResolved(); resultCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer, times(2)).handleResolvedAddresses(resultCaptor.capture()); + verify(mockLoadBalancer, times(2)).acceptResolvedAddresses(resultCaptor.capture()); assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup); verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); } finally { @@ -4206,7 +4198,7 @@ public void enableServiceConfigLookUp_withDefaultConfig() throws Exception { createChannel(); ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); + verify(mockLoadBalancer).acceptResolvedAddresses(resultCaptor.capture()); assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup); verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); } finally { @@ -4234,7 +4226,7 @@ public void enableServiceConfigLookUp_resolverReturnsNoConfig_withDefaultConfig( createChannel(); ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); + verify(mockLoadBalancer).acceptResolvedAddresses(resultCaptor.capture()); assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup); verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); } finally { @@ -4260,7 +4252,7 @@ public void enableServiceConfigLookUp_resolverReturnsNoConfig_noDefaultConfig() createChannel(); ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); + verify(mockLoadBalancer).acceptResolvedAddresses(resultCaptor.capture()); assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup); verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); } finally { @@ -4340,7 +4332,7 @@ public void healthCheckingConfigPropagated() throws Exception { ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); + verify(mockLoadBalancer).acceptResolvedAddresses(resultCaptor.capture()); assertThat(resultCaptor.getValue().getAttributes() .get(LoadBalancer.ATTR_HEALTH_CHECKING_CONFIG)) .containsExactly("serviceName", "service1"); diff --git a/core/src/test/java/io/grpc/internal/ServiceConfigErrorHandlingTest.java b/core/src/test/java/io/grpc/internal/ServiceConfigErrorHandlingTest.java index 6abb477878a..f592ebc9b37 100644 --- a/core/src/test/java/io/grpc/internal/ServiceConfigErrorHandlingTest.java +++ b/core/src/test/java/io/grpc/internal/ServiceConfigErrorHandlingTest.java @@ -192,7 +192,7 @@ public void run() { @Before public void setUp() throws Exception { - when(mockLoadBalancer.canHandleEmptyAddressListFromNameResolution()).thenCallRealMethod(); + mockLoadBalancer.setAcceptAddresses(true); LoadBalancerRegistry.getDefaultRegistry().register(mockLoadBalancerProvider); expectedUri = new URI(TARGET); when(mockTransportFactory.getScheduledExecutorService()) @@ -268,7 +268,7 @@ public void emptyAddresses_validConfig_2ndResolution_lbNeedsAddress() throws Exc ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); + verify(mockLoadBalancer).acceptResolvedAddresses(resultCaptor.capture()); ResolvedAddresses resolvedAddresses = resultCaptor.getValue(); assertThat(resolvedAddresses.getAddresses()).containsExactly(addressGroup); assertThat(resolvedAddresses.getLoadBalancingPolicyConfig()).isEqualTo("12"); @@ -280,19 +280,14 @@ public void emptyAddresses_validConfig_2ndResolution_lbNeedsAddress() throws Exc nameResolverFactory.servers.clear(); // 2nd resolution + mockLoadBalancer.setAcceptAddresses(false); nameResolverFactory.allResolved(); // 2nd service config without addresses - ArgumentCaptor statusCaptor = ArgumentCaptor.forClass(Status.class); - verify(mockLoadBalancer, never()).handleResolvedAddresses(any(ResolvedAddresses.class)); - verify(mockLoadBalancer).handleNameResolutionError(statusCaptor.capture()); - assertThat(statusCaptor.getValue().getCode()).isEqualTo(Status.Code.UNAVAILABLE); - assertThat(statusCaptor.getValue().getDescription()) - .contains("NameResolver returned no usable address."); - assertThat(channel.getState(true)).isEqualTo(ConnectivityState.TRANSIENT_FAILURE); - assertWithMessage("Empty address should schedule NameResolver retry") - .that(getNameResolverRefresh()) - .isNotNull(); + verify(mockLoadBalancer).acceptResolvedAddresses(any(ResolvedAddresses.class)); + + // A resolution retry has been scheduled + assertEquals(1, timer.numPendingTasks(NAME_RESOLVER_REFRESH_TASK_FILTER)); } @Test @@ -302,7 +297,6 @@ public void emptyAddresses_validConfig_lbDoesNotNeedAddress() throws Exception { .setServers(Collections.emptyList()) .build(); channelBuilder.nameResolverFactory(nameResolverFactory); - when(mockLoadBalancer.canHandleEmptyAddressListFromNameResolution()).thenReturn(true); Map rawServiceConfig = parseJson("{\"loadBalancingConfig\": [{\"mock_lb\": {\"check\": \"val\"}}]}"); @@ -312,7 +306,7 @@ public void emptyAddresses_validConfig_lbDoesNotNeedAddress() throws Exception { ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); + verify(mockLoadBalancer).acceptResolvedAddresses(resultCaptor.capture()); ResolvedAddresses resolvedAddresses = resultCaptor.getValue(); assertThat(resolvedAddresses.getAddresses()).isEmpty(); @@ -338,7 +332,7 @@ public void validConfig_lbDoesNotNeedAddress() throws Exception { ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); + verify(mockLoadBalancer).acceptResolvedAddresses(resultCaptor.capture()); ResolvedAddresses resolvedAddresses = resultCaptor.getValue(); assertThat(resolvedAddresses.getAddresses()).containsExactly(addressGroup); assertThat(resolvedAddresses.getLoadBalancingPolicyConfig()).isEqualTo("foo"); @@ -360,7 +354,7 @@ public void noConfig_noDefaultConfig() { ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); + verify(mockLoadBalancer).acceptResolvedAddresses(resultCaptor.capture()); ResolvedAddresses resolvedAddresses = resultCaptor.getValue(); assertThat(resolvedAddresses.getAddresses()).containsExactly(addressGroup); assertThat(resolvedAddresses.getLoadBalancingPolicyConfig()).isNull(); @@ -386,7 +380,7 @@ public void noConfig_usingDefaultConfig() throws Exception { ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); + verify(mockLoadBalancer).acceptResolvedAddresses(resultCaptor.capture()); ResolvedAddresses resolvedAddresses = resultCaptor.getValue(); assertThat(resolvedAddresses.getAddresses()).containsExactly(addressGroup); assertThat(resolvedAddresses.getLoadBalancingPolicyConfig()).isEqualTo("foo"); @@ -431,7 +425,7 @@ public void invalidConfig_withDefaultConfig() throws Exception { ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); + verify(mockLoadBalancer).acceptResolvedAddresses(resultCaptor.capture()); ResolvedAddresses resolvedAddresses = resultCaptor.getValue(); assertThat(resolvedAddresses.getAddresses()).containsExactly(addressGroup); @@ -462,7 +456,7 @@ public void invalidConfig_2ndResolution() throws Exception { ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); + verify(mockLoadBalancer).acceptResolvedAddresses(resultCaptor.capture()); ResolvedAddresses resolvedAddresses = resultCaptor.getValue(); assertThat(resolvedAddresses.getAddresses()).containsExactly(addressGroup); assertThat(resolvedAddresses.getLoadBalancingPolicyConfig()).isEqualTo("1st raw config"); @@ -477,7 +471,7 @@ public void invalidConfig_2ndResolution() throws Exception { nextLbPolicyConfigError.set(Status.UNKNOWN); nameResolverFactory.allResolved(); - verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); + verify(mockLoadBalancer).acceptResolvedAddresses(resultCaptor.capture()); ResolvedAddresses newResolvedAddress = resultCaptor.getValue(); // should use previous service config because new service config is invalid. assertThat(newResolvedAddress.getLoadBalancingPolicyConfig()).isEqualTo("1st raw config"); @@ -510,7 +504,7 @@ public void validConfig_thenNoConfig_withDefaultConfig() throws Exception { ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class); - verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); + verify(mockLoadBalancer).acceptResolvedAddresses(resultCaptor.capture()); ResolvedAddresses resolvedAddresses = resultCaptor.getValue(); assertThat(resolvedAddresses.getAddresses()).containsExactly(addressGroup); // should use previous service config because new resolution result is no config. @@ -522,7 +516,7 @@ public void validConfig_thenNoConfig_withDefaultConfig() throws Exception { nameResolverFactory.nextRawServiceConfig.set(null); nameResolverFactory.allResolved(); - verify(mockLoadBalancer, times(2)).handleResolvedAddresses(resultCaptor.capture()); + verify(mockLoadBalancer, times(2)).acceptResolvedAddresses(resultCaptor.capture()); ResolvedAddresses newResolvedAddress = resultCaptor.getValue(); assertThat(newResolvedAddress.getLoadBalancingPolicyConfig()).isEqualTo("mate"); assertThat(newResolvedAddress.getAttributes().get(InternalConfigSelector.KEY)) @@ -658,6 +652,8 @@ private FakeClock.ScheduledTask getNameResolverRefresh() { private static class FakeLoadBalancer extends LoadBalancer { + private boolean acceptAddresses = true; + @Nullable private Helper helper; @@ -665,6 +661,15 @@ public void setHelper(Helper helper) { this.helper = helper; } + @Override + public boolean acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { + return acceptAddresses; + } + + public void setAcceptAddresses(boolean acceptAddresses) { + this.acceptAddresses = acceptAddresses; + } + @Override public void handleNameResolutionError(final Status error) { helper.updateBalancingState(ConnectivityState.TRANSIENT_FAILURE, diff --git a/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java b/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java index 9ac27532ad4..a6e7b7d80c7 100644 --- a/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java +++ b/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java @@ -568,7 +568,7 @@ public LoadBalancer newLoadBalancer(final Helper helper) { LoadBalancer loadBalancer = new LoadBalancer() { @Override - public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { + public boolean acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { Map config = (Map) resolvedAddresses.getLoadBalancingPolicyConfig(); if (DEFAULT_TARGET.equals(config.get("target"))) { helper.updateBalancingState( @@ -590,6 +590,8 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { } }); } + + return true; } @Override From 1f33fe63836f7753b4fe0ed7d244af66453a1f41 Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Wed, 31 Aug 2022 10:43:22 -0700 Subject: [PATCH 36/72] core: Assure that context cancellationCause is set (#9501) core: Assure that context cancellationCause is set Makes sure that whenever a context is in a cancelled state, we also have a cancellationCause. --- api/src/main/java/io/grpc/InternalStatus.java | 12 ++++++++++++ .../main/java/io/grpc/internal/ServerCallImpl.java | 10 +++++++--- core/src/main/java/io/grpc/internal/ServerImpl.java | 12 +++++++++++- .../java/io/grpc/internal/ServerCallImplTest.java | 3 ++- .../test/java/io/grpc/internal/ServerImplTest.java | 9 ++++++--- 5 files changed, 38 insertions(+), 8 deletions(-) diff --git a/api/src/main/java/io/grpc/InternalStatus.java b/api/src/main/java/io/grpc/InternalStatus.java index 9f6854a2de2..b6549bb435f 100644 --- a/api/src/main/java/io/grpc/InternalStatus.java +++ b/api/src/main/java/io/grpc/InternalStatus.java @@ -16,6 +16,8 @@ package io.grpc; +import javax.annotation.Nullable; + /** * Accesses internal data. Do not use this. */ @@ -34,4 +36,14 @@ private InternalStatus() {} */ @Internal public static final Metadata.Key CODE_KEY = Status.CODE_KEY; + + /** + * Create a new {@link StatusRuntimeException} with the internal option of skipping the filling + * of the stack trace. + */ + @Internal + public static final StatusRuntimeException asRuntimeException(Status status, + @Nullable Metadata trailers, boolean fillInStackTrace) { + return new StatusRuntimeException(status, trailers, fillInStackTrace); + } } diff --git a/core/src/main/java/io/grpc/internal/ServerCallImpl.java b/core/src/main/java/io/grpc/internal/ServerCallImpl.java index 0b680715294..47ffdf9caaf 100644 --- a/core/src/main/java/io/grpc/internal/ServerCallImpl.java +++ b/core/src/main/java/io/grpc/internal/ServerCallImpl.java @@ -35,6 +35,7 @@ import io.grpc.Context; import io.grpc.DecompressorRegistry; import io.grpc.InternalDecompressorRegistry; +import io.grpc.InternalStatus; import io.grpc.Metadata; import io.grpc.MethodDescriptor; import io.grpc.SecurityLevel; @@ -368,19 +369,22 @@ public void closed(Status status) { } private void closedInternal(Status status) { + Throwable cancelCause = null; try { if (status.isOk()) { listener.onComplete(); } else { call.cancelled = true; listener.onCancel(); + // The status will not have a cause in all failure scenarios but we want to make sure + // we always cancel the context with one to keep the context cancelled state consistent. + cancelCause = InternalStatus.asRuntimeException( + Status.CANCELLED.withDescription("RPC cancelled"), null, false); } } finally { // Cancel context after delivering RPC closure notification to allow the application to // clean up and update any state based on whether onComplete or onCancel was called. - // Note that in failure situations JumpToApplicationThreadServerStreamListener has already - // closed the context. In these situations this cancel() call will be a no-op. - context.cancel(null); + context.cancel(cancelCause); } } diff --git a/core/src/main/java/io/grpc/internal/ServerImpl.java b/core/src/main/java/io/grpc/internal/ServerImpl.java index 6bfe2d38ab3..bbd52c14bba 100644 --- a/core/src/main/java/io/grpc/internal/ServerImpl.java +++ b/core/src/main/java/io/grpc/internal/ServerImpl.java @@ -45,6 +45,7 @@ import io.grpc.InternalInstrumented; import io.grpc.InternalLogId; import io.grpc.InternalServerInterceptors; +import io.grpc.InternalStatus; import io.grpc.Metadata; import io.grpc.ServerCall; import io.grpc.ServerCallExecutorSupplier; @@ -894,9 +895,18 @@ private void closedInternal(final Status status) { // For cancellations, promptly inform any users of the context that their work should be // aborted. Otherwise, we can wait until pending work is done. if (!status.isOk()) { + // Since status was not OK we know that the call did not complete and got cancelled. To + // reflect this on the context we need to close it with a cause exception. Since not every + // failed status has an exception we will create one here if needed. + Throwable cancelCause = status.getCause(); + if (cancelCause == null) { + cancelCause = InternalStatus.asRuntimeException( + Status.CANCELLED.withDescription("RPC cancelled"), null, false); + } + // The callExecutor might be busy doing user work. To avoid waiting, use an executor that // is not serializing. - cancelExecutor.execute(new ContextCloser(context, status.getCause())); + cancelExecutor.execute(new ContextCloser(context, cancelCause)); } final Link link = PerfMark.linkOut(); diff --git a/core/src/test/java/io/grpc/internal/ServerCallImplTest.java b/core/src/test/java/io/grpc/internal/ServerCallImplTest.java index e8087ed2aa6..4818c6c2017 100644 --- a/core/src/test/java/io/grpc/internal/ServerCallImplTest.java +++ b/core/src/test/java/io/grpc/internal/ServerCallImplTest.java @@ -20,6 +20,7 @@ import static io.grpc.internal.GrpcUtil.CONTENT_LENGTH_KEY; 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.Assert.assertTrue; import static org.junit.Assert.fail; @@ -424,7 +425,7 @@ public void streamListener_closedCancelled() { verify(callListener).onCancel(); assertTrue(context.isCancelled()); - assertNull(context.cancellationCause()); + assertNotNull(context.cancellationCause()); } @Test diff --git a/core/src/test/java/io/grpc/internal/ServerImplTest.java b/core/src/test/java/io/grpc/internal/ServerImplTest.java index 0f5c510f97c..d3c07787b60 100644 --- a/core/src/test/java/io/grpc/internal/ServerImplTest.java +++ b/core/src/test/java/io/grpc/internal/ServerImplTest.java @@ -130,7 +130,7 @@ public class ServerImplTest { private static final Context.Key SERVER_TRACER_ADDED_KEY = Context.key("tracer-added"); private static final Context.CancellableContext SERVER_CONTEXT = Context.ROOT.withValue(SERVER_ONLY, "yes").withCancellation(); - private static final FakeClock.TaskFilter CONTEXT_CLOSER_TASK_FITLER = + private static final FakeClock.TaskFilter CONTEXT_CLOSER_TASK_FILTER = new FakeClock.TaskFilter() { @Override public boolean shouldAccept(Runnable runnable) { @@ -1085,7 +1085,7 @@ private void checkContext() { assertTrue(onHalfCloseCalled.get()); streamListener.closed(Status.CANCELLED); - assertEquals(1, executor.numPendingTasks(CONTEXT_CLOSER_TASK_FITLER)); + assertEquals(1, executor.numPendingTasks(CONTEXT_CLOSER_TASK_FILTER)); assertEquals(2, executor.runDueTasks()); assertTrue(onCancelCalled.get()); @@ -1179,10 +1179,11 @@ public void testStreamClose_clientCancelTriggersImmediateCancellation() throws E assertFalse(callReference.get().isCancelled()); assertFalse(context.get().isCancelled()); streamListener.closed(Status.CANCELLED); - assertEquals(1, executor.numPendingTasks(CONTEXT_CLOSER_TASK_FITLER)); + assertEquals(1, executor.numPendingTasks(CONTEXT_CLOSER_TASK_FILTER)); assertEquals(2, executor.runDueTasks()); assertTrue(callReference.get().isCancelled()); assertTrue(context.get().isCancelled()); + assertThat(context.get().cancellationCause()).isNotNull(); assertTrue(contextCancelled.get()); } @@ -1208,6 +1209,7 @@ public void testStreamClose_clientOkTriggersDelayedCancellation() throws Excepti assertEquals(1, executor.runDueTasks()); assertFalse(callReference.get().isCancelled()); assertTrue(context.get().isCancelled()); + assertThat(context.get().cancellationCause()).isNull(); assertTrue(contextCancelled.get()); } @@ -1228,6 +1230,7 @@ public void testStreamClose_deadlineExceededTriggersImmediateCancellation() thro assertTrue(callReference.get().isCancelled()); assertTrue(context.get().isCancelled()); + assertThat(context.get().cancellationCause()).isNotNull(); assertTrue(contextCancelled.get()); } From 0a699ad694616de39ad5a471e6de17aea25f70d1 Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Wed, 31 Aug 2022 16:02:27 -0700 Subject: [PATCH 37/72] netty: upgrade netty from 4.1.77.Final to 4.1.29.Final and tcnative from 2.0.53 to 2.0.54 (#9451) --- SECURITY.md | 3 ++- examples/example-tls/pom.xml | 2 +- gradle/libs.versions.toml | 4 ++-- repositories.bzl | 28 ++++++++++++++-------------- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index b78850f0804..1d36eb90103 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -411,7 +411,8 @@ grpc-netty version | netty-handler version | netty-tcnative-boringssl-static ver 1.35.x-1.41.x | 4.1.52.Final | 2.0.34.Final 1.42.x-1.43.x | 4.1.63.Final | 2.0.38.Final 1.44.x-1.47.x | 4.1.72.Final | 2.0.46.Final -1.48.x- | 4.1.77.Final | 2.0.53.Final +1.48.x-1.49.x | 4.1.77.Final | 2.0.53.Final +1.50.x- | 4.1.79.Final | 2.0.54.Final _(grpc-netty-shaded avoids issues with keeping these versions in sync.)_ diff --git a/examples/example-tls/pom.xml b/examples/example-tls/pom.xml index 3c9fe14d4fd..42b6d2d4594 100644 --- a/examples/example-tls/pom.xml +++ b/examples/example-tls/pom.xml @@ -14,7 +14,7 @@ UTF-8 1.50.0-SNAPSHOT 3.21.1 - 2.0.53.Final + 2.0.54.Final 1.7 1.7 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c053c9d1bcc..3351eba9cb7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,8 +5,8 @@ autovalue = "1.9" checkstyle = "6.17" googleauth = "1.4.0" guava = "31.1-android" -netty = '4.1.77.Final' -nettytcnative = '2.0.53.Final' +netty = '4.1.79.Final' +nettytcnative = '2.0.54.Final' opencensus = "0.31.0" protobuf = "3.21.1" diff --git a/repositories.bzl b/repositories.bzl index bb9170a842b..cdf6d69e23e 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -26,20 +26,20 @@ IO_GRPC_GRPC_JAVA_ARTIFACTS = [ "com.google.truth:truth:1.0.1", "com.squareup.okhttp:okhttp:2.7.5", "com.squareup.okio:okio:1.17.5", - "io.netty:netty-buffer:4.1.77.Final", - "io.netty:netty-codec-http2:4.1.77.Final", - "io.netty:netty-codec-http:4.1.77.Final", - "io.netty:netty-codec-socks:4.1.77.Final", - "io.netty:netty-codec:4.1.77.Final", - "io.netty:netty-common:4.1.77.Final", - "io.netty:netty-handler-proxy:4.1.77.Final", - "io.netty:netty-handler:4.1.77.Final", - "io.netty:netty-resolver:4.1.77.Final", - "io.netty:netty-tcnative-boringssl-static:2.0.53.Final", - "io.netty:netty-tcnative-classes:2.0.53.Final", - "io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.77.Final", - "io.netty:netty-transport-native-unix-common:4.1.72.Final", - "io.netty:netty-transport:4.1.77.Final", + "io.netty:netty-buffer:4.1.79.Final", + "io.netty:netty-codec-http2:4.1.79.Final", + "io.netty:netty-codec-http:4.1.79.Final", + "io.netty:netty-codec-socks:4.1.79.Final", + "io.netty:netty-codec:4.1.79.Final", + "io.netty:netty-common:4.1.79.Final", + "io.netty:netty-handler-proxy:4.1.79.Final", + "io.netty:netty-handler:4.1.79.Final", + "io.netty:netty-resolver:4.1.79.Final", + "io.netty:netty-tcnative-boringssl-static:2.0.54.Final", + "io.netty:netty-tcnative-classes:2.0.54.Final", + "io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.79.Final", + "io.netty:netty-transport-native-unix-common:4.1.79.Final", + "io.netty:netty-transport:4.1.79.Final", "io.opencensus:opencensus-api:0.24.0", "io.opencensus:opencensus-contrib-grpc-metrics:0.24.0", "io.perfmark:perfmark-api:0.25.0", From c27f2d5a5713d9d4fb119e17fc0d4ed1349be0c2 Mon Sep 17 00:00:00 2001 From: Larry Safran <107004254+larry-safran@users.noreply.github.com> Date: Fri, 2 Sep 2022 21:55:38 -0700 Subject: [PATCH 38/72] [core,api,auth: Choose executor based on need for thread affinity (#9504) * core,api,auth: Choose the callOptions executor when applying request metadata to credentials during newStream based upon whether AppEngineCredentials are being used as they require a specific thread to do the processing. Add an interface to differentiate whether the specific thread is needed. Fixes b/244209681 --- .../InternalMayRequireSpecificExecutor.java | 22 +++++++++++ .../GoogleAuthLibraryCallCredentials.java | 38 ++++++++++++++++++- ...llCredentialsApplyingTransportFactory.java | 19 +++++++++- 3 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 api/src/main/java/io/grpc/InternalMayRequireSpecificExecutor.java diff --git a/api/src/main/java/io/grpc/InternalMayRequireSpecificExecutor.java b/api/src/main/java/io/grpc/InternalMayRequireSpecificExecutor.java new file mode 100644 index 00000000000..0be807ce9dc --- /dev/null +++ b/api/src/main/java/io/grpc/InternalMayRequireSpecificExecutor.java @@ -0,0 +1,22 @@ +/* + * Copyright 2022 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 +public interface InternalMayRequireSpecificExecutor { + boolean isSpecificExecutorRequired(); +} diff --git a/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java b/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java index d01109249a4..edd73e9845d 100644 --- a/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java +++ b/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java @@ -22,6 +22,7 @@ import com.google.auth.RequestMetadataCallback; import com.google.common.annotations.VisibleForTesting; import com.google.common.io.BaseEncoding; +import io.grpc.InternalMayRequireSpecificExecutor; import io.grpc.Metadata; import io.grpc.MethodDescriptor; import io.grpc.SecurityLevel; @@ -44,13 +45,16 @@ /** * Wraps {@link Credentials} as a {@link io.grpc.CallCredentials}. */ -final class GoogleAuthLibraryCallCredentials extends io.grpc.CallCredentials { +final class GoogleAuthLibraryCallCredentials extends io.grpc.CallCredentials + implements InternalMayRequireSpecificExecutor { private static final Logger log = Logger.getLogger(GoogleAuthLibraryCallCredentials.class.getName()); private static final JwtHelper jwtHelper = createJwtHelperOrNull(GoogleAuthLibraryCallCredentials.class.getClassLoader()); private static final Class googleCredentialsClass = loadGoogleCredentialsClass(); + private static final Class appEngineCredentialsClass + = loadAppEngineCredentials(); private final boolean requirePrivacy; @VisibleForTesting @@ -59,6 +63,8 @@ final class GoogleAuthLibraryCallCredentials extends io.grpc.CallCredentials { private Metadata lastHeaders; private Map> lastMetadata; + private Boolean requiresSpecificExecutor; + public GoogleAuthLibraryCallCredentials(Credentials creds) { this(creds, jwtHelper); } @@ -242,6 +248,16 @@ private static Class loadGoogleCredentialsClass() { return rawGoogleCredentialsClass.asSubclass(Credentials.class); } + @Nullable + private static Class loadAppEngineCredentials() { + try { + return Class.forName("com.google.auth.appengine.AppEngineCredentials"); + } catch (ClassNotFoundException ex) { + log.log(Level.FINE, "AppEngineCredentials not available in classloader", ex); + return null; + } + } + private static class MethodPair { private final Method getter; private final Method builderSetter; @@ -353,4 +369,24 @@ public Credentials tryServiceAccountToJwt(Credentials creds) { return creds; } } + + /** + * This method is to support the hack for AppEngineCredentials which need to run on a + * specific thread. + * @return Whether a specific executor is needed or if any executor can be used + */ + @Override + public boolean isSpecificExecutorRequired() { + // Cache the value so we only need to try to load the class once + if (requiresSpecificExecutor == null) { + if (appEngineCredentialsClass == null) { + requiresSpecificExecutor = Boolean.FALSE; + } else { + requiresSpecificExecutor = appEngineCredentialsClass.isInstance(creds); + } + } + + return requiresSpecificExecutor; + } + } diff --git a/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java b/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java index 04409558bea..e008302d7ae 100644 --- a/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java +++ b/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 The gRPC Authors + * Copyright 2016,2022 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. @@ -27,6 +27,7 @@ import io.grpc.ChannelLogger; import io.grpc.ClientStreamTracer; import io.grpc.CompositeCallCredentials; +import io.grpc.InternalMayRequireSpecificExecutor; import io.grpc.Metadata; import io.grpc.MethodDescriptor; import io.grpc.SecurityLevel; @@ -144,7 +145,21 @@ public Attributes getTransportAttrs() { } }; try { - creds.applyRequestMetadata(requestInfo, appExecutor, applier); + // Hack to allow appengine to work when using AppEngineCredentials (b/244209681) + // since processing must happen on a specific thread. + // + // Ideally would always use appExecutor and we could eliminate the interface + // InternalMayRequireSpecificExecutor + Executor executor; + if (creds instanceof InternalMayRequireSpecificExecutor + && ((InternalMayRequireSpecificExecutor)creds).isSpecificExecutorRequired() + && callOptions.getExecutor() != null) { + executor = callOptions.getExecutor(); + } else { + executor = appExecutor; + } + + creds.applyRequestMetadata(requestInfo, executor, applier); } catch (Throwable t) { applier.fail(Status.UNAUTHENTICATED .withDescription("Credentials should use fail() instead of throwing exceptions") From c8c3d3d6b23f3bc9e6c84a30b6c811d38251f3e1 Mon Sep 17 00:00:00 2001 From: Sergii Tkachenko Date: Tue, 6 Sep 2022 11:18:27 -0700 Subject: [PATCH 39/72] xDS interop: enable pod log collection in the buildscripts (#9495) - Enables pod log collection in all PSM interop jobs implemented in https://github.com/grpc/grpc/pull/30594. - Associate test suite runs with their own log file, so it's displayed on the "Target Log" tab - Updates security job to not stop after a failed suite, so that authz_test run even if security_test failed - Fix run_test not returning correct exit status, causing false positives in some cases. See https://github.com/grpc/grpc/pull/30768 --- buildscripts/kokoro/psm-security.cfg | 2 +- buildscripts/kokoro/psm-security.sh | 22 ++++++++++++++++------ buildscripts/kokoro/xds_k8s_lb.cfg | 2 +- buildscripts/kokoro/xds_k8s_lb.sh | 10 +++++++--- buildscripts/kokoro/xds_url_map.cfg | 2 +- buildscripts/kokoro/xds_url_map.sh | 10 +++++++--- 6 files changed, 33 insertions(+), 15 deletions(-) diff --git a/buildscripts/kokoro/psm-security.cfg b/buildscripts/kokoro/psm-security.cfg index f2cfd7babff..9df0bbb7867 100644 --- a/buildscripts/kokoro/psm-security.cfg +++ b/buildscripts/kokoro/psm-security.cfg @@ -7,7 +7,7 @@ timeout_mins: 180 action { define_artifacts { regex: "artifacts/**/*sponge_log.xml" - regex: "artifacts/**/*sponge_log.log" + regex: "artifacts/**/*.log" strip_prefix: "artifacts" } } diff --git a/buildscripts/kokoro/psm-security.sh b/buildscripts/kokoro/psm-security.sh index 201772723dd..defe427e5d7 100755 --- a/buildscripts/kokoro/psm-security.sh +++ b/buildscripts/kokoro/psm-security.sh @@ -119,6 +119,8 @@ run_test() { # Test driver usage: # https://github.com/grpc/grpc/tree/master/tools/run_tests/xds_k8s_test_driver#basic-usage local test_name="${1:?Usage: run_test test_name}" + local out_dir="${TEST_XML_OUTPUT_DIR}/${test_name}" + mkdir -pv "${out_dir}" set -x python -m "tests.${test_name}" \ --flagfile="${TEST_DRIVER_FLAGFILE}" \ @@ -126,9 +128,11 @@ run_test() { --server_image="${SERVER_IMAGE_NAME}:${GIT_COMMIT}" \ --client_image="${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ --testing_version="${TESTING_VERSION}" \ - --xml_output_file="${TEST_XML_OUTPUT_DIR}/${test_name}/sponge_log.xml" \ - --force_cleanup - set +x + --force_cleanup \ + --collect_app_logs \ + --log_dir="${out_dir}" \ + --xml_output_file="${out_dir}/sponge_log.xml" \ + |& tee "${out_dir}/sponge_log.log" } ####################################### @@ -170,9 +174,15 @@ main() { build_docker_images_if_needed # Run tests cd "${TEST_DRIVER_FULL_DIR}" - run_test baseline_test - run_test security_test - run_test authz_test + local failed_tests=0 + test_suites=("baseline_test" "security_test" "authz_test") + for test in "${test_suites[@]}"; do + run_test $test || (( failed_tests++ )) + done + echo "Failed test suites: ${failed_tests}" + if (( failed_tests > 0 )); then + exit 1 + fi } main "$@" diff --git a/buildscripts/kokoro/xds_k8s_lb.cfg b/buildscripts/kokoro/xds_k8s_lb.cfg index 43971896cd4..10ea2d43b5d 100644 --- a/buildscripts/kokoro/xds_k8s_lb.cfg +++ b/buildscripts/kokoro/xds_k8s_lb.cfg @@ -7,7 +7,7 @@ timeout_mins: 180 action { define_artifacts { regex: "artifacts/**/*sponge_log.xml" - regex: "artifacts/**/*sponge_log.log" + regex: "artifacts/**/*.log" strip_prefix: "artifacts" } } diff --git a/buildscripts/kokoro/xds_k8s_lb.sh b/buildscripts/kokoro/xds_k8s_lb.sh index 295c4d5c7bc..f229f2abba6 100755 --- a/buildscripts/kokoro/xds_k8s_lb.sh +++ b/buildscripts/kokoro/xds_k8s_lb.sh @@ -118,6 +118,8 @@ run_test() { # Test driver usage: # https://github.com/grpc/grpc/tree/master/tools/run_tests/xds_k8s_test_driver#basic-usage local test_name="${1:?Usage: run_test test_name}" + local out_dir="${TEST_XML_OUTPUT_DIR}/${test_name}" + mkdir -pv "${out_dir}" set -x python -m "tests.${test_name}" \ --flagfile="${TEST_DRIVER_FLAGFILE}" \ @@ -126,9 +128,11 @@ run_test() { --server_image="${SERVER_IMAGE_NAME}:${GIT_COMMIT}" \ --client_image="${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ --testing_version="${TESTING_VERSION}" \ - --xml_output_file="${TEST_XML_OUTPUT_DIR}/${test_name}/sponge_log.xml" \ - --force_cleanup - set +x + --force_cleanup \ + --collect_app_logs \ + --log_dir="${out_dir}" \ + --xml_output_file="${out_dir}/sponge_log.xml" \ + |& tee "${out_dir}/sponge_log.log" } ####################################### diff --git a/buildscripts/kokoro/xds_url_map.cfg b/buildscripts/kokoro/xds_url_map.cfg index 4b5be84f880..1fa6c0141cb 100644 --- a/buildscripts/kokoro/xds_url_map.cfg +++ b/buildscripts/kokoro/xds_url_map.cfg @@ -7,7 +7,7 @@ timeout_mins: 90 action { define_artifacts { regex: "artifacts/**/*sponge_log.xml" - regex: "artifacts/**/*sponge_log.log" + regex: "artifacts/**/*.log" strip_prefix: "artifacts" } } diff --git a/buildscripts/kokoro/xds_url_map.sh b/buildscripts/kokoro/xds_url_map.sh index 657f695475d..50737e1569d 100755 --- a/buildscripts/kokoro/xds_url_map.sh +++ b/buildscripts/kokoro/xds_url_map.sh @@ -118,15 +118,19 @@ run_test() { # Test driver usage: # https://github.com/grpc/grpc/tree/master/tools/run_tests/xds_k8s_test_driver#basic-usage local test_name="${1:?Usage: run_test test_name}" + local out_dir="${TEST_XML_OUTPUT_DIR}/${test_name}" + mkdir -pv "${out_dir}" set -x python -m "tests.${test_name}" \ --flagfile="${TEST_DRIVER_FLAGFILE}" \ + --flagfile="config/url-map.cfg" \ --kube_context="${KUBE_CONTEXT}" \ --client_image="${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ --testing_version="${TESTING_VERSION}" \ - --xml_output_file="${TEST_XML_OUTPUT_DIR}/${test_name}/sponge_log.xml" \ - --flagfile="config/url-map.cfg" - set +x + --collect_app_logs \ + --log_dir="${out_dir}" \ + --xml_output_file="${out_dir}/sponge_log.xml" \ + |& tee "${out_dir}/sponge_log.log" } ####################################### From 2ee65a9c261636abb950319815fb89ef501acf11 Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Tue, 6 Sep 2022 13:06:48 -0700 Subject: [PATCH 40/72] core: Don't forward acceptResolvedAddresses() (#9511) Forwarding acceptResolvedAddresses() to a delegate in ForwardingLoadBalancer can cause problems if an extending class expects its handleResolvedAddresses implementation to be called even when a client calls handleResolvedAddresses(). This would not happen as ForwardingLoadBalancer would directly send the call to the delegate. --- .../src/main/java/io/grpc/util/ForwardingLoadBalancer.java | 5 ----- .../test/java/io/grpc/util/ForwardingLoadBalancerTest.java | 7 ++++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/io/grpc/util/ForwardingLoadBalancer.java b/core/src/main/java/io/grpc/util/ForwardingLoadBalancer.java index 516488f0d1a..cefcbf344ea 100644 --- a/core/src/main/java/io/grpc/util/ForwardingLoadBalancer.java +++ b/core/src/main/java/io/grpc/util/ForwardingLoadBalancer.java @@ -34,11 +34,6 @@ public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { delegate().handleResolvedAddresses(resolvedAddresses); } - @Override - public boolean acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { - return delegate().acceptResolvedAddresses(resolvedAddresses); - } - @Override public void handleNameResolutionError(Status error) { delegate().handleNameResolutionError(error); diff --git a/core/src/test/java/io/grpc/util/ForwardingLoadBalancerTest.java b/core/src/test/java/io/grpc/util/ForwardingLoadBalancerTest.java index 01226466845..f9b53400cea 100644 --- a/core/src/test/java/io/grpc/util/ForwardingLoadBalancerTest.java +++ b/core/src/test/java/io/grpc/util/ForwardingLoadBalancerTest.java @@ -20,8 +20,8 @@ import io.grpc.ForwardingTestUtil; import io.grpc.LoadBalancer; -import java.lang.reflect.Method; -import java.util.Collections; +import io.grpc.LoadBalancer.ResolvedAddresses; +import java.util.Arrays; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -44,6 +44,7 @@ public void allMethodsForwarded() throws Exception { LoadBalancer.class, mockDelegate, new TestBalancer(), - Collections.emptyList()); + Arrays.asList( + LoadBalancer.class.getMethod("acceptResolvedAddresses", ResolvedAddresses.class))); } } From bf692c04e6c2143ac598c04e76c039bc6aa5dafc Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Tue, 6 Sep 2022 15:49:49 -0700 Subject: [PATCH 41/72] Add custom_lb to the test suites. (#9502) Co-authored-by: Sergii Tkachenko --- buildscripts/kokoro/xds_k8s_lb.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildscripts/kokoro/xds_k8s_lb.sh b/buildscripts/kokoro/xds_k8s_lb.sh index f229f2abba6..6e0277ecb81 100755 --- a/buildscripts/kokoro/xds_k8s_lb.sh +++ b/buildscripts/kokoro/xds_k8s_lb.sh @@ -176,7 +176,7 @@ main() { # Run tests cd "${TEST_DRIVER_FULL_DIR}" local failed_tests=0 - test_suites=("api_listener_test" "change_backend_service_test" "failover_test" "remove_neg_test" "round_robin_test" "affinity_test" "outlier_detection_test") + test_suites=("api_listener_test" "change_backend_service_test" "failover_test" "remove_neg_test" "round_robin_test" "affinity_test" "outlier_detection_test" "custom_lb_test") for test in "${test_suites[@]}"; do run_test $test || (( failed_tests++ )) done From 0fd57516794b7fc21c7002fe152af10581a5ca7f Mon Sep 17 00:00:00 2001 From: Prateek Karandikar Date: Wed, 7 Sep 2022 04:31:49 +0100 Subject: [PATCH 42/72] Add security policies for checking device owner/profile owner. (#9428) --- binder/build.gradle | 4 +- .../java/io/grpc/binder/SecurityPolicies.java | 70 +++++++ .../io/grpc/binder/SecurityPoliciesTest.java | 174 ++++++++++++++++++ 3 files changed, 246 insertions(+), 2 deletions(-) diff --git a/binder/build.gradle b/binder/build.gradle index d18fe5e6b20..41c4bd4cc22 100644 --- a/binder/build.gradle +++ b/binder/build.gradle @@ -23,14 +23,14 @@ android { } } } - compileSdkVersion 29 + compileSdkVersion 30 compileOptions { sourceCompatibility 1.8 targetCompatibility 1.8 } defaultConfig { minSdkVersion 19 - targetSdkVersion 29 + targetSdkVersion 30 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/binder/src/main/java/io/grpc/binder/SecurityPolicies.java b/binder/src/main/java/io/grpc/binder/SecurityPolicies.java index 25f215be696..653ae90bd77 100644 --- a/binder/src/main/java/io/grpc/binder/SecurityPolicies.java +++ b/binder/src/main/java/io/grpc/binder/SecurityPolicies.java @@ -17,11 +17,14 @@ package io.grpc.binder; import android.annotation.SuppressLint; +import android.app.admin.DevicePolicyManager; +import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.Signature; import android.os.Build; +import android.os.Build.VERSION; import android.os.Process; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; @@ -173,6 +176,50 @@ public Status checkAuthorization(int uid) { }; } + /** + * Creates {@link SecurityPolicy} which checks if the app is a device owner app. See + * {@link DevicePolicyManager}. + */ + public static SecurityPolicy isDeviceOwner(Context applicationContext) { + DevicePolicyManager devicePolicyManager = + (DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE); + return anyPackageWithUidSatisfies( + applicationContext, + pkg -> VERSION.SDK_INT >= 18 && devicePolicyManager.isDeviceOwnerApp(pkg), + "Rejected by device owner policy. No packages found for UID.", + "Rejected by device owner policy"); + } + + /** + * Creates {@link SecurityPolicy} which checks if the app is a profile owner app. See + * {@link DevicePolicyManager}. + */ + public static SecurityPolicy isProfileOwner(Context applicationContext) { + DevicePolicyManager devicePolicyManager = + (DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE); + return anyPackageWithUidSatisfies( + applicationContext, + pkg -> VERSION.SDK_INT >= 21 && devicePolicyManager.isProfileOwnerApp(pkg), + "Rejected by profile owner policy. No packages found for UID.", + "Rejected by profile owner policy"); + } + + /** + * Creates {@link SecurityPolicy} which checks if the app is a profile owner app on an + * organization-owned device. See {@link DevicePolicyManager}. + */ + public static SecurityPolicy isProfileOwnerOnOrganizationOwnedDevice(Context applicationContext) { + DevicePolicyManager devicePolicyManager = + (DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE); + return anyPackageWithUidSatisfies( + applicationContext, + pkg -> VERSION.SDK_INT >= 30 + && devicePolicyManager.isProfileOwnerApp(pkg) + && devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile(), + "Rejected by profile owner on organization-owned device policy. No packages found for UID.", + "Rejected by profile owner on organization-owned device policy"); + } + private static Status checkUidSignature( PackageManager packageManager, int uid, @@ -406,6 +453,29 @@ private static Status checkPermissions( return Status.OK; } + private static SecurityPolicy anyPackageWithUidSatisfies( + Context applicationContext, + Predicate condition, + String errorMessageForNoPackages, + String errorMessageForDenied) { + return new SecurityPolicy() { + @Override + public Status checkAuthorization(int uid) { + String[] packages = applicationContext.getPackageManager().getPackagesForUid(uid); + if (packages == null || packages.length == 0) { + return Status.UNAUTHENTICATED.withDescription(errorMessageForNoPackages); + } + + for (String pkg : packages) { + if (condition.apply(pkg)) { + return Status.OK; + } + } + return Status.PERMISSION_DENIED.withDescription(errorMessageForDenied); + } + }; + } + /** * Checks if the SHA-256 hash of the {@code signature} matches one of the {@code * expectedSignatureSha256Hashes}. diff --git a/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java b/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java index a26d4dd58af..909547a902b 100644 --- a/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java +++ b/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java @@ -24,10 +24,15 @@ import static com.google.common.truth.Truth.assertThat; import static org.robolectric.Shadows.shadowOf; +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.Signature; +import android.os.Build; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; import android.os.Process; import androidx.test.core.app.ApplicationProvider; import com.google.common.collect.ImmutableList; @@ -40,6 +45,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) public final class SecurityPoliciesTest { @@ -59,6 +65,7 @@ public final class SecurityPoliciesTest { private Context appContext; private PackageManager packageManager; + private DevicePolicyManager devicePolicyManager; private SecurityPolicy policy; @@ -66,6 +73,8 @@ public final class SecurityPoliciesTest { public void setUp() { appContext = ApplicationProvider.getApplicationContext(); packageManager = appContext.getPackageManager(); + devicePolicyManager = + (DevicePolicyManager) appContext.getSystemService(Context.DEVICE_POLICY_SERVICE); } @SuppressWarnings("deprecation") @@ -323,6 +332,171 @@ public void testHasPermissions_failsIfPackageDoesNotHavePermissions() throws Exc .contains(OTHER_UID_PACKAGE_NAME); } + @Test + @Config(sdk = 18) + public void testIsDeviceOwner_succeedsForDeviceOwner() throws Exception { + PackageInfo info = + newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build(); + + installPackages(OTHER_UID, info); + shadowOf(devicePolicyManager) + .setDeviceOwner(new ComponentName(OTHER_UID_PACKAGE_NAME, "foo")); + + policy = SecurityPolicies.isDeviceOwner(appContext); + + assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode()); + } + + @Test + @Config(sdk = 18) + public void testIsDeviceOwner_failsForNotDeviceOwner() throws Exception { + PackageInfo info = + newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build(); + + installPackages(OTHER_UID, info); + + policy = SecurityPolicies.isDeviceOwner(appContext); + + assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode()); + } + + @Test + @Config(sdk = 18) + public void testIsDeviceOwner_failsWhenNoPackagesForUid() throws Exception { + policy = SecurityPolicies.isDeviceOwner(appContext); + + assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.UNAUTHENTICATED.getCode()); + } + + @Test + @Config(sdk = 17) + public void testIsDeviceOwner_failsForSdkLevelTooLow() throws Exception { + PackageInfo info = + newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build(); + + installPackages(OTHER_UID, info); + + policy = SecurityPolicies.isDeviceOwner(appContext); + + assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode()); + } + + @Test + @Config(sdk = 21) + public void testIsProfileOwner_succeedsForProfileOwner() throws Exception { + PackageInfo info = + newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build(); + + installPackages(OTHER_UID, info); + shadowOf(devicePolicyManager) + .setProfileOwner(new ComponentName(OTHER_UID_PACKAGE_NAME, "foo")); + + policy = SecurityPolicies.isProfileOwner(appContext); + + assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode()); + } + + @Test + @Config(sdk = 21) + public void testIsProfileOwner_failsForNotProfileOwner() throws Exception { + PackageInfo info = + newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build(); + + installPackages(OTHER_UID, info); + + policy = SecurityPolicies.isProfileOwner(appContext); + + assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode()); + } + + @Test + @Config(sdk = 21) + public void testIsProfileOwner_failsWhenNoPackagesForUid() throws Exception { + policy = SecurityPolicies.isProfileOwner(appContext); + + assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.UNAUTHENTICATED.getCode()); + } + + @Test + @Config(sdk = 19) + public void testIsProfileOwner_failsForSdkLevelTooLow() throws Exception { + PackageInfo info = + newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build(); + + installPackages(OTHER_UID, info); + + policy = SecurityPolicies.isProfileOwner(appContext); + + assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode()); + } + + @Test + @Config(sdk = 30) + public void testIsProfileOwnerOnOrgOwned_succeedsForProfileOwnerOnOrgOwned() throws Exception { + PackageInfo info = + newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build(); + + installPackages(OTHER_UID, info); + shadowOf(devicePolicyManager) + .setProfileOwner(new ComponentName(OTHER_UID_PACKAGE_NAME, "foo")); + shadowOf(devicePolicyManager).setOrganizationOwnedDeviceWithManagedProfile(true); + + policy = SecurityPolicies.isProfileOwnerOnOrganizationOwnedDevice(appContext); + + assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode()); + + } + + @Test + @Config(sdk = 30) + public void testIsProfileOwnerOnOrgOwned_failsForProfileOwnerOnNonOrgOwned() throws Exception { + PackageInfo info = + newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build(); + + installPackages(OTHER_UID, info); + shadowOf(devicePolicyManager) + .setProfileOwner(new ComponentName(OTHER_UID_PACKAGE_NAME, "foo")); + shadowOf(devicePolicyManager).setOrganizationOwnedDeviceWithManagedProfile(false); + + policy = SecurityPolicies.isProfileOwnerOnOrganizationOwnedDevice(appContext); + + assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode()); + } + + @Test + @Config(sdk = 21) + public void testIsProfileOwnerOnOrgOwned_failsForNotProfileOwner() throws Exception { + PackageInfo info = + newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build(); + + installPackages(OTHER_UID, info); + + policy = SecurityPolicies.isProfileOwnerOnOrganizationOwnedDevice(appContext); + + assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode()); + } + + @Test + @Config(sdk = 21) + public void testIsProfileOwnerOnOrgOwned_failsWhenNoPackagesForUid() throws Exception { + policy = SecurityPolicies.isProfileOwnerOnOrganizationOwnedDevice(appContext); + + assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.UNAUTHENTICATED.getCode()); + } + + @Test + @Config(sdk = 29) + public void testIsProfileOwnerOnOrgOwned_failsForSdkLevelTooLow() throws Exception { + PackageInfo info = + newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build(); + + installPackages(OTHER_UID, info); + + policy = SecurityPolicies.isProfileOwner(appContext); + + assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode()); + } + private static PackageInfoBuilder newBuilder() { return new PackageInfoBuilder(); } From 5946eb0d6a245d87bbaa4acd8cb1f9772c1059e1 Mon Sep 17 00:00:00 2001 From: Larry Safran <107004254+larry-safran@users.noreply.github.com> Date: Wed, 7 Sep 2022 14:58:42 -0700 Subject: [PATCH 43/72] style fix (#9526) --- .../grpc/auth/GoogleAuthLibraryCallCredentials.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java b/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java index edd73e9845d..2a414e792d9 100644 --- a/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java +++ b/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java @@ -51,9 +51,9 @@ final class GoogleAuthLibraryCallCredentials extends io.grpc.CallCredentials = Logger.getLogger(GoogleAuthLibraryCallCredentials.class.getName()); private static final JwtHelper jwtHelper = createJwtHelperOrNull(GoogleAuthLibraryCallCredentials.class.getClassLoader()); - private static final Class googleCredentialsClass + private static final Class GOOGLE_CREDENTIALS_CLASS = loadGoogleCredentialsClass(); - private static final Class appEngineCredentialsClass + private static final Class APP_ENGINE_CREDENTIALS_CLASS = loadAppEngineCredentials(); private final boolean requirePrivacy; @@ -73,12 +73,12 @@ public GoogleAuthLibraryCallCredentials(Credentials creds) { GoogleAuthLibraryCallCredentials(Credentials creds, JwtHelper jwtHelper) { checkNotNull(creds, "creds"); boolean requirePrivacy = false; - if (googleCredentialsClass != null) { + if (GOOGLE_CREDENTIALS_CLASS != null) { // All GoogleCredentials instances are bearer tokens and should only be used on private // channels. This catches all return values from GoogleCredentials.getApplicationDefault(). // This should be checked before upgrading the Service Account to JWT, as JWT is also a bearer // token. - requirePrivacy = googleCredentialsClass.isInstance(creds); + requirePrivacy = GOOGLE_CREDENTIALS_CLASS.isInstance(creds); } if (jwtHelper != null) { creds = jwtHelper.tryServiceAccountToJwt(creds); @@ -379,10 +379,10 @@ public Credentials tryServiceAccountToJwt(Credentials creds) { public boolean isSpecificExecutorRequired() { // Cache the value so we only need to try to load the class once if (requiresSpecificExecutor == null) { - if (appEngineCredentialsClass == null) { + if (APP_ENGINE_CREDENTIALS_CLASS == null) { requiresSpecificExecutor = Boolean.FALSE; } else { - requiresSpecificExecutor = appEngineCredentialsClass.isInstance(creds); + requiresSpecificExecutor = APP_ENGINE_CREDENTIALS_CLASS.isInstance(creds); } } From 6bafca93a76b3e038befffb864af596f8105f9d4 Mon Sep 17 00:00:00 2001 From: Chris Povirk Date: Wed, 7 Sep 2022 18:50:31 -0400 Subject: [PATCH 44/72] core: Use real ByteBuffer instead of mock (#9523) My motivation for making this change is that [`ByteBuffer` is becoming `sealed`](https://download.java.net/java/early_access/loom/docs/api/java.base/java/nio/ByteBuffer.html) in new versions of Java. This makes it impossible for Mockito's _current_ default mockmaker to mock it. That said, Mockito will likely [switch its default mockmaker](https://github.com/mockito/mockito/issues/2589) to an alternative that _is_ able to mock `sealed` classes. However, there are downside to that, such as [slower performance](https://github.com/mockito/mockito/issues/2589#issuecomment-1192725206), so it's probably better to leave our options open by avoiding mocking at all. And in this case, it's equally easy to use real objects. As a bonus, I think that real objects makes the code a little easier to follow: Before, we created mocks that the code under test never interacted with in any way. (The code just passed them through to a delegate.) When I first read the tests, I was confused, since I assumed that the mock we were creating was the same mock that we then passed to `verify` at the end of the method. That turned out not to be the case. --- .../internal/ForwardingReadableBufferTest.java | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/core/src/test/java/io/grpc/internal/ForwardingReadableBufferTest.java b/core/src/test/java/io/grpc/internal/ForwardingReadableBufferTest.java index b778f25e5de..8ce45bc77cf 100644 --- a/core/src/test/java/io/grpc/internal/ForwardingReadableBufferTest.java +++ b/core/src/test/java/io/grpc/internal/ForwardingReadableBufferTest.java @@ -36,13 +36,10 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; -/** - * Tests for {@link ForwardingReadableBuffer}. - */ +/** Tests for {@link ForwardingReadableBuffer}. */ @RunWith(JUnit4.class) public class ForwardingReadableBufferTest { - @Rule - public final MockitoRule mocks = MockitoJUnit.rule(); + @Rule public final MockitoRule mocks = MockitoJUnit.rule(); @Mock private ReadableBuffer delegate; private ForwardingReadableBuffer buffer; @@ -55,10 +52,7 @@ public void setUp() { @Test public void allMethodsForwarded() throws Exception { ForwardingTestUtil.testMethodsForwarded( - ReadableBuffer.class, - delegate, - buffer, - Collections.emptyList()); + ReadableBuffer.class, delegate, buffer, Collections.emptyList()); } @Test @@ -99,7 +93,7 @@ public void readBytes() { @Test public void readBytes_overload1() { - ByteBuffer dest = mock(ByteBuffer.class); + ByteBuffer dest = ByteBuffer.allocate(0); buffer.readBytes(dest); verify(delegate).readBytes(dest); From 53a2d506956b70fd264df2ab75b586de43edbf5c Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Thu, 8 Sep 2022 08:49:07 -0700 Subject: [PATCH 45/72] xds: always update priority LB connectivity state (#9527) Removes the option of skipping the update of the priority LB state when the failover timer is pending. This consistency facilitates a future change weher we delay child LB status updates if the priority LB is performing an update. The upcoming priority LB policy gRFC also does not require this update to ever be skipped. --- .../java/io/grpc/xds/PriorityLoadBalancer.java | 14 +++++--------- .../java/io/grpc/xds/PriorityLoadBalancerTest.java | 3 ++- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java b/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java index 63855a28dd4..5cae9139ae6 100644 --- a/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java @@ -99,9 +99,7 @@ public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { children.get(priority).updateResolvedAddresses(); } } - // Not to report connecting in case a pending priority bumps up on top of the current READY - // priority. - tryNextPriority(false); + tryNextPriority(); } @Override @@ -128,7 +126,7 @@ public void shutdown() { children.clear(); } - private void tryNextPriority(boolean reportConnecting) { + private void tryNextPriority() { for (int i = 0; i < priorityNames.size(); i++) { String priority = priorityNames.get(i); if (!children.containsKey(priority)) { @@ -153,9 +151,7 @@ private void tryNextPriority(boolean reportConnecting) { return; } if (child.failOverTimer != null && child.failOverTimer.isPending()) { - if (reportConnecting) { - updateOverallState(priority, CONNECTING, BUFFER_PICKER); - } + updateOverallState(priority, child.connectivityState, child.picker); return; // Give priority i time to connect. } if (priority.equals(currentPriority) && child.connectivityState != TRANSIENT_FAILURE) { @@ -216,7 +212,7 @@ public void run() { Status.UNAVAILABLE.withDescription("Connection timeout for priority " + priority)); logger.log(XdsLogLevel.DEBUG, "Priority {0} failed over to next", priority); currentPriority = null; // reset currentPriority to guarantee failover happen - tryNextPriority(true); + tryNextPriority(); } } @@ -324,7 +320,7 @@ public void run() { seenReadyOrIdleSinceTransientFailure = false; failOverTimer.cancel(); } - tryNextPriority(true); + tryNextPriority(); } }); } diff --git a/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java index 47888a7980e..6b1fae48f1d 100644 --- a/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java @@ -23,6 +23,7 @@ import static io.grpc.ConnectivityState.TRANSIENT_FAILURE; import static io.grpc.xds.XdsSubchannelPickers.BUFFER_PICKER; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; @@ -675,7 +676,7 @@ public void raceBetweenShutdownAndChildLbBalancingStateUpdate() { .setAddresses(ImmutableList.of()) .setLoadBalancingPolicyConfig(priorityLbConfig) .build()); - verify(helper).updateBalancingState(eq(CONNECTING), eq(BUFFER_PICKER)); + verify(helper, times(2)).updateBalancingState(eq(CONNECTING), isA(SubchannelPicker.class)); // LB shutdown and subchannel state change can happen simultaneously. If shutdown runs first, // any further balancing state update should be ignored. From 95b9d6db29ce7241de764beb64cd2bdee781ffb5 Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Thu, 8 Sep 2022 08:51:16 -0700 Subject: [PATCH 46/72] Revert "okhttp: add max connection idle at OkHttpServer (#9494)" (#9528) This reverts commit 7291ad44c6d5d2b614762d4f549008c14eeff312. --- .../io/grpc/okhttp/OkHttpServerBuilder.java | 28 ------- .../io/grpc/okhttp/OkHttpServerTransport.java | 23 ------ .../okhttp/OkHttpServerTransportTest.java | 74 ++----------------- 3 files changed, 6 insertions(+), 119 deletions(-) diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java index 0e1273c7f23..5928a2e38c1 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java @@ -16,8 +16,6 @@ package io.grpc.okhttp; -import static com.google.common.base.Preconditions.checkArgument; - import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.errorprone.annotations.DoNotCall; @@ -64,10 +62,6 @@ public final class OkHttpServerBuilder extends ForwardingServerBuilder { private static final Logger log = Logger.getLogger(OkHttpServerBuilder.class.getName()); private static final int DEFAULT_FLOW_CONTROL_WINDOW = 65535; - - static final long MAX_CONNECTION_IDLE_NANOS_DISABLED = Long.MAX_VALUE; - private static final long MIN_MAX_CONNECTION_IDLE_NANO = TimeUnit.SECONDS.toNanos(1L); - private static final long AS_LARGE_AS_INFINITE = TimeUnit.DAYS.toNanos(1000L); private static final ObjectPool DEFAULT_TRANSPORT_EXECUTOR_POOL = OkHttpChannelBuilder.DEFAULT_TRANSPORT_EXECUTOR_POOL; @@ -116,7 +110,6 @@ public static OkHttpServerBuilder forPort(SocketAddress address, ServerCredentia int flowControlWindow = DEFAULT_FLOW_CONTROL_WINDOW; int maxInboundMetadataSize = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE; int maxInboundMessageSize = GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE; - long maxConnectionIdleInNanos = MAX_CONNECTION_IDLE_NANOS_DISABLED; @VisibleForTesting OkHttpServerBuilder( @@ -185,27 +178,6 @@ public OkHttpServerBuilder keepAliveTime(long keepAliveTime, TimeUnit timeUnit) return this; } - /** - * Sets a custom max connection idle time, connection being idle for longer than which will be - * gracefully terminated. Idleness duration is defined since the most recent time the number of - * outstanding RPCs became zero or the connection establishment. An unreasonably small value might - * be increased. {@code Long.MAX_VALUE} nano seconds or an unreasonably large value will disable - * max connection idle. - */ - @Override - public OkHttpServerBuilder maxConnectionIdle(long maxConnectionIdle, TimeUnit timeUnit) { - checkArgument(maxConnectionIdle > 0L, "max connection idle must be positive: %s", - maxConnectionIdle); - maxConnectionIdleInNanos = timeUnit.toNanos(maxConnectionIdle); - if (maxConnectionIdleInNanos >= AS_LARGE_AS_INFINITE) { - maxConnectionIdleInNanos = MAX_CONNECTION_IDLE_NANOS_DISABLED; - } - if (maxConnectionIdleInNanos < MIN_MAX_CONNECTION_IDLE_NANO) { - maxConnectionIdleInNanos = MIN_MAX_CONNECTION_IDLE_NANO; - } - return this; - } - /** * Sets a time waiting for read activity after sending a keepalive ping. If the time expires * without any read activity on the connection, the connection is considered dead. An unreasonably diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerTransport.java index b2b581d7155..3a4ed86f5da 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerTransport.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerTransport.java @@ -16,8 +16,6 @@ package io.grpc.okhttp; -import static io.grpc.okhttp.OkHttpServerBuilder.MAX_CONNECTION_IDLE_NANOS_DISABLED; - import com.google.common.base.Preconditions; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -30,7 +28,6 @@ import io.grpc.Status; import io.grpc.internal.GrpcUtil; import io.grpc.internal.KeepAliveManager; -import io.grpc.internal.MaxConnectionIdleManager; import io.grpc.internal.ObjectPool; import io.grpc.internal.SerializingExecutor; import io.grpc.internal.ServerTransport; @@ -94,7 +91,6 @@ final class OkHttpServerTransport implements ServerTransport, private ScheduledExecutorService scheduledExecutorService; private Attributes attributes; private KeepAliveManager keepAliveManager; - private MaxConnectionIdleManager maxConnectionIdleManager; private final Object lock = new Object(); @GuardedBy("lock") @@ -193,11 +189,6 @@ private void startIo(SerializingExecutor serializingExecutor) { keepAliveManager.onTransportStarted(); } - if (config.maxConnectionIdleNanos != MAX_CONNECTION_IDLE_NANOS_DISABLED) { - maxConnectionIdleManager = new MaxConnectionIdleManager(config.maxConnectionIdleNanos); - maxConnectionIdleManager.start(this::shutdown, scheduledExecutorService); - } - transportExecutor.execute( new FrameHandler(variant.newReader(Okio.buffer(Okio.source(socket)), false))); } catch (Error | IOException | RuntimeException ex) { @@ -320,9 +311,6 @@ private void terminated() { if (keepAliveManager != null) { keepAliveManager.onTransportTermination(); } - if (maxConnectionIdleManager != null) { - maxConnectionIdleManager.onTransportTermination(); - } transportExecutor = config.transportExecutorPool.returnObject(transportExecutor); scheduledExecutorService = config.scheduledExecutorServicePool.returnObject(scheduledExecutorService); @@ -381,9 +369,6 @@ public OutboundFlowController.StreamState[] getActiveStreams() { void streamClosed(int streamId, boolean flush) { synchronized (lock) { streams.remove(streamId); - if (maxConnectionIdleManager != null && streams.isEmpty()) { - maxConnectionIdleManager.onTransportIdle(); - } if (gracefulShutdown && streams.isEmpty()) { frameWriter.close(); } else { @@ -448,7 +433,6 @@ static final class Config { final int flowControlWindow; final int maxInboundMessageSize; final int maxInboundMetadataSize; - final long maxConnectionIdleNanos; public Config( OkHttpServerBuilder builder, @@ -468,7 +452,6 @@ public Config( flowControlWindow = builder.flowControlWindow; maxInboundMessageSize = builder.maxInboundMessageSize; maxInboundMetadataSize = builder.maxInboundMetadataSize; - maxConnectionIdleNanos = builder.maxConnectionIdleInNanos; } } @@ -714,9 +697,6 @@ public void headers(boolean outFinished, authority == null ? null : asciiString(authority), statsTraceCtx, tracer); - if (maxConnectionIdleManager != null && streams.isEmpty()) { - maxConnectionIdleManager.onTransportActive(); - } streams.put(streamId, stream); listener.streamCreated(streamForApp, method, metadata); stream.onStreamAllocated(); @@ -973,9 +953,6 @@ private void respondWithHttpError( synchronized (lock) { Http2ErrorStreamState stream = new Http2ErrorStreamState(streamId, lock, outboundFlow, config.flowControlWindow); - if (maxConnectionIdleManager != null && streams.isEmpty()) { - maxConnectionIdleManager.onTransportActive(); - } streams.put(streamId, stream); if (inFinished) { stream.inboundDataReceived(new Buffer(), 0, true); diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java index 93e39583fb9..201829d38ab 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java @@ -90,7 +90,6 @@ public class OkHttpServerTransportTest { private static final int TIME_OUT_MS = 2000; private static final int INITIAL_WINDOW_SIZE = 65535; - private static final long MAX_CONNECTION_IDLE = TimeUnit.SECONDS.toNanos(1); private MockServerTransportListener mockTransportListener = new MockServerTransportListener(); private ServerTransportListener transportListener @@ -106,11 +105,10 @@ public class OkHttpServerTransportTest { private ExecutorService threadPool = Executors.newCachedThreadPool(); private HandshakerSocketFactory handshakerSocketFactory = mock(HandshakerSocketFactory.class, delegatesTo(new PlaintextHandshakerSocketFactory())); - private final FakeClock fakeClock = new FakeClock(); private OkHttpServerBuilder serverBuilder = new OkHttpServerBuilder(new InetSocketAddress(1234), handshakerSocketFactory) .executor(new FakeClock().getScheduledExecutorService()) // Executor unused - .scheduledExecutorService(fakeClock.getScheduledExecutorService()) + .scheduledExecutorService(new FakeClock().getScheduledExecutorService()) .transportExecutor(new Executor() { @Override public void execute(Runnable runnable) { if (runnable instanceof OkHttpServerTransport.FrameHandler) { @@ -121,8 +119,7 @@ public class OkHttpServerTransportTest { } } }) - .flowControlWindow(INITIAL_WINDOW_SIZE) - .maxConnectionIdle(MAX_CONNECTION_IDLE, TimeUnit.NANOSECONDS); + .flowControlWindow(INITIAL_WINDOW_SIZE); @Rule public final Timeout globalTimeout = Timeout.seconds(10); @@ -149,63 +146,6 @@ public void startThenShutdown() throws Exception { shutdownAndTerminate(/*lastStreamId=*/ 0); } - @Test - public void maxConnectionIdleTimer() throws Exception { - initTransport(); - handshake(); - clientFrameWriter.headers(1, Arrays.asList( - HTTP_SCHEME_HEADER, - METHOD_HEADER, - new Header(Header.TARGET_AUTHORITY, "example.com:80"), - new Header(Header.TARGET_PATH, "/com.example/SimpleService.doit"), - CONTENT_TYPE_HEADER, - TE_HEADER)); - clientFrameWriter.synStream(true, false, 1, -1, Arrays.asList( - new Header("some-client-sent-trailer", "trailer-value"))); - pingPong(); - - MockStreamListener streamListener = mockTransportListener.newStreams.pop(); - assertThat(streamListener.messages.peek()).isNull(); - assertThat(streamListener.halfClosedCalled).isTrue(); - - streamListener.stream.close(Status.OK, new Metadata()); - - List

responseTrailers = Arrays.asList( - new Header(":status", "200"), - CONTENT_TYPE_HEADER, - new Header("grpc-status", "0")); - assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue(); - verify(clientFramesRead) - .headers(false, true, 1, -1, responseTrailers, HeadersMode.HTTP_20_HEADERS); - - fakeClock.forwardNanos(MAX_CONNECTION_IDLE); - fakeClock.forwardNanos(MAX_CONNECTION_IDLE); - verifyGracefulShutdown(1); - } - - @Test - public void maxConnectionIdleTimer_respondWithError() throws Exception { - initTransport(); - handshake(); - - clientFrameWriter.headers(1, Arrays.asList( - HTTP_SCHEME_HEADER, - METHOD_HEADER, - new Header(Header.TARGET_PATH, "/com.example/SimpleService.doit"), - CONTENT_TYPE_HEADER, - TE_HEADER, - new Header("host", "example.com:80"), - new Header("host", "example.com:80"))); - clientFrameWriter.flush(); - - verifyHttpError( - 1, 400, Status.Code.INTERNAL, "Multiple host headers disallowed. RFC7230 section 5.4"); - - fakeClock.forwardNanos(MAX_CONNECTION_IDLE); - fakeClock.forwardNanos(MAX_CONNECTION_IDLE); - verifyGracefulShutdown(1); - } - @Test public void startThenShutdownTwice() throws Exception { initTransport(); @@ -376,8 +316,7 @@ public void activeRpc_delaysShutdownTermination() throws Exception { clientFrameWriter.data(true, 1, requestMessageFrame, (int) requestMessageFrame.size()); pingPong(); - serverTransport.shutdown(); - verifyGracefulShutdown(1); + shutdownAndVerifyGraceful(1); verify(transportListener, never()).transportTerminated(); MockStreamListener streamListener = mockTransportListener.newStreams.pop(); @@ -1099,8 +1038,8 @@ private Metadata metadata(String... keysAndValues) { return metadata; } - private void verifyGracefulShutdown(int lastStreamId) - throws IOException { + private void shutdownAndVerifyGraceful(int lastStreamId) throws IOException { + serverTransport.shutdown(); assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue(); verify(clientFramesRead).goAway(2147483647, ErrorCode.NO_ERROR, ByteString.EMPTY); assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue(); @@ -1113,8 +1052,7 @@ private void verifyGracefulShutdown(int lastStreamId) private void shutdownAndTerminate(int lastStreamId) throws IOException { assertThat(serverTransport.getActiveStreams().length).isEqualTo(0); - serverTransport.shutdown(); - verifyGracefulShutdown(lastStreamId); + shutdownAndVerifyGraceful(lastStreamId); assertThat(clientFrameReader.nextFrame(clientFramesRead)).isFalse(); verify(transportListener, timeout(TIME_OUT_MS)).transportTerminated(); } From 074e91930474ce14b8b3fb18af12aeea89201872 Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Thu, 8 Sep 2022 09:35:03 -0700 Subject: [PATCH 47/72] xds: rename Sds to Security or Xds in various classes to eliminate references to SDS (#9529) --- ...ilterChainMatchingProtocolNegotiators.java | 2 +- .../io/grpc/xds/XdsChannelCredentials.java | 4 +- .../io/grpc/xds/XdsServerCredentials.java | 4 +- .../CertProviderClientSslContextProvider.java | 4 +- .../CertProviderServerSslContextProvider.java | 4 +- ....java => SecurityProtocolNegotiators.java} | 7 +- .../xds/internal/sds/SslContextProvider.java | 8 +- .../sds/SslContextProviderSupplier.java | 2 +- ...ctory.java => XdsTrustManagerFactory.java} | 22 ++--- ...tManager.java => XdsX509TrustManager.java} | 6 +- ...rChainMatchingProtocolNegotiatorsTest.java | 2 +- .../XdsClientWrapperForServerSdsTestMisc.java | 2 +- ...a => SecurityProtocolNegotiatorsTest.java} | 38 ++++---- ...t.java => XdsTrustManagerFactoryTest.java} | 92 +++++++++---------- ...Test.java => XdsX509TrustManagerTest.java} | 66 ++++++------- 15 files changed, 132 insertions(+), 131 deletions(-) rename xds/src/main/java/io/grpc/xds/internal/sds/{SdsProtocolNegotiators.java => SecurityProtocolNegotiators.java} (98%) rename xds/src/main/java/io/grpc/xds/internal/sds/trust/{SdsTrustManagerFactory.java => XdsTrustManagerFactory.java} (90%) rename xds/src/main/java/io/grpc/xds/internal/sds/trust/{SdsX509TrustManager.java => XdsX509TrustManager.java} (98%) rename xds/src/test/java/io/grpc/xds/internal/sds/{SdsProtocolNegotiatorsTest.java => SecurityProtocolNegotiatorsTest.java} (92%) rename xds/src/test/java/io/grpc/xds/internal/sds/trust/{SdsTrustManagerFactoryTest.java => XdsTrustManagerFactoryTest.java} (78%) rename xds/src/test/java/io/grpc/xds/internal/sds/trust/{SdsX509TrustManagerTest.java => XdsX509TrustManagerTest.java} (92%) diff --git a/xds/src/main/java/io/grpc/xds/FilterChainMatchingProtocolNegotiators.java b/xds/src/main/java/io/grpc/xds/FilterChainMatchingProtocolNegotiators.java index e75440225dc..1bea9754189 100644 --- a/xds/src/main/java/io/grpc/xds/FilterChainMatchingProtocolNegotiators.java +++ b/xds/src/main/java/io/grpc/xds/FilterChainMatchingProtocolNegotiators.java @@ -20,7 +20,7 @@ import static io.grpc.xds.InternalXdsAttributes.ATTR_DRAIN_GRACE_NANOS; import static io.grpc.xds.InternalXdsAttributes.ATTR_FILTER_CHAIN_SELECTOR_MANAGER; import static io.grpc.xds.XdsServerWrapper.ATTR_SERVER_ROUTING_CONFIG; -import static io.grpc.xds.internal.sds.SdsProtocolNegotiators.ATTR_SERVER_SSL_CONTEXT_PROVIDER_SUPPLIER; +import static io.grpc.xds.internal.sds.SecurityProtocolNegotiators.ATTR_SERVER_SSL_CONTEXT_PROVIDER_SUPPLIER; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; diff --git a/xds/src/main/java/io/grpc/xds/XdsChannelCredentials.java b/xds/src/main/java/io/grpc/xds/XdsChannelCredentials.java index d07f10555c6..81d1b8ecfbf 100644 --- a/xds/src/main/java/io/grpc/xds/XdsChannelCredentials.java +++ b/xds/src/main/java/io/grpc/xds/XdsChannelCredentials.java @@ -22,7 +22,7 @@ import io.grpc.ExperimentalApi; import io.grpc.netty.InternalNettyChannelCredentials; import io.grpc.netty.InternalProtocolNegotiator; -import io.grpc.xds.internal.sds.SdsProtocolNegotiators; +import io.grpc.xds.internal.sds.SecurityProtocolNegotiators; @ExperimentalApi("https://github.com/grpc/grpc-java/issues/7514") public class XdsChannelCredentials { @@ -40,6 +40,6 @@ public static ChannelCredentials create(ChannelCredentials fallback) { InternalProtocolNegotiator.ClientFactory fallbackNegotiator = InternalNettyChannelCredentials.toNegotiator(checkNotNull(fallback, "fallback")); return InternalNettyChannelCredentials.create( - SdsProtocolNegotiators.clientProtocolNegotiatorFactory(fallbackNegotiator)); + SecurityProtocolNegotiators.clientProtocolNegotiatorFactory(fallbackNegotiator)); } } diff --git a/xds/src/main/java/io/grpc/xds/XdsServerCredentials.java b/xds/src/main/java/io/grpc/xds/XdsServerCredentials.java index e6e78f319c7..38015e17afc 100644 --- a/xds/src/main/java/io/grpc/xds/XdsServerCredentials.java +++ b/xds/src/main/java/io/grpc/xds/XdsServerCredentials.java @@ -22,7 +22,7 @@ import io.grpc.ServerCredentials; import io.grpc.netty.InternalNettyServerCredentials; import io.grpc.netty.InternalProtocolNegotiator; -import io.grpc.xds.internal.sds.SdsProtocolNegotiators; +import io.grpc.xds.internal.sds.SecurityProtocolNegotiators; @ExperimentalApi("https://github.com/grpc/grpc-java/issues/7514") public class XdsServerCredentials { @@ -40,6 +40,6 @@ public static ServerCredentials create(ServerCredentials fallback) { InternalProtocolNegotiator.ServerFactory fallbackNegotiator = InternalNettyServerCredentials.toNegotiator(checkNotNull(fallback, "fallback")); return InternalNettyServerCredentials.create( - SdsProtocolNegotiators.serverProtocolNegotiatorFactory(fallbackNegotiator)); + SecurityProtocolNegotiators.serverProtocolNegotiatorFactory(fallbackNegotiator)); } } diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProvider.java index ce9ef3de680..ddb530f67fb 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProvider.java @@ -26,7 +26,7 @@ import io.grpc.netty.GrpcSslContexts; import io.grpc.xds.Bootstrapper.CertificateProviderInfo; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; -import io.grpc.xds.internal.sds.trust.SdsTrustManagerFactory; +import io.grpc.xds.internal.sds.trust.XdsTrustManagerFactory; import io.netty.handler.ssl.SslContextBuilder; import java.security.cert.CertStoreException; import java.security.cert.X509Certificate; @@ -62,7 +62,7 @@ protected final SslContextBuilder getSslContextBuilder( SslContextBuilder sslContextBuilder = GrpcSslContexts.forClient() .trustManager( - new SdsTrustManagerFactory( + new XdsTrustManagerFactory( savedTrustedRoots.toArray(new X509Certificate[0]), certificateValidationContextdationContext)); if (isMtls()) { diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProvider.java index a7f0849d00b..34fe49e1daf 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProvider.java @@ -26,7 +26,7 @@ import io.grpc.netty.GrpcSslContexts; import io.grpc.xds.Bootstrapper.CertificateProviderInfo; import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; -import io.grpc.xds.internal.sds.trust.SdsTrustManagerFactory; +import io.grpc.xds.internal.sds.trust.XdsTrustManagerFactory; import io.netty.handler.ssl.SslContextBuilder; import java.io.IOException; @@ -66,7 +66,7 @@ protected final SslContextBuilder getSslContextBuilder( setClientAuthValues( sslContextBuilder, isMtls() - ? new SdsTrustManagerFactory( + ? new XdsTrustManagerFactory( savedTrustedRoots.toArray(new X509Certificate[0]), certificateValidationContextdationContext) : null); diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/SdsProtocolNegotiators.java b/xds/src/main/java/io/grpc/xds/internal/sds/SecurityProtocolNegotiators.java similarity index 98% rename from xds/src/main/java/io/grpc/xds/internal/sds/SdsProtocolNegotiators.java rename to xds/src/main/java/io/grpc/xds/internal/sds/SecurityProtocolNegotiators.java index a032737e647..cb3521aa585 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/SdsProtocolNegotiators.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/SecurityProtocolNegotiators.java @@ -48,13 +48,14 @@ * context. */ @VisibleForTesting -public final class SdsProtocolNegotiators { +public final class SecurityProtocolNegotiators { // Prevent instantiation. - private SdsProtocolNegotiators() { + private SecurityProtocolNegotiators() { } - private static final Logger logger = Logger.getLogger(SdsProtocolNegotiators.class.getName()); + private static final Logger logger + = Logger.getLogger(SecurityProtocolNegotiators.class.getName()); private static final AsciiString SCHEME = AsciiString.of("http"); diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/SslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/sds/SslContextProvider.java index 6b661715e48..f72793cd050 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/SslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/SslContextProvider.java @@ -24,7 +24,7 @@ import io.grpc.xds.EnvoyServerProtoData.BaseTlsContext; import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; -import io.grpc.xds.internal.sds.trust.SdsTrustManagerFactory; +import io.grpc.xds.internal.sds.trust.XdsTrustManagerFactory; import io.netty.handler.ssl.ClientAuth; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; @@ -70,11 +70,11 @@ protected CommonTlsContext getCommonTlsContext() { } protected void setClientAuthValues( - SslContextBuilder sslContextBuilder, SdsTrustManagerFactory sdsTrustManagerFactory) + SslContextBuilder sslContextBuilder, XdsTrustManagerFactory xdsTrustManagerFactory) throws CertificateException, IOException, CertStoreException { DownstreamTlsContext downstreamTlsContext = getDownstreamTlsContext(); - if (sdsTrustManagerFactory != null) { - sslContextBuilder.trustManager(sdsTrustManagerFactory); + if (xdsTrustManagerFactory != null) { + sslContextBuilder.trustManager(xdsTrustManagerFactory); sslContextBuilder.clientAuth( downstreamTlsContext.isRequireClientCertificate() ? ClientAuth.REQUIRE diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/SslContextProviderSupplier.java b/xds/src/main/java/io/grpc/xds/internal/sds/SslContextProviderSupplier.java index 664b4881bc2..183c4d2a634 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/SslContextProviderSupplier.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/SslContextProviderSupplier.java @@ -29,7 +29,7 @@ /** * Enables Client or server side to initialize this object with the received {@link BaseTlsContext} - * and communicate it to the consumer i.e. {@link SdsProtocolNegotiators} + * and communicate it to the consumer i.e. {@link SecurityProtocolNegotiators} * to lazily evaluate the {@link SslContextProvider}. The supplier prevents credentials leakage in * cases where the user is not using xDS credentials but the client/server contains a non-default * {@link BaseTlsContext}. diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/trust/SdsTrustManagerFactory.java b/xds/src/main/java/io/grpc/xds/internal/sds/trust/XdsTrustManagerFactory.java similarity index 90% rename from xds/src/main/java/io/grpc/xds/internal/sds/trust/SdsTrustManagerFactory.java rename to xds/src/main/java/io/grpc/xds/internal/sds/trust/XdsTrustManagerFactory.java index 479569f1596..2cdcd3a2d65 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/trust/SdsTrustManagerFactory.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/trust/XdsTrustManagerFactory.java @@ -43,15 +43,15 @@ /** * Factory class used by providers of {@link TlsContextManagerImpl} to provide a - * {@link SdsX509TrustManager} for trust and SAN checks. + * {@link XdsX509TrustManager} for trust and SAN checks. */ -public final class SdsTrustManagerFactory extends SimpleTrustManagerFactory { +public final class XdsTrustManagerFactory extends SimpleTrustManagerFactory { - private static final Logger logger = Logger.getLogger(SdsTrustManagerFactory.class.getName()); - private SdsX509TrustManager sdsX509TrustManager; + private static final Logger logger = Logger.getLogger(XdsTrustManagerFactory.class.getName()); + private XdsX509TrustManager xdsX509TrustManager; /** Constructor constructs from a {@link CertificateValidationContext}. */ - public SdsTrustManagerFactory(CertificateValidationContext certificateValidationContext) + public XdsTrustManagerFactory(CertificateValidationContext certificateValidationContext) throws CertificateException, IOException, CertStoreException { this( getTrustedCaFromCertContext(certificateValidationContext), @@ -59,13 +59,13 @@ public SdsTrustManagerFactory(CertificateValidationContext certificateValidation false); } - public SdsTrustManagerFactory( + public XdsTrustManagerFactory( X509Certificate[] certs, CertificateValidationContext staticCertificateValidationContext) throws CertStoreException { this(certs, staticCertificateValidationContext, true); } - private SdsTrustManagerFactory( + private XdsTrustManagerFactory( X509Certificate[] certs, CertificateValidationContext certificateValidationContext, boolean validationContextIsStatic) @@ -75,7 +75,7 @@ private SdsTrustManagerFactory( certificateValidationContext == null || !certificateValidationContext.hasTrustedCa(), "only static certificateValidationContext expected"); } - sdsX509TrustManager = createSdsX509TrustManager(certs, certificateValidationContext); + xdsX509TrustManager = createSdsX509TrustManager(certs, certificateValidationContext); } private static X509Certificate[] getTrustedCaFromCertContext( @@ -100,7 +100,7 @@ private static X509Certificate[] getTrustedCaFromCertContext( } @VisibleForTesting - static SdsX509TrustManager createSdsX509TrustManager( + static XdsX509TrustManager createSdsX509TrustManager( X509Certificate[] certs, CertificateValidationContext certContext) throws CertStoreException { TrustManagerFactory tmf = null; try { @@ -133,7 +133,7 @@ static SdsX509TrustManager createSdsX509TrustManager( if (myDelegate == null) { throw new CertStoreException("Native X509 TrustManager not found."); } - return new SdsX509TrustManager(certContext, myDelegate); + return new XdsX509TrustManager(certContext, myDelegate); } @Override @@ -148,6 +148,6 @@ protected void engineInit(ManagerFactoryParameters managerFactoryParameters) thr @Override protected TrustManager[] engineGetTrustManagers() { - return new TrustManager[] {sdsX509TrustManager}; + return new TrustManager[] {xdsX509TrustManager}; } } diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/trust/SdsX509TrustManager.java b/xds/src/main/java/io/grpc/xds/internal/sds/trust/XdsX509TrustManager.java similarity index 98% rename from xds/src/main/java/io/grpc/xds/internal/sds/trust/SdsX509TrustManager.java rename to xds/src/main/java/io/grpc/xds/internal/sds/trust/XdsX509TrustManager.java index 3178d2b3e4b..c44c42321ef 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/trust/SdsX509TrustManager.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/trust/XdsX509TrustManager.java @@ -41,7 +41,7 @@ * Extension of {@link X509ExtendedTrustManager} that implements verification of * SANs (subject-alternate-names) against the list in CertificateValidationContext. */ -final class SdsX509TrustManager extends X509ExtendedTrustManager implements X509TrustManager { +final class XdsX509TrustManager extends X509ExtendedTrustManager implements X509TrustManager { // ref: io.grpc.okhttp.internal.OkHostnameVerifier and // sun.security.x509.GeneralNameInterface @@ -52,8 +52,8 @@ final class SdsX509TrustManager extends X509ExtendedTrustManager implements X509 private final X509ExtendedTrustManager delegate; private final CertificateValidationContext certContext; - SdsX509TrustManager(@Nullable CertificateValidationContext certContext, - X509ExtendedTrustManager delegate) { + XdsX509TrustManager(@Nullable CertificateValidationContext certContext, + X509ExtendedTrustManager delegate) { checkNotNull(delegate, "delegate"); this.certContext = certContext; this.delegate = delegate; diff --git a/xds/src/test/java/io/grpc/xds/FilterChainMatchingProtocolNegotiatorsTest.java b/xds/src/test/java/io/grpc/xds/FilterChainMatchingProtocolNegotiatorsTest.java index a4efb226ce9..47208d44651 100644 --- a/xds/src/test/java/io/grpc/xds/FilterChainMatchingProtocolNegotiatorsTest.java +++ b/xds/src/test/java/io/grpc/xds/FilterChainMatchingProtocolNegotiatorsTest.java @@ -18,7 +18,7 @@ import static com.google.common.truth.Truth.assertThat; import static io.grpc.xds.XdsServerWrapper.ATTR_SERVER_ROUTING_CONFIG; -import static io.grpc.xds.internal.sds.SdsProtocolNegotiators.ATTR_SERVER_SSL_CONTEXT_PROVIDER_SUPPLIER; +import static io.grpc.xds.internal.sds.SecurityProtocolNegotiators.ATTR_SERVER_SSL_CONTEXT_PROVIDER_SUPPLIER; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; diff --git a/xds/src/test/java/io/grpc/xds/XdsClientWrapperForServerSdsTestMisc.java b/xds/src/test/java/io/grpc/xds/XdsClientWrapperForServerSdsTestMisc.java index 442bfef653c..932bc0c2d82 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientWrapperForServerSdsTestMisc.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientWrapperForServerSdsTestMisc.java @@ -18,7 +18,7 @@ import static com.google.common.truth.Truth.assertThat; import static io.grpc.xds.FilterChainMatchingProtocolNegotiators.FilterChainMatchingHandler.FilterChainSelector.NO_FILTER_CHAIN; -import static io.grpc.xds.internal.sds.SdsProtocolNegotiators.ATTR_SERVER_SSL_CONTEXT_PROVIDER_SUPPLIER; +import static io.grpc.xds.internal.sds.SecurityProtocolNegotiators.ATTR_SERVER_SSL_CONTEXT_PROVIDER_SUPPLIER; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/SdsProtocolNegotiatorsTest.java b/xds/src/test/java/io/grpc/xds/internal/sds/SecurityProtocolNegotiatorsTest.java similarity index 92% rename from xds/src/test/java/io/grpc/xds/internal/sds/SdsProtocolNegotiatorsTest.java rename to xds/src/test/java/io/grpc/xds/internal/sds/SecurityProtocolNegotiatorsTest.java index 502d2185a82..9bab877b665 100644 --- a/xds/src/test/java/io/grpc/xds/internal/sds/SdsProtocolNegotiatorsTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/sds/SecurityProtocolNegotiatorsTest.java @@ -22,7 +22,7 @@ import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_KEY_FILE; import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; -import static io.grpc.xds.internal.sds.SdsProtocolNegotiators.ATTR_SERVER_SSL_CONTEXT_PROVIDER_SUPPLIER; +import static io.grpc.xds.internal.sds.SecurityProtocolNegotiators.ATTR_SERVER_SSL_CONTEXT_PROVIDER_SUPPLIER; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -52,8 +52,8 @@ import io.grpc.xds.InternalXdsAttributes; import io.grpc.xds.TlsContextManager; import io.grpc.xds.internal.certprovider.CommonCertProviderTestUtils; -import io.grpc.xds.internal.sds.SdsProtocolNegotiators.ClientSdsHandler; -import io.grpc.xds.internal.sds.SdsProtocolNegotiators.ClientSdsProtocolNegotiator; +import io.grpc.xds.internal.sds.SecurityProtocolNegotiators.ClientSdsHandler; +import io.grpc.xds.internal.sds.SecurityProtocolNegotiators.ClientSdsProtocolNegotiator; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPipeline; @@ -83,9 +83,9 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -/** Unit tests for {@link SdsProtocolNegotiators}. */ +/** Unit tests for {@link SecurityProtocolNegotiators}. */ @RunWith(JUnit4.class) -public class SdsProtocolNegotiatorsTest { +public class SecurityProtocolNegotiatorsTest { private final GrpcHttp2ConnectionHandler grpcHandler = FakeGrpcHttp2ConnectionHandler.newHandler(); @@ -156,8 +156,8 @@ public void clientSdsHandler_addLast() SslContextProviderSupplier sslContextProviderSupplier = new SslContextProviderSupplier(upstreamTlsContext, new TlsContextManagerImpl(bootstrapInfoForClient)); - SdsProtocolNegotiators.ClientSdsHandler clientSdsHandler = - new SdsProtocolNegotiators.ClientSdsHandler(grpcHandler, sslContextProviderSupplier); + SecurityProtocolNegotiators.ClientSdsHandler clientSdsHandler = + new SecurityProtocolNegotiators.ClientSdsHandler(grpcHandler, sslContextProviderSupplier); pipeline.addLast(clientSdsHandler); channelHandlerCtx = pipeline.context(clientSdsHandler); assertNotNull(channelHandlerCtx); // clientSdsHandler ctx is non-null since we just added it @@ -221,8 +221,8 @@ public SocketAddress remoteAddress() { "google_cloud_private_spiffe-server", true, true); TlsContextManagerImpl tlsContextManager = new TlsContextManagerImpl(bootstrapInfoForServer); - SdsProtocolNegotiators.HandlerPickerHandler handlerPickerHandler = - new SdsProtocolNegotiators.HandlerPickerHandler(grpcHandler, + SecurityProtocolNegotiators.HandlerPickerHandler handlerPickerHandler = + new SecurityProtocolNegotiators.HandlerPickerHandler(grpcHandler, InternalProtocolNegotiators.serverPlaintext()); pipeline.addLast(handlerPickerHandler); channelHandlerCtx = pipeline.context(handlerPickerHandler); @@ -236,7 +236,7 @@ public SocketAddress remoteAddress() { pipeline.fireUserEventTriggered(InternalProtocolNegotiationEvent.withAttributes(event, attr)); channelHandlerCtx = pipeline.context(handlerPickerHandler); assertThat(channelHandlerCtx).isNull(); - channelHandlerCtx = pipeline.context(SdsProtocolNegotiators.ServerSdsHandler.class); + channelHandlerCtx = pipeline.context(SecurityProtocolNegotiators.ServerSdsHandler.class); assertThat(channelHandlerCtx).isNotNull(); SslContextProviderSupplier sslContextProviderSupplier = @@ -259,7 +259,7 @@ protected void onException(Throwable throwable) { Object fromFuture = future.get(2, TimeUnit.SECONDS); assertThat(fromFuture).isInstanceOf(SslContext.class); channel.runPendingTasks(); - channelHandlerCtx = pipeline.context(SdsProtocolNegotiators.ServerSdsHandler.class); + channelHandlerCtx = pipeline.context(SecurityProtocolNegotiators.ServerSdsHandler.class); assertThat(channelHandlerCtx).isNull(); // pipeline should only have SslHandler and ServerTlsHandler @@ -287,8 +287,8 @@ public SocketAddress localAddress() { }; pipeline = channel.pipeline(); - SdsProtocolNegotiators.HandlerPickerHandler handlerPickerHandler = - new SdsProtocolNegotiators.HandlerPickerHandler( + SecurityProtocolNegotiators.HandlerPickerHandler handlerPickerHandler = + new SecurityProtocolNegotiators.HandlerPickerHandler( grpcHandler, mockProtocolNegotiator); pipeline.addLast(handlerPickerHandler); channelHandlerCtx = pipeline.context(handlerPickerHandler); @@ -313,8 +313,8 @@ public void serverSdsHandler_nullTlsContext_expectFallbackProtocolNegotiator() { ChannelHandler mockChannelHandler = mock(ChannelHandler.class); ProtocolNegotiator mockProtocolNegotiator = mock(ProtocolNegotiator.class); when(mockProtocolNegotiator.newHandler(grpcHandler)).thenReturn(mockChannelHandler); - SdsProtocolNegotiators.HandlerPickerHandler handlerPickerHandler = - new SdsProtocolNegotiators.HandlerPickerHandler( + SecurityProtocolNegotiators.HandlerPickerHandler handlerPickerHandler = + new SecurityProtocolNegotiators.HandlerPickerHandler( grpcHandler, mockProtocolNegotiator); pipeline.addLast(handlerPickerHandler); channelHandlerCtx = pipeline.context(handlerPickerHandler); @@ -333,8 +333,8 @@ public void serverSdsHandler_nullTlsContext_expectFallbackProtocolNegotiator() { @Test public void nullTlsContext_nullFallbackProtocolNegotiator_expectException() { - SdsProtocolNegotiators.HandlerPickerHandler handlerPickerHandler = - new SdsProtocolNegotiators.HandlerPickerHandler( + SecurityProtocolNegotiators.HandlerPickerHandler handlerPickerHandler = + new SecurityProtocolNegotiators.HandlerPickerHandler( grpcHandler, null); pipeline.addLast(handlerPickerHandler); channelHandlerCtx = pipeline.context(handlerPickerHandler); @@ -368,8 +368,8 @@ public void clientSdsProtocolNegotiatorNewHandler_fireProtocolNegotiationEvent() SslContextProviderSupplier sslContextProviderSupplier = new SslContextProviderSupplier(upstreamTlsContext, new TlsContextManagerImpl(bootstrapInfoForClient)); - SdsProtocolNegotiators.ClientSdsHandler clientSdsHandler = - new SdsProtocolNegotiators.ClientSdsHandler(grpcHandler, sslContextProviderSupplier); + SecurityProtocolNegotiators.ClientSdsHandler clientSdsHandler = + new SecurityProtocolNegotiators.ClientSdsHandler(grpcHandler, sslContextProviderSupplier); pipeline.addLast(clientSdsHandler); channelHandlerCtx = pipeline.context(clientSdsHandler); diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/trust/SdsTrustManagerFactoryTest.java b/xds/src/test/java/io/grpc/xds/internal/sds/trust/XdsTrustManagerFactoryTest.java similarity index 78% rename from xds/src/test/java/io/grpc/xds/internal/sds/trust/SdsTrustManagerFactoryTest.java rename to xds/src/test/java/io/grpc/xds/internal/sds/trust/XdsTrustManagerFactoryTest.java index f693261f929..ac2db38d190 100644 --- a/xds/src/test/java/io/grpc/xds/internal/sds/trust/SdsTrustManagerFactoryTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/sds/trust/XdsTrustManagerFactoryTest.java @@ -38,22 +38,22 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -/** Unit tests for {@link SdsTrustManagerFactory}. */ +/** Unit tests for {@link XdsTrustManagerFactory}. */ @RunWith(JUnit4.class) -public class SdsTrustManagerFactoryTest { +public class XdsTrustManagerFactoryTest { @Test public void constructor_fromFile() throws CertificateException, IOException, CertStoreException { - SdsTrustManagerFactory factory = - new SdsTrustManagerFactory(getCertContextFromPath(CA_PEM_FILE)); + XdsTrustManagerFactory factory = + new XdsTrustManagerFactory(getCertContextFromPath(CA_PEM_FILE)); assertThat(factory).isNotNull(); TrustManager[] tms = factory.getTrustManagers(); assertThat(tms).isNotNull(); assertThat(tms).hasLength(1); TrustManager myTm = tms[0]; - assertThat(myTm).isInstanceOf(SdsX509TrustManager.class); - SdsX509TrustManager sdsX509TrustManager = (SdsX509TrustManager) myTm; - X509Certificate[] acceptedIssuers = sdsX509TrustManager.getAcceptedIssuers(); + assertThat(myTm).isInstanceOf(XdsX509TrustManager.class); + XdsX509TrustManager xdsX509TrustManager = (XdsX509TrustManager) myTm; + X509Certificate[] acceptedIssuers = xdsX509TrustManager.getAcceptedIssuers(); assertThat(acceptedIssuers).isNotNull(); assertThat(acceptedIssuers).hasLength(1); X509Certificate caCert = acceptedIssuers[0]; @@ -64,16 +64,16 @@ public void constructor_fromFile() throws CertificateException, IOException, Cer @Test public void constructor_fromInlineBytes() throws CertificateException, IOException, CertStoreException { - SdsTrustManagerFactory factory = - new SdsTrustManagerFactory(getCertContextFromPathAsInlineBytes(CA_PEM_FILE)); + XdsTrustManagerFactory factory = + new XdsTrustManagerFactory(getCertContextFromPathAsInlineBytes(CA_PEM_FILE)); assertThat(factory).isNotNull(); TrustManager[] tms = factory.getTrustManagers(); assertThat(tms).isNotNull(); assertThat(tms).hasLength(1); TrustManager myTm = tms[0]; - assertThat(myTm).isInstanceOf(SdsX509TrustManager.class); - SdsX509TrustManager sdsX509TrustManager = (SdsX509TrustManager) myTm; - X509Certificate[] acceptedIssuers = sdsX509TrustManager.getAcceptedIssuers(); + assertThat(myTm).isInstanceOf(XdsX509TrustManager.class); + XdsX509TrustManager xdsX509TrustManager = (XdsX509TrustManager) myTm; + X509Certificate[] acceptedIssuers = xdsX509TrustManager.getAcceptedIssuers(); assertThat(acceptedIssuers).isNotNull(); assertThat(acceptedIssuers).hasLength(1); X509Certificate caCert = acceptedIssuers[0]; @@ -87,16 +87,16 @@ public void constructor_fromRootCert() X509Certificate x509Cert = TestUtils.loadX509Cert(CA_PEM_FILE); CertificateValidationContext staticValidationContext = buildStaticValidationContext("san1", "san2"); - SdsTrustManagerFactory factory = - new SdsTrustManagerFactory(new X509Certificate[]{x509Cert}, staticValidationContext); + XdsTrustManagerFactory factory = + new XdsTrustManagerFactory(new X509Certificate[]{x509Cert}, staticValidationContext); assertThat(factory).isNotNull(); TrustManager[] tms = factory.getTrustManagers(); assertThat(tms).isNotNull(); assertThat(tms).hasLength(1); TrustManager myTm = tms[0]; - assertThat(myTm).isInstanceOf(SdsX509TrustManager.class); - SdsX509TrustManager sdsX509TrustManager = (SdsX509TrustManager) myTm; - X509Certificate[] acceptedIssuers = sdsX509TrustManager.getAcceptedIssuers(); + assertThat(myTm).isInstanceOf(XdsX509TrustManager.class); + XdsX509TrustManager xdsX509TrustManager = (XdsX509TrustManager) myTm; + X509Certificate[] acceptedIssuers = xdsX509TrustManager.getAcceptedIssuers(); assertThat(acceptedIssuers).isNotNull(); assertThat(acceptedIssuers).hasLength(1); X509Certificate caCert = acceptedIssuers[0]; @@ -110,12 +110,12 @@ public void constructorRootCert_checkServerTrusted() X509Certificate x509Cert = TestUtils.loadX509Cert(CA_PEM_FILE); CertificateValidationContext staticValidationContext = buildStaticValidationContext("san1", "waterzooi.test.google.be"); - SdsTrustManagerFactory factory = - new SdsTrustManagerFactory(new X509Certificate[]{x509Cert}, staticValidationContext); - SdsX509TrustManager sdsX509TrustManager = (SdsX509TrustManager) factory.getTrustManagers()[0]; + XdsTrustManagerFactory factory = + new XdsTrustManagerFactory(new X509Certificate[]{x509Cert}, staticValidationContext); + XdsX509TrustManager xdsX509TrustManager = (XdsX509TrustManager) factory.getTrustManagers()[0]; X509Certificate[] serverChain = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); - sdsX509TrustManager.checkServerTrusted(serverChain, "RSA"); + xdsX509TrustManager.checkServerTrusted(serverChain, "RSA"); } @Test @@ -123,7 +123,7 @@ public void constructorRootCert_nonStaticContext_throwsException() throws CertificateException, IOException, CertStoreException { X509Certificate x509Cert = TestUtils.loadX509Cert(CA_PEM_FILE); try { - new SdsTrustManagerFactory( + new XdsTrustManagerFactory( new X509Certificate[] {x509Cert}, getCertContextFromPath(CA_PEM_FILE)); Assert.fail("no exception thrown"); } catch (IllegalArgumentException expected) { @@ -139,13 +139,13 @@ public void constructorRootCert_checkServerTrusted_throwsException() X509Certificate x509Cert = TestUtils.loadX509Cert(CA_PEM_FILE); CertificateValidationContext staticValidationContext = buildStaticValidationContext("san1", "san2"); - SdsTrustManagerFactory factory = - new SdsTrustManagerFactory(new X509Certificate[]{x509Cert}, staticValidationContext); - SdsX509TrustManager sdsX509TrustManager = (SdsX509TrustManager) factory.getTrustManagers()[0]; + XdsTrustManagerFactory factory = + new XdsTrustManagerFactory(new X509Certificate[]{x509Cert}, staticValidationContext); + XdsX509TrustManager xdsX509TrustManager = (XdsX509TrustManager) factory.getTrustManagers()[0]; X509Certificate[] serverChain = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); try { - sdsX509TrustManager.checkServerTrusted(serverChain, "RSA"); + xdsX509TrustManager.checkServerTrusted(serverChain, "RSA"); Assert.fail("no exception thrown"); } catch (CertificateException expected) { assertThat(expected) @@ -160,13 +160,13 @@ public void constructorRootCert_checkClientTrusted_throwsException() X509Certificate x509Cert = TestUtils.loadX509Cert(CA_PEM_FILE); CertificateValidationContext staticValidationContext = buildStaticValidationContext("san1", "san2"); - SdsTrustManagerFactory factory = - new SdsTrustManagerFactory(new X509Certificate[]{x509Cert}, staticValidationContext); - SdsX509TrustManager sdsX509TrustManager = (SdsX509TrustManager) factory.getTrustManagers()[0]; + XdsTrustManagerFactory factory = + new XdsTrustManagerFactory(new X509Certificate[]{x509Cert}, staticValidationContext); + XdsX509TrustManager xdsX509TrustManager = (XdsX509TrustManager) factory.getTrustManagers()[0]; X509Certificate[] clientChain = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); try { - sdsX509TrustManager.checkClientTrusted(clientChain, "RSA"); + xdsX509TrustManager.checkClientTrusted(clientChain, "RSA"); Assert.fail("no exception thrown"); } catch (CertificateException expected) { assertThat(expected) @@ -178,35 +178,35 @@ public void constructorRootCert_checkClientTrusted_throwsException() @Test public void checkServerTrusted_goodCert() throws CertificateException, IOException, CertStoreException { - SdsTrustManagerFactory factory = - new SdsTrustManagerFactory(getCertContextFromPath(CA_PEM_FILE)); - SdsX509TrustManager sdsX509TrustManager = (SdsX509TrustManager) factory.getTrustManagers()[0]; + XdsTrustManagerFactory factory = + new XdsTrustManagerFactory(getCertContextFromPath(CA_PEM_FILE)); + XdsX509TrustManager xdsX509TrustManager = (XdsX509TrustManager) factory.getTrustManagers()[0]; X509Certificate[] serverChain = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); - sdsX509TrustManager.checkServerTrusted(serverChain, "RSA"); + xdsX509TrustManager.checkServerTrusted(serverChain, "RSA"); } @Test public void checkClientTrusted_goodCert() throws CertificateException, IOException, CertStoreException { - SdsTrustManagerFactory factory = - new SdsTrustManagerFactory(getCertContextFromPath(CA_PEM_FILE)); - SdsX509TrustManager sdsX509TrustManager = (SdsX509TrustManager) factory.getTrustManagers()[0]; + XdsTrustManagerFactory factory = + new XdsTrustManagerFactory(getCertContextFromPath(CA_PEM_FILE)); + XdsX509TrustManager xdsX509TrustManager = (XdsX509TrustManager) factory.getTrustManagers()[0]; X509Certificate[] clientChain = CertificateUtils.toX509Certificates(TestUtils.loadCert(CLIENT_PEM_FILE)); - sdsX509TrustManager.checkClientTrusted(clientChain, "RSA"); + xdsX509TrustManager.checkClientTrusted(clientChain, "RSA"); } @Test public void checkServerTrusted_badCert_throwsException() throws CertificateException, IOException, CertStoreException { - SdsTrustManagerFactory factory = - new SdsTrustManagerFactory(getCertContextFromPath(CA_PEM_FILE)); - SdsX509TrustManager sdsX509TrustManager = (SdsX509TrustManager) factory.getTrustManagers()[0]; + XdsTrustManagerFactory factory = + new XdsTrustManagerFactory(getCertContextFromPath(CA_PEM_FILE)); + XdsX509TrustManager xdsX509TrustManager = (XdsX509TrustManager) factory.getTrustManagers()[0]; X509Certificate[] serverChain = CertificateUtils.toX509Certificates(TestUtils.loadCert(BAD_SERVER_PEM_FILE)); try { - sdsX509TrustManager.checkServerTrusted(serverChain, "RSA"); + xdsX509TrustManager.checkServerTrusted(serverChain, "RSA"); Assert.fail("no exception thrown"); } catch (CertificateException expected) { assertThat(expected) @@ -218,13 +218,13 @@ public void checkServerTrusted_badCert_throwsException() @Test public void checkClientTrusted_badCert_throwsException() throws CertificateException, IOException, CertStoreException { - SdsTrustManagerFactory factory = - new SdsTrustManagerFactory(getCertContextFromPath(CA_PEM_FILE)); - SdsX509TrustManager sdsX509TrustManager = (SdsX509TrustManager) factory.getTrustManagers()[0]; + XdsTrustManagerFactory factory = + new XdsTrustManagerFactory(getCertContextFromPath(CA_PEM_FILE)); + XdsX509TrustManager xdsX509TrustManager = (XdsX509TrustManager) factory.getTrustManagers()[0]; X509Certificate[] clientChain = CertificateUtils.toX509Certificates(TestUtils.loadCert(BAD_CLIENT_PEM_FILE)); try { - sdsX509TrustManager.checkClientTrusted(clientChain, "RSA"); + xdsX509TrustManager.checkClientTrusted(clientChain, "RSA"); Assert.fail("no exception thrown"); } catch (CertificateException expected) { assertThat(expected) diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/trust/SdsX509TrustManagerTest.java b/xds/src/test/java/io/grpc/xds/internal/sds/trust/XdsX509TrustManagerTest.java similarity index 92% rename from xds/src/test/java/io/grpc/xds/internal/sds/trust/SdsX509TrustManagerTest.java rename to xds/src/test/java/io/grpc/xds/internal/sds/trust/XdsX509TrustManagerTest.java index 7ceae30b1e8..fe68adc2ac8 100644 --- a/xds/src/test/java/io/grpc/xds/internal/sds/trust/SdsX509TrustManagerTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/sds/trust/XdsX509TrustManagerTest.java @@ -54,10 +54,10 @@ import org.mockito.junit.MockitoRule; /** - * Unit tests for {@link SdsX509TrustManager}. + * Unit tests for {@link XdsX509TrustManager}. */ @RunWith(JUnit4.class) -public class SdsX509TrustManagerTest { +public class XdsX509TrustManagerTest { @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule(); @@ -68,11 +68,11 @@ public class SdsX509TrustManagerTest { @Mock private SSLSession mockSession; - private SdsX509TrustManager trustManager; + private XdsX509TrustManager trustManager; @Test public void nullCertContextTest() throws CertificateException, IOException { - trustManager = new SdsX509TrustManager(null, mockDelegate); + trustManager = new XdsX509TrustManager(null, mockDelegate); X509Certificate[] certs = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); trustManager.verifySubjectAltNameInChain(certs); @@ -81,7 +81,7 @@ public void nullCertContextTest() throws CertificateException, IOException { @Test public void emptySanListContextTest() throws CertificateException, IOException { CertificateValidationContext certContext = CertificateValidationContext.getDefaultInstance(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); X509Certificate[] certs = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); trustManager.verifySubjectAltNameInChain(certs); @@ -93,7 +93,7 @@ public void missingPeerCerts() { @SuppressWarnings("deprecation") CertificateValidationContext certContext = CertificateValidationContext.newBuilder().addMatchSubjectAltNames(stringMatcher).build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); try { trustManager.verifySubjectAltNameInChain(null); fail("no exception thrown"); @@ -108,7 +108,7 @@ public void emptyArrayPeerCerts() { @SuppressWarnings("deprecation") CertificateValidationContext certContext = CertificateValidationContext.newBuilder().addMatchSubjectAltNames(stringMatcher).build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); try { trustManager.verifySubjectAltNameInChain(new X509Certificate[0]); fail("no exception thrown"); @@ -123,7 +123,7 @@ public void noSansInPeerCerts() throws CertificateException, IOException { @SuppressWarnings("deprecation") CertificateValidationContext certContext = CertificateValidationContext.newBuilder().addMatchSubjectAltNames(stringMatcher).build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); X509Certificate[] certs = CertificateUtils.toX509Certificates(TestUtils.loadCert(CLIENT_PEM_FILE)); try { @@ -144,7 +144,7 @@ public void oneSanInPeerCertsVerifies() throws CertificateException, IOException @SuppressWarnings("deprecation") CertificateValidationContext certContext = CertificateValidationContext.newBuilder().addMatchSubjectAltNames(stringMatcher).build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); X509Certificate[] certs = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); trustManager.verifySubjectAltNameInChain(certs); @@ -161,7 +161,7 @@ public void oneSanInPeerCertsVerifies_differentCase_expectException() @SuppressWarnings("deprecation") CertificateValidationContext certContext = CertificateValidationContext.newBuilder().addMatchSubjectAltNames(stringMatcher).build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); X509Certificate[] certs = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); try { @@ -179,7 +179,7 @@ public void oneSanInPeerCertsVerifies_ignoreCase() throws CertificateException, @SuppressWarnings("deprecation") CertificateValidationContext certContext = CertificateValidationContext.newBuilder().addMatchSubjectAltNames(stringMatcher).build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); X509Certificate[] certs = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); trustManager.verifySubjectAltNameInChain(certs); @@ -195,7 +195,7 @@ public void oneSanInPeerCerts_prefix() throws CertificateException, IOException @SuppressWarnings("deprecation") CertificateValidationContext certContext = CertificateValidationContext.newBuilder().addMatchSubjectAltNames(stringMatcher).build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); X509Certificate[] certs = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); trustManager.verifySubjectAltNameInChain(certs); @@ -209,7 +209,7 @@ public void oneSanInPeerCertsPrefix_differentCase_expectException() @SuppressWarnings("deprecation") CertificateValidationContext certContext = CertificateValidationContext.newBuilder().addMatchSubjectAltNames(stringMatcher).build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); X509Certificate[] certs = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); try { @@ -230,7 +230,7 @@ public void oneSanInPeerCerts_prefixIgnoreCase() throws CertificateException, IO @SuppressWarnings("deprecation") CertificateValidationContext certContext = CertificateValidationContext.newBuilder().addMatchSubjectAltNames(stringMatcher).build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); X509Certificate[] certs = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); trustManager.verifySubjectAltNameInChain(certs); @@ -243,7 +243,7 @@ public void oneSanInPeerCerts_suffix() throws CertificateException, IOException @SuppressWarnings("deprecation") CertificateValidationContext certContext = CertificateValidationContext.newBuilder().addMatchSubjectAltNames(stringMatcher).build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); X509Certificate[] certs = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); trustManager.verifySubjectAltNameInChain(certs); @@ -257,7 +257,7 @@ public void oneSanInPeerCertsSuffix_differentCase_expectException() @SuppressWarnings("deprecation") CertificateValidationContext certContext = CertificateValidationContext.newBuilder().addMatchSubjectAltNames(stringMatcher).build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); X509Certificate[] certs = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); try { @@ -275,7 +275,7 @@ public void oneSanInPeerCerts_suffixIgnoreCase() throws CertificateException, IO @SuppressWarnings("deprecation") CertificateValidationContext certContext = CertificateValidationContext.newBuilder().addMatchSubjectAltNames(stringMatcher).build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); X509Certificate[] certs = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); trustManager.verifySubjectAltNameInChain(certs); @@ -288,7 +288,7 @@ public void oneSanInPeerCerts_substring() throws CertificateException, IOExcepti @SuppressWarnings("deprecation") CertificateValidationContext certContext = CertificateValidationContext.newBuilder().addMatchSubjectAltNames(stringMatcher).build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); X509Certificate[] certs = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); trustManager.verifySubjectAltNameInChain(certs); @@ -302,7 +302,7 @@ public void oneSanInPeerCertsSubstring_differentCase_expectException() @SuppressWarnings("deprecation") CertificateValidationContext certContext = CertificateValidationContext.newBuilder().addMatchSubjectAltNames(stringMatcher).build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); X509Certificate[] certs = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); try { @@ -320,7 +320,7 @@ public void oneSanInPeerCerts_substringIgnoreCase() throws CertificateException, @SuppressWarnings("deprecation") CertificateValidationContext certContext = CertificateValidationContext.newBuilder().addMatchSubjectAltNames(stringMatcher).build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); X509Certificate[] certs = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); trustManager.verifySubjectAltNameInChain(certs); @@ -336,7 +336,7 @@ public void oneSanInPeerCerts_safeRegex() throws CertificateException, IOExcepti @SuppressWarnings("deprecation") CertificateValidationContext certContext = CertificateValidationContext.newBuilder().addMatchSubjectAltNames(stringMatcher).build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); X509Certificate[] certs = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); trustManager.verifySubjectAltNameInChain(certs); @@ -352,7 +352,7 @@ public void oneSanInPeerCerts_safeRegex1() throws CertificateException, IOExcept @SuppressWarnings("deprecation") CertificateValidationContext certContext = CertificateValidationContext.newBuilder().addMatchSubjectAltNames(stringMatcher).build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); X509Certificate[] certs = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); trustManager.verifySubjectAltNameInChain(certs); @@ -368,7 +368,7 @@ public void oneSanInPeerCerts_safeRegex_ipAddress() throws CertificateException, @SuppressWarnings("deprecation") CertificateValidationContext certContext = CertificateValidationContext.newBuilder().addMatchSubjectAltNames(stringMatcher).build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); X509Certificate[] certs = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); trustManager.verifySubjectAltNameInChain(certs); @@ -384,7 +384,7 @@ public void oneSanInPeerCerts_safeRegex_noMatch() throws CertificateException, I @SuppressWarnings("deprecation") CertificateValidationContext certContext = CertificateValidationContext.newBuilder().addMatchSubjectAltNames(stringMatcher).build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); X509Certificate[] certs = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); try { @@ -407,7 +407,7 @@ public void oneSanInPeerCertsVerifiesMultipleVerifySans() .addMatchSubjectAltNames(stringMatcher) .addMatchSubjectAltNames(stringMatcher1) .build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); X509Certificate[] certs = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); trustManager.verifySubjectAltNameInChain(certs); @@ -420,7 +420,7 @@ public void oneSanInPeerCertsNotFoundException() @SuppressWarnings("deprecation") CertificateValidationContext certContext = CertificateValidationContext.newBuilder().addMatchSubjectAltNames(stringMatcher).build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); X509Certificate[] certs = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); try { @@ -443,7 +443,7 @@ public void wildcardSanInPeerCertsVerifiesMultipleVerifySans() .addMatchSubjectAltNames(stringMatcher) .addMatchSubjectAltNames(stringMatcher1) // should match suffix test.youTube.Com .build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); X509Certificate[] certs = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); trustManager.verifySubjectAltNameInChain(certs); @@ -461,7 +461,7 @@ public void wildcardSanInPeerCertsVerifiesMultipleVerifySans1() .addMatchSubjectAltNames(stringMatcher) .addMatchSubjectAltNames(stringMatcher1) // should contain est.Google.f .build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); X509Certificate[] certs = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); trustManager.verifySubjectAltNameInChain(certs); @@ -478,7 +478,7 @@ public void wildcardSanInPeerCertsSubdomainMismatch() @SuppressWarnings("deprecation") CertificateValidationContext certContext = CertificateValidationContext.newBuilder().addMatchSubjectAltNames(stringMatcher).build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); X509Certificate[] certs = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); try { @@ -499,7 +499,7 @@ public void oneIpAddressInPeerCertsVerifies() throws CertificateException, IOExc .addMatchSubjectAltNames(stringMatcher) .addMatchSubjectAltNames(stringMatcher1) .build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); X509Certificate[] certs = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); trustManager.verifySubjectAltNameInChain(certs); @@ -515,7 +515,7 @@ public void oneIpAddressInPeerCertsMismatch() throws CertificateException, IOExc .addMatchSubjectAltNames(stringMatcher) .addMatchSubjectAltNames(stringMatcher1) .build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); X509Certificate[] certs = CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); try { @@ -590,7 +590,7 @@ public void unsupportedAltNameType() throws CertificateException, IOException { @SuppressWarnings("deprecation") CertificateValidationContext certContext = CertificateValidationContext.newBuilder().addMatchSubjectAltNames(stringMatcher).build(); - trustManager = new SdsX509TrustManager(certContext, mockDelegate); + trustManager = new XdsX509TrustManager(certContext, mockDelegate); X509Certificate mockCert = mock(X509Certificate.class); when(mockCert.getSubjectAlternativeNames()) @@ -629,7 +629,7 @@ private SSLParameters buildTrustManagerAndGetSslParameters() throws CertificateException, IOException, CertStoreException { X509Certificate[] caCerts = CertificateUtils.toX509Certificates(TestUtils.loadCert(CA_PEM_FILE)); - trustManager = SdsTrustManagerFactory.createSdsX509TrustManager(caCerts, + trustManager = XdsTrustManagerFactory.createSdsX509TrustManager(caCerts, null); when(mockSession.getProtocol()).thenReturn("TLSv1.2"); when(mockSession.getPeerHost()).thenReturn("peer-host-from-mock"); From 24287b0b149448fe859f702dbbde5bc195c28af7 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 8 Sep 2022 16:16:11 -0700 Subject: [PATCH 48/72] binder: Fix expected result of security test The test name includes "fails" but it asserts the result is OK. The test was added in #9428. The binder tests are passing on the Android CI, but for some reason the tests with sdk >= 29 are skipped. Robolectric 4.8 claims API level 32 support and the tests are skipped even when using Java 11 and 17. --- binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java b/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java index 909547a902b..6a692090549 100644 --- a/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java +++ b/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java @@ -460,7 +460,7 @@ public void testIsProfileOwnerOnOrgOwned_failsForProfileOwnerOnNonOrgOwned() thr policy = SecurityPolicies.isProfileOwnerOnOrganizationOwnedDevice(appContext); - assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode()); + assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode()); } @Test From 88a035e2c2599bf8e7599fc1dde394a23a07c6c1 Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Fri, 9 Sep 2022 09:21:03 -0700 Subject: [PATCH 49/72] xds: rename package io.grpc.xds.internal.sds to io.grpc.xds.internal.security (#9532) --- .../io/grpc/xds/ClusterImplLoadBalancer.java | 2 +- .../io/grpc/xds/EnvoyServerProtoData.java | 2 +- ...ilterChainMatchingProtocolNegotiators.java | 4 ++-- .../io/grpc/xds/InternalXdsAttributes.java | 4 ++-- .../grpc/xds/SharedXdsClientPoolProvider.java | 2 +- .../java/io/grpc/xds/TlsContextManager.java | 2 +- .../io/grpc/xds/XdsChannelCredentials.java | 2 +- .../io/grpc/xds/XdsServerCredentials.java | 2 +- .../java/io/grpc/xds/XdsServerWrapper.java | 2 +- .../CertProviderClientSslContextProvider.java | 2 +- .../CertProviderServerSslContextProvider.java | 2 +- .../CertProviderSslContextProvider.java | 4 ++-- .../certprovider/CertificateProvider.java | 2 +- .../CertificateProviderStore.java | 2 +- .../FileWatcherCertificateProvider.java | 2 +- .../ClientSslContextProviderFactory.java | 4 ++-- .../internal/{sds => security}/Closeable.java | 2 +- .../CommonTlsContextUtil.java | 2 +- .../DynamicSslContextProvider.java | 2 +- .../ReferenceCountingMap.java | 2 +- .../SecurityProtocolNegotiators.java | 2 +- .../ServerSslContextProviderFactory.java | 4 ++-- .../{sds => security}/SslContextProvider.java | 4 ++-- .../SslContextProviderSupplier.java | 2 +- .../TlsContextManagerImpl.java | 4 ++-- .../trust/CertificateUtils.java | 2 +- .../trust/XdsTrustManagerFactory.java | 6 ++--- .../trust/XdsX509TrustManager.java | 2 +- .../io/grpc/xds/CdsLoadBalancer2Test.java | 2 +- .../io/grpc/xds/ClientXdsClientTestBase.java | 2 +- .../grpc/xds/ClusterImplLoadBalancerTest.java | 6 ++--- .../xds/ClusterResolverLoadBalancerTest.java | 2 +- .../grpc/xds/CommonBootstrapperTestUtils.java | 2 +- ...rChainMatchingProtocolNegotiatorsTest.java | 6 ++--- .../XdsClientWrapperForServerSdsTestMisc.java | 8 +++---- .../io/grpc/xds/XdsSdsClientServerTest.java | 24 +++++++++---------- .../io/grpc/xds/XdsServerBuilderTest.java | 2 +- .../io/grpc/xds/XdsServerWrapperTest.java | 4 ++-- ...tProviderClientSslContextProviderTest.java | 18 +++++++------- ...tProviderServerSslContextProviderTest.java | 18 +++++++------- .../CommonCertProviderTestUtils.java | 2 +- .../FileWatcherCertificateProviderTest.java | 14 +++++------ .../ClientSslContextProviderFactoryTest.java | 2 +- .../CommonTlsContextTestsUtil.java | 4 ++-- .../ReferenceCountingMapTest.java | 4 ++-- .../SecurityProtocolNegotiatorsTest.java | 18 +++++++------- .../ServerSslContextProviderFactoryTest.java | 6 ++--- .../SslContextProviderSupplierTest.java | 2 +- .../TlsContextManagerTest.java | 18 +++++++------- .../trust/XdsTrustManagerFactoryTest.java | 12 +++++----- .../trust/XdsX509TrustManagerTest.java | 10 ++++---- 51 files changed, 129 insertions(+), 131 deletions(-) rename xds/src/main/java/io/grpc/xds/internal/{sds => security}/ClientSslContextProviderFactory.java (95%) rename xds/src/main/java/io/grpc/xds/internal/{sds => security}/Closeable.java (94%) rename xds/src/main/java/io/grpc/xds/internal/{sds => security}/CommonTlsContextUtil.java (98%) rename xds/src/main/java/io/grpc/xds/internal/{sds => security}/DynamicSslContextProvider.java (99%) rename xds/src/main/java/io/grpc/xds/internal/{sds => security}/ReferenceCountingMap.java (99%) rename xds/src/main/java/io/grpc/xds/internal/{sds => security}/SecurityProtocolNegotiators.java (99%) rename xds/src/main/java/io/grpc/xds/internal/{sds => security}/ServerSslContextProviderFactory.java (95%) rename xds/src/main/java/io/grpc/xds/internal/{sds => security}/SslContextProvider.java (97%) rename xds/src/main/java/io/grpc/xds/internal/{sds => security}/SslContextProviderSupplier.java (99%) rename xds/src/main/java/io/grpc/xds/internal/{sds => security}/TlsContextManagerImpl.java (97%) rename xds/src/main/java/io/grpc/xds/internal/{sds => security}/trust/CertificateUtils.java (99%) rename xds/src/main/java/io/grpc/xds/internal/{sds => security}/trust/XdsTrustManagerFactory.java (96%) rename xds/src/main/java/io/grpc/xds/internal/{sds => security}/trust/XdsX509TrustManager.java (99%) rename xds/src/test/java/io/grpc/xds/internal/{sds => security}/ClientSslContextProviderFactoryTest.java (99%) rename xds/src/test/java/io/grpc/xds/internal/{sds => security}/CommonTlsContextTestsUtil.java (99%) rename xds/src/test/java/io/grpc/xds/internal/{sds => security}/ReferenceCountingMapTest.java (97%) rename xds/src/test/java/io/grpc/xds/internal/{sds => security}/SecurityProtocolNegotiatorsTest.java (96%) rename xds/src/test/java/io/grpc/xds/internal/{sds => security}/ServerSslContextProviderFactoryTest.java (97%) rename xds/src/test/java/io/grpc/xds/internal/{sds => security}/SslContextProviderSupplierTest.java (99%) rename xds/src/test/java/io/grpc/xds/internal/{sds => security}/TlsContextManagerTest.java (91%) rename xds/src/test/java/io/grpc/xds/internal/{sds => security}/trust/XdsTrustManagerFactoryTest.java (95%) rename xds/src/test/java/io/grpc/xds/internal/{sds => security}/trust/XdsX509TrustManagerTest.java (98%) diff --git a/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java index e9796267a85..5241c55a4ab 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java @@ -45,7 +45,7 @@ import io.grpc.xds.XdsLogger.XdsLogLevel; import io.grpc.xds.XdsNameResolverProvider.CallCounterProvider; import io.grpc.xds.XdsSubchannelPickers.ErrorPicker; -import io.grpc.xds.internal.sds.SslContextProviderSupplier; +import io.grpc.xds.internal.security.SslContextProviderSupplier; import java.util.ArrayList; import java.util.Collections; import java.util.List; diff --git a/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java b/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java index fad5700f5b1..5015c56ba92 100644 --- a/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java +++ b/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java @@ -22,7 +22,7 @@ import com.google.protobuf.util.Durations; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; import io.grpc.Internal; -import io.grpc.xds.internal.sds.SslContextProviderSupplier; +import io.grpc.xds.internal.security.SslContextProviderSupplier; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Objects; diff --git a/xds/src/main/java/io/grpc/xds/FilterChainMatchingProtocolNegotiators.java b/xds/src/main/java/io/grpc/xds/FilterChainMatchingProtocolNegotiators.java index 1bea9754189..fa03b2add4d 100644 --- a/xds/src/main/java/io/grpc/xds/FilterChainMatchingProtocolNegotiators.java +++ b/xds/src/main/java/io/grpc/xds/FilterChainMatchingProtocolNegotiators.java @@ -20,7 +20,7 @@ import static io.grpc.xds.InternalXdsAttributes.ATTR_DRAIN_GRACE_NANOS; import static io.grpc.xds.InternalXdsAttributes.ATTR_FILTER_CHAIN_SELECTOR_MANAGER; import static io.grpc.xds.XdsServerWrapper.ATTR_SERVER_ROUTING_CONFIG; -import static io.grpc.xds.internal.sds.SecurityProtocolNegotiators.ATTR_SERVER_SSL_CONTEXT_PROVIDER_SUPPLIER; +import static io.grpc.xds.internal.security.SecurityProtocolNegotiators.ATTR_SERVER_SSL_CONTEXT_PROVIDER_SUPPLIER; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; @@ -41,7 +41,7 @@ import io.grpc.xds.EnvoyServerProtoData.FilterChainMatch; import io.grpc.xds.XdsServerWrapper.ServerRoutingConfig; import io.grpc.xds.internal.Matchers.CidrMatcher; -import io.grpc.xds.internal.sds.SslContextProviderSupplier; +import io.grpc.xds.internal.security.SslContextProviderSupplier; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; diff --git a/xds/src/main/java/io/grpc/xds/InternalXdsAttributes.java b/xds/src/main/java/io/grpc/xds/InternalXdsAttributes.java index 1750fbaf731..448e5fbd258 100644 --- a/xds/src/main/java/io/grpc/xds/InternalXdsAttributes.java +++ b/xds/src/main/java/io/grpc/xds/InternalXdsAttributes.java @@ -23,7 +23,7 @@ import io.grpc.NameResolver; import io.grpc.internal.ObjectPool; import io.grpc.xds.XdsNameResolverProvider.CallCounterProvider; -import io.grpc.xds.internal.sds.SslContextProviderSupplier; +import io.grpc.xds.internal.security.SslContextProviderSupplier; import java.util.Map; /** @@ -37,7 +37,7 @@ public final class InternalXdsAttributes { @Grpc.TransportAttr public static final Attributes.Key ATTR_SSL_CONTEXT_PROVIDER_SUPPLIER = - Attributes.Key.create("io.grpc.xds.internal.sds.SslContextProviderSupplier"); + Attributes.Key.create("io.grpc.xds.internal.security.SslContextProviderSupplier"); /** * Attribute key for passing around the XdsClient object pool across NameResolver/LoadBalancers. diff --git a/xds/src/main/java/io/grpc/xds/SharedXdsClientPoolProvider.java b/xds/src/main/java/io/grpc/xds/SharedXdsClientPoolProvider.java index 1c8fe0bad6d..e70919a9ca4 100644 --- a/xds/src/main/java/io/grpc/xds/SharedXdsClientPoolProvider.java +++ b/xds/src/main/java/io/grpc/xds/SharedXdsClientPoolProvider.java @@ -28,7 +28,7 @@ import io.grpc.xds.Bootstrapper.BootstrapInfo; import io.grpc.xds.ClientXdsClient.XdsChannelFactory; import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory; -import io.grpc.xds.internal.sds.TlsContextManagerImpl; +import io.grpc.xds.internal.security.TlsContextManagerImpl; import java.util.Map; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicReference; diff --git a/xds/src/main/java/io/grpc/xds/TlsContextManager.java b/xds/src/main/java/io/grpc/xds/TlsContextManager.java index e35eb68f219..772a6cff102 100644 --- a/xds/src/main/java/io/grpc/xds/TlsContextManager.java +++ b/xds/src/main/java/io/grpc/xds/TlsContextManager.java @@ -19,7 +19,7 @@ import io.grpc.Internal; import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; -import io.grpc.xds.internal.sds.SslContextProvider; +import io.grpc.xds.internal.security.SslContextProvider; @Internal public interface TlsContextManager { diff --git a/xds/src/main/java/io/grpc/xds/XdsChannelCredentials.java b/xds/src/main/java/io/grpc/xds/XdsChannelCredentials.java index 81d1b8ecfbf..189ebc0ca19 100644 --- a/xds/src/main/java/io/grpc/xds/XdsChannelCredentials.java +++ b/xds/src/main/java/io/grpc/xds/XdsChannelCredentials.java @@ -22,7 +22,7 @@ import io.grpc.ExperimentalApi; import io.grpc.netty.InternalNettyChannelCredentials; import io.grpc.netty.InternalProtocolNegotiator; -import io.grpc.xds.internal.sds.SecurityProtocolNegotiators; +import io.grpc.xds.internal.security.SecurityProtocolNegotiators; @ExperimentalApi("https://github.com/grpc/grpc-java/issues/7514") public class XdsChannelCredentials { diff --git a/xds/src/main/java/io/grpc/xds/XdsServerCredentials.java b/xds/src/main/java/io/grpc/xds/XdsServerCredentials.java index 38015e17afc..2212e7a1855 100644 --- a/xds/src/main/java/io/grpc/xds/XdsServerCredentials.java +++ b/xds/src/main/java/io/grpc/xds/XdsServerCredentials.java @@ -22,7 +22,7 @@ import io.grpc.ServerCredentials; import io.grpc.netty.InternalNettyServerCredentials; import io.grpc.netty.InternalProtocolNegotiator; -import io.grpc.xds.internal.sds.SecurityProtocolNegotiators; +import io.grpc.xds.internal.security.SecurityProtocolNegotiators; @ExperimentalApi("https://github.com/grpc/grpc-java/issues/7514") public class XdsServerCredentials { diff --git a/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java b/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java index 1690e6459a7..b269e7f9bcf 100644 --- a/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java +++ b/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java @@ -56,7 +56,7 @@ import io.grpc.xds.XdsClient.RdsUpdate; import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory; import io.grpc.xds.XdsServerBuilder.XdsServingStatusListener; -import io.grpc.xds.internal.sds.SslContextProviderSupplier; +import io.grpc.xds.internal.security.SslContextProviderSupplier; import java.io.IOException; import java.net.SocketAddress; import java.util.ArrayList; diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProvider.java index ddb530f67fb..d8d71be197b 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProvider.java @@ -26,7 +26,7 @@ import io.grpc.netty.GrpcSslContexts; import io.grpc.xds.Bootstrapper.CertificateProviderInfo; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; -import io.grpc.xds.internal.sds.trust.XdsTrustManagerFactory; +import io.grpc.xds.internal.security.trust.XdsTrustManagerFactory; import io.netty.handler.ssl.SslContextBuilder; import java.security.cert.CertStoreException; import java.security.cert.X509Certificate; diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProvider.java index 34fe49e1daf..036591c0922 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProvider.java @@ -26,7 +26,7 @@ import io.grpc.netty.GrpcSslContexts; import io.grpc.xds.Bootstrapper.CertificateProviderInfo; import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; -import io.grpc.xds.internal.sds.trust.XdsTrustManagerFactory; +import io.grpc.xds.internal.security.trust.XdsTrustManagerFactory; import io.netty.handler.ssl.SslContextBuilder; import java.io.IOException; diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderSslContextProvider.java index 5c4dba99dc3..0bdbf36e9ea 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderSslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderSslContextProvider.java @@ -22,8 +22,8 @@ import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext.CertificateProviderInstance; import io.grpc.xds.Bootstrapper.CertificateProviderInfo; import io.grpc.xds.EnvoyServerProtoData.BaseTlsContext; -import io.grpc.xds.internal.sds.CommonTlsContextUtil; -import io.grpc.xds.internal.sds.DynamicSslContextProvider; +import io.grpc.xds.internal.security.CommonTlsContextUtil; +import io.grpc.xds.internal.security.DynamicSslContextProvider; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.util.List; diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProvider.java index 04ed997fa58..3ecbba6cb37 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProvider.java @@ -20,7 +20,7 @@ import com.google.common.annotations.VisibleForTesting; import io.grpc.Status; -import io.grpc.xds.internal.sds.Closeable; +import io.grpc.xds.internal.security.Closeable; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.util.Collections; diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderStore.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderStore.java index 43143ebb3ae..08ed25bf9cb 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderStore.java +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderStore.java @@ -18,7 +18,7 @@ import com.google.common.annotations.VisibleForTesting; import io.grpc.xds.internal.certprovider.CertificateProvider.Watcher; -import io.grpc.xds.internal.sds.ReferenceCountingMap; +import io.grpc.xds.internal.security.ReferenceCountingMap; import java.io.Closeable; import java.util.Objects; diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/FileWatcherCertificateProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/FileWatcherCertificateProvider.java index b86de55766e..4cddc19ea40 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/FileWatcherCertificateProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/FileWatcherCertificateProvider.java @@ -21,7 +21,7 @@ import com.google.common.annotations.VisibleForTesting; import io.grpc.Status; import io.grpc.internal.TimeProvider; -import io.grpc.xds.internal.sds.trust.CertificateUtils; +import io.grpc.xds.internal.security.trust.CertificateUtils; import java.io.ByteArrayInputStream; import java.nio.file.Files; diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/ClientSslContextProviderFactory.java b/xds/src/main/java/io/grpc/xds/internal/security/ClientSslContextProviderFactory.java similarity index 95% rename from xds/src/main/java/io/grpc/xds/internal/sds/ClientSslContextProviderFactory.java rename to xds/src/main/java/io/grpc/xds/internal/security/ClientSslContextProviderFactory.java index bb7c0314bb4..c7f2dfc001f 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/ClientSslContextProviderFactory.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/ClientSslContextProviderFactory.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package io.grpc.xds.internal.sds; +package io.grpc.xds.internal.security; import static com.google.common.base.Preconditions.checkNotNull; import io.grpc.xds.Bootstrapper.BootstrapInfo; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.internal.certprovider.CertProviderClientSslContextProvider; -import io.grpc.xds.internal.sds.ReferenceCountingMap.ValueFactory; +import io.grpc.xds.internal.security.ReferenceCountingMap.ValueFactory; /** Factory to create client-side SslContextProvider from UpstreamTlsContext. */ final class ClientSslContextProviderFactory diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/Closeable.java b/xds/src/main/java/io/grpc/xds/internal/security/Closeable.java similarity index 94% rename from xds/src/main/java/io/grpc/xds/internal/sds/Closeable.java rename to xds/src/main/java/io/grpc/xds/internal/security/Closeable.java index 3ac8734894a..c78714abfbf 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/Closeable.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/Closeable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.sds; +package io.grpc.xds.internal.security; public interface Closeable extends java.io.Closeable { diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/CommonTlsContextUtil.java b/xds/src/main/java/io/grpc/xds/internal/security/CommonTlsContextUtil.java similarity index 98% rename from xds/src/main/java/io/grpc/xds/internal/sds/CommonTlsContextUtil.java rename to xds/src/main/java/io/grpc/xds/internal/security/CommonTlsContextUtil.java index df09e8bb247..d3003b4a792 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/CommonTlsContextUtil.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/CommonTlsContextUtil.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.sds; +package io.grpc.xds.internal.security; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateProviderPluginInstance; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/DynamicSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/security/DynamicSslContextProvider.java similarity index 99% rename from xds/src/main/java/io/grpc/xds/internal/sds/DynamicSslContextProvider.java rename to xds/src/main/java/io/grpc/xds/internal/security/DynamicSslContextProvider.java index c75347c1f5e..64c6ff8b6b6 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/DynamicSslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/DynamicSslContextProvider.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.sds; +package io.grpc.xds.internal.security; import static com.google.common.base.Preconditions.checkNotNull; diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/ReferenceCountingMap.java b/xds/src/main/java/io/grpc/xds/internal/security/ReferenceCountingMap.java similarity index 99% rename from xds/src/main/java/io/grpc/xds/internal/sds/ReferenceCountingMap.java rename to xds/src/main/java/io/grpc/xds/internal/security/ReferenceCountingMap.java index 6a3a03a2870..b7f56492fa5 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/ReferenceCountingMap.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/ReferenceCountingMap.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.sds; +package io.grpc.xds.internal.security; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/SecurityProtocolNegotiators.java b/xds/src/main/java/io/grpc/xds/internal/security/SecurityProtocolNegotiators.java similarity index 99% rename from xds/src/main/java/io/grpc/xds/internal/sds/SecurityProtocolNegotiators.java rename to xds/src/main/java/io/grpc/xds/internal/security/SecurityProtocolNegotiators.java index cb3521aa585..0c1271d1dc7 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/SecurityProtocolNegotiators.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/SecurityProtocolNegotiators.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.sds; +package io.grpc.xds.internal.security; import static com.google.common.base.Preconditions.checkNotNull; diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/ServerSslContextProviderFactory.java b/xds/src/main/java/io/grpc/xds/internal/security/ServerSslContextProviderFactory.java similarity index 95% rename from xds/src/main/java/io/grpc/xds/internal/sds/ServerSslContextProviderFactory.java rename to xds/src/main/java/io/grpc/xds/internal/security/ServerSslContextProviderFactory.java index 590ffdb47c5..14e038c9a48 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/ServerSslContextProviderFactory.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/ServerSslContextProviderFactory.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package io.grpc.xds.internal.sds; +package io.grpc.xds.internal.security; import static com.google.common.base.Preconditions.checkNotNull; import io.grpc.xds.Bootstrapper.BootstrapInfo; import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; import io.grpc.xds.internal.certprovider.CertProviderServerSslContextProvider; -import io.grpc.xds.internal.sds.ReferenceCountingMap.ValueFactory; +import io.grpc.xds.internal.security.ReferenceCountingMap.ValueFactory; /** Factory to create server-side SslContextProvider from DownstreamTlsContext. */ final class ServerSslContextProviderFactory diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/SslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/security/SslContextProvider.java similarity index 97% rename from xds/src/main/java/io/grpc/xds/internal/sds/SslContextProvider.java rename to xds/src/main/java/io/grpc/xds/internal/security/SslContextProvider.java index f72793cd050..17683940897 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/SslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/SslContextProvider.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.sds; +package io.grpc.xds.internal.security; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; @@ -24,7 +24,7 @@ import io.grpc.xds.EnvoyServerProtoData.BaseTlsContext; import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; -import io.grpc.xds.internal.sds.trust.XdsTrustManagerFactory; +import io.grpc.xds.internal.security.trust.XdsTrustManagerFactory; import io.netty.handler.ssl.ClientAuth; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/SslContextProviderSupplier.java b/xds/src/main/java/io/grpc/xds/internal/security/SslContextProviderSupplier.java similarity index 99% rename from xds/src/main/java/io/grpc/xds/internal/sds/SslContextProviderSupplier.java rename to xds/src/main/java/io/grpc/xds/internal/security/SslContextProviderSupplier.java index 183c4d2a634..e429eff44a0 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/SslContextProviderSupplier.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/SslContextProviderSupplier.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.sds; +package io.grpc.xds.internal.security; import static com.google.common.base.Preconditions.checkNotNull; diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/TlsContextManagerImpl.java b/xds/src/main/java/io/grpc/xds/internal/security/TlsContextManagerImpl.java similarity index 97% rename from xds/src/main/java/io/grpc/xds/internal/sds/TlsContextManagerImpl.java rename to xds/src/main/java/io/grpc/xds/internal/security/TlsContextManagerImpl.java index 75a5d297d90..8d4fce60350 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/TlsContextManagerImpl.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/TlsContextManagerImpl.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.sds; +package io.grpc.xds.internal.security; import static com.google.common.base.Preconditions.checkNotNull; @@ -24,7 +24,7 @@ import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.TlsContextManager; -import io.grpc.xds.internal.sds.ReferenceCountingMap.ValueFactory; +import io.grpc.xds.internal.security.ReferenceCountingMap.ValueFactory; /** * Class to manage {@link SslContextProvider} objects created from inputs we get from xDS. Used by diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/trust/CertificateUtils.java b/xds/src/main/java/io/grpc/xds/internal/security/trust/CertificateUtils.java similarity index 99% rename from xds/src/main/java/io/grpc/xds/internal/sds/trust/CertificateUtils.java rename to xds/src/main/java/io/grpc/xds/internal/security/trust/CertificateUtils.java index e4ddb99a2b1..6e244a438c0 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/trust/CertificateUtils.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/trust/CertificateUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.sds.trust; +package io.grpc.xds.internal.security.trust; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/trust/XdsTrustManagerFactory.java b/xds/src/main/java/io/grpc/xds/internal/security/trust/XdsTrustManagerFactory.java similarity index 96% rename from xds/src/main/java/io/grpc/xds/internal/sds/trust/XdsTrustManagerFactory.java rename to xds/src/main/java/io/grpc/xds/internal/security/trust/XdsTrustManagerFactory.java index 2cdcd3a2d65..26d6bcd81b8 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/trust/XdsTrustManagerFactory.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/trust/XdsTrustManagerFactory.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.sds.trust; +package io.grpc.xds.internal.security.trust; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; @@ -23,7 +23,6 @@ import com.google.common.base.Strings; import io.envoyproxy.envoy.config.core.v3.DataSource.SpecifierCase; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; -import io.grpc.xds.internal.sds.TlsContextManagerImpl; import io.netty.handler.ssl.util.SimpleTrustManagerFactory; import java.io.File; import java.io.IOException; @@ -42,8 +41,7 @@ import javax.net.ssl.X509ExtendedTrustManager; /** - * Factory class used by providers of {@link TlsContextManagerImpl} to provide a - * {@link XdsX509TrustManager} for trust and SAN checks. + * Factory class used to provide a {@link XdsX509TrustManager} for trust and SAN checks. */ public final class XdsTrustManagerFactory extends SimpleTrustManagerFactory { diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/trust/XdsX509TrustManager.java b/xds/src/main/java/io/grpc/xds/internal/security/trust/XdsX509TrustManager.java similarity index 99% rename from xds/src/main/java/io/grpc/xds/internal/sds/trust/XdsX509TrustManager.java rename to xds/src/main/java/io/grpc/xds/internal/security/trust/XdsX509TrustManager.java index c44c42321ef..4bb6f0520c4 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/trust/XdsX509TrustManager.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/trust/XdsX509TrustManager.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.sds.trust; +package io.grpc.xds.internal.security.trust; import static com.google.common.base.Preconditions.checkNotNull; diff --git a/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java b/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java index 128d7bd395e..c211551f1fe 100644 --- a/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java +++ b/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java @@ -56,7 +56,7 @@ import io.grpc.xds.LeastRequestLoadBalancer.LeastRequestConfig; import io.grpc.xds.RingHashLoadBalancer.RingHashConfig; import io.grpc.xds.XdsClient.CdsUpdate; -import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil; +import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java index 97f892a0c27..cd8766024c8 100644 --- a/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java +++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java @@ -94,7 +94,7 @@ import io.grpc.xds.XdsClient.ResourceMetadata.ResourceMetadataStatus; import io.grpc.xds.XdsClient.ResourceMetadata.UpdateFailureState; import io.grpc.xds.XdsClient.ResourceWatcher; -import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil; +import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; import java.io.IOException; import java.util.ArrayDeque; import java.util.Arrays; diff --git a/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java index 582747aecb7..65af00da7a9 100644 --- a/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java @@ -57,9 +57,9 @@ import io.grpc.xds.WeightedTargetLoadBalancerProvider.WeightedPolicySelection; import io.grpc.xds.WeightedTargetLoadBalancerProvider.WeightedTargetConfig; import io.grpc.xds.XdsNameResolverProvider.CallCounterProvider; -import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil; -import io.grpc.xds.internal.sds.SslContextProvider; -import io.grpc.xds.internal.sds.SslContextProviderSupplier; +import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; +import io.grpc.xds.internal.security.SslContextProvider; +import io.grpc.xds.internal.security.SslContextProviderSupplier; import java.net.SocketAddress; import java.util.ArrayList; import java.util.Arrays; diff --git a/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java index 2a8b95260fc..22a4985aaf7 100644 --- a/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java @@ -76,7 +76,7 @@ import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig.PriorityChildConfig; import io.grpc.xds.RingHashLoadBalancer.RingHashConfig; import io.grpc.xds.WrrLocalityLoadBalancer.WrrLocalityConfig; -import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil; +import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; import java.net.SocketAddress; import java.net.URI; import java.net.URISyntaxException; diff --git a/xds/src/test/java/io/grpc/xds/CommonBootstrapperTestUtils.java b/xds/src/test/java/io/grpc/xds/CommonBootstrapperTestUtils.java index 73632c8addb..4319700b7f5 100644 --- a/xds/src/test/java/io/grpc/xds/CommonBootstrapperTestUtils.java +++ b/xds/src/test/java/io/grpc/xds/CommonBootstrapperTestUtils.java @@ -20,7 +20,7 @@ import com.google.common.collect.ImmutableMap; import io.grpc.internal.JsonParser; import io.grpc.xds.Bootstrapper.ServerInfo; -import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil; +import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; import java.io.IOException; import java.util.HashMap; import java.util.Map; diff --git a/xds/src/test/java/io/grpc/xds/FilterChainMatchingProtocolNegotiatorsTest.java b/xds/src/test/java/io/grpc/xds/FilterChainMatchingProtocolNegotiatorsTest.java index 47208d44651..3b129e99beb 100644 --- a/xds/src/test/java/io/grpc/xds/FilterChainMatchingProtocolNegotiatorsTest.java +++ b/xds/src/test/java/io/grpc/xds/FilterChainMatchingProtocolNegotiatorsTest.java @@ -18,7 +18,7 @@ import static com.google.common.truth.Truth.assertThat; import static io.grpc.xds.XdsServerWrapper.ATTR_SERVER_ROUTING_CONFIG; -import static io.grpc.xds.internal.sds.SecurityProtocolNegotiators.ATTR_SERVER_SSL_CONTEXT_PROVIDER_SUPPLIER; +import static io.grpc.xds.internal.security.SecurityProtocolNegotiators.ATTR_SERVER_SSL_CONTEXT_PROVIDER_SUPPLIER; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -40,8 +40,8 @@ import io.grpc.xds.FilterChainMatchingProtocolNegotiators.FilterChainMatchingHandler.FilterChainSelector; import io.grpc.xds.VirtualHost.Route; import io.grpc.xds.XdsServerWrapper.ServerRoutingConfig; -import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil; -import io.grpc.xds.internal.sds.SslContextProviderSupplier; +import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; +import io.grpc.xds.internal.security.SslContextProviderSupplier; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; diff --git a/xds/src/test/java/io/grpc/xds/XdsClientWrapperForServerSdsTestMisc.java b/xds/src/test/java/io/grpc/xds/XdsClientWrapperForServerSdsTestMisc.java index 932bc0c2d82..cec4688c069 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientWrapperForServerSdsTestMisc.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientWrapperForServerSdsTestMisc.java @@ -18,7 +18,7 @@ import static com.google.common.truth.Truth.assertThat; import static io.grpc.xds.FilterChainMatchingProtocolNegotiators.FilterChainMatchingHandler.FilterChainSelector.NO_FILTER_CHAIN; -import static io.grpc.xds.internal.sds.SecurityProtocolNegotiators.ATTR_SERVER_SSL_CONTEXT_PROVIDER_SUPPLIER; +import static io.grpc.xds.internal.security.SecurityProtocolNegotiators.ATTR_SERVER_SSL_CONTEXT_PROVIDER_SUPPLIER; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; @@ -48,9 +48,9 @@ import io.grpc.xds.XdsServerBuilder.XdsServingStatusListener; import io.grpc.xds.XdsServerTestHelper.FakeXdsClient; import io.grpc.xds.XdsServerTestHelper.FakeXdsClientPoolFactory; -import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil; -import io.grpc.xds.internal.sds.SslContextProvider; -import io.grpc.xds.internal.sds.SslContextProviderSupplier; +import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; +import io.grpc.xds.internal.security.SslContextProvider; +import io.grpc.xds.internal.security.SslContextProviderSupplier; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; diff --git a/xds/src/test/java/io/grpc/xds/XdsSdsClientServerTest.java b/xds/src/test/java/io/grpc/xds/XdsSdsClientServerTest.java index 36669537255..fe28da8bffe 100644 --- a/xds/src/test/java/io/grpc/xds/XdsSdsClientServerTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsSdsClientServerTest.java @@ -17,15 +17,15 @@ package io.grpc.xds; import static com.google.common.truth.Truth.assertThat; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.BAD_CLIENT_KEY_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.BAD_CLIENT_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.BAD_SERVER_KEY_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.BAD_SERVER_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CA_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_KEY_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_KEY_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.BAD_CLIENT_KEY_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.BAD_CLIENT_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.BAD_SERVER_KEY_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.BAD_SERVER_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CA_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CLIENT_KEY_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_KEY_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; @@ -61,9 +61,9 @@ import io.grpc.xds.XdsServerTestHelper.FakeXdsClient; import io.grpc.xds.XdsServerTestHelper.FakeXdsClientPoolFactory; import io.grpc.xds.internal.Matchers.HeaderMatcher; -import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil; -import io.grpc.xds.internal.sds.SslContextProviderSupplier; -import io.grpc.xds.internal.sds.TlsContextManagerImpl; +import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; +import io.grpc.xds.internal.security.SslContextProviderSupplier; +import io.grpc.xds.internal.security.TlsContextManagerImpl; import io.netty.handler.ssl.NotSslRecordException; import java.net.Inet4Address; import java.net.InetSocketAddress; diff --git a/xds/src/test/java/io/grpc/xds/XdsServerBuilderTest.java b/xds/src/test/java/io/grpc/xds/XdsServerBuilderTest.java index d67ed9d09fc..06475522e49 100644 --- a/xds/src/test/java/io/grpc/xds/XdsServerBuilderTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsServerBuilderTest.java @@ -35,7 +35,7 @@ import io.grpc.testing.GrpcCleanupRule; import io.grpc.xds.XdsServerTestHelper.FakeXdsClient; import io.grpc.xds.XdsServerTestHelper.FakeXdsClientPoolFactory; -import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil; +import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; diff --git a/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java b/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java index 7a9b42d4b21..d0b05112bae 100644 --- a/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java @@ -66,8 +66,8 @@ import io.grpc.xds.XdsServerWrapper.ConfigApplyingInterceptor; import io.grpc.xds.XdsServerWrapper.ServerRoutingConfig; import io.grpc.xds.internal.Matchers.HeaderMatcher; -import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil; -import io.grpc.xds.internal.sds.SslContextProviderSupplier; +import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; +import io.grpc.xds.internal.security.SslContextProviderSupplier; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProviderTest.java index 111a44e3224..8495d9bcf0b 100644 --- a/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProviderTest.java @@ -19,13 +19,13 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.Truth.assertThat; import static io.grpc.xds.internal.certprovider.CommonCertProviderTestUtils.getCertFromResourceName; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CA_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_KEY_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_0_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_KEY_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.doChecksOnSslContext; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CA_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CLIENT_KEY_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_0_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_KEY_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.doChecksOnSslContext; import static org.junit.Assert.fail; import com.google.common.annotations.VisibleForTesting; @@ -36,8 +36,8 @@ import io.grpc.xds.Bootstrapper; import io.grpc.xds.CommonBootstrapperTestUtils; import io.grpc.xds.EnvoyServerProtoData; -import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil; -import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.TestCallback; +import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; +import io.grpc.xds.internal.security.CommonTlsContextTestsUtil.TestCallback; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProviderTest.java index 0309f070c82..8ce52e32333 100644 --- a/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProviderTest.java @@ -18,13 +18,13 @@ import static com.google.common.truth.Truth.assertThat; import static io.grpc.xds.internal.certprovider.CommonCertProviderTestUtils.getCertFromResourceName; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CA_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_0_KEY_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_0_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_KEY_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.doChecksOnSslContext; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CA_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_0_KEY_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_0_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_KEY_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.doChecksOnSslContext; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; @@ -36,8 +36,8 @@ import io.grpc.xds.CommonBootstrapperTestUtils; import io.grpc.xds.EnvoyServerProtoData; import io.grpc.xds.internal.certprovider.CertProviderClientSslContextProviderTest.QueuedExecutor; -import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil; -import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.TestCallback; +import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; +import io.grpc.xds.internal.security.CommonTlsContextTestsUtil.TestCallback; import java.util.Arrays; import org.junit.Before; import org.junit.Test; diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/CommonCertProviderTestUtils.java b/xds/src/test/java/io/grpc/xds/internal/certprovider/CommonCertProviderTestUtils.java index 0e60c4c6716..86264f28858 100644 --- a/xds/src/test/java/io/grpc/xds/internal/certprovider/CommonCertProviderTestUtils.java +++ b/xds/src/test/java/io/grpc/xds/internal/certprovider/CommonCertProviderTestUtils.java @@ -23,7 +23,7 @@ import io.grpc.internal.TimeProvider; import io.grpc.internal.testing.TestUtils; import io.grpc.xds.internal.certprovider.FileWatcherCertificateProviderProvider.ScheduledExecutorServiceFactory; -import io.grpc.xds.internal.sds.trust.CertificateUtils; +import io.grpc.xds.internal.security.trust.CertificateUtils; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/FileWatcherCertificateProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/certprovider/FileWatcherCertificateProviderTest.java index 4da9e6127c7..7cfae617820 100644 --- a/xds/src/test/java/io/grpc/xds/internal/certprovider/FileWatcherCertificateProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/certprovider/FileWatcherCertificateProviderTest.java @@ -17,12 +17,12 @@ package io.grpc.xds.internal.certprovider; import static com.google.common.truth.Truth.assertThat; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CA_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_KEY_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_0_KEY_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_0_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CA_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CLIENT_KEY_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_0_KEY_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_0_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -35,7 +35,7 @@ import io.grpc.Status; import io.grpc.internal.TimeProvider; import io.grpc.xds.internal.certprovider.CertificateProvider.DistributorWatcher; -import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil; +import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; import java.io.File; import java.io.IOException; import java.nio.file.Files; diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/ClientSslContextProviderFactoryTest.java b/xds/src/test/java/io/grpc/xds/internal/security/ClientSslContextProviderFactoryTest.java similarity index 99% rename from xds/src/test/java/io/grpc/xds/internal/sds/ClientSslContextProviderFactoryTest.java rename to xds/src/test/java/io/grpc/xds/internal/security/ClientSslContextProviderFactoryTest.java index c4a32954a65..8e81c241705 100644 --- a/xds/src/test/java/io/grpc/xds/internal/sds/ClientSslContextProviderFactoryTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/security/ClientSslContextProviderFactoryTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.sds; +package io.grpc.xds.internal.security; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/CommonTlsContextTestsUtil.java b/xds/src/test/java/io/grpc/xds/internal/security/CommonTlsContextTestsUtil.java similarity index 99% rename from xds/src/test/java/io/grpc/xds/internal/sds/CommonTlsContextTestsUtil.java rename to xds/src/test/java/io/grpc/xds/internal/security/CommonTlsContextTestsUtil.java index 840cced424f..81b267587fd 100644 --- a/xds/src/test/java/io/grpc/xds/internal/sds/CommonTlsContextTestsUtil.java +++ b/xds/src/test/java/io/grpc/xds/internal/security/CommonTlsContextTestsUtil.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.sds; +package io.grpc.xds.internal.security; import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; @@ -32,7 +32,7 @@ import io.envoyproxy.envoy.type.matcher.v3.StringMatcher; import io.grpc.internal.testing.TestUtils; import io.grpc.xds.EnvoyServerProtoData; -import io.grpc.xds.internal.sds.trust.CertificateUtils; +import io.grpc.xds.internal.security.trust.CertificateUtils; import io.netty.handler.ssl.SslContext; import java.io.ByteArrayInputStream; import java.io.IOException; diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/ReferenceCountingMapTest.java b/xds/src/test/java/io/grpc/xds/internal/security/ReferenceCountingMapTest.java similarity index 97% rename from xds/src/test/java/io/grpc/xds/internal/sds/ReferenceCountingMapTest.java rename to xds/src/test/java/io/grpc/xds/internal/security/ReferenceCountingMapTest.java index b94aefd2151..d54a61b5510 100644 --- a/xds/src/test/java/io/grpc/xds/internal/sds/ReferenceCountingMapTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/security/ReferenceCountingMapTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.sds; +package io.grpc.xds.internal.security; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; @@ -24,7 +24,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import io.grpc.xds.internal.sds.ReferenceCountingMap.ValueFactory; +import io.grpc.xds.internal.security.ReferenceCountingMap.ValueFactory; import org.junit.Before; import org.junit.Rule; import org.junit.Test; diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/SecurityProtocolNegotiatorsTest.java b/xds/src/test/java/io/grpc/xds/internal/security/SecurityProtocolNegotiatorsTest.java similarity index 96% rename from xds/src/test/java/io/grpc/xds/internal/sds/SecurityProtocolNegotiatorsTest.java rename to xds/src/test/java/io/grpc/xds/internal/security/SecurityProtocolNegotiatorsTest.java index 9bab877b665..7355b456933 100644 --- a/xds/src/test/java/io/grpc/xds/internal/sds/SecurityProtocolNegotiatorsTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/security/SecurityProtocolNegotiatorsTest.java @@ -14,15 +14,15 @@ * limitations under the License. */ -package io.grpc.xds.internal.sds; +package io.grpc.xds.internal.security; import static com.google.common.truth.Truth.assertThat; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CA_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_KEY_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_KEY_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; -import static io.grpc.xds.internal.sds.SecurityProtocolNegotiators.ATTR_SERVER_SSL_CONTEXT_PROVIDER_SUPPLIER; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CA_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CLIENT_KEY_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_KEY_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; +import static io.grpc.xds.internal.security.SecurityProtocolNegotiators.ATTR_SERVER_SSL_CONTEXT_PROVIDER_SUPPLIER; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -52,8 +52,8 @@ import io.grpc.xds.InternalXdsAttributes; import io.grpc.xds.TlsContextManager; import io.grpc.xds.internal.certprovider.CommonCertProviderTestUtils; -import io.grpc.xds.internal.sds.SecurityProtocolNegotiators.ClientSdsHandler; -import io.grpc.xds.internal.sds.SecurityProtocolNegotiators.ClientSdsProtocolNegotiator; +import io.grpc.xds.internal.security.SecurityProtocolNegotiators.ClientSdsHandler; +import io.grpc.xds.internal.security.SecurityProtocolNegotiators.ClientSdsProtocolNegotiator; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPipeline; diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/ServerSslContextProviderFactoryTest.java b/xds/src/test/java/io/grpc/xds/internal/security/ServerSslContextProviderFactoryTest.java similarity index 97% rename from xds/src/test/java/io/grpc/xds/internal/sds/ServerSslContextProviderFactoryTest.java rename to xds/src/test/java/io/grpc/xds/internal/security/ServerSslContextProviderFactoryTest.java index 176323a18c2..902138c06d2 100644 --- a/xds/src/test/java/io/grpc/xds/internal/sds/ServerSslContextProviderFactoryTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/security/ServerSslContextProviderFactoryTest.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package io.grpc.xds.internal.sds; +package io.grpc.xds.internal.security; import static com.google.common.truth.Truth.assertThat; -import static io.grpc.xds.internal.sds.ClientSslContextProviderFactoryTest.createAndRegisterProviderProvider; -import static io.grpc.xds.internal.sds.ClientSslContextProviderFactoryTest.verifyWatcher; +import static io.grpc.xds.internal.security.ClientSslContextProviderFactoryTest.createAndRegisterProviderProvider; +import static io.grpc.xds.internal.security.ClientSslContextProviderFactoryTest.verifyWatcher; import com.google.common.collect.ImmutableSet; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/SslContextProviderSupplierTest.java b/xds/src/test/java/io/grpc/xds/internal/security/SslContextProviderSupplierTest.java similarity index 99% rename from xds/src/test/java/io/grpc/xds/internal/sds/SslContextProviderSupplierTest.java rename to xds/src/test/java/io/grpc/xds/internal/security/SslContextProviderSupplierTest.java index 19fd0e189c1..35c1437d34c 100644 --- a/xds/src/test/java/io/grpc/xds/internal/sds/SslContextProviderSupplierTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/security/SslContextProviderSupplierTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.sds; +package io.grpc.xds.internal.security; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.eq; diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/TlsContextManagerTest.java b/xds/src/test/java/io/grpc/xds/internal/security/TlsContextManagerTest.java similarity index 91% rename from xds/src/test/java/io/grpc/xds/internal/sds/TlsContextManagerTest.java rename to xds/src/test/java/io/grpc/xds/internal/security/TlsContextManagerTest.java index 7634bfee376..4589e328d4a 100644 --- a/xds/src/test/java/io/grpc/xds/internal/sds/TlsContextManagerTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/security/TlsContextManagerTest.java @@ -14,16 +14,16 @@ * limitations under the License. */ -package io.grpc.xds.internal.sds; +package io.grpc.xds.internal.security; import static com.google.common.truth.Truth.assertThat; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CA_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_KEY_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_0_KEY_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_0_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_KEY_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CA_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CLIENT_KEY_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_0_KEY_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_0_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_KEY_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -34,7 +34,7 @@ import io.grpc.xds.CommonBootstrapperTestUtils; import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; -import io.grpc.xds.internal.sds.ReferenceCountingMap.ValueFactory; +import io.grpc.xds.internal.security.ReferenceCountingMap.ValueFactory; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/trust/XdsTrustManagerFactoryTest.java b/xds/src/test/java/io/grpc/xds/internal/security/trust/XdsTrustManagerFactoryTest.java similarity index 95% rename from xds/src/test/java/io/grpc/xds/internal/sds/trust/XdsTrustManagerFactoryTest.java rename to xds/src/test/java/io/grpc/xds/internal/security/trust/XdsTrustManagerFactoryTest.java index ac2db38d190..30c5e542a80 100644 --- a/xds/src/test/java/io/grpc/xds/internal/sds/trust/XdsTrustManagerFactoryTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/security/trust/XdsTrustManagerFactoryTest.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package io.grpc.xds.internal.sds.trust; +package io.grpc.xds.internal.security.trust; import static com.google.common.truth.Truth.assertThat; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.BAD_CLIENT_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.BAD_SERVER_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CA_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.BAD_CLIENT_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.BAD_SERVER_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CA_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; import com.google.protobuf.ByteString; import io.envoyproxy.envoy.config.core.v3.DataSource; diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/trust/XdsX509TrustManagerTest.java b/xds/src/test/java/io/grpc/xds/internal/security/trust/XdsX509TrustManagerTest.java similarity index 98% rename from xds/src/test/java/io/grpc/xds/internal/sds/trust/XdsX509TrustManagerTest.java rename to xds/src/test/java/io/grpc/xds/internal/security/trust/XdsX509TrustManagerTest.java index fe68adc2ac8..c6319ee5a9f 100644 --- a/xds/src/test/java/io/grpc/xds/internal/sds/trust/XdsX509TrustManagerTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/security/trust/XdsX509TrustManagerTest.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package io.grpc.xds.internal.sds.trust; +package io.grpc.xds.internal.security.trust; import static com.google.common.truth.Truth.assertThat; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.BAD_SERVER_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CA_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; -import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.BAD_SERVER_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CA_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; +import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; import static org.junit.Assert.fail; import static org.mockito.Mockito.CALLS_REAL_METHODS; import static org.mockito.Mockito.doReturn; From eac4178eaadffb11cc246f7319d3b83433ec5efb Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Fri, 9 Sep 2022 12:53:47 -0700 Subject: [PATCH 50/72] okhttp: add max connection idle at OkHttpServer and fix test (#9533) * Revert "Revert "okhttp: add max connection idle at OkHttpServer (#9494)" (#9528)" This reverts commit 95b9d6db29ce7241de764beb64cd2bdee781ffb5 and fixed flaky test. --- .../io/grpc/okhttp/OkHttpServerBuilder.java | 28 +++++++ .../io/grpc/okhttp/OkHttpServerTransport.java | 23 ++++++ .../okhttp/OkHttpServerTransportTest.java | 75 +++++++++++++++++-- 3 files changed, 120 insertions(+), 6 deletions(-) diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java index 5928a2e38c1..0e1273c7f23 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java @@ -16,6 +16,8 @@ package io.grpc.okhttp; +import static com.google.common.base.Preconditions.checkArgument; + import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.errorprone.annotations.DoNotCall; @@ -62,6 +64,10 @@ public final class OkHttpServerBuilder extends ForwardingServerBuilder { private static final Logger log = Logger.getLogger(OkHttpServerBuilder.class.getName()); private static final int DEFAULT_FLOW_CONTROL_WINDOW = 65535; + + static final long MAX_CONNECTION_IDLE_NANOS_DISABLED = Long.MAX_VALUE; + private static final long MIN_MAX_CONNECTION_IDLE_NANO = TimeUnit.SECONDS.toNanos(1L); + private static final long AS_LARGE_AS_INFINITE = TimeUnit.DAYS.toNanos(1000L); private static final ObjectPool DEFAULT_TRANSPORT_EXECUTOR_POOL = OkHttpChannelBuilder.DEFAULT_TRANSPORT_EXECUTOR_POOL; @@ -110,6 +116,7 @@ public static OkHttpServerBuilder forPort(SocketAddress address, ServerCredentia int flowControlWindow = DEFAULT_FLOW_CONTROL_WINDOW; int maxInboundMetadataSize = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE; int maxInboundMessageSize = GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE; + long maxConnectionIdleInNanos = MAX_CONNECTION_IDLE_NANOS_DISABLED; @VisibleForTesting OkHttpServerBuilder( @@ -178,6 +185,27 @@ public OkHttpServerBuilder keepAliveTime(long keepAliveTime, TimeUnit timeUnit) return this; } + /** + * Sets a custom max connection idle time, connection being idle for longer than which will be + * gracefully terminated. Idleness duration is defined since the most recent time the number of + * outstanding RPCs became zero or the connection establishment. An unreasonably small value might + * be increased. {@code Long.MAX_VALUE} nano seconds or an unreasonably large value will disable + * max connection idle. + */ + @Override + public OkHttpServerBuilder maxConnectionIdle(long maxConnectionIdle, TimeUnit timeUnit) { + checkArgument(maxConnectionIdle > 0L, "max connection idle must be positive: %s", + maxConnectionIdle); + maxConnectionIdleInNanos = timeUnit.toNanos(maxConnectionIdle); + if (maxConnectionIdleInNanos >= AS_LARGE_AS_INFINITE) { + maxConnectionIdleInNanos = MAX_CONNECTION_IDLE_NANOS_DISABLED; + } + if (maxConnectionIdleInNanos < MIN_MAX_CONNECTION_IDLE_NANO) { + maxConnectionIdleInNanos = MIN_MAX_CONNECTION_IDLE_NANO; + } + return this; + } + /** * Sets a time waiting for read activity after sending a keepalive ping. If the time expires * without any read activity on the connection, the connection is considered dead. An unreasonably diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerTransport.java index 3a4ed86f5da..b2b581d7155 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerTransport.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerTransport.java @@ -16,6 +16,8 @@ package io.grpc.okhttp; +import static io.grpc.okhttp.OkHttpServerBuilder.MAX_CONNECTION_IDLE_NANOS_DISABLED; + import com.google.common.base.Preconditions; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -28,6 +30,7 @@ import io.grpc.Status; import io.grpc.internal.GrpcUtil; import io.grpc.internal.KeepAliveManager; +import io.grpc.internal.MaxConnectionIdleManager; import io.grpc.internal.ObjectPool; import io.grpc.internal.SerializingExecutor; import io.grpc.internal.ServerTransport; @@ -91,6 +94,7 @@ final class OkHttpServerTransport implements ServerTransport, private ScheduledExecutorService scheduledExecutorService; private Attributes attributes; private KeepAliveManager keepAliveManager; + private MaxConnectionIdleManager maxConnectionIdleManager; private final Object lock = new Object(); @GuardedBy("lock") @@ -189,6 +193,11 @@ private void startIo(SerializingExecutor serializingExecutor) { keepAliveManager.onTransportStarted(); } + if (config.maxConnectionIdleNanos != MAX_CONNECTION_IDLE_NANOS_DISABLED) { + maxConnectionIdleManager = new MaxConnectionIdleManager(config.maxConnectionIdleNanos); + maxConnectionIdleManager.start(this::shutdown, scheduledExecutorService); + } + transportExecutor.execute( new FrameHandler(variant.newReader(Okio.buffer(Okio.source(socket)), false))); } catch (Error | IOException | RuntimeException ex) { @@ -311,6 +320,9 @@ private void terminated() { if (keepAliveManager != null) { keepAliveManager.onTransportTermination(); } + if (maxConnectionIdleManager != null) { + maxConnectionIdleManager.onTransportTermination(); + } transportExecutor = config.transportExecutorPool.returnObject(transportExecutor); scheduledExecutorService = config.scheduledExecutorServicePool.returnObject(scheduledExecutorService); @@ -369,6 +381,9 @@ public OutboundFlowController.StreamState[] getActiveStreams() { void streamClosed(int streamId, boolean flush) { synchronized (lock) { streams.remove(streamId); + if (maxConnectionIdleManager != null && streams.isEmpty()) { + maxConnectionIdleManager.onTransportIdle(); + } if (gracefulShutdown && streams.isEmpty()) { frameWriter.close(); } else { @@ -433,6 +448,7 @@ static final class Config { final int flowControlWindow; final int maxInboundMessageSize; final int maxInboundMetadataSize; + final long maxConnectionIdleNanos; public Config( OkHttpServerBuilder builder, @@ -452,6 +468,7 @@ public Config( flowControlWindow = builder.flowControlWindow; maxInboundMessageSize = builder.maxInboundMessageSize; maxInboundMetadataSize = builder.maxInboundMetadataSize; + maxConnectionIdleNanos = builder.maxConnectionIdleInNanos; } } @@ -697,6 +714,9 @@ public void headers(boolean outFinished, authority == null ? null : asciiString(authority), statsTraceCtx, tracer); + if (maxConnectionIdleManager != null && streams.isEmpty()) { + maxConnectionIdleManager.onTransportActive(); + } streams.put(streamId, stream); listener.streamCreated(streamForApp, method, metadata); stream.onStreamAllocated(); @@ -953,6 +973,9 @@ private void respondWithHttpError( synchronized (lock) { Http2ErrorStreamState stream = new Http2ErrorStreamState(streamId, lock, outboundFlow, config.flowControlWindow); + if (maxConnectionIdleManager != null && streams.isEmpty()) { + maxConnectionIdleManager.onTransportActive(); + } streams.put(streamId, stream); if (inFinished) { stream.inboundDataReceived(new Buffer(), 0, true); diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java index 201829d38ab..2711978bb00 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java @@ -90,6 +90,7 @@ public class OkHttpServerTransportTest { private static final int TIME_OUT_MS = 2000; private static final int INITIAL_WINDOW_SIZE = 65535; + private static final long MAX_CONNECTION_IDLE = TimeUnit.SECONDS.toNanos(1); private MockServerTransportListener mockTransportListener = new MockServerTransportListener(); private ServerTransportListener transportListener @@ -105,10 +106,11 @@ public class OkHttpServerTransportTest { private ExecutorService threadPool = Executors.newCachedThreadPool(); private HandshakerSocketFactory handshakerSocketFactory = mock(HandshakerSocketFactory.class, delegatesTo(new PlaintextHandshakerSocketFactory())); + private final FakeClock fakeClock = new FakeClock(); private OkHttpServerBuilder serverBuilder = new OkHttpServerBuilder(new InetSocketAddress(1234), handshakerSocketFactory) .executor(new FakeClock().getScheduledExecutorService()) // Executor unused - .scheduledExecutorService(new FakeClock().getScheduledExecutorService()) + .scheduledExecutorService(fakeClock.getScheduledExecutorService()) .transportExecutor(new Executor() { @Override public void execute(Runnable runnable) { if (runnable instanceof OkHttpServerTransport.FrameHandler) { @@ -119,7 +121,8 @@ public class OkHttpServerTransportTest { } } }) - .flowControlWindow(INITIAL_WINDOW_SIZE); + .flowControlWindow(INITIAL_WINDOW_SIZE) + .maxConnectionIdle(MAX_CONNECTION_IDLE, TimeUnit.NANOSECONDS); @Rule public final Timeout globalTimeout = Timeout.seconds(10); @@ -146,6 +149,64 @@ public void startThenShutdown() throws Exception { shutdownAndTerminate(/*lastStreamId=*/ 0); } + @Test + public void maxConnectionIdleTimer() throws Exception { + initTransport(); + handshake(); + clientFrameWriter.headers(1, Arrays.asList( + HTTP_SCHEME_HEADER, + METHOD_HEADER, + new Header(Header.TARGET_AUTHORITY, "example.com:80"), + new Header(Header.TARGET_PATH, "/com.example/SimpleService.doit"), + CONTENT_TYPE_HEADER, + TE_HEADER)); + clientFrameWriter.synStream(true, false, 1, -1, Arrays.asList( + new Header("some-client-sent-trailer", "trailer-value"))); + pingPong(); + + MockStreamListener streamListener = mockTransportListener.newStreams.pop(); + assertThat(streamListener.messages.peek()).isNull(); + assertThat(streamListener.halfClosedCalled).isTrue(); + + streamListener.stream.close(Status.OK, new Metadata()); + + List
responseTrailers = Arrays.asList( + new Header(":status", "200"), + CONTENT_TYPE_HEADER, + new Header("grpc-status", "0")); + assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue(); + verify(clientFramesRead) + .headers(false, true, 1, -1, responseTrailers, HeadersMode.HTTP_20_HEADERS); + + fakeClock.forwardNanos(MAX_CONNECTION_IDLE); + fakeClock.forwardNanos(MAX_CONNECTION_IDLE); + verifyGracefulShutdown(1); + } + + @Test + public void maxConnectionIdleTimer_respondWithError() throws Exception { + initTransport(); + handshake(); + + clientFrameWriter.headers(1, Arrays.asList( + HTTP_SCHEME_HEADER, + METHOD_HEADER, + new Header(Header.TARGET_PATH, "/com.example/SimpleService.doit"), + CONTENT_TYPE_HEADER, + TE_HEADER, + new Header("host", "example.com:80"), + new Header("host", "example.com:80"))); + clientFrameWriter.flush(); + + verifyHttpError( + 1, 400, Status.Code.INTERNAL, "Multiple host headers disallowed. RFC7230 section 5.4"); + + pingPong(); + fakeClock.forwardNanos(MAX_CONNECTION_IDLE); + fakeClock.forwardNanos(MAX_CONNECTION_IDLE); + verifyGracefulShutdown(1); + } + @Test public void startThenShutdownTwice() throws Exception { initTransport(); @@ -316,7 +377,8 @@ public void activeRpc_delaysShutdownTermination() throws Exception { clientFrameWriter.data(true, 1, requestMessageFrame, (int) requestMessageFrame.size()); pingPong(); - shutdownAndVerifyGraceful(1); + serverTransport.shutdown(); + verifyGracefulShutdown(1); verify(transportListener, never()).transportTerminated(); MockStreamListener streamListener = mockTransportListener.newStreams.pop(); @@ -1038,8 +1100,8 @@ private Metadata metadata(String... keysAndValues) { return metadata; } - private void shutdownAndVerifyGraceful(int lastStreamId) throws IOException { - serverTransport.shutdown(); + private void verifyGracefulShutdown(int lastStreamId) + throws IOException { assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue(); verify(clientFramesRead).goAway(2147483647, ErrorCode.NO_ERROR, ByteString.EMPTY); assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue(); @@ -1052,7 +1114,8 @@ private void shutdownAndVerifyGraceful(int lastStreamId) throws IOException { private void shutdownAndTerminate(int lastStreamId) throws IOException { assertThat(serverTransport.getActiveStreams().length).isEqualTo(0); - shutdownAndVerifyGraceful(lastStreamId); + serverTransport.shutdown(); + verifyGracefulShutdown(lastStreamId); assertThat(clientFrameReader.nextFrame(clientFramesRead)).isFalse(); verify(transportListener, timeout(TIME_OUT_MS)).transportTerminated(); } From 84d0b0474fc9b57fe448ec2cf9747a37d86f8413 Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Fri, 9 Sep 2022 15:46:28 -0700 Subject: [PATCH 51/72] core: Replace inappropriate picker result status codes (#9530) Certain status codes are inappropriate for a control plane to be returning and indicate a bug in the control plane. These status codes will be replaced by INTERNAL, to make it clearer to see that the control plane is misbehaving. --- .../main/java/io/grpc/internal/GrpcUtil.java | 33 +++++++- .../java/io/grpc/internal/GrpcUtilTest.java | 76 +++++++++++++++++++ 2 files changed, 107 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/GrpcUtil.java b/core/src/main/java/io/grpc/internal/GrpcUtil.java index 263ecd0118d..6564862d8af 100644 --- a/core/src/main/java/io/grpc/internal/GrpcUtil.java +++ b/core/src/main/java/io/grpc/internal/GrpcUtil.java @@ -56,7 +56,10 @@ import java.net.URISyntaxException; import java.nio.charset.Charset; import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; import java.util.List; +import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -75,6 +78,17 @@ public final class GrpcUtil { private static final Logger log = Logger.getLogger(GrpcUtil.class.getName()); + private static final Set INAPPROPRIATE_CONTROL_PLANE_STATUS + = Collections.unmodifiableSet(EnumSet.of( + Status.Code.OK, + Status.Code.INVALID_ARGUMENT, + Status.Code.NOT_FOUND, + Status.Code.ALREADY_EXISTS, + Status.Code.FAILED_PRECONDITION, + Status.Code.ABORTED, + Status.Code.OUT_OF_RANGE, + Status.Code.DATA_LOSS)); + public static final Charset US_ASCII = Charset.forName("US-ASCII"); /** @@ -747,10 +761,12 @@ public ListenableFuture getStats() { } if (!result.getStatus().isOk()) { if (result.isDrop()) { - return new FailingClientTransport(result.getStatus(), RpcProgress.DROPPED); + return new FailingClientTransport( + replaceInappropriateControlPlaneStatus(result.getStatus()), RpcProgress.DROPPED); } if (!isWaitForReady) { - return new FailingClientTransport(result.getStatus(), RpcProgress.PROCESSED); + return new FailingClientTransport( + replaceInappropriateControlPlaneStatus(result.getStatus()), RpcProgress.PROCESSED); } } return null; @@ -805,6 +821,19 @@ public static void exhaust(InputStream in) throws IOException { while (in.read(buf) != -1) {} } + /** + * Some status codes from the control plane are not appropritate to use in the data plane. If one + * is given it will be replaced with INTERNAL, indicating a bug in the control plane + * implementation. + */ + public static Status replaceInappropriateControlPlaneStatus(Status status) { + checkArgument(status != null); + return INAPPROPRIATE_CONTROL_PLANE_STATUS.contains(status.getCode()) + ? Status.INTERNAL.withDescription( + "Inappropriate status code from control plane: " + status.getCode() + " " + + status.getDescription()).withCause(status.getCause()) : status; + } + /** * Checks whether the given item exists in the iterable. This is copied from Guava Collect's * {@code Iterables.contains()} because Guava Collect is not Android-friendly thus core can't diff --git a/core/src/test/java/io/grpc/internal/GrpcUtilTest.java b/core/src/test/java/io/grpc/internal/GrpcUtilTest.java index 7e3f6e7db4e..bd2864ecc9d 100644 --- a/core/src/test/java/io/grpc/internal/GrpcUtilTest.java +++ b/core/src/test/java/io/grpc/internal/GrpcUtilTest.java @@ -16,6 +16,7 @@ package io.grpc.internal; +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.assertNotNull; @@ -27,19 +28,26 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import com.google.common.collect.Lists; import io.grpc.CallOptions; import io.grpc.ClientStreamTracer; import io.grpc.LoadBalancer.PickResult; import io.grpc.Metadata; import io.grpc.Status; +import io.grpc.Status.Code; import io.grpc.internal.ClientStreamListener.RpcProgress; import io.grpc.internal.GrpcUtil.Http2Error; import io.grpc.testing.TestMethodDescriptors; +import java.util.ArrayList; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; /** Unit tests for {@link GrpcUtil}. */ @RunWith(JUnit4.class) @@ -51,6 +59,11 @@ public class GrpcUtilTest { @SuppressWarnings("deprecation") // https://github.com/grpc/grpc-java/issues/7467 @Rule public final ExpectedException thrown = ExpectedException.none(); + @Rule public final MockitoRule mocks = MockitoJUnit.rule(); + + @Captor + private ArgumentCaptor statusCaptor; + @Test public void http2ErrorForCode() { @@ -258,6 +271,69 @@ public void getTransportFromPickResult_errorPickResult_failFast() { verify(listener).closed(eq(status), eq(RpcProgress.PROCESSED), any(Metadata.class)); } + /* Status codes that a control plane should not be returned get replaced by INTERNAL. */ + @Test + public void getTransportFromPickResult_errorPickResult_noInappropriateControlPlaneStatus() { + + // These are NOT appropriate for a control plane to return. + ArrayList inappropriateStatus = Lists.newArrayList( + Status.INVALID_ARGUMENT.withDescription("bad one").withCause(new RuntimeException()), + Status.NOT_FOUND.withDescription("not here").withCause(new RuntimeException()), + Status.ALREADY_EXISTS.withDescription("not again").withCause(new RuntimeException()), + Status.FAILED_PRECONDITION.withDescription("naah").withCause(new RuntimeException()), + Status.ABORTED.withDescription("nope").withCause(new RuntimeException()), + Status.OUT_OF_RANGE.withDescription("outta range").withCause(new RuntimeException()), + Status.DATA_LOSS.withDescription("lost").withCause(new RuntimeException())); + + for (Status status : inappropriateStatus) { + PickResult pickResult = PickResult.withError(status); + ClientTransport transport = GrpcUtil.getTransportFromPickResult(pickResult, false); + + ClientStream stream = transport.newStream( + TestMethodDescriptors.voidMethod(), new Metadata(), CallOptions.DEFAULT, + tracers); + ClientStreamListener listener = mock(ClientStreamListener.class); + stream.start(listener); + + verify(listener).closed(statusCaptor.capture(), eq(RpcProgress.PROCESSED), + any(Metadata.class)); + Status usedStatus = statusCaptor.getValue(); + assertThat(usedStatus.getCode()).isEqualTo(Code.INTERNAL); + assertThat(usedStatus.getDescription()).contains("Inappropriate status"); + assertThat(usedStatus.getCause()).isInstanceOf(RuntimeException.class); + } + } + + /* Status codes a control plane can return are not replaced. */ + @Test + public void getTransportFromPickResult_errorPickResult_appropriateControlPlaneStatus() { + + // These ARE appropriate for a control plane to return. + ArrayList inappropriateStatus = Lists.newArrayList( + Status.CANCELLED, + Status.UNKNOWN, + Status.DEADLINE_EXCEEDED, + Status.PERMISSION_DENIED, + Status.RESOURCE_EXHAUSTED, + Status.UNIMPLEMENTED, + Status.INTERNAL, + Status.UNAVAILABLE, + Status.UNAUTHENTICATED); + + for (Status status : inappropriateStatus) { + PickResult pickResult = PickResult.withError(status); + ClientTransport transport = GrpcUtil.getTransportFromPickResult(pickResult, false); + + ClientStream stream = transport.newStream( + TestMethodDescriptors.voidMethod(), new Metadata(), CallOptions.DEFAULT, + tracers); + ClientStreamListener listener = mock(ClientStreamListener.class); + stream.start(listener); + + verify(listener).closed(eq(status), eq(RpcProgress.PROCESSED), any(Metadata.class)); + } + } + @Test public void getTransportFromPickResult_dropPickResult_waitForReady() { Status status = Status.UNAVAILABLE; From 42e68149a5bbf204e0c26e388cf25fb2fbca1d41 Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Fri, 9 Sep 2022 17:27:22 -0700 Subject: [PATCH 52/72] xds: ringhash policy in TRANSIENT_FAILURE should not attempt connecting when already in connecting (#9535) --- xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java | 2 +- xds/src/test/java/io/grpc/xds/RingHashLoadBalancerTest.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java b/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java index f0183e2c298..4b365230009 100644 --- a/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java @@ -248,7 +248,7 @@ private void updateBalancingState() { overallState = READY; } else if (numTransientFailure >= 2) { overallState = TRANSIENT_FAILURE; - startConnectionAttempt = true; + startConnectionAttempt = (numConnecting == 0); } else if (numConnecting > 0) { overallState = CONNECTING; } else if (numTransientFailure == 1 && subchannels.size() > 1) { diff --git a/xds/src/test/java/io/grpc/xds/RingHashLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/RingHashLoadBalancerTest.java index 8b205e8ad85..9cfa00bc848 100644 --- a/xds/src/test/java/io/grpc/xds/RingHashLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/RingHashLoadBalancerTest.java @@ -310,7 +310,7 @@ public void aggregateSubchannelStates_twoOrMoreSubchannelsInTransientFailure() { ConnectivityStateInfo.forNonError(CONNECTING)); inOrder.verify(helper) .updateBalancingState(eq(TRANSIENT_FAILURE), any(SubchannelPicker.class)); - verifyConnection(1); + verifyConnection(0); // three in TRANSIENT_FAILURE, one in CONNECTING deliverSubchannelState( @@ -320,7 +320,7 @@ public void aggregateSubchannelStates_twoOrMoreSubchannelsInTransientFailure() { inOrder.verify(helper).refreshNameResolution(); inOrder.verify(helper) .updateBalancingState(eq(TRANSIENT_FAILURE), any(SubchannelPicker.class)); - verifyConnection(1); + verifyConnection(0); // three in TRANSIENT_FAILURE, one in READY deliverSubchannelState( @@ -884,7 +884,7 @@ public void thirdSubchannelConnecting() { deliverSubchannelState(subchannels.get(Collections.singletonList(servers.get(1))), ConnectivityStateInfo.forNonError(CONNECTING)); verify(helper, times(2)).updateBalancingState(eq(TRANSIENT_FAILURE), pickerCaptor.capture()); - verifyConnection(3); + verifyConnection(2); // Picking subchannel triggers connection. PickSubchannelArgs args = new PickSubchannelArgsImpl( From bacf18db8dcfbf50006eadaac1693fbce74bc550 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Mon, 12 Sep 2022 08:19:21 -0700 Subject: [PATCH 53/72] census: Avoid deprecated measure constants Many of the deprecated constants are just aliases for non-deprecated constants, so just swap which one we use. --- .../io/grpc/census/CensusStatsModule.java | 30 +++-- .../internal/DeprecatedCensusConstants.java | 26 ----- .../io/grpc/census/CensusModulesTest.java | 105 ++++++++---------- .../integration/AbstractInteropTest.java | 20 ++-- .../grpc/testing/integration/RetryTest.java | 6 +- 5 files changed, 75 insertions(+), 112 deletions(-) diff --git a/census/src/main/java/io/grpc/census/CensusStatsModule.java b/census/src/main/java/io/grpc/census/CensusStatsModule.java index 1d016abc89d..d05368f3ee5 100644 --- a/census/src/main/java/io/grpc/census/CensusStatsModule.java +++ b/census/src/main/java/io/grpc/census/CensusStatsModule.java @@ -369,13 +369,11 @@ void recordFinishedAttempt() { // TODO(songya): remove the deprecated measure constants once they are completed removed. .put(DeprecatedCensusConstants.RPC_CLIENT_FINISHED_COUNT, 1) // The latency is double value - .put( - DeprecatedCensusConstants.RPC_CLIENT_ROUNDTRIP_LATENCY, - roundtripNanos / NANOS_PER_MILLI) - .put(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_COUNT, outboundMessageCount) - .put(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_COUNT, inboundMessageCount) - .put(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_BYTES, outboundWireSize) - .put(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_BYTES, inboundWireSize) + .put(RpcMeasureConstants.GRPC_CLIENT_ROUNDTRIP_LATENCY, roundtripNanos / NANOS_PER_MILLI) + .put(RpcMeasureConstants.GRPC_CLIENT_SENT_MESSAGES_PER_RPC, outboundMessageCount) + .put(RpcMeasureConstants.GRPC_CLIENT_RECEIVED_MESSAGES_PER_RPC, inboundMessageCount) + .put(RpcMeasureConstants.GRPC_CLIENT_SENT_BYTES_PER_RPC, outboundWireSize) + .put(RpcMeasureConstants.GRPC_CLIENT_RECEIVED_BYTES_PER_RPC, inboundWireSize) .put( DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES, outboundUncompressedSize) @@ -443,7 +441,7 @@ static final class CallAttemptsTracerFactory extends if (module.recordStartedRpcs) { // Record here in case newClientStreamTracer() would never be called. module.statsRecorder.newMeasureMap() - .put(DeprecatedCensusConstants.RPC_CLIENT_STARTED_COUNT, 1) + .put(RpcMeasureConstants.GRPC_CLIENT_STARTED_RPCS, 1) .record(startCtx); } } @@ -462,7 +460,7 @@ public ClientStreamTracer newClientStreamTracer(StreamInfo info, Metadata metada } if (module.recordStartedRpcs && attemptsPerCall.get() > 0) { module.statsRecorder.newMeasureMap() - .put(DeprecatedCensusConstants.RPC_CLIENT_STARTED_COUNT, 1) + .put(RpcMeasureConstants.GRPC_CLIENT_STARTED_RPCS, 1) .record(startCtx); } if (info.isTransparentRetry()) { @@ -628,7 +626,7 @@ private static final class ServerTracer extends ServerStreamTracer { this.stopwatch = module.stopwatchSupplier.get().start(); if (module.recordStartedRpcs) { module.statsRecorder.newMeasureMap() - .put(DeprecatedCensusConstants.RPC_SERVER_STARTED_COUNT, 1) + .put(RpcMeasureConstants.GRPC_SERVER_STARTED_RPCS, 1) .record(parentCtx); } } @@ -728,13 +726,11 @@ public void streamClosed(Status status) { // TODO(songya): remove the deprecated measure constants once they are completed removed. .put(DeprecatedCensusConstants.RPC_SERVER_FINISHED_COUNT, 1) // The latency is double value - .put( - DeprecatedCensusConstants.RPC_SERVER_SERVER_LATENCY, - elapsedTimeNanos / NANOS_PER_MILLI) - .put(DeprecatedCensusConstants.RPC_SERVER_RESPONSE_COUNT, outboundMessageCount) - .put(DeprecatedCensusConstants.RPC_SERVER_REQUEST_COUNT, inboundMessageCount) - .put(DeprecatedCensusConstants.RPC_SERVER_RESPONSE_BYTES, outboundWireSize) - .put(DeprecatedCensusConstants.RPC_SERVER_REQUEST_BYTES, inboundWireSize) + .put(RpcMeasureConstants.GRPC_SERVER_SERVER_LATENCY, elapsedTimeNanos / NANOS_PER_MILLI) + .put(RpcMeasureConstants.GRPC_SERVER_SENT_MESSAGES_PER_RPC, outboundMessageCount) + .put(RpcMeasureConstants.GRPC_SERVER_RECEIVED_MESSAGES_PER_RPC, inboundMessageCount) + .put(RpcMeasureConstants.GRPC_SERVER_SENT_BYTES_PER_RPC, outboundWireSize) + .put(RpcMeasureConstants.GRPC_SERVER_RECEIVED_BYTES_PER_RPC, inboundWireSize) .put( DeprecatedCensusConstants.RPC_SERVER_UNCOMPRESSED_RESPONSE_BYTES, outboundUncompressedSize) diff --git a/census/src/main/java/io/grpc/census/internal/DeprecatedCensusConstants.java b/census/src/main/java/io/grpc/census/internal/DeprecatedCensusConstants.java index 2b0a4763a28..7470bc81b15 100644 --- a/census/src/main/java/io/grpc/census/internal/DeprecatedCensusConstants.java +++ b/census/src/main/java/io/grpc/census/internal/DeprecatedCensusConstants.java @@ -27,49 +27,23 @@ public final class DeprecatedCensusConstants { public static final MeasureLong RPC_CLIENT_ERROR_COUNT = RpcMeasureConstants.RPC_CLIENT_ERROR_COUNT; - public static final MeasureDouble RPC_CLIENT_REQUEST_BYTES = - RpcMeasureConstants.RPC_CLIENT_REQUEST_BYTES; - public static final MeasureDouble RPC_CLIENT_RESPONSE_BYTES = - RpcMeasureConstants.RPC_CLIENT_RESPONSE_BYTES; - public static final MeasureDouble RPC_CLIENT_ROUNDTRIP_LATENCY = - RpcMeasureConstants.RPC_CLIENT_ROUNDTRIP_LATENCY; - public static final MeasureDouble RPC_CLIENT_SERVER_ELAPSED_TIME = - RpcMeasureConstants.RPC_CLIENT_SERVER_ELAPSED_TIME; public static final MeasureDouble RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES = RpcMeasureConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES; public static final MeasureDouble RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES = RpcMeasureConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES; - public static final MeasureLong RPC_CLIENT_STARTED_COUNT = - RpcMeasureConstants.RPC_CLIENT_STARTED_COUNT; public static final MeasureLong RPC_CLIENT_FINISHED_COUNT = RpcMeasureConstants.RPC_CLIENT_FINISHED_COUNT; - public static final MeasureLong RPC_CLIENT_REQUEST_COUNT = - RpcMeasureConstants.RPC_CLIENT_REQUEST_COUNT; - public static final MeasureLong RPC_CLIENT_RESPONSE_COUNT = - RpcMeasureConstants.RPC_CLIENT_RESPONSE_COUNT; public static final MeasureLong RPC_SERVER_ERROR_COUNT = RpcMeasureConstants.RPC_SERVER_ERROR_COUNT; - public static final MeasureDouble RPC_SERVER_REQUEST_BYTES = - RpcMeasureConstants.RPC_SERVER_REQUEST_BYTES; - public static final MeasureDouble RPC_SERVER_RESPONSE_BYTES = - RpcMeasureConstants.RPC_SERVER_RESPONSE_BYTES; public static final MeasureDouble RPC_SERVER_SERVER_ELAPSED_TIME = RpcMeasureConstants.RPC_SERVER_SERVER_ELAPSED_TIME; - public static final MeasureDouble RPC_SERVER_SERVER_LATENCY = - RpcMeasureConstants.RPC_SERVER_SERVER_LATENCY; public static final MeasureDouble RPC_SERVER_UNCOMPRESSED_REQUEST_BYTES = RpcMeasureConstants.RPC_SERVER_UNCOMPRESSED_REQUEST_BYTES; public static final MeasureDouble RPC_SERVER_UNCOMPRESSED_RESPONSE_BYTES = RpcMeasureConstants.RPC_SERVER_UNCOMPRESSED_RESPONSE_BYTES; - public static final MeasureLong RPC_SERVER_STARTED_COUNT = - RpcMeasureConstants.RPC_SERVER_STARTED_COUNT; public static final MeasureLong RPC_SERVER_FINISHED_COUNT = RpcMeasureConstants.RPC_SERVER_FINISHED_COUNT; - public static final MeasureLong RPC_SERVER_REQUEST_COUNT = - RpcMeasureConstants.RPC_SERVER_REQUEST_COUNT; - public static final MeasureLong RPC_SERVER_RESPONSE_COUNT = - RpcMeasureConstants.RPC_SERVER_RESPONSE_COUNT; private DeprecatedCensusConstants() {} } diff --git a/census/src/test/java/io/grpc/census/CensusModulesTest.java b/census/src/test/java/io/grpc/census/CensusModulesTest.java index 891a4e5e680..9768797d579 100644 --- a/census/src/test/java/io/grpc/census/CensusModulesTest.java +++ b/census/src/test/java/io/grpc/census/CensusModulesTest.java @@ -416,8 +416,7 @@ private void subtestClientBasicStatsDefaultContext( assertEquals(1, record.tags.size()); TagValue methodTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_METHOD); assertEquals(method.getFullMethodName(), methodTag.asString()); - assertEquals( - 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_STARTED_COUNT)); + assertEquals(1, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_STARTED_RPCS)); } else { assertNull(statsRecorder.pollRecord()); } @@ -484,25 +483,26 @@ private void subtestClientBasicStatsDefaultContext( 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_FINISHED_COUNT)); assertNull(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_ERROR_COUNT)); assertEquals( - 2, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_COUNT)); + 2, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_SENT_MESSAGES_PER_RPC)); assertEquals( 1028 + 99, - record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_BYTES)); + record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_SENT_BYTES_PER_RPC)); assertEquals( 1128 + 865, record.getMetricAsLongOrFail( DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES)); assertEquals( - 2, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_COUNT)); + 2, + record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_RECEIVED_MESSAGES_PER_RPC)); assertEquals( 33 + 154, - record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_BYTES)); + record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_RECEIVED_BYTES_PER_RPC)); assertEquals( 67 + 552, record.getMetricAsLongOrFail( DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES)); assertEquals(30 + 100 + 16 + 24, - record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ROUNDTRIP_LATENCY)); + record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_ROUNDTRIP_LATENCY)); assertZeroRetryRecorded(); } else { assertNull(statsRecorder.pollRecord()); @@ -526,8 +526,7 @@ public void recordRetryStats() { assertEquals(1, record.tags.size()); TagValue methodTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_METHOD); assertEquals(method.getFullMethodName(), methodTag.asString()); - assertEquals( - 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_STARTED_COUNT)); + assertEquals(1, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_STARTED_RPCS)); fakeClock.forwardTime(30, MILLISECONDS); tracer.outboundHeaders(); @@ -553,16 +552,16 @@ record = statsRecorder.pollRecord(); 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_FINISHED_COUNT)); assertEquals(1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ERROR_COUNT)); assertEquals( - 2, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_COUNT)); + 2, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_SENT_MESSAGES_PER_RPC)); assertEquals( - 1028, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_BYTES)); + 1028, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_SENT_BYTES_PER_RPC)); assertEquals( 1128, record.getMetricAsLongOrFail( DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES)); assertEquals( 30 + 100 + 24, - record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ROUNDTRIP_LATENCY)); + record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_ROUNDTRIP_LATENCY)); // faking retry fakeClock.forwardTime(1000, MILLISECONDS); @@ -571,8 +570,7 @@ record = statsRecorder.pollRecord(); assertEquals(1, record.tags.size()); methodTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_METHOD); assertEquals(method.getFullMethodName(), methodTag.asString()); - assertEquals( - 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_STARTED_COUNT)); + assertEquals(1, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_STARTED_RPCS)); tracer.outboundHeaders(); tracer.outboundMessage(0); assertRealTimeMetric( @@ -595,16 +593,16 @@ record = statsRecorder.pollRecord(); 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_FINISHED_COUNT)); assertEquals(1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ERROR_COUNT)); assertEquals( - 2, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_COUNT)); + 2, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_SENT_MESSAGES_PER_RPC)); assertEquals( - 1028, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_BYTES)); + 1028, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_SENT_BYTES_PER_RPC)); assertEquals( 1128, record.getMetricAsLongOrFail( DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES)); assertEquals( 100 , - record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ROUNDTRIP_LATENCY)); + record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_ROUNDTRIP_LATENCY)); // fake transparent retry fakeClock.forwardTime(10, MILLISECONDS); @@ -614,8 +612,7 @@ record = statsRecorder.pollRecord(); assertEquals(1, record.tags.size()); methodTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_METHOD); assertEquals(method.getFullMethodName(), methodTag.asString()); - assertEquals( - 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_STARTED_COUNT)); + assertEquals(1, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_STARTED_RPCS)); tracer.streamClosed(Status.UNAVAILABLE); record = statsRecorder.pollRecord(); statusTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_STATUS); @@ -624,9 +621,9 @@ record = statsRecorder.pollRecord(); 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_FINISHED_COUNT)); assertEquals(1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ERROR_COUNT)); assertEquals( - 0, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_COUNT)); + 0, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_SENT_MESSAGES_PER_RPC)); assertEquals( - 0, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_BYTES)); + 0, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_SENT_BYTES_PER_RPC)); // fake another transparent retry fakeClock.forwardTime(10, MILLISECONDS); @@ -634,8 +631,7 @@ record = statsRecorder.pollRecord(); STREAM_INFO.toBuilder().setIsTransparentRetry(true).build(), new Metadata()); record = statsRecorder.pollRecord(); assertEquals(1, record.tags.size()); - assertEquals( - 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_STARTED_COUNT)); + assertEquals(1, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_STARTED_RPCS)); tracer.outboundHeaders(); tracer.outboundMessage(0); assertRealTimeMetric( @@ -667,25 +663,25 @@ record = statsRecorder.pollRecord(); 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_FINISHED_COUNT)); assertThat(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_ERROR_COUNT)).isNull(); assertEquals( - 2, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_COUNT)); + 2, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_SENT_MESSAGES_PER_RPC)); assertEquals( - 1028, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_BYTES)); + 1028, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_SENT_BYTES_PER_RPC)); assertEquals( 1128, record.getMetricAsLongOrFail( DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES)); assertEquals( - 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_COUNT)); + 1, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_RECEIVED_MESSAGES_PER_RPC)); assertEquals( 33, - record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_BYTES)); + record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_RECEIVED_BYTES_PER_RPC)); assertEquals( 67, record.getMetricAsLongOrFail( DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES)); assertEquals( 16 + 24 , - record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ROUNDTRIP_LATENCY)); + record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_ROUNDTRIP_LATENCY)); record = statsRecorder.pollRecord(); methodTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_METHOD); @@ -818,9 +814,7 @@ public void clientStreamNeverCreatedStillRecordStats() { assertEquals(1, record.tags.size()); TagValue methodTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_METHOD); assertEquals(method.getFullMethodName(), methodTag.asString()); - assertEquals( - 1, - record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_STARTED_COUNT)); + assertEquals(1, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_STARTED_RPCS)); // Completion record record = statsRecorder.pollRecord(); @@ -837,24 +831,24 @@ record = statsRecorder.pollRecord(); 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ERROR_COUNT)); assertEquals( - 0, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_COUNT)); + 0, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_SENT_MESSAGES_PER_RPC)); assertEquals( - 0, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_BYTES)); + 0, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_SENT_BYTES_PER_RPC)); assertEquals( 0, record.getMetricAsLongOrFail( DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES)); assertEquals( - 0, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_COUNT)); + 0, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_RECEIVED_MESSAGES_PER_RPC)); assertEquals( - 0, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_BYTES)); + 0, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_RECEIVED_BYTES_PER_RPC)); assertEquals(0, record.getMetricAsLongOrFail( DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES)); assertEquals( 3000, - record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ROUNDTRIP_LATENCY)); - assertNull(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_SERVER_ELAPSED_TIME)); + record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_ROUNDTRIP_LATENCY)); + assertNull(record.getMetric(RpcMeasureConstants.GRPC_CLIENT_SERVER_LATENCY)); assertZeroRetryRecorded(); } @@ -1182,9 +1176,7 @@ private void subtestServerBasicStatsNoHeaders( assertEquals(1, record.tags.size()); TagValue methodTag = record.tags.get(RpcMeasureConstants.GRPC_SERVER_METHOD); assertEquals(method.getFullMethodName(), methodTag.asString()); - assertEquals( - 1, - record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_SERVER_STARTED_COUNT)); + assertEquals(1, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_SERVER_STARTED_RPCS)); } else { assertNull(statsRecorder.pollRecord()); } @@ -1258,24 +1250,25 @@ private void subtestServerBasicStatsNoHeaders( assertEquals( 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_SERVER_ERROR_COUNT)); assertEquals( - 2, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_SERVER_RESPONSE_COUNT)); + 2, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_SERVER_SENT_MESSAGES_PER_RPC)); assertEquals( 1028 + 99, - record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_SERVER_RESPONSE_BYTES)); + record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_SERVER_SENT_BYTES_PER_RPC)); assertEquals( 1128 + 865, record.getMetricAsLongOrFail( DeprecatedCensusConstants.RPC_SERVER_UNCOMPRESSED_RESPONSE_BYTES)); assertEquals( - 2, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_SERVER_REQUEST_COUNT)); + 2, + record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_SERVER_RECEIVED_MESSAGES_PER_RPC)); assertEquals( 34 + 154, - record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_SERVER_REQUEST_BYTES)); + record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_SERVER_RECEIVED_BYTES_PER_RPC)); assertEquals(67 + 552, record.getMetricAsLongOrFail( DeprecatedCensusConstants.RPC_SERVER_UNCOMPRESSED_REQUEST_BYTES)); assertEquals(100 + 16 + 24, - record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_SERVER_SERVER_LATENCY)); + record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_SERVER_SERVER_LATENCY)); } else { assertNull(statsRecorder.pollRecord()); } @@ -1399,24 +1392,24 @@ public void generateTraceSpanName() { private static void assertNoServerContent(StatsTestUtils.MetricsRecord record) { assertNull(record.getMetric(DeprecatedCensusConstants.RPC_SERVER_ERROR_COUNT)); - assertNull(record.getMetric(DeprecatedCensusConstants.RPC_SERVER_REQUEST_COUNT)); - assertNull(record.getMetric(DeprecatedCensusConstants.RPC_SERVER_RESPONSE_COUNT)); - assertNull(record.getMetric(DeprecatedCensusConstants.RPC_SERVER_REQUEST_BYTES)); - assertNull(record.getMetric(DeprecatedCensusConstants.RPC_SERVER_RESPONSE_BYTES)); + assertNull(record.getMetric(RpcMeasureConstants.GRPC_SERVER_RECEIVED_MESSAGES_PER_RPC)); + assertNull(record.getMetric(RpcMeasureConstants.GRPC_SERVER_SENT_MESSAGES_PER_RPC)); + assertNull(record.getMetric(RpcMeasureConstants.GRPC_SERVER_RECEIVED_BYTES_PER_RPC)); + assertNull(record.getMetric(RpcMeasureConstants.GRPC_SERVER_SENT_BYTES_PER_RPC)); assertNull(record.getMetric(DeprecatedCensusConstants.RPC_SERVER_SERVER_ELAPSED_TIME)); - assertNull(record.getMetric(DeprecatedCensusConstants.RPC_SERVER_SERVER_LATENCY)); + assertNull(record.getMetric(RpcMeasureConstants.GRPC_SERVER_SERVER_LATENCY)); assertNull(record.getMetric(DeprecatedCensusConstants.RPC_SERVER_UNCOMPRESSED_REQUEST_BYTES)); assertNull(record.getMetric(DeprecatedCensusConstants.RPC_SERVER_UNCOMPRESSED_RESPONSE_BYTES)); } private static void assertNoClientContent(StatsTestUtils.MetricsRecord record) { assertNull(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_ERROR_COUNT)); - assertNull(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_COUNT)); - assertNull(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_COUNT)); - assertNull(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_BYTES)); - assertNull(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_BYTES)); - assertNull(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_ROUNDTRIP_LATENCY)); - assertNull(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_SERVER_ELAPSED_TIME)); + assertNull(record.getMetric(RpcMeasureConstants.GRPC_CLIENT_SENT_MESSAGES_PER_RPC)); + assertNull(record.getMetric(RpcMeasureConstants.GRPC_CLIENT_RECEIVED_MESSAGES_PER_RPC)); + assertNull(record.getMetric(RpcMeasureConstants.GRPC_CLIENT_SENT_BYTES_PER_RPC)); + assertNull(record.getMetric(RpcMeasureConstants.GRPC_CLIENT_RECEIVED_BYTES_PER_RPC)); + assertNull(record.getMetric(RpcMeasureConstants.GRPC_CLIENT_ROUNDTRIP_LATENCY)); + assertNull(record.getMetric(RpcMeasureConstants.GRPC_CLIENT_SERVER_LATENCY)); assertNull(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES)); assertNull(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES)); } 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 05505f74132..bd11bac8a9c 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 @@ -2398,10 +2398,10 @@ private void checkCensus(MetricsRecord record, boolean isServer, if (isServer) { assertEquals( requests.size(), - record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_SERVER_REQUEST_COUNT)); + record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_SERVER_RECEIVED_MESSAGES_PER_RPC)); assertEquals( responses.size(), - record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_SERVER_RESPONSE_COUNT)); + record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_SERVER_SENT_MESSAGES_PER_RPC)); assertEquals( uncompressedRequestsSize, record.getMetricAsLongOrFail( @@ -2410,18 +2410,18 @@ private void checkCensus(MetricsRecord record, boolean isServer, uncompressedResponsesSize, record.getMetricAsLongOrFail( DeprecatedCensusConstants.RPC_SERVER_UNCOMPRESSED_RESPONSE_BYTES)); - assertNotNull(record.getMetric(DeprecatedCensusConstants.RPC_SERVER_SERVER_LATENCY)); + assertNotNull(record.getMetric(RpcMeasureConstants.GRPC_SERVER_SERVER_LATENCY)); // It's impossible to get the expected wire sizes because it may be compressed, so we just // check if they are recorded. - assertNotNull(record.getMetric(DeprecatedCensusConstants.RPC_SERVER_REQUEST_BYTES)); - assertNotNull(record.getMetric(DeprecatedCensusConstants.RPC_SERVER_RESPONSE_BYTES)); + assertNotNull(record.getMetric(RpcMeasureConstants.GRPC_SERVER_RECEIVED_BYTES_PER_RPC)); + assertNotNull(record.getMetric(RpcMeasureConstants.GRPC_SERVER_SENT_BYTES_PER_RPC)); } else { assertEquals( requests.size(), - record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_COUNT)); + record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_SENT_MESSAGES_PER_RPC)); assertEquals( responses.size(), - record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_COUNT)); + record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_RECEIVED_MESSAGES_PER_RPC)); assertEquals( uncompressedRequestsSize, record.getMetricAsLongOrFail( @@ -2430,11 +2430,11 @@ private void checkCensus(MetricsRecord record, boolean isServer, uncompressedResponsesSize, record.getMetricAsLongOrFail( DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES)); - assertNotNull(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_ROUNDTRIP_LATENCY)); + assertNotNull(record.getMetric(RpcMeasureConstants.GRPC_CLIENT_ROUNDTRIP_LATENCY)); // It's impossible to get the expected wire sizes because it may be compressed, so we just // check if they are recorded. - assertNotNull(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_BYTES)); - assertNotNull(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_BYTES)); + assertNotNull(record.getMetric(RpcMeasureConstants.GRPC_CLIENT_SENT_BYTES_PER_RPC)); + assertNotNull(record.getMetric(RpcMeasureConstants.GRPC_CLIENT_RECEIVED_BYTES_PER_RPC)); } } diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/RetryTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/RetryTest.java index 83c5d5a2c32..157fd79524e 100644 --- a/interop-testing/src/test/java/io/grpc/testing/integration/RetryTest.java +++ b/interop-testing/src/test/java/io/grpc/testing/integration/RetryTest.java @@ -209,7 +209,7 @@ private void elapseBackoff(long time, TimeUnit unit) throws Exception { private void assertRpcStartedRecorded() throws Exception { MetricsRecord record = clientStatsRecorder.pollRecord(5, SECONDS); - assertThat(record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_STARTED_COUNT)) + assertThat(record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_STARTED_RPCS)) .isEqualTo(1); } @@ -249,9 +249,9 @@ private void assertRpcStatusRecorded( assertThat(statusTag.asString()).isEqualTo(code.toString()); assertThat(record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_FINISHED_COUNT)) .isEqualTo(1); - assertThat(record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ROUNDTRIP_LATENCY)) + assertThat(record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_ROUNDTRIP_LATENCY)) .isEqualTo(roundtripLatencyMs); - assertThat(record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_COUNT)) + assertThat(record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_SENT_MESSAGES_PER_RPC)) .isEqualTo(outboundMessages); } From bcf5cde7dd31de4be3cfc8bf8264681abc8fb292 Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Mon, 12 Sep 2022 11:17:51 -0700 Subject: [PATCH 54/72] xds: prevent concurrent priority LB picker updates (#9363) If a child policy triggers an update to the parent priority policy it will be ignored if an update is already in process. --- .../io/grpc/xds/PriorityLoadBalancer.java | 65 ++++++++++------- .../io/grpc/xds/PriorityLoadBalancerTest.java | 69 ++++++++++++++++++- 2 files changed, 106 insertions(+), 28 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java b/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java index 5cae9139ae6..7a6bff4d779 100644 --- a/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java @@ -58,7 +58,7 @@ final class PriorityLoadBalancer extends LoadBalancer { private final XdsLogger logger; // Includes all active and deactivated children. Mutable. New entries are only added from priority - // 0 up to the selected priority. An entry is only deleted 15 minutes after the its deactivation. + // 0 up to the selected priority. An entry is only deleted 15 minutes after its deactivation. private final Map children = new HashMap<>(); // Following fields are only null initially. @@ -70,6 +70,8 @@ final class PriorityLoadBalancer extends LoadBalancer { @Nullable private String currentPriority; private ConnectivityState currentConnectivityState; private SubchannelPicker currentPicker; + // Set to true if currently in the process of handling resolved addresses. + private boolean handlingResolvedAddresses; PriorityLoadBalancer(Helper helper) { this.helper = checkNotNull(helper, "helper"); @@ -82,6 +84,15 @@ final class PriorityLoadBalancer extends LoadBalancer { @Override public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { + try { + handlingResolvedAddresses = true; + handleResolvedAddressesInternal(resolvedAddresses); + } finally { + handlingResolvedAddresses = false; + } + } + + public void handleResolvedAddressesInternal(ResolvedAddresses resolvedAddresses) { logger.log(XdsLogLevel.DEBUG, "Received resolution result: {0}", resolvedAddresses); this.resolvedAddresses = resolvedAddresses; PriorityLbConfig config = (PriorityLbConfig) resolvedAddresses.getLoadBalancingPolicyConfig(); @@ -297,32 +308,34 @@ public void refreshNameResolution() { @Override public void updateBalancingState(final ConnectivityState newState, final SubchannelPicker newPicker) { - syncContext.execute(new Runnable() { - @Override - public void run() { - if (!children.containsKey(priority)) { - return; - } - connectivityState = newState; - picker = newPicker; - if (deletionTimer != null && deletionTimer.isPending()) { - return; - } - if (newState.equals(CONNECTING) ) { - if (!failOverTimer.isPending() && seenReadyOrIdleSinceTransientFailure) { - failOverTimer = syncContext.schedule(new FailOverTask(), 10, TimeUnit.SECONDS, - executor); - } - } else if (newState.equals(READY) || newState.equals(IDLE)) { - seenReadyOrIdleSinceTransientFailure = true; - failOverTimer.cancel(); - } else if (newState.equals(TRANSIENT_FAILURE)) { - seenReadyOrIdleSinceTransientFailure = false; - failOverTimer.cancel(); - } - tryNextPriority(); + if (!children.containsKey(priority)) { + return; + } + connectivityState = newState; + picker = newPicker; + + if (deletionTimer != null && deletionTimer.isPending()) { + return; + } + if (newState.equals(CONNECTING)) { + if (!failOverTimer.isPending() && seenReadyOrIdleSinceTransientFailure) { + failOverTimer = syncContext.schedule(new FailOverTask(), 10, TimeUnit.SECONDS, + executor); } - }); + } else if (newState.equals(READY) || newState.equals(IDLE)) { + seenReadyOrIdleSinceTransientFailure = true; + failOverTimer.cancel(); + } else if (newState.equals(TRANSIENT_FAILURE)) { + seenReadyOrIdleSinceTransientFailure = false; + failOverTimer.cancel(); + } + + // If we are currently handling newly resolved addresses, let's not try to reconfigure as + // the address handling process will take care of that to provide an atomic config update. + if (handlingResolvedAddresses) { + return; + } + tryNextPriority(); } @Override diff --git a/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java index 6b1fae48f1d..7227dcb3b2f 100644 --- a/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java @@ -22,8 +22,8 @@ import static io.grpc.ConnectivityState.READY; import static io.grpc.ConnectivityState.TRANSIENT_FAILURE; import static io.grpc.xds.XdsSubchannelPickers.BUFFER_PICKER; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; @@ -676,7 +676,7 @@ public void raceBetweenShutdownAndChildLbBalancingStateUpdate() { .setAddresses(ImmutableList.of()) .setLoadBalancingPolicyConfig(priorityLbConfig) .build()); - verify(helper, times(2)).updateBalancingState(eq(CONNECTING), isA(SubchannelPicker.class)); + verify(helper).updateBalancingState(eq(CONNECTING), eq(BUFFER_PICKER)); // LB shutdown and subchannel state change can happen simultaneously. If shutdown runs first, // any further balancing state update should be ignored. @@ -686,6 +686,26 @@ public void raceBetweenShutdownAndChildLbBalancingStateUpdate() { verifyNoMoreInteractions(helper); } + @Test + public void noDuplicateOverallBalancingStateUpdate() { + FakeLoadBalancerProvider fakeLbProvider = new FakeLoadBalancerProvider(); + + PriorityChildConfig priorityChildConfig0 = + new PriorityChildConfig(new PolicySelection(fakeLbProvider, new Object()), true); + PriorityChildConfig priorityChildConfig1 = + new PriorityChildConfig(new PolicySelection(fakeLbProvider, new Object()), false); + PriorityLbConfig priorityLbConfig = + new PriorityLbConfig( + ImmutableMap.of("p0", priorityChildConfig0, "p1", priorityChildConfig1), + ImmutableList.of("p0", "p1")); + priorityLb.handleResolvedAddresses( + ResolvedAddresses.newBuilder() + .setAddresses(ImmutableList.of()) + .setLoadBalancingPolicyConfig(priorityLbConfig) + .build()); + verify(helper, times(1)).updateBalancingState(any(), any()); + } + private void assertLatestConnectivityState(ConnectivityState expectedState) { verify(helper, atLeastOnce()) .updateBalancingState(connectivityStateCaptor.capture(), pickerCaptor.capture()); @@ -714,4 +734,49 @@ private void assertCurrentPickerIsBufferPicker() { PickResult pickResult = pickerCaptor.getValue().pickSubchannel(mock(PickSubchannelArgs.class)); assertThat(pickResult).isEqualTo(PickResult.withNoResult()); } + + private static class FakeLoadBalancerProvider extends LoadBalancerProvider { + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public int getPriority() { + return 5; + } + + @Override + public String getPolicyName() { + return "foo"; + } + + @Override + public LoadBalancer newLoadBalancer(Helper helper) { + return new FakeLoadBalancer(helper); + } + } + + static class FakeLoadBalancer extends LoadBalancer { + + private Helper helper; + + FakeLoadBalancer(Helper helper) { + this.helper = helper; + } + + @Override + public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { + helper.updateBalancingState(TRANSIENT_FAILURE, new ErrorPicker(Status.INTERNAL)); + } + + @Override + public void handleNameResolutionError(Status error) { + } + + @Override + public void shutdown() { + } + } } From 9853a0c4639a13a523530f99730cc93d113151a8 Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Mon, 12 Sep 2022 13:17:16 -0700 Subject: [PATCH 55/72] core: Don't delegate inappropriate ConfigSelector errors (#9536) In case a control plane returns an "inappropriate" response code, it is converted to INTERNAL to highlight the bug in the control plane. https://github.com/grpc/proposal/blob/master/A54-restrict-control-plane-status-codes.md --- .../io/grpc/internal/ManagedChannelImpl.java | 3 +- .../ConfigSelectingClientCallTest.java | 28 ++++++++++++++++++- 2 files changed, 29 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 6e80eb39255..aefe76dcb5a 100644 --- a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java +++ b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java @@ -1199,7 +1199,8 @@ public void start(Listener observer, Metadata headers) { InternalConfigSelector.Result result = configSelector.selectConfig(args); Status status = result.getStatus(); if (!status.isOk()) { - executeCloseObserverInContext(observer, status); + executeCloseObserverInContext(observer, + GrpcUtil.replaceInappropriateControlPlaneStatus(status)); delegate = (ClientCall) NOOP_CALL; return; } diff --git a/core/src/test/java/io/grpc/internal/ConfigSelectingClientCallTest.java b/core/src/test/java/io/grpc/internal/ConfigSelectingClientCallTest.java index 9b3f8ad3b23..b5b6fef3c9d 100644 --- a/core/src/test/java/io/grpc/internal/ConfigSelectingClientCallTest.java +++ b/core/src/test/java/io/grpc/internal/ConfigSelectingClientCallTest.java @@ -121,6 +121,31 @@ public void selectionErrorPropagatedToListener() { InternalConfigSelector configSelector = new InternalConfigSelector() { @Override public Result selectConfig(PickSubchannelArgs args) { + return Result.forError(Status.DEADLINE_EXCEEDED); + } + }; + + ClientCall configSelectingClientCall = new ConfigSelectingClientCall<>( + configSelector, + channel, + MoreExecutors.directExecutor(), + method, + CallOptions.DEFAULT); + configSelectingClientCall.start(callListener, new Metadata()); + ArgumentCaptor statusCaptor = ArgumentCaptor.forClass(null); + verify(callListener).onClose(statusCaptor.capture(), any(Metadata.class)); + assertThat(statusCaptor.getValue().getCode()).isEqualTo(Status.Code.DEADLINE_EXCEEDED); + + // The call should not delegate to null and fail methods with NPE. + configSelectingClientCall.request(1); + } + + @Test + public void selectionErrorPropagatedToListener_inappropriateStatus() { + InternalConfigSelector configSelector = new InternalConfigSelector() { + @Override + public Result selectConfig(PickSubchannelArgs args) { + // This status code is considered inappropriate to propagate from the control plane... return Result.forError(Status.FAILED_PRECONDITION); } }; @@ -134,7 +159,8 @@ public Result selectConfig(PickSubchannelArgs args) { configSelectingClientCall.start(callListener, new Metadata()); ArgumentCaptor statusCaptor = ArgumentCaptor.forClass(null); verify(callListener).onClose(statusCaptor.capture(), any(Metadata.class)); - assertThat(statusCaptor.getValue().getCode()).isEqualTo(Status.Code.FAILED_PRECONDITION); + // ... so it should be represented as an internal error to highlight the control plane bug. + assertThat(statusCaptor.getValue().getCode()).isEqualTo(Status.Code.INTERNAL); // The call should not delegate to null and fail methods with NPE. configSelectingClientCall.request(1); From 38b338228ae523bc727313317a72f9d40ec05c0e Mon Sep 17 00:00:00 2001 From: Kun Zhang Date: Tue, 13 Sep 2022 08:55:05 -0700 Subject: [PATCH 56/72] core: add getCallOptions() to CallCredentials.RequestInfo (#9538) This is needed internally (b/134067789#comment20). --- api/src/main/java/io/grpc/CallCredentials.java | 7 +++++++ .../internal/CallCredentialsApplyingTransportFactory.java | 5 +++++ .../java/io/grpc/internal/CallCredentialsApplyingTest.java | 1 + 3 files changed, 13 insertions(+) diff --git a/api/src/main/java/io/grpc/CallCredentials.java b/api/src/main/java/io/grpc/CallCredentials.java index 3e588e24027..1d353fb9f6d 100644 --- a/api/src/main/java/io/grpc/CallCredentials.java +++ b/api/src/main/java/io/grpc/CallCredentials.java @@ -91,6 +91,13 @@ public abstract static class RequestInfo { */ public abstract MethodDescriptor getMethodDescriptor(); + /** + * The call options used to call this RPC. + */ + public CallOptions getCallOptions() { + throw new UnsupportedOperationException("Not implemented"); + } + /** * The security level on the transport. */ diff --git a/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java b/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java index e008302d7ae..1537d1c664f 100644 --- a/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java +++ b/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java @@ -127,6 +127,11 @@ public ClientStream newStream( return method; } + @Override + public CallOptions getCallOptions() { + return callOptions; + } + @Override public SecurityLevel getSecurityLevel() { return firstNonNull( diff --git a/core/src/test/java/io/grpc/internal/CallCredentialsApplyingTest.java b/core/src/test/java/io/grpc/internal/CallCredentialsApplyingTest.java index a6142cc1a2f..9ad655735f8 100644 --- a/core/src/test/java/io/grpc/internal/CallCredentialsApplyingTest.java +++ b/core/src/test/java/io/grpc/internal/CallCredentialsApplyingTest.java @@ -148,6 +148,7 @@ public void parameterPropagation_base() { RequestInfo info = infoCaptor.getValue(); assertSame(transportAttrs, info.getTransportAttrs()); assertSame(method, info.getMethodDescriptor()); + assertSame(callOptions, info.getCallOptions()); assertSame(AUTHORITY, info.getAuthority()); assertSame(SecurityLevel.NONE, info.getSecurityLevel()); } From 944cbf84ede61d8e71ddb46cf2626b274545ac48 Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Tue, 13 Sep 2022 09:57:55 -0700 Subject: [PATCH 57/72] core: Comment on "inappropriate" service config errors (#9542) https://github.com/grpc/proposal/blob/master/A54-restrict-control-plane-status-codes.md --- core/src/main/java/io/grpc/internal/ManagedChannelImpl.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java index aefe76dcb5a..9cc7d70d1e7 100644 --- a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java +++ b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java @@ -1811,6 +1811,10 @@ public void run() { 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; } else { From 341fea8996fbd48f40140636e72e766e31e32852 Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Tue, 13 Sep 2022 13:36:40 -0700 Subject: [PATCH 58/72] core: Convert inappropriate call cred errors (#9543) If a CallCredentials implementation returns an error that is not appropriate to propagate from the control plane to the data plane, we convert it to an INTERNAL error. This makes the inappropriate control plane behavior to be discoverable in the logs. https://github.com/grpc/proposal/blob/master/A54-restrict-control-plane-status-codes.md --- .../io/grpc/internal/MetadataApplierImpl.java | 3 +- .../internal/CallCredentialsApplyingTest.java | 38 ++++++++++++++++++- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/MetadataApplierImpl.java b/core/src/main/java/io/grpc/internal/MetadataApplierImpl.java index 6893713c1d2..12cab15053f 100644 --- a/core/src/main/java/io/grpc/internal/MetadataApplierImpl.java +++ b/core/src/main/java/io/grpc/internal/MetadataApplierImpl.java @@ -82,7 +82,8 @@ public void apply(Metadata headers) { public void fail(Status status) { checkArgument(!status.isOk(), "Cannot fail with OK status"); checkState(!finalized, "apply() or fail() already called"); - finalizeWith(new FailingClientStream(status, tracers)); + finalizeWith( + new FailingClientStream(GrpcUtil.replaceInappropriateControlPlaneStatus(status), tracers)); } private void finalizeWith(ClientStream stream) { diff --git a/core/src/test/java/io/grpc/internal/CallCredentialsApplyingTest.java b/core/src/test/java/io/grpc/internal/CallCredentialsApplyingTest.java index 9ad655735f8..666c5393b6a 100644 --- a/core/src/test/java/io/grpc/internal/CallCredentialsApplyingTest.java +++ b/core/src/test/java/io/grpc/internal/CallCredentialsApplyingTest.java @@ -16,6 +16,7 @@ package io.grpc.internal; +import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; @@ -41,6 +42,7 @@ import io.grpc.MethodDescriptor; import io.grpc.SecurityLevel; import io.grpc.Status; +import io.grpc.Status.Code; import io.grpc.StringMarshaller; import java.net.SocketAddress; import java.util.concurrent.Executor; @@ -265,7 +267,7 @@ public void applyMetadata_inline() { @Test public void fail_inline() { - final Status error = Status.FAILED_PRECONDITION.withDescription("channel not secure for creds"); + final Status error = Status.UNAVAILABLE.withDescription("channel not secure for creds"); when(mockTransport.getAttributes()).thenReturn(Attributes.EMPTY); doAnswer(new Answer() { @Override @@ -291,6 +293,38 @@ public Void answer(InvocationOnMock invocation) throws Throwable { verify(mockTransport).shutdownNow(Status.UNAVAILABLE); } + // If the creds return an error that is inappropriate to directly propagate from the control plane + // to the call, it should be converted to an INTERNAL error. + @Test + public void fail_inline_inappropriate_error() { + final Status error = Status.NOT_FOUND.withDescription("channel not secure for creds"); + when(mockTransport.getAttributes()).thenReturn(Attributes.EMPTY); + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + CallCredentials.MetadataApplier applier = + (CallCredentials.MetadataApplier) invocation.getArguments()[2]; + applier.fail(error); + return null; + } + }).when(mockCreds).applyRequestMetadata(any(RequestInfo.class), + same(mockExecutor), any(CallCredentials.MetadataApplier.class)); + + FailingClientStream stream = (FailingClientStream) transport.newStream( + method, origHeaders, callOptions, tracers); + + verify(mockTransport, never()).newStream( + any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class), + ArgumentMatchers.any()); + assertThat(stream.getError().getCode()).isEqualTo(Code.INTERNAL); + assertThat(stream.getError().getDescription()).contains("Inappropriate"); + assertThat(stream.getError().getCause()).isNull(); + transport.shutdownNow(Status.UNAVAILABLE); + assertTrue(transport.newStream(method, origHeaders, callOptions, tracers) + instanceof FailingClientStream); + verify(mockTransport).shutdownNow(Status.UNAVAILABLE); + } + @Test public void applyMetadata_delayed() { when(mockTransport.getAttributes()).thenReturn(Attributes.EMPTY); @@ -406,7 +440,7 @@ public void fail_delayed() { verify(mockCreds).applyRequestMetadata(any(RequestInfo.class), same(mockExecutor), applierCaptor.capture()); - Status error = Status.FAILED_PRECONDITION.withDescription("channel not secure for creds"); + Status error = Status.UNAVAILABLE.withDescription("channel not secure for creds"); applierCaptor.getValue().fail(error); verify(mockTransport, never()).newStream( From 79c4c355ba47c3bb5e87e64c3eb32b7ee70e105c Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Thu, 15 Sep 2022 09:35:23 -0700 Subject: [PATCH 59/72] okhttp: Fair treatment when writing out streams (#9545) When allocating bytes to streams within a flow control window we always go through the streams in the same order. This can lead to large streams hogging all the bytes and a smaller one down the list getting starved out. This change shuffles the stream array to lower the chance of this happening. Fixes #9089 --- .../src/main/java/io/grpc/okhttp/OutboundFlowController.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/okhttp/src/main/java/io/grpc/okhttp/OutboundFlowController.java b/okhttp/src/main/java/io/grpc/okhttp/OutboundFlowController.java index 35117c1b225..2c959ee0768 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OutboundFlowController.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OutboundFlowController.java @@ -25,6 +25,8 @@ import com.google.common.base.Preconditions; import io.grpc.okhttp.internal.framed.FrameWriter; import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; import javax.annotation.Nullable; import okio.Buffer; @@ -152,6 +154,7 @@ public StreamState createState(Stream stream, int streamId) { */ public void writeStreams() { StreamState[] states = transport.getActiveStreams(); + Collections.shuffle(Arrays.asList(states)); int connectionWindow = connectionState.window(); for (int numStreams = states.length; numStreams > 0 && connectionWindow > 0;) { int nextNumStreams = 0; From 15033caf1cfb06b500d219be8a35b9ebec31c133 Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Thu, 15 Sep 2022 14:21:10 -0700 Subject: [PATCH 60/72] core: pick_first LB to use acceptResolvedAddresses() (#9548) --- .../grpc/internal/PickFirstLoadBalancer.java | 11 ++++- .../internal/PickFirstLoadBalancerTest.java | 40 ++++++++++++++----- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/PickFirstLoadBalancer.java b/core/src/main/java/io/grpc/internal/PickFirstLoadBalancer.java index d5f74db54a7..e9c4d79150a 100644 --- a/core/src/main/java/io/grpc/internal/PickFirstLoadBalancer.java +++ b/core/src/main/java/io/grpc/internal/PickFirstLoadBalancer.java @@ -45,8 +45,15 @@ final class PickFirstLoadBalancer extends LoadBalancer { } @Override - public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { + public boolean acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { List servers = resolvedAddresses.getAddresses(); + if (servers.isEmpty()) { + handleNameResolutionError(Status.UNAVAILABLE.withDescription( + "NameResolver returned no usable address. addrs=" + resolvedAddresses.getAddresses() + + ", attrs=" + resolvedAddresses.getAttributes())); + return false; + } + if (subchannel == null) { final Subchannel subchannel = helper.createSubchannel( CreateSubchannelArgs.newBuilder() @@ -67,6 +74,8 @@ public void onSubchannelState(ConnectivityStateInfo stateInfo) { } else { subchannel.updateAddresses(servers); } + + return true; } @Override diff --git a/core/src/test/java/io/grpc/internal/PickFirstLoadBalancerTest.java b/core/src/test/java/io/grpc/internal/PickFirstLoadBalancerTest.java index 720341da0cb..8bace289584 100644 --- a/core/src/test/java/io/grpc/internal/PickFirstLoadBalancerTest.java +++ b/core/src/test/java/io/grpc/internal/PickFirstLoadBalancerTest.java @@ -50,6 +50,7 @@ import io.grpc.LoadBalancer.SubchannelPicker; import io.grpc.LoadBalancer.SubchannelStateListener; import io.grpc.Status; +import io.grpc.Status.Code; import io.grpc.SynchronizationContext; import java.net.SocketAddress; import java.util.List; @@ -89,6 +90,8 @@ public void uncaughtException(Thread t, Throwable e) { @Captor private ArgumentCaptor pickerCaptor; @Captor + private ArgumentCaptor connectivityStateCaptor; + @Captor private ArgumentCaptor createArgsCaptor; @Captor private ArgumentCaptor stateListenerCaptor; @@ -121,7 +124,7 @@ public void tearDown() throws Exception { @Test public void pickAfterResolved() throws Exception { - loadBalancer.handleResolvedAddresses( + loadBalancer.acceptResolvedAddresses( ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(affinity).build()); verify(mockHelper).createSubchannel(createArgsCaptor.capture()); @@ -139,7 +142,7 @@ public void pickAfterResolved() throws Exception { @Test public void requestConnectionPicker() throws Exception { - loadBalancer.handleResolvedAddresses( + loadBalancer.acceptResolvedAddresses( ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(affinity).build()); InOrder inOrder = inOrder(mockHelper, mockSubchannel); @@ -164,7 +167,7 @@ public void requestConnectionPicker() throws Exception { @Test public void refreshNameResolutionAfterSubchannelConnectionBroken() { - loadBalancer.handleResolvedAddresses( + loadBalancer.acceptResolvedAddresses( ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(affinity).build()); verify(mockHelper).createSubchannel(any(CreateSubchannelArgs.class)); @@ -196,11 +199,11 @@ public void refreshNameResolutionAfterSubchannelConnectionBroken() { @Test public void pickAfterResolvedAndUnchanged() throws Exception { - loadBalancer.handleResolvedAddresses( + loadBalancer.acceptResolvedAddresses( ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(affinity).build()); verify(mockSubchannel).start(any(SubchannelStateListener.class)); verify(mockSubchannel).requestConnection(); - loadBalancer.handleResolvedAddresses( + loadBalancer.acceptResolvedAddresses( ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(affinity).build()); verify(mockSubchannel).updateAddresses(eq(servers)); verifyNoMoreInteractions(mockSubchannel); @@ -223,7 +226,7 @@ public void pickAfterResolvedAndChanged() throws Exception { InOrder inOrder = inOrder(mockHelper, mockSubchannel); - loadBalancer.handleResolvedAddresses( + loadBalancer.acceptResolvedAddresses( ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(affinity).build()); inOrder.verify(mockHelper).createSubchannel(createArgsCaptor.capture()); verify(mockSubchannel).start(any(SubchannelStateListener.class)); @@ -233,7 +236,7 @@ public void pickAfterResolvedAndChanged() throws Exception { verify(mockSubchannel).requestConnection(); assertEquals(mockSubchannel, pickerCaptor.getValue().pickSubchannel(mockArgs).getSubchannel()); - loadBalancer.handleResolvedAddresses( + loadBalancer.acceptResolvedAddresses( ResolvedAddresses.newBuilder().setAddresses(newServers).setAttributes(affinity).build()); inOrder.verify(mockSubchannel).updateAddresses(eq(newServers)); @@ -245,7 +248,7 @@ public void pickAfterResolvedAndChanged() throws Exception { public void pickAfterStateChangeAfterResolution() throws Exception { InOrder inOrder = inOrder(mockHelper); - loadBalancer.handleResolvedAddresses( + loadBalancer.acceptResolvedAddresses( ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(affinity).build()); inOrder.verify(mockHelper).createSubchannel(createArgsCaptor.capture()); CreateSubchannelArgs args = createArgsCaptor.getValue(); @@ -288,6 +291,21 @@ public void nameResolutionError() throws Exception { verifyNoMoreInteractions(mockHelper); } + @Test + public void nameResolutionError_emptyAddressList() throws Exception { + servers.clear(); + loadBalancer.acceptResolvedAddresses( + ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(affinity).build()); + verify(mockHelper).updateBalancingState(connectivityStateCaptor.capture(), + pickerCaptor.capture()); + PickResult pickResult = pickerCaptor.getValue().pickSubchannel(mockArgs); + assertThat(pickResult.getSubchannel()).isNull(); + assertThat(pickResult.getStatus().getCode()).isEqualTo(Code.UNAVAILABLE); + assertThat(pickResult.getStatus().getDescription()).contains("returned no usable address"); + verify(mockSubchannel, never()).requestConnection(); + verifyNoMoreInteractions(mockHelper); + } + @Test public void nameResolutionSuccessAfterError() throws Exception { InOrder inOrder = inOrder(mockHelper); @@ -297,7 +315,7 @@ public void nameResolutionSuccessAfterError() throws Exception { .updateBalancingState(any(ConnectivityState.class), any(SubchannelPicker.class)); verify(mockSubchannel, never()).requestConnection(); - loadBalancer.handleResolvedAddresses( + loadBalancer.acceptResolvedAddresses( ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(affinity).build()); inOrder.verify(mockHelper).createSubchannel(createArgsCaptor.capture()); CreateSubchannelArgs args = createArgsCaptor.getValue(); @@ -318,7 +336,7 @@ public void nameResolutionSuccessAfterError() throws Exception { @Test public void nameResolutionErrorWithStateChanges() throws Exception { InOrder inOrder = inOrder(mockHelper); - loadBalancer.handleResolvedAddresses( + loadBalancer.acceptResolvedAddresses( ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(affinity).build()); inOrder.verify(mockHelper).createSubchannel(createArgsCaptor.capture()); verify(mockSubchannel).start(stateListenerCaptor.capture()); @@ -358,7 +376,7 @@ public void requestConnection() { loadBalancer.requestConnection(); verify(mockSubchannel, never()).requestConnection(); - loadBalancer.handleResolvedAddresses( + loadBalancer.acceptResolvedAddresses( ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(affinity).build()); verify(mockSubchannel).requestConnection(); From a3c1d7711f0f87ea4d79b94558938ab98735cf99 Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Thu, 15 Sep 2022 16:08:55 -0700 Subject: [PATCH 61/72] okhttp: add okhttpServerBuilder permitKeepAliveTime() and permitKeepAliveWithoutCalls() for server keepAlive enforcement (#9544) --- .../io/grpc/internal}/KeepAliveEnforcer.java | 8 +- .../grpc/internal}/KeepAliveEnforcerTest.java | 2 +- .../io/grpc/netty/NettyServerHandler.java | 1 + .../io/grpc/netty/NettyServerHandlerTest.java | 1 + .../io/grpc/okhttp/OkHttpServerBuilder.java | 38 +++++++ .../io/grpc/okhttp/OkHttpServerTransport.java | 57 ++++++++-- .../okhttp/OkHttpServerTransportTest.java | 100 +++++++++++++++++- 7 files changed, 194 insertions(+), 13 deletions(-) rename {netty/src/main/java/io/grpc/netty => core/src/main/java/io/grpc/internal}/KeepAliveEnforcer.java (94%) rename {netty/src/test/java/io/grpc/netty => core/src/test/java/io/grpc/internal}/KeepAliveEnforcerTest.java (99%) diff --git a/netty/src/main/java/io/grpc/netty/KeepAliveEnforcer.java b/core/src/main/java/io/grpc/internal/KeepAliveEnforcer.java similarity index 94% rename from netty/src/main/java/io/grpc/netty/KeepAliveEnforcer.java rename to core/src/main/java/io/grpc/internal/KeepAliveEnforcer.java index 6470e440327..dd539e75a18 100644 --- a/netty/src/main/java/io/grpc/netty/KeepAliveEnforcer.java +++ b/core/src/main/java/io/grpc/internal/KeepAliveEnforcer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.netty; +package io.grpc.internal; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; @@ -22,11 +22,11 @@ import javax.annotation.CheckReturnValue; /** Monitors the client's PING usage to make sure the rate is permitted. */ -class KeepAliveEnforcer { +public final class KeepAliveEnforcer { @VisibleForTesting - static final int MAX_PING_STRIKES = 2; + public static final int MAX_PING_STRIKES = 2; @VisibleForTesting - static final long IMPLICIT_PERMIT_TIME_NANOS = TimeUnit.HOURS.toNanos(2); + public static final long IMPLICIT_PERMIT_TIME_NANOS = TimeUnit.HOURS.toNanos(2); private final boolean permitWithoutCalls; private final long minTimeNanos; diff --git a/netty/src/test/java/io/grpc/netty/KeepAliveEnforcerTest.java b/core/src/test/java/io/grpc/internal/KeepAliveEnforcerTest.java similarity index 99% rename from netty/src/test/java/io/grpc/netty/KeepAliveEnforcerTest.java rename to core/src/test/java/io/grpc/internal/KeepAliveEnforcerTest.java index 8dfeb990e2b..c58ed6ea160 100644 --- a/netty/src/test/java/io/grpc/netty/KeepAliveEnforcerTest.java +++ b/core/src/test/java/io/grpc/internal/KeepAliveEnforcerTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.netty; +package io.grpc.internal; import static com.google.common.truth.Truth.assertThat; diff --git a/netty/src/main/java/io/grpc/netty/NettyServerHandler.java b/netty/src/main/java/io/grpc/netty/NettyServerHandler.java index fa21221a0ae..62dd50ce65e 100644 --- a/netty/src/main/java/io/grpc/netty/NettyServerHandler.java +++ b/netty/src/main/java/io/grpc/netty/NettyServerHandler.java @@ -44,6 +44,7 @@ import io.grpc.ServerStreamTracer; import io.grpc.Status; import io.grpc.internal.GrpcUtil; +import io.grpc.internal.KeepAliveEnforcer; import io.grpc.internal.KeepAliveManager; import io.grpc.internal.LogExceptionRunnable; import io.grpc.internal.MaxConnectionIdleManager; diff --git a/netty/src/test/java/io/grpc/netty/NettyServerHandlerTest.java b/netty/src/test/java/io/grpc/netty/NettyServerHandlerTest.java index 170273e2c60..72c267a4825 100644 --- a/netty/src/test/java/io/grpc/netty/NettyServerHandlerTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyServerHandlerTest.java @@ -61,6 +61,7 @@ import io.grpc.Status.Code; import io.grpc.StreamTracer; import io.grpc.internal.GrpcUtil; +import io.grpc.internal.KeepAliveEnforcer; import io.grpc.internal.KeepAliveManager; import io.grpc.internal.ServerStream; import io.grpc.internal.ServerStreamListener; diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java index 0e1273c7f23..d3ea82894b0 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java @@ -20,6 +20,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotCall; import io.grpc.ChoiceServerCredentials; import io.grpc.ExperimentalApi; @@ -117,6 +118,8 @@ public static OkHttpServerBuilder forPort(SocketAddress address, ServerCredentia int maxInboundMetadataSize = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE; int maxInboundMessageSize = GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE; long maxConnectionIdleInNanos = MAX_CONNECTION_IDLE_NANOS_DISABLED; + boolean permitKeepAliveWithoutCalls; + long permitKeepAliveTimeInNanos = TimeUnit.MINUTES.toNanos(5); @VisibleForTesting OkHttpServerBuilder( @@ -223,6 +226,41 @@ public OkHttpServerBuilder keepAliveTimeout(long keepAliveTimeout, TimeUnit time return this; } + /** + * Specify the most aggressive keep-alive time clients are permitted to configure. The server will + * try to detect clients exceeding this rate and when detected will forcefully close the + * connection. The default is 5 minutes. + * + *

Even though a default is defined that allows some keep-alives, clients must not use + * keep-alive without approval from the service owner. Otherwise, they may experience failures in + * the future if the service becomes more restrictive. When unthrottled, keep-alives can cause a + * significant amount of traffic and CPU usage, so clients and servers should be conservative in + * what they use and accept. + * + * @see #permitKeepAliveWithoutCalls(boolean) + */ + @CanIgnoreReturnValue + @Override + public OkHttpServerBuilder permitKeepAliveTime(long keepAliveTime, TimeUnit timeUnit) { + checkArgument(keepAliveTime >= 0, "permit keepalive time must be non-negative: %s", + keepAliveTime); + permitKeepAliveTimeInNanos = timeUnit.toNanos(keepAliveTime); + return this; + } + + /** + * Sets whether to allow clients to send keep-alive HTTP/2 PINGs even if there are no outstanding + * RPCs on the connection. Defaults to {@code false}. + * + * @see #permitKeepAliveTime(long, TimeUnit) + */ + @CanIgnoreReturnValue + @Override + public OkHttpServerBuilder permitKeepAliveWithoutCalls(boolean permit) { + permitKeepAliveWithoutCalls = permit; + return this; + } + /** * Sets the flow control window in bytes. If not called, the default value is 64 KiB. */ diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerTransport.java index b2b581d7155..f6099bec17a 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerTransport.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerTransport.java @@ -29,6 +29,7 @@ import io.grpc.ServerStreamTracer; import io.grpc.Status; import io.grpc.internal.GrpcUtil; +import io.grpc.internal.KeepAliveEnforcer; import io.grpc.internal.KeepAliveManager; import io.grpc.internal.MaxConnectionIdleManager; import io.grpc.internal.ObjectPool; @@ -95,6 +96,7 @@ final class OkHttpServerTransport implements ServerTransport, private Attributes attributes; private KeepAliveManager keepAliveManager; private MaxConnectionIdleManager maxConnectionIdleManager; + private final KeepAliveEnforcer keepAliveEnforcer; private final Object lock = new Object(); @GuardedBy("lock") @@ -137,6 +139,8 @@ public OkHttpServerTransport(Config config, Socket bareSocket) { logId = InternalLogId.allocate(getClass(), bareSocket.getRemoteSocketAddress().toString()); transportExecutor = config.transportExecutorPool.getObject(); scheduledExecutorService = config.scheduledExecutorServicePool.getObject(); + keepAliveEnforcer = new KeepAliveEnforcer(config.permitKeepAliveWithoutCalls, + config.permitKeepAliveTimeInNanos, TimeUnit.NANOSECONDS); } public void start(ServerTransportListener listener) { @@ -159,6 +163,27 @@ private void startIo(SerializingExecutor serializingExecutor) { asyncSink.becomeConnected(Okio.sink(socket), socket); FrameWriter rawFrameWriter = asyncSink.limitControlFramesWriter( variant.newWriter(Okio.buffer(asyncSink), false)); + FrameWriter writeMonitoringFrameWriter = new ForwardingFrameWriter(rawFrameWriter) { + @Override + public void synReply(boolean outFinished, int streamId, List

headerBlock) + throws IOException { + keepAliveEnforcer.resetCounters(); + super.synReply(outFinished, streamId, headerBlock); + } + + @Override + public void headers(int streamId, List
headerBlock) throws IOException { + keepAliveEnforcer.resetCounters(); + super.headers(streamId, headerBlock); + } + + @Override + public void data(boolean outFinished, int streamId, Buffer source, int byteCount) + throws IOException { + keepAliveEnforcer.resetCounters(); + super.data(outFinished, streamId, source, byteCount); + } + }; synchronized (lock) { this.securityInfo = result.securityInfo; @@ -167,7 +192,7 @@ private void startIo(SerializingExecutor serializingExecutor) { // does not propagate syscall errors through the FrameWriter. But we handle the // AsyncSink failures with the same TransportExceptionHandler instance so it is all // mixed back together. - frameWriter = new ExceptionHandlingFrameWriter(this, rawFrameWriter); + frameWriter = new ExceptionHandlingFrameWriter(this, writeMonitoringFrameWriter); outboundFlow = new OutboundFlowController(this, frameWriter); // These writes will be queued in the serializingExecutor waiting for this function to @@ -381,8 +406,11 @@ public OutboundFlowController.StreamState[] getActiveStreams() { void streamClosed(int streamId, boolean flush) { synchronized (lock) { streams.remove(streamId); - if (maxConnectionIdleManager != null && streams.isEmpty()) { - maxConnectionIdleManager.onTransportIdle(); + if (streams.isEmpty()) { + keepAliveEnforcer.onTransportIdle(); + if (maxConnectionIdleManager != null) { + maxConnectionIdleManager.onTransportIdle(); + } } if (gracefulShutdown && streams.isEmpty()) { frameWriter.close(); @@ -449,6 +477,8 @@ static final class Config { final int maxInboundMessageSize; final int maxInboundMetadataSize; final long maxConnectionIdleNanos; + final boolean permitKeepAliveWithoutCalls; + final long permitKeepAliveTimeInNanos; public Config( OkHttpServerBuilder builder, @@ -469,6 +499,8 @@ public Config( maxInboundMessageSize = builder.maxInboundMessageSize; maxInboundMetadataSize = builder.maxInboundMetadataSize; maxConnectionIdleNanos = builder.maxConnectionIdleInNanos; + permitKeepAliveWithoutCalls = builder.permitKeepAliveWithoutCalls; + permitKeepAliveTimeInNanos = builder.permitKeepAliveTimeInNanos; } } @@ -714,8 +746,11 @@ public void headers(boolean outFinished, authority == null ? null : asciiString(authority), statsTraceCtx, tracer); - if (maxConnectionIdleManager != null && streams.isEmpty()) { - maxConnectionIdleManager.onTransportActive(); + if (streams.isEmpty()) { + keepAliveEnforcer.onTransportActive(); + if (maxConnectionIdleManager != null) { + maxConnectionIdleManager.onTransportActive(); + } } streams.put(streamId, stream); listener.streamCreated(streamForApp, method, metadata); @@ -849,6 +884,11 @@ public void settings(boolean clearPrevious, Settings settings) { @Override public void ping(boolean ack, int payload1, int payload2) { + if (!keepAliveEnforcer.pingAcceptable()) { + abruptShutdown(ErrorCode.ENHANCE_YOUR_CALM, "too_many_pings", + Status.RESOURCE_EXHAUSTED.withDescription("Too many pings from client"), false); + return; + } long payload = (((long) payload1) << 32) | (payload2 & 0xffffffffL); if (!ack) { frameLogger.logPing(OkHttpFrameLogger.Direction.INBOUND, payload); @@ -973,8 +1013,11 @@ private void respondWithHttpError( synchronized (lock) { Http2ErrorStreamState stream = new Http2ErrorStreamState(streamId, lock, outboundFlow, config.flowControlWindow); - if (maxConnectionIdleManager != null && streams.isEmpty()) { - maxConnectionIdleManager.onTransportActive(); + if (streams.isEmpty()) { + keepAliveEnforcer.onTransportActive(); + if (maxConnectionIdleManager != null) { + maxConnectionIdleManager.onTransportActive(); + } } streams.put(streamId, stream); if (inFinished) { diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java index 2711978bb00..a52045011ae 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java @@ -41,6 +41,7 @@ import io.grpc.Status; import io.grpc.internal.FakeClock; import io.grpc.internal.GrpcUtil; +import io.grpc.internal.KeepAliveEnforcer; import io.grpc.internal.ServerStream; import io.grpc.internal.ServerStreamListener; import io.grpc.internal.ServerTransportListener; @@ -122,7 +123,9 @@ public class OkHttpServerTransportTest { } }) .flowControlWindow(INITIAL_WINDOW_SIZE) - .maxConnectionIdle(MAX_CONNECTION_IDLE, TimeUnit.NANOSECONDS); + .maxConnectionIdle(MAX_CONNECTION_IDLE, TimeUnit.NANOSECONDS) + .permitKeepAliveWithoutCalls(true) + .permitKeepAliveTime(0, TimeUnit.SECONDS); @Rule public final Timeout globalTimeout = Timeout.seconds(10); @@ -1054,6 +1057,101 @@ public void channelzStats() throws Exception { assertThat(stats.remote).isEqualTo(new InetSocketAddress("127.0.0.2", 5000)); } + @Test + public void keepAliveEnforcer_enforcesPings() throws Exception { + serverBuilder.permitKeepAliveTime(1, TimeUnit.HOURS) + .permitKeepAliveWithoutCalls(false); + initTransport(); + handshake(); + + for (int i = 0; i < KeepAliveEnforcer.MAX_PING_STRIKES; i++) { + pingPong(); + } + pingPongId++; + clientFrameWriter.ping(false, pingPongId, 0); + clientFrameWriter.flush(); + assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue(); + verify(clientFramesRead).goAway(0, ErrorCode.ENHANCE_YOUR_CALM, + ByteString.encodeString("too_many_pings", GrpcUtil.US_ASCII)); + } + + @Test + public void keepAliveEnforcer_sendingDataResetsCounters() throws Exception { + serverBuilder.permitKeepAliveTime(1, TimeUnit.HOURS) + .permitKeepAliveWithoutCalls(false); + initTransport(); + handshake(); + + clientFrameWriter.headers(1, Arrays.asList( + HTTP_SCHEME_HEADER, + METHOD_HEADER, + new Header(Header.TARGET_AUTHORITY, "example.com:80"), + new Header(Header.TARGET_PATH, "/com.example/SimpleService.doit"), + CONTENT_TYPE_HEADER, + TE_HEADER, + new Header("some-metadata", "this could be anything"))); + Buffer requestMessageFrame = createMessageFrame("Hello server"); + clientFrameWriter.data(false, 1, requestMessageFrame, (int) requestMessageFrame.size()); + pingPong(); + MockStreamListener streamListener = mockTransportListener.newStreams.pop(); + + streamListener.stream.request(1); + pingPong(); + assertThat(streamListener.messages.pop()).isEqualTo("Hello server"); + + streamListener.stream.writeHeaders(metadata("User-Data", "best data")); + streamListener.stream.writeMessage(new ByteArrayInputStream("Howdy client".getBytes(UTF_8))); + streamListener.stream.flush(); + assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue(); + + for (int i = 0; i < 10; i++) { + assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue(); + pingPong(); + streamListener.stream.writeMessage(new ByteArrayInputStream("Howdy client".getBytes(UTF_8))); + streamListener.stream.flush(); + } + } + + @Test + public void keepAliveEnforcer_initialIdle() throws Exception { + serverBuilder.permitKeepAliveTime(0, TimeUnit.SECONDS) + .permitKeepAliveWithoutCalls(false); + initTransport(); + handshake(); + + for (int i = 0; i < KeepAliveEnforcer.MAX_PING_STRIKES; i++) { + pingPong(); + } + pingPongId++; + clientFrameWriter.ping(false, pingPongId, 0); + clientFrameWriter.flush(); + assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue(); + verify(clientFramesRead).goAway(0, ErrorCode.ENHANCE_YOUR_CALM, + ByteString.encodeString("too_many_pings", GrpcUtil.US_ASCII)); + } + + @Test + public void keepAliveEnforcer_noticesActive() throws Exception { + serverBuilder.permitKeepAliveTime(0, TimeUnit.SECONDS) + .permitKeepAliveWithoutCalls(false); + initTransport(); + handshake(); + + clientFrameWriter.headers(1, Arrays.asList( + HTTP_SCHEME_HEADER, + METHOD_HEADER, + new Header(Header.TARGET_AUTHORITY, "example.com:80"), + new Header(Header.TARGET_PATH, "/com.example/SimpleService.doit"), + CONTENT_TYPE_HEADER, + TE_HEADER, + new Header("some-metadata", "this could be anything"))); + for (int i = 0; i < 10; i++) { + pingPong(); + } + verify(clientFramesRead, never()).goAway(anyInt(), eq(ErrorCode.ENHANCE_YOUR_CALM), + eq(ByteString.encodeString("too_many_pings", GrpcUtil.US_ASCII))); + } + private void initTransport() throws Exception { serverTransport = new OkHttpServerTransport( new OkHttpServerTransport.Config(serverBuilder, Arrays.asList()), From e1ad984db3ca735b1196e248214171a1e1506215 Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Fri, 16 Sep 2022 10:08:16 -0700 Subject: [PATCH 62/72] xds: refactor xds client to make it resource agnostic (#9444) Mainly refactor work to make type specific xds resources generic, e.g. 1. Define abstract class XdsResourceType to be extended by pluggable new resources. It mainly contains abstract method doParse() to parse unpacked proto messges and produce a ResourceUpdate. The common unpacking proto logic is in XdsResourceType default method parse() 2. Move the parsing/processing logics to specific XdsResourceType. Implementing: XdsListenerResource for LDS XdsRouteConfigureResource for RDS XdsClusterResource for CDS XdsEndpointResource for EDS 3. The XdsResourceTypes are singleton. To process for each XdsClient, context is passed in parameters, defined by XdsResourceType.Args. 4. Watchers will use generic APIs to subscribe to resource watchXdsResource(XdsResourceType, resourceName, watcher). Watcher and ResourceSubscribers becomes java generic class. --- .../java/io/grpc/xds/AbstractXdsClient.java | 163 +- .../java/io/grpc/xds/CdsLoadBalancer2.java | 13 +- .../java/io/grpc/xds/ClientXdsClient.java | 2474 ++--------------- .../grpc/xds/ClusterResolverLoadBalancer.java | 10 +- xds/src/main/java/io/grpc/xds/XdsClient.java | 386 +-- .../java/io/grpc/xds/XdsClusterResource.java | 686 +++++ .../java/io/grpc/xds/XdsEndpointResource.java | 259 ++ .../java/io/grpc/xds/XdsListenerResource.java | 633 +++++ .../java/io/grpc/xds/XdsNameResolver.java | 24 +- .../java/io/grpc/xds/XdsResourceType.java | 308 ++ .../grpc/xds/XdsRouteConfigureResource.java | 707 +++++ .../java/io/grpc/xds/XdsServerWrapper.java | 24 +- .../io/grpc/xds/CdsLoadBalancer2Test.java | 24 +- .../io/grpc/xds/ClientXdsClientDataTest.java | 299 +- .../io/grpc/xds/ClientXdsClientTestBase.java | 503 ++-- .../io/grpc/xds/ClientXdsClientV2Test.java | 2 +- .../io/grpc/xds/ClientXdsClientV3Test.java | 2 +- .../xds/ClusterResolverLoadBalancerTest.java | 23 +- .../XdsClientWrapperForServerSdsTestMisc.java | 2 +- .../java/io/grpc/xds/XdsNameResolverTest.java | 74 +- .../io/grpc/xds/XdsSdsClientServerTest.java | 2 +- .../java/io/grpc/xds/XdsServerTestHelper.java | 59 +- .../io/grpc/xds/XdsServerWrapperTest.java | 14 +- 23 files changed, 3455 insertions(+), 3236 deletions(-) create mode 100644 xds/src/main/java/io/grpc/xds/XdsClusterResource.java create mode 100644 xds/src/main/java/io/grpc/xds/XdsEndpointResource.java create mode 100644 xds/src/main/java/io/grpc/xds/XdsListenerResource.java create mode 100644 xds/src/main/java/io/grpc/xds/XdsResourceType.java create mode 100644 xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java diff --git a/xds/src/main/java/io/grpc/xds/AbstractXdsClient.java b/xds/src/main/java/io/grpc/xds/AbstractXdsClient.java index d559c054402..9fa25ba2cf8 100644 --- a/xds/src/main/java/io/grpc/xds/AbstractXdsClient.java +++ b/xds/src/main/java/io/grpc/xds/AbstractXdsClient.java @@ -19,6 +19,14 @@ 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 io.grpc.xds.XdsClusterResource.ADS_TYPE_URL_CDS; +import static io.grpc.xds.XdsClusterResource.ADS_TYPE_URL_CDS_V2; +import static io.grpc.xds.XdsEndpointResource.ADS_TYPE_URL_EDS; +import static io.grpc.xds.XdsEndpointResource.ADS_TYPE_URL_EDS_V2; +import static io.grpc.xds.XdsListenerResource.ADS_TYPE_URL_LDS; +import static io.grpc.xds.XdsListenerResource.ADS_TYPE_URL_LDS_V2; +import static io.grpc.xds.XdsRouteConfigureResource.ADS_TYPE_URL_RDS; +import static io.grpc.xds.XdsRouteConfigureResource.ADS_TYPE_URL_RDS_V2; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Stopwatch; @@ -41,11 +49,14 @@ import io.grpc.xds.ClientXdsClient.XdsChannelFactory; import io.grpc.xds.EnvoyProtoData.Node; import io.grpc.xds.XdsClient.ResourceStore; +import io.grpc.xds.XdsClient.ResourceUpdate; import io.grpc.xds.XdsClient.XdsResponseHandler; import io.grpc.xds.XdsLogger.XdsLogLevel; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; @@ -55,23 +66,6 @@ * the xDS RPC stream. */ final class AbstractXdsClient { - - private static final String ADS_TYPE_URL_LDS_V2 = "type.googleapis.com/envoy.api.v2.Listener"; - private static final String ADS_TYPE_URL_LDS = - "type.googleapis.com/envoy.config.listener.v3.Listener"; - private static final String ADS_TYPE_URL_RDS_V2 = - "type.googleapis.com/envoy.api.v2.RouteConfiguration"; - private static final String ADS_TYPE_URL_RDS = - "type.googleapis.com/envoy.config.route.v3.RouteConfiguration"; - @VisibleForTesting - static final String ADS_TYPE_URL_CDS_V2 = "type.googleapis.com/envoy.api.v2.Cluster"; - private static final String ADS_TYPE_URL_CDS = - "type.googleapis.com/envoy.config.cluster.v3.Cluster"; - private static final String ADS_TYPE_URL_EDS_V2 = - "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment"; - private static final String ADS_TYPE_URL_EDS = - "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment"; - private final SynchronizationContext syncContext; private final InternalLogId logId; private final XdsLogger logger; @@ -88,10 +82,7 @@ final class AbstractXdsClient { // Last successfully applied version_info for each resource type. Starts with empty string. // A version_info is used to update management server with client's most recent knowledge of // resources. - private String ldsVersion = ""; - private String rdsVersion = ""; - private String cdsVersion = ""; - private String edsVersion = ""; + private final Map versions = new HashMap<>(); private boolean shutdown; @Nullable @@ -162,16 +153,17 @@ public String toString() { * Updates the resource subscription for the given resource type. */ // Must be synchronized. - void adjustResourceSubscription(ResourceType type) { + void adjustResourceSubscription(XdsResourceType resourceType) { if (isInBackoff()) { return; } if (adsStream == null) { startRpcStream(); } - Collection resources = resourceStore.getSubscribedResources(serverInfo, type); + Collection resources = resourceStore.getSubscribedResources(serverInfo, + resourceType.typeName()); if (resources != null) { - adsStream.sendDiscoveryRequest(type, resources); + adsStream.sendDiscoveryRequest(resourceType, resources); } } @@ -180,24 +172,9 @@ void adjustResourceSubscription(ResourceType type) { * and sends an ACK request to the management server. */ // Must be synchronized. - void ackResponse(ResourceType type, String versionInfo, String nonce) { - switch (type) { - case LDS: - ldsVersion = versionInfo; - break; - case RDS: - rdsVersion = versionInfo; - break; - case CDS: - cdsVersion = versionInfo; - break; - case EDS: - edsVersion = versionInfo; - break; - case UNKNOWN: - default: - throw new AssertionError("Unknown resource type: " + type); - } + void ackResponse(XdsResourceType xdsResourceType, String versionInfo, String nonce) { + ResourceType type = xdsResourceType.typeName(); + versions.put(type, versionInfo); logger.log(XdsLogLevel.INFO, "Sending ACK for {0} update, nonce: {1}, current version: {2}", type, nonce, versionInfo); Collection resources = resourceStore.getSubscribedResources(serverInfo, type); @@ -212,8 +189,9 @@ void ackResponse(ResourceType type, String versionInfo, String nonce) { * accepted version) to the management server. */ // Must be synchronized. - void nackResponse(ResourceType type, String nonce, String errorDetail) { - String versionInfo = getCurrentVersion(type); + void nackResponse(XdsResourceType xdsResourceType, String nonce, String errorDetail) { + ResourceType type = xdsResourceType.typeName(); + String versionInfo = versions.getOrDefault(type, ""); logger.log(XdsLogLevel.INFO, "Sending NACK for {0} update, nonce: {1}, current version: {2}", type, nonce, versionInfo); Collection resources = resourceStore.getSubscribedResources(serverInfo, type); @@ -253,30 +231,6 @@ private void startRpcStream() { stopwatch.reset().start(); } - /** Returns the latest accepted version of the given resource type. */ - // Must be synchronized. - String getCurrentVersion(ResourceType type) { - String version; - switch (type) { - case LDS: - version = ldsVersion; - break; - case RDS: - version = rdsVersion; - break; - case CDS: - version = cdsVersion; - break; - case EDS: - version = edsVersion; - break; - case UNKNOWN: - default: - throw new AssertionError("Unknown resource type: " + type); - } - return version; - } - @VisibleForTesting final class RpcRetryTask implements Runnable { @Override @@ -291,13 +245,14 @@ public void run() { } Collection resources = resourceStore.getSubscribedResources(serverInfo, type); if (resources != null) { - adsStream.sendDiscoveryRequest(type, resources); + adsStream.sendDiscoveryRequest(resourceStore.getXdsResourceType(type), resources); } } xdsResponseHandler.handleStreamRestarted(serverInfo); } } + // TODO(zivy) : remove and replace with XdsResourceType enum ResourceType { UNKNOWN, LDS, RDS, CDS, EDS; @@ -361,17 +316,13 @@ static ResourceType fromTypeUrl(String typeUrl) { private abstract class AbstractAdsStream { private boolean responseReceived; private boolean closed; - // Response nonce for the most recently received discovery responses of each resource type. // Client initiated requests start response nonce with empty string. - // A nonce is used to indicate the specific DiscoveryResponse each DiscoveryRequest - // corresponds to. - // A nonce becomes stale following a newer nonce being presented to the client in a - // DiscoveryResponse. - private String ldsRespNonce = ""; - private String rdsRespNonce = ""; - private String cdsRespNonce = ""; - private String edsRespNonce = ""; + // Nonce in each response is echoed back in the following ACK/NACK request. It is + // used for management server to identify which response the client is ACKing/NACking. + // To avoid confusion, client-initiated requests will always use the nonce in + // most recently received responses of each resource type. + private final Map respNonces = new HashMap<>(); abstract void start(); @@ -381,35 +332,20 @@ private abstract class AbstractAdsStream { * Sends a discovery request with the given {@code versionInfo}, {@code nonce} and * {@code errorDetail}. Used for reacting to a specific discovery response. For * client-initiated discovery requests, use {@link - * #sendDiscoveryRequest(ResourceType, Collection)}. + * #sendDiscoveryRequest(XdsResourceType, Collection)}. */ - abstract void sendDiscoveryRequest(ResourceType type, String versionInfo, + abstract void sendDiscoveryRequest(ResourceType type, String version, Collection resources, String nonce, @Nullable String errorDetail); /** * Sends a client-initiated discovery request. */ - final void sendDiscoveryRequest(ResourceType type, Collection resources) { - String nonce; - switch (type) { - case LDS: - nonce = ldsRespNonce; - break; - case RDS: - nonce = rdsRespNonce; - break; - case CDS: - nonce = cdsRespNonce; - break; - case EDS: - nonce = edsRespNonce; - break; - case UNKNOWN: - default: - throw new AssertionError("Unknown resource type: " + type); - } + final void sendDiscoveryRequest(XdsResourceType xdsResourceType, + Collection resources) { + ResourceType type = xdsResourceType.typeName(); logger.log(XdsLogLevel.INFO, "Sending {0} request for resources: {1}", type, resources); - sendDiscoveryRequest(type, getCurrentVersion(type), resources, nonce, null); + sendDiscoveryRequest(type, versions.getOrDefault(type, ""), resources, + respNonces.getOrDefault(type, ""), null); } final void handleRpcResponse( @@ -418,31 +354,8 @@ final void handleRpcResponse( return; } responseReceived = true; - // Nonce in each response is echoed back in the following ACK/NACK request. It is - // used for management server to identify which response the client is ACKing/NACking. - // To avoid confusion, client-initiated requests will always use the nonce in - // most recently received responses of each resource type. - switch (type) { - case LDS: - ldsRespNonce = nonce; - xdsResponseHandler.handleLdsResponse(serverInfo, versionInfo, resources, nonce); - break; - case RDS: - rdsRespNonce = nonce; - xdsResponseHandler.handleRdsResponse(serverInfo, versionInfo, resources, nonce); - break; - case CDS: - cdsRespNonce = nonce; - xdsResponseHandler.handleCdsResponse(serverInfo, versionInfo, resources, nonce); - break; - case EDS: - edsRespNonce = nonce; - xdsResponseHandler.handleEdsResponse(serverInfo, versionInfo, resources, nonce); - break; - case UNKNOWN: - default: - logger.log(XdsLogLevel.WARNING, "Ignore an unknown type of DiscoveryResponse"); - } + respNonces.put(type, nonce); + xdsResponseHandler.handleResourceResponse(type, serverInfo, versionInfo, resources, nonce); } final void handleRpcError(Throwable t) { diff --git a/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java b/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java index 4fa701c3c42..afbb21008ef 100644 --- a/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java +++ b/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java @@ -35,9 +35,9 @@ import io.grpc.xds.CdsLoadBalancerProvider.CdsConfig; import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig; import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig.DiscoveryMechanism; -import io.grpc.xds.XdsClient.CdsResourceWatcher; -import io.grpc.xds.XdsClient.CdsUpdate; -import io.grpc.xds.XdsClient.CdsUpdate.ClusterType; +import io.grpc.xds.XdsClient.ResourceWatcher; +import io.grpc.xds.XdsClusterResource.CdsUpdate; +import io.grpc.xds.XdsClusterResource.CdsUpdate.ClusterType; import io.grpc.xds.XdsLogger.XdsLogLevel; import io.grpc.xds.XdsSubchannelPickers.ErrorPicker; import java.util.ArrayDeque; @@ -221,7 +221,7 @@ private void handleClusterDiscoveryError(Status error) { } } - private final class ClusterState implements CdsResourceWatcher { + private final class ClusterState implements ResourceWatcher { private final String name; @Nullable private Map childClusterStates; @@ -237,12 +237,12 @@ private ClusterState(String name) { } private void start() { - xdsClient.watchCdsResource(name, this); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(), name, this); } void shutdown() { shutdown = true; - xdsClient.cancelCdsResourceWatch(name, this); + xdsClient.cancelXdsResourceWatch(XdsClusterResource.getInstance(), name, this); if (childClusterStates != null) { // recursively shut down all descendants for (ClusterState state : childClusterStates.values()) { state.shutdown(); @@ -300,6 +300,7 @@ public void run() { if (shutdown) { return; } + logger.log(XdsLogLevel.DEBUG, "Received cluster update {0}", update); discovered = true; result = update; diff --git a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java index 55593eb99a9..80e70d6d1c6 100644 --- a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java +++ b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java @@ -18,110 +18,50 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static io.grpc.xds.AbstractXdsClient.ResourceType.CDS; +import static io.grpc.xds.AbstractXdsClient.ResourceType.EDS; +import static io.grpc.xds.AbstractXdsClient.ResourceType.LDS; +import static io.grpc.xds.AbstractXdsClient.ResourceType.RDS; import static io.grpc.xds.Bootstrapper.XDSTP_SCHEME; +import static io.grpc.xds.XdsResourceType.ParsedResource; +import static io.grpc.xds.XdsResourceType.ValidatedResourceUpdate; -import com.github.udpa.udpa.type.v1.TypedStruct; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; -import com.google.common.base.Splitter; import com.google.common.base.Stopwatch; -import com.google.common.base.Strings; import com.google.common.base.Supplier; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import com.google.protobuf.Any; -import com.google.protobuf.Duration; -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.protobuf.Message; -import com.google.protobuf.util.Durations; -import com.google.re2j.Pattern; -import com.google.re2j.PatternSyntaxException; -import io.envoyproxy.envoy.config.cluster.v3.CircuitBreakers.Thresholds; -import io.envoyproxy.envoy.config.cluster.v3.Cluster; -import io.envoyproxy.envoy.config.cluster.v3.Cluster.CustomClusterType; -import io.envoyproxy.envoy.config.cluster.v3.Cluster.DiscoveryType; -import io.envoyproxy.envoy.config.core.v3.HttpProtocolOptions; -import io.envoyproxy.envoy.config.core.v3.RoutingPriority; -import io.envoyproxy.envoy.config.core.v3.SocketAddress; -import io.envoyproxy.envoy.config.core.v3.SocketAddress.PortSpecifierCase; -import io.envoyproxy.envoy.config.core.v3.TrafficDirection; -import io.envoyproxy.envoy.config.core.v3.TypedExtensionConfig; -import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment; -import io.envoyproxy.envoy.config.listener.v3.Listener; -import io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin; -import io.envoyproxy.envoy.config.route.v3.RetryPolicy.RetryBackOff; -import io.envoyproxy.envoy.config.route.v3.RouteConfiguration; -import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager; -import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.Rds; -import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; -import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; -import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext; -import io.envoyproxy.envoy.service.discovery.v3.Resource; -import io.envoyproxy.envoy.type.v3.FractionalPercent; -import io.envoyproxy.envoy.type.v3.FractionalPercent.DenominatorType; import io.grpc.ChannelCredentials; import io.grpc.Context; -import io.grpc.EquivalentAddressGroup; import io.grpc.Grpc; import io.grpc.InternalLogId; import io.grpc.LoadBalancerRegistry; import io.grpc.ManagedChannel; -import io.grpc.NameResolver; import io.grpc.Status; -import io.grpc.Status.Code; import io.grpc.SynchronizationContext; import io.grpc.SynchronizationContext.ScheduledHandle; import io.grpc.internal.BackoffPolicy; -import io.grpc.internal.ServiceConfigUtil; -import io.grpc.internal.ServiceConfigUtil.LbConfig; import io.grpc.internal.TimeProvider; import io.grpc.xds.AbstractXdsClient.ResourceType; import io.grpc.xds.Bootstrapper.AuthorityInfo; import io.grpc.xds.Bootstrapper.ServerInfo; -import io.grpc.xds.ClusterSpecifierPlugin.NamedPluginConfig; -import io.grpc.xds.ClusterSpecifierPlugin.PluginConfig; -import io.grpc.xds.Endpoints.DropOverload; -import io.grpc.xds.Endpoints.LbEndpoint; -import io.grpc.xds.Endpoints.LocalityLbEndpoints; -import io.grpc.xds.EnvoyServerProtoData.CidrRange; -import io.grpc.xds.EnvoyServerProtoData.ConnectionSourceType; -import io.grpc.xds.EnvoyServerProtoData.FilterChain; -import io.grpc.xds.EnvoyServerProtoData.FilterChainMatch; -import io.grpc.xds.EnvoyServerProtoData.OutlierDetection; -import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; -import io.grpc.xds.Filter.ClientInterceptorBuilder; -import io.grpc.xds.Filter.FilterConfig; -import io.grpc.xds.Filter.NamedFilterConfig; -import io.grpc.xds.Filter.ServerInterceptorBuilder; import io.grpc.xds.LoadStatsManager2.ClusterDropStats; import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats; -import io.grpc.xds.VirtualHost.Route; -import io.grpc.xds.VirtualHost.Route.RouteAction; -import io.grpc.xds.VirtualHost.Route.RouteAction.ClusterWeight; -import io.grpc.xds.VirtualHost.Route.RouteAction.HashPolicy; -import io.grpc.xds.VirtualHost.Route.RouteAction.RetryPolicy; -import io.grpc.xds.VirtualHost.Route.RouteMatch; -import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher; import io.grpc.xds.XdsClient.ResourceStore; import io.grpc.xds.XdsClient.XdsResponseHandler; +import io.grpc.xds.XdsClusterResource.CdsUpdate; +import io.grpc.xds.XdsListenerResource.LdsUpdate; import io.grpc.xds.XdsLogger.XdsLogLevel; -import io.grpc.xds.internal.Matchers.FractionMatcher; -import io.grpc.xds.internal.Matchers.HeaderMatcher; -import java.net.InetSocketAddress; import java.net.URI; -import java.net.UnknownHostException; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -137,69 +77,6 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res // Longest time to wait, since the subscription to some resource, for concluding its absence. @VisibleForTesting static final int INITIAL_RESOURCE_FETCH_TIMEOUT_SEC = 15; - private static final String TRANSPORT_SOCKET_NAME_TLS = "envoy.transport_sockets.tls"; - @VisibleForTesting - static final String AGGREGATE_CLUSTER_TYPE_NAME = "envoy.clusters.aggregate"; - @VisibleForTesting - static final String HASH_POLICY_FILTER_STATE_KEY = "io.grpc.channel_id"; - @VisibleForTesting - static boolean enableFaultInjection = - Strings.isNullOrEmpty(System.getenv("GRPC_XDS_EXPERIMENTAL_FAULT_INJECTION")) - || Boolean.parseBoolean(System.getenv("GRPC_XDS_EXPERIMENTAL_FAULT_INJECTION")); - @VisibleForTesting - static boolean enableRetry = - Strings.isNullOrEmpty(System.getenv("GRPC_XDS_EXPERIMENTAL_ENABLE_RETRY")) - || Boolean.parseBoolean(System.getenv("GRPC_XDS_EXPERIMENTAL_ENABLE_RETRY")); - @VisibleForTesting - static boolean enableRbac = - Strings.isNullOrEmpty(System.getenv("GRPC_XDS_EXPERIMENTAL_RBAC")) - || Boolean.parseBoolean(System.getenv("GRPC_XDS_EXPERIMENTAL_RBAC")); - @VisibleForTesting - static boolean enableRouteLookup = - !Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_XDS_RLS_LB")) - && Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_XDS_RLS_LB")); - @VisibleForTesting - static boolean enableLeastRequest = - !Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_ENABLE_LEAST_REQUEST")) - ? Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_ENABLE_LEAST_REQUEST")) - : Boolean.parseBoolean(System.getProperty("io.grpc.xds.experimentalEnableLeastRequest")); - @VisibleForTesting - static boolean enableCustomLbConfig = - Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_XDS_CUSTOM_LB_CONFIG")) - || Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_XDS_CUSTOM_LB_CONFIG")); - @VisibleForTesting - static boolean enableOutlierDetection = - Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION")) - || Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION")); - private static final String TYPE_URL_HTTP_CONNECTION_MANAGER_V2 = - "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2" - + ".HttpConnectionManager"; - static final String TYPE_URL_HTTP_CONNECTION_MANAGER = - "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3" - + ".HttpConnectionManager"; - private static final String TYPE_URL_UPSTREAM_TLS_CONTEXT = - "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext"; - private static final String TYPE_URL_UPSTREAM_TLS_CONTEXT_V2 = - "type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext"; - private static final String TYPE_URL_CLUSTER_CONFIG_V2 = - "type.googleapis.com/envoy.config.cluster.aggregate.v2alpha.ClusterConfig"; - private static final String TYPE_URL_CLUSTER_CONFIG = - "type.googleapis.com/envoy.extensions.clusters.aggregate.v3.ClusterConfig"; - private static final String TYPE_URL_TYPED_STRUCT_UDPA = - "type.googleapis.com/udpa.type.v1.TypedStruct"; - private static final String TYPE_URL_TYPED_STRUCT = - "type.googleapis.com/xds.type.v3.TypedStruct"; - private static final String TYPE_URL_FILTER_CONFIG = - "type.googleapis.com/envoy.config.route.v3.FilterConfig"; - private static final String TYPE_URL_RESOURCE_V2 = "type.googleapis.com/envoy.api.v2.Resource"; - private static final String TYPE_URL_RESOURCE_V3 = - "type.googleapis.com/envoy.service.discovery.v3.Resource"; - // TODO(zdapeng): need to discuss how to handle unsupported values. - private static final Set SUPPORTED_RETRYABLE_CODES = - Collections.unmodifiableSet(EnumSet.of( - Code.CANCELLED, Code.DEADLINE_EXCEEDED, Code.INTERNAL, Code.RESOURCE_EXHAUSTED, - Code.UNAVAILABLE)); - private final SynchronizationContext syncContext = new SynchronizationContext( new Thread.UncaughtExceptionHandler() { @Override @@ -207,1909 +84,126 @@ public void uncaughtException(Thread t, Throwable e) { logger.log( XdsLogLevel.ERROR, "Uncaught exception in XdsClient SynchronizationContext. Panic!", - e); - // TODO(chengyuanzhang): better error handling. - throw new AssertionError(e); - } - }); - private final FilterRegistry filterRegistry = FilterRegistry.getDefaultRegistry(); - private final LoadBalancerRegistry loadBalancerRegistry - = LoadBalancerRegistry.getDefaultRegistry(); - private final Map serverChannelMap = new HashMap<>(); - private final Map ldsResourceSubscribers = new HashMap<>(); - private final Map rdsResourceSubscribers = new HashMap<>(); - private final Map cdsResourceSubscribers = new HashMap<>(); - private final Map edsResourceSubscribers = new HashMap<>(); - private final LoadStatsManager2 loadStatsManager; - private final Map serverLrsClientMap = new HashMap<>(); - private final XdsChannelFactory xdsChannelFactory; - private final Bootstrapper.BootstrapInfo bootstrapInfo; - private final Context context; - private final ScheduledExecutorService timeService; - private final BackoffPolicy.Provider backoffPolicyProvider; - private final Supplier stopwatchSupplier; - private final TimeProvider timeProvider; - private boolean reportingLoad; - private final TlsContextManager tlsContextManager; - private final InternalLogId logId; - private final XdsLogger logger; - private volatile boolean isShutdown; - - // TODO(zdapeng): rename to XdsClientImpl - ClientXdsClient( - XdsChannelFactory xdsChannelFactory, - Bootstrapper.BootstrapInfo bootstrapInfo, - Context context, - ScheduledExecutorService timeService, - BackoffPolicy.Provider backoffPolicyProvider, - Supplier stopwatchSupplier, - TimeProvider timeProvider, - TlsContextManager tlsContextManager) { - this.xdsChannelFactory = xdsChannelFactory; - this.bootstrapInfo = bootstrapInfo; - this.context = context; - this.timeService = timeService; - loadStatsManager = new LoadStatsManager2(stopwatchSupplier); - this.backoffPolicyProvider = backoffPolicyProvider; - this.stopwatchSupplier = stopwatchSupplier; - this.timeProvider = timeProvider; - this.tlsContextManager = checkNotNull(tlsContextManager, "tlsContextManager"); - logId = InternalLogId.allocate("xds-client", null); - logger = XdsLogger.withLogId(logId); - logger.log(XdsLogLevel.INFO, "Created"); - } - - private void maybeCreateXdsChannelWithLrs(ServerInfo serverInfo) { - syncContext.throwIfNotInThisSynchronizationContext(); - if (serverChannelMap.containsKey(serverInfo)) { - return; - } - AbstractXdsClient xdsChannel = new AbstractXdsClient( - xdsChannelFactory, - serverInfo, - bootstrapInfo.node(), - this, - this, - context, - timeService, - syncContext, - backoffPolicyProvider, - stopwatchSupplier); - LoadReportClient lrsClient = new LoadReportClient( - loadStatsManager, xdsChannel.channel(), context, serverInfo.useProtocolV3(), - bootstrapInfo.node(), syncContext, timeService, backoffPolicyProvider, stopwatchSupplier); - serverChannelMap.put(serverInfo, xdsChannel); - serverLrsClientMap.put(serverInfo, lrsClient); - } - - private Any maybeUnwrapResources(Any resource) - throws InvalidProtocolBufferException { - if (resource.getTypeUrl().equals(TYPE_URL_RESOURCE_V2) - || resource.getTypeUrl().equals(TYPE_URL_RESOURCE_V3)) { - return unpackCompatibleType(resource, Resource.class, TYPE_URL_RESOURCE_V3, - TYPE_URL_RESOURCE_V2).getResource(); - } else { - return resource; - } - } - - @Override - public void handleLdsResponse( - ServerInfo serverInfo, String versionInfo, List resources, String nonce) { - syncContext.throwIfNotInThisSynchronizationContext(); - Map parsedResources = new HashMap<>(resources.size()); - Set unpackedResources = new HashSet<>(resources.size()); - Set invalidResources = new HashSet<>(); - List errors = new ArrayList<>(); - Set retainedRdsResources = new HashSet<>(); - - for (int i = 0; i < resources.size(); i++) { - Any resource = resources.get(i); - - boolean isResourceV3; - Listener listener; - try { - resource = maybeUnwrapResources(resource); - // Unpack the Listener. - isResourceV3 = resource.getTypeUrl().equals(ResourceType.LDS.typeUrl()); - listener = unpackCompatibleType(resource, Listener.class, ResourceType.LDS.typeUrl(), - ResourceType.LDS.typeUrlV2()); - } catch (InvalidProtocolBufferException e) { - errors.add("LDS response Resource index " + i + " - can't decode Listener: " + e); - continue; - } - if (!isResourceNameValid(listener.getName(), resource.getTypeUrl())) { - errors.add( - "Unsupported resource name: " + listener.getName() + " for type: " + ResourceType.LDS); - continue; - } - String listenerName = canonifyResourceName(listener.getName()); - unpackedResources.add(listenerName); - - // Process Listener into LdsUpdate. - LdsUpdate ldsUpdate; - try { - if (listener.hasApiListener()) { - ldsUpdate = processClientSideListener( - listener, retainedRdsResources, enableFaultInjection && isResourceV3); - } else { - ldsUpdate = processServerSideListener( - listener, retainedRdsResources, enableRbac && isResourceV3); - } - } catch (ResourceInvalidException e) { - errors.add( - "LDS response Listener '" + listenerName + "' validation error: " + e.getMessage()); - invalidResources.add(listenerName); - continue; - } - - // LdsUpdate parsed successfully. - parsedResources.put(listenerName, new ParsedResource(ldsUpdate, resource)); - } - logger.log(XdsLogLevel.INFO, - "Received LDS Response version {0} nonce {1}. Parsed resources: {2}", - versionInfo, nonce, unpackedResources); - handleResourceUpdate( - serverInfo, ResourceType.LDS, parsedResources, invalidResources, retainedRdsResources, - versionInfo, nonce, errors); - } - - private LdsUpdate processClientSideListener( - Listener listener, Set rdsResources, boolean parseHttpFilter) - throws ResourceInvalidException { - // Unpack HttpConnectionManager from the Listener. - HttpConnectionManager hcm; - try { - hcm = unpackCompatibleType( - listener.getApiListener().getApiListener(), HttpConnectionManager.class, - TYPE_URL_HTTP_CONNECTION_MANAGER, TYPE_URL_HTTP_CONNECTION_MANAGER_V2); - } catch (InvalidProtocolBufferException e) { - throw new ResourceInvalidException( - "Could not parse HttpConnectionManager config from ApiListener", e); - } - return LdsUpdate.forApiListener(parseHttpConnectionManager( - hcm, rdsResources, filterRegistry, parseHttpFilter, true /* isForClient */)); - } - - private LdsUpdate processServerSideListener( - Listener proto, Set rdsResources, boolean parseHttpFilter) - throws ResourceInvalidException { - Set certProviderInstances = null; - if (getBootstrapInfo() != null && getBootstrapInfo().certProviders() != null) { - certProviderInstances = getBootstrapInfo().certProviders().keySet(); - } - return LdsUpdate.forTcpListener(parseServerSideListener( - proto, rdsResources, tlsContextManager, filterRegistry, certProviderInstances, - parseHttpFilter)); - } - - @VisibleForTesting - static EnvoyServerProtoData.Listener parseServerSideListener( - Listener proto, Set rdsResources, TlsContextManager tlsContextManager, - FilterRegistry filterRegistry, Set certProviderInstances, boolean parseHttpFilter) - throws ResourceInvalidException { - if (!proto.getTrafficDirection().equals(TrafficDirection.INBOUND) - && !proto.getTrafficDirection().equals(TrafficDirection.UNSPECIFIED)) { - throw new ResourceInvalidException( - "Listener " + proto.getName() + " with invalid traffic direction: " - + proto.getTrafficDirection()); - } - if (!proto.getListenerFiltersList().isEmpty()) { - throw new ResourceInvalidException( - "Listener " + proto.getName() + " cannot have listener_filters"); - } - if (proto.hasUseOriginalDst()) { - throw new ResourceInvalidException( - "Listener " + proto.getName() + " cannot have use_original_dst set to true"); - } - - String address = null; - if (proto.getAddress().hasSocketAddress()) { - SocketAddress socketAddress = proto.getAddress().getSocketAddress(); - address = socketAddress.getAddress(); - switch (socketAddress.getPortSpecifierCase()) { - case NAMED_PORT: - address = address + ":" + socketAddress.getNamedPort(); - break; - case PORT_VALUE: - address = address + ":" + socketAddress.getPortValue(); - break; - default: - // noop - } - } - - ImmutableList.Builder filterChains = ImmutableList.builder(); - Set uniqueSet = new HashSet<>(); - for (io.envoyproxy.envoy.config.listener.v3.FilterChain fc : proto.getFilterChainsList()) { - filterChains.add( - parseFilterChain(fc, rdsResources, tlsContextManager, filterRegistry, uniqueSet, - certProviderInstances, parseHttpFilter)); - } - FilterChain defaultFilterChain = null; - if (proto.hasDefaultFilterChain()) { - defaultFilterChain = parseFilterChain( - proto.getDefaultFilterChain(), rdsResources, tlsContextManager, filterRegistry, - null, certProviderInstances, parseHttpFilter); - } - - return EnvoyServerProtoData.Listener.create( - proto.getName(), address, filterChains.build(), defaultFilterChain); - } - - @VisibleForTesting - static FilterChain parseFilterChain( - io.envoyproxy.envoy.config.listener.v3.FilterChain proto, Set rdsResources, - TlsContextManager tlsContextManager, FilterRegistry filterRegistry, - Set uniqueSet, Set certProviderInstances, boolean parseHttpFilters) - throws ResourceInvalidException { - if (proto.getFiltersCount() != 1) { - throw new ResourceInvalidException("FilterChain " + proto.getName() - + " should contain exact one HttpConnectionManager filter"); - } - io.envoyproxy.envoy.config.listener.v3.Filter filter = proto.getFiltersList().get(0); - if (!filter.hasTypedConfig()) { - throw new ResourceInvalidException( - "FilterChain " + proto.getName() + " contains filter " + filter.getName() - + " without typed_config"); - } - Any any = filter.getTypedConfig(); - // HttpConnectionManager is the only supported network filter at the moment. - if (!any.getTypeUrl().equals(TYPE_URL_HTTP_CONNECTION_MANAGER)) { - throw new ResourceInvalidException( - "FilterChain " + proto.getName() + " contains filter " + filter.getName() - + " with unsupported typed_config type " + any.getTypeUrl()); - } - HttpConnectionManager hcmProto; - try { - hcmProto = any.unpack(HttpConnectionManager.class); - } catch (InvalidProtocolBufferException e) { - throw new ResourceInvalidException("FilterChain " + proto.getName() + " with filter " - + filter.getName() + " failed to unpack message", e); - } - io.grpc.xds.HttpConnectionManager httpConnectionManager = parseHttpConnectionManager( - hcmProto, rdsResources, filterRegistry, parseHttpFilters, false /* isForClient */); - - EnvoyServerProtoData.DownstreamTlsContext downstreamTlsContext = null; - if (proto.hasTransportSocket()) { - if (!TRANSPORT_SOCKET_NAME_TLS.equals(proto.getTransportSocket().getName())) { - throw new ResourceInvalidException("transport-socket with name " - + proto.getTransportSocket().getName() + " not supported."); - } - DownstreamTlsContext downstreamTlsContextProto; - try { - downstreamTlsContextProto = - proto.getTransportSocket().getTypedConfig().unpack(DownstreamTlsContext.class); - } catch (InvalidProtocolBufferException e) { - throw new ResourceInvalidException("FilterChain " + proto.getName() - + " failed to unpack message", e); - } - downstreamTlsContext = - EnvoyServerProtoData.DownstreamTlsContext.fromEnvoyProtoDownstreamTlsContext( - validateDownstreamTlsContext(downstreamTlsContextProto, certProviderInstances)); - } - - FilterChainMatch filterChainMatch = parseFilterChainMatch(proto.getFilterChainMatch()); - checkForUniqueness(uniqueSet, filterChainMatch); - return FilterChain.create( - proto.getName(), - filterChainMatch, - httpConnectionManager, - downstreamTlsContext, - tlsContextManager - ); - } - - @VisibleForTesting - static DownstreamTlsContext validateDownstreamTlsContext( - DownstreamTlsContext downstreamTlsContext, Set certProviderInstances) - throws ResourceInvalidException { - if (downstreamTlsContext.hasCommonTlsContext()) { - validateCommonTlsContext(downstreamTlsContext.getCommonTlsContext(), certProviderInstances, - true); - } else { - throw new ResourceInvalidException( - "common-tls-context is required in downstream-tls-context"); - } - if (downstreamTlsContext.hasRequireSni()) { - throw new ResourceInvalidException( - "downstream-tls-context with require-sni is not supported"); - } - DownstreamTlsContext.OcspStaplePolicy ocspStaplePolicy = downstreamTlsContext - .getOcspStaplePolicy(); - if (ocspStaplePolicy != DownstreamTlsContext.OcspStaplePolicy.UNRECOGNIZED - && ocspStaplePolicy != DownstreamTlsContext.OcspStaplePolicy.LENIENT_STAPLING) { - throw new ResourceInvalidException( - "downstream-tls-context with ocsp_staple_policy value " + ocspStaplePolicy.name() - + " is not supported"); - } - return downstreamTlsContext; - } - - @VisibleForTesting - static io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext - validateUpstreamTlsContext( - io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext upstreamTlsContext, - Set certProviderInstances) - throws ResourceInvalidException { - if (upstreamTlsContext.hasCommonTlsContext()) { - validateCommonTlsContext(upstreamTlsContext.getCommonTlsContext(), certProviderInstances, - false); - } else { - throw new ResourceInvalidException("common-tls-context is required in upstream-tls-context"); - } - return upstreamTlsContext; - } - - @VisibleForTesting - static void validateCommonTlsContext( - CommonTlsContext commonTlsContext, Set certProviderInstances, boolean server) - throws ResourceInvalidException { - if (commonTlsContext.hasCustomHandshaker()) { - throw new ResourceInvalidException( - "common-tls-context with custom_handshaker is not supported"); - } - if (commonTlsContext.hasTlsParams()) { - throw new ResourceInvalidException("common-tls-context with tls_params is not supported"); - } - if (commonTlsContext.hasValidationContextSdsSecretConfig()) { - throw new ResourceInvalidException( - "common-tls-context with validation_context_sds_secret_config is not supported"); - } - if (commonTlsContext.hasValidationContextCertificateProvider()) { - throw new ResourceInvalidException( - "common-tls-context with validation_context_certificate_provider is not supported"); - } - if (commonTlsContext.hasValidationContextCertificateProviderInstance()) { - throw new ResourceInvalidException( - "common-tls-context with validation_context_certificate_provider_instance is not" - + " supported"); - } - String certInstanceName = getIdentityCertInstanceName(commonTlsContext); - if (certInstanceName == null) { - if (server) { - throw new ResourceInvalidException( - "tls_certificate_provider_instance is required in downstream-tls-context"); - } - if (commonTlsContext.getTlsCertificatesCount() > 0) { - throw new ResourceInvalidException( - "tls_certificate_provider_instance is unset"); - } - if (commonTlsContext.getTlsCertificateSdsSecretConfigsCount() > 0) { - throw new ResourceInvalidException( - "tls_certificate_provider_instance is unset"); - } - if (commonTlsContext.hasTlsCertificateCertificateProvider()) { - throw new ResourceInvalidException( - "tls_certificate_provider_instance is unset"); - } - } else if (certProviderInstances == null || !certProviderInstances.contains(certInstanceName)) { - throw new ResourceInvalidException( - "CertificateProvider instance name '" + certInstanceName - + "' not defined in the bootstrap file."); - } - String rootCaInstanceName = getRootCertInstanceName(commonTlsContext); - if (rootCaInstanceName == null) { - if (!server) { - throw new ResourceInvalidException( - "ca_certificate_provider_instance is required in upstream-tls-context"); - } - } else { - if (certProviderInstances == null || !certProviderInstances.contains(rootCaInstanceName)) { - throw new ResourceInvalidException( - "ca_certificate_provider_instance name '" + rootCaInstanceName - + "' not defined in the bootstrap file."); - } - CertificateValidationContext certificateValidationContext = null; - if (commonTlsContext.hasValidationContext()) { - certificateValidationContext = commonTlsContext.getValidationContext(); - } else if (commonTlsContext.hasCombinedValidationContext() && commonTlsContext - .getCombinedValidationContext().hasDefaultValidationContext()) { - certificateValidationContext = commonTlsContext.getCombinedValidationContext() - .getDefaultValidationContext(); - } - if (certificateValidationContext != null) { - if (certificateValidationContext.getMatchSubjectAltNamesCount() > 0 && server) { - throw new ResourceInvalidException( - "match_subject_alt_names only allowed in upstream_tls_context"); - } - if (certificateValidationContext.getVerifyCertificateSpkiCount() > 0) { - throw new ResourceInvalidException( - "verify_certificate_spki in default_validation_context is not supported"); - } - if (certificateValidationContext.getVerifyCertificateHashCount() > 0) { - throw new ResourceInvalidException( - "verify_certificate_hash in default_validation_context is not supported"); - } - if (certificateValidationContext.hasRequireSignedCertificateTimestamp()) { - throw new ResourceInvalidException( - "require_signed_certificate_timestamp in default_validation_context is not " - + "supported"); - } - if (certificateValidationContext.hasCrl()) { - throw new ResourceInvalidException("crl in default_validation_context is not supported"); - } - if (certificateValidationContext.hasCustomValidatorConfig()) { - throw new ResourceInvalidException( - "custom_validator_config in default_validation_context is not supported"); - } - } - } - } - - static io.envoyproxy.envoy.config.cluster.v3.OutlierDetection validateOutlierDetection( - io.envoyproxy.envoy.config.cluster.v3.OutlierDetection outlierDetection) - throws ResourceInvalidException { - if (outlierDetection.hasInterval()) { - if (!Durations.isValid(outlierDetection.getInterval())) { - throw new ResourceInvalidException("outlier_detection interval is not a valid Duration"); - } - if (hasNegativeValues(outlierDetection.getInterval())) { - throw new ResourceInvalidException("outlier_detection interval has a negative value"); - } - } - if (outlierDetection.hasBaseEjectionTime()) { - if (!Durations.isValid(outlierDetection.getBaseEjectionTime())) { - throw new ResourceInvalidException( - "outlier_detection base_ejection_time is not a valid Duration"); - } - if (hasNegativeValues(outlierDetection.getBaseEjectionTime())) { - throw new ResourceInvalidException( - "outlier_detection base_ejection_time has a negative value"); - } - } - if (outlierDetection.hasMaxEjectionTime()) { - if (!Durations.isValid(outlierDetection.getMaxEjectionTime())) { - throw new ResourceInvalidException( - "outlier_detection max_ejection_time is not a valid Duration"); - } - if (hasNegativeValues(outlierDetection.getMaxEjectionTime())) { - throw new ResourceInvalidException( - "outlier_detection max_ejection_time has a negative value"); - } - } - if (outlierDetection.hasMaxEjectionPercent() - && outlierDetection.getMaxEjectionPercent().getValue() > 100) { - throw new ResourceInvalidException( - "outlier_detection max_ejection_percent is > 100"); - } - if (outlierDetection.hasEnforcingSuccessRate() - && outlierDetection.getEnforcingSuccessRate().getValue() > 100) { - throw new ResourceInvalidException( - "outlier_detection enforcing_success_rate is > 100"); - } - if (outlierDetection.hasFailurePercentageThreshold() - && outlierDetection.getFailurePercentageThreshold().getValue() > 100) { - throw new ResourceInvalidException( - "outlier_detection failure_percentage_threshold is > 100"); - } - if (outlierDetection.hasEnforcingFailurePercentage() - && outlierDetection.getEnforcingFailurePercentage().getValue() > 100) { - throw new ResourceInvalidException( - "outlier_detection enforcing_failure_percentage is > 100"); - } - - return outlierDetection; - } - - static boolean hasNegativeValues(Duration duration) { - return duration.getSeconds() < 0 || duration.getNanos() < 0; - } - - private static String getIdentityCertInstanceName(CommonTlsContext commonTlsContext) { - if (commonTlsContext.hasTlsCertificateProviderInstance()) { - return commonTlsContext.getTlsCertificateProviderInstance().getInstanceName(); - } else if (commonTlsContext.hasTlsCertificateCertificateProviderInstance()) { - return commonTlsContext.getTlsCertificateCertificateProviderInstance().getInstanceName(); - } - return null; - } - - private static String getRootCertInstanceName(CommonTlsContext commonTlsContext) { - if (commonTlsContext.hasValidationContext()) { - if (commonTlsContext.getValidationContext().hasCaCertificateProviderInstance()) { - return commonTlsContext.getValidationContext().getCaCertificateProviderInstance() - .getInstanceName(); - } - } else if (commonTlsContext.hasCombinedValidationContext()) { - CommonTlsContext.CombinedCertificateValidationContext combinedCertificateValidationContext - = commonTlsContext.getCombinedValidationContext(); - if (combinedCertificateValidationContext.hasDefaultValidationContext() - && combinedCertificateValidationContext.getDefaultValidationContext() - .hasCaCertificateProviderInstance()) { - return combinedCertificateValidationContext.getDefaultValidationContext() - .getCaCertificateProviderInstance().getInstanceName(); - } else if (combinedCertificateValidationContext - .hasValidationContextCertificateProviderInstance()) { - return combinedCertificateValidationContext - .getValidationContextCertificateProviderInstance().getInstanceName(); - } - } - return null; - } - - private static void checkForUniqueness(Set uniqueSet, - FilterChainMatch filterChainMatch) throws ResourceInvalidException { - if (uniqueSet != null) { - List crossProduct = getCrossProduct(filterChainMatch); - for (FilterChainMatch cur : crossProduct) { - if (!uniqueSet.add(cur)) { - throw new ResourceInvalidException("FilterChainMatch must be unique. " - + "Found duplicate: " + cur); - } - } - } - } - - private static List getCrossProduct(FilterChainMatch filterChainMatch) { - // repeating fields to process: - // prefixRanges, applicationProtocols, sourcePrefixRanges, sourcePorts, serverNames - List expandedList = expandOnPrefixRange(filterChainMatch); - expandedList = expandOnApplicationProtocols(expandedList); - expandedList = expandOnSourcePrefixRange(expandedList); - expandedList = expandOnSourcePorts(expandedList); - return expandOnServerNames(expandedList); - } - - private static List expandOnPrefixRange(FilterChainMatch filterChainMatch) { - ArrayList expandedList = new ArrayList<>(); - if (filterChainMatch.prefixRanges().isEmpty()) { - expandedList.add(filterChainMatch); - } else { - for (EnvoyServerProtoData.CidrRange cidrRange : filterChainMatch.prefixRanges()) { - expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(), - ImmutableList.of(cidrRange), - filterChainMatch.applicationProtocols(), - filterChainMatch.sourcePrefixRanges(), - filterChainMatch.connectionSourceType(), - filterChainMatch.sourcePorts(), - filterChainMatch.serverNames(), - filterChainMatch.transportProtocol())); - } - } - return expandedList; - } - - private static List expandOnApplicationProtocols( - Collection set) { - ArrayList expandedList = new ArrayList<>(); - for (FilterChainMatch filterChainMatch : set) { - if (filterChainMatch.applicationProtocols().isEmpty()) { - expandedList.add(filterChainMatch); - } else { - for (String applicationProtocol : filterChainMatch.applicationProtocols()) { - expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(), - filterChainMatch.prefixRanges(), - ImmutableList.of(applicationProtocol), - filterChainMatch.sourcePrefixRanges(), - filterChainMatch.connectionSourceType(), - filterChainMatch.sourcePorts(), - filterChainMatch.serverNames(), - filterChainMatch.transportProtocol())); - } - } - } - return expandedList; - } - - private static List expandOnSourcePrefixRange( - Collection set) { - ArrayList expandedList = new ArrayList<>(); - for (FilterChainMatch filterChainMatch : set) { - if (filterChainMatch.sourcePrefixRanges().isEmpty()) { - expandedList.add(filterChainMatch); - } else { - for (EnvoyServerProtoData.CidrRange cidrRange : filterChainMatch.sourcePrefixRanges()) { - expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(), - filterChainMatch.prefixRanges(), - filterChainMatch.applicationProtocols(), - ImmutableList.of(cidrRange), - filterChainMatch.connectionSourceType(), - filterChainMatch.sourcePorts(), - filterChainMatch.serverNames(), - filterChainMatch.transportProtocol())); - } - } - } - return expandedList; - } - - private static List expandOnSourcePorts(Collection set) { - ArrayList expandedList = new ArrayList<>(); - for (FilterChainMatch filterChainMatch : set) { - if (filterChainMatch.sourcePorts().isEmpty()) { - expandedList.add(filterChainMatch); - } else { - for (Integer sourcePort : filterChainMatch.sourcePorts()) { - expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(), - filterChainMatch.prefixRanges(), - filterChainMatch.applicationProtocols(), - filterChainMatch.sourcePrefixRanges(), - filterChainMatch.connectionSourceType(), - ImmutableList.of(sourcePort), - filterChainMatch.serverNames(), - filterChainMatch.transportProtocol())); - } - } - } - return expandedList; - } - - private static List expandOnServerNames(Collection set) { - ArrayList expandedList = new ArrayList<>(); - for (FilterChainMatch filterChainMatch : set) { - if (filterChainMatch.serverNames().isEmpty()) { - expandedList.add(filterChainMatch); - } else { - for (String serverName : filterChainMatch.serverNames()) { - expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(), - filterChainMatch.prefixRanges(), - filterChainMatch.applicationProtocols(), - filterChainMatch.sourcePrefixRanges(), - filterChainMatch.connectionSourceType(), - filterChainMatch.sourcePorts(), - ImmutableList.of(serverName), - filterChainMatch.transportProtocol())); - } - } - } - return expandedList; - } - - private static FilterChainMatch parseFilterChainMatch( - io.envoyproxy.envoy.config.listener.v3.FilterChainMatch proto) - throws ResourceInvalidException { - ImmutableList.Builder prefixRanges = ImmutableList.builder(); - ImmutableList.Builder sourcePrefixRanges = ImmutableList.builder(); - try { - for (io.envoyproxy.envoy.config.core.v3.CidrRange range : proto.getPrefixRangesList()) { - prefixRanges.add( - CidrRange.create(range.getAddressPrefix(), range.getPrefixLen().getValue())); - } - for (io.envoyproxy.envoy.config.core.v3.CidrRange range - : proto.getSourcePrefixRangesList()) { - sourcePrefixRanges.add( - CidrRange.create(range.getAddressPrefix(), range.getPrefixLen().getValue())); - } - } catch (UnknownHostException e) { - throw new ResourceInvalidException("Failed to create CidrRange", e); - } - ConnectionSourceType sourceType; - switch (proto.getSourceType()) { - case ANY: - sourceType = ConnectionSourceType.ANY; - break; - case EXTERNAL: - sourceType = ConnectionSourceType.EXTERNAL; - break; - case SAME_IP_OR_LOOPBACK: - sourceType = ConnectionSourceType.SAME_IP_OR_LOOPBACK; - break; - default: - throw new ResourceInvalidException("Unknown source-type: " + proto.getSourceType()); - } - return FilterChainMatch.create( - proto.getDestinationPort().getValue(), - prefixRanges.build(), - ImmutableList.copyOf(proto.getApplicationProtocolsList()), - sourcePrefixRanges.build(), - sourceType, - ImmutableList.copyOf(proto.getSourcePortsList()), - ImmutableList.copyOf(proto.getServerNamesList()), - proto.getTransportProtocol()); - } - - @VisibleForTesting - static io.grpc.xds.HttpConnectionManager parseHttpConnectionManager( - HttpConnectionManager proto, Set rdsResources, FilterRegistry filterRegistry, - boolean parseHttpFilter, boolean isForClient) throws ResourceInvalidException { - if (enableRbac && proto.getXffNumTrustedHops() != 0) { - throw new ResourceInvalidException( - "HttpConnectionManager with xff_num_trusted_hops unsupported"); - } - if (enableRbac && !proto.getOriginalIpDetectionExtensionsList().isEmpty()) { - throw new ResourceInvalidException("HttpConnectionManager with " - + "original_ip_detection_extensions unsupported"); - } - // Obtain max_stream_duration from Http Protocol Options. - long maxStreamDuration = 0; - if (proto.hasCommonHttpProtocolOptions()) { - HttpProtocolOptions options = proto.getCommonHttpProtocolOptions(); - if (options.hasMaxStreamDuration()) { - maxStreamDuration = Durations.toNanos(options.getMaxStreamDuration()); - } - } - - // Parse http filters. - List filterConfigs = null; - if (parseHttpFilter) { - if (proto.getHttpFiltersList().isEmpty()) { - throw new ResourceInvalidException("Missing HttpFilter in HttpConnectionManager."); - } - filterConfigs = new ArrayList<>(); - Set names = new HashSet<>(); - for (int i = 0; i < proto.getHttpFiltersCount(); i++) { - io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter - httpFilter = proto.getHttpFiltersList().get(i); - String filterName = httpFilter.getName(); - if (!names.add(filterName)) { - throw new ResourceInvalidException( - "HttpConnectionManager contains duplicate HttpFilter: " + filterName); - } - StructOrError filterConfig = - parseHttpFilter(httpFilter, filterRegistry, isForClient); - if ((i == proto.getHttpFiltersCount() - 1) - && (filterConfig == null || !isTerminalFilter(filterConfig.struct))) { - throw new ResourceInvalidException("The last HttpFilter must be a terminal filter: " - + filterName); - } - if (filterConfig == null) { - continue; - } - if (filterConfig.getErrorDetail() != null) { - throw new ResourceInvalidException( - "HttpConnectionManager contains invalid HttpFilter: " - + filterConfig.getErrorDetail()); - } - if ((i < proto.getHttpFiltersCount() - 1) && isTerminalFilter(filterConfig.getStruct())) { - throw new ResourceInvalidException("A terminal HttpFilter must be the last filter: " - + filterName); - } - filterConfigs.add(new NamedFilterConfig(filterName, filterConfig.struct)); - } - } - - // Parse inlined RouteConfiguration or RDS. - if (proto.hasRouteConfig()) { - List virtualHosts = extractVirtualHosts( - proto.getRouteConfig(), filterRegistry, parseHttpFilter); - return io.grpc.xds.HttpConnectionManager.forVirtualHosts( - maxStreamDuration, virtualHosts, filterConfigs); - } - if (proto.hasRds()) { - Rds rds = proto.getRds(); - if (!rds.hasConfigSource()) { - throw new ResourceInvalidException( - "HttpConnectionManager contains invalid RDS: missing config_source"); - } - if (!rds.getConfigSource().hasAds() && !rds.getConfigSource().hasSelf()) { - throw new ResourceInvalidException( - "HttpConnectionManager contains invalid RDS: must specify ADS or self ConfigSource"); - } - // Collect the RDS resource referenced by this HttpConnectionManager. - rdsResources.add(rds.getRouteConfigName()); - return io.grpc.xds.HttpConnectionManager.forRdsName( - maxStreamDuration, rds.getRouteConfigName(), filterConfigs); - } - throw new ResourceInvalidException( - "HttpConnectionManager neither has inlined route_config nor RDS"); - } - - // hard-coded: currently router config is the only terminal filter. - private static boolean isTerminalFilter(FilterConfig filterConfig) { - return RouterFilter.ROUTER_CONFIG.equals(filterConfig); - } - - @VisibleForTesting - @Nullable // Returns null if the filter is optional but not supported. - static StructOrError parseHttpFilter( - io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter - httpFilter, FilterRegistry filterRegistry, boolean isForClient) { - String filterName = httpFilter.getName(); - boolean isOptional = httpFilter.getIsOptional(); - if (!httpFilter.hasTypedConfig()) { - if (isOptional) { - return null; - } else { - return StructOrError.fromError( - "HttpFilter [" + filterName + "] is not optional and has no typed config"); - } - } - Message rawConfig = httpFilter.getTypedConfig(); - String typeUrl = httpFilter.getTypedConfig().getTypeUrl(); - - try { - if (typeUrl.equals(TYPE_URL_TYPED_STRUCT_UDPA)) { - TypedStruct typedStruct = httpFilter.getTypedConfig().unpack(TypedStruct.class); - typeUrl = typedStruct.getTypeUrl(); - rawConfig = typedStruct.getValue(); - } else if (typeUrl.equals(TYPE_URL_TYPED_STRUCT)) { - com.github.xds.type.v3.TypedStruct newTypedStruct = - httpFilter.getTypedConfig().unpack(com.github.xds.type.v3.TypedStruct.class); - typeUrl = newTypedStruct.getTypeUrl(); - rawConfig = newTypedStruct.getValue(); - } - } catch (InvalidProtocolBufferException e) { - return StructOrError.fromError( - "HttpFilter [" + filterName + "] contains invalid proto: " + e); - } - Filter filter = filterRegistry.get(typeUrl); - if ((isForClient && !(filter instanceof ClientInterceptorBuilder)) - || (!isForClient && !(filter instanceof ServerInterceptorBuilder))) { - if (isOptional) { - return null; - } else { - return StructOrError.fromError( - "HttpFilter [" + filterName + "](" + typeUrl + ") is required but unsupported for " - + (isForClient ? "client" : "server")); - } - } - ConfigOrError filterConfig = filter.parseFilterConfig(rawConfig); - if (filterConfig.errorDetail != null) { - return StructOrError.fromError( - "Invalid filter config for HttpFilter [" + filterName + "]: " + filterConfig.errorDetail); - } - return StructOrError.fromStruct(filterConfig.config); - } - - private static StructOrError parseVirtualHost( - io.envoyproxy.envoy.config.route.v3.VirtualHost proto, FilterRegistry filterRegistry, - boolean parseHttpFilter, Map pluginConfigMap, - Set optionalPlugins) { - String name = proto.getName(); - List routes = new ArrayList<>(proto.getRoutesCount()); - for (io.envoyproxy.envoy.config.route.v3.Route routeProto : proto.getRoutesList()) { - StructOrError route = parseRoute( - routeProto, filterRegistry, parseHttpFilter, pluginConfigMap, optionalPlugins); - if (route == null) { - continue; - } - if (route.getErrorDetail() != null) { - return StructOrError.fromError( - "Virtual host [" + name + "] contains invalid route : " + route.getErrorDetail()); - } - routes.add(route.getStruct()); - } - if (!parseHttpFilter) { - return StructOrError.fromStruct(VirtualHost.create( - name, proto.getDomainsList(), routes, new HashMap())); - } - StructOrError> overrideConfigs = - parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry); - if (overrideConfigs.errorDetail != null) { - return StructOrError.fromError( - "VirtualHost [" + proto.getName() + "] contains invalid HttpFilter config: " - + overrideConfigs.errorDetail); - } - return StructOrError.fromStruct(VirtualHost.create( - name, proto.getDomainsList(), routes, overrideConfigs.struct)); - } - - @VisibleForTesting - static StructOrError> parseOverrideFilterConfigs( - Map rawFilterConfigMap, FilterRegistry filterRegistry) { - Map overrideConfigs = new HashMap<>(); - for (String name : rawFilterConfigMap.keySet()) { - Any anyConfig = rawFilterConfigMap.get(name); - String typeUrl = anyConfig.getTypeUrl(); - boolean isOptional = false; - if (typeUrl.equals(TYPE_URL_FILTER_CONFIG)) { - io.envoyproxy.envoy.config.route.v3.FilterConfig filterConfig; - try { - filterConfig = - anyConfig.unpack(io.envoyproxy.envoy.config.route.v3.FilterConfig.class); - } catch (InvalidProtocolBufferException e) { - return StructOrError.fromError( - "FilterConfig [" + name + "] contains invalid proto: " + e); - } - isOptional = filterConfig.getIsOptional(); - anyConfig = filterConfig.getConfig(); - typeUrl = anyConfig.getTypeUrl(); - } - Message rawConfig = anyConfig; - try { - if (typeUrl.equals(TYPE_URL_TYPED_STRUCT_UDPA)) { - TypedStruct typedStruct = anyConfig.unpack(TypedStruct.class); - typeUrl = typedStruct.getTypeUrl(); - rawConfig = typedStruct.getValue(); - } else if (typeUrl.equals(TYPE_URL_TYPED_STRUCT)) { - com.github.xds.type.v3.TypedStruct newTypedStruct = - anyConfig.unpack(com.github.xds.type.v3.TypedStruct.class); - typeUrl = newTypedStruct.getTypeUrl(); - rawConfig = newTypedStruct.getValue(); - } - } catch (InvalidProtocolBufferException e) { - return StructOrError.fromError( - "FilterConfig [" + name + "] contains invalid proto: " + e); - } - Filter filter = filterRegistry.get(typeUrl); - if (filter == null) { - if (isOptional) { - continue; - } - return StructOrError.fromError( - "HttpFilter [" + name + "](" + typeUrl + ") is required but unsupported"); - } - ConfigOrError filterConfig = - filter.parseFilterConfigOverride(rawConfig); - if (filterConfig.errorDetail != null) { - return StructOrError.fromError( - "Invalid filter config for HttpFilter [" + name + "]: " + filterConfig.errorDetail); - } - overrideConfigs.put(name, filterConfig.config); - } - return StructOrError.fromStruct(overrideConfigs); - } - - @VisibleForTesting - @Nullable - static StructOrError parseRoute( - io.envoyproxy.envoy.config.route.v3.Route proto, FilterRegistry filterRegistry, - boolean parseHttpFilter, Map pluginConfigMap, - Set optionalPlugins) { - StructOrError routeMatch = parseRouteMatch(proto.getMatch()); - if (routeMatch == null) { - return null; - } - if (routeMatch.getErrorDetail() != null) { - return StructOrError.fromError( - "Route [" + proto.getName() + "] contains invalid RouteMatch: " - + routeMatch.getErrorDetail()); - } - - Map overrideConfigs = Collections.emptyMap(); - if (parseHttpFilter) { - StructOrError> overrideConfigsOrError = - parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry); - if (overrideConfigsOrError.errorDetail != null) { - return StructOrError.fromError( - "Route [" + proto.getName() + "] contains invalid HttpFilter config: " - + overrideConfigsOrError.errorDetail); - } - overrideConfigs = overrideConfigsOrError.struct; - } - - switch (proto.getActionCase()) { - case ROUTE: - StructOrError routeAction = - parseRouteAction(proto.getRoute(), filterRegistry, parseHttpFilter, pluginConfigMap, - optionalPlugins); - if (routeAction == null) { - return null; - } - if (routeAction.errorDetail != null) { - return StructOrError.fromError( - "Route [" + proto.getName() + "] contains invalid RouteAction: " - + routeAction.getErrorDetail()); - } - return StructOrError.fromStruct( - Route.forAction(routeMatch.struct, routeAction.struct, overrideConfigs)); - case NON_FORWARDING_ACTION: - return StructOrError.fromStruct( - Route.forNonForwardingAction(routeMatch.struct, overrideConfigs)); - case REDIRECT: - case DIRECT_RESPONSE: - case FILTER_ACTION: - case ACTION_NOT_SET: - default: - return StructOrError.fromError( - "Route [" + proto.getName() + "] with unknown action type: " + proto.getActionCase()); - } - } - - @VisibleForTesting - @Nullable - static StructOrError parseRouteMatch( - io.envoyproxy.envoy.config.route.v3.RouteMatch proto) { - if (proto.getQueryParametersCount() != 0) { - return null; - } - StructOrError pathMatch = parsePathMatcher(proto); - if (pathMatch.getErrorDetail() != null) { - return StructOrError.fromError(pathMatch.getErrorDetail()); - } - - FractionMatcher fractionMatch = null; - if (proto.hasRuntimeFraction()) { - StructOrError parsedFraction = - parseFractionMatcher(proto.getRuntimeFraction().getDefaultValue()); - if (parsedFraction.getErrorDetail() != null) { - return StructOrError.fromError(parsedFraction.getErrorDetail()); - } - fractionMatch = parsedFraction.getStruct(); - } - - List headerMatchers = new ArrayList<>(); - for (io.envoyproxy.envoy.config.route.v3.HeaderMatcher hmProto : proto.getHeadersList()) { - StructOrError headerMatcher = parseHeaderMatcher(hmProto); - if (headerMatcher.getErrorDetail() != null) { - return StructOrError.fromError(headerMatcher.getErrorDetail()); - } - headerMatchers.add(headerMatcher.getStruct()); - } - - return StructOrError.fromStruct(RouteMatch.create( - pathMatch.getStruct(), headerMatchers, fractionMatch)); - } - - @VisibleForTesting - static StructOrError parsePathMatcher( - io.envoyproxy.envoy.config.route.v3.RouteMatch proto) { - boolean caseSensitive = proto.getCaseSensitive().getValue(); - switch (proto.getPathSpecifierCase()) { - case PREFIX: - return StructOrError.fromStruct( - PathMatcher.fromPrefix(proto.getPrefix(), caseSensitive)); - case PATH: - return StructOrError.fromStruct(PathMatcher.fromPath(proto.getPath(), caseSensitive)); - case SAFE_REGEX: - String rawPattern = proto.getSafeRegex().getRegex(); - Pattern safeRegEx; - try { - safeRegEx = Pattern.compile(rawPattern); - } catch (PatternSyntaxException e) { - return StructOrError.fromError("Malformed safe regex pattern: " + e.getMessage()); - } - return StructOrError.fromStruct(PathMatcher.fromRegEx(safeRegEx)); - case PATHSPECIFIER_NOT_SET: - default: - return StructOrError.fromError("Unknown path match type"); - } - } - - private static StructOrError parseFractionMatcher(FractionalPercent proto) { - int numerator = proto.getNumerator(); - int denominator = 0; - switch (proto.getDenominator()) { - case HUNDRED: - denominator = 100; - break; - case TEN_THOUSAND: - denominator = 10_000; - break; - case MILLION: - denominator = 1_000_000; - break; - case UNRECOGNIZED: - default: - return StructOrError.fromError( - "Unrecognized fractional percent denominator: " + proto.getDenominator()); - } - return StructOrError.fromStruct(FractionMatcher.create(numerator, denominator)); - } - - @VisibleForTesting - static StructOrError parseHeaderMatcher( - io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto) { - switch (proto.getHeaderMatchSpecifierCase()) { - case EXACT_MATCH: - return StructOrError.fromStruct(HeaderMatcher.forExactValue( - proto.getName(), proto.getExactMatch(), proto.getInvertMatch())); - case SAFE_REGEX_MATCH: - String rawPattern = proto.getSafeRegexMatch().getRegex(); - Pattern safeRegExMatch; - try { - safeRegExMatch = Pattern.compile(rawPattern); - } catch (PatternSyntaxException e) { - return StructOrError.fromError( - "HeaderMatcher [" + proto.getName() + "] contains malformed safe regex pattern: " - + e.getMessage()); - } - return StructOrError.fromStruct(HeaderMatcher.forSafeRegEx( - proto.getName(), safeRegExMatch, proto.getInvertMatch())); - case RANGE_MATCH: - HeaderMatcher.Range rangeMatch = HeaderMatcher.Range.create( - proto.getRangeMatch().getStart(), proto.getRangeMatch().getEnd()); - return StructOrError.fromStruct(HeaderMatcher.forRange( - proto.getName(), rangeMatch, proto.getInvertMatch())); - case PRESENT_MATCH: - return StructOrError.fromStruct(HeaderMatcher.forPresent( - proto.getName(), proto.getPresentMatch(), proto.getInvertMatch())); - case PREFIX_MATCH: - return StructOrError.fromStruct(HeaderMatcher.forPrefix( - proto.getName(), proto.getPrefixMatch(), proto.getInvertMatch())); - case SUFFIX_MATCH: - return StructOrError.fromStruct(HeaderMatcher.forSuffix( - proto.getName(), proto.getSuffixMatch(), proto.getInvertMatch())); - case HEADERMATCHSPECIFIER_NOT_SET: - default: - return StructOrError.fromError("Unknown header matcher type"); - } - } - - /** - * Parses the RouteAction config. The returned result may contain a (parsed form) - * {@link RouteAction} or an error message. Returns {@code null} if the RouteAction - * should be ignored. - */ - @VisibleForTesting - @Nullable - static StructOrError parseRouteAction( - io.envoyproxy.envoy.config.route.v3.RouteAction proto, FilterRegistry filterRegistry, - boolean parseHttpFilter, Map pluginConfigMap, - Set optionalPlugins) { - Long timeoutNano = null; - if (proto.hasMaxStreamDuration()) { - io.envoyproxy.envoy.config.route.v3.RouteAction.MaxStreamDuration maxStreamDuration - = proto.getMaxStreamDuration(); - if (maxStreamDuration.hasGrpcTimeoutHeaderMax()) { - timeoutNano = Durations.toNanos(maxStreamDuration.getGrpcTimeoutHeaderMax()); - } else if (maxStreamDuration.hasMaxStreamDuration()) { - timeoutNano = Durations.toNanos(maxStreamDuration.getMaxStreamDuration()); - } - } - RetryPolicy retryPolicy = null; - if (enableRetry && proto.hasRetryPolicy()) { - StructOrError retryPolicyOrError = parseRetryPolicy(proto.getRetryPolicy()); - if (retryPolicyOrError != null) { - if (retryPolicyOrError.errorDetail != null) { - return StructOrError.fromError(retryPolicyOrError.errorDetail); - } - retryPolicy = retryPolicyOrError.struct; - } - } - List hashPolicies = new ArrayList<>(); - for (io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy config - : proto.getHashPolicyList()) { - HashPolicy policy = null; - boolean terminal = config.getTerminal(); - switch (config.getPolicySpecifierCase()) { - case HEADER: - io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy.Header headerCfg = - config.getHeader(); - Pattern regEx = null; - String regExSubstitute = null; - if (headerCfg.hasRegexRewrite() && headerCfg.getRegexRewrite().hasPattern() - && headerCfg.getRegexRewrite().getPattern().hasGoogleRe2()) { - regEx = Pattern.compile(headerCfg.getRegexRewrite().getPattern().getRegex()); - regExSubstitute = headerCfg.getRegexRewrite().getSubstitution(); - } - policy = HashPolicy.forHeader( - terminal, headerCfg.getHeaderName(), regEx, regExSubstitute); - break; - case FILTER_STATE: - if (config.getFilterState().getKey().equals(HASH_POLICY_FILTER_STATE_KEY)) { - policy = HashPolicy.forChannelId(terminal); - } - break; - default: - // Ignore - } - if (policy != null) { - hashPolicies.add(policy); - } - } - - switch (proto.getClusterSpecifierCase()) { - case CLUSTER: - return StructOrError.fromStruct(RouteAction.forCluster( - proto.getCluster(), hashPolicies, timeoutNano, retryPolicy)); - case CLUSTER_HEADER: - return null; - case WEIGHTED_CLUSTERS: - List clusterWeights - = proto.getWeightedClusters().getClustersList(); - if (clusterWeights.isEmpty()) { - return StructOrError.fromError("No cluster found in weighted cluster list"); - } - List weightedClusters = new ArrayList<>(); - for (io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight clusterWeight - : clusterWeights) { - StructOrError clusterWeightOrError = - parseClusterWeight(clusterWeight, filterRegistry, parseHttpFilter); - if (clusterWeightOrError.getErrorDetail() != null) { - return StructOrError.fromError("RouteAction contains invalid ClusterWeight: " - + clusterWeightOrError.getErrorDetail()); - } - weightedClusters.add(clusterWeightOrError.getStruct()); - } - // TODO(chengyuanzhang): validate if the sum of weights equals to total weight. - return StructOrError.fromStruct(RouteAction.forWeightedClusters( - weightedClusters, hashPolicies, timeoutNano, retryPolicy)); - case CLUSTER_SPECIFIER_PLUGIN: - if (enableRouteLookup) { - String pluginName = proto.getClusterSpecifierPlugin(); - PluginConfig pluginConfig = pluginConfigMap.get(pluginName); - if (pluginConfig == null) { - // Skip route if the plugin is not registered, but it's optional. - if (optionalPlugins.contains(pluginName)) { - return null; - } - return StructOrError.fromError( - "ClusterSpecifierPlugin for [" + pluginName + "] not found"); - } - NamedPluginConfig namedPluginConfig = NamedPluginConfig.create(pluginName, pluginConfig); - return StructOrError.fromStruct(RouteAction.forClusterSpecifierPlugin( - namedPluginConfig, hashPolicies, timeoutNano, retryPolicy)); - } else { - return null; - } - case CLUSTERSPECIFIER_NOT_SET: - default: - return null; - } - } - - @Nullable // Return null if we ignore the given policy. - private static StructOrError parseRetryPolicy( - io.envoyproxy.envoy.config.route.v3.RetryPolicy retryPolicyProto) { - int maxAttempts = 2; - if (retryPolicyProto.hasNumRetries()) { - maxAttempts = retryPolicyProto.getNumRetries().getValue() + 1; - } - Duration initialBackoff = Durations.fromMillis(25); - Duration maxBackoff = Durations.fromMillis(250); - if (retryPolicyProto.hasRetryBackOff()) { - RetryBackOff retryBackOff = retryPolicyProto.getRetryBackOff(); - if (!retryBackOff.hasBaseInterval()) { - return StructOrError.fromError("No base_interval specified in retry_backoff"); - } - Duration originalInitialBackoff = initialBackoff = retryBackOff.getBaseInterval(); - if (Durations.compare(initialBackoff, Durations.ZERO) <= 0) { - return StructOrError.fromError("base_interval in retry_backoff must be positive"); - } - if (Durations.compare(initialBackoff, Durations.fromMillis(1)) < 0) { - initialBackoff = Durations.fromMillis(1); - } - if (retryBackOff.hasMaxInterval()) { - maxBackoff = retryPolicyProto.getRetryBackOff().getMaxInterval(); - if (Durations.compare(maxBackoff, originalInitialBackoff) < 0) { - return StructOrError.fromError( - "max_interval in retry_backoff cannot be less than base_interval"); - } - if (Durations.compare(maxBackoff, Durations.fromMillis(1)) < 0) { - maxBackoff = Durations.fromMillis(1); - } - } else { - maxBackoff = Durations.fromNanos(Durations.toNanos(initialBackoff) * 10); - } - } - Iterable retryOns = - Splitter.on(',').omitEmptyStrings().trimResults().split(retryPolicyProto.getRetryOn()); - ImmutableList.Builder retryableStatusCodesBuilder = ImmutableList.builder(); - for (String retryOn : retryOns) { - Code code; - try { - code = Code.valueOf(retryOn.toUpperCase(Locale.US).replace('-', '_')); - } catch (IllegalArgumentException e) { - // unsupported value, such as "5xx" - continue; - } - if (!SUPPORTED_RETRYABLE_CODES.contains(code)) { - // unsupported value - continue; - } - retryableStatusCodesBuilder.add(code); - } - List retryableStatusCodes = retryableStatusCodesBuilder.build(); - return StructOrError.fromStruct( - RetryPolicy.create( - maxAttempts, retryableStatusCodes, initialBackoff, maxBackoff, - /* perAttemptRecvTimeout= */ null)); - } - - @VisibleForTesting - static StructOrError parseClusterWeight( - io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight proto, - FilterRegistry filterRegistry, boolean parseHttpFilter) { - if (!parseHttpFilter) { - return StructOrError.fromStruct(ClusterWeight.create( - proto.getName(), proto.getWeight().getValue(), new HashMap())); - } - StructOrError> overrideConfigs = - parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry); - if (overrideConfigs.errorDetail != null) { - return StructOrError.fromError( - "ClusterWeight [" + proto.getName() + "] contains invalid HttpFilter config: " - + overrideConfigs.errorDetail); - } - return StructOrError.fromStruct(ClusterWeight.create( - proto.getName(), proto.getWeight().getValue(), overrideConfigs.struct)); - } - - @Override - public void handleRdsResponse( - ServerInfo serverInfo, String versionInfo, List resources, String nonce) { - syncContext.throwIfNotInThisSynchronizationContext(); - Map parsedResources = new HashMap<>(resources.size()); - Set unpackedResources = new HashSet<>(resources.size()); - Set invalidResources = new HashSet<>(); - List errors = new ArrayList<>(); - - for (int i = 0; i < resources.size(); i++) { - Any resource = resources.get(i); - - // Unpack the RouteConfiguration. - RouteConfiguration routeConfig; - try { - resource = maybeUnwrapResources(resource); - routeConfig = unpackCompatibleType(resource, RouteConfiguration.class, - ResourceType.RDS.typeUrl(), ResourceType.RDS.typeUrlV2()); - } catch (InvalidProtocolBufferException e) { - errors.add("RDS response Resource index " + i + " - can't decode RouteConfiguration: " + e); - continue; - } - if (!isResourceNameValid(routeConfig.getName(), resource.getTypeUrl())) { - errors.add( - "Unsupported resource name: " + routeConfig.getName() + " for type: " - + ResourceType.RDS); - continue; - } - String routeConfigName = canonifyResourceName(routeConfig.getName()); - unpackedResources.add(routeConfigName); - - // Process RouteConfiguration into RdsUpdate. - RdsUpdate rdsUpdate; - boolean isResourceV3 = resource.getTypeUrl().equals(ResourceType.RDS.typeUrl()); - try { - rdsUpdate = processRouteConfiguration( - routeConfig, filterRegistry, enableFaultInjection && isResourceV3); - } catch (ResourceInvalidException e) { - errors.add( - "RDS response RouteConfiguration '" + routeConfigName + "' validation error: " + e - .getMessage()); - invalidResources.add(routeConfigName); - continue; - } - - parsedResources.put(routeConfigName, new ParsedResource(rdsUpdate, resource)); - } - logger.log(XdsLogLevel.INFO, - "Received RDS Response version {0} nonce {1}. Parsed resources: {2}", - versionInfo, nonce, unpackedResources); - handleResourceUpdate( - serverInfo, ResourceType.RDS, parsedResources, invalidResources, - Collections.emptySet(), versionInfo, nonce, errors); - } - - private static RdsUpdate processRouteConfiguration( - RouteConfiguration routeConfig, FilterRegistry filterRegistry, boolean parseHttpFilter) - throws ResourceInvalidException { - return new RdsUpdate(extractVirtualHosts(routeConfig, filterRegistry, parseHttpFilter)); - } - - private static List extractVirtualHosts( - RouteConfiguration routeConfig, FilterRegistry filterRegistry, boolean parseHttpFilter) - throws ResourceInvalidException { - Map pluginConfigMap = new HashMap<>(); - ImmutableSet.Builder optionalPlugins = ImmutableSet.builder(); - - if (enableRouteLookup) { - List plugins = routeConfig.getClusterSpecifierPluginsList(); - for (ClusterSpecifierPlugin plugin : plugins) { - String pluginName = plugin.getExtension().getName(); - PluginConfig pluginConfig = parseClusterSpecifierPlugin(plugin); - if (pluginConfig != null) { - if (pluginConfigMap.put(pluginName, pluginConfig) != null) { - throw new ResourceInvalidException( - "Multiple ClusterSpecifierPlugins with the same name: " + pluginName); - } - } else { - // The plugin parsed successfully, and it's not supported, but it's marked as optional. - optionalPlugins.add(pluginName); - } - } - } - List virtualHosts = new ArrayList<>(routeConfig.getVirtualHostsCount()); - for (io.envoyproxy.envoy.config.route.v3.VirtualHost virtualHostProto - : routeConfig.getVirtualHostsList()) { - StructOrError virtualHost = - parseVirtualHost(virtualHostProto, filterRegistry, parseHttpFilter, pluginConfigMap, - optionalPlugins.build()); - if (virtualHost.getErrorDetail() != null) { - throw new ResourceInvalidException( - "RouteConfiguration contains invalid virtual host: " + virtualHost.getErrorDetail()); - } - virtualHosts.add(virtualHost.getStruct()); - } - return virtualHosts; - } - - @Nullable // null if the plugin is not supported, but it's marked as optional. - private static PluginConfig parseClusterSpecifierPlugin(ClusterSpecifierPlugin pluginProto) - throws ResourceInvalidException { - return parseClusterSpecifierPlugin( - pluginProto, ClusterSpecifierPluginRegistry.getDefaultRegistry()); - } - - @Nullable // null if the plugin is not supported, but it's marked as optional. - @VisibleForTesting - static PluginConfig parseClusterSpecifierPlugin( - ClusterSpecifierPlugin pluginProto, ClusterSpecifierPluginRegistry registry) - throws ResourceInvalidException { - TypedExtensionConfig extension = pluginProto.getExtension(); - String pluginName = extension.getName(); - Any anyConfig = extension.getTypedConfig(); - String typeUrl = anyConfig.getTypeUrl(); - Message rawConfig = anyConfig; - if (typeUrl.equals(TYPE_URL_TYPED_STRUCT_UDPA) || typeUrl.equals(TYPE_URL_TYPED_STRUCT)) { - try { - TypedStruct typedStruct = unpackCompatibleType( - anyConfig, TypedStruct.class, TYPE_URL_TYPED_STRUCT_UDPA, TYPE_URL_TYPED_STRUCT); - typeUrl = typedStruct.getTypeUrl(); - rawConfig = typedStruct.getValue(); - } catch (InvalidProtocolBufferException e) { - throw new ResourceInvalidException( - "ClusterSpecifierPlugin [" + pluginName + "] contains invalid proto", e); - } - } - io.grpc.xds.ClusterSpecifierPlugin plugin = registry.get(typeUrl); - if (plugin == null) { - if (!pluginProto.getIsOptional()) { - throw new ResourceInvalidException("Unsupported ClusterSpecifierPlugin type: " + typeUrl); - } - return null; - } - ConfigOrError pluginConfigOrError = plugin.parsePlugin(rawConfig); - if (pluginConfigOrError.errorDetail != null) { - throw new ResourceInvalidException(pluginConfigOrError.errorDetail); - } - return pluginConfigOrError.config; - } - - @Override - public void handleCdsResponse( - ServerInfo serverInfo, String versionInfo, List resources, String nonce) { - syncContext.throwIfNotInThisSynchronizationContext(); - Map parsedResources = new HashMap<>(resources.size()); - Set unpackedResources = new HashSet<>(resources.size()); - Set invalidResources = new HashSet<>(); - List errors = new ArrayList<>(); - Set retainedEdsResources = new HashSet<>(); - - for (int i = 0; i < resources.size(); i++) { - Any resource = resources.get(i); - - // Unpack the Cluster. - Cluster cluster; - try { - resource = maybeUnwrapResources(resource); - cluster = unpackCompatibleType( - resource, Cluster.class, ResourceType.CDS.typeUrl(), ResourceType.CDS.typeUrlV2()); - } catch (InvalidProtocolBufferException e) { - errors.add("CDS response Resource index " + i + " - can't decode Cluster: " + e); - continue; - } - if (!isResourceNameValid(cluster.getName(), resource.getTypeUrl())) { - errors.add( - "Unsupported resource name: " + cluster.getName() + " for type: " + ResourceType.CDS); - continue; - } - String clusterName = canonifyResourceName(cluster.getName()); - - // Management server is required to always send newly requested resources, even if they - // may have been sent previously (proactively). Thus, client does not need to cache - // unrequested resources. - if (!cdsResourceSubscribers.containsKey(clusterName)) { - continue; - } - unpackedResources.add(clusterName); - - // Process Cluster into CdsUpdate. - CdsUpdate cdsUpdate; - try { - Set certProviderInstances = null; - if (getBootstrapInfo() != null && getBootstrapInfo().certProviders() != null) { - certProviderInstances = getBootstrapInfo().certProviders().keySet(); - } - cdsUpdate = processCluster(cluster, retainedEdsResources, certProviderInstances, serverInfo, - loadBalancerRegistry); - } catch (ResourceInvalidException e) { - errors.add( - "CDS response Cluster '" + clusterName + "' validation error: " + e.getMessage()); - invalidResources.add(clusterName); - continue; - } - parsedResources.put(clusterName, new ParsedResource(cdsUpdate, resource)); - } - logger.log(XdsLogLevel.INFO, - "Received CDS Response version {0} nonce {1}. Parsed resources: {2}", - versionInfo, nonce, unpackedResources); - handleResourceUpdate( - serverInfo, ResourceType.CDS, parsedResources, invalidResources, retainedEdsResources, - versionInfo, nonce, errors); - } - - @VisibleForTesting - static CdsUpdate processCluster(Cluster cluster, Set retainedEdsResources, - Set certProviderInstances, ServerInfo serverInfo, - LoadBalancerRegistry loadBalancerRegistry) - throws ResourceInvalidException { - StructOrError structOrError; - switch (cluster.getClusterDiscoveryTypeCase()) { - case TYPE: - structOrError = parseNonAggregateCluster(cluster, retainedEdsResources, - certProviderInstances, serverInfo); - break; - case CLUSTER_TYPE: - structOrError = parseAggregateCluster(cluster); - break; - case CLUSTERDISCOVERYTYPE_NOT_SET: - default: - throw new ResourceInvalidException( - "Cluster " + cluster.getName() + ": unspecified cluster discovery type"); - } - if (structOrError.getErrorDetail() != null) { - throw new ResourceInvalidException(structOrError.getErrorDetail()); - } - CdsUpdate.Builder updateBuilder = structOrError.getStruct(); - - ImmutableMap lbPolicyConfig = LoadBalancerConfigFactory.newConfig(cluster, - enableLeastRequest, enableCustomLbConfig); - - // Validate the LB config by trying to parse it with the corresponding LB provider. - LbConfig lbConfig = ServiceConfigUtil.unwrapLoadBalancingConfig(lbPolicyConfig); - NameResolver.ConfigOrError configOrError = loadBalancerRegistry.getProvider( - lbConfig.getPolicyName()).parseLoadBalancingPolicyConfig( - lbConfig.getRawConfigValue()); - if (configOrError.getError() != null) { - throw new ResourceInvalidException(structOrError.getErrorDetail()); - } - - updateBuilder.lbPolicyConfig(lbPolicyConfig); - - return updateBuilder.build(); - } - - private static StructOrError parseAggregateCluster(Cluster cluster) { - String clusterName = cluster.getName(); - CustomClusterType customType = cluster.getClusterType(); - String typeName = customType.getName(); - if (!typeName.equals(AGGREGATE_CLUSTER_TYPE_NAME)) { - return StructOrError.fromError( - "Cluster " + clusterName + ": unsupported custom cluster type: " + typeName); - } - io.envoyproxy.envoy.extensions.clusters.aggregate.v3.ClusterConfig clusterConfig; - try { - clusterConfig = unpackCompatibleType(customType.getTypedConfig(), - io.envoyproxy.envoy.extensions.clusters.aggregate.v3.ClusterConfig.class, - TYPE_URL_CLUSTER_CONFIG, TYPE_URL_CLUSTER_CONFIG_V2); - } catch (InvalidProtocolBufferException e) { - return StructOrError.fromError("Cluster " + clusterName + ": malformed ClusterConfig: " + e); - } - return StructOrError.fromStruct(CdsUpdate.forAggregate( - clusterName, clusterConfig.getClustersList())); - } - - private static StructOrError parseNonAggregateCluster( - Cluster cluster, Set edsResources, Set certProviderInstances, - ServerInfo serverInfo) { - String clusterName = cluster.getName(); - ServerInfo lrsServerInfo = null; - Long maxConcurrentRequests = null; - UpstreamTlsContext upstreamTlsContext = null; - OutlierDetection outlierDetection = null; - if (cluster.hasLrsServer()) { - if (!cluster.getLrsServer().hasSelf()) { - return StructOrError.fromError( - "Cluster " + clusterName + ": only support LRS for the same management server"); - } - lrsServerInfo = serverInfo; - } - if (cluster.hasCircuitBreakers()) { - List thresholds = cluster.getCircuitBreakers().getThresholdsList(); - for (Thresholds threshold : thresholds) { - if (threshold.getPriority() != RoutingPriority.DEFAULT) { - continue; - } - if (threshold.hasMaxRequests()) { - maxConcurrentRequests = (long) threshold.getMaxRequests().getValue(); + e); + // TODO(chengyuanzhang): better error handling. + throw new AssertionError(e); } - } - } - if (cluster.getTransportSocketMatchesCount() > 0) { - return StructOrError.fromError("Cluster " + clusterName - + ": transport-socket-matches not supported."); - } - if (cluster.hasTransportSocket()) { - if (!TRANSPORT_SOCKET_NAME_TLS.equals(cluster.getTransportSocket().getName())) { - return StructOrError.fromError("transport-socket with name " - + cluster.getTransportSocket().getName() + " not supported."); - } - try { - upstreamTlsContext = UpstreamTlsContext.fromEnvoyProtoUpstreamTlsContext( - validateUpstreamTlsContext( - unpackCompatibleType(cluster.getTransportSocket().getTypedConfig(), - io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext.class, - TYPE_URL_UPSTREAM_TLS_CONTEXT, TYPE_URL_UPSTREAM_TLS_CONTEXT_V2), - certProviderInstances)); - } catch (InvalidProtocolBufferException | ResourceInvalidException e) { - return StructOrError.fromError( - "Cluster " + clusterName + ": malformed UpstreamTlsContext: " + e); - } - } - if (cluster.hasOutlierDetection() && enableOutlierDetection) { - try { - outlierDetection = OutlierDetection.fromEnvoyOutlierDetection( - validateOutlierDetection(cluster.getOutlierDetection())); - } catch (ResourceInvalidException e) { - return StructOrError.fromError( - "Cluster " + clusterName + ": malformed outlier_detection: " + e); - } - } - + }); + private final FilterRegistry filterRegistry = FilterRegistry.getDefaultRegistry(); + private final LoadBalancerRegistry loadBalancerRegistry + = LoadBalancerRegistry.getDefaultRegistry(); + private final Map serverChannelMap = new HashMap<>(); + private final Map, + Map>> + resourceSubscribers = new HashMap<>(); + private final Map> xdsResourceTypeMap = + ImmutableMap.of( + LDS, XdsListenerResource.getInstance(), + RDS, XdsRouteConfigureResource.getInstance(), + CDS, XdsClusterResource.getInstance(), + EDS, XdsEndpointResource.getInstance()); + private final LoadStatsManager2 loadStatsManager; + private final Map serverLrsClientMap = new HashMap<>(); + private final XdsChannelFactory xdsChannelFactory; + private final Bootstrapper.BootstrapInfo bootstrapInfo; + private final Context context; + private final ScheduledExecutorService timeService; + private final BackoffPolicy.Provider backoffPolicyProvider; + private final Supplier stopwatchSupplier; + private final TimeProvider timeProvider; + private boolean reportingLoad; + private final TlsContextManager tlsContextManager; + private final InternalLogId logId; + private final XdsLogger logger; + private volatile boolean isShutdown; - DiscoveryType type = cluster.getType(); - if (type == DiscoveryType.EDS) { - String edsServiceName = null; - io.envoyproxy.envoy.config.cluster.v3.Cluster.EdsClusterConfig edsClusterConfig = - cluster.getEdsClusterConfig(); - if (!edsClusterConfig.getEdsConfig().hasAds() - && ! edsClusterConfig.getEdsConfig().hasSelf()) { - return StructOrError.fromError( - "Cluster " + clusterName + ": field eds_cluster_config must be set to indicate to use" - + " EDS over ADS or self ConfigSource"); - } - // If the service_name field is set, that value will be used for the EDS request. - if (!edsClusterConfig.getServiceName().isEmpty()) { - edsServiceName = edsClusterConfig.getServiceName(); - edsResources.add(edsServiceName); - } else { - edsResources.add(clusterName); - } - return StructOrError.fromStruct(CdsUpdate.forEds( - clusterName, edsServiceName, lrsServerInfo, maxConcurrentRequests, upstreamTlsContext, - outlierDetection)); - } else if (type.equals(DiscoveryType.LOGICAL_DNS)) { - if (!cluster.hasLoadAssignment()) { - return StructOrError.fromError( - "Cluster " + clusterName + ": LOGICAL_DNS clusters must have a single host"); - } - ClusterLoadAssignment assignment = cluster.getLoadAssignment(); - if (assignment.getEndpointsCount() != 1 - || assignment.getEndpoints(0).getLbEndpointsCount() != 1) { - return StructOrError.fromError( - "Cluster " + clusterName + ": LOGICAL_DNS clusters must have a single " - + "locality_lb_endpoint and a single lb_endpoint"); - } - io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint lbEndpoint = - assignment.getEndpoints(0).getLbEndpoints(0); - if (!lbEndpoint.hasEndpoint() || !lbEndpoint.getEndpoint().hasAddress() - || !lbEndpoint.getEndpoint().getAddress().hasSocketAddress()) { - return StructOrError.fromError( - "Cluster " + clusterName - + ": LOGICAL_DNS clusters must have an endpoint with address and socket_address"); - } - SocketAddress socketAddress = lbEndpoint.getEndpoint().getAddress().getSocketAddress(); - if (!socketAddress.getResolverName().isEmpty()) { - return StructOrError.fromError( - "Cluster " + clusterName - + ": LOGICAL DNS clusters must NOT have a custom resolver name set"); - } - if (socketAddress.getPortSpecifierCase() != PortSpecifierCase.PORT_VALUE) { - return StructOrError.fromError( - "Cluster " + clusterName - + ": LOGICAL DNS clusters socket_address must have port_value"); - } - String dnsHostName = String.format( - Locale.US, "%s:%d", socketAddress.getAddress(), socketAddress.getPortValue()); - return StructOrError.fromStruct(CdsUpdate.forLogicalDns( - clusterName, dnsHostName, lrsServerInfo, maxConcurrentRequests, upstreamTlsContext)); - } - return StructOrError.fromError( - "Cluster " + clusterName + ": unsupported built-in discovery type: " + type); + // TODO(zdapeng): rename to XdsClientImpl + ClientXdsClient( + XdsChannelFactory xdsChannelFactory, + Bootstrapper.BootstrapInfo bootstrapInfo, + Context context, + ScheduledExecutorService timeService, + BackoffPolicy.Provider backoffPolicyProvider, + Supplier stopwatchSupplier, + TimeProvider timeProvider, + TlsContextManager tlsContextManager) { + this.xdsChannelFactory = xdsChannelFactory; + this.bootstrapInfo = bootstrapInfo; + this.context = context; + this.timeService = timeService; + loadStatsManager = new LoadStatsManager2(stopwatchSupplier); + this.backoffPolicyProvider = backoffPolicyProvider; + this.stopwatchSupplier = stopwatchSupplier; + this.timeProvider = timeProvider; + this.tlsContextManager = checkNotNull(tlsContextManager, "tlsContextManager"); + logId = InternalLogId.allocate("xds-client", null); + logger = XdsLogger.withLogId(logId); + logger.log(XdsLogLevel.INFO, "Created"); } - @Override - public void handleEdsResponse( - ServerInfo serverInfo, String versionInfo, List resources, String nonce) { + private void maybeCreateXdsChannelWithLrs(ServerInfo serverInfo) { syncContext.throwIfNotInThisSynchronizationContext(); - Map parsedResources = new HashMap<>(resources.size()); - Set unpackedResources = new HashSet<>(resources.size()); - Set invalidResources = new HashSet<>(); - List errors = new ArrayList<>(); - - for (int i = 0; i < resources.size(); i++) { - Any resource = resources.get(i); - - // Unpack the ClusterLoadAssignment. - ClusterLoadAssignment assignment; - try { - resource = maybeUnwrapResources(resource); - assignment = - unpackCompatibleType(resource, ClusterLoadAssignment.class, ResourceType.EDS.typeUrl(), - ResourceType.EDS.typeUrlV2()); - } catch (InvalidProtocolBufferException e) { - errors.add( - "EDS response Resource index " + i + " - can't decode ClusterLoadAssignment: " + e); - continue; - } - if (!isResourceNameValid(assignment.getClusterName(), resource.getTypeUrl())) { - errors.add("Unsupported resource name: " + assignment.getClusterName() + " for type: " - + ResourceType.EDS); - continue; - } - String clusterName = canonifyResourceName(assignment.getClusterName()); - - // Skip information for clusters not requested. - // Management server is required to always send newly requested resources, even if they - // may have been sent previously (proactively). Thus, client does not need to cache - // unrequested resources. - if (!edsResourceSubscribers.containsKey(clusterName)) { - continue; - } - unpackedResources.add(clusterName); - - // Process ClusterLoadAssignment into EdsUpdate. - EdsUpdate edsUpdate; - try { - edsUpdate = processClusterLoadAssignment(assignment); - } catch (ResourceInvalidException e) { - errors.add("EDS response ClusterLoadAssignment '" + clusterName - + "' validation error: " + e.getMessage()); - invalidResources.add(clusterName); - continue; - } - parsedResources.put(clusterName, new ParsedResource(edsUpdate, resource)); - } - logger.log( - XdsLogLevel.INFO, "Received EDS Response version {0} nonce {1}. Parsed resources: {2}", - versionInfo, nonce, unpackedResources); - handleResourceUpdate( - serverInfo, ResourceType.EDS, parsedResources, invalidResources, - Collections.emptySet(), versionInfo, nonce, errors); - } - - private static EdsUpdate processClusterLoadAssignment(ClusterLoadAssignment assignment) - throws ResourceInvalidException { - Map> priorities = new HashMap<>(); - Map localityLbEndpointsMap = new LinkedHashMap<>(); - List dropOverloads = new ArrayList<>(); - int maxPriority = -1; - for (io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints localityLbEndpointsProto - : assignment.getEndpointsList()) { - StructOrError structOrError = - parseLocalityLbEndpoints(localityLbEndpointsProto); - if (structOrError == null) { - continue; - } - if (structOrError.getErrorDetail() != null) { - throw new ResourceInvalidException(structOrError.getErrorDetail()); - } - - LocalityLbEndpoints localityLbEndpoints = structOrError.getStruct(); - int priority = localityLbEndpoints.priority(); - maxPriority = Math.max(maxPriority, priority); - // Note endpoints with health status other than HEALTHY and UNKNOWN are still - // handed over to watching parties. It is watching parties' responsibility to - // filter out unhealthy endpoints. See EnvoyProtoData.LbEndpoint#isHealthy(). - Locality locality = parseLocality(localityLbEndpointsProto.getLocality()); - localityLbEndpointsMap.put(locality, localityLbEndpoints); - if (!priorities.containsKey(priority)) { - priorities.put(priority, new HashSet<>()); - } - if (!priorities.get(priority).add(locality)) { - throw new ResourceInvalidException("ClusterLoadAssignment has duplicate locality:" - + locality + " for priority:" + priority); - } - } - if (priorities.size() != maxPriority + 1) { - throw new ResourceInvalidException("ClusterLoadAssignment has sparse priorities"); - } - - for (ClusterLoadAssignment.Policy.DropOverload dropOverloadProto - : assignment.getPolicy().getDropOverloadsList()) { - dropOverloads.add(parseDropOverload(dropOverloadProto)); - } - return new EdsUpdate(assignment.getClusterName(), localityLbEndpointsMap, dropOverloads); - } - - private static Locality parseLocality(io.envoyproxy.envoy.config.core.v3.Locality proto) { - return Locality.create(proto.getRegion(), proto.getZone(), proto.getSubZone()); - } - - private static DropOverload parseDropOverload( - io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment.Policy.DropOverload proto) { - return DropOverload.create(proto.getCategory(), getRatePerMillion(proto.getDropPercentage())); - } - - @VisibleForTesting - @Nullable - static StructOrError parseLocalityLbEndpoints( - io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints proto) { - // Filter out localities without or with 0 weight. - if (!proto.hasLoadBalancingWeight() || proto.getLoadBalancingWeight().getValue() < 1) { - return null; - } - if (proto.getPriority() < 0) { - return StructOrError.fromError("negative priority"); - } - List endpoints = new ArrayList<>(proto.getLbEndpointsCount()); - for (io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint endpoint : proto.getLbEndpointsList()) { - // The endpoint field of each lb_endpoints must be set. - // Inside of it: the address field must be set. - if (!endpoint.hasEndpoint() || !endpoint.getEndpoint().hasAddress()) { - return StructOrError.fromError("LbEndpoint with no endpoint/address"); - } - io.envoyproxy.envoy.config.core.v3.SocketAddress socketAddress = - endpoint.getEndpoint().getAddress().getSocketAddress(); - InetSocketAddress addr = - new InetSocketAddress(socketAddress.getAddress(), socketAddress.getPortValue()); - boolean isHealthy = - endpoint.getHealthStatus() == io.envoyproxy.envoy.config.core.v3.HealthStatus.HEALTHY - || endpoint.getHealthStatus() - == io.envoyproxy.envoy.config.core.v3.HealthStatus.UNKNOWN; - endpoints.add(LbEndpoint.create( - new EquivalentAddressGroup(ImmutableList.of(addr)), - endpoint.getLoadBalancingWeight().getValue(), isHealthy)); - } - return StructOrError.fromStruct(LocalityLbEndpoints.create( - endpoints, proto.getLoadBalancingWeight().getValue(), proto.getPriority())); - } - - /** - * Helper method to unpack serialized {@link com.google.protobuf.Any} message, while replacing - * Type URL {@code compatibleTypeUrl} with {@code typeUrl}. - * - * @param The type of unpacked message - * @param any serialized message to unpack - * @param clazz the class to unpack the message to - * @param typeUrl type URL to replace message Type URL, when it's compatible - * @param compatibleTypeUrl compatible Type URL to be replaced with {@code typeUrl} - * @return Unpacked message - * @throws InvalidProtocolBufferException if the message couldn't be unpacked - */ - private static T unpackCompatibleType( - Any any, Class clazz, String typeUrl, String compatibleTypeUrl) - throws InvalidProtocolBufferException { - if (any.getTypeUrl().equals(compatibleTypeUrl)) { - any = any.toBuilder().setTypeUrl(typeUrl).build(); + if (serverChannelMap.containsKey(serverInfo)) { + return; } - return any.unpack(clazz); + AbstractXdsClient xdsChannel = new AbstractXdsClient( + xdsChannelFactory, + serverInfo, + bootstrapInfo.node(), + this, + this, + context, + timeService, + syncContext, + backoffPolicyProvider, + stopwatchSupplier); + LoadReportClient lrsClient = new LoadReportClient( + loadStatsManager, xdsChannel.channel(), context, serverInfo.useProtocolV3(), + bootstrapInfo.node(), syncContext, timeService, backoffPolicyProvider, stopwatchSupplier); + serverChannelMap.put(serverInfo, xdsChannel); + serverLrsClientMap.put(serverInfo, lrsClient); } - private static int getRatePerMillion(FractionalPercent percent) { - int numerator = percent.getNumerator(); - DenominatorType type = percent.getDenominator(); - switch (type) { - case TEN_THOUSAND: - numerator *= 100; - break; - case HUNDRED: - numerator *= 10_000; - break; - case MILLION: - break; - case UNRECOGNIZED: - default: - throw new IllegalArgumentException("Unknown denominator type of " + percent); - } - - if (numerator > 1_000_000 || numerator < 0) { - numerator = 1_000_000; + @Override + public void handleResourceResponse( + ResourceType resourceType, ServerInfo serverInfo, String versionInfo, List resources, + String nonce) { + syncContext.throwIfNotInThisSynchronizationContext(); + XdsResourceType xdsResourceType = + xdsResourceTypeMap.get(resourceType); + if (xdsResourceType == null) { + logger.log(XdsLogLevel.WARNING, "Ignore an unknown type of DiscoveryResponse"); + return; } - return numerator; + Set toParseResourceNames = (resourceType == LDS || resourceType == RDS ) ? null : + resourceSubscribers.get(xdsResourceType).keySet(); + XdsResourceType.Args args = new XdsResourceType.Args(serverInfo, versionInfo, nonce, + bootstrapInfo, filterRegistry, loadBalancerRegistry, tlsContextManager, + toParseResourceNames); + handleResourceUpdate(args, resources, xdsResourceType); } @Override public void handleStreamClosed(Status error) { syncContext.throwIfNotInThisSynchronizationContext(); cleanUpResourceTimers(); - for (ResourceSubscriber subscriber : ldsResourceSubscribers.values()) { - subscriber.onError(error); - } - for (ResourceSubscriber subscriber : rdsResourceSubscribers.values()) { - subscriber.onError(error); - } - for (ResourceSubscriber subscriber : cdsResourceSubscribers.values()) { - subscriber.onError(error); - } - for (ResourceSubscriber subscriber : edsResourceSubscribers.values()) { - subscriber.onError(error); + for (Map> subscriberMap : + resourceSubscribers.values()) { + for (ResourceSubscriber subscriber : subscriberMap.values()) { + subscriber.onError(error); + } } } @Override public void handleStreamRestarted(ServerInfo serverInfo) { syncContext.throwIfNotInThisSynchronizationContext(); - for (ResourceSubscriber subscriber : ldsResourceSubscribers.values()) { - if (subscriber.serverInfo.equals(serverInfo)) { - subscriber.restartTimer(); - } - } - for (ResourceSubscriber subscriber : rdsResourceSubscribers.values()) { - if (subscriber.serverInfo.equals(serverInfo)) { - subscriber.restartTimer(); - } - } - for (ResourceSubscriber subscriber : cdsResourceSubscribers.values()) { - if (subscriber.serverInfo.equals(serverInfo)) { - subscriber.restartTimer(); - } - } - for (ResourceSubscriber subscriber : edsResourceSubscribers.values()) { - if (subscriber.serverInfo.equals(serverInfo)) { - subscriber.restartTimer(); + for (Map> subscriberMap : + resourceSubscribers.values()) { + for (ResourceSubscriber subscriber : subscriberMap.values()) { + if (subscriber.serverInfo.equals(serverInfo)) { + subscriber.restartTimer(); + } } } } @@ -2142,26 +236,23 @@ boolean isShutDown() { return isShutdown; } - private Map getSubscribedResourcesMap(ResourceType type) { - switch (type) { - case LDS: - return ldsResourceSubscribers; - case RDS: - return rdsResourceSubscribers; - case CDS: - return cdsResourceSubscribers; - case EDS: - return edsResourceSubscribers; - case UNKNOWN: - default: - throw new AssertionError("Unknown resource type"); - } + private Map> getSubscribedResourcesMap( + ResourceType type) { + return resourceSubscribers.getOrDefault(xdsResourceTypeMap.get(type), Collections.emptyMap()); + } + + @Nullable + @Override + public XdsResourceType getXdsResourceType(ResourceType type) { + return xdsResourceTypeMap.get(type); } @Nullable @Override - public Collection getSubscribedResources(ServerInfo serverInfo, ResourceType type) { - Map resources = getSubscribedResourcesMap(type); + public Collection getSubscribedResources(ServerInfo serverInfo, + ResourceType type) { + Map> resources = + getSubscribedResourcesMap(type); ImmutableSet.Builder builder = ImmutableSet.builder(); for (String key : resources.keySet()) { if (resources.get(key).serverInfo.equals(serverInfo)) { @@ -2183,16 +274,15 @@ public void run() { // A map from a "resource type" to a map ("resource name": "resource metadata") ImmutableMap.Builder> metadataSnapshot = ImmutableMap.builder(); - for (ResourceType type : ResourceType.values()) { - if (type == ResourceType.UNKNOWN) { - continue; - } + for (XdsResourceType resourceType: xdsResourceTypeMap.values()) { ImmutableMap.Builder metadataMap = ImmutableMap.builder(); - for (Map.Entry resourceEntry - : getSubscribedResourcesMap(type).entrySet()) { + Map> resourceSubscriberMap = + resourceSubscribers.getOrDefault(resourceType, Collections.emptyMap()); + for (Map.Entry> resourceEntry + : resourceSubscriberMap.entrySet()) { metadataMap.put(resourceEntry.getKey(), resourceEntry.getValue().metadata); } - metadataSnapshot.put(type, metadataMap.buildOrThrow()); + metadataSnapshot.put(resourceType.typeName(), metadataMap.buildOrThrow()); } future.set(metadataSnapshot.buildOrThrow()); } @@ -2206,91 +296,23 @@ TlsContextManager getTlsContextManager() { } @Override - void watchLdsResource(final String resourceName, final LdsResourceWatcher watcher) { - syncContext.execute(new Runnable() { - @Override - public void run() { - ResourceSubscriber subscriber = ldsResourceSubscribers.get(resourceName); - if (subscriber == null) { - logger.log(XdsLogLevel.INFO, "Subscribe LDS resource {0}", resourceName); - subscriber = new ResourceSubscriber(ResourceType.LDS, resourceName); - ldsResourceSubscribers.put(resourceName, subscriber); - if (subscriber.xdsChannel != null) { - subscriber.xdsChannel.adjustResourceSubscription(ResourceType.LDS); - } - } - subscriber.addWatcher(watcher); - } - }); - } - - @Override - void cancelLdsResourceWatch(final String resourceName, final LdsResourceWatcher watcher) { - syncContext.execute(new Runnable() { - @Override - public void run() { - ResourceSubscriber subscriber = ldsResourceSubscribers.get(resourceName); - subscriber.removeWatcher(watcher); - if (!subscriber.isWatched()) { - subscriber.cancelResourceWatch(); - ldsResourceSubscribers.remove(resourceName); - if (subscriber.xdsChannel != null) { - subscriber.xdsChannel.adjustResourceSubscription(ResourceType.LDS); - } - } - } - }); - } - - @Override - void watchRdsResource(final String resourceName, final RdsResourceWatcher watcher) { - syncContext.execute(new Runnable() { - @Override - public void run() { - ResourceSubscriber subscriber = rdsResourceSubscribers.get(resourceName); - if (subscriber == null) { - logger.log(XdsLogLevel.INFO, "Subscribe RDS resource {0}", resourceName); - subscriber = new ResourceSubscriber(ResourceType.RDS, resourceName); - rdsResourceSubscribers.put(resourceName, subscriber); - if (subscriber.xdsChannel != null) { - subscriber.xdsChannel.adjustResourceSubscription(ResourceType.RDS); - } - } - subscriber.addWatcher(watcher); - } - }); - } - - @Override - void cancelRdsResourceWatch(final String resourceName, final RdsResourceWatcher watcher) { + void watchXdsResource(XdsResourceType type, String resourceName, + ResourceWatcher watcher) { syncContext.execute(new Runnable() { @Override + @SuppressWarnings("unchecked") public void run() { - ResourceSubscriber subscriber = rdsResourceSubscribers.get(resourceName); - subscriber.removeWatcher(watcher); - if (!subscriber.isWatched()) { - subscriber.cancelResourceWatch(); - rdsResourceSubscribers.remove(resourceName); - if (subscriber.xdsChannel != null) { - subscriber.xdsChannel.adjustResourceSubscription(ResourceType.RDS); - } + if (!resourceSubscribers.containsKey(type)) { + resourceSubscribers.put(type, new HashMap<>()); } - } - }); - } - - @Override - void watchCdsResource(final String resourceName, final CdsResourceWatcher watcher) { - syncContext.execute(new Runnable() { - @Override - public void run() { - ResourceSubscriber subscriber = cdsResourceSubscribers.get(resourceName); + ResourceSubscriber subscriber = + (ResourceSubscriber) resourceSubscribers.get(type).get(resourceName);; if (subscriber == null) { - logger.log(XdsLogLevel.INFO, "Subscribe CDS resource {0}", resourceName); - subscriber = new ResourceSubscriber(ResourceType.CDS, resourceName); - cdsResourceSubscribers.put(resourceName, subscriber); + logger.log(XdsLogLevel.INFO, "Subscribe {0} resource {1}", type, resourceName); + subscriber = new ResourceSubscriber<>(type.typeName(), resourceName); + resourceSubscribers.get(type).put(resourceName, subscriber); if (subscriber.xdsChannel != null) { - subscriber.xdsChannel.adjustResourceSubscription(ResourceType.CDS); + subscriber.xdsChannel.adjustResourceSubscription(type); } } subscriber.addWatcher(watcher); @@ -2299,54 +321,24 @@ public void run() { } @Override - void cancelCdsResourceWatch(final String resourceName, final CdsResourceWatcher watcher) { + void cancelXdsResourceWatch(XdsResourceType type, + String resourceName, + ResourceWatcher watcher) { syncContext.execute(new Runnable() { @Override + @SuppressWarnings("unchecked") public void run() { - ResourceSubscriber subscriber = cdsResourceSubscribers.get(resourceName); + ResourceSubscriber subscriber = + (ResourceSubscriber) resourceSubscribers.get(type).get(resourceName);; subscriber.removeWatcher(watcher); if (!subscriber.isWatched()) { subscriber.cancelResourceWatch(); - cdsResourceSubscribers.remove(resourceName); - if (subscriber.xdsChannel != null) { - subscriber.xdsChannel.adjustResourceSubscription(ResourceType.CDS); - } - } - } - }); - } - - @Override - void watchEdsResource(final String resourceName, final EdsResourceWatcher watcher) { - syncContext.execute(new Runnable() { - @Override - public void run() { - ResourceSubscriber subscriber = edsResourceSubscribers.get(resourceName); - if (subscriber == null) { - logger.log(XdsLogLevel.INFO, "Subscribe EDS resource {0}", resourceName); - subscriber = new ResourceSubscriber(ResourceType.EDS, resourceName); - edsResourceSubscribers.put(resourceName, subscriber); + resourceSubscribers.get(type).remove(resourceName); if (subscriber.xdsChannel != null) { - subscriber.xdsChannel.adjustResourceSubscription(ResourceType.EDS); + subscriber.xdsChannel.adjustResourceSubscription(type); } - } - subscriber.addWatcher(watcher); - } - }); - } - - @Override - void cancelEdsResourceWatch(final String resourceName, final EdsResourceWatcher watcher) { - syncContext.execute(new Runnable() { - @Override - public void run() { - ResourceSubscriber subscriber = edsResourceSubscribers.get(resourceName); - subscriber.removeWatcher(watcher); - if (!subscriber.isWatched()) { - subscriber.cancelResourceWatch(); - edsResourceSubscribers.remove(resourceName); - if (subscriber.xdsChannel != null) { - subscriber.xdsChannel.adjustResourceSubscription(ResourceType.EDS); + if (resourceSubscribers.get(type).isEmpty()) { + resourceSubscribers.remove(type); } } } @@ -2399,54 +391,57 @@ public String toString() { } private void cleanUpResourceTimers() { - for (ResourceSubscriber subscriber : ldsResourceSubscribers.values()) { - subscriber.stopTimer(); - } - for (ResourceSubscriber subscriber : rdsResourceSubscribers.values()) { - subscriber.stopTimer(); - } - for (ResourceSubscriber subscriber : cdsResourceSubscribers.values()) { - subscriber.stopTimer(); - } - for (ResourceSubscriber subscriber : edsResourceSubscribers.values()) { - subscriber.stopTimer(); + for (Map> subscriberMap : resourceSubscribers.values()) { + for (ResourceSubscriber subscriber : subscriberMap.values()) { + subscriber.stopTimer(); + } } } - private void handleResourceUpdate( - ServerInfo serverInfo, ResourceType type, Map parsedResources, - Set invalidResources, Set retainedResources, String version, String nonce, - List errors) { + @SuppressWarnings("unchecked") + private void handleResourceUpdate(XdsResourceType.Args args, + List resources, + XdsResourceType xdsResourceType) { + ValidatedResourceUpdate result = xdsResourceType.parse(args, resources); + logger.log(XdsLogger.XdsLogLevel.INFO, + "Received {0} Response version {1} nonce {2}. Parsed resources: {3}", + xdsResourceType.typeName(), args.versionInfo, args.nonce, result.unpackedResources); + Map> parsedResources = result.parsedResources; + Set invalidResources = result.invalidResources; + Set retainedResources = result.retainedResources; + List errors = result.errors; String errorDetail = null; if (errors.isEmpty()) { checkArgument(invalidResources.isEmpty(), "found invalid resources but missing errors"); - serverChannelMap.get(serverInfo).ackResponse(type, version, nonce); + serverChannelMap.get(args.serverInfo).ackResponse(xdsResourceType, args.versionInfo, + args.nonce); } else { errorDetail = Joiner.on('\n').join(errors); logger.log(XdsLogLevel.WARNING, "Failed processing {0} Response version {1} nonce {2}. Errors:\n{3}", - type, version, nonce, errorDetail); - serverChannelMap.get(serverInfo).nackResponse(type, nonce, errorDetail); + xdsResourceType.typeName(), args.versionInfo, args.nonce, errorDetail); + serverChannelMap.get(args.serverInfo).nackResponse(xdsResourceType, args.nonce, errorDetail); } long updateTime = timeProvider.currentTimeNanos(); - for (Map.Entry entry : getSubscribedResourcesMap(type).entrySet()) { + for (Map.Entry> entry : + getSubscribedResourcesMap(xdsResourceType.typeName()).entrySet()) { String resourceName = entry.getKey(); - ResourceSubscriber subscriber = entry.getValue(); + ResourceSubscriber subscriber = (ResourceSubscriber) entry.getValue(); if (parsedResources.containsKey(resourceName)) { // Happy path: the resource updated successfully. Notify the watchers of the update. - subscriber.onData(parsedResources.get(resourceName), version, updateTime); + subscriber.onData(parsedResources.get(resourceName), args.versionInfo, updateTime); continue; } if (invalidResources.contains(resourceName)) { // The resource update is invalid. Capture the error without notifying the watchers. - subscriber.onRejected(version, updateTime, errorDetail); + subscriber.onRejected(args.versionInfo, updateTime, errorDetail); } // Nothing else to do for incremental ADS resources. - if (type != ResourceType.LDS && type != ResourceType.CDS) { + if (xdsResourceType.dependentResource() == null) { continue; } @@ -2474,9 +469,13 @@ private void handleResourceUpdate( // LDS/CDS responses represents the state of the world, RDS/EDS resources not referenced in // LDS/CDS resources should be deleted. - if (type == ResourceType.LDS || type == ResourceType.CDS) { - Map dependentSubscribers = - type == ResourceType.LDS ? rdsResourceSubscribers : edsResourceSubscribers; + if (xdsResourceType.dependentResource() != null) { + XdsResourceType dependency = xdsResourceTypeMap.get(xdsResourceType.dependentResource()); + Map> dependentSubscribers = + resourceSubscribers.get(dependency); + if (dependentSubscribers == null) { + return; + } for (String resource : dependentSubscribers.keySet()) { if (!retainedResources.contains(resource)) { dependentSubscribers.get(resource).onAbsent(); @@ -2486,18 +485,18 @@ private void handleResourceUpdate( } private void retainDependentResource( - ResourceSubscriber subscriber, Set retainedResources) { + ResourceSubscriber subscriber, Set retainedResources) { if (subscriber.data == null) { return; } String resourceName = null; - if (subscriber.type == ResourceType.LDS) { + if (subscriber.type == LDS) { LdsUpdate ldsUpdate = (LdsUpdate) subscriber.data; io.grpc.xds.HttpConnectionManager hcm = ldsUpdate.httpConnectionManager(); if (hcm != null) { resourceName = hcm.rdsName(); } - } else if (subscriber.type == ResourceType.CDS) { + } else if (subscriber.type == CDS) { CdsUpdate cdsUpdate = (CdsUpdate) subscriber.data; resourceName = cdsUpdate.edsServiceName(); } @@ -2507,34 +506,16 @@ private void retainDependentResource( } } - private static final class ParsedResource { - private final ResourceUpdate resourceUpdate; - private final Any rawResource; - - private ParsedResource(ResourceUpdate resourceUpdate, Any rawResource) { - this.resourceUpdate = checkNotNull(resourceUpdate, "resourceUpdate"); - this.rawResource = checkNotNull(rawResource, "rawResource"); - } - - private ResourceUpdate getResourceUpdate() { - return resourceUpdate; - } - - private Any getRawResource() { - return rawResource; - } - } - /** * Tracks a single subscribed resource. */ - private final class ResourceSubscriber { + private final class ResourceSubscriber { @Nullable private final ServerInfo serverInfo; @Nullable private final AbstractXdsClient xdsChannel; private final ResourceType type; private final String resource; - private final Set watchers = new HashSet<>(); - @Nullable private ResourceUpdate data; + private final Set> watchers = new HashSet<>(); + @Nullable private T data; private boolean absent; // Tracks whether the deletion has been ignored per bootstrap server feature. // See https://github.com/grpc/proposal/blob/master/A53-xds-ignore-resource-deletion.md @@ -2582,7 +563,7 @@ private ServerInfo getServerInfo(String resource) { return bootstrapInfo.servers().get(0); // use first server } - void addWatcher(ResourceWatcher watcher) { + void addWatcher(ResourceWatcher watcher) { checkArgument(!watchers.contains(watcher), "watcher %s already registered", watcher); watchers.add(watcher); if (errorDescription != null) { @@ -2596,7 +577,7 @@ void addWatcher(ResourceWatcher watcher) { } } - void removeWatcher(ResourceWatcher watcher) { + void removeWatcher(ResourceWatcher watcher) { checkArgument(watchers.contains(watcher), "watcher %s not registered", watcher); watchers.remove(watcher); } @@ -2653,7 +634,7 @@ boolean isWatched() { return !watchers.isEmpty(); } - void onData(ParsedResource parsedResource, String version, long updateTime) { + void onData(ParsedResource parsedResource, String version, long updateTime) { if (respTimer != null && respTimer.isPending()) { respTimer.cancel(); respTimer = null; @@ -2670,7 +651,7 @@ void onData(ParsedResource parsedResource, String version, long updateTime) { resourceDeletionIgnored = false; } if (!Objects.equals(oldData, data)) { - for (ResourceWatcher watcher : watchers) { + for (ResourceWatcher watcher : watchers) { notifyWatcher(watcher, data); } } @@ -2685,7 +666,7 @@ void onAbsent() { // and the resource is reusable. boolean ignoreResourceDeletionEnabled = serverInfo != null && serverInfo.ignoreResourceDeletion(); - boolean isStateOfTheWorld = (type == ResourceType.LDS || type == ResourceType.CDS); + boolean isStateOfTheWorld = (type == LDS || type == CDS); if (ignoreResourceDeletionEnabled && isStateOfTheWorld && data != null) { if (!resourceDeletionIgnored) { logger.log(XdsLogLevel.FORCE_WARNING, @@ -2701,7 +682,7 @@ void onAbsent() { data = null; absent = true; metadata = ResourceMetadata.newResourceMetadataDoesNotExist(); - for (ResourceWatcher watcher : watchers) { + for (ResourceWatcher watcher : watchers) { watcher.onResourceDoesNotExist(resource); } } @@ -2720,7 +701,7 @@ void onError(Status error) { .withDescription(description + "nodeID: " + bootstrapInfo.node().getId()) .withCause(error.getCause()); - for (ResourceWatcher watcher : watchers) { + for (ResourceWatcher watcher : watchers) { watcher.onError(errorAugmented); } } @@ -2730,24 +711,8 @@ void onRejected(String rejectedVersion, long rejectedTime, String rejectedDetail .newResourceMetadataNacked(metadata, rejectedVersion, rejectedTime, rejectedDetails); } - private void notifyWatcher(ResourceWatcher watcher, ResourceUpdate update) { - switch (type) { - case LDS: - ((LdsResourceWatcher) watcher).onChanged((LdsUpdate) update); - break; - case RDS: - ((RdsResourceWatcher) watcher).onChanged((RdsUpdate) update); - break; - case CDS: - ((CdsResourceWatcher) watcher).onChanged((CdsUpdate) update); - break; - case EDS: - ((EdsResourceWatcher) watcher).onChanged((EdsUpdate) update); - break; - case UNKNOWN: - default: - throw new AssertionError("should never be here"); - } + private void notifyWatcher(ResourceWatcher watcher, T update) { + watcher.onChanged(update); } } @@ -2764,55 +729,6 @@ static final class ResourceInvalidException extends Exception { } } - @VisibleForTesting - static final class StructOrError { - - /** - * Returns a {@link StructOrError} for the successfully converted data object. - */ - private static StructOrError fromStruct(T struct) { - return new StructOrError<>(struct); - } - - /** - * Returns a {@link StructOrError} for the failure to convert the data object. - */ - private static StructOrError fromError(String errorDetail) { - return new StructOrError<>(errorDetail); - } - - private final String errorDetail; - private final T struct; - - private StructOrError(T struct) { - this.struct = checkNotNull(struct, "struct"); - this.errorDetail = null; - } - - private StructOrError(String errorDetail) { - this.struct = null; - this.errorDetail = checkNotNull(errorDetail, "errorDetail"); - } - - /** - * Returns struct if exists, otherwise null. - */ - @VisibleForTesting - @Nullable - T getStruct() { - return struct; - } - - /** - * Returns error detail if exists, otherwise null. - */ - @VisibleForTesting - @Nullable - String getErrorDetail() { - return errorDetail; - } - } - abstract static class XdsChannelFactory { static final XdsChannelFactory DEFAULT_XDS_CHANNEL_FACTORY = new XdsChannelFactory() { @Override diff --git a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java index 96e2cf441a0..f4aaf9426bc 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java @@ -52,8 +52,8 @@ import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig; import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig.PriorityChildConfig; -import io.grpc.xds.XdsClient.EdsResourceWatcher; -import io.grpc.xds.XdsClient.EdsUpdate; +import io.grpc.xds.XdsClient.ResourceWatcher; +import io.grpc.xds.XdsEndpointResource.EdsUpdate; import io.grpc.xds.XdsLogger.XdsLogLevel; import io.grpc.xds.XdsSubchannelPickers.ErrorPicker; import java.net.URI; @@ -350,7 +350,7 @@ void shutdown() { } } - private final class EdsClusterState extends ClusterState implements EdsResourceWatcher { + private final class EdsClusterState extends ClusterState implements ResourceWatcher { @Nullable private final String edsServiceName; private Map localityPriorityNames = Collections.emptyMap(); @@ -367,7 +367,7 @@ private EdsClusterState(String name, @Nullable String edsServiceName, void start() { String resourceName = edsServiceName != null ? edsServiceName : name; logger.log(XdsLogLevel.INFO, "Start watching EDS resource {0}", resourceName); - xdsClient.watchEdsResource(resourceName, this); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(), resourceName, this); } @Override @@ -375,7 +375,7 @@ protected void shutdown() { super.shutdown(); String resourceName = edsServiceName != null ? edsServiceName : name; logger.log(XdsLogLevel.INFO, "Stop watching EDS resource {0}", resourceName); - xdsClient.cancelEdsResourceWatch(resourceName, this); + xdsClient.cancelXdsResourceWatch(XdsEndpointResource.getInstance(), resourceName, this); } @Override diff --git a/xds/src/main/java/io/grpc/xds/XdsClient.java b/xds/src/main/java/io/grpc/xds/XdsClient.java index 028ae155ba6..feb3afa3e98 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClient.java +++ b/xds/src/main/java/io/grpc/xds/XdsClient.java @@ -19,23 +19,14 @@ import static com.google.common.base.Preconditions.checkNotNull; import static io.grpc.xds.Bootstrapper.XDSTP_SCHEME; -import com.google.auto.value.AutoValue; import com.google.common.base.Joiner; -import com.google.common.base.MoreObjects; import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.common.net.UrlEscapers; import com.google.common.util.concurrent.ListenableFuture; import com.google.protobuf.Any; import io.grpc.Status; import io.grpc.xds.AbstractXdsClient.ResourceType; import io.grpc.xds.Bootstrapper.ServerInfo; -import io.grpc.xds.Endpoints.DropOverload; -import io.grpc.xds.Endpoints.LocalityLbEndpoints; -import io.grpc.xds.EnvoyServerProtoData.Listener; -import io.grpc.xds.EnvoyServerProtoData.OutlierDetection; -import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.LoadStatsManager2.ClusterDropStats; import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats; import java.net.URI; @@ -43,10 +34,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import javax.annotation.Nullable; /** @@ -118,295 +107,13 @@ static String percentEncodePath(String input) { return Joiner.on('/').join(encodedSegs); } - @AutoValue - abstract static class LdsUpdate implements ResourceUpdate { - // Http level api listener configuration. - @Nullable - abstract HttpConnectionManager httpConnectionManager(); - - // Tcp level listener configuration. - @Nullable - abstract Listener listener(); - - static LdsUpdate forApiListener(HttpConnectionManager httpConnectionManager) { - checkNotNull(httpConnectionManager, "httpConnectionManager"); - return new AutoValue_XdsClient_LdsUpdate(httpConnectionManager, null); - } - - static LdsUpdate forTcpListener(Listener listener) { - checkNotNull(listener, "listener"); - return new AutoValue_XdsClient_LdsUpdate(null, listener); - } - } - - static final class RdsUpdate implements ResourceUpdate { - // The list virtual hosts that make up the route table. - final List virtualHosts; - - RdsUpdate(List virtualHosts) { - this.virtualHosts = Collections.unmodifiableList( - new ArrayList<>(checkNotNull(virtualHosts, "virtualHosts"))); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("virtualHosts", virtualHosts) - .toString(); - } - - @Override - public int hashCode() { - return Objects.hash(virtualHosts); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - RdsUpdate that = (RdsUpdate) o; - return Objects.equals(virtualHosts, that.virtualHosts); - } - } - - /** xDS resource update for cluster-level configuration. */ - @AutoValue - abstract static class CdsUpdate implements ResourceUpdate { - abstract String clusterName(); - - abstract ClusterType clusterType(); - - abstract ImmutableMap lbPolicyConfig(); - - // Only valid if lbPolicy is "ring_hash_experimental". - abstract long minRingSize(); - - // Only valid if lbPolicy is "ring_hash_experimental". - abstract long maxRingSize(); - - // Only valid if lbPolicy is "least_request_experimental". - abstract int choiceCount(); - - // Alternative resource name to be used in EDS requests. - /// Only valid for EDS cluster. - @Nullable - abstract String edsServiceName(); - - // Corresponding DNS name to be used if upstream endpoints of the cluster is resolvable - // via DNS. - // Only valid for LOGICAL_DNS cluster. - @Nullable - abstract String dnsHostName(); - - // Load report server info for reporting loads via LRS. - // Only valid for EDS or LOGICAL_DNS cluster. - @Nullable - abstract ServerInfo lrsServerInfo(); - - // Max number of concurrent requests can be sent to this cluster. - // Only valid for EDS or LOGICAL_DNS cluster. - @Nullable - abstract Long maxConcurrentRequests(); - - // TLS context used to connect to connect to this cluster. - // Only valid for EDS or LOGICAL_DNS cluster. - @Nullable - abstract UpstreamTlsContext upstreamTlsContext(); - - // List of underlying clusters making of this aggregate cluster. - // Only valid for AGGREGATE cluster. - @Nullable - abstract ImmutableList prioritizedClusterNames(); - - // Outlier detection configuration. - @Nullable - abstract OutlierDetection outlierDetection(); - - static Builder forAggregate(String clusterName, List prioritizedClusterNames) { - checkNotNull(prioritizedClusterNames, "prioritizedClusterNames"); - return new AutoValue_XdsClient_CdsUpdate.Builder() - .clusterName(clusterName) - .clusterType(ClusterType.AGGREGATE) - .minRingSize(0) - .maxRingSize(0) - .choiceCount(0) - .prioritizedClusterNames(ImmutableList.copyOf(prioritizedClusterNames)); - } - - static Builder forEds(String clusterName, @Nullable String edsServiceName, - @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, - @Nullable UpstreamTlsContext upstreamTlsContext, - @Nullable OutlierDetection outlierDetection) { - return new AutoValue_XdsClient_CdsUpdate.Builder() - .clusterName(clusterName) - .clusterType(ClusterType.EDS) - .minRingSize(0) - .maxRingSize(0) - .choiceCount(0) - .edsServiceName(edsServiceName) - .lrsServerInfo(lrsServerInfo) - .maxConcurrentRequests(maxConcurrentRequests) - .upstreamTlsContext(upstreamTlsContext) - .outlierDetection(outlierDetection); - } - - static Builder forLogicalDns(String clusterName, String dnsHostName, - @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, - @Nullable UpstreamTlsContext upstreamTlsContext) { - return new AutoValue_XdsClient_CdsUpdate.Builder() - .clusterName(clusterName) - .clusterType(ClusterType.LOGICAL_DNS) - .minRingSize(0) - .maxRingSize(0) - .choiceCount(0) - .dnsHostName(dnsHostName) - .lrsServerInfo(lrsServerInfo) - .maxConcurrentRequests(maxConcurrentRequests) - .upstreamTlsContext(upstreamTlsContext); - } - - enum ClusterType { - EDS, LOGICAL_DNS, AGGREGATE - } - - enum LbPolicy { - ROUND_ROBIN, RING_HASH, LEAST_REQUEST - } - - // FIXME(chengyuanzhang): delete this after UpstreamTlsContext's toString() is fixed. - @Override - public final String toString() { - return MoreObjects.toStringHelper(this) - .add("clusterName", clusterName()) - .add("clusterType", clusterType()) - .add("lbPolicyConfig", lbPolicyConfig()) - .add("minRingSize", minRingSize()) - .add("maxRingSize", maxRingSize()) - .add("choiceCount", choiceCount()) - .add("edsServiceName", edsServiceName()) - .add("dnsHostName", dnsHostName()) - .add("lrsServerInfo", lrsServerInfo()) - .add("maxConcurrentRequests", maxConcurrentRequests()) - .add("prioritizedClusterNames", prioritizedClusterNames()) - // Exclude upstreamTlsContext and outlierDetection as their string representations are - // cumbersome. - .toString(); - } - - @AutoValue.Builder - abstract static class Builder { - // Private, use one of the static factory methods instead. - protected abstract Builder clusterName(String clusterName); - - // Private, use one of the static factory methods instead. - protected abstract Builder clusterType(ClusterType clusterType); - - protected abstract Builder lbPolicyConfig(ImmutableMap lbPolicyConfig); - - Builder roundRobinLbPolicy() { - return this.lbPolicyConfig(ImmutableMap.of("round_robin", ImmutableMap.of())); - } - - Builder ringHashLbPolicy(Long minRingSize, Long maxRingSize) { - return this.lbPolicyConfig(ImmutableMap.of("ring_hash_experimental", - ImmutableMap.of("minRingSize", minRingSize.doubleValue(), "maxRingSize", - maxRingSize.doubleValue()))); - } - - Builder leastRequestLbPolicy(Integer choiceCount) { - return this.lbPolicyConfig(ImmutableMap.of("least_request_experimental", - ImmutableMap.of("choiceCount", choiceCount.doubleValue()))); - } - - // Private, use leastRequestLbPolicy(int). - protected abstract Builder choiceCount(int choiceCount); - - // Private, use ringHashLbPolicy(long, long). - protected abstract Builder minRingSize(long minRingSize); - - // Private, use ringHashLbPolicy(long, long). - protected abstract Builder maxRingSize(long maxRingSize); - - // Private, use CdsUpdate.forEds() instead. - protected abstract Builder edsServiceName(String edsServiceName); - - // Private, use CdsUpdate.forLogicalDns() instead. - protected abstract Builder dnsHostName(String dnsHostName); - - // Private, use one of the static factory methods instead. - protected abstract Builder lrsServerInfo(ServerInfo lrsServerInfo); - - // Private, use one of the static factory methods instead. - protected abstract Builder maxConcurrentRequests(Long maxConcurrentRequests); - - // Private, use one of the static factory methods instead. - protected abstract Builder upstreamTlsContext(UpstreamTlsContext upstreamTlsContext); - - // Private, use CdsUpdate.forAggregate() instead. - protected abstract Builder prioritizedClusterNames(List prioritizedClusterNames); - - protected abstract Builder outlierDetection(OutlierDetection outlierDetection); - - abstract CdsUpdate build(); - } - } - - static final class EdsUpdate implements ResourceUpdate { - final String clusterName; - final Map localityLbEndpointsMap; - final List dropPolicies; - - EdsUpdate(String clusterName, Map localityLbEndpoints, - List dropPolicies) { - this.clusterName = checkNotNull(clusterName, "clusterName"); - this.localityLbEndpointsMap = Collections.unmodifiableMap( - new LinkedHashMap<>(checkNotNull(localityLbEndpoints, "localityLbEndpoints"))); - this.dropPolicies = Collections.unmodifiableList( - new ArrayList<>(checkNotNull(dropPolicies, "dropPolicies"))); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - EdsUpdate that = (EdsUpdate) o; - return Objects.equals(clusterName, that.clusterName) - && Objects.equals(localityLbEndpointsMap, that.localityLbEndpointsMap) - && Objects.equals(dropPolicies, that.dropPolicies); - } - - @Override - public int hashCode() { - return Objects.hash(clusterName, localityLbEndpointsMap, dropPolicies); - } - - @Override - public String toString() { - return - MoreObjects - .toStringHelper(this) - .add("clusterName", clusterName) - .add("localityLbEndpointsMap", localityLbEndpointsMap) - .add("dropPolicies", dropPolicies) - .toString(); - } - } - interface ResourceUpdate { } /** * Watcher interface for a single requested xDS resource. */ - interface ResourceWatcher { + interface ResourceWatcher { /** * Called when the resource discovery RPC encounters some transient error. @@ -426,22 +133,8 @@ interface ResourceWatcher { * @param resourceName name of the resource requested in discovery request. */ void onResourceDoesNotExist(String resourceName); - } - - interface LdsResourceWatcher extends ResourceWatcher { - void onChanged(LdsUpdate update); - } - interface RdsResourceWatcher extends ResourceWatcher { - void onChanged(RdsUpdate update); - } - - interface CdsResourceWatcher extends ResourceWatcher { - void onChanged(CdsUpdate update); - } - - interface EdsResourceWatcher extends ResourceWatcher { - void onChanged(EdsUpdate update); + void onChanged(T update); } /** @@ -609,58 +302,19 @@ TlsContextManager getTlsContextManager() { } /** - * Registers a data watcher for the given LDS resource. + * Registers a data watcher for the given Xds resource. */ - void watchLdsResource(String resourceName, LdsResourceWatcher watcher) { + void watchXdsResource(XdsResourceType type, String resourceName, + ResourceWatcher watcher) { throw new UnsupportedOperationException(); } /** - * Unregisters the given LDS resource watcher. + * Unregisters the given resource watcher. */ - void cancelLdsResourceWatch(String resourceName, LdsResourceWatcher watcher) { - throw new UnsupportedOperationException(); - } - - /** - * Registers a data watcher for the given RDS resource. - */ - void watchRdsResource(String resourceName, RdsResourceWatcher watcher) { - throw new UnsupportedOperationException(); - } - - /** - * Unregisters the given RDS resource watcher. - */ - void cancelRdsResourceWatch(String resourceName, RdsResourceWatcher watcher) { - throw new UnsupportedOperationException(); - } - - /** - * Registers a data watcher for the given CDS resource. - */ - void watchCdsResource(String resourceName, CdsResourceWatcher watcher) { - throw new UnsupportedOperationException(); - } - - /** - * Unregisters the given CDS resource watcher. - */ - void cancelCdsResourceWatch(String resourceName, CdsResourceWatcher watcher) { - throw new UnsupportedOperationException(); - } - - /** - * Registers a data watcher for the given EDS resource. - */ - void watchEdsResource(String resourceName, EdsResourceWatcher watcher) { - throw new UnsupportedOperationException(); - } - - /** - * Unregisters the given EDS resource watcher. - */ - void cancelEdsResourceWatch(String resourceName, EdsResourceWatcher watcher) { + void cancelXdsResourceWatch(XdsResourceType type, + String resourceName, + ResourceWatcher watcher) { throw new UnsupportedOperationException(); } @@ -691,21 +345,10 @@ ClusterLocalityStats addClusterLocalityStats( } interface XdsResponseHandler { - /** Called when an LDS response is received. */ - void handleLdsResponse( - ServerInfo serverInfo, String versionInfo, List resources, String nonce); - - /** Called when an RDS response is received. */ - void handleRdsResponse( - ServerInfo serverInfo, String versionInfo, List resources, String nonce); - - /** Called when an CDS response is received. */ - void handleCdsResponse( - ServerInfo serverInfo, String versionInfo, List resources, String nonce); - - /** Called when an EDS response is received. */ - void handleEdsResponse( - ServerInfo serverInfo, String versionInfo, List resources, String nonce); + /** Called when a xds response is received. */ + void handleResourceResponse( + ResourceType resourceType, ServerInfo serverInfo, String versionInfo, List resources, + String nonce); /** Called when the ADS stream is closed passively. */ // Must be synchronized. @@ -727,5 +370,8 @@ interface ResourceStore { // Must be synchronized. @Nullable Collection getSubscribedResources(ServerInfo serverInfo, ResourceType type); + + @Nullable + XdsResourceType getXdsResourceType(ResourceType type); } } diff --git a/xds/src/main/java/io/grpc/xds/XdsClusterResource.java b/xds/src/main/java/io/grpc/xds/XdsClusterResource.java new file mode 100644 index 00000000000..4dc3095efa9 --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/XdsClusterResource.java @@ -0,0 +1,686 @@ +/* + * Copyright 2022 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.xds; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.grpc.xds.AbstractXdsClient.ResourceType; +import static io.grpc.xds.AbstractXdsClient.ResourceType.CDS; +import static io.grpc.xds.AbstractXdsClient.ResourceType.EDS; +import static io.grpc.xds.Bootstrapper.ServerInfo; + +import com.google.auto.value.AutoValue; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.protobuf.Duration; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.Message; +import com.google.protobuf.util.Durations; +import io.envoyproxy.envoy.config.cluster.v3.CircuitBreakers.Thresholds; +import io.envoyproxy.envoy.config.cluster.v3.Cluster; +import io.envoyproxy.envoy.config.core.v3.RoutingPriority; +import io.envoyproxy.envoy.config.core.v3.SocketAddress; +import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; +import io.grpc.LoadBalancerRegistry; +import io.grpc.NameResolver; +import io.grpc.internal.ServiceConfigUtil; +import io.grpc.internal.ServiceConfigUtil.LbConfig; +import io.grpc.xds.ClientXdsClient.ResourceInvalidException; +import io.grpc.xds.EnvoyServerProtoData.OutlierDetection; +import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; +import io.grpc.xds.XdsClient.ResourceUpdate; +import io.grpc.xds.XdsClusterResource.CdsUpdate; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import javax.annotation.Nullable; + +class XdsClusterResource extends XdsResourceType { + static final String ADS_TYPE_URL_CDS_V2 = "type.googleapis.com/envoy.api.v2.Cluster"; + static final String ADS_TYPE_URL_CDS = + "type.googleapis.com/envoy.config.cluster.v3.Cluster"; + private static final String TYPE_URL_UPSTREAM_TLS_CONTEXT = + "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext"; + private static final String TYPE_URL_UPSTREAM_TLS_CONTEXT_V2 = + "type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext"; + + private static final XdsClusterResource instance = new XdsClusterResource(); + + public static XdsClusterResource getInstance() { + return instance; + } + + @Override + @Nullable + String extractResourceName(Message unpackedResource) { + if (!(unpackedResource instanceof Cluster)) { + return null; + } + return ((Cluster) unpackedResource).getName(); + } + + @Override + ResourceType typeName() { + return CDS; + } + + @Override + String typeUrl() { + return ADS_TYPE_URL_CDS; + } + + @Override + String typeUrlV2() { + return ADS_TYPE_URL_CDS_V2; + } + + @Nullable + @Override + ResourceType dependentResource() { + return EDS; + } + + @Override + @SuppressWarnings("unchecked") + Class unpackedClassName() { + return Cluster.class; + } + + @Override + CdsUpdate doParse(Args args, Message unpackedMessage, + Set retainedResources, boolean isResourceV3) + throws ResourceInvalidException { + if (!(unpackedMessage instanceof Cluster)) { + throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass()); + } + Set certProviderInstances = null; + if (args.bootstrapInfo != null && args.bootstrapInfo.certProviders() != null) { + certProviderInstances = args.bootstrapInfo.certProviders().keySet(); + } + return processCluster((Cluster) unpackedMessage, retainedResources, certProviderInstances, + args.serverInfo, args.loadBalancerRegistry); + } + + @VisibleForTesting + static CdsUpdate processCluster(Cluster cluster, Set retainedEdsResources, + Set certProviderInstances, + Bootstrapper.ServerInfo serverInfo, + LoadBalancerRegistry loadBalancerRegistry) + throws ResourceInvalidException { + StructOrError structOrError; + switch (cluster.getClusterDiscoveryTypeCase()) { + case TYPE: + structOrError = parseNonAggregateCluster(cluster, retainedEdsResources, + certProviderInstances, serverInfo); + break; + case CLUSTER_TYPE: + structOrError = parseAggregateCluster(cluster); + break; + case CLUSTERDISCOVERYTYPE_NOT_SET: + default: + throw new ResourceInvalidException( + "Cluster " + cluster.getName() + ": unspecified cluster discovery type"); + } + if (structOrError.getErrorDetail() != null) { + throw new ResourceInvalidException(structOrError.getErrorDetail()); + } + CdsUpdate.Builder updateBuilder = structOrError.getStruct(); + + ImmutableMap lbPolicyConfig = LoadBalancerConfigFactory.newConfig(cluster, + enableLeastRequest, enableCustomLbConfig); + + // Validate the LB config by trying to parse it with the corresponding LB provider. + LbConfig lbConfig = ServiceConfigUtil.unwrapLoadBalancingConfig(lbPolicyConfig); + NameResolver.ConfigOrError configOrError = loadBalancerRegistry.getProvider( + lbConfig.getPolicyName()).parseLoadBalancingPolicyConfig( + lbConfig.getRawConfigValue()); + if (configOrError.getError() != null) { + throw new ResourceInvalidException(structOrError.getErrorDetail()); + } + + updateBuilder.lbPolicyConfig(lbPolicyConfig); + + return updateBuilder.build(); + } + + private static StructOrError parseAggregateCluster(Cluster cluster) { + String clusterName = cluster.getName(); + Cluster.CustomClusterType customType = cluster.getClusterType(); + String typeName = customType.getName(); + if (!typeName.equals(AGGREGATE_CLUSTER_TYPE_NAME)) { + return StructOrError.fromError( + "Cluster " + clusterName + ": unsupported custom cluster type: " + typeName); + } + io.envoyproxy.envoy.extensions.clusters.aggregate.v3.ClusterConfig clusterConfig; + try { + clusterConfig = unpackCompatibleType(customType.getTypedConfig(), + io.envoyproxy.envoy.extensions.clusters.aggregate.v3.ClusterConfig.class, + TYPE_URL_CLUSTER_CONFIG, TYPE_URL_CLUSTER_CONFIG_V2); + } catch (InvalidProtocolBufferException e) { + return StructOrError.fromError("Cluster " + clusterName + ": malformed ClusterConfig: " + e); + } + return StructOrError.fromStruct(CdsUpdate.forAggregate( + clusterName, clusterConfig.getClustersList())); + } + + private static StructOrError parseNonAggregateCluster( + Cluster cluster, Set edsResources, Set certProviderInstances, + Bootstrapper.ServerInfo serverInfo) { + String clusterName = cluster.getName(); + Bootstrapper.ServerInfo lrsServerInfo = null; + Long maxConcurrentRequests = null; + EnvoyServerProtoData.UpstreamTlsContext upstreamTlsContext = null; + OutlierDetection outlierDetection = null; + if (cluster.hasLrsServer()) { + if (!cluster.getLrsServer().hasSelf()) { + return StructOrError.fromError( + "Cluster " + clusterName + ": only support LRS for the same management server"); + } + lrsServerInfo = serverInfo; + } + if (cluster.hasCircuitBreakers()) { + List thresholds = cluster.getCircuitBreakers().getThresholdsList(); + for (Thresholds threshold : thresholds) { + if (threshold.getPriority() != RoutingPriority.DEFAULT) { + continue; + } + if (threshold.hasMaxRequests()) { + maxConcurrentRequests = (long) threshold.getMaxRequests().getValue(); + } + } + } + if (cluster.getTransportSocketMatchesCount() > 0) { + return StructOrError.fromError("Cluster " + clusterName + + ": transport-socket-matches not supported."); + } + if (cluster.hasTransportSocket()) { + if (!TRANSPORT_SOCKET_NAME_TLS.equals(cluster.getTransportSocket().getName())) { + return StructOrError.fromError("transport-socket with name " + + cluster.getTransportSocket().getName() + " not supported."); + } + try { + upstreamTlsContext = UpstreamTlsContext.fromEnvoyProtoUpstreamTlsContext( + validateUpstreamTlsContext( + unpackCompatibleType(cluster.getTransportSocket().getTypedConfig(), + io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext.class, + TYPE_URL_UPSTREAM_TLS_CONTEXT, TYPE_URL_UPSTREAM_TLS_CONTEXT_V2), + certProviderInstances)); + } catch (InvalidProtocolBufferException | ResourceInvalidException e) { + return StructOrError.fromError( + "Cluster " + clusterName + ": malformed UpstreamTlsContext: " + e); + } + } + + if (cluster.hasOutlierDetection() && enableOutlierDetection) { + try { + outlierDetection = OutlierDetection.fromEnvoyOutlierDetection( + validateOutlierDetection(cluster.getOutlierDetection())); + } catch (ResourceInvalidException e) { + return StructOrError.fromError( + "Cluster " + clusterName + ": malformed outlier_detection: " + e); + } + } + + Cluster.DiscoveryType type = cluster.getType(); + if (type == Cluster.DiscoveryType.EDS) { + String edsServiceName = null; + io.envoyproxy.envoy.config.cluster.v3.Cluster.EdsClusterConfig edsClusterConfig = + cluster.getEdsClusterConfig(); + if (!edsClusterConfig.getEdsConfig().hasAds() + && ! edsClusterConfig.getEdsConfig().hasSelf()) { + return StructOrError.fromError( + "Cluster " + clusterName + ": field eds_cluster_config must be set to indicate to use" + + " EDS over ADS or self ConfigSource"); + } + // If the service_name field is set, that value will be used for the EDS request. + if (!edsClusterConfig.getServiceName().isEmpty()) { + edsServiceName = edsClusterConfig.getServiceName(); + edsResources.add(edsServiceName); + } else { + edsResources.add(clusterName); + } + return StructOrError.fromStruct(CdsUpdate.forEds( + clusterName, edsServiceName, lrsServerInfo, maxConcurrentRequests, upstreamTlsContext, + outlierDetection)); + } else if (type.equals(Cluster.DiscoveryType.LOGICAL_DNS)) { + if (!cluster.hasLoadAssignment()) { + return StructOrError.fromError( + "Cluster " + clusterName + ": LOGICAL_DNS clusters must have a single host"); + } + ClusterLoadAssignment assignment = cluster.getLoadAssignment(); + if (assignment.getEndpointsCount() != 1 + || assignment.getEndpoints(0).getLbEndpointsCount() != 1) { + return StructOrError.fromError( + "Cluster " + clusterName + ": LOGICAL_DNS clusters must have a single " + + "locality_lb_endpoint and a single lb_endpoint"); + } + io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint lbEndpoint = + assignment.getEndpoints(0).getLbEndpoints(0); + if (!lbEndpoint.hasEndpoint() || !lbEndpoint.getEndpoint().hasAddress() + || !lbEndpoint.getEndpoint().getAddress().hasSocketAddress()) { + return StructOrError.fromError( + "Cluster " + clusterName + + ": LOGICAL_DNS clusters must have an endpoint with address and socket_address"); + } + SocketAddress socketAddress = lbEndpoint.getEndpoint().getAddress().getSocketAddress(); + if (!socketAddress.getResolverName().isEmpty()) { + return StructOrError.fromError( + "Cluster " + clusterName + + ": LOGICAL DNS clusters must NOT have a custom resolver name set"); + } + if (socketAddress.getPortSpecifierCase() != SocketAddress.PortSpecifierCase.PORT_VALUE) { + return StructOrError.fromError( + "Cluster " + clusterName + + ": LOGICAL DNS clusters socket_address must have port_value"); + } + String dnsHostName = String.format( + Locale.US, "%s:%d", socketAddress.getAddress(), socketAddress.getPortValue()); + return StructOrError.fromStruct(CdsUpdate.forLogicalDns( + clusterName, dnsHostName, lrsServerInfo, maxConcurrentRequests, upstreamTlsContext)); + } + return StructOrError.fromError( + "Cluster " + clusterName + ": unsupported built-in discovery type: " + type); + } + + static io.envoyproxy.envoy.config.cluster.v3.OutlierDetection validateOutlierDetection( + io.envoyproxy.envoy.config.cluster.v3.OutlierDetection outlierDetection) + throws ResourceInvalidException { + if (outlierDetection.hasInterval()) { + if (!Durations.isValid(outlierDetection.getInterval())) { + throw new ResourceInvalidException("outlier_detection interval is not a valid Duration"); + } + if (hasNegativeValues(outlierDetection.getInterval())) { + throw new ResourceInvalidException("outlier_detection interval has a negative value"); + } + } + if (outlierDetection.hasBaseEjectionTime()) { + if (!Durations.isValid(outlierDetection.getBaseEjectionTime())) { + throw new ResourceInvalidException( + "outlier_detection base_ejection_time is not a valid Duration"); + } + if (hasNegativeValues(outlierDetection.getBaseEjectionTime())) { + throw new ResourceInvalidException( + "outlier_detection base_ejection_time has a negative value"); + } + } + if (outlierDetection.hasMaxEjectionTime()) { + if (!Durations.isValid(outlierDetection.getMaxEjectionTime())) { + throw new ResourceInvalidException( + "outlier_detection max_ejection_time is not a valid Duration"); + } + if (hasNegativeValues(outlierDetection.getMaxEjectionTime())) { + throw new ResourceInvalidException( + "outlier_detection max_ejection_time has a negative value"); + } + } + if (outlierDetection.hasMaxEjectionPercent() + && outlierDetection.getMaxEjectionPercent().getValue() > 100) { + throw new ResourceInvalidException( + "outlier_detection max_ejection_percent is > 100"); + } + if (outlierDetection.hasEnforcingSuccessRate() + && outlierDetection.getEnforcingSuccessRate().getValue() > 100) { + throw new ResourceInvalidException( + "outlier_detection enforcing_success_rate is > 100"); + } + if (outlierDetection.hasFailurePercentageThreshold() + && outlierDetection.getFailurePercentageThreshold().getValue() > 100) { + throw new ResourceInvalidException( + "outlier_detection failure_percentage_threshold is > 100"); + } + if (outlierDetection.hasEnforcingFailurePercentage() + && outlierDetection.getEnforcingFailurePercentage().getValue() > 100) { + throw new ResourceInvalidException( + "outlier_detection enforcing_failure_percentage is > 100"); + } + + return outlierDetection; + } + + static boolean hasNegativeValues(Duration duration) { + return duration.getSeconds() < 0 || duration.getNanos() < 0; + } + + @VisibleForTesting + static io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + validateUpstreamTlsContext( + io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext upstreamTlsContext, + Set certProviderInstances) + throws ResourceInvalidException { + if (upstreamTlsContext.hasCommonTlsContext()) { + validateCommonTlsContext(upstreamTlsContext.getCommonTlsContext(), certProviderInstances, + false); + } else { + throw new ResourceInvalidException("common-tls-context is required in upstream-tls-context"); + } + return upstreamTlsContext; + } + + @VisibleForTesting + static void validateCommonTlsContext( + CommonTlsContext commonTlsContext, Set certProviderInstances, boolean server) + throws ResourceInvalidException { + if (commonTlsContext.hasCustomHandshaker()) { + throw new ResourceInvalidException( + "common-tls-context with custom_handshaker is not supported"); + } + if (commonTlsContext.hasTlsParams()) { + throw new ResourceInvalidException("common-tls-context with tls_params is not supported"); + } + if (commonTlsContext.hasValidationContextSdsSecretConfig()) { + throw new ResourceInvalidException( + "common-tls-context with validation_context_sds_secret_config is not supported"); + } + if (commonTlsContext.hasValidationContextCertificateProvider()) { + throw new ResourceInvalidException( + "common-tls-context with validation_context_certificate_provider is not supported"); + } + if (commonTlsContext.hasValidationContextCertificateProviderInstance()) { + throw new ResourceInvalidException( + "common-tls-context with validation_context_certificate_provider_instance is not" + + " supported"); + } + String certInstanceName = getIdentityCertInstanceName(commonTlsContext); + if (certInstanceName == null) { + if (server) { + throw new ResourceInvalidException( + "tls_certificate_provider_instance is required in downstream-tls-context"); + } + if (commonTlsContext.getTlsCertificatesCount() > 0) { + throw new ResourceInvalidException( + "tls_certificate_provider_instance is unset"); + } + if (commonTlsContext.getTlsCertificateSdsSecretConfigsCount() > 0) { + throw new ResourceInvalidException( + "tls_certificate_provider_instance is unset"); + } + if (commonTlsContext.hasTlsCertificateCertificateProvider()) { + throw new ResourceInvalidException( + "tls_certificate_provider_instance is unset"); + } + } else if (certProviderInstances == null || !certProviderInstances.contains(certInstanceName)) { + throw new ResourceInvalidException( + "CertificateProvider instance name '" + certInstanceName + + "' not defined in the bootstrap file."); + } + String rootCaInstanceName = getRootCertInstanceName(commonTlsContext); + if (rootCaInstanceName == null) { + if (!server) { + throw new ResourceInvalidException( + "ca_certificate_provider_instance is required in upstream-tls-context"); + } + } else { + if (certProviderInstances == null || !certProviderInstances.contains(rootCaInstanceName)) { + throw new ResourceInvalidException( + "ca_certificate_provider_instance name '" + rootCaInstanceName + + "' not defined in the bootstrap file."); + } + CertificateValidationContext certificateValidationContext = null; + if (commonTlsContext.hasValidationContext()) { + certificateValidationContext = commonTlsContext.getValidationContext(); + } else if (commonTlsContext.hasCombinedValidationContext() && commonTlsContext + .getCombinedValidationContext().hasDefaultValidationContext()) { + certificateValidationContext = commonTlsContext.getCombinedValidationContext() + .getDefaultValidationContext(); + } + if (certificateValidationContext != null) { + if (certificateValidationContext.getMatchSubjectAltNamesCount() > 0 && server) { + throw new ResourceInvalidException( + "match_subject_alt_names only allowed in upstream_tls_context"); + } + if (certificateValidationContext.getVerifyCertificateSpkiCount() > 0) { + throw new ResourceInvalidException( + "verify_certificate_spki in default_validation_context is not supported"); + } + if (certificateValidationContext.getVerifyCertificateHashCount() > 0) { + throw new ResourceInvalidException( + "verify_certificate_hash in default_validation_context is not supported"); + } + if (certificateValidationContext.hasRequireSignedCertificateTimestamp()) { + throw new ResourceInvalidException( + "require_signed_certificate_timestamp in default_validation_context is not " + + "supported"); + } + if (certificateValidationContext.hasCrl()) { + throw new ResourceInvalidException("crl in default_validation_context is not supported"); + } + if (certificateValidationContext.hasCustomValidatorConfig()) { + throw new ResourceInvalidException( + "custom_validator_config in default_validation_context is not supported"); + } + } + } + } + + private static String getIdentityCertInstanceName(CommonTlsContext commonTlsContext) { + if (commonTlsContext.hasTlsCertificateProviderInstance()) { + return commonTlsContext.getTlsCertificateProviderInstance().getInstanceName(); + } else if (commonTlsContext.hasTlsCertificateCertificateProviderInstance()) { + return commonTlsContext.getTlsCertificateCertificateProviderInstance().getInstanceName(); + } + return null; + } + + private static String getRootCertInstanceName(CommonTlsContext commonTlsContext) { + if (commonTlsContext.hasValidationContext()) { + if (commonTlsContext.getValidationContext().hasCaCertificateProviderInstance()) { + return commonTlsContext.getValidationContext().getCaCertificateProviderInstance() + .getInstanceName(); + } + } else if (commonTlsContext.hasCombinedValidationContext()) { + CommonTlsContext.CombinedCertificateValidationContext combinedCertificateValidationContext + = commonTlsContext.getCombinedValidationContext(); + if (combinedCertificateValidationContext.hasDefaultValidationContext() + && combinedCertificateValidationContext.getDefaultValidationContext() + .hasCaCertificateProviderInstance()) { + return combinedCertificateValidationContext.getDefaultValidationContext() + .getCaCertificateProviderInstance().getInstanceName(); + } else if (combinedCertificateValidationContext + .hasValidationContextCertificateProviderInstance()) { + return combinedCertificateValidationContext + .getValidationContextCertificateProviderInstance().getInstanceName(); + } + } + return null; + } + + /** xDS resource update for cluster-level configuration. */ + @AutoValue + abstract static class CdsUpdate implements ResourceUpdate { + abstract String clusterName(); + + abstract ClusterType clusterType(); + + abstract ImmutableMap lbPolicyConfig(); + + // Only valid if lbPolicy is "ring_hash_experimental". + abstract long minRingSize(); + + // Only valid if lbPolicy is "ring_hash_experimental". + abstract long maxRingSize(); + + // Only valid if lbPolicy is "least_request_experimental". + abstract int choiceCount(); + + // Alternative resource name to be used in EDS requests. + /// Only valid for EDS cluster. + @Nullable + abstract String edsServiceName(); + + // Corresponding DNS name to be used if upstream endpoints of the cluster is resolvable + // via DNS. + // Only valid for LOGICAL_DNS cluster. + @Nullable + abstract String dnsHostName(); + + // Load report server info for reporting loads via LRS. + // Only valid for EDS or LOGICAL_DNS cluster. + @Nullable + abstract ServerInfo lrsServerInfo(); + + // Max number of concurrent requests can be sent to this cluster. + // Only valid for EDS or LOGICAL_DNS cluster. + @Nullable + abstract Long maxConcurrentRequests(); + + // TLS context used to connect to connect to this cluster. + // Only valid for EDS or LOGICAL_DNS cluster. + @Nullable + abstract UpstreamTlsContext upstreamTlsContext(); + + // List of underlying clusters making of this aggregate cluster. + // Only valid for AGGREGATE cluster. + @Nullable + abstract ImmutableList prioritizedClusterNames(); + + // Outlier detection configuration. + @Nullable + abstract OutlierDetection outlierDetection(); + + static Builder forAggregate(String clusterName, List prioritizedClusterNames) { + checkNotNull(prioritizedClusterNames, "prioritizedClusterNames"); + return new AutoValue_XdsClusterResource_CdsUpdate.Builder() + .clusterName(clusterName) + .clusterType(ClusterType.AGGREGATE) + .minRingSize(0) + .maxRingSize(0) + .choiceCount(0) + .prioritizedClusterNames(ImmutableList.copyOf(prioritizedClusterNames)); + } + + static Builder forEds(String clusterName, @Nullable String edsServiceName, + @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, + @Nullable UpstreamTlsContext upstreamTlsContext, + @Nullable OutlierDetection outlierDetection) { + return new AutoValue_XdsClusterResource_CdsUpdate.Builder() + .clusterName(clusterName) + .clusterType(ClusterType.EDS) + .minRingSize(0) + .maxRingSize(0) + .choiceCount(0) + .edsServiceName(edsServiceName) + .lrsServerInfo(lrsServerInfo) + .maxConcurrentRequests(maxConcurrentRequests) + .upstreamTlsContext(upstreamTlsContext) + .outlierDetection(outlierDetection); + } + + static Builder forLogicalDns(String clusterName, String dnsHostName, + @Nullable ServerInfo lrsServerInfo, + @Nullable Long maxConcurrentRequests, + @Nullable UpstreamTlsContext upstreamTlsContext) { + return new AutoValue_XdsClusterResource_CdsUpdate.Builder() + .clusterName(clusterName) + .clusterType(ClusterType.LOGICAL_DNS) + .minRingSize(0) + .maxRingSize(0) + .choiceCount(0) + .dnsHostName(dnsHostName) + .lrsServerInfo(lrsServerInfo) + .maxConcurrentRequests(maxConcurrentRequests) + .upstreamTlsContext(upstreamTlsContext); + } + + enum ClusterType { + EDS, LOGICAL_DNS, AGGREGATE + } + + enum LbPolicy { + ROUND_ROBIN, RING_HASH, LEAST_REQUEST + } + + // FIXME(chengyuanzhang): delete this after UpstreamTlsContext's toString() is fixed. + @Override + public final String toString() { + return MoreObjects.toStringHelper(this) + .add("clusterName", clusterName()) + .add("clusterType", clusterType()) + .add("lbPolicyConfig", lbPolicyConfig()) + .add("minRingSize", minRingSize()) + .add("maxRingSize", maxRingSize()) + .add("choiceCount", choiceCount()) + .add("edsServiceName", edsServiceName()) + .add("dnsHostName", dnsHostName()) + .add("lrsServerInfo", lrsServerInfo()) + .add("maxConcurrentRequests", maxConcurrentRequests()) + // Exclude upstreamTlsContext and outlierDetection as their string representations are + // cumbersome. + .add("prioritizedClusterNames", prioritizedClusterNames()) + .toString(); + } + + @AutoValue.Builder + abstract static class Builder { + // Private, use one of the static factory methods instead. + protected abstract Builder clusterName(String clusterName); + + // Private, use one of the static factory methods instead. + protected abstract Builder clusterType(ClusterType clusterType); + + protected abstract Builder lbPolicyConfig(ImmutableMap lbPolicyConfig); + + Builder roundRobinLbPolicy() { + return this.lbPolicyConfig(ImmutableMap.of("round_robin", ImmutableMap.of())); + } + + Builder ringHashLbPolicy(Long minRingSize, Long maxRingSize) { + return this.lbPolicyConfig(ImmutableMap.of("ring_hash_experimental", + ImmutableMap.of("minRingSize", minRingSize.doubleValue(), "maxRingSize", + maxRingSize.doubleValue()))); + } + + Builder leastRequestLbPolicy(Integer choiceCount) { + return this.lbPolicyConfig(ImmutableMap.of("least_request_experimental", + ImmutableMap.of("choiceCount", choiceCount.doubleValue()))); + } + + // Private, use leastRequestLbPolicy(int). + protected abstract Builder choiceCount(int choiceCount); + + // Private, use ringHashLbPolicy(long, long). + protected abstract Builder minRingSize(long minRingSize); + + // Private, use ringHashLbPolicy(long, long). + protected abstract Builder maxRingSize(long maxRingSize); + + // Private, use CdsUpdate.forEds() instead. + protected abstract Builder edsServiceName(String edsServiceName); + + // Private, use CdsUpdate.forLogicalDns() instead. + protected abstract Builder dnsHostName(String dnsHostName); + + // Private, use one of the static factory methods instead. + protected abstract Builder lrsServerInfo(ServerInfo lrsServerInfo); + + // Private, use one of the static factory methods instead. + protected abstract Builder maxConcurrentRequests(Long maxConcurrentRequests); + + // Private, use one of the static factory methods instead. + protected abstract Builder upstreamTlsContext(UpstreamTlsContext upstreamTlsContext); + + // Private, use CdsUpdate.forAggregate() instead. + protected abstract Builder prioritizedClusterNames(List prioritizedClusterNames); + + protected abstract Builder outlierDetection(OutlierDetection outlierDetection); + + abstract CdsUpdate build(); + } + } +} diff --git a/xds/src/main/java/io/grpc/xds/XdsEndpointResource.java b/xds/src/main/java/io/grpc/xds/XdsEndpointResource.java new file mode 100644 index 00000000000..db1e93d13f3 --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/XdsEndpointResource.java @@ -0,0 +1,259 @@ +/* + * Copyright 2022 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.xds; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.grpc.xds.AbstractXdsClient.ResourceType; +import static io.grpc.xds.AbstractXdsClient.ResourceType.EDS; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableList; +import com.google.protobuf.Message; +import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment; +import io.envoyproxy.envoy.type.v3.FractionalPercent; +import io.grpc.EquivalentAddressGroup; +import io.grpc.xds.ClientXdsClient.ResourceInvalidException; +import io.grpc.xds.Endpoints.DropOverload; +import io.grpc.xds.Endpoints.LocalityLbEndpoints; +import io.grpc.xds.XdsClient.ResourceUpdate; +import io.grpc.xds.XdsEndpointResource.EdsUpdate; +import java.net.InetSocketAddress; +import java.util.ArrayList; +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.Objects; +import java.util.Set; +import javax.annotation.Nullable; + +class XdsEndpointResource extends XdsResourceType { + static final String ADS_TYPE_URL_EDS_V2 = + "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment"; + static final String ADS_TYPE_URL_EDS = + "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment"; + + private static final XdsEndpointResource instance = new XdsEndpointResource(); + + public static XdsEndpointResource getInstance() { + return instance; + } + + @Override + @Nullable + String extractResourceName(Message unpackedResource) { + if (!(unpackedResource instanceof ClusterLoadAssignment)) { + return null; + } + return ((ClusterLoadAssignment) unpackedResource).getClusterName(); + } + + @Override + ResourceType typeName() { + return EDS; + } + + @Override + String typeUrl() { + return ADS_TYPE_URL_EDS; + } + + @Override + String typeUrlV2() { + return ADS_TYPE_URL_EDS_V2; + } + + @Nullable + @Override + ResourceType dependentResource() { + return null; + } + + @Override + Class unpackedClassName() { + return ClusterLoadAssignment.class; + } + + @Override + EdsUpdate doParse(Args args, Message unpackedMessage, + Set retainedResources, boolean isResourceV3) + throws ResourceInvalidException { + if (!(unpackedMessage instanceof ClusterLoadAssignment)) { + throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass()); + } + return processClusterLoadAssignment((ClusterLoadAssignment) unpackedMessage); + } + + private static EdsUpdate processClusterLoadAssignment(ClusterLoadAssignment assignment) + throws ResourceInvalidException { + Map> priorities = new HashMap<>(); + Map localityLbEndpointsMap = new LinkedHashMap<>(); + List dropOverloads = new ArrayList<>(); + int maxPriority = -1; + for (io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints localityLbEndpointsProto + : assignment.getEndpointsList()) { + StructOrError structOrError = + parseLocalityLbEndpoints(localityLbEndpointsProto); + if (structOrError == null) { + continue; + } + if (structOrError.getErrorDetail() != null) { + throw new ResourceInvalidException(structOrError.getErrorDetail()); + } + + LocalityLbEndpoints localityLbEndpoints = structOrError.getStruct(); + int priority = localityLbEndpoints.priority(); + maxPriority = Math.max(maxPriority, priority); + // Note endpoints with health status other than HEALTHY and UNKNOWN are still + // handed over to watching parties. It is watching parties' responsibility to + // filter out unhealthy endpoints. See EnvoyProtoData.LbEndpoint#isHealthy(). + Locality locality = parseLocality(localityLbEndpointsProto.getLocality()); + localityLbEndpointsMap.put(locality, localityLbEndpoints); + if (!priorities.containsKey(priority)) { + priorities.put(priority, new HashSet<>()); + } + if (!priorities.get(priority).add(locality)) { + throw new ResourceInvalidException("ClusterLoadAssignment has duplicate locality:" + + locality + " for priority:" + priority); + } + } + if (priorities.size() != maxPriority + 1) { + throw new ResourceInvalidException("ClusterLoadAssignment has sparse priorities"); + } + + for (ClusterLoadAssignment.Policy.DropOverload dropOverloadProto + : assignment.getPolicy().getDropOverloadsList()) { + dropOverloads.add(parseDropOverload(dropOverloadProto)); + } + return new EdsUpdate(assignment.getClusterName(), localityLbEndpointsMap, dropOverloads); + } + + private static Locality parseLocality(io.envoyproxy.envoy.config.core.v3.Locality proto) { + return Locality.create(proto.getRegion(), proto.getZone(), proto.getSubZone()); + } + + private static DropOverload parseDropOverload( + io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment.Policy.DropOverload proto) { + return DropOverload.create(proto.getCategory(), getRatePerMillion(proto.getDropPercentage())); + } + + private static int getRatePerMillion(FractionalPercent percent) { + int numerator = percent.getNumerator(); + FractionalPercent.DenominatorType type = percent.getDenominator(); + switch (type) { + case TEN_THOUSAND: + numerator *= 100; + break; + case HUNDRED: + numerator *= 10_000; + break; + case MILLION: + break; + case UNRECOGNIZED: + default: + throw new IllegalArgumentException("Unknown denominator type of " + percent); + } + + if (numerator > 1_000_000 || numerator < 0) { + numerator = 1_000_000; + } + return numerator; + } + + + @VisibleForTesting + @Nullable + static StructOrError parseLocalityLbEndpoints( + io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints proto) { + // Filter out localities without or with 0 weight. + if (!proto.hasLoadBalancingWeight() || proto.getLoadBalancingWeight().getValue() < 1) { + return null; + } + if (proto.getPriority() < 0) { + return StructOrError.fromError("negative priority"); + } + List endpoints = new ArrayList<>(proto.getLbEndpointsCount()); + for (io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint endpoint : proto.getLbEndpointsList()) { + // The endpoint field of each lb_endpoints must be set. + // Inside of it: the address field must be set. + if (!endpoint.hasEndpoint() || !endpoint.getEndpoint().hasAddress()) { + return StructOrError.fromError("LbEndpoint with no endpoint/address"); + } + io.envoyproxy.envoy.config.core.v3.SocketAddress socketAddress = + endpoint.getEndpoint().getAddress().getSocketAddress(); + InetSocketAddress addr = + new InetSocketAddress(socketAddress.getAddress(), socketAddress.getPortValue()); + boolean isHealthy = + endpoint.getHealthStatus() == io.envoyproxy.envoy.config.core.v3.HealthStatus.HEALTHY + || endpoint.getHealthStatus() + == io.envoyproxy.envoy.config.core.v3.HealthStatus.UNKNOWN; + endpoints.add(Endpoints.LbEndpoint.create( + new EquivalentAddressGroup(ImmutableList.of(addr)), + endpoint.getLoadBalancingWeight().getValue(), isHealthy)); + } + return StructOrError.fromStruct(Endpoints.LocalityLbEndpoints.create( + endpoints, proto.getLoadBalancingWeight().getValue(), proto.getPriority())); + } + + static final class EdsUpdate implements ResourceUpdate { + final String clusterName; + final Map localityLbEndpointsMap; + final List dropPolicies; + + EdsUpdate(String clusterName, Map localityLbEndpoints, + List dropPolicies) { + this.clusterName = checkNotNull(clusterName, "clusterName"); + this.localityLbEndpointsMap = Collections.unmodifiableMap( + new LinkedHashMap<>(checkNotNull(localityLbEndpoints, "localityLbEndpoints"))); + this.dropPolicies = Collections.unmodifiableList( + new ArrayList<>(checkNotNull(dropPolicies, "dropPolicies"))); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + EdsUpdate that = (EdsUpdate) o; + return Objects.equals(clusterName, that.clusterName) + && Objects.equals(localityLbEndpointsMap, that.localityLbEndpointsMap) + && Objects.equals(dropPolicies, that.dropPolicies); + } + + @Override + public int hashCode() { + return Objects.hash(clusterName, localityLbEndpointsMap, dropPolicies); + } + + @Override + public String toString() { + return + MoreObjects + .toStringHelper(this) + .add("clusterName", clusterName) + .add("localityLbEndpointsMap", localityLbEndpointsMap) + .add("dropPolicies", dropPolicies) + .toString(); + } + } +} diff --git a/xds/src/main/java/io/grpc/xds/XdsListenerResource.java b/xds/src/main/java/io/grpc/xds/XdsListenerResource.java new file mode 100644 index 00000000000..397ba32dc6e --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/XdsListenerResource.java @@ -0,0 +1,633 @@ +/* + * Copyright 2022 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.xds; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.grpc.xds.AbstractXdsClient.ResourceType; +import static io.grpc.xds.AbstractXdsClient.ResourceType.LDS; +import static io.grpc.xds.AbstractXdsClient.ResourceType.RDS; +import static io.grpc.xds.ClientXdsClient.ResourceInvalidException; +import static io.grpc.xds.XdsClient.ResourceUpdate; +import static io.grpc.xds.XdsClusterResource.validateCommonTlsContext; +import static io.grpc.xds.XdsRouteConfigureResource.extractVirtualHosts; + +import com.github.udpa.udpa.type.v1.TypedStruct; +import com.google.auto.value.AutoValue; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.protobuf.Any; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.Message; +import com.google.protobuf.util.Durations; +import io.envoyproxy.envoy.config.core.v3.HttpProtocolOptions; +import io.envoyproxy.envoy.config.core.v3.SocketAddress; +import io.envoyproxy.envoy.config.core.v3.TrafficDirection; +import io.envoyproxy.envoy.config.listener.v3.Listener; +import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager; +import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.Rds; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext; +import io.grpc.xds.EnvoyServerProtoData.CidrRange; +import io.grpc.xds.EnvoyServerProtoData.ConnectionSourceType; +import io.grpc.xds.EnvoyServerProtoData.FilterChain; +import io.grpc.xds.EnvoyServerProtoData.FilterChainMatch; +import io.grpc.xds.Filter.FilterConfig; +import io.grpc.xds.XdsListenerResource.LdsUpdate; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.annotation.Nullable; + +class XdsListenerResource extends XdsResourceType { + static final String ADS_TYPE_URL_LDS_V2 = "type.googleapis.com/envoy.api.v2.Listener"; + static final String ADS_TYPE_URL_LDS = + "type.googleapis.com/envoy.config.listener.v3.Listener"; + private static final String TYPE_URL_HTTP_CONNECTION_MANAGER_V2 = + "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2" + + ".HttpConnectionManager"; + static final String TYPE_URL_HTTP_CONNECTION_MANAGER = + "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3" + + ".HttpConnectionManager"; + private static final String TRANSPORT_SOCKET_NAME_TLS = "envoy.transport_sockets.tls"; + private static final XdsListenerResource instance = new XdsListenerResource(); + + public static XdsListenerResource getInstance() { + return instance; + } + + @Override + @Nullable + String extractResourceName(Message unpackedResource) { + if (!(unpackedResource instanceof Listener)) { + return null; + } + return ((Listener) unpackedResource).getName(); + } + + @Override + ResourceType typeName() { + return LDS; + } + + @Override + Class unpackedClassName() { + return Listener.class; + } + + @Override + String typeUrl() { + return ADS_TYPE_URL_LDS; + } + + @Override + String typeUrlV2() { + return ADS_TYPE_URL_LDS_V2; + } + + @Nullable + @Override + ResourceType dependentResource() { + return RDS; + } + + @Override + LdsUpdate doParse(Args args, Message unpackedMessage, Set retainedResources, + boolean isResourceV3) + throws ResourceInvalidException { + if (!(unpackedMessage instanceof Listener)) { + throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass()); + } + Listener listener = (Listener) unpackedMessage; + + if (listener.hasApiListener()) { + return processClientSideListener( + listener, retainedResources, args, enableFaultInjection && isResourceV3); + } else { + return processServerSideListener( + listener, retainedResources, args, enableRbac && isResourceV3); + } + } + + private LdsUpdate processClientSideListener( + Listener listener, Set rdsResources, Args args, boolean parseHttpFilter) + throws ResourceInvalidException { + // Unpack HttpConnectionManager from the Listener. + HttpConnectionManager hcm; + try { + hcm = unpackCompatibleType( + listener.getApiListener().getApiListener(), HttpConnectionManager.class, + TYPE_URL_HTTP_CONNECTION_MANAGER, TYPE_URL_HTTP_CONNECTION_MANAGER_V2); + } catch (InvalidProtocolBufferException e) { + throw new ResourceInvalidException( + "Could not parse HttpConnectionManager config from ApiListener", e); + } + return LdsUpdate.forApiListener(parseHttpConnectionManager( + hcm, rdsResources, args.filterRegistry, parseHttpFilter, true /* isForClient */)); + } + + private LdsUpdate processServerSideListener( + Listener proto, Set rdsResources, Args args, boolean parseHttpFilter) + throws ResourceInvalidException { + Set certProviderInstances = null; + if (args.bootstrapInfo != null && args.bootstrapInfo.certProviders() != null) { + certProviderInstances = args.bootstrapInfo.certProviders().keySet(); + } + return LdsUpdate.forTcpListener(parseServerSideListener( + proto, rdsResources, args.tlsContextManager, args.filterRegistry, certProviderInstances, + parseHttpFilter)); + } + + @VisibleForTesting + static EnvoyServerProtoData.Listener parseServerSideListener( + Listener proto, Set rdsResources, TlsContextManager tlsContextManager, + FilterRegistry filterRegistry, Set certProviderInstances, boolean parseHttpFilter) + throws ResourceInvalidException { + if (!proto.getTrafficDirection().equals(TrafficDirection.INBOUND) + && !proto.getTrafficDirection().equals(TrafficDirection.UNSPECIFIED)) { + throw new ResourceInvalidException( + "Listener " + proto.getName() + " with invalid traffic direction: " + + proto.getTrafficDirection()); + } + if (!proto.getListenerFiltersList().isEmpty()) { + throw new ResourceInvalidException( + "Listener " + proto.getName() + " cannot have listener_filters"); + } + if (proto.hasUseOriginalDst()) { + throw new ResourceInvalidException( + "Listener " + proto.getName() + " cannot have use_original_dst set to true"); + } + + String address = null; + if (proto.getAddress().hasSocketAddress()) { + SocketAddress socketAddress = proto.getAddress().getSocketAddress(); + address = socketAddress.getAddress(); + switch (socketAddress.getPortSpecifierCase()) { + case NAMED_PORT: + address = address + ":" + socketAddress.getNamedPort(); + break; + case PORT_VALUE: + address = address + ":" + socketAddress.getPortValue(); + break; + default: + // noop + } + } + + ImmutableList.Builder filterChains = ImmutableList.builder(); + Set uniqueSet = new HashSet<>(); + for (io.envoyproxy.envoy.config.listener.v3.FilterChain fc : proto.getFilterChainsList()) { + filterChains.add( + parseFilterChain(fc, rdsResources, tlsContextManager, filterRegistry, uniqueSet, + certProviderInstances, parseHttpFilter)); + } + FilterChain defaultFilterChain = null; + if (proto.hasDefaultFilterChain()) { + defaultFilterChain = parseFilterChain( + proto.getDefaultFilterChain(), rdsResources, tlsContextManager, filterRegistry, + null, certProviderInstances, parseHttpFilter); + } + + return EnvoyServerProtoData.Listener.create( + proto.getName(), address, filterChains.build(), defaultFilterChain); + } + + @VisibleForTesting + static FilterChain parseFilterChain( + io.envoyproxy.envoy.config.listener.v3.FilterChain proto, Set rdsResources, + TlsContextManager tlsContextManager, FilterRegistry filterRegistry, + Set uniqueSet, Set certProviderInstances, boolean parseHttpFilters) + throws ResourceInvalidException { + if (proto.getFiltersCount() != 1) { + throw new ResourceInvalidException("FilterChain " + proto.getName() + + " should contain exact one HttpConnectionManager filter"); + } + io.envoyproxy.envoy.config.listener.v3.Filter filter = proto.getFiltersList().get(0); + if (!filter.hasTypedConfig()) { + throw new ResourceInvalidException( + "FilterChain " + proto.getName() + " contains filter " + filter.getName() + + " without typed_config"); + } + Any any = filter.getTypedConfig(); + // HttpConnectionManager is the only supported network filter at the moment. + if (!any.getTypeUrl().equals(TYPE_URL_HTTP_CONNECTION_MANAGER)) { + throw new ResourceInvalidException( + "FilterChain " + proto.getName() + " contains filter " + filter.getName() + + " with unsupported typed_config type " + any.getTypeUrl()); + } + HttpConnectionManager hcmProto; + try { + hcmProto = any.unpack(HttpConnectionManager.class); + } catch (InvalidProtocolBufferException e) { + throw new ResourceInvalidException("FilterChain " + proto.getName() + " with filter " + + filter.getName() + " failed to unpack message", e); + } + io.grpc.xds.HttpConnectionManager httpConnectionManager = parseHttpConnectionManager( + hcmProto, rdsResources, filterRegistry, parseHttpFilters, false /* isForClient */); + + EnvoyServerProtoData.DownstreamTlsContext downstreamTlsContext = null; + if (proto.hasTransportSocket()) { + if (!TRANSPORT_SOCKET_NAME_TLS.equals(proto.getTransportSocket().getName())) { + throw new ResourceInvalidException("transport-socket with name " + + proto.getTransportSocket().getName() + " not supported."); + } + DownstreamTlsContext downstreamTlsContextProto; + try { + downstreamTlsContextProto = + proto.getTransportSocket().getTypedConfig().unpack(DownstreamTlsContext.class); + } catch (InvalidProtocolBufferException e) { + throw new ResourceInvalidException("FilterChain " + proto.getName() + + " failed to unpack message", e); + } + downstreamTlsContext = + EnvoyServerProtoData.DownstreamTlsContext.fromEnvoyProtoDownstreamTlsContext( + validateDownstreamTlsContext(downstreamTlsContextProto, certProviderInstances)); + } + + FilterChainMatch filterChainMatch = parseFilterChainMatch(proto.getFilterChainMatch()); + checkForUniqueness(uniqueSet, filterChainMatch); + return FilterChain.create( + proto.getName(), + filterChainMatch, + httpConnectionManager, + downstreamTlsContext, + tlsContextManager + ); + } + + @VisibleForTesting + static DownstreamTlsContext validateDownstreamTlsContext( + DownstreamTlsContext downstreamTlsContext, Set certProviderInstances) + throws ResourceInvalidException { + if (downstreamTlsContext.hasCommonTlsContext()) { + validateCommonTlsContext(downstreamTlsContext.getCommonTlsContext(), certProviderInstances, + true); + } else { + throw new ResourceInvalidException( + "common-tls-context is required in downstream-tls-context"); + } + if (downstreamTlsContext.hasRequireSni()) { + throw new ResourceInvalidException( + "downstream-tls-context with require-sni is not supported"); + } + DownstreamTlsContext.OcspStaplePolicy ocspStaplePolicy = downstreamTlsContext + .getOcspStaplePolicy(); + if (ocspStaplePolicy != DownstreamTlsContext.OcspStaplePolicy.UNRECOGNIZED + && ocspStaplePolicy != DownstreamTlsContext.OcspStaplePolicy.LENIENT_STAPLING) { + throw new ResourceInvalidException( + "downstream-tls-context with ocsp_staple_policy value " + ocspStaplePolicy.name() + + " is not supported"); + } + return downstreamTlsContext; + } + + private static void checkForUniqueness(Set uniqueSet, + FilterChainMatch filterChainMatch) throws ResourceInvalidException { + if (uniqueSet != null) { + List crossProduct = getCrossProduct(filterChainMatch); + for (FilterChainMatch cur : crossProduct) { + if (!uniqueSet.add(cur)) { + throw new ResourceInvalidException("FilterChainMatch must be unique. " + + "Found duplicate: " + cur); + } + } + } + } + + private static List getCrossProduct(FilterChainMatch filterChainMatch) { + // repeating fields to process: + // prefixRanges, applicationProtocols, sourcePrefixRanges, sourcePorts, serverNames + List expandedList = expandOnPrefixRange(filterChainMatch); + expandedList = expandOnApplicationProtocols(expandedList); + expandedList = expandOnSourcePrefixRange(expandedList); + expandedList = expandOnSourcePorts(expandedList); + return expandOnServerNames(expandedList); + } + + private static List expandOnPrefixRange(FilterChainMatch filterChainMatch) { + ArrayList expandedList = new ArrayList<>(); + if (filterChainMatch.prefixRanges().isEmpty()) { + expandedList.add(filterChainMatch); + } else { + for (CidrRange cidrRange : filterChainMatch.prefixRanges()) { + expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(), + ImmutableList.of(cidrRange), + filterChainMatch.applicationProtocols(), + filterChainMatch.sourcePrefixRanges(), + filterChainMatch.connectionSourceType(), + filterChainMatch.sourcePorts(), + filterChainMatch.serverNames(), + filterChainMatch.transportProtocol())); + } + } + return expandedList; + } + + private static List expandOnApplicationProtocols( + Collection set) { + ArrayList expandedList = new ArrayList<>(); + for (FilterChainMatch filterChainMatch : set) { + if (filterChainMatch.applicationProtocols().isEmpty()) { + expandedList.add(filterChainMatch); + } else { + for (String applicationProtocol : filterChainMatch.applicationProtocols()) { + expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(), + filterChainMatch.prefixRanges(), + ImmutableList.of(applicationProtocol), + filterChainMatch.sourcePrefixRanges(), + filterChainMatch.connectionSourceType(), + filterChainMatch.sourcePorts(), + filterChainMatch.serverNames(), + filterChainMatch.transportProtocol())); + } + } + } + return expandedList; + } + + private static List expandOnSourcePrefixRange( + Collection set) { + ArrayList expandedList = new ArrayList<>(); + for (FilterChainMatch filterChainMatch : set) { + if (filterChainMatch.sourcePrefixRanges().isEmpty()) { + expandedList.add(filterChainMatch); + } else { + for (EnvoyServerProtoData.CidrRange cidrRange : filterChainMatch.sourcePrefixRanges()) { + expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(), + filterChainMatch.prefixRanges(), + filterChainMatch.applicationProtocols(), + ImmutableList.of(cidrRange), + filterChainMatch.connectionSourceType(), + filterChainMatch.sourcePorts(), + filterChainMatch.serverNames(), + filterChainMatch.transportProtocol())); + } + } + } + return expandedList; + } + + private static List expandOnSourcePorts(Collection set) { + ArrayList expandedList = new ArrayList<>(); + for (FilterChainMatch filterChainMatch : set) { + if (filterChainMatch.sourcePorts().isEmpty()) { + expandedList.add(filterChainMatch); + } else { + for (Integer sourcePort : filterChainMatch.sourcePorts()) { + expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(), + filterChainMatch.prefixRanges(), + filterChainMatch.applicationProtocols(), + filterChainMatch.sourcePrefixRanges(), + filterChainMatch.connectionSourceType(), + ImmutableList.of(sourcePort), + filterChainMatch.serverNames(), + filterChainMatch.transportProtocol())); + } + } + } + return expandedList; + } + + private static List expandOnServerNames(Collection set) { + ArrayList expandedList = new ArrayList<>(); + for (FilterChainMatch filterChainMatch : set) { + if (filterChainMatch.serverNames().isEmpty()) { + expandedList.add(filterChainMatch); + } else { + for (String serverName : filterChainMatch.serverNames()) { + expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(), + filterChainMatch.prefixRanges(), + filterChainMatch.applicationProtocols(), + filterChainMatch.sourcePrefixRanges(), + filterChainMatch.connectionSourceType(), + filterChainMatch.sourcePorts(), + ImmutableList.of(serverName), + filterChainMatch.transportProtocol())); + } + } + } + return expandedList; + } + + private static FilterChainMatch parseFilterChainMatch( + io.envoyproxy.envoy.config.listener.v3.FilterChainMatch proto) + throws ResourceInvalidException { + ImmutableList.Builder prefixRanges = ImmutableList.builder(); + ImmutableList.Builder sourcePrefixRanges = ImmutableList.builder(); + try { + for (io.envoyproxy.envoy.config.core.v3.CidrRange range : proto.getPrefixRangesList()) { + prefixRanges.add( + CidrRange.create(range.getAddressPrefix(), range.getPrefixLen().getValue())); + } + for (io.envoyproxy.envoy.config.core.v3.CidrRange range + : proto.getSourcePrefixRangesList()) { + sourcePrefixRanges.add( + CidrRange.create(range.getAddressPrefix(), range.getPrefixLen().getValue())); + } + } catch (UnknownHostException e) { + throw new ResourceInvalidException("Failed to create CidrRange", e); + } + ConnectionSourceType sourceType; + switch (proto.getSourceType()) { + case ANY: + sourceType = ConnectionSourceType.ANY; + break; + case EXTERNAL: + sourceType = ConnectionSourceType.EXTERNAL; + break; + case SAME_IP_OR_LOOPBACK: + sourceType = ConnectionSourceType.SAME_IP_OR_LOOPBACK; + break; + default: + throw new ResourceInvalidException("Unknown source-type: " + proto.getSourceType()); + } + return FilterChainMatch.create( + proto.getDestinationPort().getValue(), + prefixRanges.build(), + ImmutableList.copyOf(proto.getApplicationProtocolsList()), + sourcePrefixRanges.build(), + sourceType, + ImmutableList.copyOf(proto.getSourcePortsList()), + ImmutableList.copyOf(proto.getServerNamesList()), + proto.getTransportProtocol()); + } + + @VisibleForTesting + static io.grpc.xds.HttpConnectionManager parseHttpConnectionManager( + HttpConnectionManager proto, Set rdsResources, FilterRegistry filterRegistry, + boolean parseHttpFilter, boolean isForClient) throws ResourceInvalidException { + if (enableRbac && proto.getXffNumTrustedHops() != 0) { + throw new ResourceInvalidException( + "HttpConnectionManager with xff_num_trusted_hops unsupported"); + } + if (enableRbac && !proto.getOriginalIpDetectionExtensionsList().isEmpty()) { + throw new ResourceInvalidException("HttpConnectionManager with " + + "original_ip_detection_extensions unsupported"); + } + // Obtain max_stream_duration from Http Protocol Options. + long maxStreamDuration = 0; + if (proto.hasCommonHttpProtocolOptions()) { + HttpProtocolOptions options = proto.getCommonHttpProtocolOptions(); + if (options.hasMaxStreamDuration()) { + maxStreamDuration = Durations.toNanos(options.getMaxStreamDuration()); + } + } + + // Parse http filters. + List filterConfigs = null; + if (parseHttpFilter) { + if (proto.getHttpFiltersList().isEmpty()) { + throw new ResourceInvalidException("Missing HttpFilter in HttpConnectionManager."); + } + filterConfigs = new ArrayList<>(); + Set names = new HashSet<>(); + for (int i = 0; i < proto.getHttpFiltersCount(); i++) { + io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter + httpFilter = proto.getHttpFiltersList().get(i); + String filterName = httpFilter.getName(); + if (!names.add(filterName)) { + throw new ResourceInvalidException( + "HttpConnectionManager contains duplicate HttpFilter: " + filterName); + } + StructOrError filterConfig = + parseHttpFilter(httpFilter, filterRegistry, isForClient); + if ((i == proto.getHttpFiltersCount() - 1) + && (filterConfig == null || !isTerminalFilter(filterConfig.getStruct()))) { + throw new ResourceInvalidException("The last HttpFilter must be a terminal filter: " + + filterName); + } + if (filterConfig == null) { + continue; + } + if (filterConfig.getErrorDetail() != null) { + throw new ResourceInvalidException( + "HttpConnectionManager contains invalid HttpFilter: " + + filterConfig.getErrorDetail()); + } + if ((i < proto.getHttpFiltersCount() - 1) && isTerminalFilter(filterConfig.getStruct())) { + throw new ResourceInvalidException("A terminal HttpFilter must be the last filter: " + + filterName); + } + filterConfigs.add(new Filter.NamedFilterConfig(filterName, filterConfig.getStruct())); + } + } + + // Parse inlined RouteConfiguration or RDS. + if (proto.hasRouteConfig()) { + List virtualHosts = extractVirtualHosts( + proto.getRouteConfig(), filterRegistry, parseHttpFilter); + return io.grpc.xds.HttpConnectionManager.forVirtualHosts( + maxStreamDuration, virtualHosts, filterConfigs); + } + if (proto.hasRds()) { + Rds rds = proto.getRds(); + if (!rds.hasConfigSource()) { + throw new ResourceInvalidException( + "HttpConnectionManager contains invalid RDS: missing config_source"); + } + if (!rds.getConfigSource().hasAds() && !rds.getConfigSource().hasSelf()) { + throw new ResourceInvalidException( + "HttpConnectionManager contains invalid RDS: must specify ADS or self ConfigSource"); + } + // Collect the RDS resource referenced by this HttpConnectionManager. + rdsResources.add(rds.getRouteConfigName()); + return io.grpc.xds.HttpConnectionManager.forRdsName( + maxStreamDuration, rds.getRouteConfigName(), filterConfigs); + } + throw new ResourceInvalidException( + "HttpConnectionManager neither has inlined route_config nor RDS"); + } + + // hard-coded: currently router config is the only terminal filter. + private static boolean isTerminalFilter(Filter.FilterConfig filterConfig) { + return RouterFilter.ROUTER_CONFIG.equals(filterConfig); + } + + @VisibleForTesting + @Nullable // Returns null if the filter is optional but not supported. + static StructOrError parseHttpFilter( + io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter + httpFilter, FilterRegistry filterRegistry, boolean isForClient) { + String filterName = httpFilter.getName(); + boolean isOptional = httpFilter.getIsOptional(); + if (!httpFilter.hasTypedConfig()) { + if (isOptional) { + return null; + } else { + return StructOrError.fromError( + "HttpFilter [" + filterName + "] is not optional and has no typed config"); + } + } + Message rawConfig = httpFilter.getTypedConfig(); + String typeUrl = httpFilter.getTypedConfig().getTypeUrl(); + + try { + if (typeUrl.equals(TYPE_URL_TYPED_STRUCT_UDPA)) { + TypedStruct typedStruct = httpFilter.getTypedConfig().unpack(TypedStruct.class); + typeUrl = typedStruct.getTypeUrl(); + rawConfig = typedStruct.getValue(); + } else if (typeUrl.equals(TYPE_URL_TYPED_STRUCT)) { + com.github.xds.type.v3.TypedStruct newTypedStruct = + httpFilter.getTypedConfig().unpack(com.github.xds.type.v3.TypedStruct.class); + typeUrl = newTypedStruct.getTypeUrl(); + rawConfig = newTypedStruct.getValue(); + } + } catch (InvalidProtocolBufferException e) { + return StructOrError.fromError( + "HttpFilter [" + filterName + "] contains invalid proto: " + e); + } + Filter filter = filterRegistry.get(typeUrl); + if ((isForClient && !(filter instanceof Filter.ClientInterceptorBuilder)) + || (!isForClient && !(filter instanceof Filter.ServerInterceptorBuilder))) { + if (isOptional) { + return null; + } else { + return StructOrError.fromError( + "HttpFilter [" + filterName + "](" + typeUrl + ") is required but unsupported for " + + (isForClient ? "client" : "server")); + } + } + ConfigOrError filterConfig = filter.parseFilterConfig(rawConfig); + if (filterConfig.errorDetail != null) { + return StructOrError.fromError( + "Invalid filter config for HttpFilter [" + filterName + "]: " + filterConfig.errorDetail); + } + return StructOrError.fromStruct(filterConfig.config); + } + + @AutoValue + abstract static class LdsUpdate implements ResourceUpdate { + // Http level api listener configuration. + @Nullable + abstract io.grpc.xds.HttpConnectionManager httpConnectionManager(); + + // Tcp level listener configuration. + @Nullable + abstract EnvoyServerProtoData.Listener listener(); + + static LdsUpdate forApiListener(io.grpc.xds.HttpConnectionManager httpConnectionManager) { + checkNotNull(httpConnectionManager, "httpConnectionManager"); + return new AutoValue_XdsListenerResource_LdsUpdate(httpConnectionManager, null); + } + + static LdsUpdate forTcpListener(EnvoyServerProtoData.Listener listener) { + checkNotNull(listener, "listener"); + return new AutoValue_XdsListenerResource_LdsUpdate(null, listener); + } + } +} diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java index 0d51d3afa80..702c217a3ee 100644 --- a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java +++ b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java @@ -63,13 +63,12 @@ import io.grpc.xds.VirtualHost.Route.RouteAction.RetryPolicy; import io.grpc.xds.VirtualHost.Route.RouteMatch; import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher; -import io.grpc.xds.XdsClient.LdsResourceWatcher; -import io.grpc.xds.XdsClient.LdsUpdate; -import io.grpc.xds.XdsClient.RdsResourceWatcher; -import io.grpc.xds.XdsClient.RdsUpdate; +import io.grpc.xds.XdsClient.ResourceWatcher; +import io.grpc.xds.XdsListenerResource.LdsUpdate; import io.grpc.xds.XdsLogger.XdsLogLevel; import io.grpc.xds.XdsNameResolverProvider.CallCounterProvider; import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory; +import io.grpc.xds.XdsRouteConfigureResource.RdsUpdate; import io.grpc.xds.internal.Matchers.FractionMatcher; import io.grpc.xds.internal.Matchers.HeaderMatcher; import java.util.ArrayList; @@ -688,7 +687,7 @@ public Result selectConfig(PickSubchannelArgs args) { } } - private class ResolveState implements LdsResourceWatcher { + private class ResolveState implements ResourceWatcher { private final ConfigOrError emptyServiceConfig = serviceConfigParser.parseServiceConfig(Collections.emptyMap()); private final String ldsResourceName; @@ -723,7 +722,8 @@ public void run() { rdsName, httpConnectionManager.httpMaxStreamDurationNano(), httpConnectionManager.httpFilterConfigs()); logger.log(XdsLogLevel.INFO, "Start watching RDS resource {0}", rdsName); - xdsClient.watchRdsResource(rdsName, routeDiscoveryState); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(), + rdsName, routeDiscoveryState); } } }); @@ -762,14 +762,14 @@ public void run() { private void start() { logger.log(XdsLogLevel.INFO, "Start watching LDS resource {0}", ldsResourceName); - xdsClient.watchLdsResource(ldsResourceName, this); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(), ldsResourceName, this); } private void stop() { logger.log(XdsLogLevel.INFO, "Stop watching LDS resource {0}", ldsResourceName); stopped = true; cleanUpRouteDiscoveryState(); - xdsClient.cancelLdsResourceWatch(ldsResourceName, this); + xdsClient.cancelXdsResourceWatch(XdsListenerResource.getInstance(), ldsResourceName, this); } // called in syncContext @@ -905,7 +905,8 @@ private void cleanUpRouteDiscoveryState() { if (routeDiscoveryState != null) { String rdsName = routeDiscoveryState.resourceName; logger.log(XdsLogLevel.INFO, "Stop watching RDS resource {0}", rdsName); - xdsClient.cancelRdsResourceWatch(rdsName, routeDiscoveryState); + xdsClient.cancelXdsResourceWatch(XdsRouteConfigureResource.getInstance(), rdsName, + routeDiscoveryState); routeDiscoveryState = null; } } @@ -914,7 +915,7 @@ private void cleanUpRouteDiscoveryState() { * Discovery state for RouteConfiguration resource. One instance for each Listener resource * update. */ - private class RouteDiscoveryState implements RdsResourceWatcher { + private class RouteDiscoveryState implements ResourceWatcher { private final String resourceName; private final long httpMaxStreamDurationNano; @Nullable @@ -936,7 +937,8 @@ public void run() { return; } logger.log(XdsLogLevel.INFO, "Received RDS resource update: {0}", update); - updateRoutes(update.virtualHosts, httpMaxStreamDurationNano, filterConfigs); + updateRoutes(update.virtualHosts, httpMaxStreamDurationNano, + filterConfigs); } }); } diff --git a/xds/src/main/java/io/grpc/xds/XdsResourceType.java b/xds/src/main/java/io/grpc/xds/XdsResourceType.java new file mode 100644 index 00000000000..52c143934e6 --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/XdsResourceType.java @@ -0,0 +1,308 @@ +/* + * Copyright 2022 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.xds; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.grpc.xds.AbstractXdsClient.ResourceType; +import static io.grpc.xds.Bootstrapper.ServerInfo; +import static io.grpc.xds.ClientXdsClient.ResourceInvalidException; +import static io.grpc.xds.XdsClient.ResourceUpdate; +import static io.grpc.xds.XdsClient.canonifyResourceName; +import static io.grpc.xds.XdsClient.isResourceNameValid; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; +import com.google.protobuf.Any; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.Message; +import io.envoyproxy.envoy.service.discovery.v3.Resource; +import io.grpc.LoadBalancerRegistry; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nullable; + +abstract class XdsResourceType { + static final String TYPE_URL_RESOURCE_V2 = "type.googleapis.com/envoy.api.v2.Resource"; + static final String TYPE_URL_RESOURCE_V3 = + "type.googleapis.com/envoy.service.discovery.v3.Resource"; + static final String TRANSPORT_SOCKET_NAME_TLS = "envoy.transport_sockets.tls"; + @VisibleForTesting + static final String AGGREGATE_CLUSTER_TYPE_NAME = "envoy.clusters.aggregate"; + @VisibleForTesting + static final String HASH_POLICY_FILTER_STATE_KEY = "io.grpc.channel_id"; + @VisibleForTesting + static boolean enableFaultInjection = getFlag("GRPC_XDS_EXPERIMENTAL_FAULT_INJECTION", true); + @VisibleForTesting + static boolean enableRetry = getFlag("GRPC_XDS_EXPERIMENTAL_ENABLE_RETRY", true); + @VisibleForTesting + static boolean enableRbac = getFlag("GRPC_XDS_EXPERIMENTAL_RBAC", true); + @VisibleForTesting + static boolean enableRouteLookup = getFlag("GRPC_EXPERIMENTAL_XDS_RLS_LB", false); + @VisibleForTesting + static boolean enableLeastRequest = + !Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_ENABLE_LEAST_REQUEST")) + ? Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_ENABLE_LEAST_REQUEST")) + : Boolean.parseBoolean(System.getProperty("io.grpc.xds.experimentalEnableLeastRequest")); + @VisibleForTesting + static boolean enableCustomLbConfig = getFlag("GRPC_EXPERIMENTAL_XDS_CUSTOM_LB_CONFIG", true); + @VisibleForTesting + static boolean enableOutlierDetection = getFlag("GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION", + true); + static final String TYPE_URL_CLUSTER_CONFIG_V2 = + "type.googleapis.com/envoy.config.cluster.aggregate.v2alpha.ClusterConfig"; + static final String TYPE_URL_CLUSTER_CONFIG = + "type.googleapis.com/envoy.extensions.clusters.aggregate.v3.ClusterConfig"; + static final String TYPE_URL_TYPED_STRUCT_UDPA = + "type.googleapis.com/udpa.type.v1.TypedStruct"; + static final String TYPE_URL_TYPED_STRUCT = + "type.googleapis.com/xds.type.v3.TypedStruct"; + + @Nullable + abstract String extractResourceName(Message unpackedResource); + + abstract Class unpackedClassName(); + + abstract ResourceType typeName(); + + abstract String typeUrl(); + + abstract String typeUrlV2(); + + // Non-null for State of the World resources. + @Nullable + abstract ResourceType dependentResource(); + + static class Args { + final ServerInfo serverInfo; + final String versionInfo; + final String nonce; + final Bootstrapper.BootstrapInfo bootstrapInfo; + final FilterRegistry filterRegistry; + final LoadBalancerRegistry loadBalancerRegistry; + final TlsContextManager tlsContextManager; + // Management server is required to always send newly requested resources, even if they + // may have been sent previously (proactively). Thus, client does not need to cache + // unrequested resources. + // Only resources in the set needs to be parsed. Null means parse everything. + final @Nullable Set subscribedResources; + + public Args(ServerInfo serverInfo, String versionInfo, String nonce, + Bootstrapper.BootstrapInfo bootstrapInfo, + FilterRegistry filterRegistry, + LoadBalancerRegistry loadBalancerRegistry, + TlsContextManager tlsContextManager, + @Nullable Set subscribedResources) { + this.serverInfo = serverInfo; + this.versionInfo = versionInfo; + this.nonce = nonce; + this.bootstrapInfo = bootstrapInfo; + this.filterRegistry = filterRegistry; + this.loadBalancerRegistry = loadBalancerRegistry; + this.tlsContextManager = tlsContextManager; + this.subscribedResources = subscribedResources; + } + } + + ValidatedResourceUpdate parse(Args args, List resources) { + Map> parsedResources = new HashMap<>(resources.size()); + Set unpackedResources = new HashSet<>(resources.size()); + Set invalidResources = new HashSet<>(); + List errors = new ArrayList<>(); + Set retainedResources = new HashSet<>(); + + for (int i = 0; i < resources.size(); i++) { + Any resource = resources.get(i); + + boolean isResourceV3; + Message unpackedMessage; + try { + resource = maybeUnwrapResources(resource); + isResourceV3 = resource.getTypeUrl().equals(typeUrl()); + unpackedMessage = unpackCompatibleType(resource, unpackedClassName(), + typeUrl(), typeUrlV2()); + } catch (InvalidProtocolBufferException e) { + errors.add(String.format("%s response Resource index %d - can't decode %s: %s", + typeName(), i, unpackedClassName().getSimpleName(), e.getMessage())); + continue; + } + String name = extractResourceName(unpackedMessage); + if (name == null || !isResourceNameValid(name, resource.getTypeUrl())) { + errors.add( + "Unsupported resource name: " + name + " for type: " + typeName()); + continue; + } + String cname = canonifyResourceName(name); + if (args.subscribedResources != null && !args.subscribedResources.contains(name)) { + continue; + } + unpackedResources.add(cname); + + T resourceUpdate; + try { + resourceUpdate = doParse(args, unpackedMessage, retainedResources, isResourceV3); + } catch (ClientXdsClient.ResourceInvalidException e) { + errors.add(String.format("%s response %s '%s' validation error: %s", + typeName(), unpackedClassName().getSimpleName(), cname, e.getMessage())); + invalidResources.add(cname); + continue; + } + + // Resource parsed successfully. + parsedResources.put(cname, new ParsedResource(resourceUpdate, resource)); + } + return new ValidatedResourceUpdate(parsedResources, unpackedResources, invalidResources, + errors, retainedResources); + + } + + abstract T doParse(Args args, Message unpackedMessage, Set retainedResources, + boolean isResourceV3) + throws ResourceInvalidException; + + /** + * Helper method to unpack serialized {@link com.google.protobuf.Any} message, while replacing + * Type URL {@code compatibleTypeUrl} with {@code typeUrl}. + * + * @param The type of unpacked message + * @param any serialized message to unpack + * @param clazz the class to unpack the message to + * @param typeUrl type URL to replace message Type URL, when it's compatible + * @param compatibleTypeUrl compatible Type URL to be replaced with {@code typeUrl} + * @return Unpacked message + * @throws InvalidProtocolBufferException if the message couldn't be unpacked + */ + static T unpackCompatibleType( + Any any, Class clazz, String typeUrl, String compatibleTypeUrl) + throws InvalidProtocolBufferException { + if (any.getTypeUrl().equals(compatibleTypeUrl)) { + any = any.toBuilder().setTypeUrl(typeUrl).build(); + } + return any.unpack(clazz); + } + + private Any maybeUnwrapResources(Any resource) + throws InvalidProtocolBufferException { + if (resource.getTypeUrl().equals(TYPE_URL_RESOURCE_V2) + || resource.getTypeUrl().equals(TYPE_URL_RESOURCE_V3)) { + return unpackCompatibleType(resource, Resource.class, TYPE_URL_RESOURCE_V3, + TYPE_URL_RESOURCE_V2).getResource(); + } else { + return resource; + } + } + + static final class ParsedResource { + private final T resourceUpdate; + private final Any rawResource; + + public ParsedResource(T resourceUpdate, Any rawResource) { + this.resourceUpdate = checkNotNull(resourceUpdate, "resourceUpdate"); + this.rawResource = checkNotNull(rawResource, "rawResource"); + } + + T getResourceUpdate() { + return resourceUpdate; + } + + Any getRawResource() { + return rawResource; + } + } + + static final class ValidatedResourceUpdate { + Map> parsedResources; + Set unpackedResources; + Set invalidResources; + List errors; + Set retainedResources; + + // validated resource update + public ValidatedResourceUpdate(Map> parsedResources, + Set unpackedResources, + Set invalidResources, + List errors, + Set retainedResources) { + this.parsedResources = parsedResources; + this.unpackedResources = unpackedResources; + this.invalidResources = invalidResources; + this.errors = errors; + this.retainedResources = retainedResources; + } + } + + private static boolean getFlag(String envVarName, boolean enableByDefault) { + String envVar = System.getenv(envVarName); + if (enableByDefault) { + return Strings.isNullOrEmpty(envVar) || Boolean.parseBoolean(envVar); + } else { + return !Strings.isNullOrEmpty(envVar) && Boolean.parseBoolean(envVar); + } + } + + @VisibleForTesting + static final class StructOrError { + + /** + * Returns a {@link StructOrError} for the successfully converted data object. + */ + static StructOrError fromStruct(T struct) { + return new StructOrError<>(struct); + } + + /** + * Returns a {@link StructOrError} for the failure to convert the data object. + */ + static StructOrError fromError(String errorDetail) { + return new StructOrError<>(errorDetail); + } + + private final String errorDetail; + private final T struct; + + private StructOrError(T struct) { + this.struct = checkNotNull(struct, "struct"); + this.errorDetail = null; + } + + private StructOrError(String errorDetail) { + this.struct = null; + this.errorDetail = checkNotNull(errorDetail, "errorDetail"); + } + + /** + * Returns struct if exists, otherwise null. + */ + @VisibleForTesting + @Nullable + T getStruct() { + return struct; + } + + /** + * Returns error detail if exists, otherwise null. + */ + @VisibleForTesting + @Nullable + String getErrorDetail() { + return errorDetail; + } + } +} diff --git a/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java b/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java new file mode 100644 index 00000000000..f2fca0b1bf7 --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java @@ -0,0 +1,707 @@ +/* + * Copyright 2022 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.xds; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.grpc.xds.AbstractXdsClient.ResourceType.RDS; +import static io.grpc.xds.AbstractXdsClient.ResourceType; +import static io.grpc.xds.XdsRouteConfigureResource.RdsUpdate; + +import com.github.udpa.udpa.type.v1.TypedStruct; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.MoreObjects; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.protobuf.Any; +import com.google.protobuf.Duration; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.Message; +import com.google.protobuf.util.Durations; +import com.google.re2j.Pattern; +import com.google.re2j.PatternSyntaxException; +import io.envoyproxy.envoy.config.core.v3.TypedExtensionConfig; +import io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin; +import io.envoyproxy.envoy.config.route.v3.RetryPolicy.RetryBackOff; +import io.envoyproxy.envoy.config.route.v3.RouteConfiguration; +import io.envoyproxy.envoy.type.v3.FractionalPercent; +import io.grpc.Status; +import io.grpc.xds.ClientXdsClient.ResourceInvalidException; +import io.grpc.xds.ClusterSpecifierPlugin.NamedPluginConfig; +import io.grpc.xds.ClusterSpecifierPlugin.PluginConfig; +import io.grpc.xds.Filter.FilterConfig; +import io.grpc.xds.VirtualHost.Route; +import io.grpc.xds.VirtualHost.Route.RouteAction; +import io.grpc.xds.VirtualHost.Route.RouteAction.ClusterWeight; +import io.grpc.xds.VirtualHost.Route.RouteAction.HashPolicy; +import io.grpc.xds.VirtualHost.Route.RouteAction.RetryPolicy; +import io.grpc.xds.VirtualHost.Route.RouteMatch; +import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher; +import io.grpc.xds.XdsClient.ResourceUpdate; +import io.grpc.xds.internal.Matchers.FractionMatcher; +import io.grpc.xds.internal.Matchers.HeaderMatcher; +import io.grpc.xds.internal.Matchers; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import javax.annotation.Nullable; + +class XdsRouteConfigureResource extends XdsResourceType { + static final String ADS_TYPE_URL_RDS_V2 = + "type.googleapis.com/envoy.api.v2.RouteConfiguration"; + static final String ADS_TYPE_URL_RDS = + "type.googleapis.com/envoy.config.route.v3.RouteConfiguration"; + private static final String TYPE_URL_FILTER_CONFIG = + "type.googleapis.com/envoy.config.route.v3.FilterConfig"; + // TODO(zdapeng): need to discuss how to handle unsupported values. + private static final Set SUPPORTED_RETRYABLE_CODES = + Collections.unmodifiableSet(EnumSet.of( + Status.Code.CANCELLED, Status.Code.DEADLINE_EXCEEDED, Status.Code.INTERNAL, + Status.Code.RESOURCE_EXHAUSTED, Status.Code.UNAVAILABLE)); + + private static final XdsRouteConfigureResource instance = new XdsRouteConfigureResource(); + + public static XdsRouteConfigureResource getInstance() { + return instance; + } + + @Override + @Nullable + String extractResourceName(Message unpackedResource) { + if (!(unpackedResource instanceof RouteConfiguration)) { + return null; + } + return ((RouteConfiguration) unpackedResource).getName(); + } + + @Override + ResourceType typeName() { + return RDS; + } + + @Override + String typeUrl() { + return ADS_TYPE_URL_RDS; + } + + @Override + String typeUrlV2() { + return ADS_TYPE_URL_RDS_V2; + } + + @Nullable + @Override + ResourceType dependentResource() { + return null; + } + + @Override + Class unpackedClassName() { + return RouteConfiguration.class; + } + + @Override + RdsUpdate doParse(XdsResourceType.Args args, Message unpackedMessage, + Set retainedResources, boolean isResourceV3) + throws ResourceInvalidException { + if (!(unpackedMessage instanceof RouteConfiguration)) { + throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass()); + } + return processRouteConfiguration((RouteConfiguration) unpackedMessage, + args.filterRegistry, enableFaultInjection && isResourceV3); + } + + private static RdsUpdate processRouteConfiguration( + RouteConfiguration routeConfig, FilterRegistry filterRegistry, boolean parseHttpFilter) + throws ResourceInvalidException { + return new RdsUpdate(extractVirtualHosts(routeConfig, filterRegistry, parseHttpFilter)); + } + + static List extractVirtualHosts( + RouteConfiguration routeConfig, FilterRegistry filterRegistry, boolean parseHttpFilter) + throws ResourceInvalidException { + Map pluginConfigMap = new HashMap<>(); + ImmutableSet.Builder optionalPlugins = ImmutableSet.builder(); + + if (enableRouteLookup) { + List plugins = routeConfig.getClusterSpecifierPluginsList(); + for (ClusterSpecifierPlugin plugin : plugins) { + String pluginName = plugin.getExtension().getName(); + PluginConfig pluginConfig = parseClusterSpecifierPlugin(plugin); + if (pluginConfig != null) { + if (pluginConfigMap.put(pluginName, pluginConfig) != null) { + throw new ResourceInvalidException( + "Multiple ClusterSpecifierPlugins with the same name: " + pluginName); + } + } else { + // The plugin parsed successfully, and it's not supported, but it's marked as optional. + optionalPlugins.add(pluginName); + } + } + } + List virtualHosts = new ArrayList<>(routeConfig.getVirtualHostsCount()); + for (io.envoyproxy.envoy.config.route.v3.VirtualHost virtualHostProto + : routeConfig.getVirtualHostsList()) { + StructOrError virtualHost = + parseVirtualHost(virtualHostProto, filterRegistry, parseHttpFilter, pluginConfigMap, + optionalPlugins.build()); + if (virtualHost.getErrorDetail() != null) { + throw new ResourceInvalidException( + "RouteConfiguration contains invalid virtual host: " + virtualHost.getErrorDetail()); + } + virtualHosts.add(virtualHost.getStruct()); + } + return virtualHosts; + } + + private static StructOrError parseVirtualHost( + io.envoyproxy.envoy.config.route.v3.VirtualHost proto, FilterRegistry filterRegistry, + boolean parseHttpFilter, Map pluginConfigMap, + Set optionalPlugins) { + String name = proto.getName(); + List routes = new ArrayList<>(proto.getRoutesCount()); + for (io.envoyproxy.envoy.config.route.v3.Route routeProto : proto.getRoutesList()) { + StructOrError route = parseRoute( + routeProto, filterRegistry, parseHttpFilter, pluginConfigMap, optionalPlugins); + if (route == null) { + continue; + } + if (route.getErrorDetail() != null) { + return StructOrError.fromError( + "Virtual host [" + name + "] contains invalid route : " + route.getErrorDetail()); + } + routes.add(route.getStruct()); + } + if (!parseHttpFilter) { + return StructOrError.fromStruct(VirtualHost.create( + name, proto.getDomainsList(), routes, new HashMap())); + } + StructOrError> overrideConfigs = + parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry); + if (overrideConfigs.getErrorDetail() != null) { + return StructOrError.fromError( + "VirtualHost [" + proto.getName() + "] contains invalid HttpFilter config: " + + overrideConfigs.getErrorDetail()); + } + return StructOrError.fromStruct(VirtualHost.create( + name, proto.getDomainsList(), routes, overrideConfigs.getStruct())); + } + + @VisibleForTesting + static StructOrError> parseOverrideFilterConfigs( + Map rawFilterConfigMap, FilterRegistry filterRegistry) { + Map overrideConfigs = new HashMap<>(); + for (String name : rawFilterConfigMap.keySet()) { + Any anyConfig = rawFilterConfigMap.get(name); + String typeUrl = anyConfig.getTypeUrl(); + boolean isOptional = false; + if (typeUrl.equals(TYPE_URL_FILTER_CONFIG)) { + io.envoyproxy.envoy.config.route.v3.FilterConfig filterConfig; + try { + filterConfig = + anyConfig.unpack(io.envoyproxy.envoy.config.route.v3.FilterConfig.class); + } catch (InvalidProtocolBufferException e) { + return StructOrError.fromError( + "FilterConfig [" + name + "] contains invalid proto: " + e); + } + isOptional = filterConfig.getIsOptional(); + anyConfig = filterConfig.getConfig(); + typeUrl = anyConfig.getTypeUrl(); + } + Message rawConfig = anyConfig; + try { + if (typeUrl.equals(TYPE_URL_TYPED_STRUCT_UDPA)) { + TypedStruct typedStruct = anyConfig.unpack(TypedStruct.class); + typeUrl = typedStruct.getTypeUrl(); + rawConfig = typedStruct.getValue(); + } else if (typeUrl.equals(TYPE_URL_TYPED_STRUCT)) { + com.github.xds.type.v3.TypedStruct newTypedStruct = + anyConfig.unpack(com.github.xds.type.v3.TypedStruct.class); + typeUrl = newTypedStruct.getTypeUrl(); + rawConfig = newTypedStruct.getValue(); + } + } catch (InvalidProtocolBufferException e) { + return StructOrError.fromError( + "FilterConfig [" + name + "] contains invalid proto: " + e); + } + Filter filter = filterRegistry.get(typeUrl); + if (filter == null) { + if (isOptional) { + continue; + } + return StructOrError.fromError( + "HttpFilter [" + name + "](" + typeUrl + ") is required but unsupported"); + } + ConfigOrError filterConfig = + filter.parseFilterConfigOverride(rawConfig); + if (filterConfig.errorDetail != null) { + return StructOrError.fromError( + "Invalid filter config for HttpFilter [" + name + "]: " + filterConfig.errorDetail); + } + overrideConfigs.put(name, filterConfig.config); + } + return StructOrError.fromStruct(overrideConfigs); + } + + @VisibleForTesting + @Nullable + static StructOrError parseRoute( + io.envoyproxy.envoy.config.route.v3.Route proto, FilterRegistry filterRegistry, + boolean parseHttpFilter, Map pluginConfigMap, + Set optionalPlugins) { + StructOrError routeMatch = parseRouteMatch(proto.getMatch()); + if (routeMatch == null) { + return null; + } + if (routeMatch.getErrorDetail() != null) { + return StructOrError.fromError( + "Route [" + proto.getName() + "] contains invalid RouteMatch: " + + routeMatch.getErrorDetail()); + } + + Map overrideConfigs = Collections.emptyMap(); + if (parseHttpFilter) { + StructOrError> overrideConfigsOrError = + parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry); + if (overrideConfigsOrError.getErrorDetail() != null) { + return StructOrError.fromError( + "Route [" + proto.getName() + "] contains invalid HttpFilter config: " + + overrideConfigsOrError.getErrorDetail()); + } + overrideConfigs = overrideConfigsOrError.getStruct(); + } + + switch (proto.getActionCase()) { + case ROUTE: + StructOrError routeAction = + parseRouteAction(proto.getRoute(), filterRegistry, parseHttpFilter, pluginConfigMap, + optionalPlugins); + if (routeAction == null) { + return null; + } + if (routeAction.getErrorDetail() != null) { + return StructOrError.fromError( + "Route [" + proto.getName() + "] contains invalid RouteAction: " + + routeAction.getErrorDetail()); + } + return StructOrError.fromStruct( + Route.forAction(routeMatch.getStruct(), routeAction.getStruct(), overrideConfigs)); + case NON_FORWARDING_ACTION: + return StructOrError.fromStruct( + Route.forNonForwardingAction(routeMatch.getStruct(), overrideConfigs)); + case REDIRECT: + case DIRECT_RESPONSE: + case FILTER_ACTION: + case ACTION_NOT_SET: + default: + return StructOrError.fromError( + "Route [" + proto.getName() + "] with unknown action type: " + proto.getActionCase()); + } + } + + @VisibleForTesting + @Nullable + static StructOrError parseRouteMatch( + io.envoyproxy.envoy.config.route.v3.RouteMatch proto) { + if (proto.getQueryParametersCount() != 0) { + return null; + } + StructOrError pathMatch = parsePathMatcher(proto); + if (pathMatch.getErrorDetail() != null) { + return StructOrError.fromError(pathMatch.getErrorDetail()); + } + + FractionMatcher fractionMatch = null; + if (proto.hasRuntimeFraction()) { + StructOrError parsedFraction = + parseFractionMatcher(proto.getRuntimeFraction().getDefaultValue()); + if (parsedFraction.getErrorDetail() != null) { + return StructOrError.fromError(parsedFraction.getErrorDetail()); + } + fractionMatch = parsedFraction.getStruct(); + } + + List headerMatchers = new ArrayList<>(); + for (io.envoyproxy.envoy.config.route.v3.HeaderMatcher hmProto : proto.getHeadersList()) { + StructOrError headerMatcher = parseHeaderMatcher(hmProto); + if (headerMatcher.getErrorDetail() != null) { + return StructOrError.fromError(headerMatcher.getErrorDetail()); + } + headerMatchers.add(headerMatcher.getStruct()); + } + + return StructOrError.fromStruct(RouteMatch.create( + pathMatch.getStruct(), headerMatchers, fractionMatch)); + } + + @VisibleForTesting + static StructOrError parsePathMatcher( + io.envoyproxy.envoy.config.route.v3.RouteMatch proto) { + boolean caseSensitive = proto.getCaseSensitive().getValue(); + switch (proto.getPathSpecifierCase()) { + case PREFIX: + return StructOrError.fromStruct( + PathMatcher.fromPrefix(proto.getPrefix(), caseSensitive)); + case PATH: + return StructOrError.fromStruct(PathMatcher.fromPath(proto.getPath(), caseSensitive)); + case SAFE_REGEX: + String rawPattern = proto.getSafeRegex().getRegex(); + Pattern safeRegEx; + try { + safeRegEx = Pattern.compile(rawPattern); + } catch (PatternSyntaxException e) { + return StructOrError.fromError("Malformed safe regex pattern: " + e.getMessage()); + } + return StructOrError.fromStruct(PathMatcher.fromRegEx(safeRegEx)); + case PATHSPECIFIER_NOT_SET: + default: + return StructOrError.fromError("Unknown path match type"); + } + } + + private static StructOrError parseFractionMatcher(FractionalPercent proto) { + int numerator = proto.getNumerator(); + int denominator = 0; + switch (proto.getDenominator()) { + case HUNDRED: + denominator = 100; + break; + case TEN_THOUSAND: + denominator = 10_000; + break; + case MILLION: + denominator = 1_000_000; + break; + case UNRECOGNIZED: + default: + return StructOrError.fromError( + "Unrecognized fractional percent denominator: " + proto.getDenominator()); + } + return StructOrError.fromStruct(FractionMatcher.create(numerator, denominator)); + } + + @VisibleForTesting + static StructOrError parseHeaderMatcher( + io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto) { + switch (proto.getHeaderMatchSpecifierCase()) { + case EXACT_MATCH: + return StructOrError.fromStruct(HeaderMatcher.forExactValue( + proto.getName(), proto.getExactMatch(), proto.getInvertMatch())); + case SAFE_REGEX_MATCH: + String rawPattern = proto.getSafeRegexMatch().getRegex(); + Pattern safeRegExMatch; + try { + safeRegExMatch = Pattern.compile(rawPattern); + } catch (PatternSyntaxException e) { + return StructOrError.fromError( + "HeaderMatcher [" + proto.getName() + "] contains malformed safe regex pattern: " + + e.getMessage()); + } + return StructOrError.fromStruct(Matchers.HeaderMatcher.forSafeRegEx( + proto.getName(), safeRegExMatch, proto.getInvertMatch())); + case RANGE_MATCH: + Matchers.HeaderMatcher.Range rangeMatch = Matchers.HeaderMatcher.Range.create( + proto.getRangeMatch().getStart(), proto.getRangeMatch().getEnd()); + return StructOrError.fromStruct(Matchers.HeaderMatcher.forRange( + proto.getName(), rangeMatch, proto.getInvertMatch())); + case PRESENT_MATCH: + return StructOrError.fromStruct(Matchers.HeaderMatcher.forPresent( + proto.getName(), proto.getPresentMatch(), proto.getInvertMatch())); + case PREFIX_MATCH: + return StructOrError.fromStruct(Matchers.HeaderMatcher.forPrefix( + proto.getName(), proto.getPrefixMatch(), proto.getInvertMatch())); + case SUFFIX_MATCH: + return StructOrError.fromStruct(Matchers.HeaderMatcher.forSuffix( + proto.getName(), proto.getSuffixMatch(), proto.getInvertMatch())); + case HEADERMATCHSPECIFIER_NOT_SET: + default: + return StructOrError.fromError("Unknown header matcher type"); + } + } + + /** + * Parses the RouteAction config. The returned result may contain a (parsed form) + * {@link RouteAction} or an error message. Returns {@code null} if the RouteAction + * should be ignored. + */ + @VisibleForTesting + @Nullable + static StructOrError parseRouteAction( + io.envoyproxy.envoy.config.route.v3.RouteAction proto, FilterRegistry filterRegistry, + boolean parseHttpFilter, Map pluginConfigMap, + Set optionalPlugins) { + Long timeoutNano = null; + if (proto.hasMaxStreamDuration()) { + io.envoyproxy.envoy.config.route.v3.RouteAction.MaxStreamDuration maxStreamDuration + = proto.getMaxStreamDuration(); + if (maxStreamDuration.hasGrpcTimeoutHeaderMax()) { + timeoutNano = Durations.toNanos(maxStreamDuration.getGrpcTimeoutHeaderMax()); + } else if (maxStreamDuration.hasMaxStreamDuration()) { + timeoutNano = Durations.toNanos(maxStreamDuration.getMaxStreamDuration()); + } + } + RetryPolicy retryPolicy = null; + if (enableRetry && proto.hasRetryPolicy()) { + StructOrError retryPolicyOrError = parseRetryPolicy(proto.getRetryPolicy()); + if (retryPolicyOrError != null) { + if (retryPolicyOrError.getErrorDetail() != null) { + return StructOrError.fromError(retryPolicyOrError.getErrorDetail()); + } + retryPolicy = retryPolicyOrError.getStruct(); + } + } + List hashPolicies = new ArrayList<>(); + for (io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy config + : proto.getHashPolicyList()) { + HashPolicy policy = null; + boolean terminal = config.getTerminal(); + switch (config.getPolicySpecifierCase()) { + case HEADER: + io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy.Header headerCfg = + config.getHeader(); + Pattern regEx = null; + String regExSubstitute = null; + if (headerCfg.hasRegexRewrite() && headerCfg.getRegexRewrite().hasPattern() + && headerCfg.getRegexRewrite().getPattern().hasGoogleRe2()) { + regEx = Pattern.compile(headerCfg.getRegexRewrite().getPattern().getRegex()); + regExSubstitute = headerCfg.getRegexRewrite().getSubstitution(); + } + policy = HashPolicy.forHeader( + terminal, headerCfg.getHeaderName(), regEx, regExSubstitute); + break; + case FILTER_STATE: + if (config.getFilterState().getKey().equals(HASH_POLICY_FILTER_STATE_KEY)) { + policy = HashPolicy.forChannelId(terminal); + } + break; + default: + // Ignore + } + if (policy != null) { + hashPolicies.add(policy); + } + } + + switch (proto.getClusterSpecifierCase()) { + case CLUSTER: + return StructOrError.fromStruct(RouteAction.forCluster( + proto.getCluster(), hashPolicies, timeoutNano, retryPolicy)); + case CLUSTER_HEADER: + return null; + case WEIGHTED_CLUSTERS: + List clusterWeights + = proto.getWeightedClusters().getClustersList(); + if (clusterWeights.isEmpty()) { + return StructOrError.fromError("No cluster found in weighted cluster list"); + } + List weightedClusters = new ArrayList<>(); + for (io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight clusterWeight + : clusterWeights) { + StructOrError clusterWeightOrError = + parseClusterWeight(clusterWeight, filterRegistry, parseHttpFilter); + if (clusterWeightOrError.getErrorDetail() != null) { + return StructOrError.fromError("RouteAction contains invalid ClusterWeight: " + + clusterWeightOrError.getErrorDetail()); + } + weightedClusters.add(clusterWeightOrError.getStruct()); + } + // TODO(chengyuanzhang): validate if the sum of weights equals to total weight. + return StructOrError.fromStruct(VirtualHost.Route.RouteAction.forWeightedClusters( + weightedClusters, hashPolicies, timeoutNano, retryPolicy)); + case CLUSTER_SPECIFIER_PLUGIN: + if (enableRouteLookup) { + String pluginName = proto.getClusterSpecifierPlugin(); + PluginConfig pluginConfig = pluginConfigMap.get(pluginName); + if (pluginConfig == null) { + // Skip route if the plugin is not registered, but it's optional. + if (optionalPlugins.contains(pluginName)) { + return null; + } + return StructOrError.fromError( + "ClusterSpecifierPlugin for [" + pluginName + "] not found"); + } + NamedPluginConfig namedPluginConfig = NamedPluginConfig.create(pluginName, pluginConfig); + return StructOrError.fromStruct(VirtualHost.Route.RouteAction.forClusterSpecifierPlugin( + namedPluginConfig, hashPolicies, timeoutNano, retryPolicy)); + } else { + return null; + } + case CLUSTERSPECIFIER_NOT_SET: + default: + return null; + } + } + + @Nullable // Return null if we ignore the given policy. + private static StructOrError parseRetryPolicy( + io.envoyproxy.envoy.config.route.v3.RetryPolicy retryPolicyProto) { + int maxAttempts = 2; + if (retryPolicyProto.hasNumRetries()) { + maxAttempts = retryPolicyProto.getNumRetries().getValue() + 1; + } + Duration initialBackoff = Durations.fromMillis(25); + Duration maxBackoff = Durations.fromMillis(250); + if (retryPolicyProto.hasRetryBackOff()) { + RetryBackOff retryBackOff = retryPolicyProto.getRetryBackOff(); + if (!retryBackOff.hasBaseInterval()) { + return StructOrError.fromError("No base_interval specified in retry_backoff"); + } + Duration originalInitialBackoff = initialBackoff = retryBackOff.getBaseInterval(); + if (Durations.compare(initialBackoff, Durations.ZERO) <= 0) { + return StructOrError.fromError("base_interval in retry_backoff must be positive"); + } + if (Durations.compare(initialBackoff, Durations.fromMillis(1)) < 0) { + initialBackoff = Durations.fromMillis(1); + } + if (retryBackOff.hasMaxInterval()) { + maxBackoff = retryPolicyProto.getRetryBackOff().getMaxInterval(); + if (Durations.compare(maxBackoff, originalInitialBackoff) < 0) { + return StructOrError.fromError( + "max_interval in retry_backoff cannot be less than base_interval"); + } + if (Durations.compare(maxBackoff, Durations.fromMillis(1)) < 0) { + maxBackoff = Durations.fromMillis(1); + } + } else { + maxBackoff = Durations.fromNanos(Durations.toNanos(initialBackoff) * 10); + } + } + Iterable retryOns = + Splitter.on(',').omitEmptyStrings().trimResults().split(retryPolicyProto.getRetryOn()); + ImmutableList.Builder retryableStatusCodesBuilder = ImmutableList.builder(); + for (String retryOn : retryOns) { + Status.Code code; + try { + code = Status.Code.valueOf(retryOn.toUpperCase(Locale.US).replace('-', '_')); + } catch (IllegalArgumentException e) { + // unsupported value, such as "5xx" + continue; + } + if (!SUPPORTED_RETRYABLE_CODES.contains(code)) { + // unsupported value + continue; + } + retryableStatusCodesBuilder.add(code); + } + List retryableStatusCodes = retryableStatusCodesBuilder.build(); + return StructOrError.fromStruct( + VirtualHost.Route.RouteAction.RetryPolicy.create( + maxAttempts, retryableStatusCodes, initialBackoff, maxBackoff, + /* perAttemptRecvTimeout= */ null)); + } + + @VisibleForTesting + static StructOrError parseClusterWeight( + io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight proto, + FilterRegistry filterRegistry, boolean parseHttpFilter) { + if (!parseHttpFilter) { + return StructOrError.fromStruct(ClusterWeight.create(proto.getName(), + proto.getWeight().getValue(), new HashMap())); + } + StructOrError> overrideConfigs = + parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry); + if (overrideConfigs.getErrorDetail() != null) { + return StructOrError.fromError( + "ClusterWeight [" + proto.getName() + "] contains invalid HttpFilter config: " + + overrideConfigs.getErrorDetail()); + } + return StructOrError.fromStruct(VirtualHost.Route.RouteAction.ClusterWeight.create( + proto.getName(), proto.getWeight().getValue(), overrideConfigs.getStruct())); + } + + @Nullable // null if the plugin is not supported, but it's marked as optional. + private static PluginConfig parseClusterSpecifierPlugin(ClusterSpecifierPlugin pluginProto) + throws ResourceInvalidException { + return parseClusterSpecifierPlugin( + pluginProto, ClusterSpecifierPluginRegistry.getDefaultRegistry()); + } + + @Nullable // null if the plugin is not supported, but it's marked as optional. + @VisibleForTesting + static PluginConfig parseClusterSpecifierPlugin( + ClusterSpecifierPlugin pluginProto, ClusterSpecifierPluginRegistry registry) + throws ResourceInvalidException { + TypedExtensionConfig extension = pluginProto.getExtension(); + String pluginName = extension.getName(); + Any anyConfig = extension.getTypedConfig(); + String typeUrl = anyConfig.getTypeUrl(); + Message rawConfig = anyConfig; + if (typeUrl.equals(TYPE_URL_TYPED_STRUCT_UDPA) || typeUrl.equals(TYPE_URL_TYPED_STRUCT)) { + try { + TypedStruct typedStruct = unpackCompatibleType( + anyConfig, TypedStruct.class, TYPE_URL_TYPED_STRUCT_UDPA, TYPE_URL_TYPED_STRUCT); + typeUrl = typedStruct.getTypeUrl(); + rawConfig = typedStruct.getValue(); + } catch (InvalidProtocolBufferException e) { + throw new ResourceInvalidException( + "ClusterSpecifierPlugin [" + pluginName + "] contains invalid proto", e); + } + } + io.grpc.xds.ClusterSpecifierPlugin plugin = registry.get(typeUrl); + if (plugin == null) { + if (!pluginProto.getIsOptional()) { + throw new ResourceInvalidException("Unsupported ClusterSpecifierPlugin type: " + typeUrl); + } + return null; + } + ConfigOrError pluginConfigOrError = plugin.parsePlugin(rawConfig); + if (pluginConfigOrError.errorDetail != null) { + throw new ResourceInvalidException(pluginConfigOrError.errorDetail); + } + return pluginConfigOrError.config; + } + + static final class RdsUpdate implements ResourceUpdate { + // The list virtual hosts that make up the route table. + final List virtualHosts; + + RdsUpdate(List virtualHosts) { + this.virtualHosts = Collections.unmodifiableList( + new ArrayList<>(checkNotNull(virtualHosts, "virtualHosts"))); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("virtualHosts", virtualHosts) + .toString(); + } + + @Override + public int hashCode() { + return Objects.hash(virtualHosts); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RdsUpdate that = (RdsUpdate) o; + return Objects.equals(virtualHosts, that.virtualHosts); + } + } +} diff --git a/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java b/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java index b269e7f9bcf..a58a3f6bc24 100644 --- a/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java +++ b/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java @@ -50,11 +50,10 @@ import io.grpc.xds.FilterChainMatchingProtocolNegotiators.FilterChainMatchingHandler.FilterChainSelector; import io.grpc.xds.ThreadSafeRandom.ThreadSafeRandomImpl; import io.grpc.xds.VirtualHost.Route; -import io.grpc.xds.XdsClient.LdsResourceWatcher; -import io.grpc.xds.XdsClient.LdsUpdate; -import io.grpc.xds.XdsClient.RdsResourceWatcher; -import io.grpc.xds.XdsClient.RdsUpdate; +import io.grpc.xds.XdsClient.ResourceWatcher; +import io.grpc.xds.XdsListenerResource.LdsUpdate; import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory; +import io.grpc.xds.XdsRouteConfigureResource.RdsUpdate; import io.grpc.xds.XdsServerBuilder.XdsServingStatusListener; import io.grpc.xds.internal.security.SslContextProviderSupplier; import java.io.IOException; @@ -344,7 +343,7 @@ public void run() { } } - private final class DiscoveryState implements LdsResourceWatcher { + private final class DiscoveryState implements ResourceWatcher { private final String resourceName; // RDS resource name is the key. private final Map routeDiscoveryStates = new HashMap<>(); @@ -368,7 +367,7 @@ public Listener interceptCall(ServerCall call, private DiscoveryState(String resourceName) { this.resourceName = checkNotNull(resourceName, "resourceName"); - xdsClient.watchLdsResource(resourceName, this); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(), resourceName, this); } @Override @@ -402,7 +401,8 @@ public void run() { if (rdsState == null) { rdsState = new RouteDiscoveryState(hcm.rdsName()); routeDiscoveryStates.put(hcm.rdsName(), rdsState); - xdsClient.watchRdsResource(hcm.rdsName(), rdsState); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(), + hcm.rdsName(), rdsState); } if (rdsState.isPending) { pendingRds.add(hcm.rdsName()); @@ -412,7 +412,8 @@ public void run() { } for (Map.Entry entry: routeDiscoveryStates.entrySet()) { if (!allRds.contains(entry.getKey())) { - xdsClient.cancelRdsResourceWatch(entry.getKey(), entry.getValue()); + xdsClient.cancelXdsResourceWatch(XdsRouteConfigureResource.getInstance(), + entry.getKey(), entry.getValue()); } } routeDiscoveryStates.keySet().retainAll(allRds); @@ -458,7 +459,7 @@ private void shutdown() { stopped = true; cleanUpRouteDiscoveryStates(); logger.log(Level.FINE, "Stop watching LDS resource {0}", resourceName); - xdsClient.cancelLdsResourceWatch(resourceName, this); + xdsClient.cancelXdsResourceWatch(XdsListenerResource.getInstance(), resourceName, this); List toRelease = getSuppliersInUse(); filterChainSelectorManager.updateSelector(FilterChainSelector.NO_FILTER_CHAIN); for (SslContextProviderSupplier s: toRelease) { @@ -588,7 +589,8 @@ private void cleanUpRouteDiscoveryStates() { for (RouteDiscoveryState rdsState : routeDiscoveryStates.values()) { String rdsName = rdsState.resourceName; logger.log(Level.FINE, "Stop watching RDS resource {0}", rdsName); - xdsClient.cancelRdsResourceWatch(rdsName, rdsState); + xdsClient.cancelXdsResourceWatch(XdsRouteConfigureResource.getInstance(), rdsName, + rdsState); } routeDiscoveryStates.clear(); savedRdsRoutingConfigRef.clear(); @@ -626,7 +628,7 @@ private void releaseSuppliersInFlight() { } } - private final class RouteDiscoveryState implements RdsResourceWatcher { + private final class RouteDiscoveryState implements ResourceWatcher { private final String resourceName; private ImmutableList savedVirtualHosts; private boolean isPending = true; diff --git a/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java b/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java index c211551f1fe..1fd81e606a5 100644 --- a/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java +++ b/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java @@ -17,6 +17,7 @@ package io.grpc.xds; import static com.google.common.truth.Truth.assertThat; +import static io.grpc.xds.AbstractXdsClient.ResourceType.CDS; import static io.grpc.xds.XdsLbPolicies.CLUSTER_RESOLVER_POLICY_NAME; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; @@ -55,7 +56,7 @@ import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.LeastRequestLoadBalancer.LeastRequestConfig; import io.grpc.xds.RingHashLoadBalancer.RingHashConfig; -import io.grpc.xds.XdsClient.CdsUpdate; +import io.grpc.xds.XdsClusterResource.CdsUpdate; import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; import java.util.ArrayList; import java.util.Arrays; @@ -91,7 +92,7 @@ public class CdsLoadBalancer2Test { null, null, null, null, SuccessRateEjection.create(null, null, null, null), null); - private final SynchronizationContext syncContext = new SynchronizationContext( + private static final SynchronizationContext syncContext = new SynchronizationContext( new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { @@ -649,17 +650,24 @@ public void shutdown() { } } - private static final class FakeXdsClient extends XdsClient { - private final Map watchers = new HashMap<>(); + private final class FakeXdsClient extends XdsClient { + private final Map> watchers = new HashMap<>(); @Override - void watchCdsResource(String resourceName, CdsResourceWatcher watcher) { + @SuppressWarnings("unchecked") + void watchXdsResource(XdsResourceType type, String resourceName, + ResourceWatcher watcher) { + assertThat(type.typeName()).isEqualTo(CDS); assertThat(watchers).doesNotContainKey(resourceName); - watchers.put(resourceName, watcher); + watchers.put(resourceName, (ResourceWatcher)watcher); } @Override - void cancelCdsResourceWatch(String resourceName, CdsResourceWatcher watcher) { + @SuppressWarnings("unchecked") + void cancelXdsResourceWatch(XdsResourceType type, + String resourceName, + ResourceWatcher watcher) { + assertThat(type.typeName()).isEqualTo(CDS); assertThat(watchers).containsKey(resourceName); watchers.remove(resourceName); } @@ -677,7 +685,7 @@ private void deliverResourceNotExist(String clusterName) { } private void deliverError(Status error) { - for (CdsResourceWatcher watcher : watchers.values()) { + for (ResourceWatcher watcher : watchers.values()) { watcher.onError(error); } } diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java index fdc39d6f6f8..dae17bfb89d 100644 --- a/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java +++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java @@ -118,7 +118,6 @@ import io.grpc.xds.AbstractXdsClient.ResourceType; import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.ClientXdsClient.ResourceInvalidException; -import io.grpc.xds.ClientXdsClient.StructOrError; import io.grpc.xds.ClusterSpecifierPlugin.NamedPluginConfig; import io.grpc.xds.ClusterSpecifierPlugin.PluginConfig; import io.grpc.xds.Endpoints.LbEndpoint; @@ -131,7 +130,8 @@ import io.grpc.xds.VirtualHost.Route.RouteAction.HashPolicy; import io.grpc.xds.VirtualHost.Route.RouteMatch; import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher; -import io.grpc.xds.XdsClient.CdsUpdate; +import io.grpc.xds.XdsClusterResource.CdsUpdate; +import io.grpc.xds.XdsResourceType.StructOrError; import io.grpc.xds.internal.Matchers.FractionMatcher; import io.grpc.xds.internal.Matchers.HeaderMatcher; import java.util.Arrays; @@ -168,22 +168,22 @@ public class ClientXdsClientDataTest { @Before public void setUp() { - originalEnableRetry = ClientXdsClient.enableRetry; + originalEnableRetry = XdsResourceType.enableRetry; assertThat(originalEnableRetry).isTrue(); - originalEnableRbac = ClientXdsClient.enableRbac; + originalEnableRbac = XdsResourceType.enableRbac; assertThat(originalEnableRbac).isTrue(); - originalEnableRouteLookup = ClientXdsClient.enableRouteLookup; + originalEnableRouteLookup = XdsResourceType.enableRouteLookup; assertThat(originalEnableRouteLookup).isFalse(); - originalEnableLeastRequest = ClientXdsClient.enableLeastRequest; + originalEnableLeastRequest = XdsResourceType.enableLeastRequest; assertThat(originalEnableLeastRequest).isFalse(); } @After public void tearDown() { - ClientXdsClient.enableRetry = originalEnableRetry; - ClientXdsClient.enableRbac = originalEnableRbac; - ClientXdsClient.enableRouteLookup = originalEnableRouteLookup; - ClientXdsClient.enableLeastRequest = originalEnableLeastRequest; + XdsResourceType.enableRetry = originalEnableRetry; + XdsResourceType.enableRbac = originalEnableRbac; + XdsResourceType.enableRouteLookup = originalEnableRouteLookup; + XdsResourceType.enableLeastRequest = originalEnableLeastRequest; } @Test @@ -198,7 +198,7 @@ public void parseRoute_withRouteAction() { io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() .setCluster("cluster-foo")) .build(); - StructOrError struct = ClientXdsClient.parseRoute( + StructOrError struct = XdsRouteConfigureResource.parseRoute( proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct()) @@ -221,7 +221,7 @@ public void parseRoute_withNonForwardingAction() { .setPath("/service/method")) .setNonForwardingAction(NonForwardingAction.getDefaultInstance()) .build(); - StructOrError struct = ClientXdsClient.parseRoute( + StructOrError struct = XdsRouteConfigureResource.parseRoute( proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getStruct()) .isEqualTo( @@ -240,7 +240,7 @@ public void parseRoute_withUnsupportedActionTypes() { .setMatch(io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPath("")) .setRedirect(RedirectAction.getDefaultInstance()) .build(); - res = ClientXdsClient.parseRoute( + res = XdsRouteConfigureResource.parseRoute( redirectRoute, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(res.getStruct()).isNull(); assertThat(res.getErrorDetail()) @@ -252,7 +252,7 @@ public void parseRoute_withUnsupportedActionTypes() { .setMatch(io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPath("")) .setDirectResponse(DirectResponseAction.getDefaultInstance()) .build(); - res = ClientXdsClient.parseRoute( + res = XdsRouteConfigureResource.parseRoute( directResponseRoute, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(res.getStruct()).isNull(); assertThat(res.getErrorDetail()) @@ -264,7 +264,7 @@ public void parseRoute_withUnsupportedActionTypes() { .setMatch(io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPath("")) .setFilterAction(FilterAction.getDefaultInstance()) .build(); - res = ClientXdsClient.parseRoute( + res = XdsRouteConfigureResource.parseRoute( filterRoute, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(res.getStruct()).isNull(); assertThat(res.getErrorDetail()) @@ -286,7 +286,7 @@ public void parseRoute_skipRouteWithUnsupportedMatcher() { io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() .setCluster("cluster-foo")) .build(); - assertThat(ClientXdsClient.parseRoute( + assertThat(XdsRouteConfigureResource.parseRoute( proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of())) .isNull(); } @@ -303,7 +303,7 @@ public void parseRoute_skipRouteWithUnsupportedAction() { io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() .setClusterHeader("cluster header")) // cluster_header action not supported .build(); - assertThat(ClientXdsClient.parseRoute( + assertThat(XdsRouteConfigureResource.parseRoute( proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of())) .isNull(); } @@ -323,7 +323,7 @@ public void parseRouteMatch_withHeaderMatcher() { .setName(":method") .setExactMatch("PUT")) .build(); - StructOrError struct = ClientXdsClient.parseRouteMatch(proto); + StructOrError struct = XdsRouteConfigureResource.parseRouteMatch(proto); assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct()) .isEqualTo( @@ -347,7 +347,7 @@ public void parseRouteMatch_withRuntimeFractionMatcher() { .setNumerator(30) .setDenominator(FractionalPercent.DenominatorType.HUNDRED))) .build(); - StructOrError struct = ClientXdsClient.parseRouteMatch(proto); + StructOrError struct = XdsRouteConfigureResource.parseRouteMatch(proto); assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct()) .isEqualTo( @@ -362,7 +362,7 @@ public void parsePathMatcher_withFullPath() { io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder() .setPath("/service/method") .build(); - StructOrError struct = ClientXdsClient.parsePathMatcher(proto); + StructOrError struct = XdsRouteConfigureResource.parsePathMatcher(proto); assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct()).isEqualTo( PathMatcher.fromPath("/service/method", false)); @@ -372,7 +372,7 @@ public void parsePathMatcher_withFullPath() { public void parsePathMatcher_withPrefix() { io.envoyproxy.envoy.config.route.v3.RouteMatch proto = io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPrefix("/").build(); - StructOrError struct = ClientXdsClient.parsePathMatcher(proto); + StructOrError struct = XdsRouteConfigureResource.parsePathMatcher(proto); assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct()).isEqualTo( PathMatcher.fromPrefix("/", false)); @@ -384,7 +384,7 @@ public void parsePathMatcher_withSafeRegEx() { io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder() .setSafeRegex(RegexMatcher.newBuilder().setRegex(".")) .build(); - StructOrError struct = ClientXdsClient.parsePathMatcher(proto); + StructOrError struct = XdsRouteConfigureResource.parsePathMatcher(proto); assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct()).isEqualTo(PathMatcher.fromRegEx(Pattern.compile("."))); } @@ -397,7 +397,7 @@ public void parseHeaderMatcher_withExactMatch() { .setName(":method") .setExactMatch("PUT") .build(); - StructOrError struct1 = ClientXdsClient.parseHeaderMatcher(proto); + StructOrError struct1 = XdsRouteConfigureResource.parseHeaderMatcher(proto); assertThat(struct1.getErrorDetail()).isNull(); assertThat(struct1.getStruct()).isEqualTo( HeaderMatcher.forExactValue(":method", "PUT", false)); @@ -411,7 +411,7 @@ public void parseHeaderMatcher_withSafeRegExMatch() { .setName(":method") .setSafeRegexMatch(RegexMatcher.newBuilder().setRegex("P*")) .build(); - StructOrError struct3 = ClientXdsClient.parseHeaderMatcher(proto); + StructOrError struct3 = XdsRouteConfigureResource.parseHeaderMatcher(proto); assertThat(struct3.getErrorDetail()).isNull(); assertThat(struct3.getStruct()).isEqualTo( HeaderMatcher.forSafeRegEx(":method", Pattern.compile("P*"), false)); @@ -424,7 +424,7 @@ public void parseHeaderMatcher_withRangeMatch() { .setName("timeout") .setRangeMatch(Int64Range.newBuilder().setStart(10L).setEnd(20L)) .build(); - StructOrError struct4 = ClientXdsClient.parseHeaderMatcher(proto); + StructOrError struct4 = XdsRouteConfigureResource.parseHeaderMatcher(proto); assertThat(struct4.getErrorDetail()).isNull(); assertThat(struct4.getStruct()).isEqualTo( HeaderMatcher.forRange("timeout", HeaderMatcher.Range.create(10L, 20L), false)); @@ -437,7 +437,7 @@ public void parseHeaderMatcher_withPresentMatch() { .setName("user-agent") .setPresentMatch(true) .build(); - StructOrError struct5 = ClientXdsClient.parseHeaderMatcher(proto); + StructOrError struct5 = XdsRouteConfigureResource.parseHeaderMatcher(proto); assertThat(struct5.getErrorDetail()).isNull(); assertThat(struct5.getStruct()).isEqualTo( HeaderMatcher.forPresent("user-agent", true, false)); @@ -451,7 +451,7 @@ public void parseHeaderMatcher_withPrefixMatch() { .setName("authority") .setPrefixMatch("service-foo") .build(); - StructOrError struct6 = ClientXdsClient.parseHeaderMatcher(proto); + StructOrError struct6 = XdsRouteConfigureResource.parseHeaderMatcher(proto); assertThat(struct6.getErrorDetail()).isNull(); assertThat(struct6.getStruct()).isEqualTo( HeaderMatcher.forPrefix("authority", "service-foo", false)); @@ -465,7 +465,7 @@ public void parseHeaderMatcher_withSuffixMatch() { .setName("authority") .setSuffixMatch("googleapis.com") .build(); - StructOrError struct7 = ClientXdsClient.parseHeaderMatcher(proto); + StructOrError struct7 = XdsRouteConfigureResource.parseHeaderMatcher(proto); assertThat(struct7.getErrorDetail()).isNull(); assertThat(struct7.getStruct()).isEqualTo( HeaderMatcher.forSuffix("authority", "googleapis.com", false)); @@ -479,7 +479,7 @@ public void parseHeaderMatcher_malformedRegExPattern() { .setName(":method") .setSafeRegexMatch(RegexMatcher.newBuilder().setRegex("[")) .build(); - StructOrError struct = ClientXdsClient.parseHeaderMatcher(proto); + StructOrError struct = XdsRouteConfigureResource.parseHeaderMatcher(proto); assertThat(struct.getErrorDetail()).isNotNull(); assertThat(struct.getStruct()).isNull(); } @@ -491,7 +491,7 @@ public void parseRouteAction_withCluster() { .setCluster("cluster-foo") .build(); StructOrError struct = - ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct().cluster()).isEqualTo("cluster-foo"); @@ -515,7 +515,7 @@ public void parseRouteAction_withWeightedCluster() { .setWeight(UInt32Value.newBuilder().setValue(70)))) .build(); StructOrError struct = - ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct().cluster()).isNull(); @@ -535,7 +535,7 @@ public void parseRouteAction_withTimeoutByGrpcTimeoutHeaderMax() { .setMaxStreamDuration(Durations.fromMillis(20L))) .build(); StructOrError struct = - ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getStruct().timeoutNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L)); } @@ -550,7 +550,7 @@ public void parseRouteAction_withTimeoutByMaxStreamDuration() { .setMaxStreamDuration(Durations.fromSeconds(5L))) .build(); StructOrError struct = - ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getStruct().timeoutNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L)); } @@ -562,14 +562,14 @@ public void parseRouteAction_withTimeoutUnset() { .setCluster("cluster-foo") .build(); StructOrError struct = - ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getStruct().timeoutNano()).isNull(); } @Test public void parseRouteAction_withRetryPolicy() { - ClientXdsClient.enableRetry = true; + XdsResourceType.enableRetry = true; RetryPolicy.Builder builder = RetryPolicy.newBuilder() .setNumRetries(UInt32Value.of(3)) .setRetryBackOff( @@ -585,7 +585,7 @@ public void parseRouteAction_withRetryPolicy() { .setRetryPolicy(builder.build()) .build(); StructOrError struct = - ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); RouteAction.RetryPolicy retryPolicy = struct.getStruct().retryPolicy(); assertThat(retryPolicy.maxAttempts()).isEqualTo(4); @@ -609,7 +609,7 @@ public void parseRouteAction_withRetryPolicy() { .setCluster("cluster-foo") .setRetryPolicy(builder.build()) .build(); - struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getStruct().retryPolicy()).isNotNull(); assertThat(struct.getStruct().retryPolicy().retryableStatusCodes()).isEmpty(); @@ -622,7 +622,7 @@ public void parseRouteAction_withRetryPolicy() { .setCluster("cluster-foo") .setRetryPolicy(builder) .build(); - struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getErrorDetail()).isEqualTo("No base_interval specified in retry_backoff"); @@ -632,7 +632,7 @@ public void parseRouteAction_withRetryPolicy() { .setCluster("cluster-foo") .setRetryPolicy(builder) .build(); - struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); retryPolicy = struct.getStruct().retryPolicy(); assertThat(retryPolicy.maxBackoff()).isEqualTo(Durations.fromMillis(500 * 10)); @@ -643,7 +643,7 @@ public void parseRouteAction_withRetryPolicy() { .setCluster("cluster-foo") .setRetryPolicy(builder) .build(); - struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getErrorDetail()) .isEqualTo("base_interval in retry_backoff must be positive"); @@ -656,7 +656,7 @@ public void parseRouteAction_withRetryPolicy() { .setCluster("cluster-foo") .setRetryPolicy(builder) .build(); - struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getErrorDetail()) .isEqualTo("max_interval in retry_backoff cannot be less than base_interval"); @@ -669,7 +669,7 @@ public void parseRouteAction_withRetryPolicy() { .setCluster("cluster-foo") .setRetryPolicy(builder) .build(); - struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getErrorDetail()) .isEqualTo("max_interval in retry_backoff cannot be less than base_interval"); @@ -682,7 +682,7 @@ public void parseRouteAction_withRetryPolicy() { .setCluster("cluster-foo") .setRetryPolicy(builder) .build(); - struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getStruct().retryPolicy().initialBackoff()) .isEqualTo(Durations.fromMillis(1)); @@ -698,7 +698,7 @@ public void parseRouteAction_withRetryPolicy() { .setCluster("cluster-foo") .setRetryPolicy(builder) .build(); - struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); retryPolicy = struct.getStruct().retryPolicy(); assertThat(retryPolicy.initialBackoff()).isEqualTo(Durations.fromMillis(25)); @@ -717,7 +717,7 @@ public void parseRouteAction_withRetryPolicy() { .setCluster("cluster-foo") .setRetryPolicy(builder) .build(); - struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getStruct().retryPolicy().retryableStatusCodes()) .containsExactly(Code.CANCELLED); @@ -735,7 +735,7 @@ public void parseRouteAction_withRetryPolicy() { .setCluster("cluster-foo") .setRetryPolicy(builder) .build(); - struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getStruct().retryPolicy().retryableStatusCodes()) .containsExactly(Code.CANCELLED); @@ -753,7 +753,7 @@ public void parseRouteAction_withRetryPolicy() { .setCluster("cluster-foo") .setRetryPolicy(builder) .build(); - struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getStruct().retryPolicy().retryableStatusCodes()) .containsExactly(Code.CANCELLED); @@ -784,14 +784,14 @@ public void parseRouteAction_withHashPolicies() { io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy.newBuilder() .setFilterState( FilterState.newBuilder() - .setKey(ClientXdsClient.HASH_POLICY_FILTER_STATE_KEY))) + .setKey(XdsResourceType.HASH_POLICY_FILTER_STATE_KEY))) .addHashPolicy( io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy.newBuilder() .setQueryParameter( QueryParameter.newBuilder().setName("param"))) // unsupported .build(); StructOrError struct = - ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); List policies = struct.getStruct().hashPolicies(); assertThat(policies).hasSize(2); @@ -811,20 +811,20 @@ public void parseRouteAction_custerSpecifierNotSet() { io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() .build(); StructOrError struct = - ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct).isNull(); } @Test public void parseRouteAction_clusterSpecifier_routeLookupDisabled() { - ClientXdsClient.enableRouteLookup = false; + XdsResourceType.enableRouteLookup = false; io.envoyproxy.envoy.config.route.v3.RouteAction proto = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() .setClusterSpecifierPlugin(CLUSTER_SPECIFIER_PLUGIN.name()) .build(); StructOrError struct = - ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct).isNull(); } @@ -837,7 +837,7 @@ public void parseClusterWeight() { .setWeight(UInt32Value.newBuilder().setValue(30)) .build(); ClusterWeight clusterWeight = - ClientXdsClient.parseClusterWeight(proto, filterRegistry, false).getStruct(); + XdsRouteConfigureResource.parseClusterWeight(proto, filterRegistry, false).getStruct(); assertThat(clusterWeight.name()).isEqualTo("cluster-foo"); assertThat(clusterWeight.weight()).isEqualTo(30); } @@ -859,7 +859,7 @@ public void parseLocalityLbEndpoints_withHealthyEndpoints() { .setHealthStatus(io.envoyproxy.envoy.config.core.v3.HealthStatus.HEALTHY) .setLoadBalancingWeight(UInt32Value.newBuilder().setValue(20))) // endpoint weight .build(); - StructOrError struct = ClientXdsClient.parseLocalityLbEndpoints(proto); + StructOrError struct = XdsEndpointResource.parseLocalityLbEndpoints(proto); assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct()).isEqualTo( LocalityLbEndpoints.create( @@ -883,7 +883,7 @@ public void parseLocalityLbEndpoints_treatUnknownHealthAsHealthy() { .setHealthStatus(io.envoyproxy.envoy.config.core.v3.HealthStatus.UNKNOWN) .setLoadBalancingWeight(UInt32Value.newBuilder().setValue(20))) // endpoint weight .build(); - StructOrError struct = ClientXdsClient.parseLocalityLbEndpoints(proto); + StructOrError struct = XdsEndpointResource.parseLocalityLbEndpoints(proto); assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct()).isEqualTo( LocalityLbEndpoints.create( @@ -907,7 +907,7 @@ public void parseLocalityLbEndpoints_withUnHealthyEndpoints() { .setHealthStatus(io.envoyproxy.envoy.config.core.v3.HealthStatus.UNHEALTHY) .setLoadBalancingWeight(UInt32Value.newBuilder().setValue(20))) // endpoint weight .build(); - StructOrError struct = ClientXdsClient.parseLocalityLbEndpoints(proto); + StructOrError struct = XdsEndpointResource.parseLocalityLbEndpoints(proto); assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct()).isEqualTo( LocalityLbEndpoints.create( @@ -931,7 +931,7 @@ public void parseLocalityLbEndpoints_ignorZeroWeightLocality() { .setHealthStatus(io.envoyproxy.envoy.config.core.v3.HealthStatus.UNKNOWN) .setLoadBalancingWeight(UInt32Value.newBuilder().setValue(20))) // endpoint weight .build(); - assertThat(ClientXdsClient.parseLocalityLbEndpoints(proto)).isNull(); + assertThat(XdsEndpointResource.parseLocalityLbEndpoints(proto)).isNull(); } @Test @@ -951,7 +951,7 @@ public void parseLocalityLbEndpoints_invalidPriority() { .setHealthStatus(io.envoyproxy.envoy.config.core.v3.HealthStatus.UNKNOWN) .setLoadBalancingWeight(UInt32Value.newBuilder().setValue(20))) // endpoint weight .build(); - StructOrError struct = ClientXdsClient.parseLocalityLbEndpoints(proto); + StructOrError struct = XdsEndpointResource.parseLocalityLbEndpoints(proto); assertThat(struct.getErrorDetail()).isEqualTo("negative priority"); } @@ -961,7 +961,7 @@ public void parseHttpFilter_unsupportedButOptional() { .setIsOptional(true) .setTypedConfig(Any.pack(StringValue.of("unsupported"))) .build(); - assertThat(ClientXdsClient.parseHttpFilter(httpFilter, filterRegistry, true)).isNull(); + assertThat(XdsListenerResource.parseHttpFilter(httpFilter, filterRegistry, true)).isNull(); } private static class SimpleFilterConfig implements FilterConfig { @@ -1022,7 +1022,7 @@ public void parseHttpFilter_typedStructMigration() { .setTypeUrl("test-url") .setValue(rawStruct) .build())).build(); - FilterConfig config = ClientXdsClient.parseHttpFilter(httpFilter, filterRegistry, + FilterConfig config = XdsListenerResource.parseHttpFilter(httpFilter, filterRegistry, true).getStruct(); assertThat(((SimpleFilterConfig)config).getConfig()).isEqualTo(rawStruct); @@ -1033,7 +1033,7 @@ public void parseHttpFilter_typedStructMigration() { .setTypeUrl("test-url") .setValue(rawStruct) .build())).build(); - config = ClientXdsClient.parseHttpFilter(httpFilterNewTypeStruct, filterRegistry, + config = XdsListenerResource.parseHttpFilter(httpFilterNewTypeStruct, filterRegistry, true).getStruct(); assertThat(((SimpleFilterConfig)config).getConfig()).isEqualTo(rawStruct); } @@ -1059,8 +1059,8 @@ public void parseOverrideHttpFilter_typedStructMigration() { .setValue(rawStruct1) .build()) ); - Map map = ClientXdsClient.parseOverrideFilterConfigs(rawFilterMap, - filterRegistry).getStruct(); + Map map = XdsRouteConfigureResource.parseOverrideFilterConfigs( + rawFilterMap, filterRegistry).getStruct(); assertThat(((SimpleFilterConfig)map.get("struct-0")).getConfig()).isEqualTo(rawStruct0); assertThat(((SimpleFilterConfig)map.get("struct-1")).getConfig()).isEqualTo(rawStruct1); } @@ -1072,7 +1072,7 @@ public void parseHttpFilter_unsupportedAndRequired() { .setName("unsupported.filter") .setTypedConfig(Any.pack(StringValue.of("string value"))) .build(); - assertThat(ClientXdsClient.parseHttpFilter(httpFilter, filterRegistry, true) + assertThat(XdsListenerResource.parseHttpFilter(httpFilter, filterRegistry, true) .getErrorDetail()).isEqualTo( "HttpFilter [unsupported.filter]" + "(type.googleapis.com/google.protobuf.StringValue) is required but unsupported " @@ -1088,7 +1088,7 @@ public void parseHttpFilter_routerFilterForClient() { .setName("envoy.router") .setTypedConfig(Any.pack(Router.getDefaultInstance())) .build(); - FilterConfig config = ClientXdsClient.parseHttpFilter( + FilterConfig config = XdsListenerResource.parseHttpFilter( httpFilter, filterRegistry, true /* isForClient */).getStruct(); assertThat(config.typeUrl()).isEqualTo(RouterFilter.TYPE_URL); } @@ -1102,7 +1102,7 @@ public void parseHttpFilter_routerFilterForServer() { .setName("envoy.router") .setTypedConfig(Any.pack(Router.getDefaultInstance())) .build(); - FilterConfig config = ClientXdsClient.parseHttpFilter( + FilterConfig config = XdsListenerResource.parseHttpFilter( httpFilter, filterRegistry, false /* isForClient */).getStruct(); assertThat(config.typeUrl()).isEqualTo(RouterFilter.TYPE_URL); } @@ -1129,7 +1129,7 @@ public void parseHttpFilter_faultConfigForClient() { .setDenominator(DenominatorType.HUNDRED))) .build())) .build(); - FilterConfig config = ClientXdsClient.parseHttpFilter( + FilterConfig config = XdsListenerResource.parseHttpFilter( httpFilter, filterRegistry, true /* isForClient */).getStruct(); assertThat(config).isInstanceOf(FaultConfig.class); } @@ -1157,7 +1157,7 @@ public void parseHttpFilter_faultConfigUnsupportedForServer() { .build())) .build(); StructOrError config = - ClientXdsClient.parseHttpFilter(httpFilter, filterRegistry, false /* isForClient */); + XdsListenerResource.parseHttpFilter(httpFilter, filterRegistry, false /* isForClient */); assertThat(config.getErrorDetail()).isEqualTo( "HttpFilter [envoy.fault](" + FaultFilter.TYPE_URL + ") is required but " + "unsupported for server"); @@ -1185,7 +1185,7 @@ public void parseHttpFilter_rbacConfigForServer() { .build()) .build())) .build(); - FilterConfig config = ClientXdsClient.parseHttpFilter( + FilterConfig config = XdsListenerResource.parseHttpFilter( httpFilter, filterRegistry, false /* isForClient */).getStruct(); assertThat(config).isInstanceOf(RbacConfig.class); } @@ -1213,7 +1213,7 @@ public void parseHttpFilter_rbacConfigUnsupportedForClient() { .build())) .build(); StructOrError config = - ClientXdsClient.parseHttpFilter(httpFilter, filterRegistry, true /* isForClient */); + XdsListenerResource.parseHttpFilter(httpFilter, filterRegistry, true /* isForClient */); assertThat(config.getErrorDetail()).isEqualTo( "HttpFilter [envoy.auth](" + RbacFilter.TYPE_URL + ") is required but " + "unsupported for client"); @@ -1238,7 +1238,8 @@ public void parseOverrideRbacFilterConfig() { .build(); Map configOverrides = ImmutableMap.of("envoy.auth", Any.pack(rbacPerRoute)); Map parsedConfigs = - ClientXdsClient.parseOverrideFilterConfigs(configOverrides, filterRegistry).getStruct(); + XdsRouteConfigureResource.parseOverrideFilterConfigs(configOverrides, filterRegistry) + .getStruct(); assertThat(parsedConfigs).hasSize(1); assertThat(parsedConfigs).containsKey("envoy.auth"); assertThat(parsedConfigs.get("envoy.auth")).isInstanceOf(RbacConfig.class); @@ -1258,7 +1259,8 @@ public void parseOverrideFilterConfigs_unsupportedButOptional() { .setIsOptional(true).setConfig(Any.pack(StringValue.of("string value"))) .build())); Map parsedConfigs = - ClientXdsClient.parseOverrideFilterConfigs(configOverrides, filterRegistry).getStruct(); + XdsRouteConfigureResource.parseOverrideFilterConfigs(configOverrides, filterRegistry) + .getStruct(); assertThat(parsedConfigs).hasSize(1); assertThat(parsedConfigs).containsKey("envoy.fault"); } @@ -1276,7 +1278,7 @@ public void parseOverrideFilterConfigs_unsupportedAndRequired() { Any.pack(io.envoyproxy.envoy.config.route.v3.FilterConfig.newBuilder() .setIsOptional(false).setConfig(Any.pack(StringValue.of("string value"))) .build())); - assertThat(ClientXdsClient.parseOverrideFilterConfigs(configOverrides, filterRegistry) + assertThat(XdsRouteConfigureResource.parseOverrideFilterConfigs(configOverrides, filterRegistry) .getErrorDetail()).isEqualTo( "HttpFilter [unsupported.filter]" + "(type.googleapis.com/google.protobuf.StringValue) is required but unsupported"); @@ -1286,7 +1288,7 @@ public void parseOverrideFilterConfigs_unsupportedAndRequired() { Any.pack(httpFault), "unsupported.filter", Any.pack(StringValue.of("string value"))); - assertThat(ClientXdsClient.parseOverrideFilterConfigs(configOverrides, filterRegistry) + assertThat(XdsRouteConfigureResource.parseOverrideFilterConfigs(configOverrides, filterRegistry) .getErrorDetail()).isEqualTo( "HttpFilter [unsupported.filter]" + "(type.googleapis.com/google.protobuf.StringValue) is required but unsupported"); @@ -1299,7 +1301,7 @@ public void parseHttpConnectionManager_xffNumTrustedHopsUnsupported() HttpConnectionManager hcm = HttpConnectionManager.newBuilder().setXffNumTrustedHops(2).build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("HttpConnectionManager with xff_num_trusted_hops unsupported"); - ClientXdsClient.parseHttpConnectionManager( + XdsListenerResource.parseHttpConnectionManager( hcm, new HashSet(), filterRegistry, false /* does not matter */, true /* does not matter */); } @@ -1313,7 +1315,7 @@ public void parseHttpConnectionManager_OriginalIpDetectionExtensionsMustEmpty() .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("HttpConnectionManager with original_ip_detection_extensions unsupported"); - ClientXdsClient.parseHttpConnectionManager( + XdsListenerResource.parseHttpConnectionManager( hcm, new HashSet(), filterRegistry, false /* does not matter */, false); } @@ -1328,7 +1330,7 @@ public void parseHttpConnectionManager_missingRdsAndInlinedRouteConfiguration() .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("HttpConnectionManager neither has inlined route_config nor RDS"); - ClientXdsClient.parseHttpConnectionManager( + XdsListenerResource.parseHttpConnectionManager( hcm, new HashSet(), filterRegistry, false /* does not matter */, true /* does not matter */); } @@ -1347,7 +1349,7 @@ public void parseHttpConnectionManager_duplicateHttpFilters() throws ResourceInv .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("HttpConnectionManager contains duplicate HttpFilter: envoy.filter.foo"); - ClientXdsClient.parseHttpConnectionManager( + XdsListenerResource.parseHttpConnectionManager( hcm, new HashSet(), filterRegistry, true /* parseHttpFilter */, true /* does not matter */); } @@ -1365,7 +1367,7 @@ public void parseHttpConnectionManager_lastNotTerminal() throws ResourceInvalidE .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("The last HttpFilter must be a terminal filter: envoy.filter.bar"); - ClientXdsClient.parseHttpConnectionManager( + XdsListenerResource.parseHttpConnectionManager( hcm, new HashSet(), filterRegistry, true /* parseHttpFilter */, true /* does not matter */); } @@ -1383,7 +1385,7 @@ public void parseHttpConnectionManager_terminalNotLast() throws ResourceInvalidE .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("A terminal HttpFilter must be the last filter: terminal"); - ClientXdsClient.parseHttpConnectionManager( + XdsListenerResource.parseHttpConnectionManager( hcm, new HashSet(), filterRegistry, true /* parseHttpFilter */, true); } @@ -1399,7 +1401,7 @@ public void parseHttpConnectionManager_unknownFilters() throws ResourceInvalidEx .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("The last HttpFilter must be a terminal filter: envoy.filter.bar"); - ClientXdsClient.parseHttpConnectionManager( + XdsListenerResource.parseHttpConnectionManager( hcm, new HashSet(), filterRegistry, true /* parseHttpFilter */, true /* does not matter */); } @@ -1411,14 +1413,14 @@ public void parseHttpConnectionManager_emptyFilters() throws ResourceInvalidExce .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("Missing HttpFilter in HttpConnectionManager."); - ClientXdsClient.parseHttpConnectionManager( + XdsListenerResource.parseHttpConnectionManager( hcm, new HashSet(), filterRegistry, true /* parseHttpFilter */, true /* does not matter */); } @Test public void parseHttpConnectionManager_clusterSpecifierPlugin() throws Exception { - ClientXdsClient.enableRouteLookup = true; + XdsResourceType.enableRouteLookup = true; RouteLookupConfig routeLookupConfig = RouteLookupConfig.newBuilder() .addGrpcKeybuilders( GrpcKeyBuilder.newBuilder() @@ -1457,7 +1459,7 @@ public void parseHttpConnectionManager_clusterSpecifierPlugin() throws Exception .addRoutes(route))) .build(); - io.grpc.xds.HttpConnectionManager parsedHcm = ClientXdsClient.parseHttpConnectionManager( + io.grpc.xds.HttpConnectionManager parsedHcm = XdsListenerResource.parseHttpConnectionManager( hcm, new HashSet(), filterRegistry, false /* parseHttpFilter */, true /* does not matter */); @@ -1471,7 +1473,7 @@ public void parseHttpConnectionManager_clusterSpecifierPlugin() throws Exception @Test public void parseHttpConnectionManager_duplicatePluginName() throws Exception { - ClientXdsClient.enableRouteLookup = true; + XdsResourceType.enableRouteLookup = true; RouteLookupConfig routeLookupConfig1 = RouteLookupConfig.newBuilder() .addGrpcKeybuilders( GrpcKeyBuilder.newBuilder() @@ -1534,14 +1536,14 @@ public void parseHttpConnectionManager_duplicatePluginName() throws Exception { thrown.expect(ResourceInvalidException.class); thrown.expectMessage("Multiple ClusterSpecifierPlugins with the same name: rls-plugin-1"); - ClientXdsClient.parseHttpConnectionManager( + XdsListenerResource.parseHttpConnectionManager( hcm, new HashSet(), filterRegistry, false /* parseHttpFilter */, true /* does not matter */); } @Test public void parseHttpConnectionManager_pluginNameNotFound() throws Exception { - ClientXdsClient.enableRouteLookup = true; + XdsResourceType.enableRouteLookup = true; RouteLookupConfig routeLookupConfig = RouteLookupConfig.newBuilder() .addGrpcKeybuilders( GrpcKeyBuilder.newBuilder() @@ -1583,7 +1585,7 @@ public void parseHttpConnectionManager_pluginNameNotFound() throws Exception { thrown.expect(ResourceInvalidException.class); thrown.expectMessage("ClusterSpecifierPlugin for [invalid-plugin-name] not found"); - ClientXdsClient.parseHttpConnectionManager( + XdsListenerResource.parseHttpConnectionManager( hcm, new HashSet(), filterRegistry, false /* parseHttpFilter */, true /* does not matter */); } @@ -1591,7 +1593,7 @@ public void parseHttpConnectionManager_pluginNameNotFound() throws Exception { @Test public void parseHttpConnectionManager_optionalPlugin() throws ResourceInvalidException { - ClientXdsClient.enableRouteLookup = true; + XdsResourceType.enableRouteLookup = true; // RLS Plugin, and a route to it. RouteLookupConfig routeLookupConfig = RouteLookupConfig.newBuilder() @@ -1653,7 +1655,7 @@ public void parseHttpConnectionManager_optionalPlugin() throws ResourceInvalidEx .addRoutes(rlsRoute) .addRoutes(optionalRoute)) .build(); - io.grpc.xds.HttpConnectionManager parsedHcm = ClientXdsClient.parseHttpConnectionManager( + io.grpc.xds.HttpConnectionManager parsedHcm = XdsListenerResource.parseHttpConnectionManager( HttpConnectionManager.newBuilder().setRouteConfig(routeConfig).build(), new HashSet<>(), filterRegistry, false /* parseHttpFilter */, true /* does not matter */); @@ -1669,7 +1671,7 @@ public void parseHttpConnectionManager_optionalPlugin() throws ResourceInvalidEx @Test public void parseHttpConnectionManager_validateRdsConfigSource() throws Exception { - ClientXdsClient.enableRouteLookup = true; + XdsResourceType.enableRouteLookup = true; Set rdsResources = new HashSet<>(); HttpConnectionManager hcm1 = @@ -1679,7 +1681,7 @@ public void parseHttpConnectionManager_validateRdsConfigSource() throws Exceptio .setConfigSource( ConfigSource.newBuilder().setAds(AggregatedConfigSource.getDefaultInstance()))) .build(); - ClientXdsClient.parseHttpConnectionManager( + XdsListenerResource.parseHttpConnectionManager( hcm1, rdsResources, filterRegistry, false /* parseHttpFilter */, true /* does not matter */); @@ -1690,7 +1692,7 @@ public void parseHttpConnectionManager_validateRdsConfigSource() throws Exceptio .setConfigSource( ConfigSource.newBuilder().setSelf(SelfConfigSource.getDefaultInstance()))) .build(); - ClientXdsClient.parseHttpConnectionManager( + XdsListenerResource.parseHttpConnectionManager( hcm2, rdsResources, filterRegistry, false /* parseHttpFilter */, true /* does not matter */); @@ -1705,7 +1707,7 @@ public void parseHttpConnectionManager_validateRdsConfigSource() throws Exceptio thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "HttpConnectionManager contains invalid RDS: must specify ADS or self ConfigSource"); - ClientXdsClient.parseHttpConnectionManager( + XdsListenerResource.parseHttpConnectionManager( hcm3, rdsResources, filterRegistry, false /* parseHttpFilter */, true /* does not matter */); } @@ -1744,7 +1746,8 @@ public ConfigOrError parsePlugin(Message rawProtoMessage .setTypedConfig(Any.pack(typedStruct))) .build(); - PluginConfig pluginConfig = ClientXdsClient.parseClusterSpecifierPlugin(pluginProto, registry); + PluginConfig pluginConfig = XdsRouteConfigureResource + .parseClusterSpecifierPlugin(pluginProto, registry); assertThat(pluginConfig).isInstanceOf(TestPluginConfig.class); } @@ -1782,7 +1785,8 @@ public ConfigOrError parsePlugin(Message rawProtoMessage .setTypedConfig(Any.pack(typedStruct))) .build(); - PluginConfig pluginConfig = ClientXdsClient.parseClusterSpecifierPlugin(pluginProto, registry); + PluginConfig pluginConfig = XdsRouteConfigureResource + .parseClusterSpecifierPlugin(pluginProto, registry); assertThat(pluginConfig).isInstanceOf(TestPluginConfig.class); } @@ -1799,7 +1803,7 @@ public void parseClusterSpecifierPlugin_unregisteredPlugin() throws Exception { thrown.expectMessage( "Unsupported ClusterSpecifierPlugin type: type.googleapis.com/google.protobuf.StringValue"); - ClientXdsClient.parseClusterSpecifierPlugin(pluginProto, registry); + XdsRouteConfigureResource.parseClusterSpecifierPlugin(pluginProto, registry); } @Test @@ -1813,7 +1817,8 @@ public void parseClusterSpecifierPlugin_unregisteredPlugin_optional() .setIsOptional(true) .build(); - PluginConfig pluginConfig = ClientXdsClient.parseClusterSpecifierPlugin(pluginProto, registry); + PluginConfig pluginConfig = XdsRouteConfigureResource + .parseClusterSpecifierPlugin(pluginProto, registry); assertThat(pluginConfig).isNull(); } @@ -1832,7 +1837,7 @@ public void parseClusterSpecifierPlugin_brokenPlugin() { .build(); try { - ClientXdsClient.parseClusterSpecifierPlugin( + XdsRouteConfigureResource.parseClusterSpecifierPlugin( io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin.newBuilder() .setExtension(brokenPlugin) .build(), @@ -1860,7 +1865,7 @@ public void parseClusterSpecifierPlugin_brokenPlugin_optional() { // Despite being optional, still should fail. try { - ClientXdsClient.parseClusterSpecifierPlugin( + XdsRouteConfigureResource.parseClusterSpecifierPlugin( io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin.newBuilder() .setIsOptional(true) .setExtension(brokenPlugin) @@ -1887,7 +1892,7 @@ public void parseCluster_ringHashLbPolicy_defaultLbConfig() throws ResourceInval .setLbPolicy(LbPolicy.RING_HASH) .build(); - CdsUpdate update = ClientXdsClient.processCluster( + CdsUpdate update = XdsClusterResource.processCluster( cluster, new HashSet(), null, LRS_SERVER_INFO, LoadBalancerRegistry.getDefaultRegistry()); LbConfig lbConfig = ServiceConfigUtil.unwrapLoadBalancingConfig(update.lbPolicyConfig()); @@ -1896,7 +1901,7 @@ public void parseCluster_ringHashLbPolicy_defaultLbConfig() throws ResourceInval @Test public void parseCluster_leastRequestLbPolicy_defaultLbConfig() throws ResourceInvalidException { - ClientXdsClient.enableLeastRequest = true; + XdsResourceType.enableLeastRequest = true; Cluster cluster = Cluster.newBuilder() .setName("cluster-foo.googleapis.com") .setType(DiscoveryType.EDS) @@ -1909,7 +1914,7 @@ public void parseCluster_leastRequestLbPolicy_defaultLbConfig() throws ResourceI .setLbPolicy(LbPolicy.LEAST_REQUEST) .build(); - CdsUpdate update = ClientXdsClient.processCluster( + CdsUpdate update = XdsClusterResource.processCluster( cluster, new HashSet(), null, LRS_SERVER_INFO, LoadBalancerRegistry.getDefaultRegistry()); LbConfig lbConfig = ServiceConfigUtil.unwrapLoadBalancingConfig(update.lbPolicyConfig()); @@ -1938,7 +1943,7 @@ public void parseCluster_transportSocketMatches_exception() throws ResourceInval thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "Cluster cluster-foo.googleapis.com: transport-socket-matches not supported."); - ClientXdsClient.processCluster(cluster, new HashSet(), null, LRS_SERVER_INFO, + XdsClusterResource.processCluster(cluster, new HashSet(), null, LRS_SERVER_INFO, LoadBalancerRegistry.getDefaultRegistry()); } @@ -1956,7 +1961,7 @@ public void parseCluster_validateEdsSourceConfig() throws ResourceInvalidExcepti .setServiceName("service-foo.googleapis.com")) .setLbPolicy(LbPolicy.ROUND_ROBIN) .build(); - ClientXdsClient.processCluster(cluster1, retainedEdsResources, null, LRS_SERVER_INFO, + XdsClusterResource.processCluster(cluster1, retainedEdsResources, null, LRS_SERVER_INFO, LoadBalancerRegistry.getDefaultRegistry()); Cluster cluster2 = Cluster.newBuilder() @@ -1970,7 +1975,7 @@ public void parseCluster_validateEdsSourceConfig() throws ResourceInvalidExcepti .setServiceName("service-foo.googleapis.com")) .setLbPolicy(LbPolicy.ROUND_ROBIN) .build(); - ClientXdsClient.processCluster(cluster2, retainedEdsResources, null, LRS_SERVER_INFO, + XdsClusterResource.processCluster(cluster2, retainedEdsResources, null, LRS_SERVER_INFO, LoadBalancerRegistry.getDefaultRegistry()); Cluster cluster3 = Cluster.newBuilder() @@ -1989,7 +1994,7 @@ public void parseCluster_validateEdsSourceConfig() throws ResourceInvalidExcepti thrown.expectMessage( "Cluster cluster-foo.googleapis.com: field eds_cluster_config must be set to indicate to" + " use EDS over ADS or self ConfigSource"); - ClientXdsClient.processCluster(cluster3, retainedEdsResources, null, LRS_SERVER_INFO, + XdsClusterResource.processCluster(cluster3, retainedEdsResources, null, LRS_SERVER_INFO, LoadBalancerRegistry.getDefaultRegistry()); } @@ -2002,7 +2007,7 @@ public void parseServerSideListener_invalidTrafficDirection() throws ResourceInv .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("Listener listener1 with invalid traffic direction: OUTBOUND"); - ClientXdsClient.parseServerSideListener( + XdsListenerResource.parseServerSideListener( listener, new HashSet(), null, filterRegistry, null, true /* does not matter */); } @@ -2012,7 +2017,7 @@ public void parseServerSideListener_noTrafficDirection() throws ResourceInvalidE Listener.newBuilder() .setName("listener1") .build(); - ClientXdsClient.parseServerSideListener( + XdsListenerResource.parseServerSideListener( listener, new HashSet(), null, filterRegistry, null, true /* does not matter */); } @@ -2026,7 +2031,7 @@ public void parseServerSideListener_listenerFiltersPresent() throws ResourceInva .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("Listener listener1 cannot have listener_filters"); - ClientXdsClient.parseServerSideListener( + XdsListenerResource.parseServerSideListener( listener, new HashSet(), null, filterRegistry, null, true /* does not matter */); } @@ -2040,7 +2045,7 @@ public void parseServerSideListener_useOriginalDst() throws ResourceInvalidExcep .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("Listener listener1 cannot have use_original_dst set to true"); - ClientXdsClient.parseServerSideListener( + XdsListenerResource.parseServerSideListener( listener, new HashSet(), null, filterRegistry, null, true /* does not matter */); } @@ -2089,7 +2094,7 @@ public void parseServerSideListener_nonUniqueFilterChainMatch() throws ResourceI .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("FilterChainMatch must be unique. Found duplicate:"); - ClientXdsClient.parseServerSideListener( + XdsListenerResource.parseServerSideListener( listener, new HashSet(), null, filterRegistry, null, true /* does not matter */); } @@ -2138,7 +2143,7 @@ public void parseServerSideListener_nonUniqueFilterChainMatch_sameFilter() .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("FilterChainMatch must be unique. Found duplicate:"); - ClientXdsClient.parseServerSideListener( + XdsListenerResource.parseServerSideListener( listener, new HashSet(), null, filterRegistry, null, true /* does not matter */); } @@ -2187,7 +2192,7 @@ public void parseServerSideListener_uniqueFilterChainMatch() throws ResourceInva .setTrafficDirection(TrafficDirection.INBOUND) .addAllFilterChains(Arrays.asList(filterChain1, filterChain2)) .build(); - ClientXdsClient.parseServerSideListener( + XdsListenerResource.parseServerSideListener( listener, new HashSet(), null, filterRegistry, null, true /* does not matter */); } @@ -2202,7 +2207,7 @@ public void parseFilterChain_noHcm() throws ResourceInvalidException { thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "FilterChain filter-chain-foo should contain exact one HttpConnectionManager filter"); - ClientXdsClient.parseFilterChain( + XdsListenerResource.parseFilterChain( filterChain, new HashSet(), null, filterRegistry, null, null, true /* does not matter */); } @@ -2221,7 +2226,7 @@ public void parseFilterChain_duplicateFilter() throws ResourceInvalidException { thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "FilterChain filter-chain-foo should contain exact one HttpConnectionManager filter"); - ClientXdsClient.parseFilterChain( + XdsListenerResource.parseFilterChain( filterChain, new HashSet(), null, filterRegistry, null, null, true /* does not matter */); } @@ -2240,7 +2245,7 @@ public void parseFilterChain_filterMissingTypedConfig() throws ResourceInvalidEx thrown.expectMessage( "FilterChain filter-chain-foo contains filter envoy.http_connection_manager " + "without typed_config"); - ClientXdsClient.parseFilterChain( + XdsListenerResource.parseFilterChain( filterChain, new HashSet(), null, filterRegistry, null, null, true /* does not matter */); } @@ -2263,7 +2268,7 @@ public void parseFilterChain_unsupportedFilter() throws ResourceInvalidException thrown.expectMessage( "FilterChain filter-chain-foo contains filter unsupported with unsupported " + "typed_config type unsupported-type-url"); - ClientXdsClient.parseFilterChain( + XdsListenerResource.parseFilterChain( filterChain, new HashSet(), null, filterRegistry, null, null, true /* does not matter */); } @@ -2291,10 +2296,10 @@ public void parseFilterChain_noName() throws ResourceInvalidException { .build())) .build(); - EnvoyServerProtoData.FilterChain parsedFilterChain1 = ClientXdsClient.parseFilterChain( + EnvoyServerProtoData.FilterChain parsedFilterChain1 = XdsListenerResource.parseFilterChain( filterChain1, new HashSet(), null, filterRegistry, null, null, true /* does not matter */); - EnvoyServerProtoData.FilterChain parsedFilterChain2 = ClientXdsClient.parseFilterChain( + EnvoyServerProtoData.FilterChain parsedFilterChain2 = XdsListenerResource.parseFilterChain( filterChain2, new HashSet(), null, filterRegistry, null, null, true /* does not matter */); assertThat(parsedFilterChain1.name()).isEqualTo(parsedFilterChain2.name()); @@ -2307,7 +2312,7 @@ public void validateCommonTlsContext_tlsParams() throws ResourceInvalidException .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("common-tls-context with tls_params is not supported"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false); } @Test @@ -2317,7 +2322,7 @@ public void validateCommonTlsContext_customHandshaker() throws ResourceInvalidEx .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("common-tls-context with custom_handshaker is not supported"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false); } @Test @@ -2327,7 +2332,7 @@ public void validateCommonTlsContext_validationContext() throws ResourceInvalidE .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("ca_certificate_provider_instance is required in upstream-tls-context"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false); } @Test @@ -2339,7 +2344,7 @@ public void validateCommonTlsContext_validationContextSdsSecretConfig() thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "common-tls-context with validation_context_sds_secret_config is not supported"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false); } @Test @@ -2353,7 +2358,7 @@ public void validateCommonTlsContext_validationContextCertificateProvider() thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "common-tls-context with validation_context_certificate_provider is not supported"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false); } @Test @@ -2368,7 +2373,7 @@ public void validateCommonTlsContext_validationContextCertificateProviderInstanc thrown.expectMessage( "common-tls-context with validation_context_certificate_provider_instance is not " + "supported"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false); } @Test @@ -2379,7 +2384,7 @@ public void validateCommonTlsContext_tlsCertificateProviderInstance_isRequiredFo thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "tls_certificate_provider_instance is required in downstream-tls-context"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, true); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, true); } @Test @@ -2390,7 +2395,7 @@ public void validateCommonTlsContext_tlsNewCertificateProviderInstance() .setTlsCertificateProviderInstance( CertificateProviderPluginInstance.newBuilder().setInstanceName("name1").build()) .build(); - ClientXdsClient + XdsClusterResource .validateCommonTlsContext(commonTlsContext, ImmutableSet.of("name1", "name2"), true); } @@ -2402,7 +2407,7 @@ public void validateCommonTlsContext_tlsCertificateProviderInstance() .setTlsCertificateCertificateProviderInstance( CertificateProviderInstance.newBuilder().setInstanceName("name1").build()) .build(); - ClientXdsClient + XdsClusterResource .validateCommonTlsContext(commonTlsContext, ImmutableSet.of("name1", "name2"), true); } @@ -2417,7 +2422,7 @@ public void validateCommonTlsContext_tlsCertificateProviderInstance_absentInBoot thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "CertificateProvider instance name 'bad-name' not defined in the bootstrap file."); - ClientXdsClient + XdsClusterResource .validateCommonTlsContext(commonTlsContext, ImmutableSet.of("name1", "name2"), true); } @@ -2432,7 +2437,7 @@ public void validateCommonTlsContext_validationContextProviderInstance() CertificateProviderInstance.newBuilder().setInstanceName("name1").build()) .build()) .build(); - ClientXdsClient + XdsClusterResource .validateCommonTlsContext(commonTlsContext, ImmutableSet.of("name1", "name2"), false); } @@ -2450,7 +2455,7 @@ public void validateCommonTlsContext_validationContextProviderInstance_absentInB thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "ca_certificate_provider_instance name 'bad-name' not defined in the bootstrap file."); - ClientXdsClient + XdsClusterResource .validateCommonTlsContext(commonTlsContext, ImmutableSet.of("name1", "name2"), false); } @@ -2462,7 +2467,7 @@ public void validateCommonTlsContext_tlsCertificatesCount() throws ResourceInval .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("tls_certificate_provider_instance is unset"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false); } @Test @@ -2474,7 +2479,7 @@ public void validateCommonTlsContext_tlsCertificateSdsSecretConfigsCount() thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "tls_certificate_provider_instance is unset"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false); } @Test @@ -2488,7 +2493,7 @@ public void validateCommonTlsContext_tlsCertificateCertificateProvider() thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "tls_certificate_provider_instance is unset"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false); } @Test @@ -2498,7 +2503,7 @@ public void validateCommonTlsContext_combinedValidationContext_isRequiredForClie .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("ca_certificate_provider_instance is required in upstream-tls-context"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false); } @Test @@ -2511,7 +2516,7 @@ public void validateCommonTlsContext_combinedValidationContextWithoutCertProvide thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "ca_certificate_provider_instance is required in upstream-tls-context"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false); } @Test @@ -2531,7 +2536,7 @@ public void validateCommonTlsContext_combinedValContextWithDefaultValContextForS .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("match_subject_alt_names only allowed in upstream_tls_context"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), true); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), true); } @Test @@ -2551,7 +2556,7 @@ public void validateCommonTlsContext_combinedValContextWithDefaultValContextVeri thrown.expect(ResourceInvalidException.class); thrown.expectMessage("verify_certificate_spki in default_validation_context is not " + "supported"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false); } @Test @@ -2571,7 +2576,7 @@ public void validateCommonTlsContext_combinedValContextWithDefaultValContextVeri thrown.expect(ResourceInvalidException.class); thrown.expectMessage("verify_certificate_hash in default_validation_context is not " + "supported"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false); } @Test @@ -2592,7 +2597,7 @@ public void validateCommonTlsContext_combinedValContextDfltValContextRequireSign thrown.expectMessage( "require_signed_certificate_timestamp in default_validation_context is not " + "supported"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false); } @Test @@ -2611,7 +2616,7 @@ public void validateCommonTlsContext_combinedValidationContextWithDefaultValidat .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("crl in default_validation_context is not supported"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false); } @Test @@ -2631,7 +2636,7 @@ public void validateCommonTlsContext_combinedValContextWithDfltValContextCustomV thrown.expect(ResourceInvalidException.class); thrown.expectMessage("custom_validator_config in default_validation_context is not " + "supported"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false); } @Test @@ -2639,7 +2644,7 @@ public void validateDownstreamTlsContext_noCommonTlsContext() throws ResourceInv DownstreamTlsContext downstreamTlsContext = DownstreamTlsContext.getDefaultInstance(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("common-tls-context is required in downstream-tls-context"); - ClientXdsClient.validateDownstreamTlsContext(downstreamTlsContext, null); + XdsListenerResource.validateDownstreamTlsContext(downstreamTlsContext, null); } @Test @@ -2659,7 +2664,7 @@ public void validateDownstreamTlsContext_hasRequireSni() throws ResourceInvalidE .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("downstream-tls-context with require-sni is not supported"); - ClientXdsClient.validateDownstreamTlsContext(downstreamTlsContext, ImmutableSet.of("")); + XdsListenerResource.validateDownstreamTlsContext(downstreamTlsContext, ImmutableSet.of("")); } @Test @@ -2680,7 +2685,7 @@ public void validateDownstreamTlsContext_hasOcspStaplePolicy() throws ResourceIn thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "downstream-tls-context with ocsp_staple_policy value STRICT_STAPLING is not supported"); - ClientXdsClient.validateDownstreamTlsContext(downstreamTlsContext, ImmutableSet.of("")); + XdsListenerResource.validateDownstreamTlsContext(downstreamTlsContext, ImmutableSet.of("")); } @Test @@ -2688,7 +2693,7 @@ public void validateUpstreamTlsContext_noCommonTlsContext() throws ResourceInval UpstreamTlsContext upstreamTlsContext = UpstreamTlsContext.getDefaultInstance(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("common-tls-context is required in upstream-tls-context"); - ClientXdsClient.validateUpstreamTlsContext(upstreamTlsContext, null); + XdsClusterResource.validateUpstreamTlsContext(upstreamTlsContext, null); } @Test diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java index cd8766024c8..788110e1c20 100644 --- a/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java +++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java @@ -81,19 +81,16 @@ import io.grpc.xds.EnvoyServerProtoData.SuccessRateEjection; import io.grpc.xds.FaultConfig.FractionalPercent.DenominatorType; import io.grpc.xds.LoadStatsManager2.ClusterDropStats; -import io.grpc.xds.XdsClient.CdsResourceWatcher; -import io.grpc.xds.XdsClient.CdsUpdate; -import io.grpc.xds.XdsClient.CdsUpdate.ClusterType; -import io.grpc.xds.XdsClient.EdsResourceWatcher; -import io.grpc.xds.XdsClient.EdsUpdate; -import io.grpc.xds.XdsClient.LdsResourceWatcher; -import io.grpc.xds.XdsClient.LdsUpdate; -import io.grpc.xds.XdsClient.RdsResourceWatcher; -import io.grpc.xds.XdsClient.RdsUpdate; import io.grpc.xds.XdsClient.ResourceMetadata; import io.grpc.xds.XdsClient.ResourceMetadata.ResourceMetadataStatus; import io.grpc.xds.XdsClient.ResourceMetadata.UpdateFailureState; +import io.grpc.xds.XdsClient.ResourceUpdate; import io.grpc.xds.XdsClient.ResourceWatcher; +import io.grpc.xds.XdsClusterResource.CdsUpdate; +import io.grpc.xds.XdsClusterResource.CdsUpdate.ClusterType; +import io.grpc.xds.XdsEndpointResource.EdsUpdate; +import io.grpc.xds.XdsListenerResource.LdsUpdate; +import io.grpc.xds.XdsRouteConfigureResource.RdsUpdate; import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; import java.io.IOException; import java.util.ArrayDeque; @@ -254,13 +251,13 @@ public long currentTimeNanos() { @Mock private BackoffPolicy backoffPolicy2; @Mock - private LdsResourceWatcher ldsResourceWatcher; + private ResourceWatcher ldsResourceWatcher; @Mock - private RdsResourceWatcher rdsResourceWatcher; + private ResourceWatcher rdsResourceWatcher; @Mock - private CdsResourceWatcher cdsResourceWatcher; + private ResourceWatcher cdsResourceWatcher; @Mock - private EdsResourceWatcher edsResourceWatcher; + private ResourceWatcher edsResourceWatcher; @Mock private TlsContextManager tlsContextManager; @@ -282,12 +279,12 @@ public void setUp() throws IOException { when(backoffPolicy2.nextBackoffNanos()).thenReturn(20L, 200L); // Start the server and the client. - originalEnableFaultInjection = ClientXdsClient.enableFaultInjection; - ClientXdsClient.enableFaultInjection = true; - originalEnableRbac = ClientXdsClient.enableRbac; + originalEnableFaultInjection = XdsResourceType.enableFaultInjection; + XdsResourceType.enableFaultInjection = true; + originalEnableRbac = XdsResourceType.enableRbac; assertThat(originalEnableRbac).isTrue(); - originalEnableLeastRequest = ClientXdsClient.enableLeastRequest; - ClientXdsClient.enableLeastRequest = true; + originalEnableLeastRequest = XdsResourceType.enableLeastRequest; + XdsResourceType.enableLeastRequest = true; originalEnableFederation = BootstrapperImpl.enableFederation; final String serverName = InProcessServerBuilder.generateName(); cleanupRule.register( @@ -361,9 +358,9 @@ SERVER_URI_EMPTY_AUTHORITY, CHANNEL_CREDENTIALS, useProtocolV3()))))) @After public void tearDown() { - ClientXdsClient.enableFaultInjection = originalEnableFaultInjection; - ClientXdsClient.enableRbac = originalEnableRbac; - ClientXdsClient.enableLeastRequest = originalEnableLeastRequest; + XdsResourceType.enableFaultInjection = originalEnableFaultInjection; + XdsResourceType.enableRbac = originalEnableRbac; + XdsResourceType.enableLeastRequest = originalEnableLeastRequest; BootstrapperImpl.enableFederation = originalEnableFederation; xdsClient.shutdown(); channel.shutdown(); // channel not owned by XdsClient @@ -579,7 +576,8 @@ private void validateGoldenClusterLoadAssignment(EdsUpdate edsUpdate) { @Test public void ldsResourceNotFound() { - DiscoveryRpcCall call = startResourceWatcher(LDS, LDS_RESOURCE, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE, + ldsResourceWatcher); Any listener = Any.pack(mf.buildListenerWithApiListener("bar.googleapis.com", mf.buildRouteConfiguration("route-bar.googleapis.com", mf.buildOpaqueVirtualHosts(1)))); @@ -604,20 +602,23 @@ public void ldsResourceUpdated_withXdstpResourceName_withUnknownAuthority() { String ldsResourceName = useProtocolV3() ? "xdstp://unknown.example.com/envoy.config.listener.v3.Listener/listener1" : "xdstp://unknown.example.com/envoy.api.v2.Listener/listener1"; - xdsClient.watchLdsResource(ldsResourceName, ldsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),ldsResourceName, + ldsResourceWatcher); verify(ldsResourceWatcher).onError(errorCaptor.capture()); Status error = errorCaptor.getValue(); assertThat(error.getCode()).isEqualTo(Code.INVALID_ARGUMENT); assertThat(error.getDescription()).isEqualTo( "Wrong configuration: xds server does not exist for resource " + ldsResourceName); assertThat(resourceDiscoveryCalls.poll()).isNull(); - xdsClient.cancelLdsResourceWatch(ldsResourceName, ldsResourceWatcher); + xdsClient.cancelXdsResourceWatch(XdsListenerResource.getInstance(),ldsResourceName, + ldsResourceWatcher); assertThat(resourceDiscoveryCalls.poll()).isNull(); } @Test public void ldsResponseErrorHandling_allResourcesFailedUnpack() { - DiscoveryRpcCall call = startResourceWatcher(LDS, LDS_RESOURCE, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE, + ldsResourceWatcher); verifyResourceMetadataRequested(LDS, LDS_RESOURCE); call.sendResponse(LDS, ImmutableList.of(FAILING_ANY, FAILING_ANY), VERSION_1, "0000"); @@ -633,7 +634,8 @@ public void ldsResponseErrorHandling_allResourcesFailedUnpack() { @Test public void ldsResponseErrorHandling_someResourcesFailedUnpack() { - DiscoveryRpcCall call = startResourceWatcher(LDS, LDS_RESOURCE, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE, + ldsResourceWatcher); verifyResourceMetadataRequested(LDS, LDS_RESOURCE); // Correct resource is in the middle to ensure processing continues on errors. @@ -660,9 +662,9 @@ public void ldsResponseErrorHandling_someResourcesFailedUnpack() { @Test public void ldsResponseErrorHandling_subscribedResourceInvalid() { List subscribedResourceNames = ImmutableList.of("A", "B", "C"); - xdsClient.watchLdsResource("A", ldsResourceWatcher); - xdsClient.watchLdsResource("B", ldsResourceWatcher); - xdsClient.watchLdsResource("C", ldsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),"A", ldsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),"B", ldsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),"C", ldsResourceWatcher); DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); assertThat(call).isNotNull(); verifyResourceMetadataRequested(LDS, "A"); @@ -725,12 +727,12 @@ public void ldsResponseErrorHandling_subscribedResourceInvalid() { @Test public void ldsResponseErrorHandling_subscribedResourceInvalid_withRdsSubscription() { List subscribedResourceNames = ImmutableList.of("A", "B", "C"); - xdsClient.watchLdsResource("A", ldsResourceWatcher); - xdsClient.watchRdsResource("A.1", rdsResourceWatcher); - xdsClient.watchLdsResource("B", ldsResourceWatcher); - xdsClient.watchRdsResource("B.1", rdsResourceWatcher); - xdsClient.watchLdsResource("C", ldsResourceWatcher); - xdsClient.watchRdsResource("C.1", rdsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),"A", ldsResourceWatcher); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),"A.1", rdsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),"B", ldsResourceWatcher); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),"B.1", rdsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),"C", ldsResourceWatcher); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),"C.1", rdsResourceWatcher); DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); assertThat(call).isNotNull(); verifyResourceMetadataRequested(LDS, "A"); @@ -803,7 +805,8 @@ public void ldsResponseErrorHandling_subscribedResourceInvalid_withRdsSubscripti @Test public void ldsResourceFound_containsVirtualHosts() { - DiscoveryRpcCall call = startResourceWatcher(LDS, LDS_RESOURCE, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE, + ldsResourceWatcher); // Client sends an ACK LDS request. call.sendResponse(LDS, testListenerVhosts, VERSION_1, "0000"); @@ -817,7 +820,8 @@ public void ldsResourceFound_containsVirtualHosts() { @Test public void wrappedLdsResource() { - DiscoveryRpcCall call = startResourceWatcher(LDS, LDS_RESOURCE, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE, + ldsResourceWatcher); // Client sends an ACK LDS request. call.sendResponse(LDS, mf.buildWrappedResource(testListenerVhosts), VERSION_1, "0000"); @@ -831,7 +835,8 @@ public void wrappedLdsResource() { @Test public void ldsResourceFound_containsRdsName() { - DiscoveryRpcCall call = startResourceWatcher(LDS, LDS_RESOURCE, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE, + ldsResourceWatcher); call.sendResponse(LDS, testListenerRds, VERSION_1, "0000"); // Client sends an ACK LDS request. @@ -844,15 +849,17 @@ public void ldsResourceFound_containsRdsName() { } @Test + @SuppressWarnings("unchecked") public void cachedLdsResource_data() { - DiscoveryRpcCall call = startResourceWatcher(LDS, LDS_RESOURCE, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE, + ldsResourceWatcher); // Client sends an ACK LDS request. call.sendResponse(LDS, testListenerRds, VERSION_1, "0000"); call.verifyRequest(LDS, LDS_RESOURCE, VERSION_1, "0000", NODE); - LdsResourceWatcher watcher = mock(LdsResourceWatcher.class); - xdsClient.watchLdsResource(LDS_RESOURCE, watcher); + ResourceWatcher watcher = mock(ResourceWatcher.class); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),LDS_RESOURCE, watcher); verify(watcher).onChanged(ldsUpdateCaptor.capture()); verifyGoldenListenerRds(ldsUpdateCaptor.getValue()); call.verifyNoMoreRequest(); @@ -861,13 +868,15 @@ public void cachedLdsResource_data() { } @Test + @SuppressWarnings("unchecked") public void cachedLdsResource_absent() { - DiscoveryRpcCall call = startResourceWatcher(LDS, LDS_RESOURCE, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE, + ldsResourceWatcher); fakeClock.forwardTime(ClientXdsClient.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC, TimeUnit.SECONDS); verify(ldsResourceWatcher).onResourceDoesNotExist(LDS_RESOURCE); // Add another watcher. - LdsResourceWatcher watcher = mock(LdsResourceWatcher.class); - xdsClient.watchLdsResource(LDS_RESOURCE, watcher); + ResourceWatcher watcher = mock(ResourceWatcher.class); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),LDS_RESOURCE, watcher); verify(watcher).onResourceDoesNotExist(LDS_RESOURCE); call.verifyNoMoreRequest(); verifyResourceMetadataDoesNotExist(LDS, LDS_RESOURCE); @@ -876,7 +885,8 @@ public void cachedLdsResource_absent() { @Test public void ldsResourceUpdated() { - DiscoveryRpcCall call = startResourceWatcher(LDS, LDS_RESOURCE, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE, + ldsResourceWatcher); verifyResourceMetadataRequested(LDS, LDS_RESOURCE); // Initial LDS response. @@ -903,7 +913,8 @@ public void ldsResourceUpdated_withXdstpResourceName() { String ldsResourceName = useProtocolV3() ? "xdstp://authority.xds.com/envoy.config.listener.v3.Listener/listener1" : "xdstp://authority.xds.com/envoy.api.v2.Listener/listener1"; - DiscoveryRpcCall call = startResourceWatcher(LDS, ldsResourceName, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), ldsResourceName, + ldsResourceWatcher); assertThat(channelForCustomAuthority).isNotNull(); verifyResourceMetadataRequested(LDS, ldsResourceName); @@ -923,7 +934,8 @@ public void ldsResourceUpdated_withXdstpResourceName_withEmptyAuthority() { String ldsResourceName = useProtocolV3() ? "xdstp:///envoy.config.listener.v3.Listener/listener1" : "xdstp:///envoy.api.v2.Listener/listener1"; - DiscoveryRpcCall call = startResourceWatcher(LDS, ldsResourceName, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), ldsResourceName, + ldsResourceWatcher); assertThat(channelForEmptyAuthority).isNotNull(); verifyResourceMetadataRequested(LDS, ldsResourceName); @@ -943,7 +955,8 @@ public void ldsResourceUpdated_withXdstpResourceName_witUnorderedContextParams() String ldsResourceName = useProtocolV3() ? "xdstp://authority.xds.com/envoy.config.listener.v3.Listener/listener1/a?bar=2&foo=1" : "xdstp://authority.xds.com/envoy.api.v2.Listener/listener1/a?bar=2&foo=1"; - DiscoveryRpcCall call = startResourceWatcher(LDS, ldsResourceName, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), ldsResourceName, + ldsResourceWatcher); assertThat(channelForCustomAuthority).isNotNull(); String ldsResourceNameWithUnorderedContextParams = useProtocolV3() @@ -963,7 +976,8 @@ public void ldsResourceUpdated_withXdstpResourceName_withWrongType() { String ldsResourceName = useProtocolV3() ? "xdstp://authority.xds.com/envoy.config.listener.v3.Listener/listener1" : "xdstp://authority.xds.com/envoy.api.v2.Listener/listener1"; - DiscoveryRpcCall call = startResourceWatcher(LDS, ldsResourceName, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), ldsResourceName, + ldsResourceWatcher); assertThat(channelForCustomAuthority).isNotNull(); String ldsResourceNameWithWrongType = @@ -984,7 +998,8 @@ public void rdsResourceUpdated_withXdstpResourceName_withWrongType() { String rdsResourceName = useProtocolV3() ? "xdstp://authority.xds.com/envoy.config.route.v3.RouteConfiguration/route1" : "xdstp://authority.xds.com/envoy.api.v2.RouteConfiguration/route1"; - DiscoveryRpcCall call = startResourceWatcher(RDS, rdsResourceName, rdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsRouteConfigureResource.getInstance(), + rdsResourceName, rdsResourceWatcher); assertThat(channelForCustomAuthority).isNotNull(); String rdsResourceNameWithWrongType = @@ -1004,14 +1019,16 @@ public void rdsResourceUpdated_withXdstpResourceName_unknownAuthority() { String rdsResourceName = useProtocolV3() ? "xdstp://unknown.example.com/envoy.config.route.v3.RouteConfiguration/route1" : "xdstp://unknown.example.com/envoy.api.v2.RouteConfiguration/route1"; - xdsClient.watchRdsResource(rdsResourceName, rdsResourceWatcher); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),rdsResourceName, + rdsResourceWatcher); verify(rdsResourceWatcher).onError(errorCaptor.capture()); Status error = errorCaptor.getValue(); assertThat(error.getCode()).isEqualTo(Code.INVALID_ARGUMENT); assertThat(error.getDescription()).isEqualTo( "Wrong configuration: xds server does not exist for resource " + rdsResourceName); assertThat(resourceDiscoveryCalls.poll()).isNull(); - xdsClient.cancelRdsResourceWatch(rdsResourceName, rdsResourceWatcher); + xdsClient.cancelXdsResourceWatch( + XdsRouteConfigureResource.getInstance(),rdsResourceName, rdsResourceWatcher); assertThat(resourceDiscoveryCalls.poll()).isNull(); } @@ -1021,7 +1038,8 @@ public void cdsResourceUpdated_withXdstpResourceName_withWrongType() { String cdsResourceName = useProtocolV3() ? "xdstp://authority.xds.com/envoy.config.cluster.v3.Cluster/cluster1" : "xdstp://authority.xds.com/envoy.api.v2.Cluster/cluster1"; - DiscoveryRpcCall call = startResourceWatcher(CDS, cdsResourceName, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), cdsResourceName, + cdsResourceWatcher); assertThat(channelForCustomAuthority).isNotNull(); String cdsResourceNameWithWrongType = @@ -1042,14 +1060,16 @@ public void cdsResourceUpdated_withXdstpResourceName_unknownAuthority() { String cdsResourceName = useProtocolV3() ? "xdstp://unknown.example.com/envoy.config.cluster.v3.Cluster/cluster1" : "xdstp://unknown.example.com/envoy.api.v2.Cluster/cluster1"; - xdsClient.watchCdsResource(cdsResourceName, cdsResourceWatcher); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),cdsResourceName, + cdsResourceWatcher); verify(cdsResourceWatcher).onError(errorCaptor.capture()); Status error = errorCaptor.getValue(); assertThat(error.getCode()).isEqualTo(Code.INVALID_ARGUMENT); assertThat(error.getDescription()).isEqualTo( "Wrong configuration: xds server does not exist for resource " + cdsResourceName); assertThat(resourceDiscoveryCalls.poll()).isNull(); - xdsClient.cancelCdsResourceWatch(cdsResourceName, cdsResourceWatcher); + xdsClient.cancelXdsResourceWatch(XdsClusterResource.getInstance(),cdsResourceName, + cdsResourceWatcher); assertThat(resourceDiscoveryCalls.poll()).isNull(); } @@ -1059,7 +1079,8 @@ public void edsResourceUpdated_withXdstpResourceName_withWrongType() { String edsResourceName = useProtocolV3() ? "xdstp://authority.xds.com/envoy.config.endpoint.v3.ClusterLoadAssignment/cluster1" : "xdstp://authority.xds.com/envoy.api.v2.ClusterLoadAssignment/cluster1"; - DiscoveryRpcCall call = startResourceWatcher(EDS, edsResourceName, edsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), edsResourceName, + edsResourceWatcher); assertThat(channelForCustomAuthority).isNotNull(); String edsResourceNameWithWrongType = @@ -1083,21 +1104,24 @@ public void edsResourceUpdated_withXdstpResourceName_unknownAuthority() { String edsResourceName = useProtocolV3() ? "xdstp://unknown.example.com/envoy.config.endpoint.v3.ClusterLoadAssignment/cluster1" : "xdstp://unknown.example.com/envoy.api.v2.ClusterLoadAssignment/cluster1"; - xdsClient.watchEdsResource(edsResourceName, edsResourceWatcher); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(), + edsResourceName, edsResourceWatcher); verify(edsResourceWatcher).onError(errorCaptor.capture()); Status error = errorCaptor.getValue(); assertThat(error.getCode()).isEqualTo(Code.INVALID_ARGUMENT); assertThat(error.getDescription()).isEqualTo( "Wrong configuration: xds server does not exist for resource " + edsResourceName); assertThat(resourceDiscoveryCalls.poll()).isNull(); - xdsClient.cancelEdsResourceWatch(edsResourceName, edsResourceWatcher); + xdsClient.cancelXdsResourceWatch(XdsEndpointResource.getInstance(), + edsResourceName, edsResourceWatcher); assertThat(resourceDiscoveryCalls.poll()).isNull(); } @Test public void ldsResourceUpdate_withFaultInjection() { Assume.assumeTrue(useProtocolV3()); - DiscoveryRpcCall call = startResourceWatcher(LDS, LDS_RESOURCE, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE, + ldsResourceWatcher); Any listener = Any.pack( mf.buildListenerWithApiListener( LDS_RESOURCE, @@ -1163,7 +1187,8 @@ public void ldsResourceUpdate_withFaultInjection() { public void ldsResourceDeleted() { Assume.assumeFalse(ignoreResourceDeletion()); - DiscoveryRpcCall call = startResourceWatcher(LDS, LDS_RESOURCE, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE, + ldsResourceWatcher); verifyResourceMetadataRequested(LDS, LDS_RESOURCE); // Initial LDS response. @@ -1190,7 +1215,8 @@ public void ldsResourceDeleted() { public void ldsResourceDeleted_ignoreResourceDeletion() { Assume.assumeTrue(ignoreResourceDeletion()); - DiscoveryRpcCall call = startResourceWatcher(LDS, LDS_RESOURCE, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE, + ldsResourceWatcher); verifyResourceMetadataRequested(LDS, LDS_RESOURCE); // Initial LDS response. @@ -1223,13 +1249,14 @@ public void ldsResourceDeleted_ignoreResourceDeletion() { } @Test + @SuppressWarnings("unchecked") public void multipleLdsWatchers() { String ldsResourceTwo = "bar.googleapis.com"; - LdsResourceWatcher watcher1 = mock(LdsResourceWatcher.class); - LdsResourceWatcher watcher2 = mock(LdsResourceWatcher.class); - xdsClient.watchLdsResource(LDS_RESOURCE, ldsResourceWatcher); - xdsClient.watchLdsResource(ldsResourceTwo, watcher1); - xdsClient.watchLdsResource(ldsResourceTwo, watcher2); + ResourceWatcher watcher1 = mock(ResourceWatcher.class); + ResourceWatcher watcher2 = mock(ResourceWatcher.class); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),LDS_RESOURCE, ldsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),ldsResourceTwo, watcher1); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),ldsResourceTwo, watcher2); DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); call.verifyRequest(LDS, ImmutableList.of(LDS_RESOURCE, ldsResourceTwo), "", "", NODE); // Both LDS resources were requested. @@ -1247,7 +1274,7 @@ public void multipleLdsWatchers() { Any listenerTwo = Any.pack(mf.buildListenerWithApiListenerForRds(ldsResourceTwo, RDS_RESOURCE)); call.sendResponse(LDS, ImmutableList.of(testListenerVhosts, listenerTwo), VERSION_1, "0000"); - // ldsResourceWatcher called with listenerVhosts. + // ResourceWatcher called with listenerVhosts. verify(ldsResourceWatcher).onChanged(ldsUpdateCaptor.capture()); verifyGoldenListenerVhosts(ldsUpdateCaptor.getValue()); // watcher1 called with listenerTwo. @@ -1266,7 +1293,8 @@ public void multipleLdsWatchers() { @Test public void rdsResourceNotFound() { - DiscoveryRpcCall call = startResourceWatcher(RDS, RDS_RESOURCE, rdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsRouteConfigureResource.getInstance(), + RDS_RESOURCE, rdsResourceWatcher); Any routeConfig = Any.pack(mf.buildRouteConfiguration("route-bar.googleapis.com", mf.buildOpaqueVirtualHosts(2))); call.sendResponse(ResourceType.RDS, routeConfig, VERSION_1, "0000"); @@ -1286,7 +1314,8 @@ public void rdsResourceNotFound() { @Test public void rdsResponseErrorHandling_allResourcesFailedUnpack() { - DiscoveryRpcCall call = startResourceWatcher(RDS, RDS_RESOURCE, rdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsRouteConfigureResource.getInstance(), + RDS_RESOURCE, rdsResourceWatcher); verifyResourceMetadataRequested(RDS, RDS_RESOURCE); call.sendResponse(RDS, ImmutableList.of(FAILING_ANY, FAILING_ANY), VERSION_1, "0000"); @@ -1302,7 +1331,8 @@ public void rdsResponseErrorHandling_allResourcesFailedUnpack() { @Test public void rdsResponseErrorHandling_someResourcesFailedUnpack() { - DiscoveryRpcCall call = startResourceWatcher(RDS, RDS_RESOURCE, rdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsRouteConfigureResource.getInstance(), + RDS_RESOURCE, rdsResourceWatcher); verifyResourceMetadataRequested(RDS, RDS_RESOURCE); // Correct resource is in the middle to ensure processing continues on errors. @@ -1329,9 +1359,9 @@ public void rdsResponseErrorHandling_someResourcesFailedUnpack() { @Test public void rdsResponseErrorHandling_subscribedResourceInvalid() { List subscribedResourceNames = ImmutableList.of("A", "B", "C"); - xdsClient.watchRdsResource("A", rdsResourceWatcher); - xdsClient.watchRdsResource("B", rdsResourceWatcher); - xdsClient.watchRdsResource("C", rdsResourceWatcher); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),"A", rdsResourceWatcher); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),"B", rdsResourceWatcher); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),"C", rdsResourceWatcher); DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); assertThat(call).isNotNull(); verifyResourceMetadataRequested(RDS, "A"); @@ -1386,7 +1416,8 @@ public void rdsResponseErrorHandling_subscribedResourceInvalid() { @Test public void rdsResourceFound() { - DiscoveryRpcCall call = startResourceWatcher(RDS, RDS_RESOURCE, rdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsRouteConfigureResource.getInstance(), + RDS_RESOURCE, rdsResourceWatcher); call.sendResponse(RDS, testRouteConfig, VERSION_1, "0000"); // Client sends an ACK RDS request. @@ -1400,7 +1431,8 @@ public void rdsResourceFound() { @Test public void wrappedRdsResource() { - DiscoveryRpcCall call = startResourceWatcher(RDS, RDS_RESOURCE, rdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsRouteConfigureResource.getInstance(), + RDS_RESOURCE, rdsResourceWatcher); call.sendResponse(RDS, mf.buildWrappedResource(testRouteConfig), VERSION_1, "0000"); // Client sends an ACK RDS request. @@ -1413,15 +1445,17 @@ public void wrappedRdsResource() { } @Test + @SuppressWarnings("unchecked") public void cachedRdsResource_data() { - DiscoveryRpcCall call = startResourceWatcher(RDS, RDS_RESOURCE, rdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsRouteConfigureResource.getInstance(), + RDS_RESOURCE, rdsResourceWatcher); call.sendResponse(RDS, testRouteConfig, VERSION_1, "0000"); // Client sends an ACK RDS request. call.verifyRequest(RDS, RDS_RESOURCE, VERSION_1, "0000", NODE); - RdsResourceWatcher watcher = mock(RdsResourceWatcher.class); - xdsClient.watchRdsResource(RDS_RESOURCE, watcher); + ResourceWatcher watcher = mock(ResourceWatcher.class); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),RDS_RESOURCE, watcher); verify(watcher).onChanged(rdsUpdateCaptor.capture()); verifyGoldenRouteConfig(rdsUpdateCaptor.getValue()); call.verifyNoMoreRequest(); @@ -1430,13 +1464,15 @@ public void cachedRdsResource_data() { } @Test + @SuppressWarnings("unchecked") public void cachedRdsResource_absent() { - DiscoveryRpcCall call = startResourceWatcher(RDS, RDS_RESOURCE, rdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsRouteConfigureResource.getInstance(), + RDS_RESOURCE, rdsResourceWatcher); fakeClock.forwardTime(ClientXdsClient.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC, TimeUnit.SECONDS); verify(rdsResourceWatcher).onResourceDoesNotExist(RDS_RESOURCE); // Add another watcher. - RdsResourceWatcher watcher = mock(RdsResourceWatcher.class); - xdsClient.watchRdsResource(RDS_RESOURCE, watcher); + ResourceWatcher watcher = mock(ResourceWatcher.class); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),RDS_RESOURCE, watcher); verify(watcher).onResourceDoesNotExist(RDS_RESOURCE); call.verifyNoMoreRequest(); verifyResourceMetadataDoesNotExist(RDS, RDS_RESOURCE); @@ -1445,7 +1481,8 @@ public void cachedRdsResource_absent() { @Test public void rdsResourceUpdated() { - DiscoveryRpcCall call = startResourceWatcher(RDS, RDS_RESOURCE, rdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsRouteConfigureResource.getInstance(), + RDS_RESOURCE, rdsResourceWatcher); verifyResourceMetadataRequested(RDS, RDS_RESOURCE); // Initial RDS response. @@ -1471,8 +1508,10 @@ public void rdsResourceUpdated() { @Test public void rdsResourceDeletedByLdsApiListener() { - xdsClient.watchLdsResource(LDS_RESOURCE, ldsResourceWatcher); - xdsClient.watchRdsResource(RDS_RESOURCE, rdsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),LDS_RESOURCE, + ldsResourceWatcher); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),RDS_RESOURCE, + rdsResourceWatcher); verifyResourceMetadataRequested(LDS, LDS_RESOURCE); verifyResourceMetadataRequested(RDS, RDS_RESOURCE); verifySubscribedResourcesMetadataSizes(1, 0, 1, 0); @@ -1509,8 +1548,10 @@ public void rdsResourceDeletedByLdsApiListener() { @Test public void rdsResourcesDeletedByLdsTcpListener() { Assume.assumeTrue(useProtocolV3()); - xdsClient.watchLdsResource(LISTENER_RESOURCE, ldsResourceWatcher); - xdsClient.watchRdsResource(RDS_RESOURCE, rdsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(), LISTENER_RESOURCE, + ldsResourceWatcher); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(), RDS_RESOURCE, + rdsResourceWatcher); verifyResourceMetadataRequested(LDS, LISTENER_RESOURCE); verifyResourceMetadataRequested(RDS, RDS_RESOURCE); verifySubscribedResourcesMetadataSizes(1, 0, 1, 0); @@ -1573,13 +1614,15 @@ public void rdsResourcesDeletedByLdsTcpListener() { } @Test + @SuppressWarnings("unchecked") public void multipleRdsWatchers() { String rdsResourceTwo = "route-bar.googleapis.com"; - RdsResourceWatcher watcher1 = mock(RdsResourceWatcher.class); - RdsResourceWatcher watcher2 = mock(RdsResourceWatcher.class); - xdsClient.watchRdsResource(RDS_RESOURCE, rdsResourceWatcher); - xdsClient.watchRdsResource(rdsResourceTwo, watcher1); - xdsClient.watchRdsResource(rdsResourceTwo, watcher2); + ResourceWatcher watcher1 = mock(ResourceWatcher.class); + ResourceWatcher watcher2 = mock(ResourceWatcher.class); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),RDS_RESOURCE, + rdsResourceWatcher); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),rdsResourceTwo, watcher1); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),rdsResourceTwo, watcher2); DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); call.verifyRequest(RDS, Arrays.asList(RDS_RESOURCE, rdsResourceTwo), "", "", NODE); // Both RDS resources were requested. @@ -1618,7 +1661,8 @@ public void multipleRdsWatchers() { @Test public void cdsResourceNotFound() { - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); List clusters = ImmutableList.of( Any.pack(mf.buildEdsCluster("cluster-bar.googleapis.com", null, "round_robin", null, @@ -1629,7 +1673,7 @@ public void cdsResourceNotFound() { // Client sent an ACK CDS request. call.verifyRequest(CDS, CDS_RESOURCE, VERSION_1, "0000", NODE); - verifyNoInteractions(cdsResourceWatcher); + verifyNoInteractions(ldsResourceWatcher); verifyResourceMetadataRequested(CDS, CDS_RESOURCE); verifySubscribedResourcesMetadataSizes(0, 1, 0, 0); // Server failed to return subscribed resource within expected time window. @@ -1642,7 +1686,8 @@ public void cdsResourceNotFound() { @Test public void cdsResponseErrorHandling_allResourcesFailedUnpack() { - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); verifyResourceMetadataRequested(CDS, CDS_RESOURCE); call.sendResponse(CDS, ImmutableList.of(FAILING_ANY, FAILING_ANY), VERSION_1, "0000"); @@ -1653,12 +1698,13 @@ public void cdsResponseErrorHandling_allResourcesFailedUnpack() { call.verifyRequestNack(CDS, CDS_RESOURCE, "", "0000", NODE, ImmutableList.of( "CDS response Resource index 0 - can't decode Cluster: ", "CDS response Resource index 1 - can't decode Cluster: ")); - verifyNoInteractions(cdsResourceWatcher); + verifyNoInteractions(ldsResourceWatcher); } @Test public void cdsResponseErrorHandling_someResourcesFailedUnpack() { - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); verifyResourceMetadataRequested(CDS, CDS_RESOURCE); // Correct resource is in the middle to ensure processing continues on errors. @@ -1686,9 +1732,9 @@ public void cdsResponseErrorHandling_someResourcesFailedUnpack() { @Test public void cdsResponseErrorHandling_subscribedResourceInvalid() { List subscribedResourceNames = ImmutableList.of("A", "B", "C"); - xdsClient.watchCdsResource("A", cdsResourceWatcher); - xdsClient.watchCdsResource("B", cdsResourceWatcher); - xdsClient.watchCdsResource("C", cdsResourceWatcher); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),"A", cdsResourceWatcher); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),"B", cdsResourceWatcher); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),"C", cdsResourceWatcher); DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); assertThat(call).isNotNull(); verifyResourceMetadataRequested(CDS, "A"); @@ -1762,12 +1808,12 @@ public void cdsResponseErrorHandling_subscribedResourceInvalid() { @Test public void cdsResponseErrorHandling_subscribedResourceInvalid_withEdsSubscription() { List subscribedResourceNames = ImmutableList.of("A", "B", "C"); - xdsClient.watchCdsResource("A", cdsResourceWatcher); - xdsClient.watchEdsResource("A.1", edsResourceWatcher); - xdsClient.watchCdsResource("B", cdsResourceWatcher); - xdsClient.watchEdsResource("B.1", edsResourceWatcher); - xdsClient.watchCdsResource("C", cdsResourceWatcher); - xdsClient.watchEdsResource("C.1", edsResourceWatcher); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),"A", cdsResourceWatcher); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),"A.1", edsResourceWatcher); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),"B", cdsResourceWatcher); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),"B.1", edsResourceWatcher); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),"C", cdsResourceWatcher); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),"C.1", edsResourceWatcher); DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); assertThat(call).isNotNull(); verifyResourceMetadataRequested(CDS, "A"); @@ -1849,7 +1895,8 @@ public void cdsResponseErrorHandling_subscribedResourceInvalid_withEdsSubscripti @Test public void cdsResourceFound() { - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), + CDS_RESOURCE, cdsResourceWatcher); call.sendResponse(CDS, testClusterRoundRobin, VERSION_1, "0000"); // Client sent an ACK CDS request. @@ -1864,7 +1911,8 @@ public void cdsResourceFound() { @Test public void wrappedCdsResource() { - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); call.sendResponse(CDS, mf.buildWrappedResource(testClusterRoundRobin), VERSION_1, "0000"); // Client sent an ACK CDS request. @@ -1879,7 +1927,8 @@ public void wrappedCdsResource() { @Test public void cdsResourceFound_leastRequestLbPolicy() { - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); Message leastRequestConfig = mf.buildLeastRequestLbConfig(3); Any clusterRingHash = Any.pack( mf.buildEdsCluster(CDS_RESOURCE, null, "least_request_experimental", null, @@ -1910,7 +1959,8 @@ public void cdsResourceFound_leastRequestLbPolicy() { @Test public void cdsResourceFound_ringHashLbPolicy() { - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); Message ringHashConfig = mf.buildRingHashLbConfig("xx_hash", 10L, 100L); Any clusterRingHash = Any.pack( mf.buildEdsCluster(CDS_RESOURCE, null, "ring_hash_experimental", ringHashConfig, null, @@ -1941,7 +1991,8 @@ public void cdsResourceFound_ringHashLbPolicy() { @Test public void cdsResponseWithAggregateCluster() { - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); List candidates = Arrays.asList( "cluster1.googleapis.com", "cluster2.googleapis.com", "cluster3.googleapis.com"); Any clusterAggregate = @@ -1966,7 +2017,8 @@ public void cdsResponseWithAggregateCluster() { @Test public void cdsResponseWithCircuitBreakers() { - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); Any clusterCircuitBreakers = Any.pack( mf.buildEdsCluster(CDS_RESOURCE, null, "round_robin", null, null, false, null, "envoy.transport_sockets.tls", mf.buildCircuitBreakers(50, 200), null)); @@ -1999,7 +2051,8 @@ public void cdsResponseWithCircuitBreakers() { @SuppressWarnings("deprecation") public void cdsResponseWithUpstreamTlsContext() { Assume.assumeTrue(useProtocolV3()); - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); // Management server sends back CDS response with UpstreamTlsContext. Any clusterEds = @@ -2017,7 +2070,8 @@ public void cdsResponseWithUpstreamTlsContext() { // Client sent an ACK CDS request. call.verifyRequest(CDS, CDS_RESOURCE, VERSION_1, "0000", NODE); - verify(cdsResourceWatcher, times(1)).onChanged(cdsUpdateCaptor.capture()); + verify(cdsResourceWatcher, times(1)) + .onChanged(cdsUpdateCaptor.capture()); CdsUpdate cdsUpdate = cdsUpdateCaptor.getValue(); CommonTlsContext.CertificateProviderInstance certificateProviderInstance = cdsUpdate.upstreamTlsContext().getCommonTlsContext().getCombinedValidationContext() @@ -2035,7 +2089,8 @@ public void cdsResponseWithUpstreamTlsContext() { @SuppressWarnings("deprecation") public void cdsResponseWithNewUpstreamTlsContext() { Assume.assumeTrue(useProtocolV3()); - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); // Management server sends back CDS response with UpstreamTlsContext. Any clusterEds = @@ -2070,7 +2125,8 @@ public void cdsResponseWithNewUpstreamTlsContext() { @Test public void cdsResponseErrorHandling_badUpstreamTlsContext() { Assume.assumeTrue(useProtocolV3()); - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); // Management server sends back CDS response with UpstreamTlsContext. List clusters = ImmutableList.of(Any @@ -2096,9 +2152,10 @@ public void cdsResponseErrorHandling_badUpstreamTlsContext() { @SuppressWarnings("deprecation") public void cdsResponseWithOutlierDetection() { Assume.assumeTrue(useProtocolV3()); - ClientXdsClient.enableOutlierDetection = true; + XdsClusterResource.enableOutlierDetection = true; - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); OutlierDetection outlierDetectionXds = OutlierDetection.newBuilder() .setInterval(Durations.fromNanos(100)) @@ -2167,9 +2224,10 @@ public void cdsResponseWithOutlierDetection() { @SuppressWarnings("deprecation") public void cdsResponseWithOutlierDetection_supportDisabled() { Assume.assumeTrue(useProtocolV3()); - ClientXdsClient.enableOutlierDetection = false; + XdsClusterResource.enableOutlierDetection = false; - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); OutlierDetection outlierDetectionXds = OutlierDetection.newBuilder() .setInterval(Durations.fromNanos(100)).build(); @@ -2206,9 +2264,10 @@ public void cdsResponseWithOutlierDetection_supportDisabled() { @SuppressWarnings("deprecation") public void cdsResponseWithInvalidOutlierDetectionNacks() { Assume.assumeTrue(useProtocolV3()); - ClientXdsClient.enableOutlierDetection = true; + XdsClusterResource.enableOutlierDetection = true; - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); OutlierDetection outlierDetectionXds = OutlierDetection.newBuilder() .setMaxEjectionPercent(UInt32Value.of(101)).build(); @@ -2238,21 +2297,21 @@ public void cdsResponseWithInvalidOutlierDetectionNacks() { @Test(expected = ResourceInvalidException.class) public void validateOutlierDetection_invalidInterval() throws ResourceInvalidException { - ClientXdsClient.validateOutlierDetection( + XdsClusterResource.validateOutlierDetection( OutlierDetection.newBuilder().setInterval(Duration.newBuilder().setSeconds(Long.MAX_VALUE)) .build()); } @Test(expected = ResourceInvalidException.class) public void validateOutlierDetection_negativeInterval() throws ResourceInvalidException { - ClientXdsClient.validateOutlierDetection( + XdsClusterResource.validateOutlierDetection( OutlierDetection.newBuilder().setInterval(Duration.newBuilder().setSeconds(-1)) .build()); } @Test(expected = ResourceInvalidException.class) public void validateOutlierDetection_invalidBaseEjectionTime() throws ResourceInvalidException { - ClientXdsClient.validateOutlierDetection( + XdsClusterResource.validateOutlierDetection( OutlierDetection.newBuilder() .setBaseEjectionTime(Duration.newBuilder().setSeconds(Long.MAX_VALUE)) .build()); @@ -2260,14 +2319,14 @@ public void validateOutlierDetection_invalidBaseEjectionTime() throws ResourceIn @Test(expected = ResourceInvalidException.class) public void validateOutlierDetection_negativeBaseEjectionTime() throws ResourceInvalidException { - ClientXdsClient.validateOutlierDetection( + XdsClusterResource.validateOutlierDetection( OutlierDetection.newBuilder().setBaseEjectionTime(Duration.newBuilder().setSeconds(-1)) .build()); } @Test(expected = ResourceInvalidException.class) public void validateOutlierDetection_invalidMaxEjectionTime() throws ResourceInvalidException { - ClientXdsClient.validateOutlierDetection( + XdsClusterResource.validateOutlierDetection( OutlierDetection.newBuilder() .setMaxEjectionTime(Duration.newBuilder().setSeconds(Long.MAX_VALUE)) .build()); @@ -2275,35 +2334,35 @@ public void validateOutlierDetection_invalidMaxEjectionTime() throws ResourceInv @Test(expected = ResourceInvalidException.class) public void validateOutlierDetection_negativeMaxEjectionTime() throws ResourceInvalidException { - ClientXdsClient.validateOutlierDetection( + XdsClusterResource.validateOutlierDetection( OutlierDetection.newBuilder().setMaxEjectionTime(Duration.newBuilder().setSeconds(-1)) .build()); } @Test(expected = ResourceInvalidException.class) public void validateOutlierDetection_maxEjectionPercentTooHigh() throws ResourceInvalidException { - ClientXdsClient.validateOutlierDetection( + XdsClusterResource.validateOutlierDetection( OutlierDetection.newBuilder().setMaxEjectionPercent(UInt32Value.of(101)).build()); } @Test(expected = ResourceInvalidException.class) public void validateOutlierDetection_enforcingSuccessRateTooHigh() throws ResourceInvalidException { - ClientXdsClient.validateOutlierDetection( + XdsClusterResource.validateOutlierDetection( OutlierDetection.newBuilder().setEnforcingSuccessRate(UInt32Value.of(101)).build()); } @Test(expected = ResourceInvalidException.class) public void validateOutlierDetection_failurePercentageThresholdTooHigh() throws ResourceInvalidException { - ClientXdsClient.validateOutlierDetection( + XdsClusterResource.validateOutlierDetection( OutlierDetection.newBuilder().setFailurePercentageThreshold(UInt32Value.of(101)).build()); } @Test(expected = ResourceInvalidException.class) public void validateOutlierDetection_enforcingFailurePercentageTooHigh() throws ResourceInvalidException { - ClientXdsClient.validateOutlierDetection( + XdsClusterResource.validateOutlierDetection( OutlierDetection.newBuilder().setEnforcingFailurePercentage(UInt32Value.of(101)).build()); } @@ -2313,7 +2372,8 @@ public void validateOutlierDetection_enforcingFailurePercentageTooHigh() @Test public void cdsResponseErrorHandling_badTransportSocketName() { Assume.assumeTrue(useProtocolV3()); - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); // Management server sends back CDS response with UpstreamTlsContext. List clusters = ImmutableList.of(Any @@ -2332,15 +2392,17 @@ public void cdsResponseErrorHandling_badTransportSocketName() { } @Test + @SuppressWarnings("unchecked") public void cachedCdsResource_data() { - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); call.sendResponse(CDS, testClusterRoundRobin, VERSION_1, "0000"); // Client sends an ACK CDS request. call.verifyRequest(CDS, CDS_RESOURCE, VERSION_1, "0000", NODE); - CdsResourceWatcher watcher = mock(CdsResourceWatcher.class); - xdsClient.watchCdsResource(CDS_RESOURCE, watcher); + ResourceWatcher watcher = mock(ResourceWatcher.class); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(), CDS_RESOURCE, watcher); verify(watcher).onChanged(cdsUpdateCaptor.capture()); verifyGoldenClusterRoundRobin(cdsUpdateCaptor.getValue()); call.verifyNoMoreRequest(); @@ -2351,12 +2413,14 @@ public void cachedCdsResource_data() { } @Test + @SuppressWarnings("unchecked") public void cachedCdsResource_absent() { - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); fakeClock.forwardTime(ClientXdsClient.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC, TimeUnit.SECONDS); verify(cdsResourceWatcher).onResourceDoesNotExist(CDS_RESOURCE); - CdsResourceWatcher watcher = mock(CdsResourceWatcher.class); - xdsClient.watchCdsResource(CDS_RESOURCE, watcher); + ResourceWatcher watcher = mock(ResourceWatcher.class); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),CDS_RESOURCE, watcher); verify(watcher).onResourceDoesNotExist(CDS_RESOURCE); call.verifyNoMoreRequest(); verifyResourceMetadataDoesNotExist(CDS, CDS_RESOURCE); @@ -2365,7 +2429,8 @@ public void cachedCdsResource_absent() { @Test public void cdsResourceUpdated() { - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); verifyResourceMetadataRequested(CDS, CDS_RESOURCE); // Initial CDS response. @@ -2419,7 +2484,8 @@ public void cdsResourceUpdated() { // Assures that CDS updates identical to the current config are ignored. @Test public void cdsResourceUpdatedWithDuplicate() { - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); String edsService = "eds-service-bar.googleapis.com"; String transportSocketName = "envoy.transport_sockets.tls"; @@ -2467,7 +2533,8 @@ public void cdsResourceUpdatedWithDuplicate() { public void cdsResourceDeleted() { Assume.assumeFalse(ignoreResourceDeletion()); - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); verifyResourceMetadataRequested(CDS, CDS_RESOURCE); // Initial CDS response. @@ -2495,7 +2562,8 @@ public void cdsResourceDeleted() { public void cdsResourceDeleted_ignoreResourceDeletion() { Assume.assumeTrue(ignoreResourceDeletion()); - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); verifyResourceMetadataRequested(CDS, CDS_RESOURCE); // Initial CDS response. @@ -2516,7 +2584,7 @@ public void cdsResourceDeleted_ignoreResourceDeletion() { TIME_INCREMENT); verifySubscribedResourcesMetadataSizes(0, 1, 0, 0); // onResourceDoesNotExist must not be called. - verify(cdsResourceWatcher, never()).onResourceDoesNotExist(CDS_RESOURCE); + verify(ldsResourceWatcher, never()).onResourceDoesNotExist(CDS_RESOURCE); // Next update is correct, and contains the cluster again. call.sendResponse(CDS, testClusterRoundRobin, VERSION_3, "0003"); @@ -2526,17 +2594,18 @@ public void cdsResourceDeleted_ignoreResourceDeletion() { verifyResourceMetadataAcked(CDS, CDS_RESOURCE, testClusterRoundRobin, VERSION_3, TIME_INCREMENT * 3); verifySubscribedResourcesMetadataSizes(0, 1, 0, 0); - verifyNoMoreInteractions(cdsResourceWatcher); + verifyNoMoreInteractions(ldsResourceWatcher); } @Test + @SuppressWarnings("unchecked") public void multipleCdsWatchers() { String cdsResourceTwo = "cluster-bar.googleapis.com"; - CdsResourceWatcher watcher1 = mock(CdsResourceWatcher.class); - CdsResourceWatcher watcher2 = mock(CdsResourceWatcher.class); - xdsClient.watchCdsResource(CDS_RESOURCE, cdsResourceWatcher); - xdsClient.watchCdsResource(cdsResourceTwo, watcher1); - xdsClient.watchCdsResource(cdsResourceTwo, watcher2); + ResourceWatcher watcher1 = mock(ResourceWatcher.class); + ResourceWatcher watcher2 = mock(ResourceWatcher.class); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),CDS_RESOURCE, cdsResourceWatcher); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),cdsResourceTwo, watcher1); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),cdsResourceTwo, watcher2); DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); call.verifyRequest(CDS, Arrays.asList(CDS_RESOURCE, cdsResourceTwo), "", "", NODE); verifyResourceMetadataRequested(CDS, CDS_RESOURCE); @@ -2607,7 +2676,8 @@ public void multipleCdsWatchers() { @Test public void edsResourceNotFound() { - DiscoveryRpcCall call = startResourceWatcher(EDS, EDS_RESOURCE, edsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), EDS_RESOURCE, + edsResourceWatcher); Any clusterLoadAssignment = Any.pack(mf.buildClusterLoadAssignment( "cluster-bar.googleapis.com", ImmutableList.of(lbEndpointHealthy), @@ -2629,7 +2699,8 @@ public void edsResourceNotFound() { @Test public void edsResponseErrorHandling_allResourcesFailedUnpack() { - DiscoveryRpcCall call = startResourceWatcher(EDS, EDS_RESOURCE, edsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), EDS_RESOURCE, + edsResourceWatcher); verifyResourceMetadataRequested(EDS, EDS_RESOURCE); call.sendResponse(EDS, ImmutableList.of(FAILING_ANY, FAILING_ANY), VERSION_1, "0000"); @@ -2645,7 +2716,8 @@ public void edsResponseErrorHandling_allResourcesFailedUnpack() { @Test public void edsResponseErrorHandling_someResourcesFailedUnpack() { - DiscoveryRpcCall call = startResourceWatcher(EDS, EDS_RESOURCE, edsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), EDS_RESOURCE, + edsResourceWatcher); verifyResourceMetadataRequested(EDS, EDS_RESOURCE); // Correct resource is in the middle to ensure processing continues on errors. @@ -2675,9 +2747,9 @@ public void edsResponseErrorHandling_someResourcesFailedUnpack() { @Test public void edsResponseErrorHandling_subscribedResourceInvalid() { List subscribedResourceNames = ImmutableList.of("A", "B", "C"); - xdsClient.watchEdsResource("A", edsResourceWatcher); - xdsClient.watchEdsResource("B", edsResourceWatcher); - xdsClient.watchEdsResource("C", edsResourceWatcher); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),"A", edsResourceWatcher); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),"B", edsResourceWatcher); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),"C", edsResourceWatcher); DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); assertThat(call).isNotNull(); verifyResourceMetadataRequested(EDS, "A"); @@ -2735,7 +2807,8 @@ public void edsResponseErrorHandling_subscribedResourceInvalid() { @Test public void edsResourceFound() { - DiscoveryRpcCall call = startResourceWatcher(EDS, EDS_RESOURCE, edsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), EDS_RESOURCE, + edsResourceWatcher); call.sendResponse(EDS, testClusterLoadAssignment, VERSION_1, "0000"); // Client sent an ACK EDS request. @@ -2749,7 +2822,8 @@ public void edsResourceFound() { @Test public void wrappedEdsResourceFound() { - DiscoveryRpcCall call = startResourceWatcher(EDS, EDS_RESOURCE, edsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), EDS_RESOURCE, + edsResourceWatcher); call.sendResponse(EDS, mf.buildWrappedResource(testClusterLoadAssignment), VERSION_1, "0000"); // Client sent an ACK EDS request. @@ -2762,15 +2836,17 @@ public void wrappedEdsResourceFound() { } @Test + @SuppressWarnings("unchecked") public void cachedEdsResource_data() { - DiscoveryRpcCall call = startResourceWatcher(EDS, EDS_RESOURCE, edsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), EDS_RESOURCE, + edsResourceWatcher); call.sendResponse(EDS, testClusterLoadAssignment, VERSION_1, "0000"); // Client sent an ACK EDS request. call.verifyRequest(EDS, EDS_RESOURCE, VERSION_1, "0000", NODE); // Add another watcher. - EdsResourceWatcher watcher = mock(EdsResourceWatcher.class); - xdsClient.watchEdsResource(EDS_RESOURCE, watcher); + ResourceWatcher watcher = mock(ResourceWatcher.class); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),EDS_RESOURCE, watcher); verify(watcher).onChanged(edsUpdateCaptor.capture()); validateGoldenClusterLoadAssignment(edsUpdateCaptor.getValue()); call.verifyNoMoreRequest(); @@ -2780,12 +2856,14 @@ public void cachedEdsResource_data() { } @Test + @SuppressWarnings("unchecked") public void cachedEdsResource_absent() { - DiscoveryRpcCall call = startResourceWatcher(EDS, EDS_RESOURCE, edsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), EDS_RESOURCE, + edsResourceWatcher); fakeClock.forwardTime(ClientXdsClient.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC, TimeUnit.SECONDS); verify(edsResourceWatcher).onResourceDoesNotExist(EDS_RESOURCE); - EdsResourceWatcher watcher = mock(EdsResourceWatcher.class); - xdsClient.watchEdsResource(EDS_RESOURCE, watcher); + ResourceWatcher watcher = mock(ResourceWatcher.class); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),EDS_RESOURCE, watcher); verify(watcher).onResourceDoesNotExist(EDS_RESOURCE); call.verifyNoMoreRequest(); verifyResourceMetadataDoesNotExist(EDS, EDS_RESOURCE); @@ -2794,7 +2872,8 @@ public void cachedEdsResource_absent() { @Test public void edsResourceUpdated() { - DiscoveryRpcCall call = startResourceWatcher(EDS, EDS_RESOURCE, edsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), EDS_RESOURCE, + edsResourceWatcher); verifyResourceMetadataRequested(EDS, EDS_RESOURCE); // Initial EDS response. @@ -2830,7 +2909,8 @@ public void edsResourceUpdated() { @Test public void edsDuplicateLocalityInTheSamePriority() { - DiscoveryRpcCall call = startResourceWatcher(EDS, EDS_RESOURCE, edsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), EDS_RESOURCE, + edsResourceWatcher); verifyResourceMetadataRequested(EDS, EDS_RESOURCE); // Updated EDS response. @@ -2852,14 +2932,15 @@ public void edsDuplicateLocalityInTheSamePriority() { } @Test + @SuppressWarnings("unchecked") public void edsResourceDeletedByCds() { String resource = "backend-service.googleapis.com"; - CdsResourceWatcher cdsWatcher = mock(CdsResourceWatcher.class); - EdsResourceWatcher edsWatcher = mock(EdsResourceWatcher.class); - xdsClient.watchCdsResource(resource, cdsWatcher); - xdsClient.watchEdsResource(resource, edsWatcher); - xdsClient.watchCdsResource(CDS_RESOURCE, cdsResourceWatcher); - xdsClient.watchEdsResource(EDS_RESOURCE, edsResourceWatcher); + ResourceWatcher cdsWatcher = mock(ResourceWatcher.class); + ResourceWatcher edsWatcher = mock(ResourceWatcher.class); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),resource, cdsWatcher); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),resource, edsWatcher); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),CDS_RESOURCE, cdsResourceWatcher); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),EDS_RESOURCE, edsResourceWatcher); verifyResourceMetadataRequested(CDS, CDS_RESOURCE); verifyResourceMetadataRequested(CDS, resource); verifyResourceMetadataRequested(EDS, EDS_RESOURCE); @@ -2939,13 +3020,14 @@ public void edsResourceDeletedByCds() { } @Test + @SuppressWarnings("unchecked") public void multipleEdsWatchers() { String edsResourceTwo = "cluster-load-assignment-bar.googleapis.com"; - EdsResourceWatcher watcher1 = mock(EdsResourceWatcher.class); - EdsResourceWatcher watcher2 = mock(EdsResourceWatcher.class); - xdsClient.watchEdsResource(EDS_RESOURCE, edsResourceWatcher); - xdsClient.watchEdsResource(edsResourceTwo, watcher1); - xdsClient.watchEdsResource(edsResourceTwo, watcher2); + ResourceWatcher watcher1 = mock(ResourceWatcher.class); + ResourceWatcher watcher2 = mock(ResourceWatcher.class); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),EDS_RESOURCE, edsResourceWatcher); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),edsResourceTwo, watcher1); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),edsResourceTwo, watcher2); DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); call.verifyRequest(EDS, Arrays.asList(EDS_RESOURCE, edsResourceTwo), "", "", NODE); verifyResourceMetadataRequested(EDS, EDS_RESOURCE); @@ -3012,7 +3094,8 @@ public void useIndependentRpcContext() { CancellableContext cancellableContext = Context.current().withCancellation(); Context prevContext = cancellableContext.attach(); try { - DiscoveryRpcCall call = startResourceWatcher(LDS, LDS_RESOURCE, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE, + ldsResourceWatcher); // The inbound RPC finishes and closes its context. The outbound RPC's control plane RPC // should not be impacted. @@ -3029,10 +3112,11 @@ public void useIndependentRpcContext() { @Test public void streamClosedAndRetryWithBackoff() { InOrder inOrder = Mockito.inOrder(backoffPolicyProvider, backoffPolicy1, backoffPolicy2); - xdsClient.watchLdsResource(LDS_RESOURCE, ldsResourceWatcher); - xdsClient.watchRdsResource(RDS_RESOURCE, rdsResourceWatcher); - xdsClient.watchCdsResource(CDS_RESOURCE, cdsResourceWatcher); - xdsClient.watchEdsResource(EDS_RESOURCE, edsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),LDS_RESOURCE, ldsResourceWatcher); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),RDS_RESOURCE, + rdsResourceWatcher); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),CDS_RESOURCE, cdsResourceWatcher); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),EDS_RESOURCE, edsResourceWatcher); DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); call.verifyRequest(LDS, LDS_RESOURCE, "", "", NODE); call.verifyRequest(RDS, RDS_RESOURCE, "", "", NODE); @@ -3149,8 +3233,10 @@ public void streamClosedAndRetryWithBackoff() { @Test public void streamClosedAndRetryRaceWithAddRemoveWatchers() { - xdsClient.watchLdsResource(LDS_RESOURCE, ldsResourceWatcher); - xdsClient.watchRdsResource(RDS_RESOURCE, rdsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(), + LDS_RESOURCE, ldsResourceWatcher); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(), + RDS_RESOURCE, rdsResourceWatcher); DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); call.sendError(Status.UNAVAILABLE.asException()); verify(ldsResourceWatcher).onError(errorCaptor.capture()); @@ -3161,10 +3247,14 @@ public void streamClosedAndRetryRaceWithAddRemoveWatchers() { Iterables.getOnlyElement(fakeClock.getPendingTasks(RPC_RETRY_TASK_FILTER)); assertThat(retryTask.getDelay(TimeUnit.NANOSECONDS)).isEqualTo(10L); - xdsClient.cancelLdsResourceWatch(LDS_RESOURCE, ldsResourceWatcher); - xdsClient.cancelRdsResourceWatch(RDS_RESOURCE, rdsResourceWatcher); - xdsClient.watchCdsResource(CDS_RESOURCE, cdsResourceWatcher); - xdsClient.watchEdsResource(EDS_RESOURCE, edsResourceWatcher); + xdsClient.cancelXdsResourceWatch(XdsListenerResource.getInstance(), + LDS_RESOURCE, ldsResourceWatcher); + xdsClient.cancelXdsResourceWatch(XdsRouteConfigureResource.getInstance(), + RDS_RESOURCE, rdsResourceWatcher); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(), + CDS_RESOURCE, cdsResourceWatcher); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(), + EDS_RESOURCE, edsResourceWatcher); fakeClock.forwardNanos(10L); call = resourceDiscoveryCalls.poll(); call.verifyRequest(CDS, CDS_RESOURCE, "", "", NODE); @@ -3181,10 +3271,11 @@ public void streamClosedAndRetryRaceWithAddRemoveWatchers() { @Test public void streamClosedAndRetryRestartsResourceInitialFetchTimerForUnresolvedResources() { - xdsClient.watchLdsResource(LDS_RESOURCE, ldsResourceWatcher); - xdsClient.watchRdsResource(RDS_RESOURCE, rdsResourceWatcher); - xdsClient.watchCdsResource(CDS_RESOURCE, cdsResourceWatcher); - xdsClient.watchEdsResource(EDS_RESOURCE, edsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(), LDS_RESOURCE, ldsResourceWatcher); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(), RDS_RESOURCE, + rdsResourceWatcher); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(), CDS_RESOURCE, cdsResourceWatcher); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(), EDS_RESOURCE, edsResourceWatcher); DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); ScheduledTask ldsResourceTimeout = Iterables.getOnlyElement(fakeClock.getPendingTasks(LDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)); @@ -3221,7 +3312,7 @@ public void streamClosedAndRetryRestartsResourceInitialFetchTimerForUnresolvedRe @Test public void reportLoadStatsToServer() { - xdsClient.watchLdsResource(LDS_RESOURCE, ldsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(), LDS_RESOURCE, ldsResourceWatcher); String clusterName = "cluster-foo.googleapis.com"; ClusterDropStats dropStats = xdsClient.addClusterDropStats(xdsServerInfo, clusterName, null); LrsRpcCall lrsCall = loadReportCalls.poll(); @@ -3249,7 +3340,8 @@ public void reportLoadStatsToServer() { public void serverSideListenerFound() { Assume.assumeTrue(useProtocolV3()); ClientXdsClientTestBase.DiscoveryRpcCall call = - startResourceWatcher(LDS, LISTENER_RESOURCE, ldsResourceWatcher); + startResourceWatcher(XdsListenerResource.getInstance(), LISTENER_RESOURCE, + ldsResourceWatcher); Message hcmFilter = mf.buildHttpConnectionManagerFilter( "route-foo.googleapis.com", null, Collections.singletonList(mf.buildTerminalFilter())); @@ -3285,7 +3377,8 @@ public void serverSideListenerFound() { public void serverSideListenerNotFound() { Assume.assumeTrue(useProtocolV3()); ClientXdsClientTestBase.DiscoveryRpcCall call = - startResourceWatcher(LDS, LISTENER_RESOURCE, ldsResourceWatcher); + startResourceWatcher(XdsListenerResource.getInstance(), LISTENER_RESOURCE, + ldsResourceWatcher); Message hcmFilter = mf.buildHttpConnectionManagerFilter( "route-foo.googleapis.com", null, Collections.singletonList(mf.buildTerminalFilter())); @@ -3312,7 +3405,8 @@ public void serverSideListenerNotFound() { public void serverSideListenerResponseErrorHandling_badDownstreamTlsContext() { Assume.assumeTrue(useProtocolV3()); ClientXdsClientTestBase.DiscoveryRpcCall call = - startResourceWatcher(LDS, LISTENER_RESOURCE, ldsResourceWatcher); + startResourceWatcher(XdsListenerResource.getInstance(), LISTENER_RESOURCE, + ldsResourceWatcher); Message hcmFilter = mf.buildHttpConnectionManagerFilter( "route-foo.googleapis.com", null, Collections.singletonList(mf.buildTerminalFilter())); @@ -3338,7 +3432,8 @@ public void serverSideListenerResponseErrorHandling_badDownstreamTlsContext() { public void serverSideListenerResponseErrorHandling_badTransportSocketName() { Assume.assumeTrue(useProtocolV3()); ClientXdsClientTestBase.DiscoveryRpcCall call = - startResourceWatcher(LDS, LISTENER_RESOURCE, ldsResourceWatcher); + startResourceWatcher(XdsListenerResource.getInstance(), LISTENER_RESOURCE, + ldsResourceWatcher); Message hcmFilter = mf.buildHttpConnectionManagerFilter( "route-foo.googleapis.com", null, Collections.singletonList(mf.buildTerminalFilter())); @@ -3361,32 +3456,32 @@ public void serverSideListenerResponseErrorHandling_badTransportSocketName() { verifyStatusWithNodeId(errorCaptor.getValue(), Code.UNAVAILABLE, errorMsg); } - private DiscoveryRpcCall startResourceWatcher( - ResourceType type, String name, ResourceWatcher watcher) { + private DiscoveryRpcCall startResourceWatcher( + XdsResourceType type, String name, ResourceWatcher watcher) { FakeClock.TaskFilter timeoutTaskFilter; - switch (type) { + switch (type.typeName()) { case LDS: timeoutTaskFilter = LDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER; - xdsClient.watchLdsResource(name, (LdsResourceWatcher) watcher); + xdsClient.watchXdsResource(type, name, watcher); break; case RDS: timeoutTaskFilter = RDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER; - xdsClient.watchRdsResource(name, (RdsResourceWatcher) watcher); + xdsClient.watchXdsResource(type, name, watcher); break; case CDS: timeoutTaskFilter = CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER; - xdsClient.watchCdsResource(name, (CdsResourceWatcher) watcher); + xdsClient.watchXdsResource(type, name, watcher); break; case EDS: timeoutTaskFilter = EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER; - xdsClient.watchEdsResource(name, (EdsResourceWatcher) watcher); + xdsClient.watchXdsResource(type, name, watcher); break; case UNKNOWN: default: throw new AssertionError("should never be here"); } DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); - call.verifyRequest(type, Collections.singletonList(name), "", "", NODE); + call.verifyRequest(type.typeName(), Collections.singletonList(name), "", "", NODE); ScheduledTask timeoutTask = Iterables.getOnlyElement(fakeClock.getPendingTasks(timeoutTaskFilter)); assertThat(timeoutTask.getDelay(TimeUnit.SECONDS)) diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientV2Test.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientV2Test.java index 3e2cafd58fd..3bb6e421388 100644 --- a/xds/src/test/java/io/grpc/xds/ClientXdsClientV2Test.java +++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientV2Test.java @@ -463,7 +463,7 @@ protected Message buildAggregateCluster(String clusterName, String lbPolicy, ClusterConfig clusterConfig = ClusterConfig.newBuilder().addAllClusters(clusters).build(); CustomClusterType type = CustomClusterType.newBuilder() - .setName(ClientXdsClient.AGGREGATE_CLUSTER_TYPE_NAME) + .setName(XdsResourceType.AGGREGATE_CLUSTER_TYPE_NAME) .setTypedConfig(Any.pack(clusterConfig)) .build(); Cluster.Builder builder = Cluster.newBuilder().setName(clusterName).setClusterType(type); diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientV3Test.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientV3Test.java index b051643c0bf..6eb48e5bb00 100644 --- a/xds/src/test/java/io/grpc/xds/ClientXdsClientV3Test.java +++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientV3Test.java @@ -519,7 +519,7 @@ protected Message buildAggregateCluster(String clusterName, String lbPolicy, ClusterConfig clusterConfig = ClusterConfig.newBuilder().addAllClusters(clusters).build(); CustomClusterType type = CustomClusterType.newBuilder() - .setName(ClientXdsClient.AGGREGATE_CLUSTER_TYPE_NAME) + .setName(XdsResourceType.AGGREGATE_CLUSTER_TYPE_NAME) .setTypedConfig(Any.pack(clusterConfig)) .build(); Cluster.Builder builder = Cluster.newBuilder().setName(clusterName).setClusterType(type); diff --git a/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java index 22a4985aaf7..741c2ba8cdb 100644 --- a/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java @@ -17,6 +17,7 @@ package io.grpc.xds; import static com.google.common.truth.Truth.assertThat; +import static io.grpc.xds.AbstractXdsClient.ResourceType.EDS; import static io.grpc.xds.XdsLbPolicies.CLUSTER_IMPL_POLICY_NAME; import static io.grpc.xds.XdsLbPolicies.PRIORITY_POLICY_NAME; import static io.grpc.xds.XdsLbPolicies.WEIGHTED_TARGET_POLICY_NAME; @@ -76,6 +77,7 @@ import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig.PriorityChildConfig; import io.grpc.xds.RingHashLoadBalancer.RingHashConfig; import io.grpc.xds.WrrLocalityLoadBalancer.WrrLocalityConfig; +import io.grpc.xds.XdsEndpointResource.EdsUpdate; import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; import java.net.SocketAddress; import java.net.URI; @@ -185,7 +187,6 @@ public XdsClient returnObject(Object object) { private int xdsClientRefs; private ClusterResolverLoadBalancer loadBalancer; - @Before public void setUp() throws URISyntaxException { MockitoAnnotations.initMocks(this); @@ -1168,16 +1169,24 @@ public String toString() { } private static final class FakeXdsClient extends XdsClient { - private final Map watchers = new HashMap<>(); + private final Map> watchers = new HashMap<>(); + @Override - void watchEdsResource(String resourceName, EdsResourceWatcher watcher) { + @SuppressWarnings("unchecked") + void watchXdsResource(XdsResourceType type, String resourceName, + ResourceWatcher watcher) { + assertThat(type.typeName()).isEqualTo(EDS); assertThat(watchers).doesNotContainKey(resourceName); - watchers.put(resourceName, watcher); + watchers.put(resourceName, (ResourceWatcher) watcher); } @Override - void cancelEdsResourceWatch(String resourceName, EdsResourceWatcher watcher) { + @SuppressWarnings("unchecked") + void cancelXdsResourceWatch(XdsResourceType type, + String resourceName, + ResourceWatcher watcher) { + assertThat(type.typeName()).isEqualTo(EDS); assertThat(watchers).containsKey(resourceName); watchers.remove(resourceName); } @@ -1192,7 +1201,7 @@ void deliverClusterLoadAssignment(String resource, List dropOverlo Map localityLbEndpointsMap) { if (watchers.containsKey(resource)) { watchers.get(resource).onChanged( - new EdsUpdate(resource, localityLbEndpointsMap, dropOverloads)); + new XdsEndpointResource.EdsUpdate(resource, localityLbEndpointsMap, dropOverloads)); } } @@ -1203,7 +1212,7 @@ void deliverResourceNotFound(String resource) { } void deliverError(Status error) { - for (EdsResourceWatcher watcher : watchers.values()) { + for (ResourceWatcher watcher : watchers.values()) { watcher.onError(error); } } diff --git a/xds/src/test/java/io/grpc/xds/XdsClientWrapperForServerSdsTestMisc.java b/xds/src/test/java/io/grpc/xds/XdsClientWrapperForServerSdsTestMisc.java index cec4688c069..50991781180 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientWrapperForServerSdsTestMisc.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientWrapperForServerSdsTestMisc.java @@ -44,7 +44,7 @@ import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; import io.grpc.xds.FilterChainMatchingProtocolNegotiators.FilterChainMatchingHandler; import io.grpc.xds.FilterChainMatchingProtocolNegotiators.FilterChainMatchingHandler.FilterChainSelector; -import io.grpc.xds.XdsClient.LdsUpdate; +import io.grpc.xds.XdsListenerResource.LdsUpdate; import io.grpc.xds.XdsServerBuilder.XdsServingStatusListener; import io.grpc.xds.XdsServerTestHelper.FakeXdsClient; import io.grpc.xds.XdsServerTestHelper.FakeXdsClientPoolFactory; diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java index d96fa2b30a8..f2eddf00fe6 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java @@ -86,7 +86,9 @@ import io.grpc.xds.VirtualHost.Route.RouteAction.RetryPolicy; import io.grpc.xds.VirtualHost.Route.RouteMatch; import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher; +import io.grpc.xds.XdsListenerResource.LdsUpdate; import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory; +import io.grpc.xds.XdsRouteConfigureResource.RdsUpdate; import io.grpc.xds.internal.Matchers.HeaderMatcher; import java.io.IOException; import java.util.ArrayList; @@ -2072,8 +2074,8 @@ private class FakeXdsClient extends XdsClient { // Should never be subscribing to more than one LDS and RDS resource at any point of time. private String ldsResource; // should always be AUTHORITY private String rdsResource; - private LdsResourceWatcher ldsWatcher; - private RdsResourceWatcher rdsWatcher; + private ResourceWatcher ldsWatcher; + private ResourceWatcher rdsWatcher; @Override BootstrapInfo getBootstrapInfo() { @@ -2081,37 +2083,49 @@ BootstrapInfo getBootstrapInfo() { } @Override - void watchLdsResource(String resourceName, LdsResourceWatcher watcher) { - assertThat(ldsResource).isNull(); - assertThat(ldsWatcher).isNull(); - assertThat(resourceName).isEqualTo(expectedLdsResourceName); - ldsResource = resourceName; - ldsWatcher = watcher; - } - - @Override - void cancelLdsResourceWatch(String resourceName, LdsResourceWatcher watcher) { - assertThat(ldsResource).isNotNull(); - assertThat(ldsWatcher).isNotNull(); - assertThat(resourceName).isEqualTo(expectedLdsResourceName); - ldsResource = null; - ldsWatcher = null; - } - - @Override - void watchRdsResource(String resourceName, RdsResourceWatcher watcher) { - assertThat(rdsResource).isNull(); - assertThat(rdsWatcher).isNull(); - rdsResource = resourceName; - rdsWatcher = watcher; + @SuppressWarnings("unchecked") + void watchXdsResource(XdsResourceType resourceType, + String resourceName, + ResourceWatcher watcher) { + + switch (resourceType.typeName()) { + case LDS: + assertThat(ldsResource).isNull(); + assertThat(ldsWatcher).isNull(); + assertThat(resourceName).isEqualTo(expectedLdsResourceName); + ldsResource = resourceName; + ldsWatcher = (ResourceWatcher) watcher; + break; + case RDS: + assertThat(rdsResource).isNull(); + assertThat(rdsWatcher).isNull(); + rdsResource = resourceName; + rdsWatcher = (ResourceWatcher) watcher; + break; + default: + } } @Override - void cancelRdsResourceWatch(String resourceName, RdsResourceWatcher watcher) { - assertThat(rdsResource).isNotNull(); - assertThat(rdsWatcher).isNotNull(); - rdsResource = null; - rdsWatcher = null; + void cancelXdsResourceWatch(XdsResourceType type, + String resourceName, + ResourceWatcher watcher) { + switch (type.typeName()) { + case LDS: + assertThat(ldsResource).isNotNull(); + assertThat(ldsWatcher).isNotNull(); + assertThat(resourceName).isEqualTo(expectedLdsResourceName); + ldsResource = null; + ldsWatcher = null; + break; + case RDS: + assertThat(rdsResource).isNotNull(); + assertThat(rdsWatcher).isNotNull(); + rdsResource = null; + rdsWatcher = null; + break; + default: + } } void deliverLdsUpdate(long httpMaxStreamDurationNano, List virtualHosts) { diff --git a/xds/src/test/java/io/grpc/xds/XdsSdsClientServerTest.java b/xds/src/test/java/io/grpc/xds/XdsSdsClientServerTest.java index fe28da8bffe..3ef23c11375 100644 --- a/xds/src/test/java/io/grpc/xds/XdsSdsClientServerTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsSdsClientServerTest.java @@ -57,7 +57,7 @@ import io.grpc.xds.VirtualHost.Route; import io.grpc.xds.VirtualHost.Route.RouteMatch; import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher; -import io.grpc.xds.XdsClient.LdsUpdate; +import io.grpc.xds.XdsListenerResource.LdsUpdate; import io.grpc.xds.XdsServerTestHelper.FakeXdsClient; import io.grpc.xds.XdsServerTestHelper.FakeXdsClientPoolFactory; import io.grpc.xds.internal.Matchers.HeaderMatcher; diff --git a/xds/src/test/java/io/grpc/xds/XdsServerTestHelper.java b/xds/src/test/java/io/grpc/xds/XdsServerTestHelper.java index 15868ba414e..c13be0361df 100644 --- a/xds/src/test/java/io/grpc/xds/XdsServerTestHelper.java +++ b/xds/src/test/java/io/grpc/xds/XdsServerTestHelper.java @@ -30,7 +30,8 @@ import io.grpc.xds.Filter.FilterConfig; import io.grpc.xds.Filter.NamedFilterConfig; import io.grpc.xds.VirtualHost.Route; -import io.grpc.xds.XdsClient.LdsUpdate; +import io.grpc.xds.XdsListenerResource.LdsUpdate; +import io.grpc.xds.XdsRouteConfigureResource.RdsUpdate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -163,9 +164,9 @@ public XdsClient returnObject(Object object) { static final class FakeXdsClient extends XdsClient { boolean shutdown; SettableFuture ldsResource = SettableFuture.create(); - LdsResourceWatcher ldsWatcher; + ResourceWatcher ldsWatcher; CountDownLatch rdsCount = new CountDownLatch(1); - final Map rdsWatchers = new HashMap<>(); + final Map> rdsWatchers = new HashMap<>(); @Override public TlsContextManager getTlsContextManager() { @@ -178,28 +179,40 @@ public BootstrapInfo getBootstrapInfo() { } @Override - void watchLdsResource(String resourceName, LdsResourceWatcher watcher) { - assertThat(ldsWatcher).isNull(); - ldsWatcher = watcher; - ldsResource.set(resourceName); + @SuppressWarnings("unchecked") + void watchXdsResource(XdsResourceType resourceType, + String resourceName, + ResourceWatcher watcher) { + switch (resourceType.typeName()) { + case LDS: + assertThat(ldsWatcher).isNull(); + ldsWatcher = (ResourceWatcher) watcher; + ldsResource.set(resourceName); + break; + case RDS: + //re-register is not allowed. + assertThat(rdsWatchers.put(resourceName, (ResourceWatcher)watcher)).isNull(); + rdsCount.countDown(); + break; + default: + } } @Override - void cancelLdsResourceWatch(String resourceName, LdsResourceWatcher watcher) { - assertThat(ldsWatcher).isNotNull(); - ldsResource = null; - ldsWatcher = null; - } - - @Override - void watchRdsResource(String resourceName, RdsResourceWatcher watcher) { - assertThat(rdsWatchers.put(resourceName, watcher)).isNull(); //re-register is not allowed. - rdsCount.countDown(); - } - - @Override - void cancelRdsResourceWatch(String resourceName, RdsResourceWatcher watcher) { - rdsWatchers.remove(resourceName); + void cancelXdsResourceWatch(XdsResourceType type, + String resourceName, + ResourceWatcher watcher) { + switch (type.typeName()) { + case LDS: + assertThat(ldsWatcher).isNotNull(); + ldsResource = null; + ldsWatcher = null; + break; + case RDS: + rdsWatchers.remove(resourceName); + break; + default: + } } @Override @@ -213,7 +226,7 @@ boolean isShutDown() { } void deliverLdsUpdate(List filterChains, - FilterChain defaultFilterChain) { + FilterChain defaultFilterChain) { ldsWatcher.onChanged(LdsUpdate.forTcpListener(Listener.create( "listener", "0.0.0.0:1", ImmutableList.copyOf(filterChains), defaultFilterChain))); } diff --git a/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java b/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java index d0b05112bae..ef2f606f3fa 100644 --- a/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java @@ -57,9 +57,8 @@ import io.grpc.xds.VirtualHost.Route; import io.grpc.xds.VirtualHost.Route.RouteMatch; import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher; -import io.grpc.xds.XdsClient.LdsResourceWatcher; -import io.grpc.xds.XdsClient.RdsResourceWatcher; -import io.grpc.xds.XdsClient.RdsUpdate; +import io.grpc.xds.XdsClient.ResourceWatcher; +import io.grpc.xds.XdsRouteConfigureResource.RdsUpdate; import io.grpc.xds.XdsServerBuilder.XdsServingStatusListener; import io.grpc.xds.XdsServerTestHelper.FakeXdsClient; import io.grpc.xds.XdsServerTestHelper.FakeXdsClientPoolFactory; @@ -177,6 +176,7 @@ public void run() { } @Test + @SuppressWarnings("unchecked") public void testBootstrap_templateWithXdstp() throws Exception { Bootstrapper.BootstrapInfo b = Bootstrapper.BootstrapInfo.builder() .servers(Arrays.asList( @@ -187,6 +187,7 @@ public void testBootstrap_templateWithXdstp() throws Exception { "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/grpc/server/%s") .build(); XdsClient xdsClient = mock(XdsClient.class); + XdsListenerResource listenerResource = XdsListenerResource.getInstance(); when(xdsClient.getBootstrapInfo()).thenReturn(b); xdsServerWrapper = new XdsServerWrapper("[::FFFF:129.144.52.38]:80", mockBuilder, listener, selectorManager, new FakeXdsClientPoolFactory(xdsClient), filterRegistry); @@ -200,10 +201,11 @@ public void run() { } } }); - verify(xdsClient, timeout(5000)).watchLdsResource( + verify(xdsClient, timeout(5000)).watchXdsResource( + eq(listenerResource), eq("xdstp://xds.authority.com/envoy.config.listener.v3.Listener/grpc/server/" + "%5B::FFFF:129.144.52.38%5D:80"), - any(LdsResourceWatcher.class)); + any(ResourceWatcher.class)); } @Test @@ -727,7 +729,7 @@ public void run() { xdsClient.ldsWatcher.onError(Status.INTERNAL); assertThat(selectorManager.getSelectorToUpdateSelector()) .isSameInstanceAs(FilterChainSelector.NO_FILTER_CHAIN); - RdsResourceWatcher saveRdsWatcher = xdsClient.rdsWatchers.get("rds"); + ResourceWatcher saveRdsWatcher = xdsClient.rdsWatchers.get("rds"); verify(mockBuilder, times(1)).build(); verify(listener, times(2)).onNotServing(any(StatusException.class)); assertThat(sslSupplier0.isShutdown()).isFalse(); From 8925696b3e6a1cf59208a6da5f4c33c4476172e6 Mon Sep 17 00:00:00 2001 From: apolcyn Date: Mon, 19 Sep 2022 08:29:41 -0700 Subject: [PATCH 63/72] Revert "xds: prevent concurrent priority LB picker updates (#9363)" (#9554) This reverts commit bcf5cde7dd31de4be3cfc8bf8264681abc8fb292. --- .../io/grpc/xds/PriorityLoadBalancer.java | 65 +++++++---------- .../io/grpc/xds/PriorityLoadBalancerTest.java | 69 +------------------ 2 files changed, 28 insertions(+), 106 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java b/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java index 7a6bff4d779..5cae9139ae6 100644 --- a/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java @@ -58,7 +58,7 @@ final class PriorityLoadBalancer extends LoadBalancer { private final XdsLogger logger; // Includes all active and deactivated children. Mutable. New entries are only added from priority - // 0 up to the selected priority. An entry is only deleted 15 minutes after its deactivation. + // 0 up to the selected priority. An entry is only deleted 15 minutes after the its deactivation. private final Map children = new HashMap<>(); // Following fields are only null initially. @@ -70,8 +70,6 @@ final class PriorityLoadBalancer extends LoadBalancer { @Nullable private String currentPriority; private ConnectivityState currentConnectivityState; private SubchannelPicker currentPicker; - // Set to true if currently in the process of handling resolved addresses. - private boolean handlingResolvedAddresses; PriorityLoadBalancer(Helper helper) { this.helper = checkNotNull(helper, "helper"); @@ -84,15 +82,6 @@ final class PriorityLoadBalancer extends LoadBalancer { @Override public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { - try { - handlingResolvedAddresses = true; - handleResolvedAddressesInternal(resolvedAddresses); - } finally { - handlingResolvedAddresses = false; - } - } - - public void handleResolvedAddressesInternal(ResolvedAddresses resolvedAddresses) { logger.log(XdsLogLevel.DEBUG, "Received resolution result: {0}", resolvedAddresses); this.resolvedAddresses = resolvedAddresses; PriorityLbConfig config = (PriorityLbConfig) resolvedAddresses.getLoadBalancingPolicyConfig(); @@ -308,34 +297,32 @@ public void refreshNameResolution() { @Override public void updateBalancingState(final ConnectivityState newState, final SubchannelPicker newPicker) { - if (!children.containsKey(priority)) { - return; - } - connectivityState = newState; - picker = newPicker; - - if (deletionTimer != null && deletionTimer.isPending()) { - return; - } - if (newState.equals(CONNECTING)) { - if (!failOverTimer.isPending() && seenReadyOrIdleSinceTransientFailure) { - failOverTimer = syncContext.schedule(new FailOverTask(), 10, TimeUnit.SECONDS, - executor); + syncContext.execute(new Runnable() { + @Override + public void run() { + if (!children.containsKey(priority)) { + return; + } + connectivityState = newState; + picker = newPicker; + if (deletionTimer != null && deletionTimer.isPending()) { + return; + } + if (newState.equals(CONNECTING) ) { + if (!failOverTimer.isPending() && seenReadyOrIdleSinceTransientFailure) { + failOverTimer = syncContext.schedule(new FailOverTask(), 10, TimeUnit.SECONDS, + executor); + } + } else if (newState.equals(READY) || newState.equals(IDLE)) { + seenReadyOrIdleSinceTransientFailure = true; + failOverTimer.cancel(); + } else if (newState.equals(TRANSIENT_FAILURE)) { + seenReadyOrIdleSinceTransientFailure = false; + failOverTimer.cancel(); + } + tryNextPriority(); } - } else if (newState.equals(READY) || newState.equals(IDLE)) { - seenReadyOrIdleSinceTransientFailure = true; - failOverTimer.cancel(); - } else if (newState.equals(TRANSIENT_FAILURE)) { - seenReadyOrIdleSinceTransientFailure = false; - failOverTimer.cancel(); - } - - // If we are currently handling newly resolved addresses, let's not try to reconfigure as - // the address handling process will take care of that to provide an atomic config update. - if (handlingResolvedAddresses) { - return; - } - tryNextPriority(); + }); } @Override diff --git a/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java index 7227dcb3b2f..6b1fae48f1d 100644 --- a/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java @@ -22,8 +22,8 @@ import static io.grpc.ConnectivityState.READY; import static io.grpc.ConnectivityState.TRANSIENT_FAILURE; import static io.grpc.xds.XdsSubchannelPickers.BUFFER_PICKER; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; @@ -676,7 +676,7 @@ public void raceBetweenShutdownAndChildLbBalancingStateUpdate() { .setAddresses(ImmutableList.of()) .setLoadBalancingPolicyConfig(priorityLbConfig) .build()); - verify(helper).updateBalancingState(eq(CONNECTING), eq(BUFFER_PICKER)); + verify(helper, times(2)).updateBalancingState(eq(CONNECTING), isA(SubchannelPicker.class)); // LB shutdown and subchannel state change can happen simultaneously. If shutdown runs first, // any further balancing state update should be ignored. @@ -686,26 +686,6 @@ public void raceBetweenShutdownAndChildLbBalancingStateUpdate() { verifyNoMoreInteractions(helper); } - @Test - public void noDuplicateOverallBalancingStateUpdate() { - FakeLoadBalancerProvider fakeLbProvider = new FakeLoadBalancerProvider(); - - PriorityChildConfig priorityChildConfig0 = - new PriorityChildConfig(new PolicySelection(fakeLbProvider, new Object()), true); - PriorityChildConfig priorityChildConfig1 = - new PriorityChildConfig(new PolicySelection(fakeLbProvider, new Object()), false); - PriorityLbConfig priorityLbConfig = - new PriorityLbConfig( - ImmutableMap.of("p0", priorityChildConfig0, "p1", priorityChildConfig1), - ImmutableList.of("p0", "p1")); - priorityLb.handleResolvedAddresses( - ResolvedAddresses.newBuilder() - .setAddresses(ImmutableList.of()) - .setLoadBalancingPolicyConfig(priorityLbConfig) - .build()); - verify(helper, times(1)).updateBalancingState(any(), any()); - } - private void assertLatestConnectivityState(ConnectivityState expectedState) { verify(helper, atLeastOnce()) .updateBalancingState(connectivityStateCaptor.capture(), pickerCaptor.capture()); @@ -734,49 +714,4 @@ private void assertCurrentPickerIsBufferPicker() { PickResult pickResult = pickerCaptor.getValue().pickSubchannel(mock(PickSubchannelArgs.class)); assertThat(pickResult).isEqualTo(PickResult.withNoResult()); } - - private static class FakeLoadBalancerProvider extends LoadBalancerProvider { - - @Override - public boolean isAvailable() { - return true; - } - - @Override - public int getPriority() { - return 5; - } - - @Override - public String getPolicyName() { - return "foo"; - } - - @Override - public LoadBalancer newLoadBalancer(Helper helper) { - return new FakeLoadBalancer(helper); - } - } - - static class FakeLoadBalancer extends LoadBalancer { - - private Helper helper; - - FakeLoadBalancer(Helper helper) { - this.helper = helper; - } - - @Override - public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { - helper.updateBalancingState(TRANSIENT_FAILURE, new ErrorPicker(Status.INTERNAL)); - } - - @Override - public void handleNameResolutionError(Status error) { - } - - @Override - public void shutdown() { - } - } } From 2289956ec7159687b5ec5863ec494194df864efa Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Mon, 19 Sep 2022 20:20:08 -0700 Subject: [PATCH 64/72] core: outlier_detection LB to use acceptResolvedAddresses() (#9557) Switch OutlierDetectionLoadBalancer to implement acceptResolvedAddresses() to allow for the eventual deprecation of handleResolvedAddresses(). --- .../util/OutlierDetectionLoadBalancer.java | 3 +- .../OutlierDetectionLoadBalancerTest.java | 71 ++++++++++--------- 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java index 87f9bdce2b3..e7d93d9d52f 100644 --- a/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java +++ b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java @@ -89,7 +89,7 @@ public OutlierDetectionLoadBalancer(Helper helper, TimeProvider timeProvider) { } @Override - public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { + public boolean acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { OutlierDetectionLoadBalancerConfig config = (OutlierDetectionLoadBalancerConfig) resolvedAddresses.getLoadBalancingPolicyConfig(); @@ -142,6 +142,7 @@ public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { switchLb.handleResolvedAddresses( resolvedAddresses.toBuilder().setLoadBalancingPolicyConfig(config.childPolicy.getConfig()) .build()); + return true; } @Override diff --git a/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java b/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java index 4e94b0089fd..ccf3d40cdb6 100644 --- a/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java +++ b/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java @@ -202,7 +202,7 @@ public void handleNameResolutionError_noChildLb() { @Test public void handleNameResolutionError_withChildLb() { - loadBalancer.handleResolvedAddresses(buildResolvedAddress( + loadBalancer.acceptResolvedAddresses(buildResolvedAddress( new OutlierDetectionLoadBalancerConfig.Builder() .setSuccessRateEjection(new SuccessRateEjection.Builder().build()) .setChildPolicy(new PolicySelection(mockChildLbProvider, null)).build(), @@ -217,7 +217,7 @@ public void handleNameResolutionError_withChildLb() { */ @Test public void shutdown() { - loadBalancer.handleResolvedAddresses(buildResolvedAddress( + loadBalancer.acceptResolvedAddresses(buildResolvedAddress( new OutlierDetectionLoadBalancerConfig.Builder() .setSuccessRateEjection(new SuccessRateEjection.Builder().build()) .setChildPolicy(new PolicySelection(mockChildLbProvider, null)).build(), @@ -230,14 +230,14 @@ public void shutdown() { * Base case for accepting new resolved addresses. */ @Test - public void handleResolvedAddresses() { + public void acceptResolvedAddresses() { OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() .setSuccessRateEjection(new SuccessRateEjection.Builder().build()) .setChildPolicy(new PolicySelection(mockChildLbProvider, null)).build(); ResolvedAddresses resolvedAddresses = buildResolvedAddress(config, new EquivalentAddressGroup(mockSocketAddress)); - loadBalancer.handleResolvedAddresses(resolvedAddresses); + loadBalancer.acceptResolvedAddresses(resolvedAddresses); // Handling of resolved addresses is delegated verify(mockChildLb).handleResolvedAddresses( @@ -256,14 +256,14 @@ public void handleResolvedAddresses() { * Outlier detection first enabled, then removed. */ @Test - public void handleResolvedAddresses_outlierDetectionDisabled() { + public void acceptResolvedAddresses_outlierDetectionDisabled() { OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() .setSuccessRateEjection(new SuccessRateEjection.Builder().build()) .setChildPolicy(new PolicySelection(mockChildLbProvider, null)).build(); ResolvedAddresses resolvedAddresses = buildResolvedAddress(config, new EquivalentAddressGroup(mockSocketAddress)); - loadBalancer.handleResolvedAddresses(resolvedAddresses); + loadBalancer.acceptResolvedAddresses(resolvedAddresses); fakeClock.forwardTime(15, TimeUnit.SECONDS); @@ -272,7 +272,7 @@ public void handleResolvedAddresses_outlierDetectionDisabled() { config = new OutlierDetectionLoadBalancerConfig.Builder().setChildPolicy( new PolicySelection(mockChildLbProvider, null)).build(); - loadBalancer.handleResolvedAddresses( + loadBalancer.acceptResolvedAddresses( buildResolvedAddress(config, new EquivalentAddressGroup(mockSocketAddress))); // Pending task should be gone since OD is disabled. @@ -284,14 +284,14 @@ public void handleResolvedAddresses_outlierDetectionDisabled() { * Tests different scenarios when the timer interval in the config changes. */ @Test - public void handleResolvedAddresses_intervalUpdate() { + public void acceptResolvedAddresses_intervalUpdate() { OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() .setSuccessRateEjection(new SuccessRateEjection.Builder().build()) .setChildPolicy(new PolicySelection(mockChildLbProvider, null)).build(); ResolvedAddresses resolvedAddresses = buildResolvedAddress(config, new EquivalentAddressGroup(mockSocketAddress)); - loadBalancer.handleResolvedAddresses(resolvedAddresses); + loadBalancer.acceptResolvedAddresses(resolvedAddresses); // Config update has doubled the interval config = new OutlierDetectionLoadBalancerConfig.Builder() @@ -299,7 +299,7 @@ public void handleResolvedAddresses_intervalUpdate() { .setSuccessRateEjection(new SuccessRateEjection.Builder().build()) .setChildPolicy(new PolicySelection(mockChildLbProvider, null)).build(); - loadBalancer.handleResolvedAddresses( + loadBalancer.acceptResolvedAddresses( buildResolvedAddress(config, new EquivalentAddressGroup(mockSocketAddress))); // If the timer has not run yet the task is just rescheduled to run after the new delay. @@ -320,7 +320,7 @@ public void handleResolvedAddresses_intervalUpdate() { // not changing in the config, the next task due time should remain unchanged. fakeClock.forwardTime(4, TimeUnit.SECONDS); task = fakeClock.getPendingTasks().iterator().next(); - loadBalancer.handleResolvedAddresses( + loadBalancer.acceptResolvedAddresses( buildResolvedAddress(config, new EquivalentAddressGroup(mockSocketAddress))); assertThat(task.dueTimeNanos).isEqualTo(config.intervalNanos + config.intervalNanos + 1); } @@ -334,7 +334,7 @@ public void delegatePick() throws Exception { .setSuccessRateEjection(new SuccessRateEjection.Builder().build()) .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); - loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers.get(0))); + loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers.get(0))); // Make one of the subchannels READY. final Subchannel readySubchannel = subchannels.values().iterator().next(); @@ -361,7 +361,7 @@ public void successRateNoOutliers() { new SuccessRateEjection.Builder().setMinimumHosts(3).setRequestVolume(10).build()) .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); - loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers)); generateLoad(ImmutableMap.of(), 7); @@ -385,7 +385,7 @@ public void successRateOneOutlier() { .setRequestVolume(10).build()) .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); - loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers)); generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); @@ -410,7 +410,7 @@ public void successRateOneOutlier_configChange() { .setRequestVolume(10).build()) .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); - loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers)); generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); @@ -430,7 +430,7 @@ public void successRateOneOutlier_configChange() { .setEnforcementPercentage(0).build()) .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); - loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers)); generateLoad(ImmutableMap.of(subchannel2, Status.DEADLINE_EXCEEDED), 8); @@ -455,7 +455,7 @@ public void successRateOneOutlier_unejected() { .setRequestVolume(10).build()) .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); - loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers)); generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); @@ -488,7 +488,7 @@ public void successRateOneOutlier_notEnoughVolume() { .setRequestVolume(20).build()) .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); - loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers)); // We produce an outlier, but don't give it enough calls to reach the minimum volume. generateLoad( @@ -516,7 +516,7 @@ public void successRateOneOutlier_notEnoughAddressesWithVolume() { .setRequestVolume(20).build()) .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); - loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers)); generateLoad( ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), @@ -546,7 +546,7 @@ public void successRateOneOutlier_enforcementPercentage() { .build()) .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); - loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers)); generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); @@ -571,7 +571,7 @@ public void successRateTwoOutliers() { .setStdevFactor(1).build()) .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); - loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers)); generateLoad(ImmutableMap.of( subchannel1, Status.DEADLINE_EXCEEDED, @@ -600,7 +600,7 @@ public void successRateThreeOutliers_maxEjectionPercentage() { .setStdevFactor(1).build()) .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); - loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers)); generateLoad(ImmutableMap.of( subchannel1, Status.DEADLINE_EXCEEDED, @@ -634,7 +634,7 @@ public void failurePercentageNoOutliers() { .setRequestVolume(10).build()) .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); - loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers)); // By default all calls will return OK. generateLoad(ImmutableMap.of(), 7); @@ -659,7 +659,7 @@ public void failurePercentageOneOutlier() { .setRequestVolume(10).build()) .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); - loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers)); generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); @@ -683,7 +683,7 @@ public void failurePercentageOneOutlier_notEnoughVolume() { .setRequestVolume(100).build()) // We won't produce this much volume... .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); - loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers)); generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); @@ -708,7 +708,7 @@ public void failurePercentageOneOutlier_notEnoughAddressesWithVolume() { .setRequestVolume(20).build()) .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); - loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers)); generateLoad( ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), @@ -738,7 +738,7 @@ public void failurePercentageOneOutlier_enforcementPercentage() { .build()) .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); - loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers)); generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); @@ -767,7 +767,7 @@ public void successRateAndFailurePercentageThreeOutliers() { .build()) .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); - loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers)); // Three subchannels with problems, but one only has a single call that failed. // This is not enough for success rate to catch, but failure percentage is @@ -804,7 +804,7 @@ public void subchannelUpdateAddress_singleReplaced() { .setRequestVolume(10).build()) .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); - loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers)); generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); @@ -851,7 +851,7 @@ public void subchannelUpdateAddress_singleReplacedWithMultiple() { .setRequestVolume(10).build()) .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); - loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers)); generateLoad(ImmutableMap.of(), 7); @@ -895,7 +895,7 @@ public void subchannelUpdateAddress_multipleReplacedWithSingle() { .setRequestVolume(10).build()) .setChildPolicy(new PolicySelection(fakeLbProvider, null)).build(); - loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers)); generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 6); @@ -957,7 +957,7 @@ public void successRateAndFailurePercentage_noOutliers() { .setRequestVolume(10).build()) .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); - loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers)); generateLoad(ImmutableMap.of(), 7); @@ -984,7 +984,7 @@ public void successRateAndFailurePercentage_successRateOutlier() { .setEnforcementPercentage(0).build()) // Configured, but not enforcing. .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); - loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers)); generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); @@ -1011,7 +1011,7 @@ public void successRateAndFailurePercentage_errorPercentageOutlier() { .setRequestVolume(10).build()) // Configured, but not enforcing. .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); - loadBalancer.handleResolvedAddresses(buildResolvedAddress(config, servers)); + loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers)); generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); @@ -1127,7 +1127,7 @@ private static final class FakeLoadBalancer extends LoadBalancer { } @Override - public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { + public boolean acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { subchannelList = new ArrayList<>(); for (EquivalentAddressGroup eag: resolvedAddresses.getAddresses()) { Subchannel subchannel = helper.createSubchannel(CreateSubchannelArgs.newBuilder() @@ -1136,6 +1136,7 @@ public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { subchannel.start(mock(SubchannelStateListener.class)); deliverSubchannelState(READY); } + return true; } @Override From 0cda133c52ed937f9b0a19bcbfc36bf2892c7aa8 Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Fri, 23 Sep 2022 13:24:27 -0700 Subject: [PATCH 65/72] observability: replace current OpenCensus registerAllGrpcViews with only selected views for GCP observability (#9561) --- .../gcp/observability/GcpObservability.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/gcp-observability/src/main/java/io/grpc/gcp/observability/GcpObservability.java b/gcp-observability/src/main/java/io/grpc/gcp/observability/GcpObservability.java index 751ac058cd8..6587444db59 100644 --- a/gcp-observability/src/main/java/io/grpc/gcp/observability/GcpObservability.java +++ b/gcp-observability/src/main/java/io/grpc/gcp/observability/GcpObservability.java @@ -37,13 +37,15 @@ import io.grpc.gcp.observability.logging.Sink; import io.grpc.internal.TimeProvider; import io.opencensus.common.Duration; -import io.opencensus.contrib.grpc.metrics.RpcViews; +import io.opencensus.contrib.grpc.metrics.RpcViewConstants; import io.opencensus.exporter.stats.stackdriver.StackdriverStatsConfiguration; import io.opencensus.exporter.stats.stackdriver.StackdriverStatsExporter; import io.opencensus.exporter.trace.stackdriver.StackdriverTraceConfiguration; import io.opencensus.exporter.trace.stackdriver.StackdriverTraceExporter; import io.opencensus.metrics.LabelKey; import io.opencensus.metrics.LabelValue; +import io.opencensus.stats.Stats; +import io.opencensus.stats.ViewManager; import io.opencensus.trace.AttributeValue; import io.opencensus.trace.Tracing; import io.opencensus.trace.config.TraceConfig; @@ -145,11 +147,23 @@ static ConditionalClientInterceptor getConditionalInterceptor(ClientInterceptor (m, c) -> !SERVICES_TO_EXCLUDE.contains(m.getServiceName())); } + private static void registerObservabilityViews() { + ViewManager viewManager = Stats.getViewManager(); + + // client views + viewManager.registerView(RpcViewConstants.GRPC_CLIENT_COMPLETED_RPC_VIEW); + viewManager.registerView(RpcViewConstants.GRPC_CLIENT_STARTED_RPC_VIEW); + + // server views + viewManager.registerView(RpcViewConstants.GRPC_SERVER_COMPLETED_RPC_VIEW); + viewManager.registerView(RpcViewConstants.GRPC_SERVER_STARTED_RPC_VIEW); + } + @VisibleForTesting void registerStackDriverExporter(String projectId, Map customTags) throws IOException { if (config.isEnableCloudMonitoring()) { - RpcViews.registerAllGrpcViews(); + registerObservabilityViews(); StackdriverStatsConfiguration.Builder statsConfigurationBuilder = StackdriverStatsConfiguration.builder(); if (projectId != null) { From 6f8e44a7f52e94491423b3fa8e41463e916514a8 Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Sat, 24 Sep 2022 00:05:15 -0700 Subject: [PATCH 66/72] xds: security code refactoring/renaming (#9555) * xds: security code refactoring/renaming 1) move certprovider package under security 2) refactor inner Factory into CertProviderClientSslContextProviderFactory and CertProviderServerSslContextProviderFactory 3) Make CertProviderClientSslContextProvider and CertProviderServerSslContextProvider non-public 4) use only public (non package private) types like SslContextProvider (instead of CertProviderClientSslContextProvider etc) --- .../ClientSslContextProviderFactory.java | 8 +- .../security/DynamicSslContextProvider.java | 2 + .../ServerSslContextProviderFactory.java | 8 +- .../internal/security/SslContextProvider.java | 2 + .../CertProviderClientSslContextProvider.java | 47 +----------- ...oviderClientSslContextProviderFactory.java | 76 +++++++++++++++++++ .../CertProviderServerSslContextProvider.java | 62 +++------------ ...oviderServerSslContextProviderFactory.java | 76 +++++++++++++++++++ .../CertProviderSslContextProvider.java | 2 +- .../certprovider/CertificateProvider.java | 2 +- .../CertificateProviderProvider.java | 4 +- .../CertificateProviderRegistry.java | 2 +- .../CertificateProviderStore.java | 5 +- .../FileWatcherCertificateProvider.java | 2 +- ...ileWatcherCertificateProviderProvider.java | 2 +- .../ClientSslContextProviderFactoryTest.java | 40 ++++++---- .../SecurityProtocolNegotiatorsTest.java | 2 +- .../ServerSslContextProviderFactoryTest.java | 33 ++++---- ...tProviderClientSslContextProviderTest.java | 20 ++--- ...tProviderServerSslContextProviderTest.java | 22 +++--- .../CertificateProviderStoreTest.java | 2 +- .../CommonCertProviderTestUtils.java | 4 +- ...atcherCertificateProviderProviderTest.java | 2 +- .../FileWatcherCertificateProviderTest.java | 4 +- .../certprovider/TestCertificateProvider.java | 2 +- 25 files changed, 261 insertions(+), 170 deletions(-) rename xds/src/main/java/io/grpc/xds/internal/{ => security}/certprovider/CertProviderClientSslContextProvider.java (58%) create mode 100644 xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderClientSslContextProviderFactory.java rename xds/src/main/java/io/grpc/xds/internal/{ => security}/certprovider/CertProviderServerSslContextProvider.java (52%) create mode 100644 xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderServerSslContextProviderFactory.java rename xds/src/main/java/io/grpc/xds/internal/{ => security}/certprovider/CertProviderSslContextProvider.java (99%) rename xds/src/main/java/io/grpc/xds/internal/{ => security}/certprovider/CertificateProvider.java (98%) rename xds/src/main/java/io/grpc/xds/internal/{ => security}/certprovider/CertificateProviderProvider.java (93%) rename xds/src/main/java/io/grpc/xds/internal/{ => security}/certprovider/CertificateProviderRegistry.java (98%) rename xds/src/main/java/io/grpc/xds/internal/{ => security}/certprovider/CertificateProviderStore.java (98%) rename xds/src/main/java/io/grpc/xds/internal/{ => security}/certprovider/FileWatcherCertificateProvider.java (99%) rename xds/src/main/java/io/grpc/xds/internal/{ => security}/certprovider/FileWatcherCertificateProviderProvider.java (99%) rename xds/src/test/java/io/grpc/xds/internal/{ => security}/certprovider/CertProviderClientSslContextProviderTest.java (95%) rename xds/src/test/java/io/grpc/xds/internal/{ => security}/certprovider/CertProviderServerSslContextProviderTest.java (95%) rename xds/src/test/java/io/grpc/xds/internal/{ => security}/certprovider/CertificateProviderStoreTest.java (99%) rename xds/src/test/java/io/grpc/xds/internal/{ => security}/certprovider/CommonCertProviderTestUtils.java (94%) rename xds/src/test/java/io/grpc/xds/internal/{ => security}/certprovider/FileWatcherCertificateProviderProviderTest.java (99%) rename xds/src/test/java/io/grpc/xds/internal/{ => security}/certprovider/FileWatcherCertificateProviderTest.java (99%) rename xds/src/test/java/io/grpc/xds/internal/{ => security}/certprovider/TestCertificateProvider.java (98%) diff --git a/xds/src/main/java/io/grpc/xds/internal/security/ClientSslContextProviderFactory.java b/xds/src/main/java/io/grpc/xds/internal/security/ClientSslContextProviderFactory.java index c7f2dfc001f..4bf11fba3ff 100644 --- a/xds/src/main/java/io/grpc/xds/internal/security/ClientSslContextProviderFactory.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/ClientSslContextProviderFactory.java @@ -20,23 +20,23 @@ import io.grpc.xds.Bootstrapper.BootstrapInfo; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; -import io.grpc.xds.internal.certprovider.CertProviderClientSslContextProvider; import io.grpc.xds.internal.security.ReferenceCountingMap.ValueFactory; +import io.grpc.xds.internal.security.certprovider.CertProviderClientSslContextProviderFactory; /** Factory to create client-side SslContextProvider from UpstreamTlsContext. */ final class ClientSslContextProviderFactory implements ValueFactory { private BootstrapInfo bootstrapInfo; - private final CertProviderClientSslContextProvider.Factory + private final CertProviderClientSslContextProviderFactory certProviderClientSslContextProviderFactory; ClientSslContextProviderFactory(BootstrapInfo bootstrapInfo) { - this(bootstrapInfo, CertProviderClientSslContextProvider.Factory.getInstance()); + this(bootstrapInfo, CertProviderClientSslContextProviderFactory.getInstance()); } ClientSslContextProviderFactory( - BootstrapInfo bootstrapInfo, CertProviderClientSslContextProvider.Factory factory) { + BootstrapInfo bootstrapInfo, CertProviderClientSslContextProviderFactory factory) { this.bootstrapInfo = bootstrapInfo; this.certProviderClientSslContextProviderFactory = factory; } diff --git a/xds/src/main/java/io/grpc/xds/internal/security/DynamicSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/security/DynamicSslContextProvider.java index 64c6ff8b6b6..6bf66d022ff 100644 --- a/xds/src/main/java/io/grpc/xds/internal/security/DynamicSslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/DynamicSslContextProvider.java @@ -21,6 +21,7 @@ import com.google.common.collect.ImmutableList; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; +import io.grpc.Internal; import io.grpc.Status; import io.grpc.xds.EnvoyServerProtoData.BaseTlsContext; import io.netty.handler.ssl.ApplicationProtocolConfig; @@ -34,6 +35,7 @@ import javax.annotation.Nullable; /** Base class for dynamic {@link SslContextProvider}s. */ +@Internal public abstract class DynamicSslContextProvider extends SslContextProvider { protected final List pendingCallbacks = new ArrayList<>(); diff --git a/xds/src/main/java/io/grpc/xds/internal/security/ServerSslContextProviderFactory.java b/xds/src/main/java/io/grpc/xds/internal/security/ServerSslContextProviderFactory.java index 14e038c9a48..6206ccdcfe6 100644 --- a/xds/src/main/java/io/grpc/xds/internal/security/ServerSslContextProviderFactory.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/ServerSslContextProviderFactory.java @@ -20,23 +20,23 @@ import io.grpc.xds.Bootstrapper.BootstrapInfo; import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; -import io.grpc.xds.internal.certprovider.CertProviderServerSslContextProvider; import io.grpc.xds.internal.security.ReferenceCountingMap.ValueFactory; +import io.grpc.xds.internal.security.certprovider.CertProviderServerSslContextProviderFactory; /** Factory to create server-side SslContextProvider from DownstreamTlsContext. */ final class ServerSslContextProviderFactory implements ValueFactory { private BootstrapInfo bootstrapInfo; - private final CertProviderServerSslContextProvider.Factory + private final CertProviderServerSslContextProviderFactory certProviderServerSslContextProviderFactory; ServerSslContextProviderFactory(BootstrapInfo bootstrapInfo) { - this(bootstrapInfo, CertProviderServerSslContextProvider.Factory.getInstance()); + this(bootstrapInfo, CertProviderServerSslContextProviderFactory.getInstance()); } ServerSslContextProviderFactory( - BootstrapInfo bootstrapInfo, CertProviderServerSslContextProvider.Factory factory) { + BootstrapInfo bootstrapInfo, CertProviderServerSslContextProviderFactory factory) { this.bootstrapInfo = bootstrapInfo; this.certProviderServerSslContextProviderFactory = factory; } diff --git a/xds/src/main/java/io/grpc/xds/internal/security/SslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/security/SslContextProvider.java index 17683940897..7544f5d9fc3 100644 --- a/xds/src/main/java/io/grpc/xds/internal/security/SslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/SslContextProvider.java @@ -21,6 +21,7 @@ import com.google.common.annotations.VisibleForTesting; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; +import io.grpc.Internal; import io.grpc.xds.EnvoyServerProtoData.BaseTlsContext; import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; @@ -39,6 +40,7 @@ * stream that is receiving the requested secret(s) or it could represent file-system based * secret(s) that are dynamic. */ +@Internal public abstract class SslContextProvider implements Closeable { protected final BaseTlsContext tlsContext; diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderClientSslContextProvider.java similarity index 58% rename from xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProvider.java rename to xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderClientSslContextProvider.java index d8d71be197b..3953fd5c46b 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderClientSslContextProvider.java @@ -14,15 +14,13 @@ * limitations under the License. */ -package io.grpc.xds.internal.certprovider; +package io.grpc.xds.internal.security.certprovider; import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.VisibleForTesting; import io.envoyproxy.envoy.config.core.v3.Node; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; -import io.grpc.Internal; import io.grpc.netty.GrpcSslContexts; import io.grpc.xds.Bootstrapper.CertificateProviderInfo; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; @@ -34,10 +32,9 @@ import javax.annotation.Nullable; /** A client SslContext provider using CertificateProviderInstance to fetch secrets. */ -@Internal -public final class CertProviderClientSslContextProvider extends CertProviderSslContextProvider { +final class CertProviderClientSslContextProvider extends CertProviderSslContextProvider { - private CertProviderClientSslContextProvider( + CertProviderClientSslContextProvider( Node node, @Nullable Map certProviders, CommonTlsContext.CertificateProviderInstance certInstance, @@ -71,42 +68,4 @@ protected final SslContextBuilder getSslContextBuilder( return sslContextBuilder; } - /** Creates CertProviderClientSslContextProvider. */ - @Internal - public static final class Factory { - private static final Factory DEFAULT_INSTANCE = - new Factory(CertificateProviderStore.getInstance()); - private final CertificateProviderStore certificateProviderStore; - - @VisibleForTesting public Factory(CertificateProviderStore certificateProviderStore) { - this.certificateProviderStore = certificateProviderStore; - } - - public static Factory getInstance() { - return DEFAULT_INSTANCE; - } - - /** Creates a {@link CertProviderClientSslContextProvider}. */ - public CertProviderClientSslContextProvider getProvider( - UpstreamTlsContext upstreamTlsContext, - Node node, - @Nullable Map certProviders) { - checkNotNull(upstreamTlsContext, "upstreamTlsContext"); - CommonTlsContext commonTlsContext = upstreamTlsContext.getCommonTlsContext(); - CertificateValidationContext staticCertValidationContext = getStaticValidationContext( - commonTlsContext); - CommonTlsContext.CertificateProviderInstance rootCertInstance = getRootCertProviderInstance( - commonTlsContext); - CommonTlsContext.CertificateProviderInstance certInstance = getCertProviderInstance( - commonTlsContext); - return new CertProviderClientSslContextProvider( - node, - certProviders, - certInstance, - rootCertInstance, - staticCertValidationContext, - upstreamTlsContext, - certificateProviderStore); - } - } } diff --git a/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderClientSslContextProviderFactory.java b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderClientSslContextProviderFactory.java new file mode 100644 index 00000000000..ef91cb56703 --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderClientSslContextProviderFactory.java @@ -0,0 +1,76 @@ +/* + * Copyright 2022 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.xds.internal.security.certprovider; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.VisibleForTesting; +import io.envoyproxy.envoy.config.core.v3.Node; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; +import io.grpc.Internal; +import io.grpc.xds.Bootstrapper.CertificateProviderInfo; +import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; +import io.grpc.xds.internal.security.SslContextProvider; +import java.util.Map; +import javax.annotation.Nullable; + +/** + * Creates CertProviderClientSslContextProvider. + */ +@Internal +public final class CertProviderClientSslContextProviderFactory { + + private static final CertProviderClientSslContextProviderFactory DEFAULT_INSTANCE = + new CertProviderClientSslContextProviderFactory(CertificateProviderStore.getInstance()); + private final CertificateProviderStore certificateProviderStore; + + @VisibleForTesting + public CertProviderClientSslContextProviderFactory( + CertificateProviderStore certificateProviderStore) { + this.certificateProviderStore = certificateProviderStore; + } + + public static CertProviderClientSslContextProviderFactory getInstance() { + return DEFAULT_INSTANCE; + } + + /** + * Creates a {@link CertProviderClientSslContextProvider}. + */ + public SslContextProvider getProvider( + UpstreamTlsContext upstreamTlsContext, + Node node, + @Nullable Map certProviders) { + checkNotNull(upstreamTlsContext, "upstreamTlsContext"); + CommonTlsContext commonTlsContext = upstreamTlsContext.getCommonTlsContext(); + CertificateValidationContext staticCertValidationContext + = CertProviderSslContextProvider.getStaticValidationContext(commonTlsContext); + CommonTlsContext.CertificateProviderInstance rootCertInstance + = CertProviderSslContextProvider.getRootCertProviderInstance(commonTlsContext); + CommonTlsContext.CertificateProviderInstance certInstance + = CertProviderSslContextProvider.getCertProviderInstance(commonTlsContext); + return new CertProviderClientSslContextProvider( + node, + certProviders, + certInstance, + rootCertInstance, + staticCertValidationContext, + upstreamTlsContext, + certificateProviderStore); + } +} diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderServerSslContextProvider.java similarity index 52% rename from xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProvider.java rename to xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderServerSslContextProvider.java index 036591c0922..9d936f02dc1 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderServerSslContextProvider.java @@ -14,21 +14,18 @@ * limitations under the License. */ -package io.grpc.xds.internal.certprovider; +package io.grpc.xds.internal.security.certprovider; import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.annotations.VisibleForTesting; import io.envoyproxy.envoy.config.core.v3.Node; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; -import io.grpc.Internal; import io.grpc.netty.GrpcSslContexts; import io.grpc.xds.Bootstrapper.CertificateProviderInfo; import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; import io.grpc.xds.internal.security.trust.XdsTrustManagerFactory; import io.netty.handler.ssl.SslContextBuilder; - import java.io.IOException; import java.security.cert.CertStoreException; import java.security.cert.CertificateException; @@ -37,17 +34,16 @@ import javax.annotation.Nullable; /** A server SslContext provider using CertificateProviderInstance to fetch secrets. */ -@Internal -public final class CertProviderServerSslContextProvider extends CertProviderSslContextProvider { +final class CertProviderServerSslContextProvider extends CertProviderSslContextProvider { - private CertProviderServerSslContextProvider( - Node node, - @Nullable Map certProviders, - CommonTlsContext.CertificateProviderInstance certInstance, - CommonTlsContext.CertificateProviderInstance rootCertInstance, - CertificateValidationContext staticCertValidationContext, - DownstreamTlsContext downstreamTlsContext, - CertificateProviderStore certificateProviderStore) { + CertProviderServerSslContextProvider( + Node node, + @Nullable Map certProviders, + CommonTlsContext.CertificateProviderInstance certInstance, + CommonTlsContext.CertificateProviderInstance rootCertInstance, + CertificateValidationContext staticCertValidationContext, + DownstreamTlsContext downstreamTlsContext, + CertificateProviderStore certificateProviderStore) { super( node, certProviders, @@ -74,42 +70,4 @@ protected final SslContextBuilder getSslContextBuilder( return sslContextBuilder; } - /** Creates CertProviderServerSslContextProvider. */ - @Internal - public static final class Factory { - private static final Factory DEFAULT_INSTANCE = - new Factory(CertificateProviderStore.getInstance()); - private final CertificateProviderStore certificateProviderStore; - - @VisibleForTesting public Factory(CertificateProviderStore certificateProviderStore) { - this.certificateProviderStore = certificateProviderStore; - } - - public static Factory getInstance() { - return DEFAULT_INSTANCE; - } - - /** Creates a {@link CertProviderServerSslContextProvider}. */ - public CertProviderServerSslContextProvider getProvider( - DownstreamTlsContext downstreamTlsContext, - Node node, - @Nullable Map certProviders) { - checkNotNull(downstreamTlsContext, "downstreamTlsContext"); - CommonTlsContext commonTlsContext = downstreamTlsContext.getCommonTlsContext(); - CertificateValidationContext staticCertValidationContext = getStaticValidationContext( - commonTlsContext); - CommonTlsContext.CertificateProviderInstance rootCertInstance = getRootCertProviderInstance( - commonTlsContext); - CommonTlsContext.CertificateProviderInstance certInstance = getCertProviderInstance( - commonTlsContext); - return new CertProviderServerSslContextProvider( - node, - certProviders, - certInstance, - rootCertInstance, - staticCertValidationContext, - downstreamTlsContext, - certificateProviderStore); - } - } } diff --git a/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderServerSslContextProviderFactory.java b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderServerSslContextProviderFactory.java new file mode 100644 index 00000000000..3189d49f27b --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderServerSslContextProviderFactory.java @@ -0,0 +1,76 @@ +/* + * Copyright 2022 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.xds.internal.security.certprovider; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.VisibleForTesting; +import io.envoyproxy.envoy.config.core.v3.Node; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; +import io.grpc.Internal; +import io.grpc.xds.Bootstrapper.CertificateProviderInfo; +import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; +import io.grpc.xds.internal.security.SslContextProvider; +import java.util.Map; +import javax.annotation.Nullable; + +/** + * Creates CertProviderServerSslContextProvider. + */ +@Internal +public final class CertProviderServerSslContextProviderFactory { + + private static final CertProviderServerSslContextProviderFactory DEFAULT_INSTANCE = + new CertProviderServerSslContextProviderFactory(CertificateProviderStore.getInstance()); + private final CertificateProviderStore certificateProviderStore; + + @VisibleForTesting + public CertProviderServerSslContextProviderFactory( + CertificateProviderStore certificateProviderStore) { + this.certificateProviderStore = certificateProviderStore; + } + + public static CertProviderServerSslContextProviderFactory getInstance() { + return DEFAULT_INSTANCE; + } + + /** + * Creates a {@link CertProviderServerSslContextProvider}. + */ + public SslContextProvider getProvider( + DownstreamTlsContext downstreamTlsContext, + Node node, + @Nullable Map certProviders) { + checkNotNull(downstreamTlsContext, "downstreamTlsContext"); + CommonTlsContext commonTlsContext = downstreamTlsContext.getCommonTlsContext(); + CertificateValidationContext staticCertValidationContext + = CertProviderSslContextProvider.getStaticValidationContext(commonTlsContext); + CommonTlsContext.CertificateProviderInstance rootCertInstance + = CertProviderSslContextProvider.getRootCertProviderInstance(commonTlsContext); + CommonTlsContext.CertificateProviderInstance certInstance + = CertProviderSslContextProvider.getCertProviderInstance(commonTlsContext); + return new CertProviderServerSslContextProvider( + node, + certProviders, + certInstance, + rootCertInstance, + staticCertValidationContext, + downstreamTlsContext, + certificateProviderStore); + } +} diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderSslContextProvider.java similarity index 99% rename from xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderSslContextProvider.java rename to xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderSslContextProvider.java index 0bdbf36e9ea..065501fa53c 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderSslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderSslContextProvider.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.certprovider; +package io.grpc.xds.internal.security.certprovider; import io.envoyproxy.envoy.config.core.v3.Node; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProvider.java b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertificateProvider.java similarity index 98% rename from xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProvider.java rename to xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertificateProvider.java index 3ecbba6cb37..a0d5d0fc69f 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertificateProvider.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.certprovider; +package io.grpc.xds.internal.security.certprovider; import static com.google.common.base.Preconditions.checkNotNull; diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderProvider.java b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertificateProviderProvider.java similarity index 93% rename from xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderProvider.java rename to xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertificateProviderProvider.java index a426542eea0..e2e26ead502 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertificateProviderProvider.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package io.grpc.xds.internal.certprovider; +package io.grpc.xds.internal.security.certprovider; import io.grpc.Internal; -import io.grpc.xds.internal.certprovider.CertificateProvider.Watcher; +import io.grpc.xds.internal.security.certprovider.CertificateProvider.Watcher; /** * Provider of {@link CertificateProvider}s. Implemented by the implementer of the plugin. We may diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderRegistry.java b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertificateProviderRegistry.java similarity index 98% rename from xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderRegistry.java rename to xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertificateProviderRegistry.java index 12eb6f6573f..2c320b79964 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderRegistry.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertificateProviderRegistry.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.certprovider; +package io.grpc.xds.internal.security.certprovider; import static com.google.common.base.Preconditions.checkNotNull; diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderStore.java b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertificateProviderStore.java similarity index 98% rename from xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderStore.java rename to xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertificateProviderStore.java index 08ed25bf9cb..0fe342a36c0 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderStore.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertificateProviderStore.java @@ -14,12 +14,11 @@ * limitations under the License. */ -package io.grpc.xds.internal.certprovider; +package io.grpc.xds.internal.security.certprovider; import com.google.common.annotations.VisibleForTesting; -import io.grpc.xds.internal.certprovider.CertificateProvider.Watcher; import io.grpc.xds.internal.security.ReferenceCountingMap; - +import io.grpc.xds.internal.security.certprovider.CertificateProvider.Watcher; import java.io.Closeable; import java.util.Objects; import java.util.logging.Level; diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/FileWatcherCertificateProvider.java b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProvider.java similarity index 99% rename from xds/src/main/java/io/grpc/xds/internal/certprovider/FileWatcherCertificateProvider.java rename to xds/src/main/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProvider.java index 4cddc19ea40..e5855f55b08 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/FileWatcherCertificateProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProvider.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.certprovider; +package io.grpc.xds.internal.security.certprovider; import static com.google.common.base.Preconditions.checkNotNull; diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/FileWatcherCertificateProviderProvider.java b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProviderProvider.java similarity index 99% rename from xds/src/main/java/io/grpc/xds/internal/certprovider/FileWatcherCertificateProviderProvider.java rename to xds/src/main/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProviderProvider.java index c1b0ce3f508..c4b140442cb 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/FileWatcherCertificateProviderProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProviderProvider.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.certprovider; +package io.grpc.xds.internal.security.certprovider; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; diff --git a/xds/src/test/java/io/grpc/xds/internal/security/ClientSslContextProviderFactoryTest.java b/xds/src/test/java/io/grpc/xds/internal/security/ClientSslContextProviderFactoryTest.java index 8e81c241705..4f85afc2ead 100644 --- a/xds/src/test/java/io/grpc/xds/internal/security/ClientSslContextProviderFactoryTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/security/ClientSslContextProviderFactoryTest.java @@ -32,12 +32,12 @@ import io.grpc.xds.CommonBootstrapperTestUtils; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.XdsInitializationException; -import io.grpc.xds.internal.certprovider.CertProviderClientSslContextProvider; -import io.grpc.xds.internal.certprovider.CertificateProvider; -import io.grpc.xds.internal.certprovider.CertificateProviderProvider; -import io.grpc.xds.internal.certprovider.CertificateProviderRegistry; -import io.grpc.xds.internal.certprovider.CertificateProviderStore; -import io.grpc.xds.internal.certprovider.TestCertificateProvider; +import io.grpc.xds.internal.security.certprovider.CertProviderClientSslContextProviderFactory; +import io.grpc.xds.internal.security.certprovider.CertificateProvider; +import io.grpc.xds.internal.security.certprovider.CertificateProviderProvider; +import io.grpc.xds.internal.security.certprovider.CertificateProviderRegistry; +import io.grpc.xds.internal.security.certprovider.CertificateProviderStore; +import io.grpc.xds.internal.security.certprovider.TestCertificateProvider; import java.io.IOException; import org.junit.Assert; import org.junit.Before; @@ -53,7 +53,7 @@ public class ClientSslContextProviderFactoryTest { CertificateProviderRegistry certificateProviderRegistry; CertificateProviderStore certificateProviderStore; - CertProviderClientSslContextProvider.Factory certProviderClientSslContextProviderFactory; + CertProviderClientSslContextProviderFactory certProviderClientSslContextProviderFactory; ClientSslContextProviderFactory clientSslContextProviderFactory; @Before @@ -61,7 +61,7 @@ public void setUp() { certificateProviderRegistry = new CertificateProviderRegistry(); certificateProviderStore = new CertificateProviderStore(certificateProviderRegistry); certProviderClientSslContextProviderFactory = - new CertProviderClientSslContextProvider.Factory(certificateProviderStore); + new CertProviderClientSslContextProviderFactory(certificateProviderStore); } @Test @@ -84,12 +84,14 @@ public void createCertProviderClientSslContextProvider() throws XdsInitializatio bootstrapInfo, certProviderClientSslContextProviderFactory); SslContextProvider sslContextProvider = clientSslContextProviderFactory.create(upstreamTlsContext); - assertThat(sslContextProvider).isInstanceOf(CertProviderClientSslContextProvider.class); + assertThat(sslContextProvider.getClass().getSimpleName()).isEqualTo( + "CertProviderClientSslContextProvider"); verifyWatcher(sslContextProvider, watcherCaptor[0]); // verify that bootstrapInfo is cached... sslContextProvider = clientSslContextProviderFactory.create(upstreamTlsContext); - assertThat(sslContextProvider).isInstanceOf(CertProviderClientSslContextProvider.class); + assertThat(sslContextProvider.getClass().getSimpleName()).isEqualTo( + "CertProviderClientSslContextProvider"); } @Test @@ -117,7 +119,8 @@ public void bothPresent_expectCertProviderClientSslContextProvider() bootstrapInfo, certProviderClientSslContextProviderFactory); SslContextProvider sslContextProvider = clientSslContextProviderFactory.create(upstreamTlsContext); - assertThat(sslContextProvider).isInstanceOf(CertProviderClientSslContextProvider.class); + assertThat(sslContextProvider.getClass().getSimpleName()).isEqualTo( + "CertProviderClientSslContextProvider"); verifyWatcher(sslContextProvider, watcherCaptor[0]); } @@ -142,7 +145,8 @@ public void createCertProviderClientSslContextProvider_onlyRootCert() bootstrapInfo, certProviderClientSslContextProviderFactory); SslContextProvider sslContextProvider = clientSslContextProviderFactory.create(upstreamTlsContext); - assertThat(sslContextProvider).isInstanceOf(CertProviderClientSslContextProvider.class); + assertThat(sslContextProvider.getClass().getSimpleName()).isEqualTo( + "CertProviderClientSslContextProvider"); verifyWatcher(sslContextProvider, watcherCaptor[0]); } @@ -175,7 +179,8 @@ public void createCertProviderClientSslContextProvider_withStaticContext() certProviderClientSslContextProviderFactory); SslContextProvider sslContextProvider = clientSslContextProviderFactory.create(upstreamTlsContext); - assertThat(sslContextProvider).isInstanceOf(CertProviderClientSslContextProvider.class); + assertThat(sslContextProvider.getClass().getSimpleName()).isEqualTo( + "CertProviderClientSslContextProvider"); verifyWatcher(sslContextProvider, watcherCaptor[0]); } @@ -204,7 +209,8 @@ public void createCertProviderClientSslContextProvider_2providers() bootstrapInfo, certProviderClientSslContextProviderFactory); SslContextProvider sslContextProvider = clientSslContextProviderFactory.create(upstreamTlsContext); - assertThat(sslContextProvider).isInstanceOf(CertProviderClientSslContextProvider.class); + assertThat(sslContextProvider.getClass().getSimpleName()).isEqualTo( + "CertProviderClientSslContextProvider"); verifyWatcher(sslContextProvider, watcherCaptor[0]); verifyWatcher(sslContextProvider, watcherCaptor[1]); } @@ -240,7 +246,8 @@ public void createNewCertProviderClientSslContextProvider_withSans() { bootstrapInfo, certProviderClientSslContextProviderFactory); SslContextProvider sslContextProvider = clientSslContextProviderFactory.create(upstreamTlsContext); - assertThat(sslContextProvider).isInstanceOf(CertProviderClientSslContextProvider.class); + assertThat(sslContextProvider.getClass().getSimpleName()).isEqualTo( + "CertProviderClientSslContextProvider"); verifyWatcher(sslContextProvider, watcherCaptor[0]); verifyWatcher(sslContextProvider, watcherCaptor[1]); } @@ -273,7 +280,8 @@ public void createNewCertProviderClientSslContextProvider_onlyRootCert() { bootstrapInfo, certProviderClientSslContextProviderFactory); SslContextProvider sslContextProvider = clientSslContextProviderFactory.create(upstreamTlsContext); - assertThat(sslContextProvider).isInstanceOf(CertProviderClientSslContextProvider.class); + assertThat(sslContextProvider.getClass().getSimpleName()).isEqualTo( + "CertProviderClientSslContextProvider"); verifyWatcher(sslContextProvider, watcherCaptor[0]); } diff --git a/xds/src/test/java/io/grpc/xds/internal/security/SecurityProtocolNegotiatorsTest.java b/xds/src/test/java/io/grpc/xds/internal/security/SecurityProtocolNegotiatorsTest.java index 7355b456933..0531189f2ac 100644 --- a/xds/src/test/java/io/grpc/xds/internal/security/SecurityProtocolNegotiatorsTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/security/SecurityProtocolNegotiatorsTest.java @@ -51,9 +51,9 @@ import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.InternalXdsAttributes; import io.grpc.xds.TlsContextManager; -import io.grpc.xds.internal.certprovider.CommonCertProviderTestUtils; import io.grpc.xds.internal.security.SecurityProtocolNegotiators.ClientSdsHandler; import io.grpc.xds.internal.security.SecurityProtocolNegotiators.ClientSdsProtocolNegotiator; +import io.grpc.xds.internal.security.certprovider.CommonCertProviderTestUtils; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPipeline; diff --git a/xds/src/test/java/io/grpc/xds/internal/security/ServerSslContextProviderFactoryTest.java b/xds/src/test/java/io/grpc/xds/internal/security/ServerSslContextProviderFactoryTest.java index 902138c06d2..07648194f72 100644 --- a/xds/src/test/java/io/grpc/xds/internal/security/ServerSslContextProviderFactoryTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/security/ServerSslContextProviderFactoryTest.java @@ -29,10 +29,10 @@ import io.grpc.xds.EnvoyServerProtoData; import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; import io.grpc.xds.XdsInitializationException; -import io.grpc.xds.internal.certprovider.CertProviderServerSslContextProvider; -import io.grpc.xds.internal.certprovider.CertificateProvider; -import io.grpc.xds.internal.certprovider.CertificateProviderRegistry; -import io.grpc.xds.internal.certprovider.CertificateProviderStore; +import io.grpc.xds.internal.security.certprovider.CertProviderServerSslContextProviderFactory; +import io.grpc.xds.internal.security.certprovider.CertificateProvider; +import io.grpc.xds.internal.security.certprovider.CertificateProviderRegistry; +import io.grpc.xds.internal.security.certprovider.CertificateProviderStore; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -44,7 +44,7 @@ public class ServerSslContextProviderFactoryTest { CertificateProviderRegistry certificateProviderRegistry; CertificateProviderStore certificateProviderStore; - CertProviderServerSslContextProvider.Factory certProviderServerSslContextProviderFactory; + CertProviderServerSslContextProviderFactory certProviderServerSslContextProviderFactory; ServerSslContextProviderFactory serverSslContextProviderFactory; @Before @@ -52,7 +52,7 @@ public void setUp() { certificateProviderRegistry = new CertificateProviderRegistry(); certificateProviderStore = new CertificateProviderStore(certificateProviderRegistry); certProviderServerSslContextProviderFactory = - new CertProviderServerSslContextProvider.Factory(certificateProviderStore); + new CertProviderServerSslContextProviderFactory(certificateProviderStore); } @Test @@ -76,12 +76,14 @@ public void createCertProviderServerSslContextProvider() throws XdsInitializatio bootstrapInfo, certProviderServerSslContextProviderFactory); SslContextProvider sslContextProvider = serverSslContextProviderFactory.create(downstreamTlsContext); - assertThat(sslContextProvider).isInstanceOf(CertProviderServerSslContextProvider.class); + assertThat(sslContextProvider.getClass().getSimpleName()).isEqualTo( + "CertProviderServerSslContextProvider"); verifyWatcher(sslContextProvider, watcherCaptor[0]); // verify that bootstrapInfo is cached... sslContextProvider = serverSslContextProviderFactory.create(downstreamTlsContext); - assertThat(sslContextProvider).isInstanceOf(CertProviderServerSslContextProvider.class); + assertThat(sslContextProvider.getClass().getSimpleName()).isEqualTo( + "CertProviderServerSslContextProvider"); } @Test @@ -113,7 +115,8 @@ public void bothPresent_expectCertProviderServerSslContextProvider() bootstrapInfo, certProviderServerSslContextProviderFactory); SslContextProvider sslContextProvider = serverSslContextProviderFactory.create(downstreamTlsContext); - assertThat(sslContextProvider).isInstanceOf(CertProviderServerSslContextProvider.class); + assertThat(sslContextProvider.getClass().getSimpleName()).isEqualTo( + "CertProviderServerSslContextProvider"); verifyWatcher(sslContextProvider, watcherCaptor[0]); } @@ -139,7 +142,8 @@ public void createCertProviderServerSslContextProvider_onlyCertInstance() bootstrapInfo, certProviderServerSslContextProviderFactory); SslContextProvider sslContextProvider = serverSslContextProviderFactory.create(downstreamTlsContext); - assertThat(sslContextProvider).isInstanceOf(CertProviderServerSslContextProvider.class); + assertThat(sslContextProvider.getClass().getSimpleName()).isEqualTo( + "CertProviderServerSslContextProvider"); verifyWatcher(sslContextProvider, watcherCaptor[0]); } @@ -173,7 +177,8 @@ public void createCertProviderServerSslContextProvider_withStaticContext() bootstrapInfo, certProviderServerSslContextProviderFactory); SslContextProvider sslContextProvider = serverSslContextProviderFactory.create(downstreamTlsContext); - assertThat(sslContextProvider).isInstanceOf(CertProviderServerSslContextProvider.class); + assertThat(sslContextProvider.getClass().getSimpleName()).isEqualTo( + "CertProviderServerSslContextProvider"); verifyWatcher(sslContextProvider, watcherCaptor[0]); } @@ -203,7 +208,8 @@ public void createCertProviderServerSslContextProvider_2providers() bootstrapInfo, certProviderServerSslContextProviderFactory); SslContextProvider sslContextProvider = serverSslContextProviderFactory.create(downstreamTlsContext); - assertThat(sslContextProvider).isInstanceOf(CertProviderServerSslContextProvider.class); + assertThat(sslContextProvider.getClass().getSimpleName()).isEqualTo( + "CertProviderServerSslContextProvider"); verifyWatcher(sslContextProvider, watcherCaptor[0]); verifyWatcher(sslContextProvider, watcherCaptor[1]); } @@ -241,7 +247,8 @@ public void createNewCertProviderServerSslContextProvider_withSans() bootstrapInfo, certProviderServerSslContextProviderFactory); SslContextProvider sslContextProvider = serverSslContextProviderFactory.create(downstreamTlsContext); - assertThat(sslContextProvider).isInstanceOf(CertProviderServerSslContextProvider.class); + assertThat(sslContextProvider.getClass().getSimpleName()).isEqualTo( + "CertProviderServerSslContextProvider"); verifyWatcher(sslContextProvider, watcherCaptor[0]); verifyWatcher(sslContextProvider, watcherCaptor[1]); } diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/security/certprovider/CertProviderClientSslContextProviderTest.java similarity index 95% rename from xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProviderTest.java rename to xds/src/test/java/io/grpc/xds/internal/security/certprovider/CertProviderClientSslContextProviderTest.java index 8495d9bcf0b..857d4b017c1 100644 --- a/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/security/certprovider/CertProviderClientSslContextProviderTest.java @@ -14,11 +14,10 @@ * limitations under the License. */ -package io.grpc.xds.internal.certprovider; +package io.grpc.xds.internal.security.certprovider; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.Truth.assertThat; -import static io.grpc.xds.internal.certprovider.CommonCertProviderTestUtils.getCertFromResourceName; import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CA_PEM_FILE; import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CLIENT_KEY_FILE; import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; @@ -26,6 +25,7 @@ import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_KEY_FILE; import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.doChecksOnSslContext; +import static io.grpc.xds.internal.security.certprovider.CommonCertProviderTestUtils.getCertFromResourceName; import static org.junit.Assert.fail; import com.google.common.annotations.VisibleForTesting; @@ -56,14 +56,14 @@ public class CertProviderClientSslContextProviderTest { CertificateProviderRegistry certificateProviderRegistry; CertificateProviderStore certificateProviderStore; - private CertProviderClientSslContextProvider.Factory certProviderClientSslContextProviderFactory; + private CertProviderClientSslContextProviderFactory certProviderClientSslContextProviderFactory; @Before public void setUp() throws Exception { certificateProviderRegistry = new CertificateProviderRegistry(); certificateProviderStore = new CertificateProviderStore(certificateProviderRegistry); certProviderClientSslContextProviderFactory = - new CertProviderClientSslContextProvider.Factory(certificateProviderStore); + new CertProviderClientSslContextProviderFactory(certificateProviderStore); } /** Helper method to build CertProviderClientSslContextProvider. */ @@ -81,10 +81,11 @@ private CertProviderClientSslContextProvider getSslContextProvider( "root-default", alpnProtocols, staticCertValidationContext); - return certProviderClientSslContextProviderFactory.getProvider( - upstreamTlsContext, - bootstrapInfo.node().toEnvoyProtoNode(), - bootstrapInfo.certProviders()); + return (CertProviderClientSslContextProvider) + certProviderClientSslContextProviderFactory.getProvider( + upstreamTlsContext, + bootstrapInfo.node().toEnvoyProtoNode(), + bootstrapInfo.certProviders()); } /** Helper method to build CertProviderClientSslContextProvider. */ @@ -102,7 +103,8 @@ private CertProviderClientSslContextProvider getNewSslContextProvider( "root-default", alpnProtocols, staticCertValidationContext); - return certProviderClientSslContextProviderFactory.getProvider( + return (CertProviderClientSslContextProvider) + certProviderClientSslContextProviderFactory.getProvider( upstreamTlsContext, bootstrapInfo.node().toEnvoyProtoNode(), bootstrapInfo.certProviders()); diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/security/certprovider/CertProviderServerSslContextProviderTest.java similarity index 95% rename from xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProviderTest.java rename to xds/src/test/java/io/grpc/xds/internal/security/certprovider/CertProviderServerSslContextProviderTest.java index 8ce52e32333..14d772c779b 100644 --- a/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/security/certprovider/CertProviderServerSslContextProviderTest.java @@ -14,10 +14,9 @@ * limitations under the License. */ -package io.grpc.xds.internal.certprovider; +package io.grpc.xds.internal.security.certprovider; import static com.google.common.truth.Truth.assertThat; -import static io.grpc.xds.internal.certprovider.CommonCertProviderTestUtils.getCertFromResourceName; import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CA_PEM_FILE; import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_0_KEY_FILE; @@ -25,6 +24,7 @@ import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_KEY_FILE; import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.doChecksOnSslContext; +import static io.grpc.xds.internal.security.certprovider.CommonCertProviderTestUtils.getCertFromResourceName; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; @@ -35,9 +35,9 @@ import io.grpc.xds.Bootstrapper; import io.grpc.xds.CommonBootstrapperTestUtils; import io.grpc.xds.EnvoyServerProtoData; -import io.grpc.xds.internal.certprovider.CertProviderClientSslContextProviderTest.QueuedExecutor; import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; import io.grpc.xds.internal.security.CommonTlsContextTestsUtil.TestCallback; +import io.grpc.xds.internal.security.certprovider.CertProviderClientSslContextProviderTest.QueuedExecutor; import java.util.Arrays; import org.junit.Before; import org.junit.Test; @@ -50,14 +50,14 @@ public class CertProviderServerSslContextProviderTest { CertificateProviderRegistry certificateProviderRegistry; CertificateProviderStore certificateProviderStore; - private CertProviderServerSslContextProvider.Factory certProviderServerSslContextProviderFactory; + private CertProviderServerSslContextProviderFactory certProviderServerSslContextProviderFactory; @Before public void setUp() throws Exception { certificateProviderRegistry = new CertificateProviderRegistry(); certificateProviderStore = new CertificateProviderStore(certificateProviderRegistry); certProviderServerSslContextProviderFactory = - new CertProviderServerSslContextProvider.Factory(certificateProviderStore); + new CertProviderServerSslContextProviderFactory(certificateProviderStore); } /** Helper method to build CertProviderServerSslContextProvider. */ @@ -77,10 +77,11 @@ private CertProviderServerSslContextProvider getSslContextProvider( alpnProtocols, staticCertValidationContext, requireClientCert); - return certProviderServerSslContextProviderFactory.getProvider( - downstreamTlsContext, - bootstrapInfo.node().toEnvoyProtoNode(), - bootstrapInfo.certProviders()); + return (CertProviderServerSslContextProvider) + certProviderServerSslContextProviderFactory.getProvider( + downstreamTlsContext, + bootstrapInfo.node().toEnvoyProtoNode(), + bootstrapInfo.certProviders()); } /** Helper method to build CertProviderServerSslContextProvider. */ @@ -100,7 +101,8 @@ private CertProviderServerSslContextProvider getNewSslContextProvider( alpnProtocols, staticCertValidationContext, requireClientCert); - return certProviderServerSslContextProviderFactory.getProvider( + return (CertProviderServerSslContextProvider) + certProviderServerSslContextProviderFactory.getProvider( downstreamTlsContext, bootstrapInfo.node().toEnvoyProtoNode(), bootstrapInfo.certProviders()); diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/CertificateProviderStoreTest.java b/xds/src/test/java/io/grpc/xds/internal/security/certprovider/CertificateProviderStoreTest.java similarity index 99% rename from xds/src/test/java/io/grpc/xds/internal/certprovider/CertificateProviderStoreTest.java rename to xds/src/test/java/io/grpc/xds/internal/security/certprovider/CertificateProviderStoreTest.java index 33ec6b291ed..8f77de7b5e2 100644 --- a/xds/src/test/java/io/grpc/xds/internal/certprovider/CertificateProviderStoreTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/security/certprovider/CertificateProviderStoreTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.certprovider; +package io.grpc.xds.internal.security.certprovider; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/CommonCertProviderTestUtils.java b/xds/src/test/java/io/grpc/xds/internal/security/certprovider/CommonCertProviderTestUtils.java similarity index 94% rename from xds/src/test/java/io/grpc/xds/internal/certprovider/CommonCertProviderTestUtils.java rename to xds/src/test/java/io/grpc/xds/internal/security/certprovider/CommonCertProviderTestUtils.java index 86264f28858..c62aa2d3a81 100644 --- a/xds/src/test/java/io/grpc/xds/internal/certprovider/CommonCertProviderTestUtils.java +++ b/xds/src/test/java/io/grpc/xds/internal/security/certprovider/CommonCertProviderTestUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.certprovider; +package io.grpc.xds.internal.security.certprovider; import static java.nio.charset.StandardCharsets.UTF_8; @@ -22,7 +22,7 @@ import io.grpc.internal.FakeClock; import io.grpc.internal.TimeProvider; import io.grpc.internal.testing.TestUtils; -import io.grpc.xds.internal.certprovider.FileWatcherCertificateProviderProvider.ScheduledExecutorServiceFactory; +import io.grpc.xds.internal.security.certprovider.FileWatcherCertificateProviderProvider.ScheduledExecutorServiceFactory; import io.grpc.xds.internal.security.trust.CertificateUtils; import java.io.ByteArrayInputStream; import java.io.IOException; diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/FileWatcherCertificateProviderProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProviderProviderTest.java similarity index 99% rename from xds/src/test/java/io/grpc/xds/internal/certprovider/FileWatcherCertificateProviderProviderTest.java rename to xds/src/test/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProviderProviderTest.java index d113b520057..9f7b13f86e9 100644 --- a/xds/src/test/java/io/grpc/xds/internal/certprovider/FileWatcherCertificateProviderProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProviderProviderTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.certprovider; +package io.grpc.xds.internal.security.certprovider; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/FileWatcherCertificateProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProviderTest.java similarity index 99% rename from xds/src/test/java/io/grpc/xds/internal/certprovider/FileWatcherCertificateProviderTest.java rename to xds/src/test/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProviderTest.java index 7cfae617820..dd4cf37b880 100644 --- a/xds/src/test/java/io/grpc/xds/internal/certprovider/FileWatcherCertificateProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProviderTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.certprovider; +package io.grpc.xds.internal.security.certprovider; import static com.google.common.truth.Truth.assertThat; import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CA_PEM_FILE; @@ -34,8 +34,8 @@ import io.grpc.Status; import io.grpc.internal.TimeProvider; -import io.grpc.xds.internal.certprovider.CertificateProvider.DistributorWatcher; import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; +import io.grpc.xds.internal.security.certprovider.CertificateProvider.DistributorWatcher; import java.io.File; import java.io.IOException; import java.nio.file.Files; diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/TestCertificateProvider.java b/xds/src/test/java/io/grpc/xds/internal/security/certprovider/TestCertificateProvider.java similarity index 98% rename from xds/src/test/java/io/grpc/xds/internal/certprovider/TestCertificateProvider.java rename to xds/src/test/java/io/grpc/xds/internal/security/certprovider/TestCertificateProvider.java index 9253d071fba..aba7a910813 100644 --- a/xds/src/test/java/io/grpc/xds/internal/certprovider/TestCertificateProvider.java +++ b/xds/src/test/java/io/grpc/xds/internal/security/certprovider/TestCertificateProvider.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.certprovider; +package io.grpc.xds.internal.security.certprovider; public class TestCertificateProvider extends CertificateProvider { Object config; From e998955d1d3f00e394e4e0371a69927236a43283 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Fri, 23 Sep 2022 16:48:27 -0700 Subject: [PATCH 67/72] xds: Avoid NPE from update after removing subscriptions This fixes a regression in commit e1ad984. I'd create a test, but the NPE gets thrown away in the context of the current test setup so can't be created as quickly as we'd like to fix this. I have manually tested in a custom reproduction to confirm it resolves the NPE. Seen at b/248326695 ``` java.lang.AssertionError: java.lang.NullPointerException at io.grpc.xds.ClientXdsClient$1.uncaughtException(ClientXdsClient.java:89) at io.grpc.SynchronizationContext.drain(SynchronizationContext.java:97) at io.grpc.SynchronizationContext.execute(SynchronizationContext.java:127) at io.grpc.xds.ClientXdsClient.cancelXdsResourceWatch(ClientXdsClient.java:327) at io.grpc.xds.ClusterResolverLoadBalancer$ClusterResolverLbState$EdsClusterState.shutdown(ClusterResolverLoadBalancer.java:378) at io.grpc.xds.ClusterResolverLoadBalancer$ClusterResolverLbState.shutdown(ClusterResolverLoadBalancer.java:206) at io.grpc.util.GracefulSwitchLoadBalancer.shutdown(GracefulSwitchLoadBalancer.java:195) at io.grpc.xds.ClusterResolverLoadBalancer.shutdown(ClusterResolverLoadBalancer.java:141) at io.grpc.xds.CdsLoadBalancer2$CdsLbState.shutdown(CdsLoadBalancer2.java:136) at io.grpc.xds.CdsLoadBalancer2.shutdown(CdsLoadBalancer2.java:110) at io.grpc.util.GracefulSwitchLoadBalancer.shutdown(GracefulSwitchLoadBalancer.java:195) at io.grpc.xds.ClusterManagerLoadBalancer$ChildLbState.shutdown(ClusterManagerLoadBalancer.java:256) at io.grpc.xds.ClusterManagerLoadBalancer.shutdown(ClusterManagerLoadBalancer.java:138) at io.grpc.internal.AutoConfiguredLoadBalancerFactory$AutoConfiguredLoadBalancer.shutdown(AutoConfiguredLoadBalancerFactory.java:164) at io.grpc.internal.ManagedChannelImpl.shutdownNameResolverAndLoadBalancer(ManagedChannelImpl.java:381) at io.grpc.internal.ManagedChannelImpl.access$8200(ManagedChannelImpl.java:118) at io.grpc.internal.ManagedChannelImpl$DelayedTransportListener.transportTerminated(ManagedChannelImpl.java:2174) at io.grpc.internal.DelayedClientTransport$3.run(DelayedClientTransport.java:122) at io.grpc.SynchronizationContext.drain(SynchronizationContext.java:95) at io.grpc.SynchronizationContext.execute(SynchronizationContext.java:127) at io.grpc.internal.ManagedChannelImpl$RealChannel.shutdown(ManagedChannelImpl.java:1057) at io.grpc.internal.ManagedChannelImpl.shutdown(ManagedChannelImpl.java:817) at io.grpc.internal.ManagedChannelImpl.shutdownNow(ManagedChannelImpl.java:837) at io.grpc.internal.ManagedChannelImpl.shutdownNow(ManagedChannelImpl.java:117) at io.grpc.internal.ForwardingManagedChannel.shutdownNow(ForwardingManagedChannel.java:52) at io.grpc.internal.ManagedChannelOrphanWrapper.shutdownNow(ManagedChannelOrphanWrapper.java:65) at io.grpc.testing.integration.GrpclbFallbackTestClient.tearDown(GrpclbFallbackTestClient.java:178) at io.grpc.testing.integration.GrpclbFallbackTestClient.main(GrpclbFallbackTestClient.java:67) Caused by: java.lang.NullPointerException at io.grpc.xds.ClientXdsClient.handleResourceResponse(ClientXdsClient.java:179) at io.grpc.xds.AbstractXdsClient$AbstractAdsStream.handleRpcResponse(AbstractXdsClient.java:358) at io.grpc.xds.AbstractXdsClient$AdsStreamV3$1$1.run(AbstractXdsClient.java:511) at io.grpc.SynchronizationContext.drain(SynchronizationContext.java:95) ... 26 more ``` --- xds/src/main/java/io/grpc/xds/ClientXdsClient.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java index 80e70d6d1c6..f4dccc8a10b 100644 --- a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java +++ b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java @@ -175,8 +175,11 @@ public void handleResourceResponse( logger.log(XdsLogLevel.WARNING, "Ignore an unknown type of DiscoveryResponse"); return; } - Set toParseResourceNames = (resourceType == LDS || resourceType == RDS ) ? null : - resourceSubscribers.get(xdsResourceType).keySet(); + Set toParseResourceNames = null; + if (!(resourceType == LDS || resourceType == RDS) + && resourceSubscribers.containsKey(xdsResourceType)) { + toParseResourceNames = resourceSubscribers.get(xdsResourceType).keySet(); + } XdsResourceType.Args args = new XdsResourceType.Args(serverInfo, versionInfo, nonce, bootstrapInfo, filterRegistry, loadBalancerRegistry, tlsContextManager, toParseResourceNames); From d211b54bf958398e19e092f9fe392fc2397e1f70 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Fri, 30 Sep 2022 09:23:26 -0700 Subject: [PATCH 68/72] Bump Protobuf to 3.21.7 --- COMPILING.md | 4 ++-- examples/android/clientcache/app/build.gradle | 2 +- examples/android/helloworld/app/build.gradle | 2 +- examples/android/routeguide/app/build.gradle | 2 +- examples/android/strictmode/app/build.gradle | 2 +- examples/build.gradle | 2 +- examples/example-alts/build.gradle | 2 +- examples/example-gauth/build.gradle | 2 +- examples/example-gauth/pom.xml | 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-orca/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 +- repositories.bzl | 12 ++++++------ 20 files changed, 28 insertions(+), 28 deletions(-) diff --git a/COMPILING.md b/COMPILING.md index 1423ec88d78..026aec138ed 100644 --- a/COMPILING.md +++ b/COMPILING.md @@ -44,11 +44,11 @@ This section is only necessary if you are making changes to the code generation. Most users only need to use `skipCodegen=true` as discussed above. ### Build Protobuf -The codegen plugin is C++ code and requires protobuf 3.21.1 or later. +The codegen plugin is C++ code and requires protobuf 3.21.7 or later. For Linux, Mac and MinGW: ``` -$ PROTOBUF_VERSION=3.21.1 +$ PROTOBUF_VERSION=3.21.7 $ curl -LO https://github.com/protocolbuffers/protobuf/releases/download/v$PROTOBUF_VERSION/protobuf-all-$PROTOBUF_VERSION.tar.gz $ tar xzf protobuf-all-$PROTOBUF_VERSION.tar.gz $ cd protobuf-$PROTOBUF_VERSION diff --git a/examples/android/clientcache/app/build.gradle b/examples/android/clientcache/app/build.gradle index 78cbdd305e0..0db99fe8d04 100644 --- a/examples/android/clientcache/app/build.gradle +++ b/examples/android/clientcache/app/build.gradle @@ -32,7 +32,7 @@ android { } protobuf { - protoc { artifact = 'com.google.protobuf:protoc:3.21.1' } + protoc { artifact = 'com.google.protobuf:protoc:3.21.7' } plugins { grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION } diff --git a/examples/android/helloworld/app/build.gradle b/examples/android/helloworld/app/build.gradle index f30a1110f12..0264103ffa9 100644 --- a/examples/android/helloworld/app/build.gradle +++ b/examples/android/helloworld/app/build.gradle @@ -30,7 +30,7 @@ android { } protobuf { - protoc { artifact = 'com.google.protobuf:protoc:3.21.1' } + protoc { artifact = 'com.google.protobuf:protoc:3.21.7' } plugins { grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION } diff --git a/examples/android/routeguide/app/build.gradle b/examples/android/routeguide/app/build.gradle index 5c861238223..25e265f15fe 100644 --- a/examples/android/routeguide/app/build.gradle +++ b/examples/android/routeguide/app/build.gradle @@ -30,7 +30,7 @@ android { } protobuf { - protoc { artifact = 'com.google.protobuf:protoc:3.21.1' } + protoc { artifact = 'com.google.protobuf:protoc:3.21.7' } plugins { grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION } diff --git a/examples/android/strictmode/app/build.gradle b/examples/android/strictmode/app/build.gradle index 788c015823a..bc03b88a1df 100644 --- a/examples/android/strictmode/app/build.gradle +++ b/examples/android/strictmode/app/build.gradle @@ -31,7 +31,7 @@ android { } protobuf { - protoc { artifact = 'com.google.protobuf:protoc:3.21.1' } + protoc { artifact = 'com.google.protobuf:protoc:3.21.7' } plugins { grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION } diff --git a/examples/build.gradle b/examples/build.gradle index 9f779ff819a..dbf07112e68 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -23,7 +23,7 @@ targetCompatibility = 1.8 // 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.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protobufVersion = '3.21.1' +def protobufVersion = '3.21.7' def protocVersion = protobufVersion dependencies { diff --git a/examples/example-alts/build.gradle b/examples/example-alts/build.gradle index 4d44a130d4b..935c7009509 100644 --- a/examples/example-alts/build.gradle +++ b/examples/example-alts/build.gradle @@ -24,7 +24,7 @@ targetCompatibility = 1.8 // 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.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protocVersion = '3.21.1' +def protocVersion = '3.21.7' dependencies { // grpc-alts transitively depends on grpc-netty-shaded, grpc-protobuf, and grpc-stub diff --git a/examples/example-gauth/build.gradle b/examples/example-gauth/build.gradle index 64b870daf28..e372a426d8d 100644 --- a/examples/example-gauth/build.gradle +++ b/examples/example-gauth/build.gradle @@ -24,7 +24,7 @@ targetCompatibility = 1.8 // 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.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protobufVersion = '3.21.1' +def protobufVersion = '3.21.7' def protocVersion = protobufVersion diff --git a/examples/example-gauth/pom.xml b/examples/example-gauth/pom.xml index 8c9c8673d3f..2280eb91de6 100644 --- a/examples/example-gauth/pom.xml +++ b/examples/example-gauth/pom.xml @@ -13,7 +13,7 @@ UTF-8 1.50.0-SNAPSHOT - 3.21.1 + 3.21.7 1.7 1.7 diff --git a/examples/example-hostname/build.gradle b/examples/example-hostname/build.gradle index e9db5ef7c61..d7d4eadd091 100644 --- a/examples/example-hostname/build.gradle +++ b/examples/example-hostname/build.gradle @@ -22,7 +22,7 @@ targetCompatibility = 1.8 // 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.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protobufVersion = '3.21.1' +def protobufVersion = '3.21.7' dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" diff --git a/examples/example-hostname/pom.xml b/examples/example-hostname/pom.xml index 9ca5bfbacda..6aad5c4be8f 100644 --- a/examples/example-hostname/pom.xml +++ b/examples/example-hostname/pom.xml @@ -13,7 +13,7 @@ UTF-8 1.50.0-SNAPSHOT - 3.21.1 + 3.21.7 1.7 1.7 diff --git a/examples/example-jwt-auth/build.gradle b/examples/example-jwt-auth/build.gradle index 1d892bfb0d7..f594612b4f9 100644 --- a/examples/example-jwt-auth/build.gradle +++ b/examples/example-jwt-auth/build.gradle @@ -23,7 +23,7 @@ targetCompatibility = 1.8 // 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.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protobufVersion = '3.21.1' +def protobufVersion = '3.21.7' def protocVersion = protobufVersion dependencies { diff --git a/examples/example-jwt-auth/pom.xml b/examples/example-jwt-auth/pom.xml index b57eb8a801b..b6699dcec86 100644 --- a/examples/example-jwt-auth/pom.xml +++ b/examples/example-jwt-auth/pom.xml @@ -14,8 +14,8 @@ UTF-8 1.50.0-SNAPSHOT - 3.21.1 - 3.21.1 + 3.21.7 + 3.21.7 1.7 1.7 diff --git a/examples/example-orca/build.gradle b/examples/example-orca/build.gradle index 2bbfc97e834..d7bc4833ed5 100644 --- a/examples/example-orca/build.gradle +++ b/examples/example-orca/build.gradle @@ -18,7 +18,7 @@ sourceCompatibility = 1.8 targetCompatibility = 1.8 def grpcVersion = '1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protocVersion = '3.21.1' +def protocVersion = '3.21.7' dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index 895d6c98a7c..9da1c80628f 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -24,7 +24,7 @@ targetCompatibility = 1.8 // 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.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protocVersion = '3.21.1' +def protocVersion = '3.21.7' dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" diff --git a/examples/example-tls/pom.xml b/examples/example-tls/pom.xml index 42b6d2d4594..0bd7446a86b 100644 --- a/examples/example-tls/pom.xml +++ b/examples/example-tls/pom.xml @@ -13,7 +13,7 @@ UTF-8 1.50.0-SNAPSHOT - 3.21.1 + 3.21.7 2.0.54.Final 1.7 diff --git a/examples/example-xds/build.gradle b/examples/example-xds/build.gradle index 5f48215e876..c6c354afadf 100644 --- a/examples/example-xds/build.gradle +++ b/examples/example-xds/build.gradle @@ -24,7 +24,7 @@ targetCompatibility = 1.8 // updating the version in our release process. def grpcVersion = '1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION def nettyTcNativeVersion = '2.0.31.Final' -def protocVersion = '3.21.1' +def protocVersion = '3.21.7' dependencies { implementation "io.grpc:grpc-protobuf:${grpcVersion}" diff --git a/examples/pom.xml b/examples/pom.xml index 7aa05b7db95..9f64d21b816 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -13,8 +13,8 @@ UTF-8 1.50.0-SNAPSHOT - 3.21.1 - 3.21.1 + 3.21.7 + 3.21.7 1.7 1.7 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3351eba9cb7..37d443cd479 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ guava = "31.1-android" netty = '4.1.79.Final' nettytcnative = '2.0.54.Final' opencensus = "0.31.0" -protobuf = "3.21.1" +protobuf = "3.21.7" [libraries] android-annotations = "com.google.android:annotations:4.1.1.4" diff --git a/repositories.bzl b/repositories.bzl index cdf6d69e23e..cced1d29bee 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -150,18 +150,18 @@ def com_google_protobuf(): # This statement defines the @com_google_protobuf repo. http_archive( name = "com_google_protobuf", - sha256 = "2d9084d3dd13b86ca2e811d2331f780eb86f6d7cb02b405426e3c80dcbfabf25", - strip_prefix = "protobuf-3.21.1", - urls = ["https://github.com/protocolbuffers/protobuf/archive/v3.21.1.zip"], + sha256 = "c72840a5081484c4ac20789ea5bb5d5de6bc7c477ad76e7109fda2bc4e630fe6", + strip_prefix = "protobuf-3.21.7", + urls = ["https://github.com/protocolbuffers/protobuf/archive/v3.21.7.zip"], ) def com_google_protobuf_javalite(): # java_lite_proto_library rules implicitly depend on @com_google_protobuf_javalite http_archive( name = "com_google_protobuf_javalite", - sha256 = "2d9084d3dd13b86ca2e811d2331f780eb86f6d7cb02b405426e3c80dcbfabf25", - strip_prefix = "protobuf-3.21.1", - urls = ["https://github.com/protocolbuffers/protobuf/archive/v3.21.1.zip"], + sha256 = "c72840a5081484c4ac20789ea5bb5d5de6bc7c477ad76e7109fda2bc4e630fe6", + strip_prefix = "protobuf-3.21.7", + urls = ["https://github.com/protocolbuffers/protobuf/archive/v3.21.7.zip"], ) def io_grpc_grpc_proto(): From ce5f7898e946c31005de6bc80b9213daa28faeb3 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Tue, 4 Oct 2022 10:22:52 -0700 Subject: [PATCH 69/72] Use Protobuf 21.7 in our code generator This aligns the C++ version we're using for gRPC-generated code with the Java version. This should have no real impact to our users, as there were no features added to .proto files or the like that would be visible to users. --- buildscripts/make_dependencies.bat | 5 +---- buildscripts/make_dependencies.sh | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/buildscripts/make_dependencies.bat b/buildscripts/make_dependencies.bat index 30e8dd548a1..2bbfd394d46 100644 --- a/buildscripts/make_dependencies.bat +++ b/buildscripts/make_dependencies.bat @@ -1,6 +1,4 @@ -set PROTOBUF_VER=21.1 -@rem Workaround https://github.com/protocolbuffers/protobuf/issues/10172 -set PROTOBUF_VER_ISSUE_10172=3.%PROTOBUF_VER% +set PROTOBUF_VER=21.7 set CMAKE_NAME=cmake-3.3.2-win32-x86 if not exist "protobuf-%PROTOBUF_VER%\build\Release\" ( @@ -25,7 +23,6 @@ set PATH=%PATH%;%cd%\%CMAKE_NAME%\bin powershell -command "$ErrorActionPreference = 'stop'; & { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; iwr https://github.com/google/protobuf/archive/v%PROTOBUF_VER%.zip -OutFile protobuf.zip }" || exit /b 1 powershell -command "$ErrorActionPreference = 'stop'; & { Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory('protobuf.zip', '.') }" || exit /b 1 del protobuf.zip -rename protobuf-%PROTOBUF_VER_ISSUE_10172% protobuf-%PROTOBUF_VER% mkdir protobuf-%PROTOBUF_VER%\build pushd protobuf-%PROTOBUF_VER%\build diff --git a/buildscripts/make_dependencies.sh b/buildscripts/make_dependencies.sh index 8f0033e8a75..29eed3d5050 100755 --- a/buildscripts/make_dependencies.sh +++ b/buildscripts/make_dependencies.sh @@ -3,9 +3,7 @@ # Build protoc set -evux -o pipefail -PROTOBUF_VERSION=21.1 -# https://github.com/protocolbuffers/protobuf/issues/10172 -PROTOBUF_VERSION_ISSUE_10172=3.$PROTOBUF_VERSION +PROTOBUF_VERSION=21.7 # ARCH is x86_64 bit unless otherwise specified. ARCH="${ARCH:-x86_64}" @@ -30,7 +28,6 @@ if [ -f ${INSTALL_DIR}/bin/protoc ]; then else if [[ ! -d "$DOWNLOAD_DIR"/protobuf-"${PROTOBUF_VERSION}" ]]; then curl -Ls https://github.com/google/protobuf/releases/download/v${PROTOBUF_VERSION}/protobuf-all-${PROTOBUF_VERSION}.tar.gz | tar xz -C $DOWNLOAD_DIR - mv "$DOWNLOAD_DIR/protobuf-${PROTOBUF_VERSION_ISSUE_10172}" "$DOWNLOAD_DIR/protobuf-${PROTOBUF_VERSION}" fi pushd $DOWNLOAD_DIR/protobuf-${PROTOBUF_VERSION} # install here so we don't need sudo From 69e86b9db33c2f325521d9f3253ac64e21e9894d Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Tue, 4 Oct 2022 12:43:02 -0700 Subject: [PATCH 70/72] okhttp: Add client transport proxy socket timeout (#9586) Not having a timeout when reading the response from a proxy server can cause a hang if network connectivity is lost at the same time. --- .../io/grpc/okhttp/OkHttpClientTransport.java | 13 +++++++- .../okhttp/OkHttpClientTransportTest.java | 31 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java index 81af4f30d48..02b1c3254fa 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java @@ -221,6 +221,9 @@ protected void handleNotInUse() { @Nullable final HttpConnectProxiedSocketAddress proxiedAddr; + @VisibleForTesting + int proxySocketTimeout = 30000; + // The following fields should only be used for test. Runnable connectingCallback; SettableFuture connectedFuture; @@ -626,8 +629,8 @@ private void sendConnectionPrefaceAndSettings() { private Socket createHttpProxySocket(InetSocketAddress address, InetSocketAddress proxyAddress, String proxyUsername, String proxyPassword) throws StatusException { + Socket sock = null; try { - Socket sock; // The proxy address may not be resolved if (proxyAddress.getAddress() != null) { sock = socketFactory.createSocket(proxyAddress.getAddress(), proxyAddress.getPort()); @@ -636,6 +639,9 @@ private Socket createHttpProxySocket(InetSocketAddress address, InetSocketAddres socketFactory.createSocket(proxyAddress.getHostName(), proxyAddress.getPort()); } sock.setTcpNoDelay(true); + // A socket timeout is needed because lost network connectivity while reading from the proxy, + // can cause reading from the socket to hang. + sock.setSoTimeout(proxySocketTimeout); Source source = Okio.source(sock); BufferedSink sink = Okio.buffer(Okio.sink(sock)); @@ -682,8 +688,13 @@ private Socket createHttpProxySocket(InetSocketAddress address, InetSocketAddres statusLine.code, statusLine.message, body.readUtf8()); throw Status.UNAVAILABLE.withDescription(message).asException(); } + // As the socket will be used for RPCs from here on, we want the socket timeout back to zero. + sock.setSoTimeout(0); return sock; } catch (IOException e) { + if (sock != null) { + GrpcUtil.closeQuietly(sock); + } throw Status.UNAVAILABLE.withDescription("Failed trying to connect with proxy").withCause(e) .asException(); } diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java index e632a6c2946..fcc5e0d2381 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java @@ -1877,6 +1877,37 @@ public void proxy_immediateServerClose() throws Exception { verify(transportListener, timeout(TIME_OUT_MS)).transportTerminated(); } + @Test + public void proxy_serverHangs() throws Exception { + ServerSocket serverSocket = new ServerSocket(0); + InetSocketAddress targetAddress = InetSocketAddress.createUnresolved("theservice", 80); + clientTransport = new OkHttpClientTransport( + channelBuilder.buildTransportFactory(), + targetAddress, + "authority", + "userAgent", + EAG_ATTRS, + HttpConnectProxiedSocketAddress.newBuilder() + .setTargetAddress(targetAddress) + .setProxyAddress(new InetSocketAddress("localhost", serverSocket.getLocalPort())) + .build(), + tooManyPingsRunnable); + clientTransport.proxySocketTimeout = 10; + clientTransport.start(transportListener); + + Socket sock = serverSocket.accept(); + serverSocket.close(); + + BufferedReader reader = new BufferedReader(new InputStreamReader(sock.getInputStream(), UTF_8)); + assertEquals("CONNECT theservice:80 HTTP/1.1", reader.readLine()); + assertEquals("Host: theservice:80", reader.readLine()); + while (!"".equals(reader.readLine())) {} + + verify(transportListener, timeout(200)).transportShutdown(any(Status.class)); + verify(transportListener, timeout(TIME_OUT_MS)).transportTerminated(); + sock.close(); + } + @Test public void goAway_notUtf8() throws Exception { initTransport(); From 3aa88680d6106d272f151b21a1bf5af75cb97c23 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Mon, 10 Oct 2022 14:55:17 -0700 Subject: [PATCH 71/72] Update README etc to reference 1.50.0 --- README.md | 36 ++++++++++++------------ cronet/README.md | 2 +- documentation/android-channel-builder.md | 4 +-- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 9290082d96b..7775e726ba0 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,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.49.0/examples) and the -[Android example](https://github.com/grpc/grpc-java/tree/v1.49.0/examples/android) +The [examples](https://github.com/grpc/grpc-java/tree/v1.50.0/examples) and the +[Android example](https://github.com/grpc/grpc-java/tree/v1.50.0/examples/android) are standalone projects that showcase the usage of gRPC. Download @@ -43,18 +43,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`: io.grpc grpc-netty-shaded - 1.49.0 + 1.50.0 runtime io.grpc grpc-protobuf - 1.49.0 + 1.50.0 io.grpc grpc-stub - 1.49.0 + 1.50.0 org.apache.tomcat @@ -66,23 +66,23 @@ 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.49.0' -implementation 'io.grpc:grpc-protobuf:1.49.0' -implementation 'io.grpc:grpc-stub:1.49.0' +runtimeOnly 'io.grpc:grpc-netty-shaded:1.50.0' +implementation 'io.grpc:grpc-protobuf:1.50.0' +implementation 'io.grpc:grpc-stub:1.50.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.49.0' -implementation 'io.grpc:grpc-protobuf-lite:1.49.0' -implementation 'io.grpc:grpc-stub:1.49.0' +implementation 'io.grpc:grpc-okhttp:1.50.0' +implementation 'io.grpc:grpc-protobuf-lite:1.50.0' +implementation 'io.grpc:grpc-stub:1.50.0' compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+ ``` [the JARs]: -https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.49.0 +https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.50.0 Development snapshots are available in [Sonatypes's snapshot repository](https://oss.sonatype.org/content/repositories/snapshots/). @@ -112,9 +112,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.21.1:exe:${os.detected.classifier} + com.google.protobuf:protoc:3.21.7:exe:${os.detected.classifier} grpc-java - io.grpc:protoc-gen-grpc-java:1.49.0:exe:${os.detected.classifier} + io.grpc:protoc-gen-grpc-java:1.50.0:exe:${os.detected.classifier} @@ -140,11 +140,11 @@ plugins { protobuf { protoc { - artifact = "com.google.protobuf:protoc:3.21.1" + artifact = "com.google.protobuf:protoc:3.21.7" } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.49.0' + artifact = 'io.grpc:protoc-gen-grpc-java:1.50.0' } } generateProtoTasks { @@ -173,11 +173,11 @@ plugins { protobuf { protoc { - artifact = "com.google.protobuf:protoc:3.21.1" + artifact = "com.google.protobuf:protoc:3.21.7" } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.49.0' + artifact = 'io.grpc:protoc-gen-grpc-java:1.50.0' } } generateProtoTasks { diff --git a/cronet/README.md b/cronet/README.md index 3c767c4e972..3dda9d4c5c2 100644 --- a/cronet/README.md +++ b/cronet/README.md @@ -26,7 +26,7 @@ In your app module's `build.gradle` file, include a dependency on both `grpc-cro Google Play Services Client Library for Cronet ``` -implementation 'io.grpc:grpc-cronet:1.49.0' +implementation 'io.grpc:grpc-cronet:1.50.0' implementation 'com.google.android.gms:play-services-cronet:16.0.0' ``` diff --git a/documentation/android-channel-builder.md b/documentation/android-channel-builder.md index 70149fc9b2c..f3a92d6891b 100644 --- a/documentation/android-channel-builder.md +++ b/documentation/android-channel-builder.md @@ -36,8 +36,8 @@ In your `build.gradle` file, include a dependency on both `grpc-android` and `grpc-okhttp`: ``` -implementation 'io.grpc:grpc-android:1.49.0' -implementation 'io.grpc:grpc-okhttp:1.49.0' +implementation 'io.grpc:grpc-android:1.50.0' +implementation 'io.grpc:grpc-okhttp:1.50.0' ``` You also need permission to access the device's network state in your From df9670d83b0339279e139a54b32e52e9e8bc6ca5 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Mon, 10 Oct 2022 15:02:08 -0700 Subject: [PATCH 72/72] Bump version to 1.50.0 --- build.gradle | 2 +- .../src/test/golden/TestDeprecatedService.java.txt | 2 +- compiler/src/test/golden/TestService.java.txt | 2 +- .../src/testLite/golden/TestDeprecatedService.java.txt | 2 +- compiler/src/testLite/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-gauth/build.gradle | 2 +- examples/example-gauth/pom.xml | 4 ++-- 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-orca/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 ++-- 23 files changed, 41 insertions(+), 41 deletions(-) diff --git a/build.gradle b/build.gradle index a5243b0d573..056cb75994d 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ subprojects { apply plugin: "net.ltgt.errorprone" group = "io.grpc" - version = "1.50.0-SNAPSHOT" // CURRENT_GRPC_VERSION + version = "1.50.0" // 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 9fddc958d81..b98b10ce1f2 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.50.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.50.0)", 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 0bbe6f65ddc..50e151870fb 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.50.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.50.0)", comments = "Source: grpc/testing/compiler/test.proto") @io.grpc.stub.annotations.GrpcGenerated public final class TestServiceGrpc { diff --git a/compiler/src/testLite/golden/TestDeprecatedService.java.txt b/compiler/src/testLite/golden/TestDeprecatedService.java.txt index b13295c37b4..6795af8e330 100644 --- a/compiler/src/testLite/golden/TestDeprecatedService.java.txt +++ b/compiler/src/testLite/golden/TestDeprecatedService.java.txt @@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.50.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.50.0)", comments = "Source: grpc/testing/compiler/test.proto") @io.grpc.stub.annotations.GrpcGenerated @java.lang.Deprecated diff --git a/compiler/src/testLite/golden/TestService.java.txt b/compiler/src/testLite/golden/TestService.java.txt index d2f9c12e923..2e2b2c6cf57 100644 --- a/compiler/src/testLite/golden/TestService.java.txt +++ b/compiler/src/testLite/golden/TestService.java.txt @@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.50.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.50.0)", 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 6564862d8af..dcf552b339b 100644 --- a/core/src/main/java/io/grpc/internal/GrpcUtil.java +++ b/core/src/main/java/io/grpc/internal/GrpcUtil.java @@ -217,7 +217,7 @@ public byte[] parseAsciiString(byte[] serialized) { public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults(); - private static final String IMPLEMENTATION_VERSION = "1.50.0-SNAPSHOT"; // CURRENT_GRPC_VERSION + private static final String IMPLEMENTATION_VERSION = "1.50.0"; // 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 0db99fe8d04..f5d19fdebb2 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.21.7' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.50.0' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -54,12 +54,12 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.0.2' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.50.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.50.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.50.0' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' testImplementation 'junit:junit:4.12' testImplementation 'com.google.truth:truth:1.0.1' - testImplementation 'io.grpc:grpc-testing:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION + testImplementation 'io.grpc:grpc-testing:1.50.0' // CURRENT_GRPC_VERSION } diff --git a/examples/android/helloworld/app/build.gradle b/examples/android/helloworld/app/build.gradle index 0264103ffa9..daed6b56d55 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.21.7' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.50.0' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -52,8 +52,8 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.0.2' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.50.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.50.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.50.0' // 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 25e265f15fe..a7369519dbc 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.21.7' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.50.0' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -52,8 +52,8 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.0.2' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.50.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.50.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.50.0' // 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 bc03b88a1df..05c9cd45d20 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.21.7' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.50.0' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -53,8 +53,8 @@ dependencies { implementation 'com.android.support:appcompat-v7:28.0.0' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.50.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.50.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.50.0' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/build.gradle b/examples/build.gradle index dbf07112e68..769a3d22f0b 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -22,7 +22,7 @@ targetCompatibility = 1.8 // 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.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.50.0' // CURRENT_GRPC_VERSION def protobufVersion = '3.21.7' def protocVersion = protobufVersion diff --git a/examples/example-alts/build.gradle b/examples/example-alts/build.gradle index 935c7009509..1f371946e48 100644 --- a/examples/example-alts/build.gradle +++ b/examples/example-alts/build.gradle @@ -23,7 +23,7 @@ targetCompatibility = 1.8 // 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.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.50.0' // CURRENT_GRPC_VERSION def protocVersion = '3.21.7' dependencies { diff --git a/examples/example-gauth/build.gradle b/examples/example-gauth/build.gradle index e372a426d8d..b82f5fa22fa 100644 --- a/examples/example-gauth/build.gradle +++ b/examples/example-gauth/build.gradle @@ -23,7 +23,7 @@ targetCompatibility = 1.8 // 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.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.50.0' // CURRENT_GRPC_VERSION def protobufVersion = '3.21.7' def protocVersion = protobufVersion diff --git a/examples/example-gauth/pom.xml b/examples/example-gauth/pom.xml index 2280eb91de6..640c39c3e0c 100644 --- a/examples/example-gauth/pom.xml +++ b/examples/example-gauth/pom.xml @@ -6,13 +6,13 @@ jar - 1.50.0-SNAPSHOT + 1.50.0 example-gauth https://github.com/grpc/grpc-java UTF-8 - 1.50.0-SNAPSHOT + 1.50.0 3.21.7 1.7 diff --git a/examples/example-hostname/build.gradle b/examples/example-hostname/build.gradle index d7d4eadd091..cbd50aa39a3 100644 --- a/examples/example-hostname/build.gradle +++ b/examples/example-hostname/build.gradle @@ -21,7 +21,7 @@ targetCompatibility = 1.8 // 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.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.50.0' // CURRENT_GRPC_VERSION def protobufVersion = '3.21.7' dependencies { diff --git a/examples/example-hostname/pom.xml b/examples/example-hostname/pom.xml index 6aad5c4be8f..e0003180745 100644 --- a/examples/example-hostname/pom.xml +++ b/examples/example-hostname/pom.xml @@ -6,13 +6,13 @@ jar - 1.50.0-SNAPSHOT + 1.50.0 example-hostname https://github.com/grpc/grpc-java UTF-8 - 1.50.0-SNAPSHOT + 1.50.0 3.21.7 1.7 diff --git a/examples/example-jwt-auth/build.gradle b/examples/example-jwt-auth/build.gradle index f594612b4f9..15602ca5dd9 100644 --- a/examples/example-jwt-auth/build.gradle +++ b/examples/example-jwt-auth/build.gradle @@ -22,7 +22,7 @@ targetCompatibility = 1.8 // 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.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.50.0' // CURRENT_GRPC_VERSION def protobufVersion = '3.21.7' def protocVersion = protobufVersion diff --git a/examples/example-jwt-auth/pom.xml b/examples/example-jwt-auth/pom.xml index b6699dcec86..13d52e66514 100644 --- a/examples/example-jwt-auth/pom.xml +++ b/examples/example-jwt-auth/pom.xml @@ -7,13 +7,13 @@ jar - 1.50.0-SNAPSHOT + 1.50.0 example-jwt-auth https://github.com/grpc/grpc-java UTF-8 - 1.50.0-SNAPSHOT + 1.50.0 3.21.7 3.21.7 diff --git a/examples/example-orca/build.gradle b/examples/example-orca/build.gradle index d7bc4833ed5..8f62bb2ab8a 100644 --- a/examples/example-orca/build.gradle +++ b/examples/example-orca/build.gradle @@ -17,7 +17,7 @@ repositories { sourceCompatibility = 1.8 targetCompatibility = 1.8 -def grpcVersion = '1.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.50.0' // CURRENT_GRPC_VERSION def protocVersion = '3.21.7' dependencies { diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index 9da1c80628f..2fddbe4ee5a 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -23,7 +23,7 @@ targetCompatibility = 1.8 // 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.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.50.0' // CURRENT_GRPC_VERSION def protocVersion = '3.21.7' dependencies { diff --git a/examples/example-tls/pom.xml b/examples/example-tls/pom.xml index 0bd7446a86b..6861601cecc 100644 --- a/examples/example-tls/pom.xml +++ b/examples/example-tls/pom.xml @@ -6,13 +6,13 @@ jar - 1.50.0-SNAPSHOT + 1.50.0 example-tls https://github.com/grpc/grpc-java UTF-8 - 1.50.0-SNAPSHOT + 1.50.0 3.21.7 2.0.54.Final diff --git a/examples/example-xds/build.gradle b/examples/example-xds/build.gradle index c6c354afadf..34b10d4c75e 100644 --- a/examples/example-xds/build.gradle +++ b/examples/example-xds/build.gradle @@ -22,7 +22,7 @@ targetCompatibility = 1.8 // 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.50.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.50.0' // CURRENT_GRPC_VERSION def nettyTcNativeVersion = '2.0.31.Final' def protocVersion = '3.21.7' diff --git a/examples/pom.xml b/examples/pom.xml index 9f64d21b816..157f5b0a4b4 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -6,13 +6,13 @@ jar - 1.50.0-SNAPSHOT + 1.50.0 examples https://github.com/grpc/grpc-java UTF-8 - 1.50.0-SNAPSHOT + 1.50.0 3.21.7 3.21.7