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/README.md b/README.md index f906b351ec7..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.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.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.48.1 + 1.50.0 runtime io.grpc grpc-protobuf - 1.48.1 + 1.50.0 io.grpc grpc-stub - 1.48.1 + 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.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.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.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.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.48.1 +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.48.1: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.48.1' + 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.48.1' + artifact = 'io.grpc:protoc-gen-grpc-java:1.50.0' } } generateProtoTasks { 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/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/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/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/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/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/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/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/api/src/main/java/io/grpc/LoadBalancer.java b/api/src/main/java/io/grpc/LoadBalancer.java index 8d74216b36f..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; } /** @@ -1073,8 +1080,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 +1365,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/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/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/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/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/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/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java b/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java index 4b95a6c7f4d..2a414e792d9 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 + private static final Class GOOGLE_CREDENTIALS_CLASS = loadGoogleCredentialsClass(); + private static final Class APP_ENGINE_CREDENTIALS_CLASS + = 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); } @@ -67,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); @@ -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; @@ -298,6 +314,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)); + } } /** @@ -348,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 (APP_ENGINE_CREDENTIALS_CLASS == null) { + requiresSpecificExecutor = Boolean.FALSE; + } else { + requiresSpecificExecutor = APP_ENGINE_CREDENTIALS_CLASS.isInstance(creds); + } + } + + return requiresSpecificExecutor; + } + } 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() { 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/binder/build.gradle b/binder/build.gradle index 24dd0d9015d..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" @@ -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 -> 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..6a692090549 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.PERMISSION_DENIED.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(); } diff --git a/build.gradle b/build.gradle index a63b9cd5a6a..056cb75994d 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" // CURRENT_GRPC_VERSION repositories { maven { // The google mirror is less flaky than mavenCentral() 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 d9e575bb652..6e0277ecb81 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" } ####################################### @@ -172,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") + 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 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" } ####################################### 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 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/census/src/main/java/io/grpc/census/CensusStatsModule.java b/census/src/main/java/io/grpc/census/CensusStatsModule.java index 366be55de68..d05368f3ee5 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); @@ -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()) { @@ -562,7 +560,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. @@ -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/CensusTracingModule.java b/census/src/main/java/io/grpc/census/CensusTracingModule.java index b507db10c64..f413da94388 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. @@ -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/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 9382ab84c12..9768797d579 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 @@ -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)); } @@ -1514,7 +1507,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/compiler/src/test/golden/TestDeprecatedService.java.txt b/compiler/src/test/golden/TestDeprecatedService.java.txt index 4f21c75f7d0..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.49.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 4fc8b282a0a..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.49.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 5ae3fdd746b..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.49.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 ed26c986f45..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.49.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/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())); 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/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/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/CallCredentialsApplyingTransportFactory.java b/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java index 04409558bea..1537d1c664f 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; @@ -126,6 +127,11 @@ public ClientStream newStream( return method; } + @Override + public CallOptions getCallOptions() { + return callOptions; + } + @Override public SecurityLevel getSecurityLevel() { return firstNonNull( @@ -144,7 +150,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") diff --git a/core/src/main/java/io/grpc/internal/DelayedClientCall.java b/core/src/main/java/io/grpc/internal/DelayedClientCall.java index df56fc0c5f7..42cedddd0e7 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 @@ -151,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/main/java/io/grpc/internal/GrpcUtil.java b/core/src/main/java/io/grpc/internal/GrpcUtil.java index 605fabc0cc2..dcf552b339b 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"); /** @@ -203,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.49.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. @@ -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/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/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/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java index 3e2e3ec17fc..9cc7d70d1e7 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; } @@ -1453,8 +1454,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 +1492,6 @@ public void run() { @Override public void refreshNameResolution() { syncContext.throwIfNotInThisSynchronizationContext(); - nsRefreshedByLb = true; final class LoadBalancerRefreshNameResolution implements Runnable { @Override public void run() { @@ -1504,11 +1502,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); @@ -1818,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 { @@ -1860,16 +1857,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(); } } } @@ -1979,16 +1977,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/netty/src/main/java/io/grpc/netty/MaxConnectionIdleManager.java b/core/src/main/java/io/grpc/internal/MaxConnectionIdleManager.java similarity index 77% rename from netty/src/main/java/io/grpc/netty/MaxConnectionIdleManager.java rename to core/src/main/java/io/grpc/internal/MaxConnectionIdleManager.java index 964ae44b178..4d4a36dda01 100644 --- a/netty/src/main/java/io/grpc/netty/MaxConnectionIdleManager.java +++ b/core/src/main/java/io/grpc/internal/MaxConnectionIdleManager.java @@ -14,11 +14,9 @@ * 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; @@ -27,7 +25,7 @@ /** * Monitors connection idle time; shutdowns the connection if the max connection idle is reached. */ -abstract class MaxConnectionIdleManager { +public final class MaxConnectionIdleManager { private static final Ticker systemTicker = new Ticker() { @Override public long nanoTime() { @@ -46,23 +44,23 @@ 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()); - } - - @VisibleForTesting - void start(final ChannelHandlerContext ctx, final ScheduledExecutorService scheduler) { + /** + * 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. + */ + public void start(final Runnable closeJob, final ScheduledExecutorService scheduler) { this.scheduler = scheduler; nextIdleMonitorTime = ticker.nanoTime() + maxConnectionIdleInNanos; @@ -78,7 +76,7 @@ public void run() { } // if isActive, exit. Will schedule a new shutdownFuture once onTransportIdle } else { - close(ctx); + closeJob.run(); shutdownFuture = null; } } @@ -88,20 +86,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 +109,7 @@ void onTransportIdle() { } /** Transport is being terminated. */ - void onTransportTermination() { + public void onTransportTermination() { if (shutdownFuture != null) { shutdownFuture.cancel(false); shutdownFuture = null; @@ -124,7 +117,7 @@ void onTransportTermination() { } @VisibleForTesting - interface Ticker { + public interface Ticker { long nanoTime(); } } 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/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/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/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/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/main/java/io/grpc/util/ForwardingLoadBalancer.java b/core/src/main/java/io/grpc/util/ForwardingLoadBalancer.java index 628eda3b71b..cefcbf344ea 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 { @@ -33,14 +29,6 @@ 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); - } - @Override public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { delegate().handleResolvedAddresses(resolvedAddresses); 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/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java new file mode 100644 index 00000000000..e7d93d9d52f --- /dev/null +++ b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java @@ -0,0 +1,1082 @@ +/* + * 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.Internal; +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 + */ +@Internal +public final 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 boolean acceptResolvedAddresses(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.toBuilder().setLoadBalancingPolicyConfig(config.childPolicy.getConfig()) + .build()); + return true; + } + + @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 = args.getAddresses(); + 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 SubchannelStateListener subchannelStateListener; + + OutlierDetectionSubchannel(Subchannel delegate) { + this.delegate = delegate; + } + + @Override + public void start(SubchannelStateListener listener) { + subchannelStateListener = listener; + super.start(new OutlierDetectionSubchannelStateListener(listener)); + } + + @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 have been ejected. + */ + double ejectionPercentage() { + if (trackerMap.isEmpty()) { + return 0; + } + int totalAddresses = 0; + int ejectedAddresses = 0; + for (AddressTracker tracker : trackerMap.values()) { + totalAddresses++; + if (tracker.subchannelsEjected()) { + ejectedAddresses++; + } + } + return ((double)ejectedAddresses / 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.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) { + 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 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; + } + + // 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); + } + } + } + } + + /** 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) { + + // 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 : trackersWithVolume) { + // 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; + } + + 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); + } + } + } + } + } + + /** 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; + 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 { + + 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, + 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 { + + 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) { + 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 { + 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) { + 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..e52c7414653 --- /dev/null +++ b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancerProvider.java @@ -0,0 +1,160 @@ +/* + * 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.Internal; +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; + +@Internal +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/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/CallCredentialsApplyingTest.java b/core/src/test/java/io/grpc/internal/CallCredentialsApplyingTest.java index a6142cc1a2f..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; @@ -148,6 +150,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()); } @@ -264,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 @@ -290,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); @@ -405,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( 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); 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(); 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/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); 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; 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/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 b5c8ca0c98d..09be9a9718d 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; @@ -327,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); @@ -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)); @@ -948,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); @@ -1163,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 = @@ -1177,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(); @@ -1213,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); @@ -1234,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(); @@ -1296,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); @@ -1446,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 = @@ -1591,103 +1563,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(); @@ -3772,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()); @@ -3878,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()); @@ -4205,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)); @@ -4243,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)); @@ -4273,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)); @@ -4289,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 { @@ -4323,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 { @@ -4351,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 { @@ -4377,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 { @@ -4457,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/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/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(); 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()); } diff --git a/core/src/test/java/io/grpc/internal/ServiceConfigErrorHandlingTest.java b/core/src/test/java/io/grpc/internal/ServiceConfigErrorHandlingTest.java index 16c6f3bf302..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()) @@ -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); @@ -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,29 +280,23 @@ 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 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); Map rawServiceConfig = parseJson("{\"loadBalancingConfig\": [{\"mock_lb\": {\"check\": \"val\"}}]}"); @@ -312,11 +306,11 @@ 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(); - 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); @@ -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/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))); } } 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..ccf3d40cdb6 --- /dev/null +++ b/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java @@ -0,0 +1,1163 @@ +/* + * 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.ArrayList; +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.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 fakeLbProvider = new StandardLoadBalancerProvider( + "fake_policy") { + @Override + public LoadBalancer newLoadBalancer(Helper helper) { + return new FakeLoadBalancer(helper); + } + }; + 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.acceptResolvedAddresses(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.acceptResolvedAddresses(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 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.acceptResolvedAddresses(resolvedAddresses); + + // Handling of resolved addresses is delegated + 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); + + // 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 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.acceptResolvedAddresses(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.acceptResolvedAddresses( + 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 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.acceptResolvedAddresses(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.acceptResolvedAddresses( + 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.acceptResolvedAddresses( + 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.acceptResolvedAddresses(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.acceptResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(), 7); + + // 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.acceptResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); + + // 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.acceptResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); + + // 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.acceptResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(subchannel2, Status.DEADLINE_EXCEEDED), 8); + + // 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.acceptResolvedAddresses(buildResolvedAddress(config, servers)); + + 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); + + // 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(), 8); + + // 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.acceptResolvedAddresses(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), 7); + + // 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 we don't have enough addresses that have the + * required volume. + */ + @Test + public void successRateOneOutlier_notEnoughAddressesWithVolume() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setMaxEjectionPercent(50) + .setSuccessRateEjection( + new SuccessRateEjection.Builder() + .setMinimumHosts(5) + .setRequestVolume(20).build()) + .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); + + loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers)); + + 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); + + // 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.acceptResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); + + // 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.acceptResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of( + subchannel1, Status.DEADLINE_EXCEEDED, + subchannel2, Status.DEADLINE_EXCEEDED), 7); + + // 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))); + } + + /** + * 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 successRateThreeOutliers_maxEjectionPercentage() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setMaxEjectionPercent(30) + .setSuccessRateEjection( + new SuccessRateEjection.Builder() + .setMinimumHosts(3) + .setRequestVolume(10) + .setStdevFactor(1).build()) + .setChildPolicy(new PolicySelection(roundRobinLbProvider, null)).build(); + + loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of( + subchannel1, Status.DEADLINE_EXCEEDED, + subchannel2, Status.DEADLINE_EXCEEDED, + subchannel3, Status.DEADLINE_EXCEEDED), 7); + + // 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; + } + + assertThat(totalEjected).isEqualTo(2); + } + + + /** + * 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.acceptResolvedAddresses(buildResolvedAddress(config, servers)); + + // By default all calls will return OK. + generateLoad(ImmutableMap.of(), 7); + + // 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.acceptResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); + + // 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.acceptResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); + + // Move forward in time to a point where the detection timer has fired. + forwardTime(config); + + // We should see no ejections. + 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.acceptResolvedAddresses(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. + */ + @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.acceptResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); + + // 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.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 + // 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), 7); + + // 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.acceptResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); + + // 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.acceptResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(), 7); + + // 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. + */ + @Test + public void subchannelUpdateAddress_multipleReplacedWithSingle() { + OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder() + .setMaxEjectionPercent(50) + .setFailurePercentageEjection( + new FailurePercentageEjection.Builder() + .setMinimumHosts(3) + .setRequestVolume(10).build()) + .setChildPolicy(new PolicySelection(fakeLbProvider, null)).build(); + + loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 6); + + // 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.acceptResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(), 7); + + // 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.acceptResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); + + // 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.acceptResolvedAddresses(buildResolvedAddress(config, servers)); + + generateLoad(ImmutableMap.of(subchannel1, Status.DEADLINE_EXCEEDED), 7); + + // 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, 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, 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(expectedStateChanges)).updateBalancingState(stateCaptor.capture(), + pickerCaptor.capture()); + SubchannelPicker picker = pickerCaptor.getAllValues() + .get(pickerCaptor.getAllValues().size() - 1); + + 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())); + } + } + + /** 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 boolean acceptResolvedAddresses(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); + } + return true; + } + + @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); + } + } +} diff --git a/cronet/README.md b/cronet/README.md index 0878c251cab..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.48.1' +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 c32db5f4d76..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.48.1' -implementation 'io.grpc:grpc-okhttp:1.48.1' +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 diff --git a/examples/android/clientcache/app/build.gradle b/examples/android/clientcache/app/build.gradle index 6f0d10891a9..f5d19fdebb2 100644 --- a/examples/android/clientcache/app/build.gradle +++ b/examples/android/clientcache/app/build.gradle @@ -32,9 +32,9 @@ 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.49.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.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' // 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.49.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 5a38cff5271..daed6b56d55 100644 --- a/examples/android/helloworld/app/build.gradle +++ b/examples/android/helloworld/app/build.gradle @@ -30,9 +30,9 @@ 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.49.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.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' // 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/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"> diff --git a/examples/android/routeguide/app/build.gradle b/examples/android/routeguide/app/build.gradle index 19c8d7a27a7..a7369519dbc 100644 --- a/examples/android/routeguide/app/build.gradle +++ b/examples/android/routeguide/app/build.gradle @@ -30,9 +30,9 @@ 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.49.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.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' // 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 3f6f2d0eb0d..05c9cd45d20 100644 --- a/examples/android/strictmode/app/build.gradle +++ b/examples/android/strictmode/app/build.gradle @@ -31,9 +31,9 @@ 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.49.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.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' // 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 4b02ae122b1..769a3d22f0b 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -22,8 +22,8 @@ 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 protobufVersion = '3.21.1' +def grpcVersion = '1.50.0' // CURRENT_GRPC_VERSION +def protobufVersion = '3.21.7' def protocVersion = protobufVersion dependencies { diff --git a/examples/example-alts/build.gradle b/examples/example-alts/build.gradle index 0f22d8ab6bc..1f371946e48 100644 --- a/examples/example-alts/build.gradle +++ b/examples/example-alts/build.gradle @@ -23,8 +23,8 @@ 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 protocVersion = '3.21.1' +def grpcVersion = '1.50.0' // CURRENT_GRPC_VERSION +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 ed0a08564bc..b82f5fa22fa 100644 --- a/examples/example-gauth/build.gradle +++ b/examples/example-gauth/build.gradle @@ -23,8 +23,8 @@ 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 protobufVersion = '3.21.1' +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 79d2dec996a..640c39c3e0c 100644 --- a/examples/example-gauth/pom.xml +++ b/examples/example-gauth/pom.xml @@ -6,14 +6,14 @@ jar - 1.49.0-SNAPSHOT + 1.50.0 example-gauth https://github.com/grpc/grpc-java UTF-8 - 1.49.0-SNAPSHOT - 3.21.1 + 1.50.0 + 3.21.7 1.7 1.7 diff --git a/examples/example-hostname/build.gradle b/examples/example-hostname/build.gradle index ec212f27aee..cbd50aa39a3 100644 --- a/examples/example-hostname/build.gradle +++ b/examples/example-hostname/build.gradle @@ -21,8 +21,8 @@ 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 protobufVersion = '3.21.1' +def grpcVersion = '1.50.0' // CURRENT_GRPC_VERSION +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 5ac4cfd4e5c..e0003180745 100644 --- a/examples/example-hostname/pom.xml +++ b/examples/example-hostname/pom.xml @@ -6,14 +6,14 @@ jar - 1.49.0-SNAPSHOT + 1.50.0 example-hostname https://github.com/grpc/grpc-java UTF-8 - 1.49.0-SNAPSHOT - 3.21.1 + 1.50.0 + 3.21.7 1.7 1.7 diff --git a/examples/example-jwt-auth/build.gradle b/examples/example-jwt-auth/build.gradle index 924d8da8966..15602ca5dd9 100644 --- a/examples/example-jwt-auth/build.gradle +++ b/examples/example-jwt-auth/build.gradle @@ -22,8 +22,8 @@ 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 protobufVersion = '3.21.1' +def grpcVersion = '1.50.0' // CURRENT_GRPC_VERSION +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 9e4d78ba820..13d52e66514 100644 --- a/examples/example-jwt-auth/pom.xml +++ b/examples/example-jwt-auth/pom.xml @@ -7,15 +7,15 @@ jar - 1.49.0-SNAPSHOT + 1.50.0 example-jwt-auth https://github.com/grpc/grpc-java UTF-8 - 1.49.0-SNAPSHOT - 3.21.1 - 3.21.1 + 1.50.0 + 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 ad53634bd21..8f62bb2ab8a 100644 --- a/examples/example-orca/build.gradle +++ b/examples/example-orca/build.gradle @@ -17,8 +17,8 @@ repositories { sourceCompatibility = 1.8 targetCompatibility = 1.8 -def grpcVersion = '1.49.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def protocVersion = '3.21.1' +def grpcVersion = '1.50.0' // CURRENT_GRPC_VERSION +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 62329b2e3c4..2fddbe4ee5a 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -23,8 +23,8 @@ 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 protocVersion = '3.21.1' +def grpcVersion = '1.50.0' // CURRENT_GRPC_VERSION +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 2557cd51843..6861601cecc 100644 --- a/examples/example-tls/pom.xml +++ b/examples/example-tls/pom.xml @@ -6,15 +6,15 @@ jar - 1.49.0-SNAPSHOT + 1.50.0 example-tls https://github.com/grpc/grpc-java UTF-8 - 1.49.0-SNAPSHOT - 3.21.1 - 2.0.53.Final + 1.50.0 + 3.21.7 + 2.0.54.Final 1.7 1.7 diff --git a/examples/example-xds/build.gradle b/examples/example-xds/build.gradle index 851937aa4a9..34b10d4c75e 100644 --- a/examples/example-xds/build.gradle +++ b/examples/example-xds/build.gradle @@ -22,9 +22,9 @@ 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' // 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 386b4bddd3e..157f5b0a4b4 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -6,15 +6,15 @@ jar - 1.49.0-SNAPSHOT + 1.50.0 examples https://github.com/grpc/grpc-java UTF-8 - 1.49.0-SNAPSHOT - 3.21.1 - 3.21.1 + 1.50.0 + 3.21.7 + 3.21.7 1.7 1.7 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/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..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 @@ -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; @@ -35,36 +37,36 @@ 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; 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", + "google.devtools.cloudtrace.v2.TraceService"); private static GcpObservability instance = null; private final Sink sink; private final ObservabilityConfig config; 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. @@ -77,7 +79,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, @@ -110,7 +112,6 @@ public void close() { if (instance == null) { throw new IllegalStateException("GcpObservability already closed!"); } - unRegisterStackDriverExporter(); sink.close(); instance = null; } @@ -126,25 +127,43 @@ 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()); - tracerFactories.add(InternalCensusTracingAccessor.getServerStreamTracerFactory()); + clientInterceptors.add( + getConditionalInterceptor(InternalCensusTracingAccessor.getClientInterceptor(false))); + tracerFactories.add(InternalCensusTracingAccessor.getServerStreamTracerFactory(false)); } InternalGlobalInterceptors.setInterceptorsTracers( clientInterceptors, serverInterceptors, tracerFactories); } + static ConditionalClientInterceptor getConditionalInterceptor(ClientInterceptor interceptor) { + return new ConditionalClientInterceptor(interceptor, + (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) { @@ -158,7 +177,6 @@ void registerStackDriverExporter(String projectId, Map customTag } statsConfigurationBuilder.setExportInterval(Duration.create(METRICS_EXPORT_INTERVAL, 0)); StackdriverStatsExporter.createAndRegister(statsConfigurationBuilder.build()); - metricsEnabled = true; } if (config.isEnableCloudTracing()) { @@ -177,29 +195,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/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/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..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 @@ -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 { @@ -144,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); } 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..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 @@ -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) { @@ -160,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/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/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" 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); + } } 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/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); } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 809bb230b75..37d443cd479 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,10 +5,10 @@ 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" +protobuf = "3.21.7" [libraries] android-annotations = "com.google.android:annotations:4.1.1.4" @@ -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/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/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/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); } 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/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()) 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/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/NettyServerHandler.java b/netty/src/main/java/io/grpc/netty/NettyServerHandler.java index 1dc73fa5e0e..62dd50ce65e 100644 --- a/netty/src/main/java/io/grpc/netty/NettyServerHandler.java +++ b/netty/src/main/java/io/grpc/netty/NettyServerHandler.java @@ -44,8 +44,10 @@ 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; import io.grpc.internal.ServerTransportListener; import io.grpc.internal.StatsTraceContext; import io.grpc.internal.TransportTracer; @@ -284,16 +286,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 +357,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) { 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/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/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/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/main/java/io/grpc/okhttp/OkHttpServerBuilder.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java index 5928a2e38c1..d3ea82894b0 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java @@ -16,8 +16,11 @@ 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.CanIgnoreReturnValue; import com.google.errorprone.annotations.DoNotCall; import io.grpc.ChoiceServerCredentials; import io.grpc.ExperimentalApi; @@ -62,6 +65,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 +117,9 @@ 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; + boolean permitKeepAliveWithoutCalls; + long permitKeepAliveTimeInNanos = TimeUnit.MINUTES.toNanos(5); @VisibleForTesting OkHttpServerBuilder( @@ -178,6 +188,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 @@ -195,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 3a4ed86f5da..f6099bec17a 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; @@ -27,7 +29,9 @@ 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; import io.grpc.internal.SerializingExecutor; import io.grpc.internal.ServerTransport; @@ -91,6 +95,8 @@ final class OkHttpServerTransport implements ServerTransport, private ScheduledExecutorService scheduledExecutorService; private Attributes attributes; private KeepAliveManager keepAliveManager; + private MaxConnectionIdleManager maxConnectionIdleManager; + private final KeepAliveEnforcer keepAliveEnforcer; private final Object lock = new Object(); @GuardedBy("lock") @@ -133,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) { @@ -155,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; @@ -163,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 @@ -189,6 +218,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 +345,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 +406,12 @@ public OutboundFlowController.StreamState[] getActiveStreams() { void streamClosed(int streamId, boolean flush) { synchronized (lock) { streams.remove(streamId); + if (streams.isEmpty()) { + keepAliveEnforcer.onTransportIdle(); + if (maxConnectionIdleManager != null) { + maxConnectionIdleManager.onTransportIdle(); + } + } if (gracefulShutdown && streams.isEmpty()) { frameWriter.close(); } else { @@ -433,6 +476,9 @@ static final class Config { final int flowControlWindow; final int maxInboundMessageSize; final int maxInboundMetadataSize; + final long maxConnectionIdleNanos; + final boolean permitKeepAliveWithoutCalls; + final long permitKeepAliveTimeInNanos; public Config( OkHttpServerBuilder builder, @@ -452,6 +498,9 @@ public Config( flowControlWindow = builder.flowControlWindow; maxInboundMessageSize = builder.maxInboundMessageSize; maxInboundMetadataSize = builder.maxInboundMetadataSize; + maxConnectionIdleNanos = builder.maxConnectionIdleInNanos; + permitKeepAliveWithoutCalls = builder.permitKeepAliveWithoutCalls; + permitKeepAliveTimeInNanos = builder.permitKeepAliveTimeInNanos; } } @@ -697,6 +746,12 @@ public void headers(boolean outFinished, authority == null ? null : asciiString(authority), statsTraceCtx, tracer); + if (streams.isEmpty()) { + keepAliveEnforcer.onTransportActive(); + if (maxConnectionIdleManager != null) { + maxConnectionIdleManager.onTransportActive(); + } + } streams.put(streamId, stream); listener.streamCreated(streamForApp, method, metadata); stream.onStreamAllocated(); @@ -829,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); @@ -953,6 +1013,12 @@ private void respondWithHttpError( synchronized (lock) { Http2ErrorStreamState stream = new Http2ErrorStreamState(streamId, lock, outboundFlow, config.flowControlWindow); + if (streams.isEmpty()) { + keepAliveEnforcer.onTransportActive(); + if (maxConnectionIdleManager != null) { + maxConnectionIdleManager.onTransportActive(); + } + } streams.put(streamId, stream); if (inFinished) { stream.inboundDataReceived(new Buffer(), 0, true); 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; 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(); diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java index 201829d38ab..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; @@ -90,6 +91,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 +107,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 +122,10 @@ public class OkHttpServerTransportTest { } } }) - .flowControlWindow(INITIAL_WINDOW_SIZE); + .flowControlWindow(INITIAL_WINDOW_SIZE) + .maxConnectionIdle(MAX_CONNECTION_IDLE, TimeUnit.NANOSECONDS) + .permitKeepAliveWithoutCalls(true) + .permitKeepAliveTime(0, TimeUnit.SECONDS); @Rule public final Timeout globalTimeout = Timeout.seconds(10); @@ -146,6 +152,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 +380,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(); @@ -992,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()), @@ -1038,8 +1198,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 +1212,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(); } 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/repositories.bzl b/repositories.bzl index bb9170a842b..cced1d29bee 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", @@ -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(): 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/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 6c777f45146..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<>(); @@ -176,6 +181,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(); @@ -274,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); } } @@ -323,6 +341,10 @@ public void run() { } }); } + + void triggerPendingRpcProcessing() { + super.updateBalancingState(state, picker); + } } /** @@ -475,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)); } } @@ -512,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; @@ -530,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; } @@ -561,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); } } } @@ -598,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 @@ -614,6 +655,11 @@ boolean isStaled(long now) { return staleTime - now <= 0; } + @Override + protected long getMinEvictionTime() { + return minEvictionTime; + } + @Override void cleanup() { synchronized (lock) { @@ -687,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)); } @@ -705,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 @@ -863,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; } } @@ -931,12 +991,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 +1043,5 @@ public String toString() { .toString(); } } + } 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); } diff --git a/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java b/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java index 69aa27af182..a6e7b7d80c7 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); @@ -566,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( @@ -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 { @@ -588,6 +590,8 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { } }); } + + return true; } @Override 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(); } 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) { 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 4a599ef232c..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; @@ -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(), @@ -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 9a4c366f84f..f4dccc8a10b 100644 --- a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java +++ b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java @@ -18,109 +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.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; @@ -136,1904 +77,136 @@ 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")); - 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 - 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"); - } - } - } - } - - 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; - 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(); + 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); } - } - } - 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); - } - } + }); + 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)); - } 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); + @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; } - - if (numerator > 1_000_000 || numerator < 0) { - numerator = 1_000_000; + Set toParseResourceNames = null; + if (!(resourceType == LDS || resourceType == RDS) + && resourceSubscribers.containsKey(xdsResourceType)) { + toParseResourceNames = resourceSubscribers.get(xdsResourceType).keySet(); } - return numerator; + 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(); + } } } } @@ -2066,26 +239,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)) { @@ -2107,16 +277,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()); } @@ -2130,91 +299,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); @@ -2223,54 +324,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); } } } @@ -2323,54 +394,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; } @@ -2398,9 +472,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(); @@ -2410,18 +488,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(); } @@ -2431,34 +509,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 @@ -2506,7 +566,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) { @@ -2520,7 +580,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); } @@ -2577,7 +637,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; @@ -2594,7 +654,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); } } @@ -2609,7 +669,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, @@ -2625,7 +685,7 @@ void onAbsent() { data = null; absent = true; metadata = ResourceMetadata.newResourceMetadataDoesNotExist(); - for (ResourceWatcher watcher : watchers) { + for (ResourceWatcher watcher : watchers) { watcher.onResourceDoesNotExist(resource); } } @@ -2644,7 +704,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); } } @@ -2654,24 +714,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); } } @@ -2688,55 +732,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/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/ClusterResolverLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java index 108f406ade8..f4aaf9426bc 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,11 +46,14 @@ 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; -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; @@ -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); @@ -285,10 +290,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 @@ -318,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. @@ -329,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(); @@ -343,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(); @@ -351,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; } @@ -360,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 @@ -368,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 @@ -436,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, @@ -532,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"); @@ -732,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 = @@ -742,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..5015c56ba92 100644 --- a/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java +++ b/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java @@ -19,9 +19,10 @@ 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; +import io.grpc.xds.internal.security.SslContextProviderSupplier; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Objects; @@ -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/FilterChainMatchingProtocolNegotiators.java b/xds/src/main/java/io/grpc/xds/FilterChainMatchingProtocolNegotiators.java index e75440225dc..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.SdsProtocolNegotiators.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/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/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/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 d07f10555c6..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.SdsProtocolNegotiators; +import io.grpc.xds.internal.security.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/XdsClient.java b/xds/src/main/java/io/grpc/xds/XdsClient.java index bdffc361193..feb3afa3e98 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClient.java +++ b/xds/src/main/java/io/grpc/xds/XdsClient.java @@ -19,22 +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.UpstreamTlsContext; import io.grpc.xds.LoadStatsManager2.ClusterDropStats; import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats; import java.net.URI; @@ -42,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; /** @@ -117,286 +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(); - - 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) { - 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); - } - - 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()) - // Exclude upstreamTlsContext as its string representation is 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); - - 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. @@ -416,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); } /** @@ -599,58 +302,19 @@ TlsContextManager getTlsContextManager() { } /** - * Registers a data watcher for the given LDS resource. - */ - void watchLdsResource(String resourceName, LdsResourceWatcher watcher) { - throw new UnsupportedOperationException(); - } - - /** - * Unregisters the given LDS 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. + * Registers a data watcher for the given Xds resource. */ - void watchCdsResource(String resourceName, CdsResourceWatcher watcher) { + void watchXdsResource(XdsResourceType type, String resourceName, + ResourceWatcher watcher) { throw new UnsupportedOperationException(); } /** - * Unregisters the given CDS resource watcher. + * Unregisters the given 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(); } @@ -681,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. @@ -717,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 ff9e4a10eb1..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; @@ -123,6 +122,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 +162,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 +587,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 @@ -686,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; @@ -721,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); } } }); @@ -760,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 @@ -822,7 +824,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)) { @@ -903,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; } } @@ -912,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 @@ -934,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); } }); } @@ -982,7 +986,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/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/XdsServerCredentials.java b/xds/src/main/java/io/grpc/xds/XdsServerCredentials.java index e6e78f319c7..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.SdsProtocolNegotiators; +import io.grpc.xds.internal.security.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/XdsServerWrapper.java b/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java index 1690e6459a7..a58a3f6bc24 100644 --- a/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java +++ b/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java @@ -50,13 +50,12 @@ 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.sds.SslContextProviderSupplier; +import io.grpc.xds.internal.security.SslContextProviderSupplier; import java.io.IOException; import java.net.SocketAddress; import java.util.ArrayList; @@ -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/main/java/io/grpc/xds/internal/sds/ClientSslContextProviderFactory.java b/xds/src/main/java/io/grpc/xds/internal/security/ClientSslContextProviderFactory.java similarity index 84% 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..4bf11fba3ff 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,29 +14,29 @@ * 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; +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/sds/Closeable.java b/xds/src/main/java/io/grpc/xds/internal/security/Closeable.java similarity index 92% 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 c3695cecaf3..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,10 +14,10 @@ * limitations under the License. */ -package io.grpc.xds.internal.sds; +package io.grpc.xds.internal.security; public interface Closeable extends java.io.Closeable { @Override - public void close(); + void close(); } 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 98% 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..6bf66d022ff 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,13 +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 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/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/SdsProtocolNegotiators.java b/xds/src/main/java/io/grpc/xds/internal/security/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/security/SecurityProtocolNegotiators.java index a032737e647..0c1271d1dc7 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/SdsProtocolNegotiators.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; @@ -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/ServerSslContextProviderFactory.java b/xds/src/main/java/io/grpc/xds/internal/security/ServerSslContextProviderFactory.java similarity index 85% 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..6206ccdcfe6 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,29 +14,29 @@ * 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; +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/sds/SslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/security/SslContextProvider.java similarity index 93% 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 6b661715e48..7544f5d9fc3 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,17 +14,18 @@ * 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; 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; -import io.grpc.xds.internal.sds.trust.SdsTrustManagerFactory; +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; @@ -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; @@ -70,11 +72,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/security/SslContextProviderSupplier.java similarity index 97% 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 664b4881bc2..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; @@ -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/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/certprovider/CertProviderClientSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertProviderClientSslContextProvider.java similarity index 56% 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 ce9ef3de680..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,19 +14,17 @@ * 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; -import io.grpc.xds.internal.sds.trust.SdsTrustManagerFactory; +import io.grpc.xds.internal.security.trust.XdsTrustManagerFactory; import io.netty.handler.ssl.SslContextBuilder; import java.security.cert.CertStoreException; import java.security.cert.X509Certificate; @@ -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, @@ -62,7 +59,7 @@ protected final SslContextBuilder getSslContextBuilder( SslContextBuilder sslContextBuilder = GrpcSslContexts.forClient() .trustManager( - new SdsTrustManagerFactory( + new XdsTrustManagerFactory( savedTrustedRoots.toArray(new X509Certificate[0]), certificateValidationContextdationContext)); if (isMtls()) { @@ -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 50% 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 a7f0849d00b..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.sds.trust.SdsTrustManagerFactory; +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, @@ -66,7 +62,7 @@ protected final SslContextBuilder getSslContextBuilder( setClientAuthValues( sslContextBuilder, isMtls() - ? new SdsTrustManagerFactory( + ? new XdsTrustManagerFactory( savedTrustedRoots.toArray(new X509Certificate[0]), certificateValidationContextdationContext) : null); @@ -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 97% 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 5c4dba99dc3..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; @@ -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/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 04ed997fa58..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,13 +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.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/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 97% 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 43143ebb3ae..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.sds.ReferenceCountingMap; - +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 98% 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 b86de55766e..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,14 +14,14 @@ * 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.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/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/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/SdsTrustManagerFactory.java b/xds/src/main/java/io/grpc/xds/internal/security/trust/XdsTrustManagerFactory.java similarity index 87% rename from xds/src/main/java/io/grpc/xds/internal/sds/trust/SdsTrustManagerFactory.java rename to xds/src/main/java/io/grpc/xds/internal/security/trust/XdsTrustManagerFactory.java index 479569f1596..26d6bcd81b8 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/trust/SdsTrustManagerFactory.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,16 +41,15 @@ import javax.net.ssl.X509ExtendedTrustManager; /** - * Factory class used by providers of {@link TlsContextManagerImpl} to provide a - * {@link SdsX509TrustManager} for trust and SAN checks. + * Factory class used to provide a {@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 +57,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 +73,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 +98,7 @@ private static X509Certificate[] getTrustedCaFromCertContext( } @VisibleForTesting - static SdsX509TrustManager createSdsX509TrustManager( + static XdsX509TrustManager createSdsX509TrustManager( X509Certificate[] certs, CertificateValidationContext certContext) throws CertStoreException { TrustManagerFactory tmf = null; try { @@ -133,7 +131,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 +146,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/security/trust/XdsX509TrustManager.java similarity index 97% rename from xds/src/main/java/io/grpc/xds/internal/sds/trust/SdsX509TrustManager.java rename to xds/src/main/java/io/grpc/xds/internal/security/trust/XdsX509TrustManager.java index 3178d2b3e4b..4bb6f0520c4 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/trust/SdsX509TrustManager.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; @@ -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/CdsLoadBalancer2Test.java b/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java index a90df303e04..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; @@ -50,11 +51,13 @@ 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; -import io.grpc.xds.XdsClient.CdsUpdate; -import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil; +import io.grpc.xds.XdsClusterResource.CdsUpdate; +import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -85,8 +88,11 @@ 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( + + private static final SynchronizationContext syncContext = new SynchronizationContext( new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { @@ -154,7 +160,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 +171,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 +188,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 +208,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 +216,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 +238,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 +272,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 +282,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 +292,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 +332,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 +344,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 +355,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 +381,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 +393,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 +435,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 +512,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 +528,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 +542,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 +567,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 +575,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 { @@ -643,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); } @@ -671,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 53b4027768f..788110e1c20 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,28 +70,28 @@ 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; -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.internal.sds.CommonTlsContextTestsUtil; +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; import java.util.Arrays; @@ -211,7 +215,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. @@ -247,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; @@ -275,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( @@ -354,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 @@ -572,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)))); @@ -597,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"); @@ -626,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. @@ -653,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"); @@ -718,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"); @@ -796,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"); @@ -810,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"); @@ -824,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. @@ -837,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(); @@ -854,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); @@ -869,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. @@ -896,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); @@ -916,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); @@ -936,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() @@ -956,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 = @@ -977,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 = @@ -997,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(); } @@ -1014,14 +1038,15 @@ 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 = "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, @@ -1035,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(); } @@ -1052,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 = @@ -1076,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, @@ -1156,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. @@ -1183,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. @@ -1216,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. @@ -1240,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. @@ -1259,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"); @@ -1279,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"); @@ -1295,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. @@ -1322,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"); @@ -1379,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. @@ -1393,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. @@ -1406,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(); @@ -1423,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); @@ -1438,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. @@ -1464,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); @@ -1502,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); @@ -1566,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. @@ -1611,18 +1661,19 @@ 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, - 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. 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. @@ -1635,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"); @@ -1646,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. @@ -1679,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"); @@ -1692,13 +1745,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 +1764,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 +1786,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 @@ -1755,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"); @@ -1774,13 +1827,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 +1859,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"); @@ -1842,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. @@ -1857,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. @@ -1872,11 +1927,12 @@ 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, - 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"); @@ -1903,11 +1959,12 @@ 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, - false, null, "envoy.transport_sockets.tls", null + false, null, "envoy.transport_sockets.tls", null, null )); call.sendResponse(ResourceType.CDS, clusterRingHash, VERSION_1, "0000"); @@ -1934,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 = @@ -1959,10 +2017,11 @@ 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))); + "envoy.transport_sockets.tls", mf.buildCircuitBreakers(50, 200), null)); call.sendResponse(CDS, clusterCircuitBreakers, VERSION_1, "0000"); // Client sent an ACK CDS request. @@ -1992,25 +2051,27 @@ 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 = 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. 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() @@ -2028,20 +2089,21 @@ 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 = 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. @@ -2063,13 +2125,14 @@ 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 .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,19 +2145,242 @@ 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()); + XdsClusterResource.enableOutlierDetection = true; + + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), 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()); + XdsClusterResource.enableOutlierDetection = false; + + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), 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()); + XdsClusterResource.enableOutlierDetection = true; + + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), 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 { + XdsClusterResource.validateOutlierDetection( + OutlierDetection.newBuilder().setInterval(Duration.newBuilder().setSeconds(Long.MAX_VALUE)) + .build()); + } + + @Test(expected = ResourceInvalidException.class) + public void validateOutlierDetection_negativeInterval() throws ResourceInvalidException { + XdsClusterResource.validateOutlierDetection( + OutlierDetection.newBuilder().setInterval(Duration.newBuilder().setSeconds(-1)) + .build()); + } + + @Test(expected = ResourceInvalidException.class) + public void validateOutlierDetection_invalidBaseEjectionTime() throws ResourceInvalidException { + XdsClusterResource.validateOutlierDetection( + OutlierDetection.newBuilder() + .setBaseEjectionTime(Duration.newBuilder().setSeconds(Long.MAX_VALUE)) + .build()); + } + + @Test(expected = ResourceInvalidException.class) + public void validateOutlierDetection_negativeBaseEjectionTime() throws ResourceInvalidException { + XdsClusterResource.validateOutlierDetection( + OutlierDetection.newBuilder().setBaseEjectionTime(Duration.newBuilder().setSeconds(-1)) + .build()); + } + + @Test(expected = ResourceInvalidException.class) + public void validateOutlierDetection_invalidMaxEjectionTime() throws ResourceInvalidException { + XdsClusterResource.validateOutlierDetection( + OutlierDetection.newBuilder() + .setMaxEjectionTime(Duration.newBuilder().setSeconds(Long.MAX_VALUE)) + .build()); + } + + @Test(expected = ResourceInvalidException.class) + public void validateOutlierDetection_negativeMaxEjectionTime() throws ResourceInvalidException { + XdsClusterResource.validateOutlierDetection( + OutlierDetection.newBuilder().setMaxEjectionTime(Duration.newBuilder().setSeconds(-1)) + .build()); + } + + @Test(expected = ResourceInvalidException.class) + public void validateOutlierDetection_maxEjectionPercentTooHigh() throws ResourceInvalidException { + XdsClusterResource.validateOutlierDetection( + OutlierDetection.newBuilder().setMaxEjectionPercent(UInt32Value.of(101)).build()); + } + + @Test(expected = ResourceInvalidException.class) + public void validateOutlierDetection_enforcingSuccessRateTooHigh() + throws ResourceInvalidException { + XdsClusterResource.validateOutlierDetection( + OutlierDetection.newBuilder().setEnforcingSuccessRate(UInt32Value.of(101)).build()); + } + + @Test(expected = ResourceInvalidException.class) + public void validateOutlierDetection_failurePercentageThresholdTooHigh() + throws ResourceInvalidException { + XdsClusterResource.validateOutlierDetection( + OutlierDetection.newBuilder().setFailurePercentageThreshold(UInt32Value.of(101)).build()); + } + + @Test(expected = ResourceInvalidException.class) + public void validateOutlierDetection_enforcingFailurePercentageTooHigh() + throws ResourceInvalidException { + XdsClusterResource.validateOutlierDetection( + OutlierDetection.newBuilder().setEnforcingFailurePercentage(UInt32Value.of(101)).build()); + } + /** * CDS response containing UpstreamTlsContext with bad transportSocketName for a cluster. */ @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 .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. @@ -2106,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(); @@ -2125,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); @@ -2139,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. @@ -2169,7 +2460,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); @@ -2193,23 +2484,24 @@ 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"; 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. @@ -2241,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. @@ -2269,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. @@ -2290,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"); @@ -2300,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); @@ -2332,7 +2627,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(); @@ -2381,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), @@ -2403,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"); @@ -2419,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. @@ -2449,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"); @@ -2509,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. @@ -2523,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. @@ -2536,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(); @@ -2554,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); @@ -2568,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. @@ -2604,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. @@ -2626,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); @@ -2643,10 +2950,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 +2999,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()); @@ -2713,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); @@ -2786,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. @@ -2803,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); @@ -2923,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()); @@ -2935,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); @@ -2955,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)); @@ -2995,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(); @@ -3023,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())); @@ -3059,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())); @@ -3086,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())); @@ -3112,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())); @@ -3135,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)) @@ -3256,7 +3577,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..3bb6e421388 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( @@ -462,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); @@ -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..6eb48e5bb00 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( @@ -518,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); @@ -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/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 087b64b55e9..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; @@ -58,6 +59,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,13 +68,17 @@ 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; 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.XdsEndpointResource.EdsUpdate; +import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; import java.net.SocketAddress; import java.net.URI; import java.net.URISyntaxException; @@ -116,10 +123,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); @@ -172,7 +187,6 @@ public XdsClient returnObject(Object object) { private int xdsClientRefs; private ClusterResolverLoadBalancer loadBalancer; - @Before public void setUp() throws URISyntaxException { MockitoAnnotations.initMocks(this); @@ -182,6 +196,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 +332,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( @@ -679,7 +778,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 +843,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); @@ -1072,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); } @@ -1096,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)); } } @@ -1107,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/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 a4efb226ce9..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.SdsProtocolNegotiators.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/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. 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( diff --git a/xds/src/test/java/io/grpc/xds/XdsClientWrapperForServerSdsTestMisc.java b/xds/src/test/java/io/grpc/xds/XdsClientWrapperForServerSdsTestMisc.java index 442bfef653c..50991781180 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.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; @@ -44,13 +44,13 @@ 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; -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/XdsNameResolverTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java index 25bd866424d..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; @@ -274,7 +276,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 +301,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 +315,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 +331,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 +359,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 +369,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 +398,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 +408,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 +475,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 +498,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 +545,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 +566,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 +584,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 +614,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 +670,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 +711,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 +745,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 +784,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,13 +797,14 @@ 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); // 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); @@ -817,14 +820,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 +849,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 +889,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 +925,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 +946,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 +969,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 +1002,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 +1034,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 +1081,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 +1139,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 +1167,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 +1189,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 +1332,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 +1454,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 +1472,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 +1490,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 +1516,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 +1585,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 +1603,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 +1622,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 +1662,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 +1675,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 +1697,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 +1731,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 +1756,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 +1785,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 +1800,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 +1817,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 +1846,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 +1907,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 +1916,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 +1927,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 +1940,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(); } @@ -2071,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() { @@ -2080,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) { @@ -2122,7 +2137,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 +2155,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 +2216,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", diff --git a/xds/src/test/java/io/grpc/xds/XdsSdsClientServerTest.java b/xds/src/test/java/io/grpc/xds/XdsSdsClientServerTest.java index 36669537255..3ef23c11375 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; @@ -57,13 +57,13 @@ 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; -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/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 7a9b42d4b21..ef2f606f3fa 100644 --- a/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java @@ -57,17 +57,16 @@ 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; 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; @@ -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(); 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 90% 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..4f85afc2ead 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; @@ -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/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/SdsProtocolNegotiatorsTest.java b/xds/src/test/java/io/grpc/xds/internal/security/SecurityProtocolNegotiatorsTest.java similarity index 90% rename from xds/src/test/java/io/grpc/xds/internal/sds/SdsProtocolNegotiatorsTest.java rename to xds/src/test/java/io/grpc/xds/internal/security/SecurityProtocolNegotiatorsTest.java index 502d2185a82..0531189f2ac 100644 --- a/xds/src/test/java/io/grpc/xds/internal/sds/SdsProtocolNegotiatorsTest.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.SdsProtocolNegotiators.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; @@ -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.sds.SdsProtocolNegotiators.ClientSdsHandler; -import io.grpc.xds.internal.sds.SdsProtocolNegotiators.ClientSdsProtocolNegotiator; +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; @@ -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/ServerSslContextProviderFactoryTest.java b/xds/src/test/java/io/grpc/xds/internal/security/ServerSslContextProviderFactoryTest.java similarity index 86% 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..07648194f72 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; @@ -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/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/certprovider/CertProviderClientSslContextProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/security/certprovider/CertProviderClientSslContextProviderTest.java similarity index 91% 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 111a44e3224..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,18 +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 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 io.grpc.xds.internal.security.certprovider.CommonCertProviderTestUtils.getCertFromResourceName; 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; @@ -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 90% 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 0309f070c82..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,17 +14,17 @@ * 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.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 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.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 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 92% 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 0e60c4c6716..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,8 +22,8 @@ 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.sds.trust.CertificateUtils; +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; import java.io.InputStream; 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 95% 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 4da9e6127c7..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,15 +14,15 @@ * 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.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; @@ -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.sds.CommonTlsContextTestsUtil; +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; diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/trust/SdsTrustManagerFactoryTest.java b/xds/src/test/java/io/grpc/xds/internal/security/trust/XdsTrustManagerFactoryTest.java similarity index 74% rename from xds/src/test/java/io/grpc/xds/internal/sds/trust/SdsTrustManagerFactoryTest.java rename to xds/src/test/java/io/grpc/xds/internal/security/trust/XdsTrustManagerFactoryTest.java index f693261f929..30c5e542a80 100644 --- a/xds/src/test/java/io/grpc/xds/internal/sds/trust/SdsTrustManagerFactoryTest.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; @@ -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/security/trust/XdsX509TrustManagerTest.java similarity index 91% rename from xds/src/test/java/io/grpc/xds/internal/sds/trust/SdsX509TrustManagerTest.java rename to xds/src/test/java/io/grpc/xds/internal/security/trust/XdsX509TrustManagerTest.java index 7ceae30b1e8..c6319ee5a9f 100644 --- a/xds/src/test/java/io/grpc/xds/internal/sds/trust/SdsX509TrustManagerTest.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; @@ -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");