From 7c4fe69dfd2d63ff6b15fe3b97158c7a7ff64334 Mon Sep 17 00:00:00 2001 From: Sergii Tkachenko Date: Tue, 11 Jan 2022 17:45:24 -0800 Subject: [PATCH 01/68] xds: fix a concurrency issue in CSDS ClientStatus responses (#8795) * xds: fix a concurrency issue in CSDS ClientStatus responses Fixes an issue with ClientXdsClient.getSubscribedResourcesMetadata() executed out of shared synchronization context, and leading to: - each individual config dump containing outdated data when an xDS resource is updated during CsdsService preparing the response - config dumps for different services being out-of-sync with each other when any of the related xDS resources is updated during CsdsService preparing the response The fix replaces getSubscribedResourcesMetadata(ResourceType type) with atomic getSubscribedResourcesMetadataSnapshot() returning a snapshot of all resources for each type as they are at the moment of a CSDS request. --- .../java/io/grpc/xds/ClientXdsClient.java | 35 ++++- .../main/java/io/grpc/xds/CsdsService.java | 53 ++++++-- xds/src/main/java/io/grpc/xds/XdsClient.java | 12 +- .../io/grpc/xds/ClientXdsClientTestBase.java | 41 ++++-- .../java/io/grpc/xds/CsdsServiceTest.java | 128 +++++++++++------- 5 files changed, 186 insertions(+), 83 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java index 222191ad55b..72f5db82ed0 100644 --- a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java +++ b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java @@ -28,7 +28,10 @@ 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; @@ -158,7 +161,6 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res static boolean enableRouteLookup = !Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_XDS_RLS_LB")) && Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_XDS_RLS_LB")); - private static final String TYPE_URL_HTTP_CONNECTION_MANAGER_V2 = "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2" + ".HttpConnectionManager"; @@ -2028,12 +2030,31 @@ public Collection getSubscribedResources(ServerInfo serverInfo, Resource } @Override - Map getSubscribedResourcesMetadata(ResourceType type) { - Map metadataMap = new HashMap<>(); - for (Map.Entry entry : getSubscribedResourcesMap(type).entrySet()) { - metadataMap.put(entry.getKey(), entry.getValue().metadata); - } - return metadataMap; + ListenableFuture>> + getSubscribedResourcesMetadataSnapshot() { + final SettableFuture>> future = + SettableFuture.create(); + syncContext.execute(new Runnable() { + @Override + 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; + } + ImmutableMap.Builder metadataMap = ImmutableMap.builder(); + for (Map.Entry resourceEntry + : getSubscribedResourcesMap(type).entrySet()) { + metadataMap.put(resourceEntry.getKey(), resourceEntry.getValue().metadata); + } + metadataSnapshot.put(type, metadataMap.build()); + } + future.set(metadataSnapshot.build()); + } + }); + return future; } @Override diff --git a/xds/src/main/java/io/grpc/xds/CsdsService.java b/xds/src/main/java/io/grpc/xds/CsdsService.java index 89147536a6d..edee01f95f1 100644 --- a/xds/src/main/java/io/grpc/xds/CsdsService.java +++ b/xds/src/main/java/io/grpc/xds/CsdsService.java @@ -17,8 +17,10 @@ package io.grpc.xds; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Verify.verifyNotNull; import com.google.common.annotations.VisibleForTesting; +import com.google.common.util.concurrent.ListenableFuture; import com.google.protobuf.util.Timestamps; import io.envoyproxy.envoy.admin.v3.ClientResourceStatus; import io.envoyproxy.envoy.service.status.v3.ClientConfig; @@ -37,6 +39,9 @@ import io.grpc.xds.XdsClient.ResourceMetadata.UpdateFailureState; import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory; import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; @@ -101,21 +106,28 @@ public void onCompleted() { private boolean handleRequest( ClientStatusRequest request, StreamObserver responseObserver) { + StatusException error; try { responseObserver.onNext(getConfigDumpForRequest(request)); return true; } catch (StatusException e) { - responseObserver.onError(e); + error = e; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + logger.log(Level.FINE, "Server interrupted while building CSDS config dump", e); + error = Status.ABORTED.withDescription("Thread interrupted").withCause(e).asException(); } catch (Exception e) { logger.log(Level.WARNING, "Unexpected error while building CSDS config dump", e); - responseObserver.onError(new StatusException( - Status.INTERNAL.withDescription("Unexpected internal error").withCause(e))); + error = + Status.INTERNAL.withDescription("Unexpected internal error").withCause(e).asException(); } + + responseObserver.onError(error); return false; } private ClientStatusResponse getConfigDumpForRequest(ClientStatusRequest request) - throws StatusException { + throws StatusException, InterruptedException { if (request.getNodeMatchersCount() > 0) { throw new StatusException( Status.INVALID_ARGUMENT.withDescription("node_matchers not supported")); @@ -140,16 +152,20 @@ private ClientStatusResponse getConfigDumpForRequest(ClientStatusRequest request } @VisibleForTesting - static ClientConfig getClientConfigForXdsClient(XdsClient xdsClient) { + static ClientConfig getClientConfigForXdsClient(XdsClient xdsClient) throws InterruptedException { ClientConfig.Builder builder = ClientConfig.newBuilder() .setNode(xdsClient.getBootstrapInfo().node().toEnvoyProtoNode()); - for (ResourceType type : ResourceType.values()) { - if (type == ResourceType.UNKNOWN) { - continue; - } - Map metadataMap = xdsClient.getSubscribedResourcesMetadata(type); - for (String resourceName : metadataMap.keySet()) { - ResourceMetadata metadata = metadataMap.get(resourceName); + + Map> metadataByType = + awaitSubscribedResourcesMetadata(xdsClient.getSubscribedResourcesMetadataSnapshot()); + + for (Map.Entry> metadataByTypeEntry + : metadataByType.entrySet()) { + ResourceType type = metadataByTypeEntry.getKey(); + Map metadataByResourceName = metadataByTypeEntry.getValue(); + for (Map.Entry metadataEntry : metadataByResourceName.entrySet()) { + String resourceName = metadataEntry.getKey(); + ResourceMetadata metadata = metadataEntry.getValue(); GenericXdsConfig.Builder genericXdsConfigBuilder = GenericXdsConfig.newBuilder() .setTypeUrl(type.typeUrl()) .setName(resourceName) @@ -161,6 +177,7 @@ static ClientConfig getClientConfigForXdsClient(XdsClient xdsClient) { .setXdsConfig(metadata.getRawResource()); } if (metadata.getStatus() == ResourceMetadataStatus.NACKED) { + verifyNotNull(metadata.getErrorState(), "resource %s getErrorState", resourceName); genericXdsConfigBuilder .setErrorState(metadataUpdateFailureStateToProto(metadata.getErrorState())); } @@ -170,6 +187,18 @@ static ClientConfig getClientConfigForXdsClient(XdsClient xdsClient) { return builder.build(); } + private static Map> awaitSubscribedResourcesMetadata( + ListenableFuture>> future) + throws InterruptedException { + try { + // Normally this shouldn't take long, but add some slack for cases like a cold JVM. + return future.get(20, TimeUnit.SECONDS); + } catch (ExecutionException | TimeoutException e) { + // For CSDS' purposes, the exact reason why metadata not loaded isn't important. + throw new RuntimeException(e); + } + } + @VisibleForTesting static ClientResourceStatus metadataStatusToClientStatus(ResourceMetadataStatus status) { switch (status) { diff --git a/xds/src/main/java/io/grpc/xds/XdsClient.java b/xds/src/main/java/io/grpc/xds/XdsClient.java index 024500253a3..6f2a661361e 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClient.java +++ b/xds/src/main/java/io/grpc/xds/XdsClient.java @@ -21,6 +21,7 @@ import com.google.auto.value.AutoValue; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.ListenableFuture; import com.google.protobuf.Any; import io.grpc.Status; import io.grpc.xds.AbstractXdsClient.ResourceType; @@ -494,7 +495,16 @@ TlsContextManager getTlsContextManager() { throw new UnsupportedOperationException(); } - Map getSubscribedResourcesMetadata(ResourceType type) { + /** + * Returns a {@link ListenableFuture} to the snapshot of the subscribed resources as + * they are at the moment of the call. + * + *

The snapshot is a map from the "resource type" to + * a map ("resource name": "resource metadata"). + */ + // Must be synchronized. + ListenableFuture>> + getSubscribedResourcesMetadataSnapshot() { throw new UnsupportedOperationException(); } diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java index 42cf9baa1d9..48db92b4de5 100644 --- a/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java +++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java @@ -379,10 +379,23 @@ protected static boolean matchErrorDetail( private void verifySubscribedResourcesMetadataSizes( int ldsSize, int cdsSize, int rdsSize, int edsSize) { - assertThat(xdsClient.getSubscribedResourcesMetadata(LDS)).hasSize(ldsSize); - assertThat(xdsClient.getSubscribedResourcesMetadata(CDS)).hasSize(cdsSize); - assertThat(xdsClient.getSubscribedResourcesMetadata(RDS)).hasSize(rdsSize); - assertThat(xdsClient.getSubscribedResourcesMetadata(EDS)).hasSize(edsSize); + Map> subscribedResourcesMetadata = + awaitSubscribedResourcesMetadata(); + assertThat(subscribedResourcesMetadata.get(LDS)).hasSize(ldsSize); + assertThat(subscribedResourcesMetadata.get(CDS)).hasSize(cdsSize); + assertThat(subscribedResourcesMetadata.get(RDS)).hasSize(rdsSize); + assertThat(subscribedResourcesMetadata.get(EDS)).hasSize(edsSize); + } + + private Map> awaitSubscribedResourcesMetadata() { + try { + return xdsClient.getSubscribedResourcesMetadataSnapshot().get(20, TimeUnit.SECONDS); + } catch (Exception e) { + if (e instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } + throw new AssertionError(e); + } } /** Verify the resource requested, but not updated. */ @@ -434,22 +447,20 @@ private void verifyResourceMetadataNacked( private ResourceMetadata verifyResourceMetadata( ResourceType type, String resourceName, Any rawResource, ResourceMetadataStatus status, String versionInfo, long updateTimeNanos, boolean hasErrorState) { - ResourceMetadata resourceMetadata = - xdsClient.getSubscribedResourcesMetadata(type).get(resourceName); - assertThat(resourceMetadata).isNotNull(); + ResourceMetadata metadata = awaitSubscribedResourcesMetadata().get(type).get(resourceName); + assertThat(metadata).isNotNull(); String name = type.toString() + " resource '" + resourceName + "' metadata field "; - assertWithMessage(name + "status").that(resourceMetadata.getStatus()).isEqualTo(status); - assertWithMessage(name + "version").that(resourceMetadata.getVersion()).isEqualTo(versionInfo); - assertWithMessage(name + "rawResource").that(resourceMetadata.getRawResource()) - .isEqualTo(rawResource); - assertWithMessage(name + "updateTimeNanos").that(resourceMetadata.getUpdateTimeNanos()) + assertWithMessage(name + "status").that(metadata.getStatus()).isEqualTo(status); + assertWithMessage(name + "version").that(metadata.getVersion()).isEqualTo(versionInfo); + assertWithMessage(name + "rawResource").that(metadata.getRawResource()).isEqualTo(rawResource); + assertWithMessage(name + "updateTimeNanos").that(metadata.getUpdateTimeNanos()) .isEqualTo(updateTimeNanos); if (hasErrorState) { - assertWithMessage(name + "errorState").that(resourceMetadata.getErrorState()).isNotNull(); + assertWithMessage(name + "errorState").that(metadata.getErrorState()).isNotNull(); } else { - assertWithMessage(name + "errorState").that(resourceMetadata.getErrorState()).isNull(); + assertWithMessage(name + "errorState").that(metadata.getErrorState()).isNull(); } - return resourceMetadata; + return metadata; } /** diff --git a/xds/src/test/java/io/grpc/xds/CsdsServiceTest.java b/xds/src/test/java/io/grpc/xds/CsdsServiceTest.java index 7c6abee2835..0d929939109 100644 --- a/xds/src/test/java/io/grpc/xds/CsdsServiceTest.java +++ b/xds/src/test/java/io/grpc/xds/CsdsServiceTest.java @@ -23,7 +23,11 @@ import static io.grpc.xds.AbstractXdsClient.ResourceType.RDS; import static org.junit.Assert.fail; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.Any; import io.envoyproxy.envoy.admin.v3.ClientResourceStatus; import io.envoyproxy.envoy.config.cluster.v3.Cluster; @@ -46,13 +50,15 @@ import io.grpc.stub.StreamObserver; import io.grpc.testing.GrpcServerRule; import io.grpc.xds.AbstractXdsClient.ResourceType; +import io.grpc.xds.Bootstrapper.BootstrapInfo; +import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.XdsClient.ResourceMetadata; import io.grpc.xds.XdsClient.ResourceMetadata.ResourceMetadataStatus; import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory; -import java.util.Arrays; import java.util.EnumMap; import java.util.List; import java.util.Map; +import java.util.concurrent.Callable; import javax.annotation.Nullable; import org.junit.Before; import org.junit.Rule; @@ -69,22 +75,12 @@ public class CsdsServiceTest { "projects/42/networks/default/nodes/5c85b298-6f5b-4722-b74a-f7d1f0ccf5ad"; private static final EnvoyProtoData.Node BOOTSTRAP_NODE = EnvoyProtoData.Node.newBuilder().setId(NODE_ID).build(); - private static final XdsClient XDS_CLIENT_NO_RESOURCES = new XdsClient() { - @Override - Bootstrapper.BootstrapInfo getBootstrapInfo() { - return Bootstrapper.BootstrapInfo.builder() - .servers(Arrays.asList( - Bootstrapper.ServerInfo.create( - SERVER_URI, InsecureChannelCredentials.create(), false))) - .node(BOOTSTRAP_NODE) - .build(); - } - - @Override - Map getSubscribedResourcesMetadata(ResourceType type) { - return ImmutableMap.of(); - } - }; + private static final BootstrapInfo BOOTSTRAP_INFO = BootstrapInfo.builder() + .servers(ImmutableList.of( + ServerInfo.create(SERVER_URI, InsecureChannelCredentials.create(), true))) + .node(BOOTSTRAP_NODE) + .build(); + private static final XdsClient XDS_CLIENT_NO_RESOURCES = new FakeXdsClient(); @RunWith(JUnit4.class) public static class ServiceTests { @@ -125,13 +121,15 @@ public void fetchClientConfig_invalidArgument() { } } - /** Unexpected exceptions translated to internal error status. */ + /** Unexpected exceptions translated to Status.Code.INTERNAL. */ @Test public void fetchClientConfig_unexpectedException() { - XdsClient throwingXdsClient = new XdsClient() { + XdsClient throwingXdsClient = new FakeXdsClient() { @Override - Map getSubscribedResourcesMetadata(ResourceType type) { - throw new IllegalArgumentException("IllegalArgumentException"); + ListenableFuture>> + getSubscribedResourcesMetadataSnapshot() { + return Futures.immediateFailedFuture( + new IllegalArgumentException("IllegalArgumentException")); } }; grpcServerRule.getServiceRegistry() @@ -143,6 +141,38 @@ Map getSubscribedResourcesMetadata(ResourceType type) } catch (StatusRuntimeException e) { assertThat(e.getStatus().getCode()).isEqualTo(Code.INTERNAL); assertThat(e.getStatus().getDescription()).isEqualTo("Unexpected internal error"); + // Cause is not propagated. + } + } + + /** Interrupted exceptions translated to Status.Code.ABORTED. */ + @Test + public void fetchClientConfig_interruptedException() { + XdsClient throwingXdsClient = new FakeXdsClient() { + @Override + ListenableFuture>> + getSubscribedResourcesMetadataSnapshot() { + return Futures.submit( + new Callable>>() { + @Override + public Map> call() { + Thread.currentThread().interrupt(); + return null; + } + }, MoreExecutors.directExecutor()); + } + }; + grpcServerRule.getServiceRegistry() + .addService(new CsdsService(new FakeXdsClientPoolFactory(throwingXdsClient))); + + try { + ClientStatusResponse response = csdsStub.fetchClientStatus(REQUEST); + fail("Should've failed, got response: " + response); + } catch (StatusRuntimeException e) { + assertThat(e.getStatus().getCode()).isEqualTo(Code.ABORTED); + assertThat(e.getStatus().getDescription()).isEqualTo("Thread interrupted"); + // Clean the test thread interrupt. + assertThat(Thread.interrupted()).isEqualTo(true); } } @@ -289,34 +319,19 @@ public void metadataStatusToClientStatus() { } @Test - public void getClientConfigForXdsClient_subscribedResourcesToGenericXdsConfig() { - ClientConfig clientConfig = CsdsService.getClientConfigForXdsClient(new XdsClient() { + public void getClientConfigForXdsClient_subscribedResourcesToGenericXdsConfig() + throws InterruptedException { + ClientConfig clientConfig = CsdsService.getClientConfigForXdsClient(new FakeXdsClient() { @Override - Bootstrapper.BootstrapInfo getBootstrapInfo() { - return Bootstrapper.BootstrapInfo.builder() - .servers(Arrays.asList( - Bootstrapper.ServerInfo.create( - SERVER_URI, InsecureChannelCredentials.create(), false))) - .node(BOOTSTRAP_NODE) + protected Map> + getSubscribedResourcesMetadata() { + return new ImmutableMap.Builder>() + .put(LDS, ImmutableMap.of("subscribedResourceName.LDS", METADATA_ACKED_LDS)) + .put(RDS, ImmutableMap.of("subscribedResourceName.RDS", METADATA_ACKED_RDS)) + .put(CDS, ImmutableMap.of("subscribedResourceName.CDS", METADATA_ACKED_CDS)) + .put(EDS, ImmutableMap.of("subscribedResourceName.EDS", METADATA_ACKED_EDS)) .build(); } - - @Override - Map getSubscribedResourcesMetadata(ResourceType type) { - switch (type) { - case LDS: - return ImmutableMap.of("subscribedResourceName." + type.name(), METADATA_ACKED_LDS); - case RDS: - return ImmutableMap.of("subscribedResourceName." + type.name(), METADATA_ACKED_RDS); - case CDS: - return ImmutableMap.of("subscribedResourceName." + type.name(), METADATA_ACKED_CDS); - case EDS: - return ImmutableMap.of("subscribedResourceName." + type.name(), METADATA_ACKED_EDS); - case UNKNOWN: - default: - throw new AssertionError("Unexpected resource name"); - } - } }); verifyClientConfigNode(clientConfig); @@ -355,7 +370,7 @@ Map getSubscribedResourcesMetadata(ResourceType type) } @Test - public void getClientConfigForXdsClient_noSubscribedResources() { + public void getClientConfigForXdsClient_noSubscribedResources() throws InterruptedException { ClientConfig clientConfig = CsdsService.getClientConfigForXdsClient(XDS_CLIENT_NO_RESOURCES); verifyClientConfigNode(clientConfig); verifyClientConfigNoResources(clientConfig); @@ -395,6 +410,23 @@ private static EnumMap mapConfigDumps(ClientConf return xdsConfigMap; } + private static class FakeXdsClient extends XdsClient { + protected Map> getSubscribedResourcesMetadata() { + return ImmutableMap.of(); + } + + @Override + ListenableFuture>> + getSubscribedResourcesMetadataSnapshot() { + return Futures.immediateFuture(getSubscribedResourcesMetadata()); + } + + @Override + BootstrapInfo getBootstrapInfo() { + return BOOTSTRAP_INFO; + } + } + private static class FakeXdsClientPoolFactory implements XdsClientPoolFactory { @Nullable private final XdsClient xdsClient; @@ -424,7 +456,7 @@ public void setBootstrapOverride(Map bootstrap) { } @Override - public ObjectPool getOrCreate() throws XdsInitializationException { + public ObjectPool getOrCreate() { throw new UnsupportedOperationException("Should not be called"); } } From 58a7ace6ac2041d3c9989a33fd46188ed56fed6a Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Wed, 12 Jan 2022 12:06:27 -0800 Subject: [PATCH 02/68] Bump ErrorProne to 2.10.0 Previous versions of error prone were incompatible with Java 17 javac. In grpc-api, errorprone is now api dependency because it is on a public API. I was happy to see that Gradle failed the build without the dep change, although the error message wasn't super clear as to the cause. It seems that previously -PerrorProne=false did nothing. I'm guessing this is due to a behavior change of Gradle at some point. Swapping to using the project does build without errorProne, although the build fails with Javac complaining certain classes are unavailable. It's unclear why. It doesn't seem to be caused by the error-prone plugin. I've left it failing as a pre-existing issue. ClientCalls/ServerCalls had Deprecated removed from some methods because they were only deprecated in the internal class, not the API. And with Deprecated, InlineMeSuggester complained. I'm finding InlineMeSuggester to be overzealous, complaining about package-private methods. In time we may figure out how to use it better, or we may request changes to the checker in error-prone. --- .../alts/internal/ChannelCrypterNetty.java | 4 ++-- .../java/io/grpc/alts/internal/TsiTest.java | 4 ++-- api/build.gradle | 4 ++-- api/src/main/java/io/grpc/Attributes.java | 14 ++++++++------ api/src/main/java/io/grpc/NameResolver.java | 5 +++++ build.gradle | 18 ++++++++---------- context/src/main/java/io/grpc/Context.java | 2 +- .../internal/AbstractServerImplBuilder.java | 2 ++ .../internal/JndiResourceResolverFactory.java | 2 +- .../integration/NettyFlowControlTest.java | 14 -------------- .../integration/TrafficControlProxy.java | 2 +- .../grpc/netty/GrpcHttp2ConnectionHandler.java | 1 + .../io/grpc/netty/NettyChannelBuilder.java | 2 ++ .../java/io/grpc/netty/NettyServerBuilder.java | 3 +++ .../services/BinaryLogProviderImpl.java | 1 + .../main/java/io/grpc/stub/ClientCalls.java | 1 - .../main/java/io/grpc/stub/ServerCalls.java | 1 - .../java/io/grpc/xds/XdsServerWrapperTest.java | 6 ++---- 18 files changed, 41 insertions(+), 45 deletions(-) diff --git a/alts/src/main/java/io/grpc/alts/internal/ChannelCrypterNetty.java b/alts/src/main/java/io/grpc/alts/internal/ChannelCrypterNetty.java index 4164560e7a0..e2e7d4046fb 100644 --- a/alts/src/main/java/io/grpc/alts/internal/ChannelCrypterNetty.java +++ b/alts/src/main/java/io/grpc/alts/internal/ChannelCrypterNetty.java @@ -21,8 +21,8 @@ import java.util.List; /** - * A @{code ChannelCrypterNetty} performs stateful encryption and decryption of independent input - * and output streams. Both decrypt and encrypt gather their input from a list of Netty @{link + * A {@code ChannelCrypterNetty} performs stateful encryption and decryption of independent input + * and output streams. Both decrypt and encrypt gather their input from a list of Netty {@link * ByteBuf} instances. * *

Note that we provide implementations of this interface that provide integrity only and diff --git a/alts/src/test/java/io/grpc/alts/internal/TsiTest.java b/alts/src/test/java/io/grpc/alts/internal/TsiTest.java index f677a44a754..f75fb7fcee0 100644 --- a/alts/src/test/java/io/grpc/alts/internal/TsiTest.java +++ b/alts/src/test/java/io/grpc/alts/internal/TsiTest.java @@ -34,13 +34,13 @@ import java.util.List; import javax.crypto.AEADBadTagException; -/** Utility class that provides tests for implementations of @{link TsiHandshaker}. */ +/** Utility class that provides tests for implementations of {@link TsiHandshaker}. */ public final class TsiTest { private static final String DECRYPTION_FAILURE_RE = "Tag mismatch!|BAD_DECRYPT"; private TsiTest() {} - /** A @{code TsiHandshaker} pair for running tests. */ + /** A {@code TsiHandshaker} pair for running tests. */ public static class Handshakers { private final TsiHandshaker client; private final TsiHandshaker server; diff --git a/api/build.gradle b/api/build.gradle index 1348e49ad60..3c7ff8221ee 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -12,9 +12,9 @@ evaluationDependsOn(project(':grpc-context').path) dependencies { api project(':grpc-context'), - libraries.jsr305 - implementation libraries.guava, + libraries.jsr305, libraries.errorprone + implementation libraries.guava testImplementation project(':grpc-context').sourceSets.test.output, project(':grpc-testing'), diff --git a/api/src/main/java/io/grpc/Attributes.java b/api/src/main/java/io/grpc/Attributes.java index b70cc2bc8c3..26c2a72f909 100644 --- a/api/src/main/java/io/grpc/Attributes.java +++ b/api/src/main/java/io/grpc/Attributes.java @@ -46,11 +46,13 @@ @Immutable public final class Attributes { - private final Map, Object> data; + private final IdentityHashMap, Object> data; - public static final Attributes EMPTY = new Attributes(Collections., Object>emptyMap()); + private static final IdentityHashMap, Object> EMPTY_MAP = + new IdentityHashMap, Object>(); + public static final Attributes EMPTY = new Attributes(EMPTY_MAP); - private Attributes(Map, Object> data) { + private Attributes(IdentityHashMap, Object> data) { assert data != null; this.data = data; } @@ -212,14 +214,14 @@ public int hashCode() { */ public static final class Builder { private Attributes base; - private Map, Object> newdata; + private IdentityHashMap, Object> newdata; private Builder(Attributes base) { assert base != null; this.base = base; } - private Map, Object> data(int size) { + private IdentityHashMap, Object> data(int size) { if (newdata == null) { newdata = new IdentityHashMap<>(size); } @@ -241,7 +243,7 @@ public Builder set(Key key, T value) { @ExperimentalApi("https://github.com/grpc/grpc-java/issues/5777") public Builder discard(Key key) { if (base.data.containsKey(key)) { - Map, Object> newBaseData = new IdentityHashMap<>(base.data); + IdentityHashMap, Object> newBaseData = new IdentityHashMap<>(base.data); newBaseData.remove(key); base = new Attributes(newBaseData); } diff --git a/api/src/main/java/io/grpc/NameResolver.java b/api/src/main/java/io/grpc/NameResolver.java index f4c05aa6a64..78b35a14e38 100644 --- a/api/src/main/java/io/grpc/NameResolver.java +++ b/api/src/main/java/io/grpc/NameResolver.java @@ -21,6 +21,7 @@ import com.google.common.base.MoreObjects; import com.google.common.base.Objects; +import com.google.errorprone.annotations.InlineMe; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -204,6 +205,10 @@ public abstract static class Listener2 implements Listener { */ @Override @Deprecated + @InlineMe( + replacement = "this.onResult(ResolutionResult.newBuilder().setAddresses(servers)" + + ".setAttributes(attributes).build())", + imports = "io.grpc.NameResolver.ResolutionResult") public final void onAddresses( List servers, @ResolutionResultAttr Attributes attributes) { // TODO(jihuncho) need to promote Listener2 if we want to use ConfigOrError diff --git a/build.gradle b/build.gradle index 149ade4ae02..9a649b2886f 100644 --- a/build.gradle +++ b/build.gradle @@ -151,7 +151,7 @@ subprojects { animalsniffer_annotations: "org.codehaus.mojo:animal-sniffer-annotations:1.19", autovalue: "com.google.auto.value:auto-value:${autovalueVersion}", autovalue_annotation: "com.google.auto.value:auto-value-annotations:${autovalueVersion}", - errorprone: "com.google.errorprone:error_prone_annotations:2.9.0", + errorprone: "com.google.errorprone:error_prone_annotations:2.10.0", cronet_api: 'org.chromium.net:cronet-api:92.4515.131', cronet_embedded: 'org.chromium.net:cronet-embedded:92.4515.131', gson: "com.google.code.gson:gson:2.8.9", @@ -239,19 +239,15 @@ subprojects { } } - if (rootProject.properties.get('errorProne', true)) { + if (!project.hasProperty('errorProne') || errorProne.toBoolean()) { dependencies { - errorprone 'com.google.errorprone:error_prone_core:2.4.0' + errorprone 'com.google.errorprone:error_prone_core:2.10.0' errorproneJavac 'com.google.errorprone:javac:9+181-r4173-1' } } else { // Disable Error Prone - allprojects { - afterEvaluate { project -> - project.tasks.withType(JavaCompile) { - options.errorprone.enabled = false - } - } + tasks.withType(JavaCompile) { + options.errorprone.enabled = false } } @@ -304,7 +300,7 @@ subprojects { maxHeapSize = '1500m' } - if (rootProject.properties.get('errorProne', true)) { + if (!project.hasProperty('errorProne') || errorProne.toBoolean()) { dependencies { annotationProcessor 'com.google.guava:guava-beta-checker:1.0' } @@ -315,6 +311,7 @@ subprojects { options.errorprone.check("UnnecessaryAnonymousClass", CheckSeverity.OFF) // This project targets Java 7 (no time.Duration class) options.errorprone.check("PreferJavaTimeOverload", CheckSeverity.OFF) + options.errorprone.check("JavaUtilDate", CheckSeverity.OFF) // The warning fails to provide a source location options.errorprone.check("MissingSummary", CheckSeverity.OFF) } @@ -323,6 +320,7 @@ subprojects { options.errorprone.check("JdkObsolete", CheckSeverity.OFF) options.errorprone.check("UnnecessaryAnonymousClass", CheckSeverity.OFF) options.errorprone.check("PreferJavaTimeOverload", CheckSeverity.OFF) + options.errorprone.check("JavaUtilDate", CheckSeverity.OFF) } plugins.withId("ru.vyarus.animalsniffer") { diff --git a/context/src/main/java/io/grpc/Context.java b/context/src/main/java/io/grpc/Context.java index ad47ca9cb4d..41d3a5c94a6 100644 --- a/context/src/main/java/io/grpc/Context.java +++ b/context/src/main/java/io/grpc/Context.java @@ -1042,7 +1042,7 @@ public Context doAttach(Context toAttach) { * Implements {@link io.grpc.Context#current}. * *

Caution: {@link Context} interprets a return value of {@code null} to mean the same - * thing as {@code Context{@link #ROOT}}. + * thing as {@link Context#ROOT}. * *

See also {@link #doAttach(Context)}. * diff --git a/core/src/main/java/io/grpc/internal/AbstractServerImplBuilder.java b/core/src/main/java/io/grpc/internal/AbstractServerImplBuilder.java index d20c60cd446..c1268cfdb09 100644 --- a/core/src/main/java/io/grpc/internal/AbstractServerImplBuilder.java +++ b/core/src/main/java/io/grpc/internal/AbstractServerImplBuilder.java @@ -17,6 +17,7 @@ package io.grpc.internal; import com.google.common.base.MoreObjects; +import com.google.errorprone.annotations.DoNotCall; import io.grpc.BinaryLog; import io.grpc.BindableService; import io.grpc.CompressorRegistry; @@ -53,6 +54,7 @@ protected AbstractServerImplBuilder() {} /** * This method serves to force sub classes to "hide" this static factory. */ + @DoNotCall("Unsupported") public static ServerBuilder forPort(int port) { throw new UnsupportedOperationException("Subclass failed to hide static factory"); } diff --git a/core/src/main/java/io/grpc/internal/JndiResourceResolverFactory.java b/core/src/main/java/io/grpc/internal/JndiResourceResolverFactory.java index a13c9fe4665..22e08de9cf4 100644 --- a/core/src/main/java/io/grpc/internal/JndiResourceResolverFactory.java +++ b/core/src/main/java/io/grpc/internal/JndiResourceResolverFactory.java @@ -140,7 +140,7 @@ public List resolveSrv(String host) throws Exception { Level level = Level.WARNING; for (String rawSrv : rawSrvRecords) { try { - String[] parts = whitespace.split(rawSrv); + String[] parts = whitespace.split(rawSrv, 5); Verify.verify(parts.length == 4, "Bad SRV Record: %s", rawSrv); // SRV requires the host name to be absolute if (!parts[3].endsWith(".")) { diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/NettyFlowControlTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/NettyFlowControlTest.java index 4d7c8bdbca7..c86bd8070a0 100644 --- a/interop-testing/src/test/java/io/grpc/testing/integration/NettyFlowControlTest.java +++ b/interop-testing/src/test/java/io/grpc/testing/integration/NettyFlowControlTest.java @@ -39,17 +39,13 @@ import io.netty.channel.ChannelHandler; import io.netty.handler.codec.http2.Http2Stream; import io.netty.util.AsciiString; -import io.netty.util.concurrent.DefaultThreadFactory; import java.io.IOException; import java.net.InetSocketAddress; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.junit.After; -import org.junit.AfterClass; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -80,16 +76,6 @@ public class NettyFlowControlTest { private int proxyPort; private int serverPort; - private static final ThreadPoolExecutor executor = - new ThreadPoolExecutor(1, 10, 1, TimeUnit.SECONDS, new LinkedBlockingQueue(), - new DefaultThreadFactory("flowcontrol-test-pool", true)); - - - @AfterClass - public static void shutDownTests() { - executor.shutdown(); - } - @Before public void initTest() { startServer(REGULAR_WINDOW); diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/TrafficControlProxy.java b/interop-testing/src/test/java/io/grpc/testing/integration/TrafficControlProxy.java index 19f22cb03f1..a93d51628e4 100644 --- a/interop-testing/src/test/java/io/grpc/testing/integration/TrafficControlProxy.java +++ b/interop-testing/src/test/java/io/grpc/testing/integration/TrafficControlProxy.java @@ -51,7 +51,7 @@ public final class TrafficControlProxy { private Socket serverSock; private Socket clientSock; private final ThreadPoolExecutor executor = - new ThreadPoolExecutor(5, 10, 1, TimeUnit.SECONDS, new LinkedBlockingQueue(), + new ThreadPoolExecutor(5, 5, 1, TimeUnit.SECONDS, new LinkedBlockingQueue(), new DefaultThreadFactory("proxy-pool", true)); /** diff --git a/netty/src/main/java/io/grpc/netty/GrpcHttp2ConnectionHandler.java b/netty/src/main/java/io/grpc/netty/GrpcHttp2ConnectionHandler.java index 459c313eded..25f4f9232cf 100644 --- a/netty/src/main/java/io/grpc/netty/GrpcHttp2ConnectionHandler.java +++ b/netty/src/main/java/io/grpc/netty/GrpcHttp2ConnectionHandler.java @@ -58,6 +58,7 @@ protected GrpcHttp2ConnectionHandler( * @deprecated Use the two argument method instead. */ @Deprecated + @SuppressWarnings("InlineMeSuggester") // the caller should consider providing securityInfo public void handleProtocolNegotiationCompleted(Attributes attrs) { handleProtocolNegotiationCompleted(attrs, /*securityInfo=*/ null); } diff --git a/netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java b/netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java index 17801afa382..ca029fe7cfc 100644 --- a/netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java +++ b/netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java @@ -24,6 +24,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.InlineMe; import io.grpc.Attributes; import io.grpc.CallCredentials; import io.grpc.ChannelCredentials; @@ -393,6 +394,7 @@ public NettyChannelBuilder flowControlWindow(int flowControlWindow) { * @deprecated Use {@link #maxInboundMetadataSize} instead */ @Deprecated + @InlineMe(replacement = "this.maxInboundMetadataSize(maxHeaderListSize)") public NettyChannelBuilder maxHeaderListSize(int maxHeaderListSize) { return maxInboundMetadataSize(maxHeaderListSize); } diff --git a/netty/src/main/java/io/grpc/netty/NettyServerBuilder.java b/netty/src/main/java/io/grpc/netty/NettyServerBuilder.java index 9b67cd21f19..ff5553eb116 100644 --- a/netty/src/main/java/io/grpc/netty/NettyServerBuilder.java +++ b/netty/src/main/java/io/grpc/netty/NettyServerBuilder.java @@ -26,6 +26,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.InlineMe; import io.grpc.Attributes; import io.grpc.ExperimentalApi; import io.grpc.Internal; @@ -437,6 +438,7 @@ public NettyServerBuilder flowControlWindow(int flowControlWindow) { * future release. */ @Deprecated + @InlineMe(replacement = "this.maxInboundMessageSize(maxMessageSize)") public NettyServerBuilder maxMessageSize(int maxMessageSize) { return maxInboundMessageSize(maxMessageSize); } @@ -458,6 +460,7 @@ public NettyServerBuilder maxInboundMessageSize(int bytes) { * @deprecated Use {@link #maxInboundMetadataSize} instead */ @Deprecated + @InlineMe(replacement = "this.maxInboundMetadataSize(maxHeaderListSize)") public NettyServerBuilder maxHeaderListSize(int maxHeaderListSize) { return maxInboundMetadataSize(maxHeaderListSize); } diff --git a/services/src/main/java/io/grpc/protobuf/services/BinaryLogProviderImpl.java b/services/src/main/java/io/grpc/protobuf/services/BinaryLogProviderImpl.java index f68d67b27b7..896d92bcd91 100644 --- a/services/src/main/java/io/grpc/protobuf/services/BinaryLogProviderImpl.java +++ b/services/src/main/java/io/grpc/protobuf/services/BinaryLogProviderImpl.java @@ -42,6 +42,7 @@ public BinaryLogProviderImpl() throws IOException { * Deprecated and will be removed in a future version of gRPC. */ @Deprecated + @SuppressWarnings("InlineMeSuggester") // Only called internally; don't care public BinaryLogProviderImpl(BinaryLogSink sink) throws IOException { this(sink, System.getenv("GRPC_BINARY_LOG_CONFIG")); } diff --git a/stub/src/main/java/io/grpc/stub/ClientCalls.java b/stub/src/main/java/io/grpc/stub/ClientCalls.java index 7456948ddf1..04ed83f083a 100644 --- a/stub/src/main/java/io/grpc/stub/ClientCalls.java +++ b/stub/src/main/java/io/grpc/stub/ClientCalls.java @@ -391,7 +391,6 @@ public void setOnReadyHandler(Runnable onReadyHandler) { this.onReadyHandler = onReadyHandler; } - @Deprecated @Override public void disableAutoInboundFlowControl() { disableAutoRequestWithInitial(1); diff --git a/stub/src/main/java/io/grpc/stub/ServerCalls.java b/stub/src/main/java/io/grpc/stub/ServerCalls.java index 09f86d0364c..83954af9670 100644 --- a/stub/src/main/java/io/grpc/stub/ServerCalls.java +++ b/stub/src/main/java/io/grpc/stub/ServerCalls.java @@ -422,7 +422,6 @@ public void setOnCancelHandler(Runnable onCancelHandler) { this.onCancelHandler = onCancelHandler; } - @Deprecated @Override public void disableAutoInboundFlowControl() { disableAutoRequest(); diff --git a/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java b/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java index f011b789da9..ac7f41e65a9 100644 --- a/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java @@ -99,8 +99,6 @@ public class XdsServerWrapperTest { @Mock private Server mockServer; @Mock - private static TlsContextManager tlsContextManager; - @Mock private XdsServingStatusListener listener; private FilterChainSelectorManager selectorManager = new FilterChainSelectorManager(); @@ -441,7 +439,7 @@ public void run() { 0L, Collections.singletonList(virtualHost), new ArrayList()); EnvoyServerProtoData.FilterChain filterChain = new EnvoyServerProtoData.FilterChain( "filter-chain-foo", createMatch(), httpConnectionManager, createTls(), - tlsContextManager); + mock(TlsContextManager.class)); xdsClient.deliverLdsUpdate(Collections.singletonList(filterChain), null); start.get(5000, TimeUnit.MILLISECONDS); assertThat(ldsWatched).isEqualTo("grpc/server?udpa.resource.listening_address=0.0.0.0:1"); @@ -1190,7 +1188,7 @@ public ServerCall.Listener interceptCall(ServerCall Date: Wed, 12 Jan 2022 12:08:22 -0800 Subject: [PATCH 03/68] Start 1.45.0 development cycle (#8825) --- build.gradle | 2 +- .../src/test/golden/TestDeprecatedService.java.txt | 2 +- compiler/src/test/golden/TestService.java.txt | 2 +- .../src/testLite/golden/TestDeprecatedService.java.txt | 2 +- compiler/src/testLite/golden/TestService.java.txt | 2 +- core/src/main/java/io/grpc/internal/GrpcUtil.java | 2 +- examples/android/clientcache/app/build.gradle | 10 +++++----- examples/android/helloworld/app/build.gradle | 8 ++++---- examples/android/routeguide/app/build.gradle | 8 ++++---- examples/android/strictmode/app/build.gradle | 8 ++++---- examples/build.gradle | 2 +- examples/example-alts/build.gradle | 2 +- examples/example-gauth/build.gradle | 2 +- examples/example-gauth/pom.xml | 4 ++-- examples/example-hostname/build.gradle | 2 +- examples/example-hostname/pom.xml | 4 ++-- examples/example-jwt-auth/build.gradle | 2 +- examples/example-jwt-auth/pom.xml | 4 ++-- examples/example-tls/build.gradle | 2 +- examples/example-tls/pom.xml | 4 ++-- examples/example-xds/build.gradle | 2 +- examples/pom.xml | 4 ++-- 22 files changed, 40 insertions(+), 40 deletions(-) diff --git a/build.gradle b/build.gradle index 9a649b2886f..e569c96a0cc 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ subprojects { apply plugin: "net.ltgt.errorprone" group = "io.grpc" - version = "1.44.0-SNAPSHOT" // CURRENT_GRPC_VERSION + version = "1.45.0-SNAPSHOT" // CURRENT_GRPC_VERSION repositories { maven { // The google mirror is less flaky than mavenCentral() diff --git a/compiler/src/test/golden/TestDeprecatedService.java.txt b/compiler/src/test/golden/TestDeprecatedService.java.txt index 68d8e1172a3..bae773cf5cb 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.44.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.45.0-SNAPSHOT)", comments = "Source: grpc/testing/compiler/test.proto") @io.grpc.stub.annotations.GrpcGenerated @java.lang.Deprecated diff --git a/compiler/src/test/golden/TestService.java.txt b/compiler/src/test/golden/TestService.java.txt index 3c6cdc82b51..ac5f7b1e84a 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.44.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.45.0-SNAPSHOT)", comments = "Source: grpc/testing/compiler/test.proto") @io.grpc.stub.annotations.GrpcGenerated public final class TestServiceGrpc { diff --git a/compiler/src/testLite/golden/TestDeprecatedService.java.txt b/compiler/src/testLite/golden/TestDeprecatedService.java.txt index 8dcafe51f24..70b348a33b0 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.44.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.45.0-SNAPSHOT)", comments = "Source: grpc/testing/compiler/test.proto") @io.grpc.stub.annotations.GrpcGenerated @java.lang.Deprecated diff --git a/compiler/src/testLite/golden/TestService.java.txt b/compiler/src/testLite/golden/TestService.java.txt index 60ac085f920..2ca755211f3 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.44.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.45.0-SNAPSHOT)", comments = "Source: grpc/testing/compiler/test.proto") @io.grpc.stub.annotations.GrpcGenerated public final class TestServiceGrpc { diff --git a/core/src/main/java/io/grpc/internal/GrpcUtil.java b/core/src/main/java/io/grpc/internal/GrpcUtil.java index bce1a0f1503..ee54a1d645b 100644 --- a/core/src/main/java/io/grpc/internal/GrpcUtil.java +++ b/core/src/main/java/io/grpc/internal/GrpcUtil.java @@ -204,7 +204,7 @@ public byte[] parseAsciiString(byte[] serialized) { public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults(); - private static final String IMPLEMENTATION_VERSION = "1.44.0-SNAPSHOT"; // CURRENT_GRPC_VERSION + private static final String IMPLEMENTATION_VERSION = "1.45.0-SNAPSHOT"; // CURRENT_GRPC_VERSION /** * The default timeout in nanos for a keepalive ping request. diff --git a/examples/android/clientcache/app/build.gradle b/examples/android/clientcache/app/build.gradle index a71611df7a5..65803334c58 100644 --- a/examples/android/clientcache/app/build.gradle +++ b/examples/android/clientcache/app/build.gradle @@ -34,7 +34,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.19.2' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.44.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -54,12 +54,12 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.0.2' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.44.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.44.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.44.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' testImplementation 'junit:junit:4.12' testImplementation 'com.google.truth:truth:1.0.1' - testImplementation 'io.grpc:grpc-testing:1.44.0-SNAPSHOT' // CURRENT_GRPC_VERSION + testImplementation 'io.grpc:grpc-testing:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION } diff --git a/examples/android/helloworld/app/build.gradle b/examples/android/helloworld/app/build.gradle index cc84e9bcc5c..12b6899d881 100644 --- a/examples/android/helloworld/app/build.gradle +++ b/examples/android/helloworld/app/build.gradle @@ -32,7 +32,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.19.2' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.44.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -52,8 +52,8 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.0.2' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.44.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.44.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.44.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/android/routeguide/app/build.gradle b/examples/android/routeguide/app/build.gradle index 5a43416a5fb..f7e9c1a4bb6 100644 --- a/examples/android/routeguide/app/build.gradle +++ b/examples/android/routeguide/app/build.gradle @@ -32,7 +32,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.19.2' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.44.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -52,8 +52,8 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.0.2' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.44.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.44.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.44.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/android/strictmode/app/build.gradle b/examples/android/strictmode/app/build.gradle index ad74597ed27..9638eb38a08 100644 --- a/examples/android/strictmode/app/build.gradle +++ b/examples/android/strictmode/app/build.gradle @@ -33,7 +33,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.19.2' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.44.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -53,8 +53,8 @@ dependencies { implementation 'com.android.support:appcompat-v7:28.0.0' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.44.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.44.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.44.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/build.gradle b/examples/build.gradle index 45648d4af85..250269a36e2 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -22,7 +22,7 @@ targetCompatibility = 1.7 // 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.44.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.19.2' def protocVersion = protobufVersion diff --git a/examples/example-alts/build.gradle b/examples/example-alts/build.gradle index de4e11fd916..96ac66d485c 100644 --- a/examples/example-alts/build.gradle +++ b/examples/example-alts/build.gradle @@ -23,7 +23,7 @@ targetCompatibility = 1.7 // 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.44.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.19.2' dependencies { diff --git a/examples/example-gauth/build.gradle b/examples/example-gauth/build.gradle index fdde6e01aff..a4cd2b2fc19 100644 --- a/examples/example-gauth/build.gradle +++ b/examples/example-gauth/build.gradle @@ -23,7 +23,7 @@ targetCompatibility = 1.7 // 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.44.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.19.2' def protocVersion = protobufVersion diff --git a/examples/example-gauth/pom.xml b/examples/example-gauth/pom.xml index 2ccd6654757..5d56b44ab1e 100644 --- a/examples/example-gauth/pom.xml +++ b/examples/example-gauth/pom.xml @@ -6,13 +6,13 @@ jar - 1.44.0-SNAPSHOT + 1.45.0-SNAPSHOT example-gauth https://github.com/grpc/grpc-java UTF-8 - 1.44.0-SNAPSHOT + 1.45.0-SNAPSHOT 3.19.2 1.7 diff --git a/examples/example-hostname/build.gradle b/examples/example-hostname/build.gradle index f40528e56ea..288cb9585fb 100644 --- a/examples/example-hostname/build.gradle +++ b/examples/example-hostname/build.gradle @@ -21,7 +21,7 @@ targetCompatibility = 1.7 // 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.44.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.19.2' dependencies { diff --git a/examples/example-hostname/pom.xml b/examples/example-hostname/pom.xml index 31445d405de..53d399e2460 100644 --- a/examples/example-hostname/pom.xml +++ b/examples/example-hostname/pom.xml @@ -6,13 +6,13 @@ jar - 1.44.0-SNAPSHOT + 1.45.0-SNAPSHOT example-hostname https://github.com/grpc/grpc-java UTF-8 - 1.44.0-SNAPSHOT + 1.45.0-SNAPSHOT 3.19.2 1.7 diff --git a/examples/example-jwt-auth/build.gradle b/examples/example-jwt-auth/build.gradle index 8f962a2ffc8..5b26c85f0d8 100644 --- a/examples/example-jwt-auth/build.gradle +++ b/examples/example-jwt-auth/build.gradle @@ -22,7 +22,7 @@ targetCompatibility = 1.7 // 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.44.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.19.2' def protocVersion = protobufVersion diff --git a/examples/example-jwt-auth/pom.xml b/examples/example-jwt-auth/pom.xml index f026be38467..d78e4e6a630 100644 --- a/examples/example-jwt-auth/pom.xml +++ b/examples/example-jwt-auth/pom.xml @@ -7,13 +7,13 @@ jar - 1.44.0-SNAPSHOT + 1.45.0-SNAPSHOT example-jwt-auth https://github.com/grpc/grpc-java UTF-8 - 1.44.0-SNAPSHOT + 1.45.0-SNAPSHOT 3.19.2 3.19.2 diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index b3b8372e1c4..4d126452f4e 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -23,7 +23,7 @@ targetCompatibility = 1.7 // 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.44.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.19.2' dependencies { diff --git a/examples/example-tls/pom.xml b/examples/example-tls/pom.xml index 193e05fd812..a135dc51d94 100644 --- a/examples/example-tls/pom.xml +++ b/examples/example-tls/pom.xml @@ -6,13 +6,13 @@ jar - 1.44.0-SNAPSHOT + 1.45.0-SNAPSHOT example-tls https://github.com/grpc/grpc-java UTF-8 - 1.44.0-SNAPSHOT + 1.45.0-SNAPSHOT 3.19.2 2.0.34.Final diff --git a/examples/example-xds/build.gradle b/examples/example-xds/build.gradle index 2d35f5c40d9..6b90a913c6b 100644 --- a/examples/example-xds/build.gradle +++ b/examples/example-xds/build.gradle @@ -22,7 +22,7 @@ targetCompatibility = 1.7 // 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.44.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION def nettyTcNativeVersion = '2.0.31.Final' def protocVersion = '3.19.2' diff --git a/examples/pom.xml b/examples/pom.xml index 525fb616fa0..20c67251689 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -6,13 +6,13 @@ jar - 1.44.0-SNAPSHOT + 1.45.0-SNAPSHOT examples https://github.com/grpc/grpc-java UTF-8 - 1.44.0-SNAPSHOT + 1.45.0-SNAPSHOT 3.19.2 3.19.2 From 7a23fb27fe16f688a2058d78bcbc761725127f77 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Wed, 12 Jan 2022 14:58:44 -0800 Subject: [PATCH 04/68] rls: fix child lb leak when client channel is shutdown (#8750) When client channel is shutting down, the RlsLoadBalancer is shutting down. However, the child loadbalancers of RlsLoadBalancer are not shut down. This is causing the issue b/209831670 --- .../java/io/grpc/rls/CachingRlsLbClient.java | 64 ++++------------- .../io/grpc/rls/LbPolicyConfiguration.java | 68 +++++++++++++++++-- .../java/io/grpc/rls/LinkedHashLruCache.java | 24 +++---- rls/src/main/java/io/grpc/rls/LruCache.java | 4 +- .../io/grpc/rls/CachingRlsLbClientTest.java | 12 +++- .../grpc/rls/LbPolicyConfigurationTest.java | 43 ++++++++++++ .../io/grpc/rls/LinkedHashLruCacheTest.java | 6 +- 7 files changed, 147 insertions(+), 74 deletions(-) diff --git a/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java b/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java index 7bd9f4b68e3..8af93d81e09 100644 --- a/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java +++ b/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java @@ -30,17 +30,14 @@ import io.grpc.ChannelLogger; import io.grpc.ChannelLogger.ChannelLogLevel; import io.grpc.ConnectivityState; -import io.grpc.LoadBalancer; import io.grpc.LoadBalancer.Helper; import io.grpc.LoadBalancer.PickResult; import io.grpc.LoadBalancer.PickSubchannelArgs; import io.grpc.LoadBalancer.ResolvedAddresses; import io.grpc.LoadBalancer.SubchannelPicker; -import io.grpc.LoadBalancerProvider; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.Metadata; -import io.grpc.NameResolver.ConfigOrError; import io.grpc.Status; import io.grpc.SynchronizationContext; import io.grpc.SynchronizationContext.ScheduledHandle; @@ -51,7 +48,6 @@ import io.grpc.lookup.v1.RouteLookupServiceGrpc.RouteLookupServiceStub; import io.grpc.rls.ChildLoadBalancerHelper.ChildLoadBalancerHelperProvider; import io.grpc.rls.LbPolicyConfiguration.ChildLbStatusListener; -import io.grpc.rls.LbPolicyConfiguration.ChildLoadBalancingPolicy; import io.grpc.rls.LbPolicyConfiguration.ChildPolicyWrapper; import io.grpc.rls.LbPolicyConfiguration.RefCountedChildPolicyWrapperFactory; import io.grpc.rls.LruCache.EvictionListener; @@ -138,7 +134,8 @@ private CachingRlsLbClient(Builder builder) { rlsConfig.getCacheSizeBytes(), builder.evictionListener, scheduledExecutorService, - timeProvider); + timeProvider, + lock); logger = helper.getChannelLogger(); String serverHost = null; try { @@ -181,7 +178,9 @@ private CachingRlsLbClient(Builder builder) { new ChildLoadBalancerHelperProvider(helper, new SubchannelStateManagerImpl(), rlsPicker); refCountedChildPolicyWrapperFactory = new RefCountedChildPolicyWrapperFactory( - childLbHelperProvider, new BackoffRefreshListener()); + lbPolicyConfig.getLoadBalancingPolicy(), childLbResolvedAddressFactory, + childLbHelperProvider, + new BackoffRefreshListener()); logger.log(ChannelLogLevel.DEBUG, "CachingRlsLbClient created"); } @@ -536,6 +535,7 @@ final class DataCacheEntry extends CacheEntry { private final long staleTime; private final ChildPolicyWrapper childPolicyWrapper; + // GuardedBy CachingRlsLbClient.lock DataCacheEntry(RouteLookupRequest request, final RouteLookupResponse response) { super(request); this.response = checkNotNull(response, "response"); @@ -546,29 +546,6 @@ final class DataCacheEntry extends CacheEntry { long now = timeProvider.currentTimeNanos(); expireTime = now + maxAgeNanos; staleTime = now + staleAgeNanos; - - if (childPolicyWrapper.getPicker() != null) { - childPolicyWrapper.refreshState(); - } else { - createChildLbPolicy(); - } - } - - private void createChildLbPolicy() { - ChildLoadBalancingPolicy childPolicy = lbPolicyConfig.getLoadBalancingPolicy(); - LoadBalancerProvider lbProvider = childPolicy.getEffectiveLbProvider(); - ConfigOrError lbConfig = - lbProvider - .parseLoadBalancingPolicyConfig( - childPolicy.getEffectiveChildPolicy(childPolicyWrapper.getTarget())); - - LoadBalancer lb = lbProvider.newLoadBalancer(childPolicyWrapper.getHelper()); - logger.log( - ChannelLogLevel.DEBUG, - "RLS child lb created. config: {0}", - lbConfig.getConfig()); - lb.handleResolvedAddresses(childLbResolvedAddressFactory.create(lbConfig.getConfig())); - lb.requestConnection(); } /** @@ -637,7 +614,9 @@ boolean isStaled(long now) { @Override void cleanup() { - refCountedChildPolicyWrapperFactory.release(childPolicyWrapper); + synchronized (lock) { + refCountedChildPolicyWrapperFactory.release(childPolicyWrapper); + } } @Override @@ -856,14 +835,15 @@ private static final class RlsAsyncLruCache RlsAsyncLruCache(long maxEstimatedSizeBytes, @Nullable EvictionListener evictionListener, - ScheduledExecutorService ses, TimeProvider timeProvider) { + ScheduledExecutorService ses, TimeProvider timeProvider, Object lock) { super( maxEstimatedSizeBytes, new AutoCleaningEvictionListener(evictionListener), 1, TimeUnit.MINUTES, ses, - timeProvider); + timeProvider, + lock); } @Override @@ -985,27 +965,9 @@ private void startFallbackChildPolicy() { } fallbackChildPolicyWrapper = refCountedChildPolicyWrapperFactory.createOrGet(defaultTarget); } - LoadBalancerProvider lbProvider = - lbPolicyConfig.getLoadBalancingPolicy().getEffectiveLbProvider(); - final LoadBalancer lb = - lbProvider.newLoadBalancer(fallbackChildPolicyWrapper.getHelper()); - final ConfigOrError lbConfig = - lbProvider - .parseLoadBalancingPolicyConfig( - lbPolicyConfig - .getLoadBalancingPolicy() - .getEffectiveChildPolicy(defaultTarget)); - helper.getSynchronizationContext().execute( - new Runnable() { - @Override - public void run() { - lb.handleResolvedAddresses( - childLbResolvedAddressFactory.create(lbConfig.getConfig())); - lb.requestConnection(); - } - }); } + // GuardedBy CachingRlsLbClient.lock void close() { if (fallbackChildPolicyWrapper != null) { refCountedChildPolicyWrapperFactory.release(fallbackChildPolicyWrapper); diff --git a/rls/src/main/java/io/grpc/rls/LbPolicyConfiguration.java b/rls/src/main/java/io/grpc/rls/LbPolicyConfiguration.java index f54e441ffe5..94a9de9801f 100644 --- a/rls/src/main/java/io/grpc/rls/LbPolicyConfiguration.java +++ b/rls/src/main/java/io/grpc/rls/LbPolicyConfiguration.java @@ -22,12 +22,15 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; +import io.grpc.ChannelLogger.ChannelLogLevel; import io.grpc.ConnectivityState; +import io.grpc.LoadBalancer; import io.grpc.LoadBalancer.Helper; import io.grpc.LoadBalancer.Subchannel; import io.grpc.LoadBalancer.SubchannelPicker; import io.grpc.LoadBalancerProvider; import io.grpc.LoadBalancerRegistry; +import io.grpc.NameResolver.ConfigOrError; import io.grpc.internal.ObjectPool; import io.grpc.rls.ChildLoadBalancerHelper.ChildLoadBalancerHelperProvider; import io.grpc.rls.RlsProtoData.RouteLookupConfig; @@ -191,33 +194,49 @@ public String toString() { /** Factory for {@link ChildPolicyWrapper}. */ static final class RefCountedChildPolicyWrapperFactory { + // GuardedBy CachingRlsLbClient.lock @VisibleForTesting final Map childPolicyMap = new HashMap<>(); private final ChildLoadBalancerHelperProvider childLbHelperProvider; private final ChildLbStatusListener childLbStatusListener; + private final ChildLoadBalancingPolicy childPolicy; + private final ResolvedAddressFactory childLbResolvedAddressFactory; public RefCountedChildPolicyWrapperFactory( + ChildLoadBalancingPolicy childPolicy, + ResolvedAddressFactory childLbResolvedAddressFactory, ChildLoadBalancerHelperProvider childLbHelperProvider, ChildLbStatusListener childLbStatusListener) { + this.childPolicy = checkNotNull(childPolicy, "childPolicy"); + this.childLbResolvedAddressFactory = + checkNotNull(childLbResolvedAddressFactory, "childLbResolvedAddressFactory"); this.childLbHelperProvider = checkNotNull(childLbHelperProvider, "childLbHelperProvider"); this.childLbStatusListener = checkNotNull(childLbStatusListener, "childLbStatusListener"); } + // GuardedBy CachingRlsLbClient.lock ChildPolicyWrapper createOrGet(String target) { // TODO(creamsoup) check if the target is valid or not RefCountedChildPolicyWrapper pooledChildPolicyWrapper = childPolicyMap.get(target); if (pooledChildPolicyWrapper == null) { - ChildPolicyWrapper childPolicyWrapper = - new ChildPolicyWrapper(target, childLbHelperProvider, childLbStatusListener); + ChildPolicyWrapper childPolicyWrapper = new ChildPolicyWrapper( + target, childPolicy, childLbResolvedAddressFactory, childLbHelperProvider, + childLbStatusListener); pooledChildPolicyWrapper = RefCountedChildPolicyWrapper.of(childPolicyWrapper); childPolicyMap.put(target, pooledChildPolicyWrapper); + return pooledChildPolicyWrapper.getObject(); + } else { + ChildPolicyWrapper childPolicyWrapper = pooledChildPolicyWrapper.getObject(); + if (childPolicyWrapper.getPicker() != null) { + childPolicyWrapper.refreshState(); + } + return childPolicyWrapper; } - - return pooledChildPolicyWrapper.getObject(); } + // GuardedBy CachingRlsLbClient.lock void release(ChildPolicyWrapper childPolicyWrapper) { checkNotNull(childPolicyWrapper, "childPolicyWrapper"); String target = childPolicyWrapper.getTarget(); @@ -238,16 +257,36 @@ static final class ChildPolicyWrapper { private final String target; private final ChildPolicyReportingHelper helper; + private final LoadBalancer lb; private volatile SubchannelPicker picker; private ConnectivityState state; public ChildPolicyWrapper( String target, + ChildLoadBalancingPolicy childPolicy, + final ResolvedAddressFactory childLbResolvedAddressFactory, ChildLoadBalancerHelperProvider childLbHelperProvider, ChildLbStatusListener childLbStatusListener) { this.target = target; this.helper = new ChildPolicyReportingHelper(childLbHelperProvider, childLbStatusListener); + LoadBalancerProvider lbProvider = childPolicy.getEffectiveLbProvider(); + final ConfigOrError lbConfig = + lbProvider + .parseLoadBalancingPolicyConfig( + childPolicy.getEffectiveChildPolicy(target)); + this.lb = lbProvider.newLoadBalancer(helper); + helper.getChannelLogger().log( + ChannelLogLevel.DEBUG, "RLS child lb created. config: {0}", lbConfig.getConfig()); + helper.getSynchronizationContext().execute( + new Runnable() { + @Override + public void run() { + lb.handleResolvedAddresses( + childLbResolvedAddressFactory.create(lbConfig.getConfig())); + lb.requestConnection(); + } + }); } String getTarget() { @@ -263,7 +302,25 @@ ChildPolicyReportingHelper getHelper() { } void refreshState() { - helper.updateBalancingState(state, picker); + helper.getSynchronizationContext().execute( + new Runnable() { + @Override + public void run() { + helper.updateBalancingState(state, picker); + } + } + ); + } + + void shutdown() { + helper.getSynchronizationContext().execute( + new Runnable() { + @Override + public void run() { + lb.shutdown(); + } + } + ); } @Override @@ -346,6 +403,7 @@ public ChildPolicyWrapper returnObject(Object object) { long newCnt = refCnt.decrementAndGet(); checkState(newCnt != -1, "Cannot return never pooled childPolicyWrapper"); if (newCnt == 0) { + childPolicyWrapper.shutdown(); childPolicyWrapper = null; } return null; diff --git a/rls/src/main/java/io/grpc/rls/LinkedHashLruCache.java b/rls/src/main/java/io/grpc/rls/LinkedHashLruCache.java index 2d60f103a60..9c1a24a0d1e 100644 --- a/rls/src/main/java/io/grpc/rls/LinkedHashLruCache.java +++ b/rls/src/main/java/io/grpc/rls/LinkedHashLruCache.java @@ -48,7 +48,7 @@ @ThreadSafe abstract class LinkedHashLruCache implements LruCache { - private final Object lock = new Object(); + private final Object lock; @GuardedBy("lock") private final LinkedHashMap delegate; @@ -64,9 +64,11 @@ abstract class LinkedHashLruCache implements LruCache { int cleaningInterval, TimeUnit cleaningIntervalUnit, ScheduledExecutorService ses, - final TimeProvider timeProvider) { + final TimeProvider timeProvider, + Object lock) { checkState(estimatedMaxSizeBytes > 0, "max estimated cache size should be positive"); this.estimatedMaxSizeBytes = estimatedMaxSizeBytes; + this.lock = checkNotNull(lock, "lock"); this.evictionListener = new SizeHandlingEvictionListener(evictionListener); this.timeProvider = checkNotNull(timeProvider, "timeProvider"); delegate = new LinkedHashMap( @@ -200,14 +202,15 @@ private V invalidate(K key, EvictionType cause) { } @Override - public final void invalidateAll(Iterable keys) { - checkNotNull(keys, "keys"); + public final void invalidateAll() { synchronized (lock) { - for (K key : keys) { - SizedValue existing = delegate.remove(key); - if (existing != null) { - evictionListener.onEviction(key, existing, EvictionType.EXPLICIT); + Iterator> iterator = delegate.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + if (entry.getValue() != null) { + evictionListener.onEviction(entry.getKey(), entry.getValue(), EvictionType.EXPLICIT); } + iterator.remove(); } } } @@ -291,13 +294,10 @@ private boolean cleanupExpiredEntries(int maxExpiredEntries, long now) { public final void close() { synchronized (lock) { periodicCleaner.stop(); - doClose(); - delegate.clear(); + invalidateAll(); } } - protected void doClose() {} - /** Periodically cleans up the AsyncRequestCache. */ private final class PeriodicCleaner { diff --git a/rls/src/main/java/io/grpc/rls/LruCache.java b/rls/src/main/java/io/grpc/rls/LruCache.java index 6ab5c4bcb46..1ad5a958289 100644 --- a/rls/src/main/java/io/grpc/rls/LruCache.java +++ b/rls/src/main/java/io/grpc/rls/LruCache.java @@ -49,10 +49,10 @@ interface LruCache { V invalidate(K key); /** - * Invalidates cache entries for given keys. This operation will trigger {@link EvictionListener} + * Invalidates cache entries for all keys. This operation will trigger {@link EvictionListener} * with {@link EvictionType#EXPLICIT}. */ - void invalidateAll(Iterable keys); + void invalidateAll(); /** Returns {@code true} if given key is cached. */ @CheckReturnValue diff --git a/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java b/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java index c8222a02b8a..d10bc82d071 100644 --- a/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java +++ b/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java @@ -19,6 +19,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static io.grpc.rls.CachingRlsLbClient.RLS_DATA_KEY; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -81,6 +82,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; @@ -172,6 +174,9 @@ private void setUpRlsLbClient() { public void tearDown() throws Exception { rlsLbClient.close(); CachingRlsLbClient.enableOobChannelDirectPath = existingEnableOobChannelDirectPath; + assertWithMessage( + "On client shut down, RlsLoadBalancer must shut down with all its child loadbalancers.") + .that(lbProvider.loadBalancers).isEmpty(); } private CachedRouteLookupResponse getInSyncContext( @@ -462,6 +467,7 @@ public BackoffPolicy get() { * immediately fails when using the fallback target. */ private static final class TestLoadBalancerProvider extends LoadBalancerProvider { + final Set loadBalancers = new HashSet<>(); @Override public boolean isAvailable() { @@ -486,7 +492,7 @@ public ConfigOrError parseLoadBalancingPolicyConfig( @Override public LoadBalancer newLoadBalancer(final Helper helper) { - return new LoadBalancer() { + LoadBalancer loadBalancer = new LoadBalancer() { @Override public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { @@ -527,8 +533,12 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { @Override public void shutdown() { + loadBalancers.remove(this); } }; + + loadBalancers.add(loadBalancer); + return loadBalancer; } } diff --git a/rls/src/test/java/io/grpc/rls/LbPolicyConfigurationTest.java b/rls/src/test/java/io/grpc/rls/LbPolicyConfigurationTest.java index 7c6be039c24..36022cb25e6 100644 --- a/rls/src/test/java/io/grpc/rls/LbPolicyConfigurationTest.java +++ b/rls/src/test/java/io/grpc/rls/LbPolicyConfigurationTest.java @@ -18,17 +18,25 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; 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 io.grpc.ChannelLogger; import io.grpc.ConnectivityState; +import io.grpc.EquivalentAddressGroup; +import io.grpc.LoadBalancer; import io.grpc.LoadBalancer.Helper; +import io.grpc.LoadBalancer.ResolvedAddresses; import io.grpc.LoadBalancer.SubchannelPicker; import io.grpc.LoadBalancerProvider; import io.grpc.LoadBalancerRegistry; +import io.grpc.NameResolver.ConfigOrError; +import io.grpc.SynchronizationContext; import io.grpc.rls.ChildLoadBalancerHelper.ChildLoadBalancerHelperProvider; import io.grpc.rls.LbPolicyConfiguration.ChildLbStatusListener; import io.grpc.rls.LbPolicyConfiguration.ChildLoadBalancingPolicy; @@ -36,23 +44,58 @@ import io.grpc.rls.LbPolicyConfiguration.ChildPolicyWrapper.ChildPolicyReportingHelper; import io.grpc.rls.LbPolicyConfiguration.InvalidChildPolicyConfigException; import io.grpc.rls.LbPolicyConfiguration.RefCountedChildPolicyWrapperFactory; +import java.lang.Thread.UncaughtExceptionHandler; import java.util.Map; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.mockito.ArgumentMatchers; @RunWith(JUnit4.class) public class LbPolicyConfigurationTest { private final Helper helper = mock(Helper.class); + private final LoadBalancerProvider lbProvider = mock(LoadBalancerProvider.class); private final SubchannelStateManager subchannelStateManager = new SubchannelStateManagerImpl(); private final SubchannelPicker picker = mock(SubchannelPicker.class); private final ChildLbStatusListener childLbStatusListener = mock(ChildLbStatusListener.class); + private final ResolvedAddressFactory resolvedAddressFactory = + new ResolvedAddressFactory() { + @Override + public ResolvedAddresses create(Object childLbConfig) { + return ResolvedAddresses.newBuilder() + .setAddresses(ImmutableList.of()) + .build(); + } + }; private final RefCountedChildPolicyWrapperFactory factory = new RefCountedChildPolicyWrapperFactory( + new ChildLoadBalancingPolicy( + "targetFieldName", + ImmutableMap.of("foo", "bar"), + lbProvider), + resolvedAddressFactory, new ChildLoadBalancerHelperProvider(helper, subchannelStateManager, picker), childLbStatusListener); + @Before + public void setUp() { + doReturn(mock(ChannelLogger.class)).when(helper).getChannelLogger(); + doReturn( + new SynchronizationContext( + new UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + throw new AssertionError(e); + } + })) + .when(helper).getSynchronizationContext(); + doReturn(mock(LoadBalancer.class)).when(lbProvider).newLoadBalancer(any(Helper.class)); + doReturn(ConfigOrError.fromConfig(new Object())) + .when(lbProvider).parseLoadBalancingPolicyConfig(ArgumentMatchers.>any()); + } + @Test public void childPolicyWrapper_refCounted() { String target = "target"; diff --git a/rls/src/test/java/io/grpc/rls/LinkedHashLruCacheTest.java b/rls/src/test/java/io/grpc/rls/LinkedHashLruCacheTest.java index b7254341f64..60266e15998 100644 --- a/rls/src/test/java/io/grpc/rls/LinkedHashLruCacheTest.java +++ b/rls/src/test/java/io/grpc/rls/LinkedHashLruCacheTest.java @@ -23,7 +23,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import com.google.common.collect.ImmutableList; import io.grpc.rls.DoNotUseDirectScheduledExecutorService.FakeTimeProvider; import io.grpc.rls.LruCache.EvictionListener; import io.grpc.rls.LruCache.EvictionType; @@ -62,7 +61,8 @@ public void setUp() { 10, TimeUnit.NANOSECONDS, fakeScheduledService, - timeProvider) { + timeProvider, + new Object()) { @Override protected boolean isExpired(Integer key, Entry value, long nowNanos) { return value.expireTime <= nowNanos; @@ -210,7 +210,7 @@ public void invalidateAll() { assertThat(cache.estimatedSize()).isEqualTo(2); - cache.invalidateAll(ImmutableList.of(1, 2)); + cache.invalidateAll(); assertThat(cache.estimatedSize()).isEqualTo(0); } From 39cc44e38c8cf400f6d6abe38cf490b0149a7bad Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Wed, 12 Jan 2022 15:44:01 -0800 Subject: [PATCH 05/68] kokoro: Pretty test results Previously, only Windows had the plumbing to rename test results for the Kokoro result viewers to pretty-print. macos.cfg was the only CI that lacked a corresponding .sh, which maked unix.sh harder to reason about. Created macos.sh so that unix.sh is now just a helper script and will not be called directly by Kokoro. We now avoid "gradle clean" to avoid wiping results. Still clean compiler since we do re-run the build multiple times with varying platforms. Shouldn't be necessary, but "just in case" since I want this commit to be low risk. This improves Windows to produce detailed results even if the CI was successful. --- buildscripts/kokoro/kokoro.sh | 9 +++++++++ buildscripts/kokoro/linux_aarch64.cfg | 3 ++- buildscripts/kokoro/linux_aarch64.sh | 5 +++++ buildscripts/kokoro/linux_artifacts.cfg | 1 + buildscripts/kokoro/linux_artifacts.sh | 3 +++ buildscripts/kokoro/macos.cfg | 4 ++-- buildscripts/kokoro/macos.sh | 13 +++++++++++++ buildscripts/kokoro/unix.sh | 7 +------ buildscripts/kokoro/windows.cfg | 2 +- buildscripts/kokoro/windows64.bat | 2 +- 10 files changed, 38 insertions(+), 11 deletions(-) create mode 100755 buildscripts/kokoro/kokoro.sh create mode 100755 buildscripts/kokoro/macos.sh diff --git a/buildscripts/kokoro/kokoro.sh b/buildscripts/kokoro/kokoro.sh new file mode 100755 index 00000000000..0b3864b5092 --- /dev/null +++ b/buildscripts/kokoro/kokoro.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +spongify_logs() { + local f + while read -r f; do + mkdir "${f%.xml}" + cp "$f" "${f%.xml}/sponge_log.xml" + done < <(find "${KOKORO_ARTIFACTS_DIR:-.}" -name 'TEST-*.xml') +} diff --git a/buildscripts/kokoro/linux_aarch64.cfg b/buildscripts/kokoro/linux_aarch64.cfg index f91fbe4d702..325d910c5ea 100644 --- a/buildscripts/kokoro/linux_aarch64.cfg +++ b/buildscripts/kokoro/linux_aarch64.cfg @@ -6,7 +6,8 @@ timeout_mins: 60 action { define_artifacts { + regex: "github/grpc-java/**/build/test-results/**/sponge_log.xml" regex: "github/grpc-java/mvn-artifacts/**" regex: "github/grpc-java/artifacts/**" } -} \ No newline at end of file +} diff --git a/buildscripts/kokoro/linux_aarch64.sh b/buildscripts/kokoro/linux_aarch64.sh index 11c3f8ae3a1..f4a1292efb5 100755 --- a/buildscripts/kokoro/linux_aarch64.sh +++ b/buildscripts/kokoro/linux_aarch64.sh @@ -5,6 +5,11 @@ if [[ -f /VERSION ]]; then cat /VERSION fi +readonly GRPC_JAVA_DIR="$(cd "$(dirname "$0")"/../.. && pwd)" + +. "$GRPC_JAVA_DIR"/buildscripts/kokoro/kokoro.sh +trap spongify_logs EXIT + cd github/grpc-java buildscripts/qemu_helpers/prepare_qemu.sh diff --git a/buildscripts/kokoro/linux_artifacts.cfg b/buildscripts/kokoro/linux_artifacts.cfg index 0938e4bff2e..8691b5ff356 100644 --- a/buildscripts/kokoro/linux_artifacts.cfg +++ b/buildscripts/kokoro/linux_artifacts.cfg @@ -6,6 +6,7 @@ timeout_mins: 60 action { define_artifacts { + regex: "github/grpc-java/**/build/test-results/**/sponge_log.xml" regex: "github/grpc-java/mvn-artifacts/**" regex: "github/grpc-java/artifacts/**" } diff --git a/buildscripts/kokoro/linux_artifacts.sh b/buildscripts/kokoro/linux_artifacts.sh index b4551843c6a..e23b2bcc628 100755 --- a/buildscripts/kokoro/linux_artifacts.sh +++ b/buildscripts/kokoro/linux_artifacts.sh @@ -7,6 +7,9 @@ fi readonly GRPC_JAVA_DIR="$(cd "$(dirname "$0")"/../.. && pwd)" +. "$GRPC_JAVA_DIR"/buildscripts/kokoro/kokoro.sh +trap spongify_logs EXIT + "$GRPC_JAVA_DIR"/buildscripts/build_docker.sh "$GRPC_JAVA_DIR"/buildscripts/run_in_docker.sh /grpc-java/buildscripts/build_artifacts_in_docker.sh diff --git a/buildscripts/kokoro/macos.cfg b/buildscripts/kokoro/macos.cfg index a6bf290d1ec..f79634e3cf8 100644 --- a/buildscripts/kokoro/macos.cfg +++ b/buildscripts/kokoro/macos.cfg @@ -1,7 +1,7 @@ # Config file for internal CI # Location of the continuous shell script in repository. -build_file: "grpc-java/buildscripts/kokoro/unix.sh" +build_file: "grpc-java/buildscripts/kokoro/macos.sh" timeout_mins: 45 # We had problems with random tests timing out because it took seconds to do @@ -15,7 +15,7 @@ env_vars { # We always build mvn artifacts. action { define_artifacts { - regex: "github/grpc-java/**/build/reports/**" + regex: "github/grpc-java/**/build/test-results/**/sponge_log.xml" regex: "github/grpc-java/mvn-artifacts/**" } } diff --git a/buildscripts/kokoro/macos.sh b/buildscripts/kokoro/macos.sh new file mode 100755 index 00000000000..df52a004f52 --- /dev/null +++ b/buildscripts/kokoro/macos.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -veux -o pipefail + +if [[ -f /VERSION ]]; then + cat /VERSION +fi + +readonly GRPC_JAVA_DIR="$(cd "$(dirname "$0")"/../.. && pwd)" + +. "$GRPC_JAVA_DIR"/buildscripts/kokoro/kokoro.sh +trap spongify_logs EXIT + +"$GRPC_JAVA_DIR"/buildscripts/kokoro/unix.sh diff --git a/buildscripts/kokoro/unix.sh b/buildscripts/kokoro/unix.sh index 61d65324a07..91ee67f1d98 100755 --- a/buildscripts/kokoro/unix.sh +++ b/buildscripts/kokoro/unix.sh @@ -16,10 +16,6 @@ set -exu -o pipefail # It would be nicer to use 'readlink -f' here but osx does not support it. readonly GRPC_JAVA_DIR="$(cd "$(dirname "$0")"/../.. && pwd)" -if [[ -f /VERSION ]]; then - cat /VERSION -fi - # cd to the root dir of grpc-java cd $(dirname $0)/../.. @@ -48,7 +44,7 @@ export LD_LIBRARY_PATH=/tmp/protobuf/lib export LDFLAGS=-L/tmp/protobuf/lib export CXXFLAGS="-I/tmp/protobuf/include" -./gradlew clean $GRADLE_FLAGS +./gradlew grpc-compiler:clean $GRADLE_FLAGS if [[ -z "${SKIP_TESTS:-}" ]]; then # Ensure all *.proto changes include *.java generated code @@ -62,7 +58,6 @@ if [[ -z "${SKIP_TESTS:-}" ]]; then # Run tests ./gradlew build :grpc-all:jacocoTestReport $GRADLE_FLAGS pushd examples - ./gradlew clean $GRADLE_FLAGS ./gradlew build $GRADLE_FLAGS # --batch-mode reduces log spam mvn verify --batch-mode diff --git a/buildscripts/kokoro/windows.cfg b/buildscripts/kokoro/windows.cfg index 6b2703f99c9..bdfaa38904f 100644 --- a/buildscripts/kokoro/windows.cfg +++ b/buildscripts/kokoro/windows.cfg @@ -7,7 +7,7 @@ timeout_mins: 45 # We always build mvn artifacts. action { define_artifacts { - regex: "**/build/test-results/**/*.xml" + regex: "github/grpc-java/**/build/test-results/**/sponge_log.xml" regex: "github/grpc-java/mvn-artifacts/**" } } diff --git a/buildscripts/kokoro/windows64.bat b/buildscripts/kokoro/windows64.bat index edcb421fe7d..eaa1fcf845e 100644 --- a/buildscripts/kokoro/windows64.bat +++ b/buildscripts/kokoro/windows64.bat @@ -32,4 +32,4 @@ SET GRADLE_FLAGS=-PtargetArch=%TARGET_ARCH% -PfailOnWarnings=%FAIL_ON_WARNINGS% @rem make sure no daemons have any files open cmd.exe /C "%WORKSPACE%\gradlew.bat --stop" -cmd.exe /C "%WORKSPACE%\gradlew.bat %GRADLE_FLAGS% -Dorg.gradle.parallel=false -PrepositoryDir=%WORKSPACE%\artifacts clean grpc-compiler:build grpc-compiler:publish" || exit /b 1 +cmd.exe /C "%WORKSPACE%\gradlew.bat %GRADLE_FLAGS% -Dorg.gradle.parallel=false -PrepositoryDir=%WORKSPACE%\artifacts grpc-compiler:clean grpc-compiler:build grpc-compiler:publish" || exit /b 1 From 9ee0ac208b19c6d02e5f2d40933a3afba7d02579 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Fri, 14 Jan 2022 10:13:43 -0800 Subject: [PATCH 06/68] testing: remove opencensus dependency from grpc-testing (#8833) `io.grpc.internal.testing.StatsTestUtils` in `grpc-testing` is only used internally by `grpc-interop-testing` and unit tests. The opencensus dependency does not need to be exposed to `grpc-interop-testing` maven artifact. --- testing/build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/testing/build.gradle b/testing/build.gradle index a0f31819916..0879eca502a 100644 --- a/testing/build.gradle +++ b/testing/build.gradle @@ -13,7 +13,8 @@ dependencies { api project(':grpc-core'), project(':grpc-stub'), libraries.junit - implementation libraries.opencensus_api + // Only io.grpc.internal.testing.StatsTestUtils depends on opencensus_api, for internal use. + compileOnly libraries.opencensus_api runtimeOnly project(":grpc-context") // Pull in newer version than census-api testImplementation (libraries.mockito) { From 14feae81b32e6b7986475ac332521d22b0717f6a Mon Sep 17 00:00:00 2001 From: "Penn (Dapeng) Zhang" Date: Thu, 13 Jan 2022 10:32:40 -0800 Subject: [PATCH 07/68] android-interop-testing: migrate AndroidJUnit4 runner --- android-interop-testing/build.gradle | 4 ++-- .../integrationtest/InteropInstrumentationTest.java | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/android-interop-testing/build.gradle b/android-interop-testing/build.gradle index b18d84e2625..9ede49593dc 100644 --- a/android-interop-testing/build.gradle +++ b/android-interop-testing/build.gradle @@ -76,8 +76,8 @@ dependencies { compileOnly libraries.javax_annotation - androidTestImplementation 'androidx.test:rules:1.1.0-alpha1' - androidTestImplementation 'androidx.test:runner:1.1.0-alpha1' + androidTestImplementation 'androidx.test.ext:junit:1.1.3', + 'androidx.test:runner:1.4.0' } // Checkstyle doesn't run automatically with android diff --git a/android-interop-testing/src/androidTest/java/io/grpc/android/integrationtest/InteropInstrumentationTest.java b/android-interop-testing/src/androidTest/java/io/grpc/android/integrationtest/InteropInstrumentationTest.java index abcf9e08593..5d76f1c0a6e 100644 --- a/android-interop-testing/src/androidTest/java/io/grpc/android/integrationtest/InteropInstrumentationTest.java +++ b/android-interop-testing/src/androidTest/java/io/grpc/android/integrationtest/InteropInstrumentationTest.java @@ -16,11 +16,12 @@ package io.grpc.android.integrationtest; -import static junit.framework.Assert.assertEquals; +import static org.junit.Assert.assertEquals; import android.util.Log; -import androidx.test.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; import com.google.android.gms.common.GooglePlayServicesNotAvailableException; import com.google.android.gms.common.GooglePlayServicesRepairableException; import com.google.android.gms.security.ProviderInstaller; @@ -59,7 +60,7 @@ public void setUp() throws Exception { if (useTls) { try { - ProviderInstaller.installIfNeeded(InstrumentationRegistry.getTargetContext()); + ProviderInstaller.installIfNeeded(ApplicationProvider.getApplicationContext()); } catch (GooglePlayServicesRepairableException e) { // The provider is helpful, but it is possible to succeed without it. // Hope that the system-provided libraries are new enough. @@ -110,7 +111,7 @@ public void onComplete(String result) { }; InputStream testCa; if (useTestCa) { - testCa = InstrumentationRegistry.getTargetContext().getResources().openRawResource(R.raw.ca); + testCa = ApplicationProvider.getApplicationContext().getResources().openRawResource(R.raw.ca); } else { testCa = null; } From e2794799080103095b73913d77c10fdf20063313 Mon Sep 17 00:00:00 2001 From: "Penn (Dapeng) Zhang" Date: Fri, 14 Jan 2022 09:25:49 -0800 Subject: [PATCH 08/68] android-interop-testing: update androidTest/AndroidManifest.xml for multidex --- android-interop-testing/src/androidTest/AndroidManifest.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android-interop-testing/src/androidTest/AndroidManifest.xml b/android-interop-testing/src/androidTest/AndroidManifest.xml index 916a1c04332..9c59d201eff 100644 --- a/android-interop-testing/src/androidTest/AndroidManifest.xml +++ b/android-interop-testing/src/androidTest/AndroidManifest.xml @@ -6,8 +6,8 @@ android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="io.grpc.android.integrationtest" /> - - + From 25531d6257b7a81505b4dd856b8ae5a14555b61a Mon Sep 17 00:00:00 2001 From: John Cormie Date: Fri, 14 Jan 2022 14:06:14 -0800 Subject: [PATCH 09/68] binder: Invoke onTransportReady() in a round-robin fashion. (#8835) Also call onTransportReady() only if isReady() still holds by the time we get to a given Inbound. This dramatically reduces timeouts and improves throughput when flow control has kicked in. This approach is still not completely fair since each ongoing call might consume a different amount of window on its turn, but because of the way Outbound#writeMessageData() and BlockPool already work, everyone gets to send at least 16kb. --- .../grpc/binder/internal/BinderTransport.java | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java b/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java index e07619dca33..219651a8b69 100644 --- a/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java +++ b/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java @@ -60,6 +60,8 @@ import io.grpc.internal.StatsTraceContext; import io.grpc.internal.TimeProvider; import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; @@ -186,6 +188,9 @@ protected enum TransportState { protected final ConcurrentHashMap> ongoingCalls; + @GuardedBy("this") + private final LinkedHashSet callIdsToNotifyWhenReady = new LinkedHashSet<>(); + @GuardedBy("this") protected Attributes attributes; @@ -529,9 +534,18 @@ final void handleAcknowledgedBytes(long numBytes) { logger.log( Level.FINE, "handleAcknowledgedBytes: Transmit Window No-Longer Full. Unblock calls: " + this); - // We're ready again, and need to poke any waiting transactions. - for (Inbound inbound : ongoingCalls.values()) { - inbound.onTransportReady(); + + // The LinkedHashSet contract guarantees that an id already present in this collection will + // not lose its priority if we re-insert it here. + callIdsToNotifyWhenReady.addAll(ongoingCalls.keySet()); + + Iterator i = callIdsToNotifyWhenReady.iterator(); + while (isReady() && i.hasNext()) { + Inbound inbound = ongoingCalls.get(i.next()); + i.remove(); + if (inbound != null) { // Calls can be removed out from under us. + inbound.onTransportReady(); + } } } } From 7cf048eb28ef36842552040a4375caaebd45dd08 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Tue, 18 Jan 2022 07:17:51 -0800 Subject: [PATCH 10/68] Drop Java 7 support Oracle's Premier Support for Java 7 ended in July 2019. Per gRFC P5, dropping support for the only release. Android is able to desugar many Java 8 language features. --- alts/build.gradle | 3 --- build.gradle | 4 ++-- context/build.gradle | 3 +++ examples/build.gradle | 4 ++-- examples/example-alts/build.gradle | 4 ++-- examples/example-gauth/build.gradle | 4 ++-- examples/example-hostname/build.gradle | 4 ++-- examples/example-jwt-auth/build.gradle | 4 ++-- examples/example-tls/build.gradle | 4 ++-- examples/example-xds/build.gradle | 4 ++-- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/alts/build.gradle b/alts/build.gradle index 8c467f51c12..25e8b160243 100644 --- a/alts/build.gradle +++ b/alts/build.gradle @@ -9,9 +9,6 @@ plugins { description = "gRPC: ALTS" -sourceCompatibility = 1.7 -targetCompatibility = 1.7 - evaluationDependsOn(project(':grpc-core').path) dependencies { diff --git a/build.gradle b/build.gradle index e569c96a0cc..4447ae1fdfe 100644 --- a/build.gradle +++ b/build.gradle @@ -252,8 +252,8 @@ subprojects { } plugins.withId("java") { - sourceCompatibility = 1.7 - targetCompatibility = 1.7 + sourceCompatibility = 1.8 + targetCompatibility = 1.8 dependencies { testImplementation libraries.junit, diff --git a/context/build.gradle b/context/build.gradle index 35ad0566bb6..ba530df54fe 100644 --- a/context/build.gradle +++ b/context/build.gradle @@ -9,6 +9,9 @@ plugins { description = 'gRPC: Context' +sourceCompatibility = 1.7 +targetCompatibility = 1.7 + dependencies { testImplementation libraries.jsr305 testImplementation (libraries.guava_testlib) { diff --git a/examples/build.gradle b/examples/build.gradle index 250269a36e2..8a5599f0a65 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -14,8 +14,8 @@ repositories { mavenLocal() } -sourceCompatibility = 1.7 -targetCompatibility = 1.7 +sourceCompatibility = 1.8 +targetCompatibility = 1.8 // IMPORTANT: You probably want the non-SNAPSHOT version of gRPC. Make sure you // are looking at a tagged version of the example and not "master"! diff --git a/examples/example-alts/build.gradle b/examples/example-alts/build.gradle index 96ac66d485c..b29c159e574 100644 --- a/examples/example-alts/build.gradle +++ b/examples/example-alts/build.gradle @@ -15,8 +15,8 @@ repositories { mavenLocal() } -sourceCompatibility = 1.7 -targetCompatibility = 1.7 +sourceCompatibility = 1.8 +targetCompatibility = 1.8 // IMPORTANT: You probably want the non-SNAPSHOT version of gRPC. Make sure you // are looking at a tagged version of the example and not "master"! diff --git a/examples/example-gauth/build.gradle b/examples/example-gauth/build.gradle index a4cd2b2fc19..40bf8968b79 100644 --- a/examples/example-gauth/build.gradle +++ b/examples/example-gauth/build.gradle @@ -15,8 +15,8 @@ repositories { mavenLocal() } -sourceCompatibility = 1.7 -targetCompatibility = 1.7 +sourceCompatibility = 1.8 +targetCompatibility = 1.8 // IMPORTANT: You probably want the non-SNAPSHOT version of gRPC. Make sure you // are looking at a tagged version of the example and not "master"! diff --git a/examples/example-hostname/build.gradle b/examples/example-hostname/build.gradle index 288cb9585fb..abf4a4e5af6 100644 --- a/examples/example-hostname/build.gradle +++ b/examples/example-hostname/build.gradle @@ -13,8 +13,8 @@ repositories { mavenLocal() } -sourceCompatibility = 1.7 -targetCompatibility = 1.7 +sourceCompatibility = 1.8 +targetCompatibility = 1.8 // IMPORTANT: You probably want the non-SNAPSHOT version of gRPC. Make sure you // are looking at a tagged version of the example and not "master"! diff --git a/examples/example-jwt-auth/build.gradle b/examples/example-jwt-auth/build.gradle index 5b26c85f0d8..1e4acd3deb9 100644 --- a/examples/example-jwt-auth/build.gradle +++ b/examples/example-jwt-auth/build.gradle @@ -14,8 +14,8 @@ repositories { mavenLocal() } -sourceCompatibility = 1.7 -targetCompatibility = 1.7 +sourceCompatibility = 1.8 +targetCompatibility = 1.8 // IMPORTANT: You probably want the non-SNAPSHOT version of gRPC. Make sure you // are looking at a tagged version of the example and not "master"! diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index 4d126452f4e..2eddaf6afbe 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -15,8 +15,8 @@ repositories { mavenLocal() } -sourceCompatibility = 1.7 -targetCompatibility = 1.7 +sourceCompatibility = 1.8 +targetCompatibility = 1.8 // IMPORTANT: You probably want the non-SNAPSHOT version of gRPC. Make sure you // are looking at a tagged version of the example and not "master"! diff --git a/examples/example-xds/build.gradle b/examples/example-xds/build.gradle index 6b90a913c6b..56fef5ef12d 100644 --- a/examples/example-xds/build.gradle +++ b/examples/example-xds/build.gradle @@ -14,8 +14,8 @@ repositories { mavenLocal() } -sourceCompatibility = 1.7 -targetCompatibility = 1.7 +sourceCompatibility = 1.8 +targetCompatibility = 1.8 // IMPORTANT: You probably want the non-SNAPSHOT version of gRPC. Make sure you // are looking at a tagged version of the example and not "master"! From d853414ba3dbff6a87255cb5945c616e861355e6 Mon Sep 17 00:00:00 2001 From: markb74 <57717302+markb74@users.noreply.github.com> Date: Tue, 18 Jan 2022 18:22:59 +0100 Subject: [PATCH 11/68] Update javadoc for AndroidComponentAddress. (#8725) Be more explicit that "packagename" is the application package name. --- .../java/io/grpc/binder/AndroidComponentAddress.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/binder/src/main/java/io/grpc/binder/AndroidComponentAddress.java b/binder/src/main/java/io/grpc/binder/AndroidComponentAddress.java index c37ba457b2b..4809a2db43f 100644 --- a/binder/src/main/java/io/grpc/binder/AndroidComponentAddress.java +++ b/binder/src/main/java/io/grpc/binder/AndroidComponentAddress.java @@ -69,11 +69,15 @@ public static AndroidComponentAddress forLocalComponent(Context context, Class Date: Tue, 18 Jan 2022 08:51:51 -0800 Subject: [PATCH 12/68] netty: Assign the result of a @CheckReturnValue'ed constructor to an unused variable This fixes a soon-to-be compile error via ErrorProne. Alternatively, we could use assertThrows() instead of @Test(expected = ...), but grpc doesn't yet require Java 8. --- netty/src/test/java/io/grpc/netty/KeepAliveEnforcerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/netty/src/test/java/io/grpc/netty/KeepAliveEnforcerTest.java b/netty/src/test/java/io/grpc/netty/KeepAliveEnforcerTest.java index 33c40390826..8dfeb990e2b 100644 --- a/netty/src/test/java/io/grpc/netty/KeepAliveEnforcerTest.java +++ b/netty/src/test/java/io/grpc/netty/KeepAliveEnforcerTest.java @@ -32,12 +32,12 @@ public class KeepAliveEnforcerTest { @Test(expected = IllegalArgumentException.class) public void negativeTime() { - new KeepAliveEnforcer(true, -1, TimeUnit.NANOSECONDS); + KeepAliveEnforcer unused = new KeepAliveEnforcer(true, -1, TimeUnit.NANOSECONDS); } @Test(expected = NullPointerException.class) public void nullTimeUnit() { - new KeepAliveEnforcer(true, 1, null); + KeepAliveEnforcer unused = new KeepAliveEnforcer(true, 1, null); } @Test From d28f718c84e1ba36d202f8ca66627e62d2f60a82 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Tue, 18 Jan 2022 09:58:30 -0800 Subject: [PATCH 13/68] xds: PriorityLoadBalancer should treat IDLE in the same way as READY (#8837) --- .../io/grpc/xds/PriorityLoadBalancer.java | 3 +- .../io/grpc/xds/PriorityLoadBalancerTest.java | 140 ++++++++++++++++++ 2 files changed, 142 insertions(+), 1 deletion(-) diff --git a/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java b/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java index 80ddfd8a865..f29239331b2 100644 --- a/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java @@ -302,7 +302,8 @@ public void run() { return; } if (failOverTimer.isPending()) { - if (newState.equals(READY) || newState.equals(TRANSIENT_FAILURE)) { + if (newState.equals(READY) || newState.equals(IDLE) + || newState.equals(TRANSIENT_FAILURE)) { failOverTimer.cancel(); } } diff --git a/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java index a7e2e916b3e..420e92cf9cd 100644 --- a/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java @@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat; import static io.grpc.ConnectivityState.CONNECTING; +import static io.grpc.ConnectivityState.IDLE; import static io.grpc.ConnectivityState.READY; import static io.grpc.ConnectivityState.TRANSIENT_FAILURE; import static io.grpc.xds.XdsSubchannelPickers.BUFFER_PICKER; @@ -398,6 +399,139 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { verify(balancer3).shutdown(); } + @Test + public void typicalPriorityFailOverFlowWithIdleUpdate() { + PriorityChildConfig priorityChildConfig0 = + new PriorityChildConfig(new PolicySelection(fooLbProvider, new Object()), true); + PriorityChildConfig priorityChildConfig1 = + new PriorityChildConfig(new PolicySelection(fooLbProvider, new Object()), true); + PriorityChildConfig priorityChildConfig2 = + new PriorityChildConfig(new PolicySelection(fooLbProvider, new Object()), true); + PriorityChildConfig priorityChildConfig3 = + new PriorityChildConfig(new PolicySelection(fooLbProvider, new Object()), true); + PriorityLbConfig priorityLbConfig = + new PriorityLbConfig( + ImmutableMap.of("p0", priorityChildConfig0, "p1", priorityChildConfig1, + "p2", priorityChildConfig2, "p3", priorityChildConfig3), + ImmutableList.of("p0", "p1", "p2", "p3")); + priorityLb.handleResolvedAddresses( + ResolvedAddresses.newBuilder() + .setAddresses(ImmutableList.of()) + .setLoadBalancingPolicyConfig(priorityLbConfig) + .build()); + assertThat(fooBalancers).hasSize(1); + assertThat(fooHelpers).hasSize(1); + LoadBalancer balancer0 = Iterables.getLast(fooBalancers); + Helper helper0 = Iterables.getOnlyElement(fooHelpers); + + // p0 gets IDLE. + final Subchannel subchannel0 = mock(Subchannel.class); + helper0.updateBalancingState( + IDLE, + new SubchannelPicker() { + @Override + public PickResult pickSubchannel(PickSubchannelArgs args) { + return PickResult.withSubchannel(subchannel0); + } + }); + assertCurrentPickerPicksIdleSubchannel(subchannel0); + + // p0 fails over to p1 immediately. + helper0.updateBalancingState(TRANSIENT_FAILURE, new ErrorPicker(Status.ABORTED)); + assertLatestConnectivityState(CONNECTING); + assertThat(fooBalancers).hasSize(2); + assertThat(fooHelpers).hasSize(2); + LoadBalancer balancer1 = Iterables.getLast(fooBalancers); + + // p1 timeout, and fails over to p2 + fakeClock.forwardTime(10, TimeUnit.SECONDS); + assertLatestConnectivityState(CONNECTING); + assertThat(fooBalancers).hasSize(3); + assertThat(fooHelpers).hasSize(3); + LoadBalancer balancer2 = Iterables.getLast(fooBalancers); + Helper helper2 = Iterables.getLast(fooHelpers); + + // p2 gets IDLE + final Subchannel subchannel1 = mock(Subchannel.class); + helper2.updateBalancingState( + IDLE, + new SubchannelPicker() { + @Override + public PickResult pickSubchannel(PickSubchannelArgs args) { + return PickResult.withSubchannel(subchannel1); + } + }); + assertCurrentPickerPicksIdleSubchannel(subchannel1); + + // p0 gets back to IDLE + final Subchannel subchannel2 = mock(Subchannel.class); + helper0.updateBalancingState( + IDLE, + new SubchannelPicker() { + @Override + public PickResult pickSubchannel(PickSubchannelArgs args) { + return PickResult.withSubchannel(subchannel2); + } + }); + assertCurrentPickerPicksIdleSubchannel(subchannel2); + + // p2 fails but does not affect overall picker + helper2.updateBalancingState(TRANSIENT_FAILURE, new ErrorPicker(Status.UNAVAILABLE)); + assertCurrentPickerPicksIdleSubchannel(subchannel2); + + // p0 fails over to p3 immediately since p1 already timeout and p2 already in TRANSIENT_FAILURE. + helper0.updateBalancingState(TRANSIENT_FAILURE, new ErrorPicker(Status.UNAVAILABLE)); + assertLatestConnectivityState(CONNECTING); + assertThat(fooBalancers).hasSize(4); + assertThat(fooHelpers).hasSize(4); + LoadBalancer balancer3 = Iterables.getLast(fooBalancers); + Helper helper3 = Iterables.getLast(fooHelpers); + + // p3 timeout then the channel should go to TRANSIENT_FAILURE + fakeClock.forwardTime(10, TimeUnit.SECONDS); + assertCurrentPickerReturnsError(Status.Code.UNAVAILABLE, "timeout"); + + // p3 fails then the picker should have error status updated + helper3.updateBalancingState( + TRANSIENT_FAILURE, new ErrorPicker(Status.DATA_LOSS.withDescription("foo"))); + assertCurrentPickerReturnsError(Status.Code.DATA_LOSS, "foo"); + + // p2 gets back to IDLE + final Subchannel subchannel3 = mock(Subchannel.class); + helper2.updateBalancingState( + IDLE, + new SubchannelPicker() { + @Override + public PickResult pickSubchannel(PickSubchannelArgs args) { + return PickResult.withSubchannel(subchannel3); + } + }); + assertCurrentPickerPicksIdleSubchannel(subchannel3); + + // p0 gets back to IDLE + final Subchannel subchannel4 = mock(Subchannel.class); + helper0.updateBalancingState( + IDLE, + new SubchannelPicker() { + @Override + public PickResult pickSubchannel(PickSubchannelArgs args) { + return PickResult.withSubchannel(subchannel4); + } + }); + assertCurrentPickerPicksIdleSubchannel(subchannel4); + + // p0 fails over to p2 and picker is updated to p2's existing picker. + helper0.updateBalancingState(TRANSIENT_FAILURE, new ErrorPicker(Status.UNAVAILABLE)); + assertCurrentPickerPicksIdleSubchannel(subchannel3); + + // Deactivate child balancer get deleted. + fakeClock.forwardTime(15, TimeUnit.MINUTES); + verify(balancer0, never()).shutdown(); + verify(balancer1, never()).shutdown(); + verify(balancer2, never()).shutdown(); + verify(balancer3).shutdown(); + } + @Test public void bypassReresolutionRequestsIfConfiged() { PriorityChildConfig priorityChildConfig0 = @@ -472,4 +606,10 @@ private void assertCurrentPickerPicksSubchannel(Subchannel expectedSubchannelToP PickResult pickResult = pickerCaptor.getValue().pickSubchannel(mock(PickSubchannelArgs.class)); assertThat(pickResult.getSubchannel()).isEqualTo(expectedSubchannelToPick); } + + private void assertCurrentPickerPicksIdleSubchannel(Subchannel expectedSubchannelToPick) { + assertLatestConnectivityState(IDLE); + PickResult pickResult = pickerCaptor.getValue().pickSubchannel(mock(PickSubchannelArgs.class)); + assertThat(pickResult.getSubchannel()).isEqualTo(expectedSubchannelToPick); + } } From 3179bc3be0a81bfac2d4dd1d199f9cd0d2223b2c Mon Sep 17 00:00:00 2001 From: "Penn (Dapeng) Zhang" Date: Tue, 18 Jan 2022 08:16:21 -0800 Subject: [PATCH 14/68] Revert "use charset from StandardCharsets instead of 'Charset.forName' (#8779)" This reverts commit a74a3ad83498a3a939d863790670914b3ebd6776. --- api/src/jmh/java/io/grpc/StatusBenchmark.java | 8 ++++---- api/src/main/java/io/grpc/DecompressorRegistry.java | 4 ++-- api/src/main/java/io/grpc/InternalMetadata.java | 3 +-- api/src/test/java/io/grpc/StatusTest.java | 4 +--- .../main/java/io/grpc/internal/ClientCallImpl.java | 4 ++-- core/src/main/java/io/grpc/internal/GrpcUtil.java | 3 +-- .../main/java/io/grpc/cronet/CronetClientStream.java | 10 +++++----- .../java/io/grpc/cronet/CronetClientStreamTest.java | 12 ++++++------ .../io/grpc/examples/advanced/JsonMarshaller.java | 3 +-- .../io/grpc/examples/routeguide/RouteGuideUtil.java | 4 ++-- .../grpc/testing/integration/TestServiceClient.java | 3 +-- .../io/grpc/testing/integration/CompressionTest.java | 4 ++-- .../java/io/grpc/okhttp/OkHttpClientStreamTest.java | 4 ++-- .../main/java/io/grpc/okhttp/internal/Util.java | 3 +-- .../io/grpc/protobuf/services/BinlogHelperTest.java | 3 +-- 15 files changed, 32 insertions(+), 40 deletions(-) diff --git a/api/src/jmh/java/io/grpc/StatusBenchmark.java b/api/src/jmh/java/io/grpc/StatusBenchmark.java index 846682c3298..e3f087c6204 100644 --- a/api/src/jmh/java/io/grpc/StatusBenchmark.java +++ b/api/src/jmh/java/io/grpc/StatusBenchmark.java @@ -16,7 +16,7 @@ package io.grpc; -import java.nio.charset.StandardCharsets; +import java.nio.charset.Charset; import java.util.concurrent.TimeUnit; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; @@ -57,7 +57,7 @@ public byte[] messageEncodeEscape() { @OutputTimeUnit(TimeUnit.NANOSECONDS) public String messageDecodePlain() { return Status.MESSAGE_KEY.parseBytes( - "Unexpected RST in stream".getBytes(StandardCharsets.US_ASCII)); + "Unexpected RST in stream".getBytes(Charset.forName("US-ASCII"))); } /** @@ -68,7 +68,7 @@ public String messageDecodePlain() { @OutputTimeUnit(TimeUnit.NANOSECONDS) public String messageDecodeEscape() { return Status.MESSAGE_KEY.parseBytes( - "Some Error%10Wasabi and Horseradish are the same".getBytes(StandardCharsets.US_ASCII)); + "Some Error%10Wasabi and Horseradish are the same".getBytes(Charset.forName("US-ASCII"))); } /** @@ -88,7 +88,7 @@ public byte[] codeEncode() { @BenchmarkMode(Mode.SampleTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) public Status codeDecode() { - return Status.CODE_KEY.parseBytes("15".getBytes(StandardCharsets.US_ASCII)); + return Status.CODE_KEY.parseBytes("15".getBytes(Charset.forName("US-ASCII"))); } } diff --git a/api/src/main/java/io/grpc/DecompressorRegistry.java b/api/src/main/java/io/grpc/DecompressorRegistry.java index b2669d3c0fc..ea0433d8d0a 100644 --- a/api/src/main/java/io/grpc/DecompressorRegistry.java +++ b/api/src/main/java/io/grpc/DecompressorRegistry.java @@ -20,7 +20,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.base.Joiner; -import java.nio.charset.StandardCharsets; +import java.nio.charset.Charset; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; @@ -85,7 +85,7 @@ private DecompressorRegistry(Decompressor d, boolean advertised, DecompressorReg decompressors = Collections.unmodifiableMap(newDecompressors); advertisedDecompressors = ACCEPT_ENCODING_JOINER.join(getAdvertisedMessageEncodings()) - .getBytes(StandardCharsets.US_ASCII); + .getBytes(Charset.forName("US-ASCII")); } private DecompressorRegistry() { diff --git a/api/src/main/java/io/grpc/InternalMetadata.java b/api/src/main/java/io/grpc/InternalMetadata.java index cb98ad752b2..2823882952f 100644 --- a/api/src/main/java/io/grpc/InternalMetadata.java +++ b/api/src/main/java/io/grpc/InternalMetadata.java @@ -20,7 +20,6 @@ import io.grpc.Metadata.AsciiMarshaller; import io.grpc.Metadata.BinaryStreamMarshaller; import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; /** * Internal {@link Metadata} accessor. This is intended for use by io.grpc.internal, and the @@ -43,7 +42,7 @@ public interface TrustedAsciiMarshaller extends Metadata.TrustedAsciiMarshall * Copy of StandardCharsets, which is only available on Java 1.7 and above. */ @Internal - public static final Charset US_ASCII = StandardCharsets.US_ASCII; + public static final Charset US_ASCII = Charset.forName("US-ASCII"); /** * An instance of base64 encoder that omits padding. diff --git a/api/src/test/java/io/grpc/StatusTest.java b/api/src/test/java/io/grpc/StatusTest.java index aca5dc8ee66..9abeba436f7 100644 --- a/api/src/test/java/io/grpc/StatusTest.java +++ b/api/src/test/java/io/grpc/StatusTest.java @@ -21,8 +21,6 @@ import io.grpc.Status.Code; import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; - import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -30,7 +28,7 @@ /** Unit tests for {@link Status}. */ @RunWith(JUnit4.class) public class StatusTest { - private final Charset ascii = StandardCharsets.US_ASCII; + private final Charset ascii = Charset.forName("US-ASCII"); @Test public void verifyExceptionMessage() { diff --git a/core/src/main/java/io/grpc/internal/ClientCallImpl.java b/core/src/main/java/io/grpc/internal/ClientCallImpl.java index e60f9e54b47..db1a992b968 100644 --- a/core/src/main/java/io/grpc/internal/ClientCallImpl.java +++ b/core/src/main/java/io/grpc/internal/ClientCallImpl.java @@ -53,7 +53,7 @@ import io.perfmark.PerfMark; import io.perfmark.Tag; import java.io.InputStream; -import java.nio.charset.StandardCharsets; +import java.nio.charset.Charset; import java.util.Locale; import java.util.concurrent.CancellationException; import java.util.concurrent.Executor; @@ -71,7 +71,7 @@ final class ClientCallImpl extends ClientCall { private static final Logger log = Logger.getLogger(ClientCallImpl.class.getName()); private static final byte[] FULL_STREAM_DECOMPRESSION_ENCODINGS - = "gzip".getBytes(StandardCharsets.US_ASCII); + = "gzip".getBytes(Charset.forName("US-ASCII")); private final MethodDescriptor method; private final Tag tag; diff --git a/core/src/main/java/io/grpc/internal/GrpcUtil.java b/core/src/main/java/io/grpc/internal/GrpcUtil.java index ee54a1d645b..ab4bf7f7657 100644 --- a/core/src/main/java/io/grpc/internal/GrpcUtil.java +++ b/core/src/main/java/io/grpc/internal/GrpcUtil.java @@ -55,7 +55,6 @@ import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.List; import java.util.concurrent.Executor; @@ -76,7 +75,7 @@ public final class GrpcUtil { private static final Logger log = Logger.getLogger(GrpcUtil.class.getName()); - public static final Charset US_ASCII = StandardCharsets.US_ASCII; + public static final Charset US_ASCII = Charset.forName("US-ASCII"); /** * {@link io.grpc.Metadata.Key} for the timeout header. diff --git a/cronet/src/main/java/io/grpc/cronet/CronetClientStream.java b/cronet/src/main/java/io/grpc/cronet/CronetClientStream.java index 7329ed08c5d..d44b716146e 100644 --- a/cronet/src/main/java/io/grpc/cronet/CronetClientStream.java +++ b/cronet/src/main/java/io/grpc/cronet/CronetClientStream.java @@ -44,7 +44,7 @@ import java.lang.reflect.Method; import java.nio.Buffer; import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -408,10 +408,10 @@ private void setGrpcHeaders(BidirectionalStream.Builder builder) { // String and byte array. byte[][] serializedHeaders = TransportFrameUtil.toHttp2Headers(headers); for (int i = 0; i < serializedHeaders.length; i += 2) { - String key = new String(serializedHeaders[i], StandardCharsets.UTF_8); + String key = new String(serializedHeaders[i], Charset.forName("UTF-8")); // TODO(ericgribkoff): log an error or throw an exception if (isApplicationHeader(key)) { - String value = new String(serializedHeaders[i + 1], StandardCharsets.UTF_8); + String value = new String(serializedHeaders[i + 1], Charset.forName("UTF-8")); builder.addHeader(key, value); } } @@ -588,8 +588,8 @@ private void reportHeaders(List> headers, boolean endO byte[][] headerValues = new byte[headerList.size()][]; for (int i = 0; i < headerList.size(); i += 2) { - headerValues[i] = headerList.get(i).getBytes(StandardCharsets.UTF_8); - headerValues[i + 1] = headerList.get(i + 1).getBytes(StandardCharsets.UTF_8); + headerValues[i] = headerList.get(i).getBytes(Charset.forName("UTF-8")); + headerValues[i + 1] = headerList.get(i + 1).getBytes(Charset.forName("UTF-8")); } Metadata metadata = InternalMetadata.newMetadata(TransportFrameUtil.toRawSerializedHeaders(headerValues)); diff --git a/cronet/src/test/java/io/grpc/cronet/CronetClientStreamTest.java b/cronet/src/test/java/io/grpc/cronet/CronetClientStreamTest.java index 9b63601d717..48ce71c493f 100644 --- a/cronet/src/test/java/io/grpc/cronet/CronetClientStreamTest.java +++ b/cronet/src/test/java/io/grpc/cronet/CronetClientStreamTest.java @@ -46,7 +46,7 @@ import java.io.ByteArrayInputStream; import java.nio.Buffer; import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -168,7 +168,7 @@ public void write() { for (int i = 0; i < 5; ++i) { requests[i] = new String("request" + String.valueOf(i)); buffers[i] = allocator.allocate(requests[i].length()); - buffers[i].write(requests[i].getBytes(StandardCharsets.UTF_8), 0, requests[i].length()); + buffers[i].write(requests[i].getBytes(Charset.forName("UTF-8")), 0, requests[i].length()); // The 3rd and 5th writeFrame calls have flush=true. clientStream.abstractClientStreamSink().writeFrame(buffers[i], false, i == 2 || i == 4, 1); } @@ -261,7 +261,7 @@ public void read() { callback.onReadCompleted( cronetStream, info, - createMessageFrame(new String("response1").getBytes(StandardCharsets.UTF_8)), + createMessageFrame(new String("response1").getBytes(Charset.forName("UTF-8"))), false); // Haven't request any message, so no callback is called here. verify(clientListener, times(0)).messagesAvailable(isA(MessageProducer.class)); @@ -293,7 +293,7 @@ public void streamSucceeded() { CronetWritableBufferAllocator allocator = new CronetWritableBufferAllocator(); String request = new String("request"); WritableBuffer writableBuffer = allocator.allocate(request.length()); - writableBuffer.write(request.getBytes(StandardCharsets.UTF_8), 0, request.length()); + writableBuffer.write(request.getBytes(Charset.forName("UTF-8")), 0, request.length()); clientStream.abstractClientStreamSink().writeFrame(writableBuffer, false, true, 1); ArgumentCaptor bufferCaptor = ArgumentCaptor.forClass(ByteBuffer.class); verify(cronetStream, times(1)).write(bufferCaptor.capture(), isA(Boolean.class)); @@ -312,7 +312,7 @@ public void streamSucceeded() { callback.onReadCompleted( cronetStream, info, - createMessageFrame(new String("response").getBytes(StandardCharsets.UTF_8)), + createMessageFrame(new String("response").getBytes(Charset.forName("UTF-8"))), false); verify(clientListener, times(1)).messagesAvailable(isA(MessageProducer.class)); verify(cronetStream, times(2)).read(isA(ByteBuffer.class)); @@ -688,7 +688,7 @@ public void getUnaryRequest() { .newBidirectionalStreamBuilder( isA(String.class), isA(BidirectionalStream.Callback.class), isA(Executor.class)); - byte[] msg = "request".getBytes(StandardCharsets.UTF_8); + byte[] msg = "request".getBytes(Charset.forName("UTF-8")); stream.writeMessage(new ByteArrayInputStream(msg)); // We still haven't built the stream or sent anything. verify(cronetStream, times(0)).write(isA(ByteBuffer.class), isA(Boolean.class)); diff --git a/examples/src/main/java/io/grpc/examples/advanced/JsonMarshaller.java b/examples/src/main/java/io/grpc/examples/advanced/JsonMarshaller.java index fbe3fa50afc..8b3068cc009 100644 --- a/examples/src/main/java/io/grpc/examples/advanced/JsonMarshaller.java +++ b/examples/src/main/java/io/grpc/examples/advanced/JsonMarshaller.java @@ -30,7 +30,6 @@ import java.io.InputStreamReader; import java.io.Reader; import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; /** * A {@link Marshaller} for JSON. This marshals in the Protobuf 3 format described here: @@ -59,7 +58,7 @@ public static Marshaller jsonMarshaller(final T defaultIn public static Marshaller jsonMarshaller( final T defaultInstance, final Parser parser, final Printer printer) { - final Charset charset = StandardCharsets.UTF_8; + final Charset charset = Charset.forName("UTF-8"); return new Marshaller() { @Override diff --git a/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideUtil.java b/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideUtil.java index 0794674091f..6e49492da06 100644 --- a/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideUtil.java +++ b/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideUtil.java @@ -22,7 +22,7 @@ import java.io.InputStreamReader; import java.io.Reader; import java.net.URL; -import java.nio.charset.StandardCharsets; +import java.nio.charset.Charset; import java.util.List; /** @@ -58,7 +58,7 @@ public static URL getDefaultFeaturesFile() { public static List parseFeatures(URL file) throws IOException { InputStream input = file.openStream(); try { - Reader reader = new InputStreamReader(input, StandardCharsets.UTF_8); + Reader reader = new InputStreamReader(input, Charset.forName("UTF-8")); try { FeatureDatabase.Builder database = FeatureDatabase.newBuilder(); JsonFormat.parser().merge(reader, database); diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceClient.java b/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceClient.java index 8ac50419d5f..914db12e5a8 100644 --- a/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceClient.java +++ b/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceClient.java @@ -40,7 +40,6 @@ import java.io.File; import java.io.FileInputStream; import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; @@ -51,7 +50,7 @@ */ public class TestServiceClient { - private static final Charset UTF_8 = StandardCharsets.UTF_8; + private static final Charset UTF_8 = Charset.forName("UTF-8"); /** * The main application allowing this client to be launched from the command line. diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/CompressionTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/CompressionTest.java index fdd3ac63011..208eb40c438 100644 --- a/interop-testing/src/test/java/io/grpc/testing/integration/CompressionTest.java +++ b/interop-testing/src/test/java/io/grpc/testing/integration/CompressionTest.java @@ -52,7 +52,7 @@ import io.grpc.testing.integration.Messages.SimpleResponse; import io.grpc.testing.integration.TestServiceGrpc.TestServiceBlockingStub; import io.grpc.testing.integration.TransportCompressionTest.Fzip; -import java.nio.charset.StandardCharsets; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -312,6 +312,6 @@ public void onHeaders(Metadata headers) { } private static void assertEqualsString(String expected, byte[] actual) { - assertEquals(expected, new String(actual, StandardCharsets.US_ASCII)); + assertEquals(expected, new String(actual, Charset.forName("US-ASCII"))); } } diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientStreamTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientStreamTest.java index 46a49463e5f..99a9159eab0 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientStreamTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientStreamTest.java @@ -42,7 +42,7 @@ import io.grpc.okhttp.internal.framed.Header; import java.io.ByteArrayInputStream; import java.io.IOException; -import java.nio.charset.StandardCharsets; +import java.nio.charset.Charset; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.junit.Before; @@ -243,7 +243,7 @@ public void getUnaryRequest() throws IOException { eq(false), eq(false), eq(3), eq(0), headersCaptor.capture()); verify(transport, times(0)).streamReadyToStart(isA(OkHttpClientStream.class)); - byte[] msg = "request".getBytes(StandardCharsets.UTF_8); + byte[] msg = "request".getBytes(Charset.forName("UTF-8")); stream.writeMessage(new ByteArrayInputStream(msg)); stream.halfClose(); verify(transport).streamReadyToStart(eq(stream)); diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Util.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Util.java index a961c605656..556d849c705 100644 --- a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Util.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Util.java @@ -27,7 +27,6 @@ import java.net.ServerSocket; import java.net.Socket; import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; @@ -48,7 +47,7 @@ public final class Util { public static final String[] EMPTY_STRING_ARRAY = new String[0]; /** A cheap and type-safe constant for the UTF-8 Charset. */ - public static final Charset UTF_8 = StandardCharsets.UTF_8; + public static final Charset UTF_8 = Charset.forName("UTF-8"); private Util() { } diff --git a/services/src/test/java/io/grpc/protobuf/services/BinlogHelperTest.java b/services/src/test/java/io/grpc/protobuf/services/BinlogHelperTest.java index 23022192be3..37e503ccd41 100644 --- a/services/src/test/java/io/grpc/protobuf/services/BinlogHelperTest.java +++ b/services/src/test/java/io/grpc/protobuf/services/BinlogHelperTest.java @@ -80,7 +80,6 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -95,7 +94,7 @@ /** Tests for {@link BinlogHelper}. */ @RunWith(JUnit4.class) public final class BinlogHelperTest { - private static final Charset US_ASCII = StandardCharsets.US_ASCII; + private static final Charset US_ASCII = Charset.forName("US-ASCII"); private static final BinlogHelper HEADER_FULL = new Builder().header(Integer.MAX_VALUE).build(); private static final BinlogHelper HEADER_256 = new Builder().header(256).build(); private static final BinlogHelper MSG_FULL = new Builder().msg(Integer.MAX_VALUE).build(); From cf4cd65707fb8e8fb501754996dea215525503ca Mon Sep 17 00:00:00 2001 From: "Penn (Dapeng) Zhang" Date: Tue, 18 Jan 2022 08:36:23 -0800 Subject: [PATCH 15/68] Revert "all: clean up code related to android api level less than 19" This reverts commit 3ad4d9bfb7f7407ec0b9637ee1de508051192676. --- api/build.gradle | 2 +- auth/build.gradle | 2 +- context/build.gradle | 2 +- core/build.gradle | 2 +- .../main/java/io/grpc/internal/ProxyDetectorImpl.java | 9 ++++++++- okhttp/build.gradle | 2 +- protobuf-lite/build.gradle | 2 +- stub/build.gradle | 2 +- 8 files changed, 15 insertions(+), 8 deletions(-) diff --git a/api/build.gradle b/api/build.gradle index 3c7ff8221ee..9f5e6163153 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -25,7 +25,7 @@ dependencies { jmh project(':grpc-core') signature "org.codehaus.mojo.signature:java17:1.0@signature" - signature "net.sf.androidscents.signature:android-api-level-19:4.4.2_r4@signature" + signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature" } javadoc { diff --git a/auth/build.gradle b/auth/build.gradle index 791402fce59..233de359b49 100644 --- a/auth/build.gradle +++ b/auth/build.gradle @@ -14,5 +14,5 @@ dependencies { testImplementation project(':grpc-testing'), libraries.google_auth_oauth2_http signature "org.codehaus.mojo.signature:java17:1.0@signature" - signature "net.sf.androidscents.signature:android-api-level-19:4.4.2_r4@signature" + signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature" } diff --git a/context/build.gradle b/context/build.gradle index ba530df54fe..1e69296c825 100644 --- a/context/build.gradle +++ b/context/build.gradle @@ -18,5 +18,5 @@ dependencies { exclude group: 'junit', module: 'junit' } signature "org.codehaus.mojo.signature:java17:1.0@signature" - signature "net.sf.androidscents.signature:android-api-level-19:4.4.2_r4@signature" + signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature" } diff --git a/core/build.gradle b/core/build.gradle index ed8e5400765..bc8231fd9e1 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -43,7 +43,7 @@ dependencies { jmh project(':grpc-testing') signature "org.codehaus.mojo.signature:java17:1.0@signature" - signature "net.sf.androidscents.signature:android-api-level-19:4.4.2_r4@signature" + signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature" } javadoc { diff --git a/core/src/main/java/io/grpc/internal/ProxyDetectorImpl.java b/core/src/main/java/io/grpc/internal/ProxyDetectorImpl.java index 0cc1dd3aaca..3e7dd010e22 100644 --- a/core/src/main/java/io/grpc/internal/ProxyDetectorImpl.java +++ b/core/src/main/java/io/grpc/internal/ProxyDetectorImpl.java @@ -202,7 +202,14 @@ public ProxiedSocketAddress proxyFor(SocketAddress targetServerAddress) throws I private ProxiedSocketAddress detectProxy(InetSocketAddress targetAddr) throws IOException { URI uri; - String host = GrpcUtil.getHost(targetAddr); + String host; + try { + host = GrpcUtil.getHost(targetAddr); + } catch (Throwable t) { + // Workaround for Android API levels < 19 if getHostName causes a NetworkOnMainThreadException + log.log(Level.WARNING, "Failed to get host for proxy lookup, proceeding without proxy", t); + return null; + } try { uri = new URI( diff --git a/okhttp/build.gradle b/okhttp/build.gradle index 3271118feda..999f21e7c10 100644 --- a/okhttp/build.gradle +++ b/okhttp/build.gradle @@ -22,7 +22,7 @@ dependencies { project(':grpc-testing'), project(':grpc-netty') signature "org.codehaus.mojo.signature:java17:1.0@signature" - signature "net.sf.androidscents.signature:android-api-level-19:4.4.2_r4@signature" + signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature" } project.sourceSets { diff --git a/protobuf-lite/build.gradle b/protobuf-lite/build.gradle index 75cca43f0e6..7b58309c414 100644 --- a/protobuf-lite/build.gradle +++ b/protobuf-lite/build.gradle @@ -18,7 +18,7 @@ dependencies { testImplementation project(':grpc-core') signature "org.codehaus.mojo.signature:java17:1.0@signature" - signature "net.sf.androidscents.signature:android-api-level-19:4.4.2_r4@signature" + signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature" } compileTestJava { diff --git a/stub/build.gradle b/stub/build.gradle index 7d8040a87c2..2b5a6a4edb6 100644 --- a/stub/build.gradle +++ b/stub/build.gradle @@ -14,7 +14,7 @@ dependencies { testImplementation libraries.truth, project(':grpc-testing') signature "org.codehaus.mojo.signature:java17:1.0@signature" - signature "net.sf.androidscents.signature:android-api-level-19:4.4.2_r4@signature" + signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature" } javadoc { From d1e0be69199fada9f8a5eb03a01c755d490dbe80 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Tue, 18 Jan 2022 10:18:16 -0800 Subject: [PATCH 16/68] all: fix various gradle build warnings --- alts/build.gradle | 6 +++--- android-interop-testing/build.gradle | 4 ++++ android/build.gradle | 4 ++-- .../io/grpc/android/AndroidChannelBuilder.java | 4 ++++ binder/build.gradle | 4 ++-- .../java/io/grpc/binder/internal/Inbound.java | 4 ++-- cronet/build.gradle | 4 ++-- .../io/grpc/cronet/CronetClientStreamTest.java | 16 ++++++++-------- netty/shaded/build.gradle | 6 +++--- xds/build.gradle | 4 ++-- 10 files changed, 32 insertions(+), 24 deletions(-) diff --git a/alts/build.gradle b/alts/build.gradle index 25e8b160243..a056799d4f0 100644 --- a/alts/build.gradle +++ b/alts/build.gradle @@ -62,14 +62,14 @@ javadoc { } jar { - // Must use a different classifier to avoid conflicting with shadowJar - classifier = 'original' + // Must use a different archiveClassifier to avoid conflicting with shadowJar + archiveClassifier = 'original' } // We want to use grpc-netty-shaded instead of grpc-netty. But we also want our // source to work with Bazel, so we rewrite the code as part of the build. shadowJar { - classifier = null + archiveClassifier = null dependencies { exclude(dependency {true}) } diff --git a/android-interop-testing/build.gradle b/android-interop-testing/build.gradle index 9ede49593dc..e40507e6a3b 100644 --- a/android-interop-testing/build.gradle +++ b/android-interop-testing/build.gradle @@ -102,6 +102,10 @@ tasks.withType(JavaCompile) { "-Xlint:-cast" ] appendToProperty(it.options.errorprone.excludedPaths, ".*/R.java", "|") + appendToProperty( + it.options.errorprone.excludedPaths, + ".*/src/generated/.*", + "|") // Reuses source code from grpc-interop-testing, which targets Java 7 (no method references) options.errorprone.check("UnnecessaryAnonymousClass", CheckSeverity.OFF) } diff --git a/android/build.gradle b/android/build.gradle index b0916f9e04c..7e32ac5ca2f 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -59,12 +59,12 @@ task javadocs(type: Javadoc) { } task javadocJar(type: Jar, dependsOn: javadocs) { - classifier = 'javadoc' + archiveClassifier = 'javadoc' from javadocs.destinationDir } task sourcesJar(type: Jar) { - classifier = 'sources' + archiveClassifier = 'sources' from android.sourceSets.main.java.srcDirs } diff --git a/android/src/main/java/io/grpc/android/AndroidChannelBuilder.java b/android/src/main/java/io/grpc/android/AndroidChannelBuilder.java index 8c69ca68b5f..19b2b1a2347 100644 --- a/android/src/main/java/io/grpc/android/AndroidChannelBuilder.java +++ b/android/src/main/java/io/grpc/android/AndroidChannelBuilder.java @@ -27,6 +27,7 @@ import android.util.Log; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; +import com.google.errorprone.annotations.InlineMe; import io.grpc.CallOptions; import io.grpc.ClientCall; import io.grpc.ConnectivityState; @@ -90,6 +91,9 @@ public static AndroidChannelBuilder forAddress(String name, int port) { */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/6043") @Deprecated + @InlineMe( + replacement = "AndroidChannelBuilder.usingBuilder(builder)", + imports = "io.grpc.android.AndroidChannelBuilder") public static AndroidChannelBuilder fromBuilder(ManagedChannelBuilder builder) { return usingBuilder(builder); } diff --git a/binder/build.gradle b/binder/build.gradle index fa9d0d8cee9..dc3da4c9a72 100644 --- a/binder/build.gradle +++ b/binder/build.gradle @@ -111,12 +111,12 @@ task javadocs(type: Javadoc) { } task javadocJar(type: Jar, dependsOn: javadocs) { - classifier = 'javadoc' + archiveClassifier = 'javadoc' from javadocs.destinationDir } task sourcesJar(type: Jar) { - classifier = 'sources' + archiveClassifier = 'sources' from android.sourceSets.main.java.srcDirs } diff --git a/binder/src/main/java/io/grpc/binder/internal/Inbound.java b/binder/src/main/java/io/grpc/binder/internal/Inbound.java index f28b9bfb29a..da1a2961546 100644 --- a/binder/src/main/java/io/grpc/binder/internal/Inbound.java +++ b/binder/src/main/java/io/grpc/binder/internal/Inbound.java @@ -345,8 +345,8 @@ final synchronized void handleTransaction(Parcel parcel) { int index = parcel.readInt(); boolean hasPrefix = TransactionUtils.hasFlag(flags, TransactionUtils.FLAG_PREFIX); boolean hasMessageData = - (TransactionUtils.hasFlag(flags, TransactionUtils.FLAG_MESSAGE_DATA)); - boolean hasSuffix = (TransactionUtils.hasFlag(flags, TransactionUtils.FLAG_SUFFIX)); + TransactionUtils.hasFlag(flags, TransactionUtils.FLAG_MESSAGE_DATA); + boolean hasSuffix = TransactionUtils.hasFlag(flags, TransactionUtils.FLAG_SUFFIX); if (hasPrefix) { handlePrefix(flags, parcel); onDeliveryState(State.PREFIX_DELIVERED); diff --git a/cronet/build.gradle b/cronet/build.gradle index e0a0b7b53a7..d66eaf3a182 100644 --- a/cronet/build.gradle +++ b/cronet/build.gradle @@ -70,12 +70,12 @@ task javadocs(type: Javadoc) { } task javadocJar(type: Jar, dependsOn: javadocs) { - classifier = 'javadoc' + archiveClassifier = 'javadoc' from javadocs.destinationDir } task sourcesJar(type: Jar) { - classifier = 'sources' + archiveClassifier = 'sources' from android.sourceSets.main.java.srcDirs } diff --git a/cronet/src/test/java/io/grpc/cronet/CronetClientStreamTest.java b/cronet/src/test/java/io/grpc/cronet/CronetClientStreamTest.java index 48ce71c493f..1d17dfe9be5 100644 --- a/cronet/src/test/java/io/grpc/cronet/CronetClientStreamTest.java +++ b/cronet/src/test/java/io/grpc/cronet/CronetClientStreamTest.java @@ -245,7 +245,7 @@ public void read() { verify(cronetStream, times(0)).read(isA(ByteBuffer.class)); UrlResponseInfo info = new UrlResponseInfoImpl( - new ArrayList(), 200, "", responseHeader("200"), false, "", ""); + new ArrayList<>(), 200, "", responseHeader("200"), false, "", "", 0); callback.onResponseHeadersReceived(cronetStream, info); verify(cronetStream, times(1)).read(isA(ByteBuffer.class)); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(Metadata.class); @@ -305,7 +305,7 @@ public void streamSucceeded() { clientStream.request(2); UrlResponseInfo info = new UrlResponseInfoImpl( - new ArrayList(), 200, "", responseHeader("200"), false, "", ""); + new ArrayList<>(), 200, "", responseHeader("200"), false, "", "", 0); callback.onResponseHeadersReceived(cronetStream, info); verify(cronetStream, times(1)).read(isA(ByteBuffer.class)); // Receive one message @@ -363,7 +363,7 @@ public void streamSucceededWithGrpcError() { clientStream.request(2); UrlResponseInfo info = new UrlResponseInfoImpl( - new ArrayList(), 200, "", responseHeader("200"), false, "", ""); + new ArrayList<>(), 200, "", responseHeader("200"), false, "", "", 0); callback.onResponseHeadersReceived(cronetStream, info); verify(cronetStream, times(1)).read(isA(ByteBuffer.class)); @@ -418,7 +418,7 @@ public void streamFailedAfterResponseHeaderReceived() { // Receive response header UrlResponseInfo info = new UrlResponseInfoImpl( - new ArrayList(), 200, "", responseHeader("200"), false, "", ""); + new ArrayList<>(), 200, "", responseHeader("200"), false, "", "", 0); callback.onResponseHeadersReceived(cronetStream, info); CronetException exception = mock(CronetException.class); @@ -446,7 +446,7 @@ public void streamFailedAfterTrailerReceived() { // Receive response header UrlResponseInfo info = new UrlResponseInfoImpl( - new ArrayList(), 200, "", responseHeader("200"), false, "", ""); + new ArrayList<>(), 200, "", responseHeader("200"), false, "", "", 0); callback.onResponseHeadersReceived(cronetStream, info); // Report trailer but not endOfStream. @@ -478,7 +478,7 @@ public void streamFailedAfterTrailerAndEndOfStreamReceived() { // Receive response header UrlResponseInfo info = new UrlResponseInfoImpl( - new ArrayList(), 200, "", responseHeader("200"), false, "", ""); + new ArrayList<>(), 200, "", responseHeader("200"), false, "", "", 0); callback.onResponseHeadersReceived(cronetStream, info); // Report trailer and endOfStream @@ -531,7 +531,7 @@ public void reportTrailersWhenTrailersReceivedBeforeReadClosed() { callback.onStreamReady(cronetStream); UrlResponseInfo info = new UrlResponseInfoImpl( - new ArrayList(), 200, "", responseHeader("200"), false, "", ""); + new ArrayList<>(), 200, "", responseHeader("200"), false, "", "", 0); callback.onResponseHeadersReceived(cronetStream, info); // Receive trailer first ((CronetClientStream.BidirectionalStreamCallback) callback) @@ -560,7 +560,7 @@ public void reportTrailersWhenTrailersReceivedAfterReadClosed() { callback.onStreamReady(cronetStream); UrlResponseInfo info = new UrlResponseInfoImpl( - new ArrayList(), 200, "", responseHeader("200"), false, "", ""); + new ArrayList<>(), 200, "", responseHeader("200"), false, "", "", 0); callback.onResponseHeadersReceived(cronetStream, info); // Receive cronet's endOfStream callback.onReadCompleted(cronetStream, null, ByteBuffer.allocate(0), true); diff --git a/netty/shaded/build.gradle b/netty/shaded/build.gradle index 6b1dad644d1..3bd1b087d85 100644 --- a/netty/shaded/build.gradle +++ b/netty/shaded/build.gradle @@ -29,12 +29,12 @@ dependencies { } jar { - // Must use a different classifier to avoid conflicting with shadowJar - classifier = 'original' + // Must use a different archiveClassifier to avoid conflicting with shadowJar + archiveClassifier = 'original' } shadowJar { - classifier = null + archiveClassifier = null dependencies { include(project(':grpc-netty')) include(dependency('io.netty:')) diff --git a/xds/build.gradle b/xds/build.gradle index 16b1c877906..60bd4c7c60d 100644 --- a/xds/build.gradle +++ b/xds/build.gradle @@ -86,7 +86,7 @@ sourceSets { configureProtoCompilation() jar { - classifier = 'original' + archiveClassifier = 'original' } javadoc { @@ -110,7 +110,7 @@ javadoc { def prefixName = 'io.grpc.xds' shadowJar { - classifier = null + archiveClassifier = null dependencies { include(project(':grpc-xds')) } From 2c5a9e2aed2a15bb46d26e445578e4172e8c6cd0 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Tue, 18 Jan 2022 12:38:04 -0800 Subject: [PATCH 17/68] xds: Handle negative random numbers in c2p resolver This was noticed because Mockito can't mock Random in Java 17, so it was replaced with actual Random. But when doing that change it exposed that negative numbers would cause the id to have a double '-'. --- .../java/io/grpc/xds/GoogleCloudToProdNameResolver.java | 2 +- .../io/grpc/xds/GoogleCloudToProdNameResolverTest.java | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/GoogleCloudToProdNameResolver.java b/xds/src/main/java/io/grpc/xds/GoogleCloudToProdNameResolver.java index 2845d0a00e8..3ec0434a22a 100644 --- a/xds/src/main/java/io/grpc/xds/GoogleCloudToProdNameResolver.java +++ b/xds/src/main/java/io/grpc/xds/GoogleCloudToProdNameResolver.java @@ -175,7 +175,7 @@ public void run() { private ImmutableMap generateBootstrap(String zone, boolean supportIpv6) { ImmutableMap.Builder nodeBuilder = ImmutableMap.builder(); - nodeBuilder.put("id", "C2P-" + rand.nextInt()); + nodeBuilder.put("id", "C2P-" + (rand.nextInt() & Integer.MAX_VALUE)); if (!zone.isEmpty()) { nodeBuilder.put("locality", ImmutableMap.of("zone", zone)); } diff --git a/xds/src/test/java/io/grpc/xds/GoogleCloudToProdNameResolverTest.java b/xds/src/test/java/io/grpc/xds/GoogleCloudToProdNameResolverTest.java index 58be7108405..7c777b84bf7 100644 --- a/xds/src/test/java/io/grpc/xds/GoogleCloudToProdNameResolverTest.java +++ b/xds/src/test/java/io/grpc/xds/GoogleCloudToProdNameResolverTest.java @@ -104,8 +104,7 @@ public void close(Executor instance) {} @Mock private NameResolver.Listener2 mockListener; - @Mock - private Random mockRandom; + private Random random = new Random(1); @Captor private ArgumentCaptor errorCaptor; private boolean originalIsOnGcp; @@ -114,7 +113,6 @@ public void close(Executor instance) {} @Before public void setUp() { - when(mockRandom.nextInt()).thenReturn(123456789); nsRegistry.register(new FakeNsProvider("dns")); nsRegistry.register(new FakeNsProvider("xds")); originalIsOnGcp = GoogleCloudToProdNameResolver.isOnGcp; @@ -146,7 +144,7 @@ public HttpURLConnection createConnection(String url) throws IOException { } }; resolver = new GoogleCloudToProdNameResolver( - TARGET_URI, args, fakeExecutorResource, mockRandom, fakeXdsClientPoolFactory, + TARGET_URI, args, fakeExecutorResource, random, fakeXdsClientPoolFactory, nsRegistry.asFactory()); resolver.setHttpConnectionProvider(httpConnections); } @@ -183,7 +181,7 @@ public void onGcpAndNoProvidedBootstrapDelegateToXds() { Map bootstrap = fakeXdsClientPoolFactory.bootstrapRef.get(); Map node = (Map) bootstrap.get("node"); assertThat(node).containsExactly( - "id", "C2P-123456789", + "id", "C2P-991614323", "locality", ImmutableMap.of("zone", ZONE), "metadata", ImmutableMap.of("TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE", true)); Map server = Iterables.getOnlyElement( From a35336c15fe3235b14a16af117515e113c8d5cee Mon Sep 17 00:00:00 2001 From: Erik Johansson Date: Wed, 19 Jan 2022 19:14:24 +0100 Subject: [PATCH 18/68] xds: implement least_request load balancing policy (#8739) Implements least_request_experimental as defined by [A48](https://github.com/grpc/proposal/blob/master/A48-xds-least-request-lb-policy.md) These tests are mostly just a copy of RoundRobinLoadBalancerTest. The main difference is currently in the pickerLeastRequest test case. All other tests should be the same. --- .../java/io/grpc/xds/CdsLoadBalancer2.java | 5 + .../java/io/grpc/xds/ClientXdsClient.java | 19 + .../grpc/xds/ClusterResolverLoadBalancer.java | 17 +- .../ClusterResolverLoadBalancerProvider.java | 3 +- .../io/grpc/xds/LeastRequestLoadBalancer.java | 430 ++++++++++++ .../xds/LeastRequestLoadBalancerProvider.java | 80 +++ xds/src/main/java/io/grpc/xds/XdsClient.java | 16 +- .../services/io.grpc.LoadBalancerProvider | 1 + .../io/grpc/xds/CdsLoadBalancer2Test.java | 8 +- .../io/grpc/xds/ClientXdsClientDataTest.java | 52 ++ .../io/grpc/xds/ClientXdsClientTestBase.java | 119 ++-- .../io/grpc/xds/ClientXdsClientV2Test.java | 34 +- .../io/grpc/xds/ClientXdsClientV3Test.java | 33 +- .../xds/ClusterResolverLoadBalancerTest.java | 42 ++ .../LeastRequestLoadBalancerProviderTest.java | 139 ++++ .../xds/LeastRequestLoadBalancerTest.java | 632 ++++++++++++++++++ 16 files changed, 1562 insertions(+), 68 deletions(-) create mode 100644 xds/src/main/java/io/grpc/xds/LeastRequestLoadBalancer.java create mode 100644 xds/src/main/java/io/grpc/xds/LeastRequestLoadBalancerProvider.java create mode 100644 xds/src/test/java/io/grpc/xds/LeastRequestLoadBalancerProviderTest.java create mode 100644 xds/src/test/java/io/grpc/xds/LeastRequestLoadBalancerTest.java diff --git a/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java b/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java index 7735199134f..7a346e01871 100644 --- a/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java +++ b/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java @@ -32,6 +32,7 @@ import io.grpc.xds.CdsLoadBalancerProvider.CdsConfig; import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig; import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig.DiscoveryMechanism; +import io.grpc.xds.LeastRequestLoadBalancer.LeastRequestConfig; import io.grpc.xds.RingHashLoadBalancer.RingHashConfig; import io.grpc.xds.XdsClient.CdsResourceWatcher; import io.grpc.xds.XdsClient.CdsUpdate; @@ -190,6 +191,10 @@ private void handleClusterDiscovered() { lbProvider = lbRegistry.getProvider("ring_hash_experimental"); lbConfig = new RingHashConfig(root.result.minRingSize(), root.result.maxRingSize()); } + if (root.result.lbPolicy() == LbPolicy.LEAST_REQUEST) { + lbProvider = lbRegistry.getProvider("least_request_experimental"); + lbConfig = new LeastRequestConfig(root.result.choiceCount()); + } if (lbProvider == null) { lbProvider = lbRegistry.getProvider("round_robin"); lbConfig = null; diff --git a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java index 72f5db82ed0..1e090e164f4 100644 --- a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java +++ b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java @@ -44,6 +44,7 @@ import io.envoyproxy.envoy.config.cluster.v3.Cluster.CustomClusterType; import io.envoyproxy.envoy.config.cluster.v3.Cluster.DiscoveryType; import io.envoyproxy.envoy.config.cluster.v3.Cluster.LbPolicy; +import io.envoyproxy.envoy.config.cluster.v3.Cluster.LeastRequestLbConfig; import io.envoyproxy.envoy.config.cluster.v3.Cluster.RingHashLbConfig; import io.envoyproxy.envoy.config.core.v3.HttpProtocolOptions; import io.envoyproxy.envoy.config.core.v3.RoutingPriority; @@ -140,6 +141,8 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res @VisibleForTesting static final long DEFAULT_RING_HASH_LB_POLICY_MAX_RING_SIZE = 8 * 1024 * 1024L; @VisibleForTesting + static final int DEFAULT_LEAST_REQUEST_CHOICE_COUNT = 2; + @VisibleForTesting static final long MAX_RING_HASH_LB_POLICY_RING_SIZE = 8 * 1024 * 1024L; @VisibleForTesting static final String AGGREGATE_CLUSTER_TYPE_NAME = "envoy.clusters.aggregate"; @@ -161,6 +164,11 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res 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")); private static final String TYPE_URL_HTTP_CONNECTION_MANAGER_V2 = "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2" + ".HttpConnectionManager"; @@ -1616,6 +1624,17 @@ static CdsUpdate processCluster(Cluster cluster, Set retainedEdsResource updateBuilder.ringHashLbPolicy(minRingSize, maxRingSize); } else if (cluster.getLbPolicy() == LbPolicy.ROUND_ROBIN) { updateBuilder.roundRobinLbPolicy(); + } else if (enableLeastRequest && cluster.getLbPolicy() == LbPolicy.LEAST_REQUEST) { + LeastRequestLbConfig lbConfig = cluster.getLeastRequestLbConfig(); + int choiceCount = + lbConfig.hasChoiceCount() + ? lbConfig.getChoiceCount().getValue() + : DEFAULT_LEAST_REQUEST_CHOICE_COUNT; + if (choiceCount < DEFAULT_LEAST_REQUEST_CHOICE_COUNT) { + throw new ResourceInvalidException( + "Cluster " + cluster.getName() + ": invalid least_request_lb_config: " + lbConfig); + } + updateBuilder.leastRequestLbPolicy(choiceCount); } else { throw new ResourceInvalidException( "Cluster " + cluster.getName() + ": unsupported lb policy: " + cluster.getLbPolicy()); diff --git a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java index e4800cf668c..309daf55a18 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java @@ -672,7 +672,7 @@ private static PriorityChildConfig generateDnsBasedPriorityChildConfig( * Generates configs to be used in the priority LB policy for priorities in an EDS cluster. * *

priority LB -> cluster_impl LB (one per priority) -> (weighted_target LB - * -> round_robin (one per locality)) / ring_hash_experimental + * -> round_robin / least_request_experimental (one per locality)) / ring_hash_experimental */ private static Map generateEdsBasedPriorityChildConfigs( String cluster, @Nullable String edsServiceName, @Nullable ServerInfo lrsServerInfo, @@ -684,13 +684,14 @@ private static Map generateEdsBasedPriorityChildCon for (String priority : prioritizedLocalityWeights.keySet()) { PolicySelection leafPolicy = endpointLbPolicy; // Depending on the endpoint-level load balancing policy, different LB hierarchy may be - // created. If the endpoint-level LB policy is round_robin, it creates a two-level LB - // hierarchy: a locality-level LB policy that balances load according to locality weights - // followed by an endpoint-level LB policy that simply rounds robin the endpoints within - // the locality. If the endpoint-level LB policy is ring_hash_experimental, it creates - // a unified LB policy that balances load by weighing the product of each endpoint's weight - // and the weight of the locality it belongs to. - if (endpointLbPolicy.getProvider().getPolicyName().equals("round_robin")) { + // created. If the endpoint-level LB policy is round_robin or least_request_experimental, + // it creates a two-level LB hierarchy: a locality-level LB policy that balances load + // according to locality weights followed by an endpoint-level LB policy that balances load + // between endpoints within the locality. If the endpoint-level LB policy is + // ring_hash_experimental, it creates a unified LB policy that balances load by weighing the + // product of each endpoint's weight and the weight of the locality it belongs to. + if (endpointLbPolicy.getProvider().getPolicyName().equals("round_robin") + || endpointLbPolicy.getProvider().getPolicyName().equals("least_request_experimental")) { Map localityWeights = prioritizedLocalityWeights.get(priority); Map targets = new HashMap<>(); for (Locality locality : localityWeights.keySet()) { diff --git a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancerProvider.java b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancerProvider.java index 4aaf0dcadde..6f6f887e925 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancerProvider.java +++ b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancerProvider.java @@ -68,7 +68,8 @@ public LoadBalancer newLoadBalancer(Helper helper) { static final class ClusterResolverConfig { // Ordered list of clusters to be resolved. final List discoveryMechanisms; - // Endpoint-level load balancing policy with config (round_robin or ring_hash_experimental). + // Endpoint-level load balancing policy with config + // (round_robin, least_request_experimental or ring_hash_experimental). final PolicySelection lbPolicy; ClusterResolverConfig(List discoveryMechanisms, PolicySelection lbPolicy) { diff --git a/xds/src/main/java/io/grpc/xds/LeastRequestLoadBalancer.java b/xds/src/main/java/io/grpc/xds/LeastRequestLoadBalancer.java new file mode 100644 index 00000000000..584ac2dd16f --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/LeastRequestLoadBalancer.java @@ -0,0 +1,430 @@ +/* + * Copyright 2021 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.grpc.ConnectivityState.CONNECTING; +import static io.grpc.ConnectivityState.IDLE; +import static io.grpc.ConnectivityState.READY; +import static io.grpc.ConnectivityState.SHUTDOWN; +import static io.grpc.ConnectivityState.TRANSIENT_FAILURE; +import static io.grpc.xds.LeastRequestLoadBalancerProvider.DEFAULT_CHOICE_COUNT; +import static io.grpc.xds.LeastRequestLoadBalancerProvider.MAX_CHOICE_COUNT; +import static io.grpc.xds.LeastRequestLoadBalancerProvider.MIN_CHOICE_COUNT; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import io.grpc.Attributes; +import io.grpc.ClientStreamTracer; +import io.grpc.ClientStreamTracer.StreamInfo; +import io.grpc.ConnectivityState; +import io.grpc.ConnectivityStateInfo; +import io.grpc.EquivalentAddressGroup; +import io.grpc.LoadBalancer; +import io.grpc.Metadata; +import io.grpc.Status; +import io.grpc.xds.ThreadSafeRandom.ThreadSafeRandomImpl; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import javax.annotation.Nonnull; + +/** + * A {@link LoadBalancer} that provides least request load balancing based on + * outstanding request counters. + * It works by sampling a number of subchannels and picking the one with the + * fewest amount of outstanding requests. + * The default sampling amount of two is also known as + * the "power of two choices" (P2C). + */ +final class LeastRequestLoadBalancer extends LoadBalancer { + @VisibleForTesting + static final Attributes.Key> STATE_INFO = + Attributes.Key.create("state-info"); + @VisibleForTesting + static final Attributes.Key IN_FLIGHTS = + Attributes.Key.create("in-flights"); + + private final Helper helper; + private final ThreadSafeRandom random; + private final Map subchannels = + new HashMap<>(); + + private ConnectivityState currentState; + private LeastRequestPicker currentPicker = new EmptyPicker(EMPTY_OK); + private int choiceCount = DEFAULT_CHOICE_COUNT; + + LeastRequestLoadBalancer(Helper helper) { + this(helper, ThreadSafeRandomImpl.instance); + } + + @VisibleForTesting + LeastRequestLoadBalancer(Helper helper, ThreadSafeRandom random) { + this.helper = checkNotNull(helper, "helper"); + this.random = checkNotNull(random, "random"); + } + + @Override + public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { + LeastRequestConfig config = + (LeastRequestConfig) resolvedAddresses.getLoadBalancingPolicyConfig(); + // Config may be null if least_request is used outside xDS + if (config != null) { + choiceCount = config.choiceCount; + } + + List servers = resolvedAddresses.getAddresses(); + Set currentAddrs = subchannels.keySet(); + Map latestAddrs = stripAttrs(servers); + Set removedAddrs = setsDifference(currentAddrs, latestAddrs.keySet()); + + for (Map.Entry latestEntry : + latestAddrs.entrySet()) { + EquivalentAddressGroup strippedAddressGroup = latestEntry.getKey(); + EquivalentAddressGroup originalAddressGroup = latestEntry.getValue(); + Subchannel existingSubchannel = subchannels.get(strippedAddressGroup); + if (existingSubchannel != null) { + // EAG's Attributes may have changed. + existingSubchannel.updateAddresses(Collections.singletonList(originalAddressGroup)); + continue; + } + // Create new subchannels for new addresses. + Attributes.Builder subchannelAttrs = Attributes.newBuilder() + .set(STATE_INFO, new Ref<>(ConnectivityStateInfo.forNonError(IDLE))) + // Used to track the in flight requests on this particular subchannel + .set(IN_FLIGHTS, new AtomicInteger(0)); + + final Subchannel subchannel = checkNotNull( + helper.createSubchannel(CreateSubchannelArgs.newBuilder() + .setAddresses(originalAddressGroup) + .setAttributes(subchannelAttrs.build()) + .build()), + "subchannel"); + subchannel.start(new SubchannelStateListener() { + @Override + public void onSubchannelState(ConnectivityStateInfo state) { + processSubchannelState(subchannel, state); + } + }); + subchannels.put(strippedAddressGroup, subchannel); + subchannel.requestConnection(); + } + + ArrayList removedSubchannels = new ArrayList<>(); + for (EquivalentAddressGroup addressGroup : removedAddrs) { + removedSubchannels.add(subchannels.remove(addressGroup)); + } + + // Update the picker before shutting down the subchannels, to reduce the chance of the race + // between picking a subchannel and shutting it down. + updateBalancingState(); + + // Shutdown removed subchannels + for (Subchannel removedSubchannel : removedSubchannels) { + shutdownSubchannel(removedSubchannel); + } + } + + @Override + public void handleNameResolutionError(Status error) { + if (currentState != READY) { + updateBalancingState(TRANSIENT_FAILURE, new EmptyPicker(error)); + } + } + + private void processSubchannelState(Subchannel subchannel, ConnectivityStateInfo stateInfo) { + if (subchannels.get(stripAttrs(subchannel.getAddresses())) != subchannel) { + return; + } + if (stateInfo.getState() == TRANSIENT_FAILURE || stateInfo.getState() == IDLE) { + helper.refreshNameResolution(); + } + if (stateInfo.getState() == IDLE) { + subchannel.requestConnection(); + } + Ref subchannelStateRef = getSubchannelStateInfoRef(subchannel); + if (subchannelStateRef.value.getState().equals(TRANSIENT_FAILURE)) { + if (stateInfo.getState().equals(CONNECTING) || stateInfo.getState().equals(IDLE)) { + return; + } + } + subchannelStateRef.value = stateInfo; + updateBalancingState(); + } + + private void shutdownSubchannel(Subchannel subchannel) { + subchannel.shutdown(); + getSubchannelStateInfoRef(subchannel).value = + ConnectivityStateInfo.forNonError(SHUTDOWN); + } + + @Override + public void shutdown() { + for (Subchannel subchannel : getSubchannels()) { + shutdownSubchannel(subchannel); + } + subchannels.clear(); + } + + private static final Status EMPTY_OK = Status.OK.withDescription("no subchannels ready"); + + /** + * Updates picker with the list of active subchannels (state == READY). + */ + @SuppressWarnings("ReferenceEquality") + private void updateBalancingState() { + List activeList = filterNonFailingSubchannels(getSubchannels()); + if (activeList.isEmpty()) { + // No READY subchannels, determine aggregate state and error status + boolean isConnecting = false; + Status aggStatus = EMPTY_OK; + for (Subchannel subchannel : getSubchannels()) { + ConnectivityStateInfo stateInfo = getSubchannelStateInfoRef(subchannel).value; + // This subchannel IDLE is not because of channel IDLE_TIMEOUT, + // in which case LB is already shutdown. + // LRLB will request connection immediately on subchannel IDLE. + if (stateInfo.getState() == CONNECTING || stateInfo.getState() == IDLE) { + isConnecting = true; + } + if (aggStatus == EMPTY_OK || !aggStatus.isOk()) { + aggStatus = stateInfo.getStatus(); + } + } + updateBalancingState(isConnecting ? CONNECTING : TRANSIENT_FAILURE, + // If all subchannels are TRANSIENT_FAILURE, return the Status associated with + // an arbitrary subchannel, otherwise return OK. + new EmptyPicker(aggStatus)); + } else { + updateBalancingState(READY, new ReadyPicker(activeList, choiceCount, random)); + } + } + + private void updateBalancingState(ConnectivityState state, LeastRequestPicker picker) { + if (state != currentState || !picker.isEquivalentTo(currentPicker)) { + helper.updateBalancingState(state, picker); + currentState = state; + currentPicker = picker; + } + } + + /** + * Filters out non-ready subchannels. + */ + private static List filterNonFailingSubchannels( + Collection subchannels) { + List readySubchannels = new ArrayList<>(subchannels.size()); + for (Subchannel subchannel : subchannels) { + if (isReady(subchannel)) { + readySubchannels.add(subchannel); + } + } + return readySubchannels; + } + + /** + * Converts list of {@link EquivalentAddressGroup} to {@link EquivalentAddressGroup} set and + * remove all attributes. The values are the original EAGs. + */ + private static Map stripAttrs( + List groupList) { + Map addrs = new HashMap<>(groupList.size() * 2); + for (EquivalentAddressGroup group : groupList) { + addrs.put(stripAttrs(group), group); + } + return addrs; + } + + private static EquivalentAddressGroup stripAttrs(EquivalentAddressGroup eag) { + return new EquivalentAddressGroup(eag.getAddresses()); + } + + @VisibleForTesting + Collection getSubchannels() { + return subchannels.values(); + } + + private static Ref getSubchannelStateInfoRef( + Subchannel subchannel) { + return checkNotNull(subchannel.getAttributes().get(STATE_INFO), "STATE_INFO"); + } + + private static AtomicInteger getInFlights(Subchannel subchannel) { + return checkNotNull(subchannel.getAttributes().get(IN_FLIGHTS), "IN_FLIGHTS"); + } + + // package-private to avoid synthetic access + static boolean isReady(Subchannel subchannel) { + return getSubchannelStateInfoRef(subchannel).value.getState() == READY; + } + + private static Set setsDifference(Set a, Set b) { + Set aCopy = new HashSet<>(a); + aCopy.removeAll(b); + return aCopy; + } + + // Only subclasses are ReadyPicker or EmptyPicker + private abstract static class LeastRequestPicker extends SubchannelPicker { + abstract boolean isEquivalentTo(LeastRequestPicker picker); + } + + @VisibleForTesting + static final class ReadyPicker extends LeastRequestPicker { + private final List list; // non-empty + private final int choiceCount; + private final ThreadSafeRandom random; + + ReadyPicker(List list, int choiceCount, ThreadSafeRandom random) { + checkArgument(!list.isEmpty(), "empty list"); + this.list = list; + this.choiceCount = choiceCount; + this.random = checkNotNull(random, "random"); + } + + @Override + public PickResult pickSubchannel(PickSubchannelArgs args) { + final Subchannel subchannel = nextSubchannel(); + final OutstandingRequestsTracingFactory factory = + new OutstandingRequestsTracingFactory(getInFlights(subchannel)); + return PickResult.withSubchannel(subchannel, factory); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(ReadyPicker.class) + .add("list", list) + .add("choiceCount", choiceCount) + .toString(); + } + + private Subchannel nextSubchannel() { + Subchannel candidate = list.get(random.nextInt(list.size())); + for (int i = 0; i < choiceCount - 1; ++i) { + Subchannel sampled = list.get(random.nextInt(list.size())); + if (getInFlights(sampled).get() < getInFlights(candidate).get()) { + candidate = sampled; + } + } + return candidate; + } + + @VisibleForTesting + List getList() { + return list; + } + + @Override + boolean isEquivalentTo(LeastRequestPicker picker) { + if (!(picker instanceof ReadyPicker)) { + return false; + } + ReadyPicker other = (ReadyPicker) picker; + // the lists cannot contain duplicate subchannels + return other == this + || ((list.size() == other.list.size() && new HashSet<>(list).containsAll(other.list)) + && choiceCount == other.choiceCount); + } + } + + @VisibleForTesting + static final class EmptyPicker extends LeastRequestPicker { + + private final Status status; + + EmptyPicker(@Nonnull Status status) { + this.status = Preconditions.checkNotNull(status, "status"); + } + + @Override + public PickResult pickSubchannel(PickSubchannelArgs args) { + return status.isOk() ? PickResult.withNoResult() : PickResult.withError(status); + } + + @Override + boolean isEquivalentTo(LeastRequestPicker picker) { + return picker instanceof EmptyPicker && (Objects.equal(status, ((EmptyPicker) picker).status) + || (status.isOk() && ((EmptyPicker) picker).status.isOk())); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(EmptyPicker.class).add("status", status).toString(); + } + } + + /** + * A lighter weight Reference than AtomicReference. + */ + static final class Ref { + T value; + + Ref(T value) { + this.value = value; + } + } + + private static final class OutstandingRequestsTracingFactory extends + ClientStreamTracer.Factory { + private final AtomicInteger inFlights; + + private OutstandingRequestsTracingFactory(AtomicInteger inFlights) { + this.inFlights = checkNotNull(inFlights, "inFlights"); + } + + @Override + public ClientStreamTracer newClientStreamTracer(StreamInfo info, Metadata headers) { + return new ClientStreamTracer() { + @Override + public void streamCreated(Attributes transportAttrs, Metadata headers) { + inFlights.incrementAndGet(); + } + + @Override + public void streamClosed(Status status) { + inFlights.decrementAndGet(); + } + }; + } + } + + static final class LeastRequestConfig { + final int choiceCount; + + LeastRequestConfig(int choiceCount) { + checkArgument(choiceCount >= MIN_CHOICE_COUNT, "choiceCount <= 1"); + // Even though a choiceCount value larger than 2 is currently considered valid in xDS + // we restrict it to 10 here as specified in "A48: xDS Least Request LB Policy". + this.choiceCount = Math.min(choiceCount, MAX_CHOICE_COUNT); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("choiceCount", choiceCount) + .toString(); + } + } +} diff --git a/xds/src/main/java/io/grpc/xds/LeastRequestLoadBalancerProvider.java b/xds/src/main/java/io/grpc/xds/LeastRequestLoadBalancerProvider.java new file mode 100644 index 00000000000..3abac1d2f0d --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/LeastRequestLoadBalancerProvider.java @@ -0,0 +1,80 @@ +/* + * Copyright 2021 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds; + +import com.google.common.annotations.VisibleForTesting; +import io.grpc.Internal; +import io.grpc.LoadBalancer; +import io.grpc.LoadBalancerProvider; +import io.grpc.NameResolver.ConfigOrError; +import io.grpc.Status; +import io.grpc.internal.JsonUtil; +import io.grpc.xds.LeastRequestLoadBalancer.LeastRequestConfig; +import java.util.Map; + +/** + * Provider for the "least_request_experimental" balancing policy. + */ +@Internal +public final class LeastRequestLoadBalancerProvider extends LoadBalancerProvider { + // Minimum number of choices allowed. + static final int MIN_CHOICE_COUNT = 2; + // Maximum number of choices allowed. + static final int MAX_CHOICE_COUNT = 10; + // Same as ClientXdsClient.DEFAULT_LEAST_REQUEST_CHOICE_COUNT + @VisibleForTesting + static final Integer DEFAULT_CHOICE_COUNT = 2; + + @Override + public LoadBalancer newLoadBalancer(LoadBalancer.Helper helper) { + return new LeastRequestLoadBalancer(helper); + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public int getPriority() { + return 5; + } + + @Override + public String getPolicyName() { + return "least_request_experimental"; + } + + @Override + public ConfigOrError parseLoadBalancingPolicyConfig(Map rawConfig) { + try { + Integer choiceCount = JsonUtil.getNumberAsInteger(rawConfig, "choiceCount"); + if (choiceCount == null) { + choiceCount = DEFAULT_CHOICE_COUNT; + } + if (choiceCount < MIN_CHOICE_COUNT) { + return ConfigOrError.fromError(Status.INVALID_ARGUMENT.withDescription( + "Invalid 'choiceCount'")); + } + return ConfigOrError.fromConfig(new LeastRequestConfig(choiceCount)); + } catch (RuntimeException e) { + return ConfigOrError.fromError( + Status.fromThrowable(e).withDescription( + "Failed to parse least_request_experimental LB config: " + rawConfig)); + } + } +} diff --git a/xds/src/main/java/io/grpc/xds/XdsClient.java b/xds/src/main/java/io/grpc/xds/XdsClient.java index 6f2a661361e..789e576da06 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClient.java +++ b/xds/src/main/java/io/grpc/xds/XdsClient.java @@ -120,6 +120,9 @@ abstract static class CdsUpdate implements ResourceUpdate { // 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 @@ -158,6 +161,7 @@ static Builder forAggregate(String clusterName, List prioritizedClusterN .clusterType(ClusterType.AGGREGATE) .minRingSize(0) .maxRingSize(0) + .choiceCount(0) .prioritizedClusterNames(ImmutableList.copyOf(prioritizedClusterNames)); } @@ -169,6 +173,7 @@ static Builder forEds(String clusterName, @Nullable String edsServiceName, .clusterType(ClusterType.EDS) .minRingSize(0) .maxRingSize(0) + .choiceCount(0) .edsServiceName(edsServiceName) .lrsServerInfo(lrsServerInfo) .maxConcurrentRequests(maxConcurrentRequests) @@ -183,6 +188,7 @@ static Builder forLogicalDns(String clusterName, String dnsHostName, .clusterType(ClusterType.LOGICAL_DNS) .minRingSize(0) .maxRingSize(0) + .choiceCount(0) .dnsHostName(dnsHostName) .lrsServerInfo(lrsServerInfo) .maxConcurrentRequests(maxConcurrentRequests) @@ -194,7 +200,7 @@ enum ClusterType { } enum LbPolicy { - ROUND_ROBIN, RING_HASH + ROUND_ROBIN, RING_HASH, LEAST_REQUEST } // FIXME(chengyuanzhang): delete this after UpstreamTlsContext's toString() is fixed. @@ -206,6 +212,7 @@ public final String toString() { .add("lbPolicy", lbPolicy()) .add("minRingSize", minRingSize()) .add("maxRingSize", maxRingSize()) + .add("choiceCount", choiceCount()) .add("edsServiceName", edsServiceName()) .add("dnsHostName", dnsHostName()) .add("lrsServerInfo", lrsServerInfo()) @@ -234,6 +241,13 @@ Builder ringHashLbPolicy(long minRingSize, long maxRingSize) { return this.lbPolicy(LbPolicy.RING_HASH).minRingSize(minRingSize).maxRingSize(maxRingSize); } + Builder leastRequestLbPolicy(int choiceCount) { + return this.lbPolicy(LbPolicy.LEAST_REQUEST).choiceCount(choiceCount); + } + + // Private, use leastRequestLbPolicy(int). + protected abstract Builder choiceCount(int choiceCount); + // Private, use ringHashLbPolicy(long, long). protected abstract Builder minRingSize(long minRingSize); diff --git a/xds/src/main/resources/META-INF/services/io.grpc.LoadBalancerProvider b/xds/src/main/resources/META-INF/services/io.grpc.LoadBalancerProvider index 7ba3dcf22f5..8e5c2dd1c6a 100644 --- a/xds/src/main/resources/META-INF/services/io.grpc.LoadBalancerProvider +++ b/xds/src/main/resources/META-INF/services/io.grpc.LoadBalancerProvider @@ -4,4 +4,5 @@ io.grpc.xds.WeightedTargetLoadBalancerProvider io.grpc.xds.ClusterManagerLoadBalancerProvider io.grpc.xds.ClusterResolverLoadBalancerProvider io.grpc.xds.ClusterImplLoadBalancerProvider +io.grpc.xds.LeastRequestLoadBalancerProvider io.grpc.xds.RingHashLoadBalancerProvider diff --git a/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java b/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java index 7664660d4c6..78e6d6473ca 100644 --- a/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java +++ b/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java @@ -48,6 +48,7 @@ import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig; import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig.DiscoveryMechanism; 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; @@ -121,6 +122,7 @@ public void setUp() { lbRegistry.register(new FakeLoadBalancerProvider(CLUSTER_RESOLVER_POLICY_NAME)); lbRegistry.register(new FakeLoadBalancerProvider("round_robin")); lbRegistry.register(new FakeLoadBalancerProvider("ring_hash_experimental")); + lbRegistry.register(new FakeLoadBalancerProvider("least_request_experimental")); loadBalancer = new CdsLoadBalancer2(helper, lbRegistry); loadBalancer.handleResolvedAddresses( ResolvedAddresses.newBuilder() @@ -164,7 +166,7 @@ public void discoverTopLevelEdsCluster() { public void discoverTopLevelLogicalDnsCluster() { CdsUpdate update = CdsUpdate.forLogicalDns(CLUSTER, DNS_HOST_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext) - .roundRobinLbPolicy().build(); + .leastRequestLbPolicy(3).build(); xdsClient.deliverCdsUpdate(CLUSTER, update); assertThat(childBalancers).hasSize(1); FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers); @@ -174,7 +176,9 @@ public void discoverTopLevelLogicalDnsCluster() { DiscoveryMechanism instance = Iterables.getOnlyElement(childLbConfig.discoveryMechanisms); assertDiscoveryMechanism(instance, CLUSTER, DiscoveryMechanism.Type.LOGICAL_DNS, null, DNS_HOST_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext); - assertThat(childLbConfig.lbPolicy.getProvider().getPolicyName()).isEqualTo("round_robin"); + assertThat(childLbConfig.lbPolicy.getProvider().getPolicyName()) + .isEqualTo("least_request_experimental"); + assertThat(((LeastRequestConfig) childLbConfig.lbPolicy.getConfig()).choiceCount).isEqualTo(3); } @Test diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java index 940d96fd10e..33dbc622ac8 100644 --- a/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java +++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java @@ -37,6 +37,7 @@ import io.envoyproxy.envoy.config.cluster.v3.Cluster.DiscoveryType; import io.envoyproxy.envoy.config.cluster.v3.Cluster.EdsClusterConfig; import io.envoyproxy.envoy.config.cluster.v3.Cluster.LbPolicy; +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.core.v3.Address; @@ -156,6 +157,7 @@ public class ClientXdsClientDataTest { private boolean originalEnableRetry; private boolean originalEnableRbac; private boolean originalEnableRouteLookup; + private boolean originalEnableLeastRequest; @Before public void setUp() { @@ -165,6 +167,8 @@ public void setUp() { assertThat(originalEnableRbac).isTrue(); originalEnableRouteLookup = ClientXdsClient.enableRouteLookup; assertThat(originalEnableRouteLookup).isFalse(); + originalEnableLeastRequest = ClientXdsClient.enableLeastRequest; + assertThat(originalEnableLeastRequest).isFalse(); } @After @@ -172,6 +176,7 @@ public void tearDown() { ClientXdsClient.enableRetry = originalEnableRetry; ClientXdsClient.enableRbac = originalEnableRbac; ClientXdsClient.enableRouteLookup = originalEnableRouteLookup; + ClientXdsClient.enableLeastRequest = originalEnableLeastRequest; } @Test @@ -1667,6 +1672,28 @@ public void parseCluster_ringHashLbPolicy_defaultLbConfig() throws ResourceInval .isEqualTo(ClientXdsClient.DEFAULT_RING_HASH_LB_POLICY_MAX_RING_SIZE); } + @Test + public void parseCluster_leastRequestLbPolicy_defaultLbConfig() throws ResourceInvalidException { + ClientXdsClient.enableLeastRequest = true; + Cluster cluster = Cluster.newBuilder() + .setName("cluster-foo.googleapis.com") + .setType(DiscoveryType.EDS) + .setEdsClusterConfig( + EdsClusterConfig.newBuilder() + .setEdsConfig( + ConfigSource.newBuilder() + .setAds(AggregatedConfigSource.getDefaultInstance())) + .setServiceName("service-foo.googleapis.com")) + .setLbPolicy(LbPolicy.LEAST_REQUEST) + .build(); + + CdsUpdate update = ClientXdsClient.processCluster( + cluster, new HashSet(), null, LRS_SERVER_INFO); + assertThat(update.lbPolicy()).isEqualTo(CdsUpdate.LbPolicy.LEAST_REQUEST); + assertThat(update.choiceCount()) + .isEqualTo(ClientXdsClient.DEFAULT_LEAST_REQUEST_CHOICE_COUNT); + } + @Test public void parseCluster_transportSocketMatches_exception() throws ResourceInvalidException { Cluster cluster = Cluster.newBuilder() @@ -1741,6 +1768,31 @@ public void parseCluster_ringHashLbPolicy_invalidRingSizeConfig_tooLargeRingSize ClientXdsClient.processCluster(cluster, new HashSet(), null, LRS_SERVER_INFO); } + @Test + public void parseCluster_leastRequestLbPolicy_invalidChoiceCountConfig_tooSmallChoiceCount() + throws ResourceInvalidException { + ClientXdsClient.enableLeastRequest = true; + Cluster cluster = Cluster.newBuilder() + .setName("cluster-foo.googleapis.com") + .setType(DiscoveryType.EDS) + .setEdsClusterConfig( + EdsClusterConfig.newBuilder() + .setEdsConfig( + ConfigSource.newBuilder() + .setAds(AggregatedConfigSource.getDefaultInstance())) + .setServiceName("service-foo.googleapis.com")) + .setLbPolicy(LbPolicy.LEAST_REQUEST) + .setLeastRequestLbConfig( + LeastRequestLbConfig.newBuilder() + .setChoiceCount(UInt32Value.newBuilder().setValue(1)) + ) + .build(); + + thrown.expect(ResourceInvalidException.class); + thrown.expectMessage("Cluster cluster-foo.googleapis.com: invalid least_request_lb_config"); + ClientXdsClient.processCluster(cluster, new HashSet(), null, LRS_SERVER_INFO); + } + @Test public void parseServerSideListener_invalidTrafficDirection() throws ResourceInvalidException { Listener listener = diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java index 48db92b4de5..9e81f45cfc5 100644 --- a/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java +++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java @@ -205,8 +205,8 @@ public long currentTimeNanos() { // CDS test resources. private final Any testClusterRoundRobin = - Any.pack(mf.buildEdsCluster(CDS_RESOURCE, null, "round_robin", null, false, null, - "envoy.transport_sockets.tls", null + Any.pack(mf.buildEdsCluster(CDS_RESOURCE, null, "round_robin", null, + null, false, null, "envoy.transport_sockets.tls", null )); // EDS test resources. @@ -258,6 +258,7 @@ public long currentTimeNanos() { private ClientXdsClient xdsClient; private boolean originalEnableFaultInjection; private boolean originalEnableRbac; + private boolean originalEnableLeastRequest; @Before public void setUp() throws IOException { @@ -272,6 +273,8 @@ public void setUp() throws IOException { ClientXdsClient.enableFaultInjection = true; originalEnableRbac = ClientXdsClient.enableRbac; assertThat(originalEnableRbac).isTrue(); + originalEnableLeastRequest = ClientXdsClient.enableLeastRequest; + ClientXdsClient.enableLeastRequest = true; final String serverName = InProcessServerBuilder.generateName(); cleanupRule.register( InProcessServerBuilder @@ -345,6 +348,7 @@ SERVER_URI_EMPTY_AUTHORITY, CHANNEL_CREDENTIALS, useProtocolV3()))))) public void tearDown() { ClientXdsClient.enableFaultInjection = originalEnableFaultInjection; ClientXdsClient.enableRbac = originalEnableRbac; + ClientXdsClient.enableLeastRequest = originalEnableLeastRequest; xdsClient.shutdown(); channel.shutdown(); // channel not owned by XdsClient assertThat(adsEnded.get()).isTrue(); @@ -1264,9 +1268,9 @@ public void cdsResourceNotFound() { List clusters = ImmutableList.of( Any.pack(mf.buildEdsCluster("cluster-bar.googleapis.com", null, "round_robin", null, - false, null, "envoy.transport_sockets.tls", null)), + null, false, null, "envoy.transport_sockets.tls", null)), Any.pack(mf.buildEdsCluster("cluster-baz.googleapis.com", null, "round_robin", null, - false, null, "envoy.transport_sockets.tls", null))); + null, false, null, "envoy.transport_sockets.tls", null))); call.sendResponse(CDS, clusters, VERSION_1, "0000"); // Client sent an ACK CDS request. @@ -1340,13 +1344,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, false, null, + "A", Any.pack(mf.buildEdsCluster("A", "A.1", "round_robin", null, null, false, null, "envoy.transport_sockets.tls", null )), - "B", Any.pack(mf.buildEdsCluster("B", "B.1", "round_robin", null, false, null, + "B", Any.pack(mf.buildEdsCluster("B", "B.1", "round_robin", null, null, false, null, "envoy.transport_sockets.tls", null )), - "C", Any.pack(mf.buildEdsCluster("C", "C.1", "round_robin", null, false, null, + "C", Any.pack(mf.buildEdsCluster("C", "C.1", "round_robin", null, null, false, null, "envoy.transport_sockets.tls", null ))); call.sendResponse(CDS, resourcesV1.values().asList(), VERSION_1, "0000"); @@ -1359,7 +1363,7 @@ public void cdsResponseErrorHandling_subscribedResourceInvalid() { // CDS -> {A, B}, version 2 // Failed to parse endpoint B ImmutableMap resourcesV2 = ImmutableMap.of( - "A", Any.pack(mf.buildEdsCluster("A", "A.2", "round_robin", null, false, null, + "A", Any.pack(mf.buildEdsCluster("A", "A.2", "round_robin", null, null, false, null, "envoy.transport_sockets.tls", null )), "B", Any.pack(mf.buildClusterInvalid("B"))); @@ -1376,10 +1380,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, false, null, + "B", Any.pack(mf.buildEdsCluster("B", "B.3", "round_robin", null, null, false, null, "envoy.transport_sockets.tls", null )), - "C", Any.pack(mf.buildEdsCluster("C", "C.3", "round_robin", null, false, null, + "C", Any.pack(mf.buildEdsCluster("C", "C.3", "round_robin", null, null, false, null, "envoy.transport_sockets.tls", null ))); call.sendResponse(CDS, resourcesV3.values().asList(), VERSION_3, "0002"); @@ -1412,13 +1416,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, false, null, + "A", Any.pack(mf.buildEdsCluster("A", "A.1", "round_robin", null, null, false, null, "envoy.transport_sockets.tls", null )), - "B", Any.pack(mf.buildEdsCluster("B", "B.1", "round_robin", null, false, null, + "B", Any.pack(mf.buildEdsCluster("B", "B.1", "round_robin", null, null, false, null, "envoy.transport_sockets.tls", null )), - "C", Any.pack(mf.buildEdsCluster("C", "C.1", "round_robin", null, false, null, + "C", Any.pack(mf.buildEdsCluster("C", "C.1", "round_robin", null, null, false, null, "envoy.transport_sockets.tls", null ))); call.sendResponse(CDS, resourcesV1.values().asList(), VERSION_1, "0000"); @@ -1444,7 +1448,7 @@ public void cdsResponseErrorHandling_subscribedResourceInvalid_withEdsSubscripti // CDS -> {A, B}, version 2 // Failed to parse endpoint B ImmutableMap resourcesV2 = ImmutableMap.of( - "A", Any.pack(mf.buildEdsCluster("A", "A.2", "round_robin", null, false, null, + "A", Any.pack(mf.buildEdsCluster("A", "A.2", "round_robin", null, null, false, null, "envoy.transport_sockets.tls", null )), "B", Any.pack(mf.buildClusterInvalid("B"))); @@ -1489,13 +1493,40 @@ public void cdsResourceFound() { verifySubscribedResourcesMetadataSizes(0, 1, 0, 0); } + @Test + public void cdsResourceFound_leastRequestLbPolicy() { + DiscoveryRpcCall call = startResourceWatcher(CDS, 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 + )); + call.sendResponse(ResourceType.CDS, clusterRingHash, VERSION_1, "0000"); + + // Client sent an ACK CDS request. + call.verifyRequest(CDS, CDS_RESOURCE, VERSION_1, "0000", NODE); + verify(cdsResourceWatcher).onChanged(cdsUpdateCaptor.capture()); + CdsUpdate cdsUpdate = cdsUpdateCaptor.getValue(); + assertThat(cdsUpdate.clusterName()).isEqualTo(CDS_RESOURCE); + assertThat(cdsUpdate.clusterType()).isEqualTo(ClusterType.EDS); + assertThat(cdsUpdate.edsServiceName()).isNull(); + assertThat(cdsUpdate.lbPolicy()).isEqualTo(LbPolicy.LEAST_REQUEST); + assertThat(cdsUpdate.choiceCount()).isEqualTo(3); + assertThat(cdsUpdate.lrsServerInfo()).isNull(); + assertThat(cdsUpdate.maxConcurrentRequests()).isNull(); + assertThat(cdsUpdate.upstreamTlsContext()).isNull(); + assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); + verifyResourceMetadataAcked(CDS, CDS_RESOURCE, clusterRingHash, VERSION_1, TIME_INCREMENT); + verifySubscribedResourcesMetadataSizes(0, 1, 0, 0); + } + @Test public void cdsResourceFound_ringHashLbPolicy() { DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); Message ringHashConfig = mf.buildRingHashLbConfig("xx_hash", 10L, 100L); Any clusterRingHash = Any.pack( - mf.buildEdsCluster(CDS_RESOURCE, null, "ring_hash_experimental", ringHashConfig, false, - null, "envoy.transport_sockets.tls", null + mf.buildEdsCluster(CDS_RESOURCE, null, "ring_hash_experimental", ringHashConfig, null, + false, null, "envoy.transport_sockets.tls", null )); call.sendResponse(ResourceType.CDS, clusterRingHash, VERSION_1, "0000"); @@ -1523,7 +1554,7 @@ public void cdsResponseWithAggregateCluster() { List candidates = Arrays.asList( "cluster1.googleapis.com", "cluster2.googleapis.com", "cluster3.googleapis.com"); Any clusterAggregate = - Any.pack(mf.buildAggregateCluster(CDS_RESOURCE, "round_robin", null, candidates)); + Any.pack(mf.buildAggregateCluster(CDS_RESOURCE, "round_robin", null, null, candidates)); call.sendResponse(CDS, clusterAggregate, VERSION_1, "0000"); // Client sent an ACK CDS request. @@ -1542,7 +1573,7 @@ public void cdsResponseWithAggregateCluster() { public void cdsResponseWithCircuitBreakers() { DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); Any clusterCircuitBreakers = Any.pack( - mf.buildEdsCluster(CDS_RESOURCE, null, "round_robin", null, false, null, + mf.buildEdsCluster(CDS_RESOURCE, null, "round_robin", null, null, false, null, "envoy.transport_sockets.tls", mf.buildCircuitBreakers(50, 200))); call.sendResponse(CDS, clusterCircuitBreakers, VERSION_1, "0000"); @@ -1574,15 +1605,15 @@ public void cdsResponseWithUpstreamTlsContext() { // Management server sends back CDS response with UpstreamTlsContext. Any clusterEds = Any.pack(mf.buildEdsCluster(CDS_RESOURCE, "eds-cluster-foo.googleapis.com", "round_robin", - null, true, + null, null, true, mf.buildUpstreamTlsContext("cert-instance-name", "cert1"), "envoy.transport_sockets.tls", null)); List clusters = ImmutableList.of( Any.pack(mf.buildLogicalDnsCluster("cluster-bar.googleapis.com", - "dns-service-bar.googleapis.com", 443, "round_robin", null, false, null, null)), + "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, false, - null, "envoy.transport_sockets.tls", null))); + Any.pack(mf.buildEdsCluster("cluster-baz.googleapis.com", null, "round_robin", null, null, + false, null, "envoy.transport_sockets.tls", null))); call.sendResponse(CDS, clusters, VERSION_1, "0000"); // Client sent an ACK CDS request. @@ -1610,15 +1641,15 @@ public void cdsResponseWithNewUpstreamTlsContext() { // Management server sends back CDS response with UpstreamTlsContext. Any clusterEds = Any.pack(mf.buildEdsCluster(CDS_RESOURCE, "eds-cluster-foo.googleapis.com", "round_robin", - null, true, + null, null,true, mf.buildNewUpstreamTlsContext("cert-instance-name", "cert1"), "envoy.transport_sockets.tls", null)); List clusters = ImmutableList.of( Any.pack(mf.buildLogicalDnsCluster("cluster-bar.googleapis.com", - "dns-service-bar.googleapis.com", 443, "round_robin", null, false, null, null)), + "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, false, - null, "envoy.transport_sockets.tls", null))); + Any.pack(mf.buildEdsCluster("cluster-baz.googleapis.com", null, "round_robin", null, null, + false, null, "envoy.transport_sockets.tls", null))); call.sendResponse(CDS, clusters, VERSION_1, "0000"); // Client sent an ACK CDS request. @@ -1645,7 +1676,7 @@ public void cdsResponseErrorHandling_badUpstreamTlsContext() { // 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, true, + null, null, true, mf.buildUpstreamTlsContext(null, null), "envoy.transport_sockets.tls", null))); call.sendResponse(CDS, clusters, VERSION_1, "0000"); @@ -1673,7 +1704,7 @@ public void cdsResponseErrorHandling_badTransportSocketName() { // 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, true, + null, null, true, mf.buildUpstreamTlsContext("secret1", "cert1"), "envoy.transport_sockets.bad", null))); call.sendResponse(CDS, clusters, VERSION_1, "0000"); @@ -1737,7 +1768,7 @@ public void cdsResourceUpdated() { int dnsHostPort = 443; Any clusterDns = Any.pack(mf.buildLogicalDnsCluster(CDS_RESOURCE, dnsHostAddr, dnsHostPort, "round_robin", - null, false, null, null)); + null, null, false, null, null)); call.sendResponse(CDS, clusterDns, VERSION_1, "0000"); call.verifyRequest(CDS, CDS_RESOURCE, VERSION_1, "0000", NODE); verify(cdsResourceWatcher).onChanged(cdsUpdateCaptor.capture()); @@ -1754,7 +1785,7 @@ public void cdsResourceUpdated() { // Updated CDS response. String edsService = "eds-service-bar.googleapis.com"; Any clusterEds = Any.pack( - mf.buildEdsCluster(CDS_RESOURCE, edsService, "round_robin", null, true, null, + mf.buildEdsCluster(CDS_RESOURCE, edsService, "round_robin", null, null, true, null, "envoy.transport_sockets.tls", null )); call.sendResponse(CDS, clusterEds, VERSION_2, "0001"); @@ -1828,9 +1859,9 @@ public void multipleCdsWatchers() { String edsService = "eds-service-bar.googleapis.com"; List clusters = ImmutableList.of( Any.pack(mf.buildLogicalDnsCluster(CDS_RESOURCE, dnsHostAddr, dnsHostPort, "round_robin", - null, false, null, null)), - Any.pack(mf.buildEdsCluster(cdsResourceTwo, edsService, "round_robin", null, true, null, - "envoy.transport_sockets.tls", null))); + null, null, false, null, null)), + Any.pack(mf.buildEdsCluster(cdsResourceTwo, edsService, "round_robin", null, null, true, + null, "envoy.transport_sockets.tls", null))); call.sendResponse(CDS, clusters, VERSION_1, "0000"); verify(cdsResourceWatcher).onChanged(cdsUpdateCaptor.capture()); CdsUpdate cdsUpdate = cdsUpdateCaptor.getValue(); @@ -2091,11 +2122,11 @@ public void edsResourceDeletedByCds() { DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); List clusters = ImmutableList.of( - Any.pack(mf.buildEdsCluster(resource, null, "round_robin", null, true, null, + Any.pack(mf.buildEdsCluster(resource, null, "round_robin", null, null, true, null, "envoy.transport_sockets.tls", null )), - Any.pack(mf.buildEdsCluster(CDS_RESOURCE, EDS_RESOURCE, "round_robin", null, false, null, - "envoy.transport_sockets.tls", null))); + Any.pack(mf.buildEdsCluster(CDS_RESOURCE, EDS_RESOURCE, "round_robin", null, null, false, + null, "envoy.transport_sockets.tls", null))); call.sendResponse(CDS, clusters, VERSION_1, "0000"); verify(cdsWatcher).onChanged(cdsUpdateCaptor.capture()); CdsUpdate cdsUpdate = cdsUpdateCaptor.getValue(); @@ -2140,9 +2171,9 @@ public void edsResourceDeletedByCds() { verifySubscribedResourcesMetadataSizes(0, 2, 0, 2); clusters = ImmutableList.of( - Any.pack(mf.buildEdsCluster(resource, null, "round_robin", null, true, null, + Any.pack(mf.buildEdsCluster(resource, null, "round_robin", null, null, true, null, "envoy.transport_sockets.tls", null)), // no change - Any.pack(mf.buildEdsCluster(CDS_RESOURCE, null, "round_robin", null, false, null, + Any.pack(mf.buildEdsCluster(CDS_RESOURCE, null, "round_robin", null, null, false, null, "envoy.transport_sockets.tls", null ))); call.sendResponse(CDS, clusters, VERSION_2, "0001"); @@ -2691,20 +2722,24 @@ protected abstract Message buildVirtualHost( protected abstract Message buildClusterInvalid(String name); protected abstract Message buildEdsCluster(String clusterName, @Nullable String edsServiceName, - String lbPolicy, @Nullable Message ringHashLbConfig, boolean enableLrs, - @Nullable Message upstreamTlsContext, String transportSocketName, + String lbPolicy, @Nullable Message ringHashLbConfig, @Nullable Message leastRequestLbConfig, + boolean enableLrs, @Nullable Message upstreamTlsContext, String transportSocketName, @Nullable Message circuitBreakers); protected abstract Message buildLogicalDnsCluster(String clusterName, String dnsHostAddr, - int dnsHostPort, String lbPolicy, @Nullable Message ringHashLbConfig, boolean enableLrs, + int dnsHostPort, String lbPolicy, @Nullable Message ringHashLbConfig, + @Nullable Message leastRequestLbConfig, boolean enableLrs, @Nullable Message upstreamTlsContext, @Nullable Message circuitBreakers); protected abstract Message buildAggregateCluster(String clusterName, String lbPolicy, - @Nullable Message ringHashLbConfig, List clusters); + @Nullable Message ringHashLbConfig, @Nullable Message leastRequestLbConfig, + List clusters); protected abstract Message buildRingHashLbConfig(String hashFunction, long minRingSize, long maxRingSize); + protected abstract Message buildLeastRequestLbConfig(int choiceCount); + protected abstract Message buildUpstreamTlsContext(String instanceName, String certName); protected abstract Message buildNewUpstreamTlsContext(String instanceName, String certName); diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientV2Test.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientV2Test.java index be28f07b73c..29c7fdc4c01 100644 --- a/xds/src/test/java/io/grpc/xds/ClientXdsClientV2Test.java +++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientV2Test.java @@ -36,6 +36,7 @@ import io.envoyproxy.envoy.api.v2.Cluster.DiscoveryType; import io.envoyproxy.envoy.api.v2.Cluster.EdsClusterConfig; import io.envoyproxy.envoy.api.v2.Cluster.LbPolicy; +import io.envoyproxy.envoy.api.v2.Cluster.LeastRequestLbConfig; import io.envoyproxy.envoy.api.v2.Cluster.RingHashLbConfig; import io.envoyproxy.envoy.api.v2.Cluster.RingHashLbConfig.HashFunction; import io.envoyproxy.envoy.api.v2.ClusterLoadAssignment; @@ -392,10 +393,12 @@ protected Message buildClusterInvalid(String name) { @Override protected Message buildEdsCluster(String clusterName, @Nullable String edsServiceName, - String lbPolicy, @Nullable Message ringHashLbConfig, boolean enableLrs, + String lbPolicy, @Nullable Message ringHashLbConfig, @Nullable Message leastRequestLbConfig, + boolean enableLrs, @Nullable Message upstreamTlsContext, String transportSocketName, @Nullable Message circuitBreakers) { - Cluster.Builder builder = initClusterBuilder(clusterName, lbPolicy, ringHashLbConfig, + Cluster.Builder builder = initClusterBuilder( + clusterName, lbPolicy, ringHashLbConfig, leastRequestLbConfig, enableLrs, upstreamTlsContext, circuitBreakers); builder.setType(DiscoveryType.EDS); EdsClusterConfig.Builder edsClusterConfigBuilder = EdsClusterConfig.newBuilder(); @@ -410,9 +413,11 @@ protected Message buildEdsCluster(String clusterName, @Nullable String edsServic @Override protected Message buildLogicalDnsCluster(String clusterName, String dnsHostAddr, - int dnsHostPort, String lbPolicy, @Nullable Message ringHashLbConfig, boolean enableLrs, + int dnsHostPort, String lbPolicy, @Nullable Message ringHashLbConfig, + @Nullable Message leastRequestLbConfig, boolean enableLrs, @Nullable Message upstreamTlsContext, @Nullable Message circuitBreakers) { - Cluster.Builder builder = initClusterBuilder(clusterName, lbPolicy, ringHashLbConfig, + Cluster.Builder builder = initClusterBuilder( + clusterName, lbPolicy, ringHashLbConfig, leastRequestLbConfig, enableLrs, upstreamTlsContext, circuitBreakers); builder.setType(DiscoveryType.LOGICAL_DNS); builder.setLoadAssignment( @@ -428,7 +433,8 @@ protected Message buildLogicalDnsCluster(String clusterName, String dnsHostAddr, @Override protected Message buildAggregateCluster(String clusterName, String lbPolicy, - @Nullable Message ringHashLbConfig, List clusters) { + @Nullable Message ringHashLbConfig, @Nullable Message leastRequestLbConfig, + List clusters) { ClusterConfig clusterConfig = ClusterConfig.newBuilder().addAllClusters(clusters).build(); CustomClusterType type = CustomClusterType.newBuilder() @@ -441,6 +447,9 @@ protected Message buildAggregateCluster(String clusterName, String lbPolicy, } else if (lbPolicy.equals("ring_hash_experimental")) { builder.setLbPolicy(LbPolicy.RING_HASH); builder.setRingHashLbConfig((RingHashLbConfig) ringHashLbConfig); + } else if (lbPolicy.equals("least_request_experimental")) { + builder.setLbPolicy(LbPolicy.LEAST_REQUEST); + builder.setLeastRequestLbConfig((LeastRequestLbConfig) leastRequestLbConfig); } else { throw new AssertionError("Invalid LB policy"); } @@ -448,8 +457,9 @@ protected Message buildAggregateCluster(String clusterName, String lbPolicy, } private Cluster.Builder initClusterBuilder(String clusterName, String lbPolicy, - @Nullable Message ringHashLbConfig, boolean enableLrs, - @Nullable Message upstreamTlsContext, @Nullable Message circuitBreakers) { + @Nullable Message ringHashLbConfig, @Nullable Message leastRequestLbConfig, + boolean enableLrs, @Nullable Message upstreamTlsContext, + @Nullable Message circuitBreakers) { Cluster.Builder builder = Cluster.newBuilder(); builder.setName(clusterName); if (lbPolicy.equals("round_robin")) { @@ -457,6 +467,9 @@ private Cluster.Builder initClusterBuilder(String clusterName, String lbPolicy, } else if (lbPolicy.equals("ring_hash_experimental")) { builder.setLbPolicy(LbPolicy.RING_HASH); builder.setRingHashLbConfig((RingHashLbConfig) ringHashLbConfig); + } else if (lbPolicy.equals("least_request_experimental")) { + builder.setLbPolicy(LbPolicy.LEAST_REQUEST); + builder.setLeastRequestLbConfig((LeastRequestLbConfig) leastRequestLbConfig); } else { throw new AssertionError("Invalid LB policy"); } @@ -493,6 +506,13 @@ protected Message buildRingHashLbConfig(String hashFunction, long minRingSize, return builder.build(); } + @Override + protected Message buildLeastRequestLbConfig(int choiceCount) { + LeastRequestLbConfig.Builder builder = LeastRequestLbConfig.newBuilder(); + builder.setChoiceCount(UInt32Value.newBuilder().setValue(choiceCount)); + return builder.build(); + } + @Override protected Message buildUpstreamTlsContext(String instanceName, String certName) { GrpcService grpcService = diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientV3Test.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientV3Test.java index 69e75292778..6a75d9ab068 100644 --- a/xds/src/test/java/io/grpc/xds/ClientXdsClientV3Test.java +++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientV3Test.java @@ -38,6 +38,7 @@ import io.envoyproxy.envoy.config.cluster.v3.Cluster.DiscoveryType; import io.envoyproxy.envoy.config.cluster.v3.Cluster.EdsClusterConfig; import io.envoyproxy.envoy.config.cluster.v3.Cluster.LbPolicy; +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.core.v3.Address; @@ -448,10 +449,12 @@ protected Message buildClusterInvalid(String name) { @Override protected Message buildEdsCluster(String clusterName, @Nullable String edsServiceName, - String lbPolicy, @Nullable Message ringHashLbConfig, boolean enableLrs, + String lbPolicy, @Nullable Message ringHashLbConfig, + @Nullable Message leastRequestLbConfig, boolean enableLrs, @Nullable Message upstreamTlsContext, String transportSocketName, @Nullable Message circuitBreakers) { - Cluster.Builder builder = initClusterBuilder(clusterName, lbPolicy, ringHashLbConfig, + Cluster.Builder builder = initClusterBuilder( + clusterName, lbPolicy, ringHashLbConfig, leastRequestLbConfig, enableLrs, upstreamTlsContext, transportSocketName, circuitBreakers); builder.setType(DiscoveryType.EDS); EdsClusterConfig.Builder edsClusterConfigBuilder = EdsClusterConfig.newBuilder(); @@ -466,9 +469,11 @@ protected Message buildEdsCluster(String clusterName, @Nullable String edsServic @Override protected Message buildLogicalDnsCluster(String clusterName, String dnsHostAddr, - int dnsHostPort, String lbPolicy, @Nullable Message ringHashLbConfig, boolean enableLrs, + int dnsHostPort, String lbPolicy, @Nullable Message ringHashLbConfig, + @Nullable Message leastRequestLbConfig, boolean enableLrs, @Nullable Message upstreamTlsContext, @Nullable Message circuitBreakers) { - Cluster.Builder builder = initClusterBuilder(clusterName, lbPolicy, ringHashLbConfig, + Cluster.Builder builder = initClusterBuilder( + clusterName, lbPolicy, ringHashLbConfig, leastRequestLbConfig, enableLrs, upstreamTlsContext, "envoy.transport_sockets.tls", circuitBreakers); builder.setType(DiscoveryType.LOGICAL_DNS); builder.setLoadAssignment( @@ -484,7 +489,8 @@ protected Message buildLogicalDnsCluster(String clusterName, String dnsHostAddr, @Override protected Message buildAggregateCluster(String clusterName, String lbPolicy, - @Nullable Message ringHashLbConfig, List clusters) { + @Nullable Message ringHashLbConfig, @Nullable Message leastRequestLbConfig, + List clusters) { ClusterConfig clusterConfig = ClusterConfig.newBuilder().addAllClusters(clusters).build(); CustomClusterType type = CustomClusterType.newBuilder() @@ -497,6 +503,9 @@ protected Message buildAggregateCluster(String clusterName, String lbPolicy, } else if (lbPolicy.equals("ring_hash_experimental")) { builder.setLbPolicy(LbPolicy.RING_HASH); builder.setRingHashLbConfig((RingHashLbConfig) ringHashLbConfig); + } else if (lbPolicy.equals("least_request_experimental")) { + builder.setLbPolicy(LbPolicy.LEAST_REQUEST); + builder.setLeastRequestLbConfig((LeastRequestLbConfig) leastRequestLbConfig); } else { throw new AssertionError("Invalid LB policy"); } @@ -504,8 +513,8 @@ protected Message buildAggregateCluster(String clusterName, String lbPolicy, } private Cluster.Builder initClusterBuilder(String clusterName, String lbPolicy, - @Nullable Message ringHashLbConfig, boolean enableLrs, - @Nullable Message upstreamTlsContext, String transportSocketName, + @Nullable Message ringHashLbConfig, @Nullable Message leastRequestLbConfig, + boolean enableLrs, @Nullable Message upstreamTlsContext, String transportSocketName, @Nullable Message circuitBreakers) { Cluster.Builder builder = Cluster.newBuilder(); builder.setName(clusterName); @@ -514,6 +523,9 @@ private Cluster.Builder initClusterBuilder(String clusterName, String lbPolicy, } else if (lbPolicy.equals("ring_hash_experimental")) { builder.setLbPolicy(LbPolicy.RING_HASH); builder.setRingHashLbConfig((RingHashLbConfig) ringHashLbConfig); + } else if (lbPolicy.equals("least_request_experimental")) { + builder.setLbPolicy(LbPolicy.LEAST_REQUEST); + builder.setLeastRequestLbConfig((LeastRequestLbConfig) leastRequestLbConfig); } else { throw new AssertionError("Invalid LB policy"); } @@ -550,6 +562,13 @@ protected Message buildRingHashLbConfig(String hashFunction, long minRingSize, return builder.build(); } + @Override + protected Message buildLeastRequestLbConfig(int choiceCount) { + LeastRequestLbConfig.Builder builder = LeastRequestLbConfig.newBuilder(); + builder.setChoiceCount(UInt32Value.newBuilder().setValue(choiceCount)); + return builder.build(); + } + @Override @SuppressWarnings("deprecation") protected Message buildUpstreamTlsContext(String instanceName, String certName) { diff --git a/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java index a85f476ed4b..51a7ce5066b 100644 --- a/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java @@ -65,6 +65,7 @@ import io.grpc.xds.Endpoints.LbEndpoint; import io.grpc.xds.Endpoints.LocalityLbEndpoints; 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; @@ -136,6 +137,8 @@ public void uncaughtException(Thread t, Throwable e) { new PolicySelection(new FakeLoadBalancerProvider("round_robin"), null); private final PolicySelection ringHash = new PolicySelection( new FakeLoadBalancerProvider("ring_hash_experimental"), new RingHashConfig(10L, 100L)); + private final PolicySelection leastRequest = new PolicySelection( + new FakeLoadBalancerProvider("least_request_experimental"), new LeastRequestConfig(3)); private final List childBalancers = new ArrayList<>(); private final List resolvers = new ArrayList<>(); private final FakeXdsClient xdsClient = new FakeXdsClient(); @@ -267,6 +270,45 @@ public void edsClustersWithRingHashEndpointLbPolicy() { assertThat(ringHashConfig.maxRingSize).isEqualTo(100L); } + @Test + public void edsClustersWithLeastRequestEndpointLbPolicy() { + ClusterResolverConfig config = new ClusterResolverConfig( + Collections.singletonList(edsDiscoveryMechanism1), 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 + "[priority1]"); + PriorityChildConfig priorityChildConfig = + Iterables.getOnlyElement(priorityLbConfig.childConfigs.values()); + assertThat(priorityChildConfig.policySelection.getProvider().getPolicyName()) + .isEqualTo(CLUSTER_IMPL_POLICY_NAME); + ClusterImplConfig clusterImplConfig = + (ClusterImplConfig) priorityChildConfig.policySelection.getConfig(); + assertClusterImplConfig(clusterImplConfig, CLUSTER1, EDS_SERVICE_NAME1, LRS_SERVER_INFO, 100L, + tlsContext, Collections.emptyList(), WEIGHTED_TARGET_POLICY_NAME); + WeightedTargetConfig weightedTargetConfig = + (WeightedTargetConfig) clusterImplConfig.childPolicy.getConfig(); + assertThat(weightedTargetConfig.targets.keySet()).containsExactly(locality1.toString()); + } + @Test public void onlyEdsClusters_receivedEndpoints() { ClusterResolverConfig config = new ClusterResolverConfig( diff --git a/xds/src/test/java/io/grpc/xds/LeastRequestLoadBalancerProviderTest.java b/xds/src/test/java/io/grpc/xds/LeastRequestLoadBalancerProviderTest.java new file mode 100644 index 00000000000..2e8519b150d --- /dev/null +++ b/xds/src/test/java/io/grpc/xds/LeastRequestLoadBalancerProviderTest.java @@ -0,0 +1,139 @@ +/* + * Copyright 2021 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds; + +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.Status.Code; +import io.grpc.SynchronizationContext; +import io.grpc.internal.JsonParser; +import io.grpc.xds.LeastRequestLoadBalancer.LeastRequestConfig; +import java.io.IOException; +import java.util.Map; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link LeastRequestLoadBalancerProvider}. */ +@RunWith(JUnit4.class) +public class LeastRequestLoadBalancerProviderTest { + private static final String AUTHORITY = "foo.googleapis.com"; + + private final SynchronizationContext syncContext = new SynchronizationContext( + new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + throw new AssertionError(e); + } + }); + private final LeastRequestLoadBalancerProvider provider = new LeastRequestLoadBalancerProvider(); + + @Test + public void provided() { + for (LoadBalancerProvider current : InternalServiceProviders.getCandidatesViaServiceLoader( + LoadBalancerProvider.class, getClass().getClassLoader())) { + if (current instanceof LeastRequestLoadBalancerProvider) { + return; + } + } + fail("LeastRequestLoadBalancerProvider not registered"); + } + + @Test + public void providesLoadBalancer() { + Helper helper = mock(Helper.class); + when(helper.getSynchronizationContext()).thenReturn(syncContext); + when(helper.getAuthority()).thenReturn(AUTHORITY); + assertThat(provider.newLoadBalancer(helper)) + .isInstanceOf(LeastRequestLoadBalancer.class); + } + + @Test + public void parseLoadBalancingConfig_valid() throws IOException { + String lbConfig = "{\"choiceCount\" : 3}"; + ConfigOrError configOrError = + provider.parseLoadBalancingPolicyConfig(parseJsonObject(lbConfig)); + assertThat(configOrError.getConfig()).isNotNull(); + LeastRequestConfig config = (LeastRequestConfig) configOrError.getConfig(); + assertThat(config.choiceCount).isEqualTo(3); + } + + @Test + public void parseLoadBalancingConfig_missingChoiceCount_useDefaults() throws IOException { + String lbConfig = "{}"; + ConfigOrError configOrError = + provider.parseLoadBalancingPolicyConfig(parseJsonObject(lbConfig)); + assertThat(configOrError.getConfig()).isNotNull(); + LeastRequestConfig config = (LeastRequestConfig) configOrError.getConfig(); + assertThat(config.choiceCount) + .isEqualTo(LeastRequestLoadBalancerProvider.DEFAULT_CHOICE_COUNT); + } + + @Test + public void parseLoadBalancingConfig_invalid_negativeSize() throws IOException { + String lbConfig = "{\"choiceCount\" : -10}"; + ConfigOrError configOrError = + provider.parseLoadBalancingPolicyConfig(parseJsonObject(lbConfig)); + assertThat(configOrError.getError()).isNotNull(); + assertThat(configOrError.getError().getCode()).isEqualTo(Code.INVALID_ARGUMENT); + assertThat(configOrError.getError().getDescription()) + .isEqualTo("Invalid 'choiceCount'"); + } + + @Test + public void parseLoadBalancingConfig_invalid_tooSmallSize() throws IOException { + String lbConfig = "{\"choiceCount\" : 1}"; + ConfigOrError configOrError = + provider.parseLoadBalancingPolicyConfig(parseJsonObject(lbConfig)); + assertThat(configOrError.getError()).isNotNull(); + assertThat(configOrError.getError().getCode()).isEqualTo(Code.INVALID_ARGUMENT); + assertThat(configOrError.getError().getDescription()) + .isEqualTo("Invalid 'choiceCount'"); + } + + @Test + public void parseLoadBalancingConfig_choiceCountCappedAtMax() throws IOException { + String lbConfig = "{\"choiceCount\" : 11}"; + ConfigOrError configOrError = + provider.parseLoadBalancingPolicyConfig(parseJsonObject(lbConfig)); + assertThat(configOrError.getConfig()).isNotNull(); + LeastRequestConfig config = (LeastRequestConfig) configOrError.getConfig(); + assertThat(config.choiceCount).isEqualTo(LeastRequestLoadBalancerProvider.MAX_CHOICE_COUNT); + } + + @Test + public void parseLoadBalancingConfig_invalidInteger() throws IOException { + Map lbConfig = parseJsonObject("{\"choiceCount\" : \"NaN\"}"); + ConfigOrError configOrError = + provider.parseLoadBalancingPolicyConfig(lbConfig); + assertThat(configOrError.getError()).isNotNull(); + assertThat(configOrError.getError().getDescription()).isEqualTo( + "Failed to parse least_request_experimental LB config: " + lbConfig); + } + + @SuppressWarnings("unchecked") + private static Map parseJsonObject(String json) throws IOException { + return (Map) JsonParser.parse(json); + } +} diff --git a/xds/src/test/java/io/grpc/xds/LeastRequestLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/LeastRequestLoadBalancerTest.java new file mode 100644 index 00000000000..2d09dbfe1fc --- /dev/null +++ b/xds/src/test/java/io/grpc/xds/LeastRequestLoadBalancerTest.java @@ -0,0 +1,632 @@ +/* + * Copyright 2021 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds; + +import static com.google.common.truth.Truth.assertThat; +import static io.grpc.ConnectivityState.CONNECTING; +import static io.grpc.ConnectivityState.IDLE; +import static io.grpc.ConnectivityState.READY; +import static io.grpc.ConnectivityState.SHUTDOWN; +import static io.grpc.ConnectivityState.TRANSIENT_FAILURE; +import static io.grpc.xds.LeastRequestLoadBalancer.IN_FLIGHTS; +import static io.grpc.xds.LeastRequestLoadBalancer.STATE_INFO; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import io.grpc.Attributes; +import io.grpc.ClientStreamTracer; +import io.grpc.ClientStreamTracer.StreamInfo; +import io.grpc.ConnectivityState; +import io.grpc.ConnectivityStateInfo; +import io.grpc.EquivalentAddressGroup; +import io.grpc.LoadBalancer; +import io.grpc.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.Metadata; +import io.grpc.Status; +import io.grpc.xds.LeastRequestLoadBalancer.EmptyPicker; +import io.grpc.xds.LeastRequestLoadBalancer.LeastRequestConfig; +import io.grpc.xds.LeastRequestLoadBalancer.ReadyPicker; +import io.grpc.xds.LeastRequestLoadBalancer.Ref; +import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +/** Unit test for {@link LeastRequestLoadBalancer}. */ +@RunWith(JUnit4.class) +public class LeastRequestLoadBalancerTest { + private static final Attributes.Key MAJOR_KEY = Attributes.Key.create("major-key"); + + private LeastRequestLoadBalancer loadBalancer; + private final List servers = Lists.newArrayList(); + private final Map, Subchannel> subchannels = Maps.newLinkedHashMap(); + private final Map subchannelStateListeners = + Maps.newLinkedHashMap(); + private final Attributes affinity = + Attributes.newBuilder().set(MAJOR_KEY, "I got the keys").build(); + + @Captor + private ArgumentCaptor pickerCaptor; + @Captor + private ArgumentCaptor stateCaptor; + @Captor + private ArgumentCaptor createArgsCaptor; + @Mock + private Helper mockHelper; + @Mock + private ThreadSafeRandom mockRandom; + + @Mock // This LoadBalancer doesn't use any of the arg fields, as verified in tearDown(). + private PickSubchannelArgs mockArgs; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + for (int i = 0; i < 3; 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); + } + + 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 LeastRequestLoadBalancer(mockHelper, mockRandom); + } + + @After + public void tearDown() throws Exception { + verifyNoMoreInteractions(mockRandom); + verifyNoMoreInteractions(mockArgs); + } + + @Test + public void pickAfterResolved() throws Exception { + final Subchannel readySubchannel = subchannels.values().iterator().next(); + loadBalancer.handleResolvedAddresses( + ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(affinity).build()); + deliverSubchannelState(readySubchannel, ConnectivityStateInfo.forNonError(READY)); + + verify(mockHelper, times(3)).createSubchannel(createArgsCaptor.capture()); + List> capturedAddrs = new ArrayList<>(); + for (CreateSubchannelArgs arg : createArgsCaptor.getAllValues()) { + capturedAddrs.add(arg.getAddresses()); + } + + assertThat(capturedAddrs).containsAtLeastElementsIn(subchannels.keySet()); + for (Subchannel subchannel : subchannels.values()) { + verify(subchannel).requestConnection(); + verify(subchannel, never()).shutdown(); + } + + verify(mockHelper, times(2)) + .updateBalancingState(stateCaptor.capture(), pickerCaptor.capture()); + + assertEquals(CONNECTING, stateCaptor.getAllValues().get(0)); + assertEquals(READY, stateCaptor.getAllValues().get(1)); + assertThat(getList(pickerCaptor.getValue())).containsExactly(readySubchannel); + + verifyNoMoreInteractions(mockHelper); + } + + @Test + public void pickAfterResolvedUpdatedHosts() throws Exception { + Subchannel removedSubchannel = mock(Subchannel.class); + Subchannel oldSubchannel = mock(Subchannel.class); + Subchannel newSubchannel = mock(Subchannel.class); + + Attributes.Key key = Attributes.Key.create("check-that-it-is-propagated"); + FakeSocketAddress removedAddr = new FakeSocketAddress("removed"); + EquivalentAddressGroup removedEag = new EquivalentAddressGroup(removedAddr); + FakeSocketAddress oldAddr = new FakeSocketAddress("old"); + EquivalentAddressGroup oldEag1 = new EquivalentAddressGroup(oldAddr); + EquivalentAddressGroup oldEag2 = new EquivalentAddressGroup( + oldAddr, Attributes.newBuilder().set(key, "oldattr").build()); + FakeSocketAddress newAddr = new FakeSocketAddress("new"); + EquivalentAddressGroup newEag = new EquivalentAddressGroup( + newAddr, Attributes.newBuilder().set(key, "newattr").build()); + + subchannels.put(Collections.singletonList(removedEag), removedSubchannel); + subchannels.put(Collections.singletonList(oldEag1), oldSubchannel); + subchannels.put(Collections.singletonList(newEag), newSubchannel); + + List currentServers = Lists.newArrayList(removedEag, oldEag1); + + InOrder inOrder = inOrder(mockHelper); + + loadBalancer.handleResolvedAddresses( + ResolvedAddresses.newBuilder().setAddresses(currentServers).setAttributes(affinity) + .build()); + + inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), pickerCaptor.capture()); + + deliverSubchannelState(removedSubchannel, ConnectivityStateInfo.forNonError(READY)); + deliverSubchannelState(oldSubchannel, ConnectivityStateInfo.forNonError(READY)); + + inOrder.verify(mockHelper, times(2)).updateBalancingState(eq(READY), pickerCaptor.capture()); + SubchannelPicker picker = pickerCaptor.getValue(); + assertThat(getList(picker)).containsExactly(removedSubchannel, oldSubchannel); + + verify(removedSubchannel, times(1)).requestConnection(); + verify(oldSubchannel, times(1)).requestConnection(); + + assertThat(loadBalancer.getSubchannels()).containsExactly(removedSubchannel, + oldSubchannel); + + // This time with Attributes + List latestServers = Lists.newArrayList(oldEag2, newEag); + + loadBalancer.handleResolvedAddresses( + ResolvedAddresses.newBuilder().setAddresses(latestServers).setAttributes(affinity).build()); + + verify(newSubchannel, times(1)).requestConnection(); + verify(oldSubchannel, times(1)).updateAddresses(Arrays.asList(oldEag2)); + verify(removedSubchannel, times(1)).shutdown(); + + deliverSubchannelState(removedSubchannel, ConnectivityStateInfo.forNonError(SHUTDOWN)); + deliverSubchannelState(newSubchannel, ConnectivityStateInfo.forNonError(READY)); + + assertThat(loadBalancer.getSubchannels()).containsExactly(oldSubchannel, + newSubchannel); + + verify(mockHelper, times(3)).createSubchannel(any(CreateSubchannelArgs.class)); + inOrder.verify(mockHelper, times(2)).updateBalancingState(eq(READY), pickerCaptor.capture()); + + picker = pickerCaptor.getValue(); + assertThat(getList(picker)).containsExactly(oldSubchannel, newSubchannel); + + // test going from non-empty to empty + loadBalancer.handleResolvedAddresses( + ResolvedAddresses.newBuilder() + .setAddresses(Collections.emptyList()) + .setAttributes(affinity) + .build()); + + inOrder.verify(mockHelper).updateBalancingState(eq(TRANSIENT_FAILURE), pickerCaptor.capture()); + assertEquals(PickResult.withNoResult(), pickerCaptor.getValue().pickSubchannel(mockArgs)); + + verifyNoMoreInteractions(mockHelper); + } + + @Test + public void pickAfterStateChange() throws Exception { + InOrder inOrder = inOrder(mockHelper); + loadBalancer.handleResolvedAddresses( + ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(Attributes.EMPTY) + .build()); + Subchannel subchannel = loadBalancer.getSubchannels().iterator().next(); + Ref subchannelStateInfo = subchannel.getAttributes().get( + STATE_INFO); + + inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), isA(EmptyPicker.class)); + assertThat(subchannelStateInfo.value).isEqualTo(ConnectivityStateInfo.forNonError(IDLE)); + + deliverSubchannelState(subchannel, + ConnectivityStateInfo.forNonError(READY)); + inOrder.verify(mockHelper).updateBalancingState(eq(READY), pickerCaptor.capture()); + assertThat(pickerCaptor.getValue()).isInstanceOf(ReadyPicker.class); + assertThat(subchannelStateInfo.value).isEqualTo( + ConnectivityStateInfo.forNonError(READY)); + + Status error = Status.UNKNOWN.withDescription("¯\\_(ツ)_//¯"); + deliverSubchannelState(subchannel, + ConnectivityStateInfo.forTransientFailure(error)); + assertThat(subchannelStateInfo.value.getState()).isEqualTo(TRANSIENT_FAILURE); + assertThat(subchannelStateInfo.value.getStatus()).isEqualTo(error); + inOrder.verify(mockHelper).refreshNameResolution(); + inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), pickerCaptor.capture()); + assertThat(pickerCaptor.getValue()).isInstanceOf(EmptyPicker.class); + + deliverSubchannelState(subchannel, + ConnectivityStateInfo.forNonError(IDLE)); + inOrder.verify(mockHelper).refreshNameResolution(); + assertThat(subchannelStateInfo.value.getState()).isEqualTo(TRANSIENT_FAILURE); + assertThat(subchannelStateInfo.value.getStatus()).isEqualTo(error); + + verify(subchannel, times(2)).requestConnection(); + verify(mockHelper, times(3)).createSubchannel(any(CreateSubchannelArgs.class)); + verifyNoMoreInteractions(mockHelper); + } + + @Test + public void pickAfterConfigChange() { + final LeastRequestConfig oldConfig = new LeastRequestConfig(4); + final LeastRequestConfig newConfig = new LeastRequestConfig(6); + final Subchannel readySubchannel = subchannels.values().iterator().next(); + loadBalancer.handleResolvedAddresses( + ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(affinity) + .setLoadBalancingPolicyConfig(oldConfig).build()); + deliverSubchannelState(readySubchannel, ConnectivityStateInfo.forNonError(READY)); + verify(mockHelper, times(3)).createSubchannel(any(CreateSubchannelArgs.class)); + verify(mockHelper, times(2)) + .updateBalancingState(any(ConnectivityState.class), pickerCaptor.capture()); + + // At this point it should use a ReadyPicker with oldConfig + pickerCaptor.getValue().pickSubchannel(mockArgs); + verify(mockRandom, times(oldConfig.choiceCount)).nextInt(1); + + loadBalancer.handleResolvedAddresses( + ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(affinity) + .setLoadBalancingPolicyConfig(newConfig).build()); + verify(mockHelper, times(3)) + .updateBalancingState(any(ConnectivityState.class), pickerCaptor.capture()); + + // At this point it should use a ReadyPicker with newConfig + pickerCaptor.getValue().pickSubchannel(mockArgs); + verify(mockRandom, times(oldConfig.choiceCount + newConfig.choiceCount)).nextInt(1); + verifyNoMoreInteractions(mockHelper); + } + + @Test + public void ignoreShutdownSubchannelStateChange() { + InOrder inOrder = inOrder(mockHelper); + loadBalancer.handleResolvedAddresses( + ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(Attributes.EMPTY) + .build()); + inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), isA(EmptyPicker.class)); + + loadBalancer.shutdown(); + for (Subchannel sc : loadBalancer.getSubchannels()) { + verify(sc).shutdown(); + // When the subchannel is being shut down, a SHUTDOWN connectivity state is delivered + // back to the subchannel state listener. + deliverSubchannelState(sc, ConnectivityStateInfo.forNonError(SHUTDOWN)); + } + + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void stayTransientFailureUntilReady() { + InOrder inOrder = inOrder(mockHelper); + loadBalancer.handleResolvedAddresses( + ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(Attributes.EMPTY) + .build()); + + inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), isA(EmptyPicker.class)); + + // Simulate state transitions for each subchannel individually. + for (Subchannel sc : loadBalancer.getSubchannels()) { + Status error = Status.UNKNOWN.withDescription("connection broken"); + deliverSubchannelState( + sc, + ConnectivityStateInfo.forTransientFailure(error)); + inOrder.verify(mockHelper).refreshNameResolution(); + deliverSubchannelState( + sc, + ConnectivityStateInfo.forNonError(CONNECTING)); + Ref scStateInfo = sc.getAttributes().get( + STATE_INFO); + assertThat(scStateInfo.value.getState()).isEqualTo(TRANSIENT_FAILURE); + assertThat(scStateInfo.value.getStatus()).isEqualTo(error); + } + inOrder.verify(mockHelper).updateBalancingState(eq(TRANSIENT_FAILURE), isA(EmptyPicker.class)); + inOrder.verifyNoMoreInteractions(); + + Subchannel subchannel = loadBalancer.getSubchannels().iterator().next(); + deliverSubchannelState(subchannel, ConnectivityStateInfo.forNonError(READY)); + Ref subchannelStateInfo = subchannel.getAttributes().get( + STATE_INFO); + assertThat(subchannelStateInfo.value).isEqualTo(ConnectivityStateInfo.forNonError(READY)); + inOrder.verify(mockHelper).updateBalancingState(eq(READY), isA(ReadyPicker.class)); + + verify(mockHelper, times(3)).createSubchannel(any(CreateSubchannelArgs.class)); + verifyNoMoreInteractions(mockHelper); + } + + @Test + public void refreshNameResolutionWhenSubchannelConnectionBroken() { + InOrder inOrder = inOrder(mockHelper); + loadBalancer.handleResolvedAddresses( + ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(Attributes.EMPTY) + .build()); + + verify(mockHelper, times(3)).createSubchannel(any(CreateSubchannelArgs.class)); + inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), isA(EmptyPicker.class)); + + // Simulate state transitions for each subchannel individually. + for (Subchannel sc : loadBalancer.getSubchannels()) { + verify(sc).requestConnection(); + deliverSubchannelState(sc, ConnectivityStateInfo.forNonError(CONNECTING)); + Status error = Status.UNKNOWN.withDescription("connection broken"); + deliverSubchannelState(sc, ConnectivityStateInfo.forTransientFailure(error)); + inOrder.verify(mockHelper).refreshNameResolution(); + deliverSubchannelState(sc, ConnectivityStateInfo.forNonError(READY)); + inOrder.verify(mockHelper).updateBalancingState(eq(READY), isA(ReadyPicker.class)); + // Simulate receiving go-away so READY subchannels transit to IDLE. + deliverSubchannelState(sc, ConnectivityStateInfo.forNonError(IDLE)); + inOrder.verify(mockHelper).refreshNameResolution(); + verify(sc, times(2)).requestConnection(); + inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), isA(EmptyPicker.class)); + } + + verifyNoMoreInteractions(mockHelper); + } + + @Test + public void pickerLeastRequest() throws Exception { + int choiceCount = 2; + // This should add inFlight counters to all subchannels. + loadBalancer.handleResolvedAddresses( + ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(Attributes.EMPTY) + .setLoadBalancingPolicyConfig(new LeastRequestConfig(choiceCount)) + .build()); + + assertEquals(3, loadBalancer.getSubchannels().size()); + + List subchannels = Lists.newArrayList(loadBalancer.getSubchannels()); + + // Make sure all inFlight counters have started at 0 + assertEquals(0, + subchannels.get(0).getAttributes().get(IN_FLIGHTS).get()); + assertEquals(0, + subchannels.get(1).getAttributes().get(IN_FLIGHTS).get()); + assertEquals(0, + subchannels.get(2).getAttributes().get(IN_FLIGHTS).get()); + + for (Subchannel sc : subchannels) { + deliverSubchannelState(sc, ConnectivityStateInfo.forNonError(READY)); + } + + // Capture the active ReadyPicker once all subchannels are READY + verify(mockHelper, times(4)) + .updateBalancingState(any(ConnectivityState.class), pickerCaptor.capture()); + assertThat(pickerCaptor.getValue()).isInstanceOf(ReadyPicker.class); + + ReadyPicker picker = (ReadyPicker) pickerCaptor.getValue(); + + assertThat(picker.getList()).containsExactlyElementsIn(subchannels); + + // Make random return 0, then 2 for the sample indexes. + when(mockRandom.nextInt(subchannels.size())).thenReturn(0, 2); + PickResult pickResult1 = picker.pickSubchannel(mockArgs); + verify(mockRandom, times(choiceCount)).nextInt(subchannels.size()); + assertEquals(subchannels.get(0), pickResult1.getSubchannel()); + // This simulates sending the actual RPC on the picked channel + ClientStreamTracer streamTracer1 = + pickResult1.getStreamTracerFactory() + .newClientStreamTracer(StreamInfo.newBuilder().build(), new Metadata()); + streamTracer1.streamCreated(Attributes.EMPTY, new Metadata()); + assertEquals(1, + pickResult1.getSubchannel().getAttributes().get(IN_FLIGHTS).get()); + + // For the second pick it should pick the one with lower inFlight. + when(mockRandom.nextInt(subchannels.size())).thenReturn(0, 2); + PickResult pickResult2 = picker.pickSubchannel(mockArgs); + // Since this is the second pick we expect the total random samples to be choiceCount * 2 + verify(mockRandom, times(choiceCount * 2)).nextInt(subchannels.size()); + assertEquals(subchannels.get(2), pickResult2.getSubchannel()); + + // For the third pick we unavoidably pick subchannel with index 1. + when(mockRandom.nextInt(subchannels.size())).thenReturn(1, 1); + PickResult pickResult3 = picker.pickSubchannel(mockArgs); + verify(mockRandom, times(choiceCount * 3)).nextInt(subchannels.size()); + assertEquals(subchannels.get(1), pickResult3.getSubchannel()); + + // Finally ensure a finished RPC decreases inFlight + streamTracer1.streamClosed(Status.OK); + assertEquals(0, + pickResult1.getSubchannel().getAttributes().get(IN_FLIGHTS).get()); + } + + @Test + public void pickerEmptyList() throws Exception { + SubchannelPicker picker = new EmptyPicker(Status.UNKNOWN); + + assertEquals(null, picker.pickSubchannel(mockArgs).getSubchannel()); + assertEquals(Status.UNKNOWN, + picker.pickSubchannel(mockArgs).getStatus()); + } + + @Test + public void nameResolutionErrorWithNoChannels() throws Exception { + Status error = Status.NOT_FOUND.withDescription("nameResolutionError"); + loadBalancer.handleNameResolutionError(error); + verify(mockHelper).updateBalancingState(eq(TRANSIENT_FAILURE), pickerCaptor.capture()); + LoadBalancer.PickResult pickResult = pickerCaptor.getValue().pickSubchannel(mockArgs); + assertNull(pickResult.getSubchannel()); + assertEquals(error, pickResult.getStatus()); + verifyNoMoreInteractions(mockHelper); + } + + @Test + public void nameResolutionErrorWithActiveChannels() throws Exception { + int choiceCount = 8; + final Subchannel readySubchannel = subchannels.values().iterator().next(); + loadBalancer.handleResolvedAddresses( + ResolvedAddresses.newBuilder() + .setLoadBalancingPolicyConfig(new LeastRequestConfig(choiceCount)) + .setAddresses(servers).setAttributes(affinity).build()); + deliverSubchannelState(readySubchannel, ConnectivityStateInfo.forNonError(READY)); + loadBalancer.handleNameResolutionError(Status.NOT_FOUND.withDescription("nameResolutionError")); + + verify(mockHelper, times(3)).createSubchannel(any(CreateSubchannelArgs.class)); + verify(mockHelper, times(2)) + .updateBalancingState(stateCaptor.capture(), pickerCaptor.capture()); + + Iterator stateIterator = stateCaptor.getAllValues().iterator(); + assertEquals(CONNECTING, stateIterator.next()); + assertEquals(READY, stateIterator.next()); + + LoadBalancer.PickResult pickResult = pickerCaptor.getValue().pickSubchannel(mockArgs); + verify(mockRandom, times(choiceCount)).nextInt(1); + assertEquals(readySubchannel, pickResult.getSubchannel()); + assertEquals(Status.OK.getCode(), pickResult.getStatus().getCode()); + + LoadBalancer.PickResult pickResult2 = pickerCaptor.getValue().pickSubchannel(mockArgs); + verify(mockRandom, times(choiceCount * 2)).nextInt(1); + assertEquals(readySubchannel, pickResult2.getSubchannel()); + verifyNoMoreInteractions(mockHelper); + } + + @Test + public void subchannelStateIsolation() throws Exception { + Iterator subchannelIterator = subchannels.values().iterator(); + Subchannel sc1 = subchannelIterator.next(); + Subchannel sc2 = subchannelIterator.next(); + Subchannel sc3 = subchannelIterator.next(); + + loadBalancer.handleResolvedAddresses( + ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(Attributes.EMPTY) + .build()); + verify(sc1, times(1)).requestConnection(); + verify(sc2, times(1)).requestConnection(); + verify(sc3, times(1)).requestConnection(); + + deliverSubchannelState(sc1, ConnectivityStateInfo.forNonError(READY)); + deliverSubchannelState(sc2, ConnectivityStateInfo.forNonError(READY)); + deliverSubchannelState(sc3, ConnectivityStateInfo.forNonError(READY)); + deliverSubchannelState(sc2, ConnectivityStateInfo.forNonError(IDLE)); + deliverSubchannelState(sc3, ConnectivityStateInfo.forTransientFailure(Status.UNAVAILABLE)); + + verify(mockHelper, times(6)) + .updateBalancingState(stateCaptor.capture(), pickerCaptor.capture()); + Iterator stateIterator = stateCaptor.getAllValues().iterator(); + Iterator pickers = pickerCaptor.getAllValues().iterator(); + // The picker is incrementally updated as subchannels become READY + assertEquals(CONNECTING, stateIterator.next()); + assertThat(pickers.next()).isInstanceOf(EmptyPicker.class); + assertEquals(READY, stateIterator.next()); + assertThat(getList(pickers.next())).containsExactly(sc1); + assertEquals(READY, stateIterator.next()); + assertThat(getList(pickers.next())).containsExactly(sc1, sc2); + assertEquals(READY, stateIterator.next()); + assertThat(getList(pickers.next())).containsExactly(sc1, sc2, sc3); + // The IDLE subchannel is dropped from the picker, but a reconnection is requested + assertEquals(READY, stateIterator.next()); + assertThat(getList(pickers.next())).containsExactly(sc1, sc3); + verify(sc2, times(2)).requestConnection(); + // The failing subchannel is dropped from the picker, with no requested reconnect + assertEquals(READY, stateIterator.next()); + assertThat(getList(pickers.next())).containsExactly(sc1); + verify(sc3, times(1)).requestConnection(); + assertThat(stateIterator.hasNext()).isFalse(); + assertThat(pickers.hasNext()).isFalse(); + } + + @Test(expected = IllegalArgumentException.class) + public void readyPicker_emptyList() { + // ready picker list must be non-empty + new ReadyPicker(Collections.emptyList(), 2, mockRandom); + } + + @Test + public void internalPickerComparisons() { + EmptyPicker emptyOk1 = new EmptyPicker(Status.OK); + EmptyPicker emptyOk2 = new EmptyPicker(Status.OK.withDescription("different OK")); + EmptyPicker emptyErr = new EmptyPicker(Status.UNKNOWN.withDescription("¯\\_(ツ)_//¯")); + + Iterator subchannelIterator = subchannels.values().iterator(); + Subchannel sc1 = subchannelIterator.next(); + Subchannel sc2 = subchannelIterator.next(); + ReadyPicker ready1 = new ReadyPicker(Arrays.asList(sc1, sc2), 2, mockRandom); + ReadyPicker ready2 = new ReadyPicker(Arrays.asList(sc1), 2, mockRandom); + ReadyPicker ready3 = new ReadyPicker(Arrays.asList(sc2, sc1), 2, mockRandom); + ReadyPicker ready4 = new ReadyPicker(Arrays.asList(sc1, sc2), 2, mockRandom); + ReadyPicker ready5 = new ReadyPicker(Arrays.asList(sc2, sc1), 2, mockRandom); + ReadyPicker ready6 = new ReadyPicker(Arrays.asList(sc2, sc1), 8, mockRandom); + + assertTrue(emptyOk1.isEquivalentTo(emptyOk2)); + assertFalse(emptyOk1.isEquivalentTo(emptyErr)); + assertFalse(ready1.isEquivalentTo(ready2)); + assertTrue(ready1.isEquivalentTo(ready3)); + assertTrue(ready3.isEquivalentTo(ready4)); + assertTrue(ready4.isEquivalentTo(ready5)); + assertFalse(emptyOk1.isEquivalentTo(ready1)); + assertFalse(ready1.isEquivalentTo(emptyOk1)); + assertFalse(ready5.isEquivalentTo(ready6)); + } + + private static List getList(SubchannelPicker picker) { + return picker instanceof ReadyPicker ? ((ReadyPicker) picker).getList() : + Collections.emptyList(); + } + + private void deliverSubchannelState(Subchannel subchannel, ConnectivityStateInfo newState) { + subchannelStateListeners.get(subchannel).onSubchannelState(newState); + } + + private static class FakeSocketAddress extends SocketAddress { + final String name; + + FakeSocketAddress(String name) { + this.name = name; + } + + @Override + public String toString() { + return "FakeSocketAddress-" + name; + } + } +} From 07567eebe659ea9bb6e8c3ade880e6c207a8cf32 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Wed, 19 Jan 2022 12:55:22 -0800 Subject: [PATCH 19/68] xds: XdsNameResolver change to support RouteAction with RLS plugin Implementation of the xDS Resolver section of the design http://go/grpc-rls-in-xds/view#heading=h.wkxepad0knu --- .../java/io/grpc/xds/XdsNameResolver.java | 154 +++++++--- .../java/io/grpc/xds/XdsNameResolverTest.java | 277 ++++++++++++++---- 2 files changed, 346 insertions(+), 85 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java index b6b66327525..a52b1c3e963 100644 --- a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java +++ b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java @@ -23,6 +23,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; import com.google.common.net.UrlEscapers; @@ -49,9 +50,11 @@ import io.grpc.internal.ObjectPool; import io.grpc.xds.Bootstrapper.AuthorityInfo; import io.grpc.xds.Bootstrapper.BootstrapInfo; +import io.grpc.xds.ClusterSpecifierPlugin.PluginConfig; import io.grpc.xds.Filter.ClientInterceptorBuilder; import io.grpc.xds.Filter.FilterConfig; import io.grpc.xds.Filter.NamedFilterConfig; +import io.grpc.xds.RouteLookupServiceClusterSpecifierPlugin.RlsPluginConfig; import io.grpc.xds.ThreadSafeRandom.ThreadSafeRandomImpl; import io.grpc.xds.VirtualHost.Route; import io.grpc.xds.VirtualHost.Route.RouteAction; @@ -70,13 +73,13 @@ import io.grpc.xds.internal.Matchers.FractionMatcher; import io.grpc.xds.internal.Matchers.HeaderMatcher; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -116,7 +119,8 @@ final class XdsNameResolver extends NameResolver { private final FilterRegistry filterRegistry; private final XxHash64 hashFunc = XxHash64.INSTANCE; // Clusters (with reference counts) to which new/existing requests can be/are routed. - private final ConcurrentMap clusterRefs = new ConcurrentHashMap<>(); + // 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 volatile RoutingConfig routingConfig = RoutingConfig.empty; @@ -246,31 +250,25 @@ public void shutdown() { "methodConfig", Collections.singletonList(methodConfig.build())); } - @VisibleForTesting - static Map generateServiceConfigWithLoadBalancingConfig(Collection clusters) { - Map childPolicy = new HashMap<>(); - for (String cluster : clusters) { - List>> lbPolicy = - Collections.singletonList( - Collections.singletonMap( - "cds_experimental", Collections.singletonMap("cluster", cluster))); - childPolicy.put(cluster, Collections.singletonMap("lbPolicy", lbPolicy)); - } - return Collections.singletonMap("loadBalancingConfig", - Collections.singletonList( - Collections.singletonMap( - "cluster_manager_experimental", Collections.singletonMap( - "childPolicy", Collections.unmodifiableMap(childPolicy))))); - } - @VisibleForTesting XdsClient getXdsClient() { return xdsClient; } + // called in syncContext private void updateResolutionResult() { - Map rawServiceConfig = - generateServiceConfigWithLoadBalancingConfig(clusterRefs.keySet()); + syncContext.throwIfNotInThisSynchronizationContext(); + + ImmutableMap.Builder childPolicy = new ImmutableMap.Builder<>(); + for (String name : clusterRefs.keySet()) { + Map lbPolicy = clusterRefs.get(name).toLbPolicy(); + childPolicy.put(name, ImmutableMap.of("lbPolicy", ImmutableList.of(lbPolicy))); + } + Map rawServiceConfig = ImmutableMap.of( + "loadBalancingConfig", + ImmutableList.of(ImmutableMap.of( + "cluster_manager_experimental", ImmutableMap.of("childPolicy", childPolicy.build())))); + if (logger.isLoggable(XdsLogLevel.INFO)) { logger.log( XdsLogLevel.INFO, "Generated service config:\n{0}", new Gson().toJson(rawServiceConfig)); @@ -421,7 +419,7 @@ public Result selectConfig(PickSubchannelArgs args) { } RouteAction action = selectedRoute.routeAction(); if (action.cluster() != null) { - cluster = action.cluster(); + cluster = prefixedClusterName(action.cluster()); } else if (action.weightedClusters() != null) { int totalWeight = 0; for (ClusterWeight weightedCluster : action.weightedClusters()) { @@ -432,11 +430,14 @@ public Result selectConfig(PickSubchannelArgs args) { for (ClusterWeight weightedCluster : action.weightedClusters()) { accumulator += weightedCluster.weight(); if (select < accumulator) { - cluster = weightedCluster.name(); + cluster = prefixedClusterName(weightedCluster.name()); selectedOverrideConfigs.putAll(weightedCluster.filterConfigOverrides()); break; } } + } else if (action.namedClusterSpecifierPluginConfig() != null) { + cluster = + prefixedClusterSpecifierPluginName(action.namedClusterSpecifierPluginConfig().name()); } } while (!retainCluster(cluster)); Long timeoutNanos = null; @@ -526,10 +527,11 @@ public void onClose(Status status, Metadata trailers) { } private boolean retainCluster(String cluster) { - AtomicInteger refCount = clusterRefs.get(cluster); - if (refCount == null) { + ClusterRefState clusterRefState = clusterRefs.get(cluster); + if (clusterRefState == null) { return false; } + AtomicInteger refCount = clusterRefState.refCount; int count; do { count = refCount.get(); @@ -541,12 +543,12 @@ private boolean retainCluster(String cluster) { } private void releaseCluster(final String cluster) { - int count = clusterRefs.get(cluster).decrementAndGet(); + int count = clusterRefs.get(cluster).refCount.decrementAndGet(); if (count == 0) { syncContext.execute(new Runnable() { @Override public void run() { - if (clusterRefs.get(cluster).get() == 0) { + if (clusterRefs.get(cluster).refCount.get() == 0) { clusterRefs.remove(cluster); updateResolutionResult(); } @@ -647,6 +649,14 @@ private static String getHeaderValue(Metadata headers, String headerName) { return values == null ? null : Joiner.on(",").join(values); } + private static String prefixedClusterName(String name) { + return "cluster:" + name; + } + + private static String prefixedClusterSpecifierPluginName(String pluginName) { + return "cluster_specifier_plugin:" + pluginName; + } + private class ResolveState implements LdsResourceWatcher { private final ConfigOrError emptyServiceConfig = serviceConfigParser.parseServiceConfig(Collections.emptyMap()); @@ -733,6 +743,7 @@ private void stop() { xdsClient.cancelLdsResourceWatch(ldsResourceName, this); } + // called in syncContext private void updateRoutes(List virtualHosts, long httpMaxStreamDurationNano, @Nullable List filterConfigs) { VirtualHost virtualHost = findVirtualHostForHostName(virtualHosts, ldsResourceName); @@ -747,14 +758,31 @@ private void updateRoutes(List virtualHosts, long httpMaxStreamDura // Populate all clusters to which requests can be routed to through the virtual host. Set clusters = new HashSet<>(); + // uniqueName -> clusterName + Map clusterNameMap = new HashMap<>(); + // uniqueName -> pluginConfig + Map rlsPluginConfigMap = new HashMap<>(); for (Route route : routes) { RouteAction action = route.routeAction(); + String prefixedName; if (action != null) { if (action.cluster() != null) { - clusters.add(action.cluster()); + prefixedName = prefixedClusterName(action.cluster()); + clusters.add(prefixedName); + clusterNameMap.put(prefixedName, action.cluster()); } else if (action.weightedClusters() != null) { for (ClusterWeight weighedCluster : action.weightedClusters()) { - clusters.add(weighedCluster.name()); + prefixedName = prefixedClusterName(weighedCluster.name()); + clusters.add(prefixedName); + clusterNameMap.put(prefixedName, weighedCluster.name()); + } + } else if (action.namedClusterSpecifierPluginConfig() != null) { + PluginConfig pluginConfig = action.namedClusterSpecifierPluginConfig().config(); + if (pluginConfig instanceof RlsPluginConfig) { + prefixedName = prefixedClusterSpecifierPluginName( + action.namedClusterSpecifierPluginConfig().name()); + clusters.add(prefixedName); + rlsPluginConfigMap.put(prefixedName, (RlsPluginConfig) pluginConfig); } } } @@ -770,9 +798,28 @@ private void updateRoutes(List virtualHosts, long httpMaxStreamDura existingClusters = clusters; for (String cluster : addedClusters) { if (clusterRefs.containsKey(cluster)) { - clusterRefs.get(cluster).incrementAndGet(); + clusterRefs.get(cluster).refCount.incrementAndGet(); } else { - clusterRefs.put(cluster, new AtomicInteger(1)); + if (clusterNameMap.containsKey(cluster)) { + clusterRefs.put( + cluster, + ClusterRefState.forCluster(new AtomicInteger(1), clusterNameMap.get(cluster))); + } + if (rlsPluginConfigMap.containsKey(cluster)) { + clusterRefs.put( + cluster, + ClusterRefState.forRlsPlugin( + new AtomicInteger(1), rlsPluginConfigMap.get(cluster))); + } + shouldUpdateResult = true; + } + } + for (String cluster : clusters) { + RlsPluginConfig rlsPluginConfig = rlsPluginConfigMap.get(cluster); + if (!Objects.equals(rlsPluginConfig, clusterRefs.get(cluster).rlsPluginConfig)) { + ClusterRefState newClusterRefState = + ClusterRefState.forRlsPlugin(clusterRefs.get(cluster).refCount, rlsPluginConfig); + clusterRefs.put(cluster, newClusterRefState); shouldUpdateResult = true; } } @@ -788,7 +835,7 @@ private void updateRoutes(List virtualHosts, long httpMaxStreamDura virtualHost.filterConfigOverrides()); shouldUpdateResult = false; for (String cluster : deletedClusters) { - int count = clusterRefs.get(cluster).decrementAndGet(); + int count = clusterRefs.get(cluster).refCount.decrementAndGet(); if (count == 0) { clusterRefs.remove(cluster); shouldUpdateResult = true; @@ -802,7 +849,7 @@ private void updateRoutes(List virtualHosts, long httpMaxStreamDura private void cleanUpRoutes() { if (existingClusters != null) { for (String cluster : existingClusters) { - int count = clusterRefs.get(cluster).decrementAndGet(); + int count = clusterRefs.get(cluster).refCount.decrementAndGet(); if (count == 0) { clusterRefs.remove(cluster); } @@ -905,4 +952,45 @@ private RoutingConfig( this.virtualHostOverrideConfig = Collections.unmodifiableMap(virtualHostOverrideConfig); } } + + private static class ClusterRefState { + final AtomicInteger refCount; + @Nullable + final String traditionalCluster; + @Nullable + final RlsPluginConfig rlsPluginConfig; + + private ClusterRefState( + AtomicInteger refCount, @Nullable String traditionalCluster, + @Nullable RlsPluginConfig rlsPluginConfig) { + this.refCount = refCount; + checkArgument(traditionalCluster == null ^ rlsPluginConfig == null, + "There must be exactly one non-null value in traditionalCluster and pluginConfig"); + this.traditionalCluster = traditionalCluster; + this.rlsPluginConfig = rlsPluginConfig; + } + + private Map toLbPolicy() { + if (traditionalCluster != null) { + return ImmutableMap.of("cds_experimental", ImmutableMap.of("cluster", traditionalCluster)); + } else { + ImmutableMap rlsConfig = new ImmutableMap.Builder() + .put("routeLookupConfig", rlsPluginConfig.config()) + .put( + "childPolicy", + ImmutableList.of(ImmutableMap.of("cds_experimental", ImmutableMap.of()))) + .put("childPolicyConfigTargetFieldName", "cluster") + .build(); + return ImmutableMap.of("rls_experimental", rlsConfig); + } + } + + static ClusterRefState forCluster(AtomicInteger refCount, String name) { + return new ClusterRefState(refCount, name, null); + } + + static ClusterRefState forRlsPlugin(AtomicInteger refCount, RlsPluginConfig rlsPluginConfig) { + return new ClusterRefState(refCount, null, rlsPluginConfig); + } + } } diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java index 7968b7fb366..68349806fdf 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java @@ -72,11 +72,13 @@ import io.grpc.xds.Bootstrapper.AuthorityInfo; import io.grpc.xds.Bootstrapper.BootstrapInfo; import io.grpc.xds.Bootstrapper.ServerInfo; +import io.grpc.xds.ClusterSpecifierPlugin.NamedPluginConfig; import io.grpc.xds.EnvoyProtoData.Node; import io.grpc.xds.FaultConfig.FaultAbort; import io.grpc.xds.FaultConfig.FaultDelay; import io.grpc.xds.Filter.FilterConfig; import io.grpc.xds.Filter.NamedFilterConfig; +import io.grpc.xds.RouteLookupServiceClusterSpecifierPlugin.RlsPluginConfig; import io.grpc.xds.VirtualHost.Route; import io.grpc.xds.VirtualHost.Route.RouteAction; import io.grpc.xds.VirtualHost.Route.RouteAction.ClusterWeight; @@ -87,6 +89,7 @@ import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory; import io.grpc.xds.internal.Matchers.HeaderMatcher; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -494,7 +497,7 @@ public void resolved_noTimeout() { verify(mockListener).onResult(resolutionResultCaptor.capture()); ResolutionResult result = resolutionResultCaptor.getValue(); InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY); - assertCallSelectResult(call1, configSelector, cluster1, null); + assertCallSelectClusterResult(call1, configSelector, cluster1, null); } @Test @@ -513,7 +516,7 @@ public void resolved_fallbackToHttpMaxStreamDurationAsTimeout() { verify(mockListener).onResult(resolutionResultCaptor.capture()); ResolutionResult result = resolutionResultCaptor.getValue(); InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY); - assertCallSelectResult(call1, configSelector, cluster1, 5.0); + assertCallSelectClusterResult(call1, configSelector, cluster1, 5.0); } @Test @@ -562,7 +565,7 @@ public void retryPolicyInPerMethodConfigGeneratedByResolverIsValid() { @Test public void resolved_simpleCallSucceeds() { InternalConfigSelector configSelector = resolveToClusters(); - assertCallSelectResult(call1, configSelector, cluster1, 15.0); + assertCallSelectClusterResult(call1, configSelector, cluster1, 15.0); testCall.deliverResponseHeaders(); verifyNoMoreInteractions(mockListener); } @@ -756,7 +759,7 @@ public void resolved_rpcHashingByChannelId() { @Test public void resolved_resourceUpdateAfterCallStarted() { InternalConfigSelector configSelector = resolveToClusters(); - assertCallSelectResult(call1, configSelector, cluster1, 15.0); + assertCallSelectClusterResult(call1, configSelector, cluster1, 15.0); TestCall firstCall = testCall; reset(mockListener); @@ -784,7 +787,7 @@ public void resolved_resourceUpdateAfterCallStarted() { (Map) result.getServiceConfig().getConfig()); assertThat(result.getAttributes().get(InternalConfigSelector.KEY)) .isSameInstanceAs(configSelector); - assertCallSelectResult(call1, configSelector, "another-cluster", 20.0); + assertCallSelectClusterResult(call1, configSelector, "another-cluster", 20.0); firstCall.deliverErrorStatus(); // completes previous call verify(mockListener, times(2)).onResult(resolutionResultCaptor.capture()); @@ -824,7 +827,7 @@ public void resolved_resourceUpdatedBeforeCallStarted() { (Map) result.getServiceConfig().getConfig()); assertThat(result.getAttributes().get(InternalConfigSelector.KEY)) .isSameInstanceAs(configSelector); - assertCallSelectResult(call1, configSelector, "another-cluster", 20.0); + assertCallSelectClusterResult(call1, configSelector, "another-cluster", 20.0); verifyNoMoreInteractions(mockListener); } @@ -833,7 +836,7 @@ public void resolved_resourceUpdatedBeforeCallStarted() { @Test public void resolved_raceBetweenCallAndRepeatedResourceUpdate() { InternalConfigSelector configSelector = resolveToClusters(); - assertCallSelectResult(call1, configSelector, cluster1, 15.0); + assertCallSelectClusterResult(call1, configSelector, cluster1, 15.0); reset(mockListener); FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); @@ -873,13 +876,13 @@ public void resolved_raceBetweenCallAndRepeatedResourceUpdate() { TimeUnit.SECONDS.toNanos(15L), null), ImmutableMap.of()))); verifyNoMoreInteractions(mockListener); // no cluster added/deleted - assertCallSelectResult(call1, configSelector, "another-cluster", 15.0); + assertCallSelectClusterResult(call1, configSelector, "another-cluster", 15.0); } @Test public void resolved_raceBetweenClusterReleasedAndResourceUpdateAddBackAgain() { InternalConfigSelector configSelector = resolveToClusters(); - assertCallSelectResult(call1, configSelector, cluster1, 15.0); + assertCallSelectClusterResult(call1, configSelector, cluster1, 15.0); FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); xdsClient.deliverLdsUpdate( Collections.singletonList( @@ -933,8 +936,105 @@ public void resolved_simpleCallSucceeds_routeToWeightedCluster() { Arrays.asList(cluster1, cluster2), (Map) result.getServiceConfig().getConfig()); assertThat(result.getAttributes().get(InternalXdsAttributes.XDS_CLIENT_POOL)).isNotNull(); InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY); - assertCallSelectResult(call1, configSelector, cluster2, 20.0); - assertCallSelectResult(call1, configSelector, cluster1, 20.0); + assertCallSelectClusterResult(call1, configSelector, cluster2, 20.0); + assertCallSelectClusterResult(call1, configSelector, cluster1, 20.0); + } + + @Test + public void resolved_simpleCallSucceeds_routeToRls() { + when(mockRandom.nextInt(anyInt())).thenReturn(90, 10); + resolver.start(mockListener); + FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); + xdsClient.deliverLdsUpdate( + Collections.singletonList( + Route.forAction( + RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), + RouteAction.forClusterSpecifierPlugin( + NamedPluginConfig.create( + "rls-plugin-foo", + RlsPluginConfig.create( + ImmutableMap.of("lookupService", "rls-cbt.googleapis.com"))), + Collections.emptyList(), + TimeUnit.SECONDS.toNanos(20L), + null), + ImmutableMap.of()))); + verify(mockListener).onResult(resolutionResultCaptor.capture()); + ResolutionResult result = resolutionResultCaptor.getValue(); + assertThat(result.getAddresses()).isEmpty(); + @SuppressWarnings("unchecked") + Map resultServiceConfig = (Map) result.getServiceConfig().getConfig(); + List> rawLbConfigs = + JsonUtil.getListOfObjects(resultServiceConfig, "loadBalancingConfig"); + Map lbConfig = Iterables.getOnlyElement(rawLbConfigs); + assertThat(lbConfig.keySet()).containsExactly("cluster_manager_experimental"); + Map clusterManagerLbConfig = + JsonUtil.getObject(lbConfig, "cluster_manager_experimental"); + Map expectedRlsLbConfig = ImmutableMap.of( + "routeLookupConfig", + ImmutableMap.of("lookupService", "rls-cbt.googleapis.com"), + "childPolicy", + ImmutableList.of(ImmutableMap.of("cds_experimental", ImmutableMap.of())), + "childPolicyConfigTargetFieldName", + "cluster"); + Map expectedClusterManagerLbConfig = ImmutableMap.of( + "childPolicy", + ImmutableMap.of( + "cluster_specifier_plugin:rls-plugin-foo", + ImmutableMap.of( + "lbPolicy", + ImmutableList.of(ImmutableMap.of("rls_experimental", expectedRlsLbConfig))))); + assertThat(clusterManagerLbConfig).isEqualTo(expectedClusterManagerLbConfig); + + assertThat(result.getAttributes().get(InternalXdsAttributes.XDS_CLIENT_POOL)).isNotNull(); + InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY); + assertCallSelectRlsPluginResult( + call1, configSelector, "rls-plugin-foo", 20.0); + + // config changed + xdsClient.deliverLdsUpdate( + Collections.singletonList( + Route.forAction( + RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), + RouteAction.forClusterSpecifierPlugin( + NamedPluginConfig.create( + "rls-plugin-foo", + RlsPluginConfig.create( + // changed + ImmutableMap.of("lookupService", "rls-cbt-2.googleapis.com"))), + Collections.emptyList(), + // changed + TimeUnit.SECONDS.toNanos(30L), + null), + ImmutableMap.of()))); + verify(mockListener, times(2)).onResult(resolutionResultCaptor.capture()); + ResolutionResult result2 = resolutionResultCaptor.getValue(); + @SuppressWarnings("unchecked") + Map resultServiceConfig2 = (Map) result2.getServiceConfig().getConfig(); + List> rawLbConfigs2 = + JsonUtil.getListOfObjects(resultServiceConfig2, "loadBalancingConfig"); + Map lbConfig2 = Iterables.getOnlyElement(rawLbConfigs2); + assertThat(lbConfig2.keySet()).containsExactly("cluster_manager_experimental"); + Map clusterManagerLbConfig2 = + JsonUtil.getObject(lbConfig2, "cluster_manager_experimental"); + Map expectedRlsLbConfig2 = ImmutableMap.of( + "routeLookupConfig", + ImmutableMap.of("lookupService", "rls-cbt-2.googleapis.com"), + "childPolicy", + ImmutableList.of(ImmutableMap.of("cds_experimental", ImmutableMap.of())), + "childPolicyConfigTargetFieldName", + "cluster"); + Map expectedClusterManagerLbConfig2 = ImmutableMap.of( + "childPolicy", + ImmutableMap.of( + "cluster_specifier_plugin:rls-plugin-foo", + ImmutableMap.of( + "lbPolicy", + ImmutableList.of(ImmutableMap.of("rls_experimental", expectedRlsLbConfig2))))); + assertThat(clusterManagerLbConfig2).isEqualTo(expectedClusterManagerLbConfig2); + + InternalConfigSelector configSelector2 = result.getAttributes().get(InternalConfigSelector.KEY); + assertCallSelectRlsPluginResult( + call1, configSelector2, "rls-plugin-foo", 30.0); } @SuppressWarnings("unchecked") @@ -945,7 +1045,7 @@ private void assertEmptyResolutionResult() { assertThat((Map) result.getServiceConfig().getConfig()).isEmpty(); } - private void assertCallSelectResult( + private void assertCallSelectClusterResult( CallInfo call, InternalConfigSelector configSelector, String expectedCluster, @Nullable Double expectedTimeoutSec) { Result result = configSelector.selectConfig( @@ -956,7 +1056,7 @@ private void assertCallSelectResult( call.methodDescriptor, CallOptions.DEFAULT, channel); clientCall.start(new NoopClientCallListener(), new Metadata()); assertThat(testCall.callOptions.getOption(XdsNameResolver.CLUSTER_SELECTION_KEY)) - .isEqualTo(expectedCluster); + .isEqualTo("cluster:" + expectedCluster); @SuppressWarnings("unchecked") Map config = (Map) result.getConfig(); if (expectedTimeoutSec != null) { @@ -973,6 +1073,28 @@ private void assertCallSelectResult( } } + private void assertCallSelectRlsPluginResult( + CallInfo call, InternalConfigSelector configSelector, String expectedPluginName, + Double expectedTimeoutSec) { + Result result = configSelector.selectConfig( + new PickSubchannelArgsImpl(call.methodDescriptor, new Metadata(), CallOptions.DEFAULT)); + assertThat(result.getStatus().isOk()).isTrue(); + ClientInterceptor interceptor = result.getInterceptor(); + ClientCall clientCall = interceptor.interceptCall( + call.methodDescriptor, CallOptions.DEFAULT, channel); + clientCall.start(new NoopClientCallListener(), new Metadata()); + assertThat(testCall.callOptions.getOption(XdsNameResolver.CLUSTER_SELECTION_KEY)) + .isEqualTo("cluster_specifier_plugin:" + expectedPluginName); + @SuppressWarnings("unchecked") + Map config = (Map) result.getConfig(); + List> rawMethodConfigs = + JsonUtil.getListOfObjects(config, "methodConfig"); + Map methodConfig = Iterables.getOnlyElement(rawMethodConfigs); + List> methods = JsonUtil.getListOfObjects(methodConfig, "name"); + assertThat(Iterables.getOnlyElement(methods)).isEmpty(); + assertThat(JsonUtil.getString(methodConfig, "timeout")).isEqualTo(expectedTimeoutSec + "s"); + } + @SuppressWarnings("unchecked") private InternalConfigSelector resolveToClusters() { resolver.start(mockListener); @@ -1014,56 +1136,107 @@ private static void assertServiceConfigForLoadBalancingConfig( JsonUtil.getObject(lbConfig, "cluster_manager_experimental"); Map clusterManagerChildLbPolicies = JsonUtil.getObject(clusterManagerLbConfig, "childPolicy"); - assertThat(clusterManagerChildLbPolicies.keySet()).containsExactlyElementsIn(clusters); + List expectedChildLbClusterNames = new ArrayList<>(clusters.size()); for (String cluster : clusters) { - Map childLbConfig = JsonUtil.getObject(clusterManagerChildLbPolicies, cluster); + expectedChildLbClusterNames.add("cluster:" + cluster); + } + assertThat(clusterManagerChildLbPolicies.keySet()) + .containsExactlyElementsIn(expectedChildLbClusterNames); + for (int i = 0; i < clusters.size(); i++) { + Map childLbConfig = + JsonUtil.getObject(clusterManagerChildLbPolicies, expectedChildLbClusterNames.get(i)); assertThat(childLbConfig.keySet()).containsExactly("lbPolicy"); List> childLbConfigValues = JsonUtil.getListOfObjects(childLbConfig, "lbPolicy"); Map cdsLbPolicy = Iterables.getOnlyElement(childLbConfigValues); assertThat(cdsLbPolicy.keySet()).containsExactly("cds_experimental"); assertThat(JsonUtil.getObject(cdsLbPolicy, "cds_experimental")) - .containsExactly("cluster", cluster); + .containsExactly("cluster", clusters.get(i)); } } - @SuppressWarnings("unchecked") @Test - public void generateServiceConfig_forLoadBalancingConfig() throws IOException { - List clusters = Arrays.asList("cluster-foo", "cluster-bar", "cluster-baz"); - String expectedServiceConfigJson = "{\n" - + " \"loadBalancingConfig\": [{\n" - + " \"cluster_manager_experimental\": {\n" - + " \"childPolicy\": {\n" - + " \"cluster-foo\": {\n" - + " \"lbPolicy\": [{\n" - + " \"cds_experimental\": {\n" - + " \"cluster\": \"cluster-foo\"\n" - + " }\n" - + " }]\n" - + " },\n" - + " \"cluster-bar\": {\n" - + " \"lbPolicy\": [{\n" - + " \"cds_experimental\": {\n" - + " \"cluster\": \"cluster-bar\"\n" - + " }\n" - + " }]\n" - + " },\n" - + " \"cluster-baz\": {\n" - + " \"lbPolicy\": [{\n" - + " \"cds_experimental\": {\n" - + " \"cluster\": \"cluster-baz\"\n" - + " }\n" - + " }]\n" - + " }\n" - + " }\n" - + " }\n" - + " }]\n" - + "}"; - Map expectedServiceConfig = - (Map) JsonParser.parse(expectedServiceConfigJson); - assertThat(XdsNameResolver.generateServiceConfigWithLoadBalancingConfig(clusters)) - .isEqualTo(expectedServiceConfig); + public void generateServiceConfig_forClusterManagerLoadBalancingConfig() throws IOException { + Route route1 = Route.forAction( + RouteMatch.withPathExactOnly("HelloService/hi"), + RouteAction.forCluster( + "cluster-foo", Collections.emptyList(), TimeUnit.SECONDS.toNanos(15L), null), + ImmutableMap.of()); + Route route2 = Route.forAction( + RouteMatch.withPathExactOnly("HelloService/hello"), + RouteAction.forWeightedClusters( + ImmutableList.of( + ClusterWeight.create("cluster-bar", 50, ImmutableMap.of()), + ClusterWeight.create("cluster-baz", 50, ImmutableMap.of())), + ImmutableList.of(), + TimeUnit.SECONDS.toNanos(15L), + null), + ImmutableMap.of()); + Map rlsConfig = ImmutableMap.of("lookupService", "rls.bigtable.google.com"); + Route route3 = Route.forAction( + RouteMatch.withPathExactOnly("HelloService/greetings"), + RouteAction.forClusterSpecifierPlugin( + NamedPluginConfig.create("plugin-foo", RlsPluginConfig.create(rlsConfig)), + Collections.emptyList(), + TimeUnit.SECONDS.toNanos(20L), + null), + ImmutableMap.of()); + + resolver.start(mockListener); + FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); + xdsClient.deliverLdsUpdateForRdsName(RDS_RESOURCE_NAME); + VirtualHost virtualHost = + VirtualHost.create("virtualhost", Collections.singletonList(AUTHORITY), + ImmutableList.of(route1, route2, route3), + ImmutableMap.of()); + xdsClient.deliverRdsUpdate(RDS_RESOURCE_NAME, Collections.singletonList(virtualHost)); + + verify(mockListener).onResult(resolutionResultCaptor.capture()); + String expectedServiceConfigJson = + "{\n" + + " \"loadBalancingConfig\": [{\n" + + " \"cluster_manager_experimental\": {\n" + + " \"childPolicy\": {\n" + + " \"cluster:cluster-foo\": {\n" + + " \"lbPolicy\": [{\n" + + " \"cds_experimental\": {\n" + + " \"cluster\": \"cluster-foo\"\n" + + " }\n" + + " }]\n" + + " },\n" + + " \"cluster:cluster-bar\": {\n" + + " \"lbPolicy\": [{\n" + + " \"cds_experimental\": {\n" + + " \"cluster\": \"cluster-bar\"\n" + + " }\n" + + " }]\n" + + " },\n" + + " \"cluster:cluster-baz\": {\n" + + " \"lbPolicy\": [{\n" + + " \"cds_experimental\": {\n" + + " \"cluster\": \"cluster-baz\"\n" + + " }\n" + + " }]\n" + + " },\n" + + " \"cluster_specifier_plugin:plugin-foo\": {\n" + + " \"lbPolicy\": [{\n" + + " \"rls_experimental\": {\n" + + " \"routeLookupConfig\": {\n" + + " \"lookupService\": \"rls.bigtable.google.com\"\n" + + " },\n" + + " \"childPolicy\": [\n" + + " {\"cds_experimental\": {}}\n" + + " ],\n" + + " \"childPolicyConfigTargetFieldName\": \"cluster\"\n" + + " }\n" + + " }]\n" + + " }\n" + + " }\n" + + " }\n" + + " }]\n" + + "}"; + assertThat(resolutionResultCaptor.getValue().getServiceConfig().getConfig()) + .isEqualTo(JsonParser.parse(expectedServiceConfigJson)); } @SuppressWarnings("unchecked") From 41f2ad2540ed7d6aef3fa5b45137381a1b77a37a Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Wed, 19 Jan 2022 14:19:01 -0800 Subject: [PATCH 20/68] RELEASING.md: Use git merge-base instead of cherry for backport summary git cherry is too conservative in determining backports. Showing all commits between the branch point and the release is more reliable. --- RELEASING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASING.md b/RELEASING.md index f2e37f312b4..82406c5deb6 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -85,7 +85,7 @@ would be used to create all `v1.7` tags (e.g. `v1.7.0`, `v1.7.1`). $ echo "## gRPC Java $MAJOR.$MINOR.0 Release Notes" && echo && \ git shortlog "$(git merge-base upstream/v$MAJOR.$((MINOR-1)).x upstream/v$MAJOR.$MINOR.x)"..upstream/v$MAJOR.$MINOR.x | cat && \ echo && echo && echo "Backported commits in previous release:" && \ - git cherry -v v$MAJOR.$((MINOR-1)).0 upstream/v$MAJOR.$MINOR.x | grep ^- + git log --oneline "$(git merge-base v$MAJOR.$((MINOR-1)).0 upstream/v$MAJOR.$MINOR.x)"..v$MAJOR.$((MINOR-1)).0^ ``` Tagging the Release From 6b0009d8509e0f2d2c538338c849c585443d1dac Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Fri, 21 Jan 2022 18:44:45 -0800 Subject: [PATCH 21/68] xds/federation: allow ConfigSource to have its self field set Adopting the change in the [spec](https://github.com/grpc/proposal/blob/367ba33a0acfc411e2d2590887053e6d1e235ab1/A47-xds-federation.md#xds-api-changes): >Currently, for the ConfigSource fields in the LDS resource that points to the RDS resource and in the CDS resource that points to the EDS resource, gRPC requires the ConfigSource to have its ads field set. As part of supporting federation, gRPC will now also allow the ConfigSource to have its self field set. Both fields will have the same meaning. --- .../java/io/grpc/xds/ClientXdsClient.java | 12 ++- .../io/grpc/xds/ClientXdsClientDataTest.java | 92 +++++++++++++++++++ 2 files changed, 99 insertions(+), 5 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java index 1e090e164f4..44f5c3db20f 100644 --- a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java +++ b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java @@ -886,9 +886,9 @@ static io.grpc.xds.HttpConnectionManager parseHttpConnectionManager( throw new ResourceInvalidException( "HttpConnectionManager contains invalid RDS: missing config_source"); } - if (!rds.getConfigSource().hasAds()) { + if (!rds.getConfigSource().hasAds() && !rds.getConfigSource().hasSelf()) { throw new ResourceInvalidException( - "HttpConnectionManager contains invalid RDS: must specify ADS"); + "HttpConnectionManager contains invalid RDS: must specify ADS or self ConfigSource"); } // Collect the RDS resource referenced by this HttpConnectionManager. rdsResources.add(rds.getRouteConfigName()); @@ -1715,9 +1715,11 @@ private static StructOrError parseNonAggregateCluster( String edsServiceName = null; io.envoyproxy.envoy.config.cluster.v3.Cluster.EdsClusterConfig edsClusterConfig = cluster.getEdsClusterConfig(); - if (!edsClusterConfig.getEdsConfig().hasAds()) { - return StructOrError.fromError("Cluster " + clusterName - + ": field eds_cluster_config must be set to indicate to use EDS over ADS."); + 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()) { diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java index 33dbc622ac8..4916cc726e0 100644 --- a/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java +++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java @@ -48,6 +48,7 @@ import io.envoyproxy.envoy.config.core.v3.HttpProtocolOptions; import io.envoyproxy.envoy.config.core.v3.Locality; import io.envoyproxy.envoy.config.core.v3.RuntimeFractionalPercent; +import io.envoyproxy.envoy.config.core.v3.SelfConfigSource; import io.envoyproxy.envoy.config.core.v3.SocketAddress; import io.envoyproxy.envoy.config.core.v3.TrafficDirection; import io.envoyproxy.envoy.config.core.v3.TransportSocket; @@ -133,6 +134,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; @@ -1557,6 +1559,48 @@ public void parseHttpConnectionManager_pluginNameNotFound() throws Exception { true /* does not matter */); } + @Test + public void parseHttpConnectionManager_validateRdsConfigSource() throws Exception { + ClientXdsClient.enableRouteLookup = true; + Set rdsResources = new HashSet<>(); + + HttpConnectionManager hcm1 = + HttpConnectionManager.newBuilder() + .setRds(Rds.newBuilder() + .setRouteConfigName("rds-config-foo") + .setConfigSource( + ConfigSource.newBuilder().setAds(AggregatedConfigSource.getDefaultInstance()))) + .build(); + ClientXdsClient.parseHttpConnectionManager( + hcm1, rdsResources, filterRegistry, false /* parseHttpFilter */, + true /* does not matter */); + + HttpConnectionManager hcm2 = + HttpConnectionManager.newBuilder() + .setRds(Rds.newBuilder() + .setRouteConfigName("rds-config-foo") + .setConfigSource( + ConfigSource.newBuilder().setSelf(SelfConfigSource.getDefaultInstance()))) + .build(); + ClientXdsClient.parseHttpConnectionManager( + hcm2, rdsResources, filterRegistry, false /* parseHttpFilter */, + true /* does not matter */); + + HttpConnectionManager hcm3 = + HttpConnectionManager.newBuilder() + .setRds(Rds.newBuilder() + .setRouteConfigName("rds-config-foo") + .setConfigSource( + ConfigSource.newBuilder().setPath("foo-path"))) + .build(); + thrown.expect(ResourceInvalidException.class); + thrown.expectMessage( + "HttpConnectionManager contains invalid RDS: must specify ADS or self ConfigSource"); + ClientXdsClient.parseHttpConnectionManager( + hcm3, rdsResources, filterRegistry, false /* parseHttpFilter */, + true /* does not matter */); + } + @Test public void parseClusterSpecifierPlugin_typedStructInTypedExtension() throws Exception { class TestPluginConfig implements PluginConfig { @@ -1793,6 +1837,54 @@ public void parseCluster_leastRequestLbPolicy_invalidChoiceCountConfig_tooSmallC ClientXdsClient.processCluster(cluster, new HashSet(), null, LRS_SERVER_INFO); } + @Test + public void parseCluster_validateEdsSourceConfig() throws ResourceInvalidException { + Set retainedEdsResources = new HashSet<>(); + Cluster cluster1 = Cluster.newBuilder() + .setName("cluster-foo.googleapis.com") + .setType(DiscoveryType.EDS) + .setEdsClusterConfig( + EdsClusterConfig.newBuilder() + .setEdsConfig( + ConfigSource.newBuilder() + .setAds(AggregatedConfigSource.getDefaultInstance())) + .setServiceName("service-foo.googleapis.com")) + .setLbPolicy(LbPolicy.ROUND_ROBIN) + .build(); + ClientXdsClient.processCluster(cluster1, retainedEdsResources, null, LRS_SERVER_INFO); + + Cluster cluster2 = Cluster.newBuilder() + .setName("cluster-foo.googleapis.com") + .setType(DiscoveryType.EDS) + .setEdsClusterConfig( + EdsClusterConfig.newBuilder() + .setEdsConfig( + ConfigSource.newBuilder() + .setSelf(SelfConfigSource.getDefaultInstance())) + .setServiceName("service-foo.googleapis.com")) + .setLbPolicy(LbPolicy.ROUND_ROBIN) + .build(); + ClientXdsClient.processCluster(cluster2, retainedEdsResources, null, LRS_SERVER_INFO); + + Cluster cluster3 = Cluster.newBuilder() + .setName("cluster-foo.googleapis.com") + .setType(DiscoveryType.EDS) + .setEdsClusterConfig( + EdsClusterConfig.newBuilder() + .setEdsConfig( + ConfigSource.newBuilder() + .setPath("foo-path")) + .setServiceName("service-foo.googleapis.com")) + .setLbPolicy(LbPolicy.ROUND_ROBIN) + .build(); + + thrown.expect(ResourceInvalidException.class); + 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); + } + @Test public void parseServerSideListener_invalidTrafficDirection() throws ResourceInvalidException { Listener listener = From 1231ce686e694856200a9b86c2d10799b6d864f4 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Mon, 24 Jan 2022 10:55:19 -0800 Subject: [PATCH 22/68] xds/federation: fix percent encode Fix percent encoding to comply with [RFC-3986 section 3.3](https://datatracker.ietf.org/doc/html/rfc3986#section-3.3) as specified in [gRFC A47](https://github.com/grpc/proposal/blob/367ba33a0acfc411e2d2590887053e6d1e235ab1/A47-xds-federation.md). --- .../java/io/grpc/xds/XdsNameResolver.java | 13 +++++++++- .../java/io/grpc/xds/XdsNameResolverTest.java | 25 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java index a52b1c3e963..578f879cd08 100644 --- a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java +++ b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java @@ -22,6 +22,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; +import com.google.common.base.Splitter; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -191,7 +192,7 @@ public void start(Listener2 listener) { } String replacement = serviceAuthority; if (listenerNameTemplate.startsWith(XDSTP_SCHEME)) { - replacement = UrlEscapers.urlFragmentEscaper().escape(replacement); + replacement = percentEncodePath(replacement); } String ldsResourceName = expandPercentS(listenerNameTemplate, replacement); callCounterProvider = SharedCallCounterMap.getInstance(); @@ -199,6 +200,16 @@ public void start(Listener2 listener) { resolveState.start(); } + @VisibleForTesting + static String percentEncodePath(String input) { + Iterable pathSegs = Splitter.on('/').split(input); + List encodedSegs = new ArrayList<>(); + for (String pathSeg : pathSegs) { + encodedSegs.add(UrlEscapers.urlPathSegmentEscaper().escape(pathSeg)); + } + return Joiner.on('/').join(encodedSegs); + } + private static String expandPercentS(String template, String replacement) { return template.replace("%s", replacement); } diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java index 68349806fdf..0714d51baaf 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java @@ -1955,6 +1955,31 @@ public void routeMatching_withHeaders() { .isFalse(); } + /** + * Tests compliance with RFC 3986 section 3.3 + * https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 + */ + @Test + public void percentEncodePath() { + String unreserved = "aAzZ09-._~"; + assertThat(XdsNameResolver.percentEncodePath(unreserved)).isEqualTo(unreserved); + + String subDelims = "!$&'(*+,;/="; + assertThat(XdsNameResolver.percentEncodePath(subDelims)).isEqualTo(subDelims); + + String colonAndAt = ":@"; + assertThat(XdsNameResolver.percentEncodePath(colonAndAt)).isEqualTo(colonAndAt); + + String needBeEncoded = "?#[]"; + assertThat(XdsNameResolver.percentEncodePath(needBeEncoded)).isEqualTo("%3F%23%5B%5D"); + + String ipv4 = "0.0.0.0:8080"; + assertThat(XdsNameResolver.percentEncodePath(ipv4)).isEqualTo(ipv4); + + String ipv6 = "[::1]:8080"; + assertThat(XdsNameResolver.percentEncodePath(ipv6)).isEqualTo("%5B::1%5D:8080"); + } + private final class FakeXdsClientPoolFactory implements XdsClientPoolFactory { Map bootstrap; From b29c3ec021c49506f8cbb342f36a25e22c0a78f7 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Mon, 24 Jan 2022 16:55:43 -0800 Subject: [PATCH 23/68] xds/federation: validate and canonify resource name On reading a new `xdstp`: resource name, do a validation on the URI and canonify the query params. --- .../java/io/grpc/xds/ClientXdsClient.java | 33 ++++-- xds/src/main/java/io/grpc/xds/XdsClient.java | 57 +++++++++ .../java/io/grpc/xds/XdsNameResolver.java | 8 ++ .../io/grpc/xds/ClientXdsClientDataTest.java | 46 ++++++++ .../io/grpc/xds/ClientXdsClientTestBase.java | 111 +++++++++++++++++- .../java/io/grpc/xds/XdsNameResolverTest.java | 6 +- 6 files changed, 246 insertions(+), 15 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java index 44f5c3db20f..202bfedcca2 100644 --- a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java +++ b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java @@ -298,7 +298,12 @@ public void handleLdsResponse( errors.add("LDS response Resource index " + i + " - can't decode Listener: " + e); continue; } - String listenerName = listener.getName(); + 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. @@ -1425,7 +1430,13 @@ public void handleRdsResponse( errors.add("RDS response Resource index " + i + " - can't decode RouteConfiguration: " + e); continue; } - String routeConfigName = routeConfig.getName(); + 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. @@ -1547,7 +1558,12 @@ public void handleCdsResponse( errors.add("CDS response Resource index " + i + " - can't decode Cluster: " + e); continue; } - String clusterName = cluster.getName(); + 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 @@ -1793,7 +1809,12 @@ public void handleEdsResponse( "EDS response Resource index " + i + " - can't decode ClusterLoadAssignment: " + e); continue; } - String clusterName = assignment.getClusterName(); + 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 @@ -2382,10 +2403,6 @@ private final class ResourceSubscriber { ResourceSubscriber(ResourceType type, String resource) { syncContext.throwIfNotInThisSynchronizationContext(); this.type = type; - // TODO(zdapeng): Validate authority in resource URI for new-style resource name - // when parsing XDS response. - // TODO(zdapeng): Canonicalize the resource name by sorting the context params in normal - // lexicographic order. this.resource = resource; this.serverInfo = getServerInfo(resource); // Initialize metadata in UNKNOWN state to cover the case when resource subscriber, diff --git a/xds/src/main/java/io/grpc/xds/XdsClient.java b/xds/src/main/java/io/grpc/xds/XdsClient.java index 789e576da06..ba9c58153a6 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClient.java +++ b/xds/src/main/java/io/grpc/xds/XdsClient.java @@ -17,9 +17,12 @@ package io.grpc.xds; 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.util.concurrent.ListenableFuture; import com.google.protobuf.Any; @@ -32,6 +35,8 @@ import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.LoadStatsManager2.ClusterDropStats; import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -49,6 +54,58 @@ */ abstract class XdsClient { + static boolean isResourceNameValid(String resourceName, String typeUrl) { + checkNotNull(resourceName, "resourceName"); + if (!resourceName.startsWith(XDSTP_SCHEME)) { + return true; + } + URI uri; + try { + uri = new URI(resourceName); + } catch (URISyntaxException e) { + return false; + } + String path = uri.getPath(); + // path must be in the form of /{resource type}/{id/*} + Splitter slashSplitter = Splitter.on('/').omitEmptyStrings(); + if (path == null) { + return false; + } + List pathSegs = slashSplitter.splitToList(path); + if (pathSegs.size() < 2) { + return false; + } + String type = pathSegs.get(0); + if (!type.equals(slashSplitter.splitToList(typeUrl).get(1))) { + return false; + } + return true; + } + + static String canonifyResourceName(String resourceName) { + checkNotNull(resourceName, "resourceName"); + if (!resourceName.startsWith(XDSTP_SCHEME)) { + return resourceName; + } + URI uri = URI.create(resourceName); + String rawQuery = uri.getRawQuery(); + Splitter ampSplitter = Splitter.on('&').omitEmptyStrings(); + if (rawQuery == null) { + return resourceName; + } + List queries = ampSplitter.splitToList(rawQuery); + if (queries.size() < 2) { + return resourceName; + } + List canonicalContextParams = new ArrayList<>(queries.size()); + for (String query : queries) { + canonicalContextParams.add(query); + } + Collections.sort(canonicalContextParams); + String canonifiedQuery = Joiner.on('&').join(canonicalContextParams); + return resourceName.replace(rawQuery, canonifiedQuery); + } + @AutoValue abstract static class LdsUpdate implements ResourceUpdate { // Http level api listener configuration. diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java index 578f879cd08..56b3dbe0596 100644 --- a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java +++ b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java @@ -49,6 +49,7 @@ import io.grpc.SynchronizationContext; import io.grpc.internal.GrpcUtil; import io.grpc.internal.ObjectPool; +import io.grpc.xds.AbstractXdsClient.ResourceType; import io.grpc.xds.Bootstrapper.AuthorityInfo; import io.grpc.xds.Bootstrapper.BootstrapInfo; import io.grpc.xds.ClusterSpecifierPlugin.PluginConfig; @@ -195,6 +196,13 @@ public void start(Listener2 listener) { replacement = percentEncodePath(replacement); } String ldsResourceName = expandPercentS(listenerNameTemplate, replacement); + if (!XdsClient.isResourceNameValid(ldsResourceName, ResourceType.LDS.typeUrl()) + && !XdsClient.isResourceNameValid(ldsResourceName, ResourceType.LDS.typeUrlV2())) { + listener.onError(Status.INVALID_ARGUMENT.withDescription( + "invalid listener resource URI for service authority: " + serviceAuthority)); + return; + } + ldsResourceName = XdsClient.canonifyResourceName(ldsResourceName); callCounterProvider = SharedCallCounterMap.getInstance(); resolveState = new ResolveState(ldsResourceName); resolveState.start(); diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java index 4916cc726e0..c41773dfd97 100644 --- a/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java +++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java @@ -111,6 +111,7 @@ import io.grpc.lookup.v1.NameMatcher; import io.grpc.lookup.v1.RouteLookupClusterSpecifier; import io.grpc.lookup.v1.RouteLookupConfig; +import io.grpc.xds.AbstractXdsClient.ResourceType; import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.ClientXdsClient.ResourceInvalidException; import io.grpc.xds.ClientXdsClient.StructOrError; @@ -2573,6 +2574,51 @@ public void validateUpstreamTlsContext_noCommonTlsContext() throws ResourceInval ClientXdsClient.validateUpstreamTlsContext(upstreamTlsContext, null); } + @Test + public void validateResourceName() { + String traditionalResource = "cluster1.google.com"; + assertThat(XdsClient.isResourceNameValid(traditionalResource, ResourceType.CDS.typeUrl())) + .isTrue(); + assertThat(XdsClient.isResourceNameValid(traditionalResource, ResourceType.RDS.typeUrlV2())) + .isTrue(); + + String invalidPath = "xdstp:/abc/efg"; + assertThat(XdsClient.isResourceNameValid(invalidPath, ResourceType.CDS.typeUrl())).isFalse(); + + String invalidPath2 = "xdstp:///envoy.config.route.v3.RouteConfiguration"; + assertThat(XdsClient.isResourceNameValid(invalidPath2, ResourceType.RDS.typeUrl())).isFalse(); + + String typeMatch = "xdstp:///envoy.config.route.v3.RouteConfiguration/foo/route1"; + assertThat(XdsClient.isResourceNameValid(typeMatch, ResourceType.LDS.typeUrl())).isFalse(); + assertThat(XdsClient.isResourceNameValid(typeMatch, ResourceType.RDS.typeUrl())).isTrue(); + assertThat(XdsClient.isResourceNameValid(typeMatch, ResourceType.RDS.typeUrlV2())).isFalse(); + } + + @Test + public void canonifyResourceName() { + String traditionalResource = "cluster1.google.com"; + assertThat(XdsClient.canonifyResourceName(traditionalResource)) + .isEqualTo(traditionalResource); + assertThat(XdsClient.canonifyResourceName(traditionalResource)) + .isEqualTo(traditionalResource); + assertThat(XdsClient.canonifyResourceName(traditionalResource)) + .isEqualTo(traditionalResource); + assertThat(XdsClient.canonifyResourceName(traditionalResource)) + .isEqualTo(traditionalResource); + + String withNoQueries = "xdstp:///envoy.config.route.v3.RouteConfiguration/foo/route1"; + assertThat(XdsClient.canonifyResourceName(withNoQueries)).isEqualTo(withNoQueries); + + String withOneQueries = "xdstp:///envoy.config.route.v3.RouteConfiguration/foo/route1?name=foo"; + assertThat(XdsClient.canonifyResourceName(withOneQueries)).isEqualTo(withOneQueries); + + String withTwoQueries = "xdstp:///envoy.config.route.v3.RouteConfiguration/id/route1?b=1&a=1"; + String expectedCanonifiedName = + "xdstp:///envoy.config.route.v3.RouteConfiguration/id/route1?a=1&b=1"; + assertThat(XdsClient.canonifyResourceName(withTwoQueries)) + .isEqualTo(expectedCanonifiedName); + } + private static Filter buildHttpConnectionManagerFilter(HttpFilter... httpFilters) { return Filter.newBuilder() .setName("envoy.http_connection_manager") diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java index 9e81f45cfc5..c0ad0b47c55 100644 --- a/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java +++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java @@ -760,8 +760,9 @@ public void ldsResourceUpdated() { @Test public void ldsResourceUpdated_withXdstpResourceName() { - String ldsResourceName = - "xdstp://authority.xds.com/envoy.config.listener.v3.Listener/listener1"; + 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); assertThat(channelForCustomAuthority).isNotNull(); verifyResourceMetadataRequested(LDS, ldsResourceName); @@ -779,8 +780,9 @@ public void ldsResourceUpdated_withXdstpResourceName() { @Test public void ldsResourceUpdated_withXdstpResourceName_withEmptyAuthority() { - String ldsResourceName = - "xdstp:///envoy.config.listener.v3.Listener/listener1"; + String ldsResourceName = useProtocolV3() + ? "xdstp:///envoy.config.listener.v3.Listener/listener1" + : "xdstp:///envoy.api.v2.Listener/listener1"; DiscoveryRpcCall call = startResourceWatcher(LDS, ldsResourceName, ldsResourceWatcher); assertThat(channelForEmptyAuthority).isNotNull(); verifyResourceMetadataRequested(LDS, ldsResourceName); @@ -796,6 +798,107 @@ public void ldsResourceUpdated_withXdstpResourceName_withEmptyAuthority() { LDS, ldsResourceName, testListenerVhosts, VERSION_1, TIME_INCREMENT); } + @Test + 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); + assertThat(channelForCustomAuthority).isNotNull(); + + String ldsResourceNameWithUnorderedContextParams = useProtocolV3() + ? "xdstp://authority.xds.com/envoy.config.listener.v3.Listener/listener1/a?foo=1&bar=2" + : "xdstp://authority.xds.com/envoy.api.v2.Listener/listener1/a?foo=1&bar=2"; + Any testListenerVhosts = Any.pack(mf.buildListenerWithApiListener( + ldsResourceNameWithUnorderedContextParams, + mf.buildRouteConfiguration("do not care", mf.buildOpaqueVirtualHosts(VHOST_SIZE)))); + call.sendResponse(LDS, testListenerVhosts, VERSION_1, "0000"); + call.verifyRequest( + LDS, ldsResourceName, VERSION_1, "0000", NODE); + } + + @Test + 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); + assertThat(channelForCustomAuthority).isNotNull(); + + String ldsResourceNameWithWrongType = + "xdstp://authority.xds.com/envoy.config.route.v3.RouteConfiguration/listener1"; + Any testListenerVhosts = Any.pack(mf.buildListenerWithApiListener( + ldsResourceNameWithWrongType, + mf.buildRouteConfiguration("do not care", mf.buildOpaqueVirtualHosts(VHOST_SIZE)))); + call.sendResponse(LDS, testListenerVhosts, VERSION_1, "0000"); + call.verifyRequestNack( + LDS, ldsResourceName, "", "0000", NODE, + ImmutableList.of( + "Unsupported resource name: " + ldsResourceNameWithWrongType + " for type: LDS")); + } + + @Test + 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); + assertThat(channelForCustomAuthority).isNotNull(); + + String rdsResourceNameWithWrongType = + "xdstp://authority.xds.com/envoy.config.listener.v3.Listener/route1"; + Any testRouteConfig = Any.pack(mf.buildRouteConfiguration( + rdsResourceNameWithWrongType, mf.buildOpaqueVirtualHosts(VHOST_SIZE))); + call.sendResponse(RDS, testRouteConfig, VERSION_1, "0000"); + call.verifyRequestNack( + RDS, rdsResourceName, "", "0000", NODE, + ImmutableList.of( + "Unsupported resource name: " + rdsResourceNameWithWrongType + " for type: RDS")); + } + + @Test + 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); + 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)); + call.sendResponse(CDS, testClusterConfig, VERSION_1, "0000"); + call.verifyRequestNack( + CDS, cdsResourceName, "", "0000", NODE, + ImmutableList.of( + "Unsupported resource name: " + cdsResourceNameWithWrongType + " for type: CDS")); + } + + @Test + 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); + assertThat(channelForCustomAuthority).isNotNull(); + + String edsResourceNameWithWrongType = + "xdstp://authority.xds.com/envoy.config.listener.v3.Listener/cluster1"; + Any testEdsConfig = Any.pack(mf.buildClusterLoadAssignment( + edsResourceNameWithWrongType, + ImmutableList.of(mf.buildLocalityLbEndpoints( + "region2", "zone2", "subzone2", + mf.buildLbEndpoint("172.44.2.2", 8000, "unknown", 3), 2, 0)), + ImmutableList.of())); + call.sendResponse(EDS, testEdsConfig, VERSION_1, "0000"); + call.verifyRequestNack( + EDS, edsResourceName, "", "0000", NODE, + ImmutableList.of( + "Unsupported resource name: " + edsResourceNameWithWrongType + " for type: EDS")); + } + @Test public void ldsResourceUpdate_withFaultInjection() { Assume.assumeTrue(useProtocolV3()); diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java index 0714d51baaf..d42cd07b1b6 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java @@ -270,12 +270,12 @@ public void resolving_targetAuthorityInAuthoritiesMap() { .node(Node.newBuilder().build()) .authorities( ImmutableMap.of(targetAuthority, AuthorityInfo.create( - "xdstp://" + targetAuthority + "/envoy.config.listener.v3.Listener/%s", + "xdstp://" + targetAuthority + "/envoy.config.listener.v3.Listener/%s?foo=1&bar=2", ImmutableList.of(ServerInfo.create( "td.googleapis.com", InsecureChannelCredentials.create(), true))))) .build(); - expectedLdsResourceName = - "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/%5B::FFFF:129.144.52.38%5D:80"; + expectedLdsResourceName = "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/" + + "%5B::FFFF:129.144.52.38%5D:80?bar=2&foo=1"; // query param canonified resolver = new XdsNameResolver( "xds.authority.com", serviceAuthority, serviceConfigParser, syncContext, scheduler, xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); From 128324540fc2f19fb6472aa3f87d9f71c87210b9 Mon Sep 17 00:00:00 2001 From: John Cormie Date: Mon, 24 Jan 2022 19:54:21 -0800 Subject: [PATCH 24/68] binder: Fix a ServiceConnection leak (#8861) Closes #8726 --- .../io/grpc/binder/internal/ServiceBinding.java | 14 ++++++++++++++ .../grpc/binder/internal/ServiceBindingTest.java | 9 ++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/binder/src/main/java/io/grpc/binder/internal/ServiceBinding.java b/binder/src/main/java/io/grpc/binder/internal/ServiceBinding.java index 11480b36a33..650ead9bcdb 100644 --- a/binder/src/main/java/io/grpc/binder/internal/ServiceBinding.java +++ b/binder/src/main/java/io/grpc/binder/internal/ServiceBinding.java @@ -119,6 +119,7 @@ public synchronized void bind() { state = State.BINDING; Status bindResult = bindInternal(sourceContext, bindIntent, this, bindFlags); if (!bindResult.isOk()) { + handleBindServiceFailure(sourceContext, this); state = State.UNBOUND; mainThreadExecutor.execute(() -> notifyUnbound(bindResult)); } @@ -142,6 +143,19 @@ private static Status bindInternal( } } + // Over the years, the API contract for Context#bindService() has been inconsistent on the subject + // of error handling. But inspecting recent AOSP implementations shows that, internally, + // bindService() retains a reference to the ServiceConnection when it throws certain Exceptions + // and even when it returns false. To avoid leaks, we *always* call unbindService() in case of + // error and simply ignore any "Service not registered" IAE and other RuntimeExceptions. + private static void handleBindServiceFailure(Context context, ServiceConnection conn) { + try { + context.unbindService(conn); + } catch (RuntimeException e) { + logger.log(Level.FINE, "Could not clean up after bindService() failure.", e); + } + } + @Override @AnyThread public void unbind() { diff --git a/binder/src/test/java/io/grpc/binder/internal/ServiceBindingTest.java b/binder/src/test/java/io/grpc/binder/internal/ServiceBindingTest.java index 44a3898e532..967e91b820d 100644 --- a/binder/src/test/java/io/grpc/binder/internal/ServiceBindingTest.java +++ b/binder/src/test/java/io/grpc/binder/internal/ServiceBindingTest.java @@ -68,6 +68,9 @@ public void setUp() { shadowApplication = shadowOf(appContext); shadowApplication.setComponentNameAndServiceForBindService(serviceComponent, mockBinder); + // Don't call onServiceDisconnected() upon unbindService(), just like the real Android doesn't. + shadowApplication.setUnbindServiceCallsOnServiceDisconnected(false); + binding = newBuilder().build(); shadowOf(getMainLooper()).idle(); } @@ -137,6 +140,7 @@ public void testBindUnbind() throws Exception { assertThat(observer.gotUnboundEvent).isTrue(); assertThat(observer.unboundReason.getCode()).isEqualTo(Code.CANCELLED); assertThat(binding.isSourceContextCleared()).isTrue(); + assertThat(shadowApplication.getBoundServiceConnections()).isEmpty(); } @Test @@ -174,6 +178,7 @@ public void testBindFailure() throws Exception { assertThat(observer.gotUnboundEvent).isTrue(); assertThat(observer.unboundReason.getCode()).isEqualTo(Code.UNIMPLEMENTED); assertThat(binding.isSourceContextCleared()).isTrue(); + assertThat(shadowApplication.getBoundServiceConnections()).isEmpty(); } @Test @@ -187,6 +192,7 @@ public void testBindSecurityException() throws Exception { assertThat(observer.unboundReason.getCode()).isEqualTo(Code.PERMISSION_DENIED); assertThat(observer.unboundReason.getCause()).isEqualTo(securityException); assertThat(binding.isSourceContextCleared()).isTrue(); + assertThat(shadowApplication.getBoundServiceConnections()).isEmpty(); } @Test @@ -257,7 +263,8 @@ private void assertNoLockHeld() { } catch (IllegalMonitorStateException ime) { // Expected. } catch (InterruptedException inte) { - throw new AssertionError("Interrupted exception when we shouldn't have been able to wait.", inte); + throw new AssertionError( + "Interrupted exception when we shouldn't have been able to wait.", inte); } } From c59cc11e7a4b99c9e0b5bc32d2954508317b91e7 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Tue, 25 Jan 2022 12:01:59 -0800 Subject: [PATCH 25/68] Update RELEASING.md to clarify a step in tagging process --- RELEASING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASING.md b/RELEASING.md index 82406c5deb6..b8682961234 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -133,7 +133,7 @@ Tagging the Release $ git commit -a -m "Bump version to $MAJOR.$MINOR.$((PATCH+1))-SNAPSHOT" ``` 6. Go through PR review and push the release tag and updated release branch to - GitHub: + GitHub (DO NOT click the merge button on the GitHub page): ```bash $ git checkout v$MAJOR.$MINOR.x From 7c49e5657f53e11c0a170078a6650382be56b06a Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Tue, 25 Jan 2022 12:02:28 -0800 Subject: [PATCH 26/68] rls: fix RLS lb name The lb name of RLS lb should be "rls_experimental" instead of "rls-experimental", using underscore like "round_robin". --- rls/src/main/java/io/grpc/rls/RlsLoadBalancerProvider.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rls/src/main/java/io/grpc/rls/RlsLoadBalancerProvider.java b/rls/src/main/java/io/grpc/rls/RlsLoadBalancerProvider.java index 3755fe3f77c..d5ee8073db0 100644 --- a/rls/src/main/java/io/grpc/rls/RlsLoadBalancerProvider.java +++ b/rls/src/main/java/io/grpc/rls/RlsLoadBalancerProvider.java @@ -30,9 +30,9 @@ import java.util.Map; /** - * The provider for the "rls-experimental" balancing policy. This class should not be directly + * The provider for the "rls_experimental" balancing policy. This class should not be directly * referenced in code. The policy should be accessed through {@link - * io.grpc.LoadBalancerRegistry#getProvider} with the name "rls-experimental". + * io.grpc.LoadBalancerRegistry#getProvider} with the name "rls_experimental". */ @Internal public final class RlsLoadBalancerProvider extends LoadBalancerProvider { @@ -49,7 +49,7 @@ public int getPriority() { @Override public String getPolicyName() { - return "rls-experimental"; + return "rls_experimental"; } @Override From 5be09ec215f1d9cfc9628ad02e80caa0521f0253 Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Thu, 27 Jan 2022 09:58:19 -0800 Subject: [PATCH 27/68] observability: create the grpc-observability artifact (#8870) --- observability/build.gradle | 13 ++++++++ .../io/grpc/observability/Observability.java | 33 +++++++++++++++++++ settings.gradle | 2 ++ 3 files changed, 48 insertions(+) create mode 100644 observability/build.gradle create mode 100644 observability/src/main/java/io/grpc/observability/Observability.java diff --git a/observability/build.gradle b/observability/build.gradle new file mode 100644 index 00000000000..ecf0f3ffa86 --- /dev/null +++ b/observability/build.gradle @@ -0,0 +1,13 @@ +plugins { + id "java-library" + id "maven-publish" + + id "ru.vyarus.animalsniffer" +} + +description = "gRPC: Observability" +dependencies { + api project(':grpc-api') + + signature "org.codehaus.mojo.signature:java17:1.0@signature" +} diff --git a/observability/src/main/java/io/grpc/observability/Observability.java b/observability/src/main/java/io/grpc/observability/Observability.java new file mode 100644 index 00000000000..c4102bda98e --- /dev/null +++ b/observability/src/main/java/io/grpc/observability/Observability.java @@ -0,0 +1,33 @@ +/* + * 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.observability; + +import io.grpc.ExperimentalApi; + +/** + * The main class for gRPC Observability features. + */ +@ExperimentalApi("https://github.com/grpc/grpc-java/issues/8869") +public final class Observability { + + public static void grpcInit() { + // TODO(sanjaypujare): initialize channel and server providers and customTags map + } + + private Observability() { + } +} diff --git a/settings.gradle b/settings.gradle index d60cddcbbac..3da79fce13c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -49,6 +49,7 @@ include ":grpc-services" include ":grpc-xds" include ":grpc-bom" include ":grpc-rls" +include ":grpc-observability" project(':grpc-api').projectDir = "$rootDir/api" as File project(':grpc-core').projectDir = "$rootDir/core" as File @@ -73,6 +74,7 @@ project(':grpc-services').projectDir = "$rootDir/services" as File project(':grpc-xds').projectDir = "$rootDir/xds" as File project(':grpc-bom').projectDir = "$rootDir/bom" as File project(':grpc-rls').projectDir = "$rootDir/rls" as File +project(':grpc-observability').projectDir = "$rootDir/observability" as File if (settings.hasProperty('skipCodegen') && skipCodegen.toBoolean()) { println '*** Skipping the build of codegen and compilation of proto files because skipCodegen=true' From 46563b75bf635ad2b5f7eb7ff124cc810e32fc93 Mon Sep 17 00:00:00 2001 From: Zhouyihai Ding Date: Thu, 27 Jan 2022 11:07:14 -0800 Subject: [PATCH 28/68] core: include cause when logging Status in InternalSubchannel (#8846) It would be good to print Cause when the transport is shutdown and has throwable exception messages. The current log doesn't have this information for debugging: `SHUTDOWN with UNAVAILABLE(io exception Channel Pipeline: [HttpProxyHandler$HttpClientCodecWrapper#0, HttpProxyHandler#0, TsiHandshakeHandler#0, WriteBufferingAndExceptionHandler#0, DefaultChannelPipeline$TailContext#0])` --- core/src/main/java/io/grpc/internal/InternalSubchannel.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/main/java/io/grpc/internal/InternalSubchannel.java b/core/src/main/java/io/grpc/internal/InternalSubchannel.java index fa2bf2e46bc..4f7c8d1f22f 100644 --- a/core/src/main/java/io/grpc/internal/InternalSubchannel.java +++ b/core/src/main/java/io/grpc/internal/InternalSubchannel.java @@ -772,6 +772,9 @@ private String printShortStatus(Status status) { if (status.getDescription() != null) { buffer.append("(").append(status.getDescription()).append(")"); } + if (status.getCause() != null) { + buffer.append("[").append(status.getCause()).append("]"); + } return buffer.toString(); } From 5635c6cb449e6561db3c603f7ab1f5b3214f9b38 Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Tue, 25 Jan 2022 00:10:59 +0000 Subject: [PATCH 29/68] Update README etc to reference 1.44.0 --- README.md | 38 ++++++++++++------------ cronet/README.md | 2 +- documentation/android-channel-builder.md | 4 +-- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 610c637892b..f80bcc3f7a8 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ gRPC-Java - An RPC library and framework ======================================== -gRPC-Java works with JDK 7. gRPC-Java clients are supported on Android API +gRPC-Java works with JDK 8. gRPC-Java clients are supported on Android API levels 19 and up (KitKat and later). Deploying gRPC servers on an Android device is not supported. @@ -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.43.1/examples) and the -[Android example](https://github.com/grpc/grpc-java/tree/v1.43.1/examples/android) +The [examples](https://github.com/grpc/grpc-java/tree/v1.44.0/examples) and the +[Android example](https://github.com/grpc/grpc-java/tree/v1.44.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.43.1 + 1.44.0 runtime io.grpc grpc-protobuf - 1.43.1 + 1.44.0 io.grpc grpc-stub - 1.43.1 + 1.44.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.43.1' -implementation 'io.grpc:grpc-protobuf:1.43.1' -implementation 'io.grpc:grpc-stub:1.43.1' +runtimeOnly 'io.grpc:grpc-netty-shaded:1.44.0' +implementation 'io.grpc:grpc-protobuf:1.44.0' +implementation 'io.grpc:grpc-stub:1.44.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.43.1' -implementation 'io.grpc:grpc-protobuf-lite:1.43.1' -implementation 'io.grpc:grpc-stub:1.43.1' +implementation 'io.grpc:grpc-okhttp:1.44.0' +implementation 'io.grpc:grpc-protobuf-lite:1.44.0' +implementation 'io.grpc:grpc-stub:1.44.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.43.1 +https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.44.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.19.1:exe:${os.detected.classifier} + com.google.protobuf:protoc:3.19.2:exe:${os.detected.classifier} grpc-java - io.grpc:protoc-gen-grpc-java:1.43.1:exe:${os.detected.classifier} + io.grpc:protoc-gen-grpc-java:1.44.0:exe:${os.detected.classifier} @@ -140,11 +140,11 @@ plugins { protobuf { protoc { - artifact = "com.google.protobuf:protoc:3.19.1" + artifact = "com.google.protobuf:protoc:3.19.2" } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.43.1' + artifact = 'io.grpc:protoc-gen-grpc-java:1.44.0' } } generateProtoTasks { @@ -173,11 +173,11 @@ plugins { protobuf { protoc { - artifact = "com.google.protobuf:protoc:3.19.1" + artifact = "com.google.protobuf:protoc:3.19.2" } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.43.1' + artifact = 'io.grpc:protoc-gen-grpc-java:1.44.0' } } generateProtoTasks { diff --git a/cronet/README.md b/cronet/README.md index d53da56a8d7..e02cab401b5 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.43.1' +implementation 'io.grpc:grpc-cronet:1.44.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 09c0bef179a..41d3b650151 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.43.1' -implementation 'io.grpc:grpc-okhttp:1.43.1' +implementation 'io.grpc:grpc-android:1.44.0' +implementation 'io.grpc:grpc-okhttp:1.44.0' ``` You also need permission to access the device's network state in your From 881f747b7eaeffe2d9d0a2c461e1cd4ae636e717 Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Mon, 31 Jan 2022 10:37:54 -0800 Subject: [PATCH 30/68] observability: implement Observability.grpcInit() and LoggingChannelProvider (#8872) --- .../java/io/grpc/ManagedChannelProvider.java | 6 +- .../io/grpc/ManagedChannelRegistryTest.java | 4 +- observability/build.gradle | 8 ++ .../observability/LoggingChannelProvider.java | 90 ++++++++++++ .../io/grpc/observability/Observability.java | 31 +++- .../InternalLoggingChannelInterceptor.java | 48 +++++++ .../LoggingChannelProviderTest.java | 135 ++++++++++++++++++ .../grpc/observability/ObservabilityTest.java | 46 ++++++ 8 files changed, 358 insertions(+), 10 deletions(-) create mode 100644 observability/src/main/java/io/grpc/observability/LoggingChannelProvider.java create mode 100644 observability/src/main/java/io/grpc/observability/interceptors/InternalLoggingChannelInterceptor.java create mode 100644 observability/src/test/java/io/grpc/observability/LoggingChannelProviderTest.java create mode 100644 observability/src/test/java/io/grpc/observability/ObservabilityTest.java diff --git a/api/src/main/java/io/grpc/ManagedChannelProvider.java b/api/src/main/java/io/grpc/ManagedChannelProvider.java index f57340d9ba9..e388abfd9d0 100644 --- a/api/src/main/java/io/grpc/ManagedChannelProvider.java +++ b/api/src/main/java/io/grpc/ManagedChannelProvider.java @@ -64,18 +64,18 @@ public static ManagedChannelProvider provider() { /** * Creates a new builder with the given host and port. */ - protected abstract ManagedChannelBuilder builderForAddress(String name, int port); + public abstract ManagedChannelBuilder builderForAddress(String name, int port); /** * Creates a new builder with the given target URI. */ - protected abstract ManagedChannelBuilder builderForTarget(String target); + public abstract ManagedChannelBuilder builderForTarget(String target); /** * Creates a new builder with the given target URI and credentials. Returns an error-string result * if unable to understand the credentials. */ - protected NewChannelBuilderResult newChannelBuilder(String target, ChannelCredentials creds) { + public NewChannelBuilderResult newChannelBuilder(String target, ChannelCredentials creds) { return NewChannelBuilderResult.error("ChannelCredentials are unsupported"); } diff --git a/api/src/test/java/io/grpc/ManagedChannelRegistryTest.java b/api/src/test/java/io/grpc/ManagedChannelRegistryTest.java index 6f25f620576..d083a7b6c10 100644 --- a/api/src/test/java/io/grpc/ManagedChannelRegistryTest.java +++ b/api/src/test/java/io/grpc/ManagedChannelRegistryTest.java @@ -176,12 +176,12 @@ protected int priority() { } @Override - protected ManagedChannelBuilder builderForAddress(String name, int port) { + public ManagedChannelBuilder builderForAddress(String name, int port) { throw new UnsupportedOperationException(); } @Override - protected ManagedChannelBuilder builderForTarget(String target) { + public ManagedChannelBuilder builderForTarget(String target) { throw new UnsupportedOperationException(); } } diff --git a/observability/build.gradle b/observability/build.gradle index ecf0f3ffa86..c78541b6c18 100644 --- a/observability/build.gradle +++ b/observability/build.gradle @@ -9,5 +9,13 @@ description = "gRPC: Observability" dependencies { api project(':grpc-api') + implementation libraries.guava + + testImplementation project(':grpc-testing'), + project(':grpc-netty-shaded') + testImplementation (libraries.guava_testlib) { + exclude group: 'junit', module: 'junit' + } + signature "org.codehaus.mojo.signature:java17:1.0@signature" } diff --git a/observability/src/main/java/io/grpc/observability/LoggingChannelProvider.java b/observability/src/main/java/io/grpc/observability/LoggingChannelProvider.java new file mode 100644 index 00000000000..4444cf9a807 --- /dev/null +++ b/observability/src/main/java/io/grpc/observability/LoggingChannelProvider.java @@ -0,0 +1,90 @@ +/* + * 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.observability; + +import static com.google.common.base.Preconditions.checkNotNull; + +import io.grpc.ChannelCredentials; +import io.grpc.ManagedChannelBuilder; +import io.grpc.ManagedChannelProvider; +import io.grpc.ManagedChannelRegistry; +import io.grpc.observability.interceptors.InternalLoggingChannelInterceptor; + +/** A channel provider that injects logging interceptor. */ +final class LoggingChannelProvider extends ManagedChannelProvider { + private final ManagedChannelProvider prevProvider; + private final InternalLoggingChannelInterceptor.Factory clientInterceptorFactory; + + private static LoggingChannelProvider instance; + + private LoggingChannelProvider(InternalLoggingChannelInterceptor.Factory factory) { + prevProvider = ManagedChannelProvider.provider(); + clientInterceptorFactory = factory; + } + + static synchronized void init(InternalLoggingChannelInterceptor.Factory factory) { + if (instance != null) { + throw new IllegalStateException("LoggingChannelProvider already initialized!"); + } + instance = new LoggingChannelProvider(factory); + ManagedChannelRegistry.getDefaultRegistry().register(instance); + } + + static synchronized void finish() { + if (instance == null) { + throw new IllegalStateException("LoggingChannelProvider not initialized!"); + } + ManagedChannelRegistry.getDefaultRegistry().deregister(instance); + instance = null; + } + + @Override + protected boolean isAvailable() { + return true; + } + + @Override + protected int priority() { + return 6; + } + + private ManagedChannelBuilder addInterceptor(ManagedChannelBuilder builder) { + return builder.intercept(clientInterceptorFactory.create()); + } + + @Override + public ManagedChannelBuilder builderForAddress(String name, int port) { + return addInterceptor(prevProvider.builderForAddress(name, port)); + } + + @Override + public ManagedChannelBuilder builderForTarget(String target) { + return addInterceptor(prevProvider.builderForTarget(target)); + } + + @Override + public NewChannelBuilderResult newChannelBuilder(String target, ChannelCredentials creds) { + NewChannelBuilderResult result = prevProvider.newChannelBuilder(target, creds); + ManagedChannelBuilder builder = result.getChannelBuilder(); + if (builder != null) { + return NewChannelBuilderResult.channelBuilder( + addInterceptor(builder)); + } + checkNotNull(result.getError(), "Expected error to be set!"); + return result; + } +} diff --git a/observability/src/main/java/io/grpc/observability/Observability.java b/observability/src/main/java/io/grpc/observability/Observability.java index c4102bda98e..1f92fd817cb 100644 --- a/observability/src/main/java/io/grpc/observability/Observability.java +++ b/observability/src/main/java/io/grpc/observability/Observability.java @@ -17,15 +17,36 @@ package io.grpc.observability; import io.grpc.ExperimentalApi; +import io.grpc.ManagedChannelProvider.ProviderNotFoundException; +import io.grpc.observability.interceptors.InternalLoggingChannelInterceptor; -/** - * The main class for gRPC Observability features. - */ +/** The main class for gRPC Observability features. */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/8869") public final class Observability { + private static boolean initialized = false; + + /** + * Initialize grpc-observability. + * + * @throws ProviderNotFoundException if no underlying channel provider is available. + */ + public static synchronized void grpcInit() { + if (initialized) { + throw new IllegalStateException("Observability already initialized!"); + } + LoggingChannelProvider.init(new InternalLoggingChannelInterceptor.FactoryImpl()); + // TODO(sanjaypujare): initialize server provider and customTags map + initialized = true; + } - public static void grpcInit() { - // TODO(sanjaypujare): initialize channel and server providers and customTags map + /** Un-initialize or finish grpc-observability. */ + public static synchronized void grpcFinish() { + if (!initialized) { + throw new IllegalStateException("Observability not initialized!"); + } + LoggingChannelProvider.finish(); + // TODO(sanjaypujare): finish server provider and customTags map + initialized = false; } private Observability() { diff --git a/observability/src/main/java/io/grpc/observability/interceptors/InternalLoggingChannelInterceptor.java b/observability/src/main/java/io/grpc/observability/interceptors/InternalLoggingChannelInterceptor.java new file mode 100644 index 00000000000..e352c5b7377 --- /dev/null +++ b/observability/src/main/java/io/grpc/observability/interceptors/InternalLoggingChannelInterceptor.java @@ -0,0 +1,48 @@ +/* + * 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.observability.interceptors; + +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.Internal; +import io.grpc.MethodDescriptor; + +/** A channel provider that injects logging interceptor. */ +@Internal +public final class InternalLoggingChannelInterceptor implements ClientInterceptor { + + public interface Factory { + ClientInterceptor create(); + } + + public static class FactoryImpl implements Factory { + + @Override + public ClientInterceptor create() { + return new InternalLoggingChannelInterceptor(); + } + } + + @Override + public ClientCall interceptCall(MethodDescriptor method, + CallOptions callOptions, Channel next) { + // TODO(dnvindhya) implement the interceptor + return null; + } +} diff --git a/observability/src/test/java/io/grpc/observability/LoggingChannelProviderTest.java b/observability/src/test/java/io/grpc/observability/LoggingChannelProviderTest.java new file mode 100644 index 00000000000..639bcbc6d0d --- /dev/null +++ b/observability/src/test/java/io/grpc/observability/LoggingChannelProviderTest.java @@ -0,0 +1,135 @@ +/* + * 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.observability; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.AdditionalAnswers.delegatesTo; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.mock; +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.Grpc; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import io.grpc.ManagedChannelProvider; +import io.grpc.MethodDescriptor; +import io.grpc.TlsChannelCredentials; +import io.grpc.observability.interceptors.InternalLoggingChannelInterceptor; +import io.grpc.testing.TestMethodDescriptors; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentMatchers; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@RunWith(JUnit4.class) +public class LoggingChannelProviderTest { + @Rule + public final MockitoRule mocks = MockitoJUnit.rule(); + + private final MethodDescriptor method = TestMethodDescriptors.voidMethod(); + + @Test + public void initTwiceCausesException() { + ManagedChannelProvider prevProvider = ManagedChannelProvider.provider(); + assertThat(prevProvider).isNotInstanceOf(LoggingChannelProvider.class); + LoggingChannelProvider.init(new InternalLoggingChannelInterceptor.FactoryImpl()); + assertThat(ManagedChannelProvider.provider()).isInstanceOf(LoggingChannelProvider.class); + try { + LoggingChannelProvider.init(new InternalLoggingChannelInterceptor.FactoryImpl()); + fail("should have failed for calling init() again"); + } catch (IllegalStateException e) { + assertThat(e).hasMessageThat().contains("LoggingChannelProvider already initialized!"); + } + LoggingChannelProvider.finish(); + assertThat(ManagedChannelProvider.provider()).isSameInstanceAs(prevProvider); + } + + @Test + public void forTarget_interceptorCalled() { + ClientInterceptor interceptor = mock(ClientInterceptor.class, + delegatesTo(new NoopInterceptor())); + InternalLoggingChannelInterceptor.Factory factory = mock( + InternalLoggingChannelInterceptor.Factory.class); + when(factory.create()).thenReturn(interceptor); + LoggingChannelProvider.init(factory); + ManagedChannelBuilder builder = ManagedChannelBuilder.forTarget("localhost"); + ManagedChannel channel = builder.build(); + CallOptions callOptions = CallOptions.DEFAULT; + + ClientCall unused = channel.newCall(method, callOptions); + verify(interceptor) + .interceptCall(same(method), same(callOptions), ArgumentMatchers.any()); + channel.shutdownNow(); + LoggingChannelProvider.finish(); + } + + @Test + public void forAddress_interceptorCalled() { + ClientInterceptor interceptor = mock(ClientInterceptor.class, + delegatesTo(new NoopInterceptor())); + InternalLoggingChannelInterceptor.Factory factory = mock( + InternalLoggingChannelInterceptor.Factory.class); + when(factory.create()).thenReturn(interceptor); + LoggingChannelProvider.init(factory); + ManagedChannelBuilder builder = ManagedChannelBuilder.forAddress("localhost", 80); + ManagedChannel channel = builder.build(); + CallOptions callOptions = CallOptions.DEFAULT; + + ClientCall unused = channel.newCall(method, callOptions); + verify(interceptor) + .interceptCall(same(method), same(callOptions), ArgumentMatchers.any()); + channel.shutdownNow(); + LoggingChannelProvider.finish(); + } + + @Test + public void newChannelBuilder_interceptorCalled() { + ClientInterceptor interceptor = mock(ClientInterceptor.class, + delegatesTo(new NoopInterceptor())); + InternalLoggingChannelInterceptor.Factory factory = mock( + InternalLoggingChannelInterceptor.Factory.class); + when(factory.create()).thenReturn(interceptor); + LoggingChannelProvider.init(factory); + ManagedChannelBuilder builder = Grpc.newChannelBuilder("localhost", + TlsChannelCredentials.create()); + ManagedChannel channel = builder.build(); + CallOptions callOptions = CallOptions.DEFAULT; + + ClientCall unused = channel.newCall(method, callOptions); + verify(interceptor) + .interceptCall(same(method), same(callOptions), ArgumentMatchers.any()); + channel.shutdownNow(); + LoggingChannelProvider.finish(); + } + + private static class NoopInterceptor implements ClientInterceptor { + @Override + public ClientCall interceptCall(MethodDescriptor method, + CallOptions callOptions, Channel next) { + return next.newCall(method, callOptions); + } + } +} diff --git a/observability/src/test/java/io/grpc/observability/ObservabilityTest.java b/observability/src/test/java/io/grpc/observability/ObservabilityTest.java new file mode 100644 index 00000000000..6c71a0e2640 --- /dev/null +++ b/observability/src/test/java/io/grpc/observability/ObservabilityTest.java @@ -0,0 +1,46 @@ +/* + * 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.observability; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ObservabilityTest { + + @Test + public void initFinish() { + Observability.grpcInit(); + try { + Observability.grpcInit(); + fail("should have failed for calling grpcInit() again"); + } catch (IllegalStateException e) { + assertThat(e).hasMessageThat().contains("Observability already initialized!"); + } + Observability.grpcFinish(); + try { + Observability.grpcFinish(); + fail("should have failed for calling grpcFinit() on uninitialized"); + } catch (IllegalStateException e) { + assertThat(e).hasMessageThat().contains("Observability not initialized!"); + } + } +} From bd156f98d69d7dadf2991903b006cb762c4c18e1 Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Tue, 1 Feb 2022 10:05:59 -0800 Subject: [PATCH 31/68] observability: implement and integrate LoggingServerProvider into Observability (#8879) --- api/src/main/java/io/grpc/ServerProvider.java | 4 +- .../test/java/io/grpc/ServerRegistryTest.java | 2 +- .../io/grpc/netty/NettyServerProvider.java | 4 +- observability/build.gradle | 3 +- .../observability/LoggingServerProvider.java | 86 +++++++++++ .../io/grpc/observability/Observability.java | 9 +- .../InternalLoggingChannelInterceptor.java | 2 +- .../InternalLoggingServerInterceptor.java | 47 ++++++ .../LoggingServerProviderTest.java | 136 ++++++++++++++++++ 9 files changed, 283 insertions(+), 10 deletions(-) create mode 100644 observability/src/main/java/io/grpc/observability/LoggingServerProvider.java create mode 100644 observability/src/main/java/io/grpc/observability/interceptors/InternalLoggingServerInterceptor.java create mode 100644 observability/src/test/java/io/grpc/observability/LoggingServerProviderTest.java diff --git a/api/src/main/java/io/grpc/ServerProvider.java b/api/src/main/java/io/grpc/ServerProvider.java index f72880b375d..b8a8903633b 100644 --- a/api/src/main/java/io/grpc/ServerProvider.java +++ b/api/src/main/java/io/grpc/ServerProvider.java @@ -64,13 +64,13 @@ public static ServerProvider provider() { /** * Creates a new builder with the given port. */ - protected abstract ServerBuilder builderForPort(int port); + public abstract ServerBuilder builderForPort(int port); /** * Creates a new builder with the given port and credentials. Returns an error-string result if * unable to understand the credentials. */ - protected NewServerBuilderResult newServerBuilderForPort(int port, ServerCredentials creds) { + public NewServerBuilderResult newServerBuilderForPort(int port, ServerCredentials creds) { return NewServerBuilderResult.error("ServerCredentials are unsupported"); } diff --git a/api/src/test/java/io/grpc/ServerRegistryTest.java b/api/src/test/java/io/grpc/ServerRegistryTest.java index 377f9eda737..6d094c1805d 100644 --- a/api/src/test/java/io/grpc/ServerRegistryTest.java +++ b/api/src/test/java/io/grpc/ServerRegistryTest.java @@ -171,7 +171,7 @@ protected int priority() { } @Override - protected ServerBuilder builderForPort(int port) { + public ServerBuilder builderForPort(int port) { throw new UnsupportedOperationException(); } } diff --git a/netty/src/main/java/io/grpc/netty/NettyServerProvider.java b/netty/src/main/java/io/grpc/netty/NettyServerProvider.java index 42d075d05cb..bc936b23c5e 100644 --- a/netty/src/main/java/io/grpc/netty/NettyServerProvider.java +++ b/netty/src/main/java/io/grpc/netty/NettyServerProvider.java @@ -36,12 +36,12 @@ protected int priority() { } @Override - protected NettyServerBuilder builderForPort(int port) { + public NettyServerBuilder builderForPort(int port) { return NettyServerBuilder.forPort(port); } @Override - protected NewServerBuilderResult newServerBuilderForPort(int port, ServerCredentials creds) { + public NewServerBuilderResult newServerBuilderForPort(int port, ServerCredentials creds) { ProtocolNegotiators.FromServerCredentialsResult result = ProtocolNegotiators.from(creds); if (result.error != null) { return NewServerBuilderResult.error(result.error); diff --git a/observability/build.gradle b/observability/build.gradle index c78541b6c18..2b8f746dba2 100644 --- a/observability/build.gradle +++ b/observability/build.gradle @@ -12,10 +12,11 @@ dependencies { implementation libraries.guava testImplementation project(':grpc-testing'), + project(':grpc-testing-proto'), project(':grpc-netty-shaded') testImplementation (libraries.guava_testlib) { exclude group: 'junit', module: 'junit' } - signature "org.codehaus.mojo.signature:java17:1.0@signature" + signature "org.codehaus.mojo.signature:java18:1.0@signature" } diff --git a/observability/src/main/java/io/grpc/observability/LoggingServerProvider.java b/observability/src/main/java/io/grpc/observability/LoggingServerProvider.java new file mode 100644 index 00000000000..90a896363c4 --- /dev/null +++ b/observability/src/main/java/io/grpc/observability/LoggingServerProvider.java @@ -0,0 +1,86 @@ +/* + * 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.observability; + +import static com.google.common.base.Preconditions.checkNotNull; + +import io.grpc.ServerBuilder; +import io.grpc.ServerCredentials; +import io.grpc.ServerProvider; +import io.grpc.ServerRegistry; +import io.grpc.observability.interceptors.InternalLoggingServerInterceptor; + +/** A server provider that injects the logging interceptor. */ +final class LoggingServerProvider extends ServerProvider { + private final ServerProvider prevProvider; + private final InternalLoggingServerInterceptor.Factory serverInterceptorFactory; + + private static LoggingServerProvider instance; + + private LoggingServerProvider(InternalLoggingServerInterceptor.Factory factory) { + prevProvider = ServerProvider.provider(); + serverInterceptorFactory = factory; + } + + static synchronized void init(InternalLoggingServerInterceptor.Factory factory) { + if (instance != null) { + throw new IllegalStateException("LoggingServerProvider already initialized!"); + } + instance = new LoggingServerProvider(factory); + ServerRegistry.getDefaultRegistry().register(instance); + } + + static synchronized void finish() { + if (instance == null) { + throw new IllegalStateException("LoggingServerProvider not initialized!"); + } + ServerRegistry.getDefaultRegistry().deregister(instance); + instance = null; + } + + @Override + protected boolean isAvailable() { + return true; + } + + @Override + protected int priority() { + return 6; + } + + private ServerBuilder addInterceptor(ServerBuilder builder) { + return builder.intercept(serverInterceptorFactory.create()); + } + + @Override + public ServerBuilder builderForPort(int port) { + return addInterceptor(prevProvider.builderForPort(port)); + } + + @Override + public NewServerBuilderResult newServerBuilderForPort(int port, ServerCredentials creds) { + ServerProvider.NewServerBuilderResult result = prevProvider.newServerBuilderForPort(port, + creds); + ServerBuilder builder = result.getServerBuilder(); + if (builder != null) { + return ServerProvider.NewServerBuilderResult.serverBuilder( + addInterceptor(builder)); + } + checkNotNull(result.getError(), "Expected error to be set!"); + return result; + } +} diff --git a/observability/src/main/java/io/grpc/observability/Observability.java b/observability/src/main/java/io/grpc/observability/Observability.java index 1f92fd817cb..a7a6b04ad07 100644 --- a/observability/src/main/java/io/grpc/observability/Observability.java +++ b/observability/src/main/java/io/grpc/observability/Observability.java @@ -19,6 +19,7 @@ import io.grpc.ExperimentalApi; import io.grpc.ManagedChannelProvider.ProviderNotFoundException; import io.grpc.observability.interceptors.InternalLoggingChannelInterceptor; +import io.grpc.observability.interceptors.InternalLoggingServerInterceptor; /** The main class for gRPC Observability features. */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/8869") @@ -28,14 +29,15 @@ public final class Observability { /** * Initialize grpc-observability. * - * @throws ProviderNotFoundException if no underlying channel provider is available. + * @throws ProviderNotFoundException if no underlying channel/server provider is available. */ public static synchronized void grpcInit() { if (initialized) { throw new IllegalStateException("Observability already initialized!"); } LoggingChannelProvider.init(new InternalLoggingChannelInterceptor.FactoryImpl()); - // TODO(sanjaypujare): initialize server provider and customTags map + LoggingServerProvider.init(new InternalLoggingServerInterceptor.FactoryImpl()); + // TODO(sanjaypujare): initialize customTags map initialized = true; } @@ -45,7 +47,8 @@ public static synchronized void grpcFinish() { throw new IllegalStateException("Observability not initialized!"); } LoggingChannelProvider.finish(); - // TODO(sanjaypujare): finish server provider and customTags map + LoggingServerProvider.finish(); + // TODO(sanjaypujare): finish customTags map initialized = false; } diff --git a/observability/src/main/java/io/grpc/observability/interceptors/InternalLoggingChannelInterceptor.java b/observability/src/main/java/io/grpc/observability/interceptors/InternalLoggingChannelInterceptor.java index e352c5b7377..3e535de3816 100644 --- a/observability/src/main/java/io/grpc/observability/interceptors/InternalLoggingChannelInterceptor.java +++ b/observability/src/main/java/io/grpc/observability/interceptors/InternalLoggingChannelInterceptor.java @@ -23,7 +23,7 @@ import io.grpc.Internal; import io.grpc.MethodDescriptor; -/** A channel provider that injects logging interceptor. */ +/** A logging interceptor for {@code LoggingChannelProvider}. */ @Internal public final class InternalLoggingChannelInterceptor implements ClientInterceptor { diff --git a/observability/src/main/java/io/grpc/observability/interceptors/InternalLoggingServerInterceptor.java b/observability/src/main/java/io/grpc/observability/interceptors/InternalLoggingServerInterceptor.java new file mode 100644 index 00000000000..17385653121 --- /dev/null +++ b/observability/src/main/java/io/grpc/observability/interceptors/InternalLoggingServerInterceptor.java @@ -0,0 +1,47 @@ +/* + * 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.observability.interceptors; + +import io.grpc.Internal; +import io.grpc.Metadata; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; + +/** A logging interceptor for {@code LoggingServerProvider}. */ +@Internal +public final class InternalLoggingServerInterceptor implements ServerInterceptor { + + public interface Factory { + ServerInterceptor create(); + } + + public static class FactoryImpl implements Factory { + + @Override + public ServerInterceptor create() { + return new InternalLoggingServerInterceptor(); + } + } + + @Override + public ServerCall.Listener interceptCall(ServerCall call, + Metadata headers, ServerCallHandler next) { + // TODO(dnvindhya) implement the interceptor + return null; + } +} diff --git a/observability/src/test/java/io/grpc/observability/LoggingServerProviderTest.java b/observability/src/test/java/io/grpc/observability/LoggingServerProviderTest.java new file mode 100644 index 00000000000..fd6b60b4738 --- /dev/null +++ b/observability/src/test/java/io/grpc/observability/LoggingServerProviderTest.java @@ -0,0 +1,136 @@ +/* + * 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.observability; + +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.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import io.grpc.Grpc; +import io.grpc.InsecureServerCredentials; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import io.grpc.Metadata; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; +import io.grpc.ServerProvider; +import io.grpc.observability.interceptors.InternalLoggingServerInterceptor; +import io.grpc.stub.StreamObserver; +import io.grpc.testing.GrpcCleanupRule; +import io.grpc.testing.protobuf.SimpleRequest; +import io.grpc.testing.protobuf.SimpleResponse; +import io.grpc.testing.protobuf.SimpleServiceGrpc; +import java.io.IOException; +import java.util.function.Supplier; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentMatchers; + +@RunWith(JUnit4.class) +public class LoggingServerProviderTest { + @Rule public final GrpcCleanupRule cleanupRule = new GrpcCleanupRule(); + + @Test + public void initTwiceCausesException() { + ServerProvider prevProvider = ServerProvider.provider(); + assertThat(prevProvider).isNotInstanceOf(LoggingServerProvider.class); + LoggingServerProvider.init(new InternalLoggingServerInterceptor.FactoryImpl()); + assertThat(ServerProvider.provider()).isInstanceOf(ServerProvider.class); + try { + LoggingServerProvider.init(new InternalLoggingServerInterceptor.FactoryImpl()); + fail("should have failed for calling init() again"); + } catch (IllegalStateException e) { + assertThat(e).hasMessageThat().contains("LoggingServerProvider already initialized!"); + } + LoggingServerProvider.finish(); + assertThat(ServerProvider.provider()).isSameInstanceAs(prevProvider); + } + + @Test + public void forPort_interceptorCalled() throws IOException { + serverBuilder_interceptorCalled(() -> ServerBuilder.forPort(0)); + } + + @Test + public void newServerBuilder_interceptorCalled() throws IOException { + serverBuilder_interceptorCalled( + () -> Grpc.newServerBuilderForPort(0, InsecureServerCredentials.create())); + } + + @SuppressWarnings("unchecked") + private void serverBuilder_interceptorCalled(Supplier> serverBuilderSupplier) + throws IOException { + ServerInterceptor interceptor = + mock(ServerInterceptor.class, delegatesTo(new NoopInterceptor())); + InternalLoggingServerInterceptor.Factory factory = mock( + InternalLoggingServerInterceptor.Factory.class); + when(factory.create()).thenReturn(interceptor); + LoggingServerProvider.init(factory); + Server server = serverBuilderSupplier.get().addService(new SimpleServiceImpl()).build().start(); + int port = cleanupRule.register(server).getPort(); + ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", port).usePlaintext() + .build(); + SimpleServiceGrpc.SimpleServiceBlockingStub stub = SimpleServiceGrpc.newBlockingStub( + cleanupRule.register(channel)); + assertThat(unaryRpc("buddy", stub)).isEqualTo("Hello buddy"); + verify(interceptor).interceptCall(any(ServerCall.class), any(Metadata.class), anyCallHandler()); + LoggingServerProvider.finish(); + } + + private ServerCallHandler anyCallHandler() { + return ArgumentMatchers.any(); + } + + private static String unaryRpc( + String requestMessage, SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub) { + SimpleRequest request = SimpleRequest.newBuilder().setRequestMessage(requestMessage).build(); + SimpleResponse response = blockingStub.unaryRpc(request); + return response.getResponseMessage(); + } + + private static class SimpleServiceImpl extends SimpleServiceGrpc.SimpleServiceImplBase { + + @Override + public void unaryRpc(SimpleRequest req, StreamObserver responseObserver) { + SimpleResponse response = + SimpleResponse.newBuilder() + .setResponseMessage("Hello " + req.getRequestMessage()) + .build(); + responseObserver.onNext(response); + responseObserver.onCompleted(); + } + } + + private static class NoopInterceptor implements ServerInterceptor { + @Override + public ServerCall.Listener interceptCall( + ServerCall call, + Metadata headers, + ServerCallHandler next) { + return next.startCall(call, headers); + } + } +} From 7308d920346e2e1cae640ed1f7e4dfad1b032df8 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Tue, 1 Feb 2022 14:08:15 -0800 Subject: [PATCH 32/68] rls: support routeLookupChannelServiceConfig in RLS lb config Implementing the latest change for RLS lb config. ``` The configuration for the LB policy will be of the following form: { "routeLookupConfig": , "routeLookupChannelServiceConfig": {...service config JSON...}, "childPolicy": [ {"": {...child policy config...}} ], "childPolicyConfigTargetFieldName": "" } ``` >If the routeLookupChannelServiceConfig field is present, we will pass the specified service config to the RLS control plane channel, and we will disable fetching service config via that channel's resolver. --- .../java/io/grpc/rls/CachingRlsLbClient.java | 36 +++----------- .../io/grpc/rls/LbPolicyConfiguration.java | 15 +++++- .../io/grpc/rls/RlsLoadBalancerProvider.java | 5 +- .../io/grpc/rls/CachingRlsLbClientTest.java | 48 ++++++++++--------- .../java/io/grpc/rls/RlsLoadBalancerTest.java | 5 -- 5 files changed, 48 insertions(+), 61 deletions(-) diff --git a/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java b/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java index 8af93d81e09..ad5abd02058 100644 --- a/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java +++ b/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java @@ -23,8 +23,6 @@ import com.google.common.base.Converter; import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects.ToStringHelper; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import io.grpc.ChannelLogger; @@ -83,13 +81,6 @@ final class CachingRlsLbClient { private static final Converter RESPONSE_CONVERTER = new RouteLookupResponseConverter().reverse(); - // System property to use direct path enabled OobChannel, by default direct path is enabled. - private static final String RLS_ENABLE_OOB_CHANNEL_DIRECTPATH_PROPERTY = - "io.grpc.rls.CachingRlsLbClient.enable_oobchannel_directpath"; - @VisibleForTesting - static boolean enableOobChannelDirectPath = - Boolean.parseBoolean(System.getProperty(RLS_ENABLE_OOB_CHANNEL_DIRECTPATH_PROPERTY, "false")); - // 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) @@ -158,14 +149,14 @@ private CachingRlsLbClient(Builder builder) { ManagedChannelBuilder rlsChannelBuilder = helper.createResolvingOobChannelBuilder( rlsConfig.getLookupService(), helper.getUnsafeChannelCredentials()); rlsChannelBuilder.overrideAuthority(helper.getAuthority()); - if (enableOobChannelDirectPath) { - Map directPathServiceConfig = - getDirectPathServiceConfig(rlsConfig.getLookupService()); + Map routeLookupChannelServiceConfig = + lbPolicyConfig.getRouteLookupChannelServiceConfig(); + if (routeLookupChannelServiceConfig != null) { logger.log( ChannelLogLevel.DEBUG, - "RLS channel direct path enabled. RLS channel service config: {0}", - directPathServiceConfig); - rlsChannelBuilder.defaultServiceConfig(directPathServiceConfig); + "RLS channel service config: {0}", + routeLookupChannelServiceConfig); + rlsChannelBuilder.defaultServiceConfig(routeLookupChannelServiceConfig); rlsChannelBuilder.disableServiceConfigLookUp(); } rlsChannel = rlsChannelBuilder.build(); @@ -184,21 +175,6 @@ private CachingRlsLbClient(Builder builder) { logger.log(ChannelLogLevel.DEBUG, "CachingRlsLbClient created"); } - private static ImmutableMap getDirectPathServiceConfig(String serviceName) { - ImmutableMap pickFirstStrategy = - ImmutableMap.of("pick_first", ImmutableMap.of()); - - ImmutableMap childPolicy = - ImmutableMap.of( - "childPolicy", ImmutableList.of(pickFirstStrategy), - "serviceName", serviceName); - - ImmutableMap grpcLbPolicy = - ImmutableMap.of("grpclb", childPolicy); - - return ImmutableMap.of("loadBalancingConfig", ImmutableList.of(grpcLbPolicy)); - } - @CheckReturnValue private ListenableFuture asyncRlsCall(RouteLookupRequest request) { final SettableFuture response = SettableFuture.create(); diff --git a/rls/src/main/java/io/grpc/rls/LbPolicyConfiguration.java b/rls/src/main/java/io/grpc/rls/LbPolicyConfiguration.java index 94a9de9801f..f40cde9fd81 100644 --- a/rls/src/main/java/io/grpc/rls/LbPolicyConfiguration.java +++ b/rls/src/main/java/io/grpc/rls/LbPolicyConfiguration.java @@ -48,11 +48,15 @@ final class LbPolicyConfiguration { private final RouteLookupConfig routeLookupConfig; + @Nullable + private final Map routeLookupChannelServiceConfig; private final ChildLoadBalancingPolicy policy; LbPolicyConfiguration( - RouteLookupConfig routeLookupConfig, ChildLoadBalancingPolicy policy) { + RouteLookupConfig routeLookupConfig, @Nullable Map routeLookupChannelServiceConfig, + ChildLoadBalancingPolicy policy) { this.routeLookupConfig = checkNotNull(routeLookupConfig, "routeLookupConfig"); + this.routeLookupChannelServiceConfig = routeLookupChannelServiceConfig; this.policy = checkNotNull(policy, "policy"); } @@ -60,6 +64,11 @@ RouteLookupConfig getRouteLookupConfig() { return routeLookupConfig; } + @Nullable + Map getRouteLookupChannelServiceConfig() { + return routeLookupChannelServiceConfig; + } + ChildLoadBalancingPolicy getLoadBalancingPolicy() { return policy; } @@ -74,18 +83,20 @@ public boolean equals(Object o) { } LbPolicyConfiguration that = (LbPolicyConfiguration) o; return Objects.equals(routeLookupConfig, that.routeLookupConfig) + && Objects.equals(routeLookupChannelServiceConfig, that.routeLookupChannelServiceConfig) && Objects.equals(policy, that.policy); } @Override public int hashCode() { - return Objects.hash(routeLookupConfig, policy); + return Objects.hash(routeLookupConfig, routeLookupChannelServiceConfig, policy); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("routeLookupConfig", routeLookupConfig) + .add("routeLookupChannelServiceConfig", routeLookupChannelServiceConfig) .add("policy", policy) .toString(); } diff --git a/rls/src/main/java/io/grpc/rls/RlsLoadBalancerProvider.java b/rls/src/main/java/io/grpc/rls/RlsLoadBalancerProvider.java index d5ee8073db0..54ef848498c 100644 --- a/rls/src/main/java/io/grpc/rls/RlsLoadBalancerProvider.java +++ b/rls/src/main/java/io/grpc/rls/RlsLoadBalancerProvider.java @@ -62,12 +62,15 @@ public ConfigOrError parseLoadBalancingPolicyConfig(Map rawLoadBalanc try { RouteLookupConfig routeLookupConfig = new RouteLookupConfigConverter() .convert(JsonUtil.getObject(rawLoadBalancingConfigPolicy, "routeLookupConfig")); + Map routeLookupChannelServiceConfig = + JsonUtil.getObject(rawLoadBalancingConfigPolicy, "routeLookupChannelServiceConfig"); ChildLoadBalancingPolicy lbPolicy = ChildLoadBalancingPolicy .create( JsonUtil.getString(rawLoadBalancingConfigPolicy, "childPolicyConfigTargetFieldName"), JsonUtil.checkObjectList( checkNotNull(JsonUtil.getList(rawLoadBalancingConfigPolicy, "childPolicy")))); - return ConfigOrError.fromConfig(new LbPolicyConfiguration(routeLookupConfig, lbPolicy)); + return ConfigOrError.fromConfig( + new LbPolicyConfiguration(routeLookupConfig, routeLookupChannelServiceConfig, lbPolicy)); } catch (Exception e) { return ConfigOrError.fromError( Status.INVALID_ARGUMENT diff --git a/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java b/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java index d10bc82d071..f5ad21d047a 100644 --- a/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java +++ b/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java @@ -90,7 +90,6 @@ import java.util.concurrent.TimeoutException; import javax.annotation.Nonnull; import org.junit.After; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -144,19 +143,12 @@ public void uncaughtException(Thread t, Throwable e) { mock(Helper.class, AdditionalAnswers.delegatesTo(new FakeHelper())); private final FakeThrottler fakeThrottler = new FakeThrottler(); private final LbPolicyConfiguration lbPolicyConfiguration = - new LbPolicyConfiguration(ROUTE_LOOKUP_CONFIG, childLbPolicy); + new LbPolicyConfiguration(ROUTE_LOOKUP_CONFIG, null, childLbPolicy); private CachingRlsLbClient rlsLbClient; - private boolean existingEnableOobChannelDirectPath; private Map rlsChannelServiceConfig; private String rlsChannelOverriddenAuthority; - @Before - public void setUp() throws Exception { - existingEnableOobChannelDirectPath = CachingRlsLbClient.enableOobChannelDirectPath; - CachingRlsLbClient.enableOobChannelDirectPath = false; - } - private void setUpRlsLbClient() { rlsLbClient = CachingRlsLbClient.newBuilder() @@ -173,7 +165,6 @@ private void setUpRlsLbClient() { @After public void tearDown() throws Exception { rlsLbClient.close(); - CachingRlsLbClient.enableOobChannelDirectPath = existingEnableOobChannelDirectPath; assertWithMessage( "On client shut down, RlsLoadBalancer must shut down with all its child loadbalancers.") .that(lbProvider.loadBalancers).isEmpty(); @@ -244,9 +235,29 @@ public void get_noError_lifeCycle() throws Exception { } @Test - public void rls_overDirectPath() throws Exception { - CachingRlsLbClient.enableOobChannelDirectPath = true; - setUpRlsLbClient(); + public void rls_withCustomRlsChannelServiceConfig() throws Exception { + Map routeLookupChannelServiceConfig = + ImmutableMap.of( + "loadBalancingConfig", + ImmutableList.of(ImmutableMap.of( + "grpclb", + ImmutableMap.of( + "childPolicy", + ImmutableList.of(ImmutableMap.of("pick_first", ImmutableMap.of())), + "serviceName", + "service1")))); + LbPolicyConfiguration lbPolicyConfiguration = new LbPolicyConfiguration( + ROUTE_LOOKUP_CONFIG, routeLookupChannelServiceConfig, childLbPolicy); + rlsLbClient = + CachingRlsLbClient.newBuilder() + .setBackoffProvider(fakeBackoffProvider) + .setResolvedAddressesFactory(resolvedAddressFactory) + .setEvictionListener(evictionListener) + .setHelper(helper) + .setLbPolicyConfig(lbPolicyConfiguration) + .setThrottler(fakeThrottler) + .setTimeProvider(fakeTimeProvider) + .build(); RouteLookupRequest routeLookupRequest = new RouteLookupRequest(ImmutableMap.of( "server", "bigtable.googleapis.com", "service-key", "foo", "method-key", "bar")); rlsServerImpl.setLookupTable( @@ -265,16 +276,7 @@ public void rls_overDirectPath() throws Exception { assertThat(resp.hasData()).isTrue(); assertThat(rlsChannelOverriddenAuthority).isEqualTo("bigtable.googleapis.com:443"); - assertThat(rlsChannelServiceConfig).isEqualTo( - ImmutableMap.of( - "loadBalancingConfig", - ImmutableList.of(ImmutableMap.of( - "grpclb", - ImmutableMap.of( - "childPolicy", - ImmutableList.of(ImmutableMap.of("pick_first", ImmutableMap.of())), - "serviceName", - "service1"))))); + assertThat(rlsChannelServiceConfig).isEqualTo(routeLookupChannelServiceConfig); } @Test diff --git a/rls/src/test/java/io/grpc/rls/RlsLoadBalancerTest.java b/rls/src/test/java/io/grpc/rls/RlsLoadBalancerTest.java index bf0bc47fcc4..d03bb4bcf6f 100644 --- a/rls/src/test/java/io/grpc/rls/RlsLoadBalancerTest.java +++ b/rls/src/test/java/io/grpc/rls/RlsLoadBalancerTest.java @@ -118,16 +118,12 @@ public void uncaughtException(Thread t, Throwable e) { private MethodDescriptor fakeSearchMethod; private MethodDescriptor fakeRescueMethod; private RlsLoadBalancer rlsLb; - private boolean existingEnableOobChannelDirectPath; private String defaultTarget = "defaultTarget"; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - existingEnableOobChannelDirectPath = CachingRlsLbClient.enableOobChannelDirectPath; - CachingRlsLbClient.enableOobChannelDirectPath = false; - fakeSearchMethod = MethodDescriptor.newBuilder() .setFullMethodName("com.google/Search") @@ -168,7 +164,6 @@ public CachingRlsLbClient.Builder get() { @After public void tearDown() throws Exception { rlsLb.shutdown(); - CachingRlsLbClient.enableOobChannelDirectPath = existingEnableOobChannelDirectPath; } @Test From ca4a1d8ca38d4f9595692a788ba31111e829dbcc Mon Sep 17 00:00:00 2001 From: litclimbing <94636143+litclimbing@users.noreply.github.com> Date: Thu, 3 Feb 2022 16:56:43 -0700 Subject: [PATCH 33/68] android: fix for app coming to foreground When an app goes to the background, onBlockedStatusChanged is called with true and then called with false when it comes back to the foreground. The function onAvailable isn't called in this case and the connection wasn't being reset. Closes #8850 I noticed the comment that this is used for API versions 24+ but onBlockedStatusChanged was added in 29. I'm not sure if some kind of guard needs to be added or not. https://developer.android.com/reference/android/net/ConnectivityManager.NetworkCallback#onBlockedStatusChanged(android.net.Network,%20boolean) --- .../src/main/java/io/grpc/android/AndroidChannelBuilder.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/android/src/main/java/io/grpc/android/AndroidChannelBuilder.java b/android/src/main/java/io/grpc/android/AndroidChannelBuilder.java index 19b2b1a2347..e76b9d4ff83 100644 --- a/android/src/main/java/io/grpc/android/AndroidChannelBuilder.java +++ b/android/src/main/java/io/grpc/android/AndroidChannelBuilder.java @@ -297,6 +297,11 @@ private class DefaultNetworkCallback extends ConnectivityManager.NetworkCallback public void onAvailable(Network network) { delegate.enterIdle(); } + @Override + public void onBlockedStatusChanged (Network network, boolean blocked) { + if (!blocked) + delegate.enterIdle(); + } } /** Respond to network changes. Only used on API levels < 24. */ From a66151542150561de7be3ec8ed871f11a2e5b445 Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Fri, 4 Feb 2022 08:38:46 -0800 Subject: [PATCH 34/68] observability: revert previous visibility changes made in the provider APIs (#8889) change visibility back to protected for certain methods of Providers --- .../grpc/InternalManagedChannelProvider.java | 42 +++++++++++++++++++ .../java/io/grpc/InternalServerProvider.java | 36 ++++++++++++++++ .../java/io/grpc/ManagedChannelProvider.java | 6 +-- api/src/main/java/io/grpc/ServerProvider.java | 4 +- .../io/grpc/ManagedChannelRegistryTest.java | 4 +- .../test/java/io/grpc/ServerRegistryTest.java | 2 +- .../io/grpc/netty/NettyServerProvider.java | 4 +- .../observability/LoggingChannelProvider.java | 15 ++++--- .../observability/LoggingServerProvider.java | 10 +++-- 9 files changed, 103 insertions(+), 20 deletions(-) create mode 100644 api/src/main/java/io/grpc/InternalManagedChannelProvider.java create mode 100644 api/src/main/java/io/grpc/InternalServerProvider.java diff --git a/api/src/main/java/io/grpc/InternalManagedChannelProvider.java b/api/src/main/java/io/grpc/InternalManagedChannelProvider.java new file mode 100644 index 00000000000..076b5464b7e --- /dev/null +++ b/api/src/main/java/io/grpc/InternalManagedChannelProvider.java @@ -0,0 +1,42 @@ +/* + * 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; + +import io.grpc.ManagedChannelProvider.NewChannelBuilderResult; + +/** Internal accessor for {@link ManagedChannelProvider}. */ +@Internal +public final class InternalManagedChannelProvider { + + private InternalManagedChannelProvider() { + } + + public static ManagedChannelBuilder builderForAddress(ManagedChannelProvider provider, + String name, int port) { + return provider.builderForAddress(name, port); + } + + public static ManagedChannelBuilder builderForTarget(ManagedChannelProvider provider, + String target) { + return provider.builderForTarget(target); + } + + public static NewChannelBuilderResult newChannelBuilder(ManagedChannelProvider provider, + String target, ChannelCredentials creds) { + return provider.newChannelBuilder(target, creds); + } +} diff --git a/api/src/main/java/io/grpc/InternalServerProvider.java b/api/src/main/java/io/grpc/InternalServerProvider.java new file mode 100644 index 00000000000..d8c1d77fe76 --- /dev/null +++ b/api/src/main/java/io/grpc/InternalServerProvider.java @@ -0,0 +1,36 @@ +/* + * 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; + +import io.grpc.ServerProvider.NewServerBuilderResult; + +/** Internal accessor for {@link ServerProvider}. */ +@Internal +public final class InternalServerProvider { + + private InternalServerProvider() { + } + + public static ServerBuilder builderForPort(ServerProvider provider, int port) { + return provider.builderForPort(port); + } + + public static NewServerBuilderResult newServerBuilderForPort(ServerProvider provider, int port, + ServerCredentials creds) { + return provider.newServerBuilderForPort(port, creds); + } +} diff --git a/api/src/main/java/io/grpc/ManagedChannelProvider.java b/api/src/main/java/io/grpc/ManagedChannelProvider.java index e388abfd9d0..f57340d9ba9 100644 --- a/api/src/main/java/io/grpc/ManagedChannelProvider.java +++ b/api/src/main/java/io/grpc/ManagedChannelProvider.java @@ -64,18 +64,18 @@ public static ManagedChannelProvider provider() { /** * Creates a new builder with the given host and port. */ - public abstract ManagedChannelBuilder builderForAddress(String name, int port); + protected abstract ManagedChannelBuilder builderForAddress(String name, int port); /** * Creates a new builder with the given target URI. */ - public abstract ManagedChannelBuilder builderForTarget(String target); + protected abstract ManagedChannelBuilder builderForTarget(String target); /** * Creates a new builder with the given target URI and credentials. Returns an error-string result * if unable to understand the credentials. */ - public NewChannelBuilderResult newChannelBuilder(String target, ChannelCredentials creds) { + protected NewChannelBuilderResult newChannelBuilder(String target, ChannelCredentials creds) { return NewChannelBuilderResult.error("ChannelCredentials are unsupported"); } diff --git a/api/src/main/java/io/grpc/ServerProvider.java b/api/src/main/java/io/grpc/ServerProvider.java index b8a8903633b..f72880b375d 100644 --- a/api/src/main/java/io/grpc/ServerProvider.java +++ b/api/src/main/java/io/grpc/ServerProvider.java @@ -64,13 +64,13 @@ public static ServerProvider provider() { /** * Creates a new builder with the given port. */ - public abstract ServerBuilder builderForPort(int port); + protected abstract ServerBuilder builderForPort(int port); /** * Creates a new builder with the given port and credentials. Returns an error-string result if * unable to understand the credentials. */ - public NewServerBuilderResult newServerBuilderForPort(int port, ServerCredentials creds) { + protected NewServerBuilderResult newServerBuilderForPort(int port, ServerCredentials creds) { return NewServerBuilderResult.error("ServerCredentials are unsupported"); } diff --git a/api/src/test/java/io/grpc/ManagedChannelRegistryTest.java b/api/src/test/java/io/grpc/ManagedChannelRegistryTest.java index d083a7b6c10..6f25f620576 100644 --- a/api/src/test/java/io/grpc/ManagedChannelRegistryTest.java +++ b/api/src/test/java/io/grpc/ManagedChannelRegistryTest.java @@ -176,12 +176,12 @@ protected int priority() { } @Override - public ManagedChannelBuilder builderForAddress(String name, int port) { + protected ManagedChannelBuilder builderForAddress(String name, int port) { throw new UnsupportedOperationException(); } @Override - public ManagedChannelBuilder builderForTarget(String target) { + protected ManagedChannelBuilder builderForTarget(String target) { throw new UnsupportedOperationException(); } } diff --git a/api/src/test/java/io/grpc/ServerRegistryTest.java b/api/src/test/java/io/grpc/ServerRegistryTest.java index 6d094c1805d..377f9eda737 100644 --- a/api/src/test/java/io/grpc/ServerRegistryTest.java +++ b/api/src/test/java/io/grpc/ServerRegistryTest.java @@ -171,7 +171,7 @@ protected int priority() { } @Override - public ServerBuilder builderForPort(int port) { + protected ServerBuilder builderForPort(int port) { throw new UnsupportedOperationException(); } } diff --git a/netty/src/main/java/io/grpc/netty/NettyServerProvider.java b/netty/src/main/java/io/grpc/netty/NettyServerProvider.java index bc936b23c5e..42d075d05cb 100644 --- a/netty/src/main/java/io/grpc/netty/NettyServerProvider.java +++ b/netty/src/main/java/io/grpc/netty/NettyServerProvider.java @@ -36,12 +36,12 @@ protected int priority() { } @Override - public NettyServerBuilder builderForPort(int port) { + protected NettyServerBuilder builderForPort(int port) { return NettyServerBuilder.forPort(port); } @Override - public NewServerBuilderResult newServerBuilderForPort(int port, ServerCredentials creds) { + protected NewServerBuilderResult newServerBuilderForPort(int port, ServerCredentials creds) { ProtocolNegotiators.FromServerCredentialsResult result = ProtocolNegotiators.from(creds); if (result.error != null) { return NewServerBuilderResult.error(result.error); diff --git a/observability/src/main/java/io/grpc/observability/LoggingChannelProvider.java b/observability/src/main/java/io/grpc/observability/LoggingChannelProvider.java index 4444cf9a807..5011068c176 100644 --- a/observability/src/main/java/io/grpc/observability/LoggingChannelProvider.java +++ b/observability/src/main/java/io/grpc/observability/LoggingChannelProvider.java @@ -19,6 +19,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import io.grpc.ChannelCredentials; +import io.grpc.InternalManagedChannelProvider; import io.grpc.ManagedChannelBuilder; import io.grpc.ManagedChannelProvider; import io.grpc.ManagedChannelRegistry; @@ -67,18 +68,20 @@ private ManagedChannelBuilder addInterceptor(ManagedChannelBuilder builder } @Override - public ManagedChannelBuilder builderForAddress(String name, int port) { - return addInterceptor(prevProvider.builderForAddress(name, port)); + protected ManagedChannelBuilder builderForAddress(String name, int port) { + return addInterceptor( + InternalManagedChannelProvider.builderForAddress(prevProvider, name, port)); } @Override - public ManagedChannelBuilder builderForTarget(String target) { - return addInterceptor(prevProvider.builderForTarget(target)); + protected ManagedChannelBuilder builderForTarget(String target) { + return addInterceptor(InternalManagedChannelProvider.builderForTarget(prevProvider, target)); } @Override - public NewChannelBuilderResult newChannelBuilder(String target, ChannelCredentials creds) { - NewChannelBuilderResult result = prevProvider.newChannelBuilder(target, creds); + protected NewChannelBuilderResult newChannelBuilder(String target, ChannelCredentials creds) { + NewChannelBuilderResult result = InternalManagedChannelProvider.newChannelBuilder(prevProvider, + target, creds); ManagedChannelBuilder builder = result.getChannelBuilder(); if (builder != null) { return NewChannelBuilderResult.channelBuilder( diff --git a/observability/src/main/java/io/grpc/observability/LoggingServerProvider.java b/observability/src/main/java/io/grpc/observability/LoggingServerProvider.java index 90a896363c4..5277bcf572b 100644 --- a/observability/src/main/java/io/grpc/observability/LoggingServerProvider.java +++ b/observability/src/main/java/io/grpc/observability/LoggingServerProvider.java @@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkNotNull; +import io.grpc.InternalServerProvider; import io.grpc.ServerBuilder; import io.grpc.ServerCredentials; import io.grpc.ServerProvider; @@ -67,13 +68,14 @@ private ServerBuilder addInterceptor(ServerBuilder builder) { } @Override - public ServerBuilder builderForPort(int port) { - return addInterceptor(prevProvider.builderForPort(port)); + protected ServerBuilder builderForPort(int port) { + return addInterceptor(InternalServerProvider.builderForPort(prevProvider, port)); } @Override - public NewServerBuilderResult newServerBuilderForPort(int port, ServerCredentials creds) { - ServerProvider.NewServerBuilderResult result = prevProvider.newServerBuilderForPort(port, + protected NewServerBuilderResult newServerBuilderForPort(int port, ServerCredentials creds) { + ServerProvider.NewServerBuilderResult result = InternalServerProvider.newServerBuilderForPort( + prevProvider, port, creds); ServerBuilder builder = result.getServerBuilder(); if (builder != null) { From c46d2c276ae96f06d30739a430cda98f6f2435c3 Mon Sep 17 00:00:00 2001 From: Sergii Tkachenko Date: Fri, 4 Feb 2022 09:56:26 -0800 Subject: [PATCH 35/68] core: test JsonUtil.getObject with a map containing a null value (#8881) Verifies the behavior of JsonUtil.getObject when the map contains a null value for a given key. Note: this may be incorrect behavior. Issue to track the investigation: #8883. --- .../test/java/io/grpc/internal/JsonUtilTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/core/src/test/java/io/grpc/internal/JsonUtilTest.java b/core/src/test/java/io/grpc/internal/JsonUtilTest.java index 960800c97c0..a01f8868220 100644 --- a/core/src/test/java/io/grpc/internal/JsonUtilTest.java +++ b/core/src/test/java/io/grpc/internal/JsonUtilTest.java @@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.junit.Test; @@ -130,4 +131,16 @@ public void getNumber() { assertThat(JsonUtil.getNumberAsInteger(map, "key_nonexistent")).isNull(); assertThat(JsonUtil.getNumberAsLong(map, "key_nonexistent")).isNull(); } + + @Test + public void getObject_mapExplicitNullValue() { + Map mapWithNullValue = Collections.singletonMap("key", null); + try { + JsonUtil.getObject(mapWithNullValue, "key"); + fail("ClassCastException expected"); + } catch (ClassCastException e) { + assertThat(e).hasMessageThat() + .isEqualTo("value 'null' for key 'key' in '{key=null}' is not object"); + } + } } From 467985e958388029e9a25e6988b93d9d2e1118b9 Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Fri, 4 Feb 2022 11:28:13 -0800 Subject: [PATCH 36/68] add kokoro config for basic tests xds k8s (#8888) --- buildscripts/kokoro/xds_k8s_lb.cfg | 13 +++ buildscripts/kokoro/xds_k8s_lb.sh | 172 +++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 buildscripts/kokoro/xds_k8s_lb.cfg create mode 100755 buildscripts/kokoro/xds_k8s_lb.sh diff --git a/buildscripts/kokoro/xds_k8s_lb.cfg b/buildscripts/kokoro/xds_k8s_lb.cfg new file mode 100644 index 00000000000..43971896cd4 --- /dev/null +++ b/buildscripts/kokoro/xds_k8s_lb.cfg @@ -0,0 +1,13 @@ +# Config file for internal CI + +# Location of the continuous shell script in repository. +build_file: "grpc-java/buildscripts/kokoro/xds_k8s_lb.sh" +timeout_mins: 180 + +action { + define_artifacts { + regex: "artifacts/**/*sponge_log.xml" + regex: "artifacts/**/*sponge_log.log" + strip_prefix: "artifacts" + } +} diff --git a/buildscripts/kokoro/xds_k8s_lb.sh b/buildscripts/kokoro/xds_k8s_lb.sh new file mode 100755 index 00000000000..22f1e383440 --- /dev/null +++ b/buildscripts/kokoro/xds_k8s_lb.sh @@ -0,0 +1,172 @@ +#!/usr/bin/env bash +set -eo pipefail + +# Constants +readonly GITHUB_REPOSITORY_NAME="grpc-java" +readonly TEST_DRIVER_INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/${TEST_DRIVER_REPO_OWNER:-grpc}/grpc/${TEST_DRIVER_BRANCH:-master}/tools/internal_ci/linux/grpc_xds_k8s_install_test_driver.sh" +## xDS test server/client Docker images +readonly SERVER_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/java-server" +readonly CLIENT_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/java-client" +readonly FORCE_IMAGE_BUILD="${FORCE_IMAGE_BUILD:-0}" +readonly BUILD_APP_PATH="interop-testing/build/install/grpc-interop-testing" + +####################################### +# Builds the test app using gradle and smoke-checks its binaries +# Globals: +# SRC_DIR +# BUILD_APP_PATH +# Arguments: +# None +# Outputs: +# Writes the output of xds-test-client and xds-test-server --help to stderr +####################################### +build_java_test_app() { + echo "Building Java test app" + cd "${SRC_DIR}" + ./gradlew --no-daemon grpc-interop-testing:installDist -x test \ + -PskipCodegen=true -PskipAndroid=true --console=plain + + # Test-run binaries + run_ignore_exit_code "${SRC_DIR}/${BUILD_APP_PATH}/bin/xds-test-client" --help + run_ignore_exit_code "${SRC_DIR}/${BUILD_APP_PATH}/bin/xds-test-server" --help +} + +####################################### +# Builds test app Docker images and pushes them to GCR +# Globals: +# BUILD_APP_PATH +# SERVER_IMAGE_NAME: Test server Docker image name +# CLIENT_IMAGE_NAME: Test client Docker image name +# GIT_COMMIT: SHA-1 of git commit being built +# Arguments: +# None +# Outputs: +# Writes the output of `gcloud builds submit` to stdout, stderr +####################################### +build_test_app_docker_images() { + echo "Building Java xDS interop test app Docker images" + local docker_dir="${SRC_DIR}/buildscripts/xds-k8s" + local build_dir + build_dir="$(mktemp -d)" + # Copy Docker files, log properties, and the test app to the build dir + cp -v "${docker_dir}/"*.Dockerfile "${build_dir}" + cp -v "${docker_dir}/"*.properties "${build_dir}" + cp -rv "${SRC_DIR}/${BUILD_APP_PATH}" "${build_dir}" + # Pick a branch name for the built image + if [[ -n $KOKORO_JOB_NAME ]]; then + branch_name=$(echo "$KOKORO_JOB_NAME" | sed -E 's|^grpc/java/([^/]+)/.*|\1|') + else + branch_name='experimental' + fi + # Run Google Cloud Build + gcloud builds submit "${build_dir}" \ + --config "${docker_dir}/cloudbuild.yaml" \ + --substitutions "_SERVER_IMAGE_NAME=${SERVER_IMAGE_NAME},_CLIENT_IMAGE_NAME=${CLIENT_IMAGE_NAME},COMMIT_SHA=${GIT_COMMIT},BRANCH_NAME=${branch_name}" + # TODO(sergiitk): extra "cosmetic" tags for versioned branches, e.g. v1.34.x + # TODO(sergiitk): do this when adding support for custom configs per version +} + +####################################### +# Builds test app and its docker images unless they already exist +# Globals: +# SERVER_IMAGE_NAME: Test server Docker image name +# CLIENT_IMAGE_NAME: Test client Docker image name +# GIT_COMMIT: SHA-1 of git commit being built +# FORCE_IMAGE_BUILD +# Arguments: +# None +# Outputs: +# Writes the output to stdout, stderr +####################################### +build_docker_images_if_needed() { + # Check if images already exist + server_tags="$(gcloud_gcr_list_image_tags "${SERVER_IMAGE_NAME}" "${GIT_COMMIT}")" + printf "Server image: %s:%s\n" "${SERVER_IMAGE_NAME}" "${GIT_COMMIT}" + echo "${server_tags:-Server image not found}" + + client_tags="$(gcloud_gcr_list_image_tags "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}")" + printf "Client image: %s:%s\n" "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}" + echo "${client_tags:-Client image not found}" + + # Build if any of the images are missing, or FORCE_IMAGE_BUILD=1 + if [[ "${FORCE_IMAGE_BUILD}" == "1" || -z "${server_tags}" || -z "${client_tags}" ]]; then + build_java_test_app + build_test_app_docker_images + else + echo "Skipping Java test app build" + fi +} + +####################################### +# Executes the test case +# Globals: +# TEST_DRIVER_FLAGFILE: Relative path to test driver flagfile +# KUBE_CONTEXT: The name of kubectl context with GKE cluster access +# TEST_XML_OUTPUT_DIR: Output directory for the test xUnit XML report +# SERVER_IMAGE_NAME: Test server Docker image name +# CLIENT_IMAGE_NAME: Test client Docker image name +# GIT_COMMIT: SHA-1 of git commit being built +# Arguments: +# Test case name +# Outputs: +# Writes the output of test execution to stdout, stderr +# Test xUnit report to ${TEST_XML_OUTPUT_DIR}/${test_name}/sponge_log.xml +####################################### +run_test() { + # Test driver usage: + # https://github.com/grpc/grpc/tree/master/tools/run_tests/xds_k8s_test_driver#basic-usage + local test_name="${1:?Usage: run_test test_name}" + set -x + python -m "tests.${test_name}" \ + --flagfile="${TEST_DRIVER_FLAGFILE}" \ + --kube_context="${KUBE_CONTEXT}" \ + --server_image="${SERVER_IMAGE_NAME}:${GIT_COMMIT}" \ + --client_image="${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ + --xml_output_file="${TEST_XML_OUTPUT_DIR}/${test_name}/sponge_log.xml" \ + --force_cleanup + set +x +} + +####################################### +# Main function: provision software necessary to execute tests, and run them +# Globals: +# KOKORO_ARTIFACTS_DIR +# GITHUB_REPOSITORY_NAME +# SRC_DIR: Populated with absolute path to the source repo +# TEST_DRIVER_REPO_DIR: Populated with the path to the repo containing +# the test driver +# TEST_DRIVER_FULL_DIR: Populated with the path to the test driver source code +# TEST_DRIVER_FLAGFILE: Populated with relative path to test driver flagfile +# TEST_XML_OUTPUT_DIR: Populated with the path to test xUnit XML report +# GIT_ORIGIN_URL: Populated with the origin URL of git repo used for the build +# GIT_COMMIT: Populated with the SHA-1 of git commit being built +# GIT_COMMIT_SHORT: Populated with the short SHA-1 of git commit being built +# KUBE_CONTEXT: Populated with name of kubectl context with GKE cluster access +# Arguments: +# None +# Outputs: +# Writes the output of test execution to stdout, stderr +####################################### +main() { + local script_dir + script_dir="$(dirname "$0")" + + # Source the test driver from the master branch. + echo "Sourcing test driver install script from: ${TEST_DRIVER_INSTALL_SCRIPT_URL}" + source /dev/stdin <<< "$(curl -s "${TEST_DRIVER_INSTALL_SCRIPT_URL}")" + + activate_gke_cluster GKE_CLUSTER_PSM_BASIC + + set -x + if [[ -n "${KOKORO_ARTIFACTS_DIR}" ]]; then + kokoro_setup_test_driver "${GITHUB_REPOSITORY_NAME}" + else + local_setup_test_driver "${script_dir}" + fi + build_docker_images_if_needed + # Run tests + cd "${TEST_DRIVER_FULL_DIR}" + run_test api_listener_test +} + +main "$@" From 431fb0255fbed3f46ff3aca15ab37273c6143a47 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Fri, 4 Feb 2022 22:12:03 -0800 Subject: [PATCH 37/68] core/netty: infinite local-only transparent retry for netty (#8878) In core, add a new enum element to `RpcProgress` for the case that the stream is closed even before anything leaves the client. `RetriableStream` will do unlimited transparent retry for this type of `RpcProgress` since they are local-only. In netty, call `tranportReportStatus()` for pending streams on failure. Also fixes #8394 --- .../grpc/internal/ClientStreamListener.java | 8 ++- .../io/grpc/internal/RetriableStream.java | 9 ++-- .../io/grpc/internal/SubchannelChannel.java | 2 +- .../io/grpc/internal/RetriableStreamTest.java | 52 ++++++++++++++++++- .../grpc/testing/integration/RetryTest.java | 50 +++++++++++++----- .../io/grpc/netty/NettyClientHandler.java | 20 ++++--- .../java/io/grpc/netty/NettyClientStream.java | 13 +++-- .../io/grpc/netty/NettyClientHandlerTest.java | 17 +++++- 8 files changed, 139 insertions(+), 32 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/ClientStreamListener.java b/core/src/main/java/io/grpc/internal/ClientStreamListener.java index dfb521f40a3..8db1fbe445f 100644 --- a/core/src/main/java/io/grpc/internal/ClientStreamListener.java +++ b/core/src/main/java/io/grpc/internal/ClientStreamListener.java @@ -57,12 +57,16 @@ enum RpcProgress { */ PROCESSED, /** - * The RPC is not processed by the server's application logic. + * The stream on the wire is created but not processed by the server's application logic. */ REFUSED, /** * The RPC is dropped (by load balancer). */ - DROPPED + DROPPED, + /** + * The stream is closed even before anything leaves the client. + */ + MISCARRIED } } diff --git a/core/src/main/java/io/grpc/internal/RetriableStream.java b/core/src/main/java/io/grpc/internal/RetriableStream.java index 4afdb3f750e..8451ea6b2d7 100644 --- a/core/src/main/java/io/grpc/internal/RetriableStream.java +++ b/core/src/main/java/io/grpc/internal/RetriableStream.java @@ -101,7 +101,9 @@ public void uncaughtException(Thread t, Throwable e) { false, 0); /** - * Either transparent retry happened or reached server's application logic. + * Either non-local transparent retry happened or reached server's application logic. + * + *

Note that local-only transparent retries are unlimited. */ private final AtomicBoolean noMoreTransparentRetry = new AtomicBoolean(); @@ -851,8 +853,9 @@ public void run() { } if (state.winningSubstream == null) { - if (rpcProgress == RpcProgress.REFUSED - && noMoreTransparentRetry.compareAndSet(false, true)) { + if (rpcProgress == RpcProgress.MISCARRIED + || (rpcProgress == RpcProgress.REFUSED + && noMoreTransparentRetry.compareAndSet(false, true))) { // transparent retry final Substream newSubstream = createSubstream(substream.previousAttemptCount, true); if (isHedging) { diff --git a/core/src/main/java/io/grpc/internal/SubchannelChannel.java b/core/src/main/java/io/grpc/internal/SubchannelChannel.java index a1d454ed2fb..773dcb99dd7 100644 --- a/core/src/main/java/io/grpc/internal/SubchannelChannel.java +++ b/core/src/main/java/io/grpc/internal/SubchannelChannel.java @@ -43,7 +43,7 @@ final class SubchannelChannel extends Channel { Status.UNAVAILABLE.withDescription( "wait-for-ready RPC is not supported on Subchannel.asChannel()"); private static final FailingClientTransport notReadyTransport = - new FailingClientTransport(NOT_READY_ERROR, RpcProgress.REFUSED); + new FailingClientTransport(NOT_READY_ERROR, RpcProgress.MISCARRIED); private final InternalSubchannel subchannel; private final Executor executor; private final ScheduledExecutorService deadlineCancellationExecutor; diff --git a/core/src/test/java/io/grpc/internal/RetriableStreamTest.java b/core/src/test/java/io/grpc/internal/RetriableStreamTest.java index 8b851573b21..fe147a843a8 100644 --- a/core/src/test/java/io/grpc/internal/RetriableStreamTest.java +++ b/core/src/test/java/io/grpc/internal/RetriableStreamTest.java @@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat; import static io.grpc.internal.ClientStreamListener.RpcProgress.DROPPED; +import static io.grpc.internal.ClientStreamListener.RpcProgress.MISCARRIED; import static io.grpc.internal.ClientStreamListener.RpcProgress.PROCESSED; import static io.grpc.internal.ClientStreamListener.RpcProgress.REFUSED; import static io.grpc.internal.RetriableStream.GRPC_PREVIOUS_RPC_ATTEMPTS; @@ -1611,7 +1612,7 @@ public void throttleStream_Succeed() { } @Test - public void transparentRetry() { + public void transparentRetry_onlyOnceOnRefused() { ClientStream mockStream1 = mock(ClientStream.class); ClientStream mockStream2 = mock(ClientStream.class); ClientStream mockStream3 = mock(ClientStream.class); @@ -1661,6 +1662,55 @@ public void transparentRetry() { assertEquals(0, fakeClock.numPendingTasks()); } + @Test + public void transparentRetry_unlimitedTimesOnMiscarried() { + ClientStream mockStream1 = mock(ClientStream.class); + ClientStream mockStream2 = mock(ClientStream.class); + ClientStream mockStream3 = mock(ClientStream.class); + InOrder inOrder = inOrder( + retriableStreamRecorder, + mockStream1, mockStream2, mockStream3); + + // start + doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0); + retriableStream.start(masterListener); + + inOrder.verify(retriableStreamRecorder).newSubstream(0); + ArgumentCaptor sublistenerCaptor1 = + ArgumentCaptor.forClass(ClientStreamListener.class); + inOrder.verify(mockStream1).start(sublistenerCaptor1.capture()); + inOrder.verify(mockStream1).isReady(); + inOrder.verifyNoMoreInteractions(); + + // transparent retry + doReturn(mockStream2).when(retriableStreamRecorder).newSubstream(0); + sublistenerCaptor1.getValue() + .closed(Status.fromCode(NON_RETRIABLE_STATUS_CODE), MISCARRIED, new Metadata()); + + inOrder.verify(retriableStreamRecorder).newSubstream(0); + ArgumentCaptor sublistenerCaptor2 = + ArgumentCaptor.forClass(ClientStreamListener.class); + inOrder.verify(mockStream2).start(sublistenerCaptor2.capture()); + inOrder.verify(mockStream2).isReady(); + inOrder.verifyNoMoreInteractions(); + verify(retriableStreamRecorder, never()).postCommit(); + assertEquals(0, fakeClock.numPendingTasks()); + + // more transparent retry + doReturn(mockStream3).when(retriableStreamRecorder).newSubstream(0); + sublistenerCaptor2.getValue() + .closed(Status.fromCode(NON_RETRIABLE_STATUS_CODE), MISCARRIED, new Metadata()); + + inOrder.verify(retriableStreamRecorder).newSubstream(0); + ArgumentCaptor sublistenerCaptor3 = + ArgumentCaptor.forClass(ClientStreamListener.class); + inOrder.verify(mockStream3).start(sublistenerCaptor3.capture()); + inOrder.verify(mockStream3).isReady(); + inOrder.verifyNoMoreInteractions(); + verify(retriableStreamRecorder, never()).postCommit(); + assertEquals(0, fakeClock.numPendingTasks()); + } + @Test public void normalRetry_thenNoTransparentRetry_butNormalRetry() { ClientStream mockStream1 = mock(ClientStream.class); 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 80cf83b0939..229d873571a 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 @@ -74,7 +74,6 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -459,33 +458,41 @@ public long nanoTime() { assertRetryStatsRecorded(0, 0, 0); } - @Ignore("flaky because old transportReportStatus() is not completely migrated yet") @Test public void transparentRetryStatsRecorded() throws Exception { startNewServer(); createNewChannel(); - final AtomicBoolean transparentRetryTriggered = new AtomicBoolean(); + final AtomicBoolean originalAttemptFailed = new AtomicBoolean(); class TransparentRetryTriggeringTracer extends ClientStreamTracer { @Override public void streamCreated(Attributes transportAttrs, Metadata metadata) { - if (transparentRetryTriggered.get()) { + if (originalAttemptFailed.get()) { return; } + // Send GOAWAY from server. The client may either receive GOAWAY or create the underlying + // netty stream and write headers first, even we await server termination as below. + // In the latter case, we rerun the test. We can also call localServer.shutdown() to trigger + // GOAWAY, but it takes a lot longer time to gracefully shut down. localServer.shutdownNow(); + try { + localServer.awaitTermination(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new AssertionError(e); + } } @Override public void streamClosed(Status status) { - if (transparentRetryTriggered.get()) { + if (originalAttemptFailed.get()) { return; } - transparentRetryTriggered.set(true); + originalAttemptFailed.set(true); try { startNewServer(); channel.resetConnectBackoff(); - channel.getState(true); } catch (Exception e) { throw new AssertionError("local server can not be restarted", e); } @@ -502,13 +509,28 @@ public ClientStreamTracer newClientStreamTracer(StreamInfo info, Metadata header CallOptions callOptions = CallOptions.DEFAULT .withWaitForReady() .withStreamTracerFactory(new TransparentRetryTracerFactory()); - ClientCall call = channel.newCall(clientStreamingMethod, callOptions); - call.start(mockCallListener, new Metadata()); - assertRpcStartedRecorded(); - assertRpcStatusRecorded(Code.UNAVAILABLE, 0, 0); - assertRpcStartedRecorded(); - call.cancel("cancel", null); - assertRpcStatusRecorded(Code.CANCELLED, 0, 0); + while (true) { + ClientCall call = channel.newCall(clientStreamingMethod, callOptions); + call.start(mockCallListener, new Metadata()); + assertRpcStartedRecorded(); // original attempt + MetricsRecord record = clientStatsRecorder.pollRecord(5, SECONDS); + assertThat(record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_FINISHED_COUNT)) + .isEqualTo(1); + TagValue statusTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_STATUS); + if (statusTag.asString().equals(Code.UNAVAILABLE.toString())) { + break; + } else { + // Due to race condition, GOAWAY is not received/processed before the stream is closed due + // to connection error. Rerun the test. + assertThat(statusTag.asString()).isEqualTo(Code.UNKNOWN.toString()); + assertRetryStatsRecorded(0, 0, 0); + originalAttemptFailed.set(false); + } + } + assertRpcStartedRecorded(); // retry attempt + ServerCall serverCall = serverCalls.poll(5, SECONDS); + serverCall.close(Status.INVALID_ARGUMENT, new Metadata()); + assertRpcStatusRecorded(Code.INVALID_ARGUMENT, 0, 0); assertRetryStatsRecorded(0, 1, 0); } } diff --git a/netty/src/main/java/io/grpc/netty/NettyClientHandler.java b/netty/src/main/java/io/grpc/netty/NettyClientHandler.java index 6dde8c825ef..80d11e54859 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientHandler.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientHandler.java @@ -535,7 +535,7 @@ private void createStream(CreateStreamCommand command, ChannelPromise promise) // The connection is going away (it is really the GOAWAY case), // just terminate the stream now. command.stream().transportReportStatus( - lifecycleManager.getShutdownStatus(), RpcProgress.REFUSED, true, new Metadata()); + lifecycleManager.getShutdownStatus(), RpcProgress.MISCARRIED, true, new Metadata()); promise.setFailure(lifecycleManager.getShutdownThrowable()); return; } @@ -576,7 +576,7 @@ private void createStream(CreateStreamCommand command, ChannelPromise promise) // This should only be reachable during onGoAwayReceived, as otherwise // getShutdownThrowable() != null command.stream().setNonExistent(); - command.stream().transportReportStatus(s, RpcProgress.REFUSED, true, new Metadata()); + command.stream().transportReportStatus(s, RpcProgress.MISCARRIED, true, new Metadata()); promise.setFailure(s.asRuntimeException()); return; } @@ -635,18 +635,24 @@ public void operationComplete(ChannelFuture future) throws Exception { // Just forward on the success status to the original promise. promise.setSuccess(); } else { - final Throwable cause = future.cause(); + Throwable cause = future.cause(); if (cause instanceof StreamBufferingEncoder.Http2GoAwayException) { StreamBufferingEncoder.Http2GoAwayException e = (StreamBufferingEncoder.Http2GoAwayException) cause; Status status = statusFromH2Error( Status.Code.UNAVAILABLE, "GOAWAY closed buffered stream", e.errorCode(), e.debugData()); - stream.transportReportStatus(status, RpcProgress.REFUSED, true, new Metadata()); - promise.setFailure(status.asRuntimeException()); - } else { - promise.setFailure(cause); + cause = status.asRuntimeException(); + stream.transportReportStatus(status, RpcProgress.MISCARRIED, true, new Metadata()); + } else if (cause instanceof StreamBufferingEncoder.Http2ChannelClosedException) { + Status status = lifecycleManager.getShutdownStatus(); + if (status == null) { + status = Status.UNAVAILABLE.withCause(cause) + .withDescription("Connection closed while stream is buffered"); + } + stream.transportReportStatus(status, RpcProgress.MISCARRIED, true, new Metadata()); } + promise.setFailure(cause); } } }); diff --git a/netty/src/main/java/io/grpc/netty/NettyClientStream.java b/netty/src/main/java/io/grpc/netty/NettyClientStream.java index 0e7e69635a8..9c6c12cc0f6 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientStream.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientStream.java @@ -31,6 +31,7 @@ import io.grpc.MethodDescriptor; import io.grpc.Status; import io.grpc.internal.AbstractClientStream; +import io.grpc.internal.ClientStreamListener.RpcProgress; import io.grpc.internal.Http2ClientStreamTransportState; import io.grpc.internal.StatsTraceContext; import io.grpc.internal.TransportTracer; @@ -152,12 +153,18 @@ public void operationComplete(ChannelFuture future) throws Exception { // Stream creation failed. Close the stream if not already closed. // When the channel is shutdown, the lifecycle manager has a better view of the failure, // especially before negotiation completes (because the negotiator commonly doesn't - // receive the execeptionCaught because NettyClientHandler does not propagate it). + // receive the exceptionCaught because NettyClientHandler does not propagate it). Status s = transportState().handler.getLifecycleManager().getShutdownStatus(); if (s == null) { s = transportState().statusFromFailedFuture(future); } - transportState().transportReportStatus(s, true, new Metadata()); + if (transportState().isNonExistent()) { + transportState().transportReportStatus( + s, RpcProgress.MISCARRIED, true, new Metadata()); + } else { + transportState().transportReportStatus( + s, RpcProgress.PROCESSED, true, new Metadata()); + } } } }; @@ -268,7 +275,7 @@ void setNonExistent() { } boolean isNonExistent() { - return this.id == NON_EXISTENT_ID; + return this.id == NON_EXISTENT_ID || this.id == 0; } /** diff --git a/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java b/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java index d0d48fe9b48..d47942858a3 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java @@ -18,6 +18,7 @@ import static com.google.common.base.Charsets.UTF_8; import static com.google.common.truth.Truth.assertThat; +import static io.grpc.internal.ClientStreamListener.RpcProgress.MISCARRIED; import static io.grpc.internal.ClientStreamListener.RpcProgress.PROCESSED; import static io.grpc.internal.ClientStreamListener.RpcProgress.REFUSED; import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE; @@ -378,7 +379,7 @@ public void receivedAbruptGoAwayShouldFailRacingQueuedStreamid() throws Exceptio channelRead(goAwayFrame(0, 8 /* Cancel */, Unpooled.copiedBuffer("this is a test", UTF_8))); ArgumentCaptor captor = ArgumentCaptor.forClass(Status.class); - verify(streamListener).closed(captor.capture(), same(REFUSED), + verify(streamListener).closed(captor.capture(), same(MISCARRIED), ArgumentMatchers.notNull()); assertEquals(Status.UNAVAILABLE.getCode(), captor.getValue().getCode()); assertEquals( @@ -471,6 +472,20 @@ public void receivedGoAwayShouldFailBufferedStreams() throws Exception { status.getDescription()); } + @Test + public void channelClosureShouldFailBufferedStreams() throws Exception { + receiveMaxConcurrentStreams(0); + + ChannelFuture future = enqueue(newCreateStreamCommand(grpcHeaders, streamTransportState)); + channel().pipeline().fireChannelInactive(); + + assertTrue(future.isDone()); + assertFalse(future.isSuccess()); + ArgumentCaptor captor = ArgumentCaptor.forClass(Status.class); + verify(streamListener).closed(captor.capture(), same(MISCARRIED), ArgumentMatchers.notNull()); + assertEquals(Status.UNAVAILABLE.getCode(), captor.getValue().getCode()); + } + @Test public void receivedGoAwayShouldFailNewStreams() throws Exception { // Read a GOAWAY that indicates our stream was never processed by the server. From f0a7132fbe57e2b4b5fcf3bd1400bb9ac51cd48b Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Mon, 7 Feb 2022 11:43:17 -0800 Subject: [PATCH 38/68] xds: fix the validation code to accept new-style CertificateProviderPluginInstance wherever used (#8892) --- .../internal/sds/CommonTlsContextUtil.java | 21 +++++- .../ClientSslContextProviderFactoryTest.java | 66 +++++++++++++++++++ .../ServerSslContextProviderFactoryTest.java | 37 +++++++++++ 3 files changed, 121 insertions(+), 3 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/CommonTlsContextUtil.java b/xds/src/main/java/io/grpc/xds/internal/sds/CommonTlsContextUtil.java index 0c28c79ee22..df09e8bb247 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/CommonTlsContextUtil.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/CommonTlsContextUtil.java @@ -26,9 +26,11 @@ public final class CommonTlsContextUtil { private CommonTlsContextUtil() {} static boolean hasCertProviderInstance(CommonTlsContext commonTlsContext) { - return commonTlsContext != null - && (commonTlsContext.hasTlsCertificateCertificateProviderInstance() - || hasCertProviderValidationContext(commonTlsContext)); + if (commonTlsContext == null) { + return false; + } + return hasIdentityCertificateProviderInstance(commonTlsContext) + || hasCertProviderValidationContext(commonTlsContext); } private static boolean hasCertProviderValidationContext(CommonTlsContext commonTlsContext) { @@ -37,6 +39,19 @@ private static boolean hasCertProviderValidationContext(CommonTlsContext commonT commonTlsContext.getCombinedValidationContext(); return combinedCertificateValidationContext.hasValidationContextCertificateProviderInstance(); } + return hasValidationProviderInstance(commonTlsContext); + } + + private static boolean hasIdentityCertificateProviderInstance(CommonTlsContext commonTlsContext) { + return commonTlsContext.hasTlsCertificateProviderInstance() + || commonTlsContext.hasTlsCertificateCertificateProviderInstance(); + } + + private static boolean hasValidationProviderInstance(CommonTlsContext commonTlsContext) { + if (commonTlsContext.hasValidationContext() && commonTlsContext.getValidationContext() + .hasCaCertificateProviderInstance()) { + return true; + } return commonTlsContext.hasValidationContextCertificateProviderInstance(); } diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/ClientSslContextProviderFactoryTest.java b/xds/src/test/java/io/grpc/xds/internal/sds/ClientSslContextProviderFactoryTest.java index 06a3198b263..adc96a36336 100644 --- a/xds/src/test/java/io/grpc/xds/internal/sds/ClientSslContextProviderFactoryTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/sds/ClientSslContextProviderFactoryTest.java @@ -208,6 +208,72 @@ public void createCertProviderClientSslContextProvider_2providers() verifyWatcher(sslContextProvider, watcherCaptor[1]); } + @Test + public void createNewCertProviderClientSslContextProvider_withSans() { + final CertificateProvider.DistributorWatcher[] watcherCaptor = + new CertificateProvider.DistributorWatcher[2]; + createAndRegisterProviderProvider(certificateProviderRegistry, watcherCaptor, "testca", 0); + createAndRegisterProviderProvider( + certificateProviderRegistry, watcherCaptor, "file_watcher", 1); + + CertificateValidationContext staticCertValidationContext = + CertificateValidationContext.newBuilder() + .addAllMatchSubjectAltNames( + ImmutableSet.of( + StringMatcher.newBuilder().setExact("foo").build(), + StringMatcher.newBuilder().setExact("bar").build())) + .build(); + UpstreamTlsContext upstreamTlsContext = + CommonTlsContextTestsUtil.buildNewUpstreamTlsContextForCertProviderInstance( + "gcp_id", + "cert-default", + "file_provider", + "root-default", + /* alpnProtocols= */ null, + staticCertValidationContext); + + Bootstrapper.BootstrapInfo bootstrapInfo = CommonBootstrapperTestUtils.getTestBootstrapInfo(); + clientSslContextProviderFactory = + new ClientSslContextProviderFactory( + bootstrapInfo, certProviderClientSslContextProviderFactory); + SslContextProvider sslContextProvider = + clientSslContextProviderFactory.create(upstreamTlsContext); + assertThat(sslContextProvider).isInstanceOf(CertProviderClientSslContextProvider.class); + verifyWatcher(sslContextProvider, watcherCaptor[0]); + verifyWatcher(sslContextProvider, watcherCaptor[1]); + } + + @Test + public void createNewCertProviderClientSslContextProvider_onlyRootCert() { + final CertificateProvider.DistributorWatcher[] watcherCaptor = + new CertificateProvider.DistributorWatcher[1]; + createAndRegisterProviderProvider(certificateProviderRegistry, watcherCaptor, "testca", 0); + CertificateValidationContext staticCertValidationContext = + CertificateValidationContext.newBuilder() + .addAllMatchSubjectAltNames( + ImmutableSet.of( + StringMatcher.newBuilder().setExact("foo").build(), + StringMatcher.newBuilder().setExact("bar").build())) + .build(); + UpstreamTlsContext upstreamTlsContext = + CommonTlsContextTestsUtil.buildNewUpstreamTlsContextForCertProviderInstance( + /* certInstanceName= */ null, + /* certName= */ null, + "gcp_id", + "root-default", + /* alpnProtocols= */ null, + staticCertValidationContext); + + Bootstrapper.BootstrapInfo bootstrapInfo = CommonBootstrapperTestUtils.getTestBootstrapInfo(); + clientSslContextProviderFactory = + new ClientSslContextProviderFactory( + bootstrapInfo, certProviderClientSslContextProviderFactory); + SslContextProvider sslContextProvider = + clientSslContextProviderFactory.create(upstreamTlsContext); + assertThat(sslContextProvider).isInstanceOf(CertProviderClientSslContextProvider.class); + verifyWatcher(sslContextProvider, watcherCaptor[0]); + } + @Test public void createNullCommonTlsContext_exception() throws IOException { clientSslContextProviderFactory = diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/ServerSslContextProviderFactoryTest.java b/xds/src/test/java/io/grpc/xds/internal/sds/ServerSslContextProviderFactoryTest.java index a4bab618a36..7623b614001 100644 --- a/xds/src/test/java/io/grpc/xds/internal/sds/ServerSslContextProviderFactoryTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/sds/ServerSslContextProviderFactoryTest.java @@ -206,4 +206,41 @@ public void createCertProviderServerSslContextProvider_2providers() verifyWatcher(sslContextProvider, watcherCaptor[0]); verifyWatcher(sslContextProvider, watcherCaptor[1]); } + + @Test + public void createNewCertProviderServerSslContextProvider_withSans() + throws XdsInitializationException { + final CertificateProvider.DistributorWatcher[] watcherCaptor = + new CertificateProvider.DistributorWatcher[2]; + createAndRegisterProviderProvider(certificateProviderRegistry, watcherCaptor, "testca", 0); + createAndRegisterProviderProvider( + certificateProviderRegistry, watcherCaptor, "file_watcher", 1); + CertificateValidationContext staticCertValidationContext = + CertificateValidationContext.newBuilder() + .addAllMatchSubjectAltNames( + ImmutableSet.of( + StringMatcher.newBuilder().setExact("foo").build(), + StringMatcher.newBuilder().setExact("bar").build())) + .build(); + + DownstreamTlsContext downstreamTlsContext = + CommonTlsContextTestsUtil.buildNewDownstreamTlsContextForCertProviderInstance( + "gcp_id", + "cert-default", + "file_provider", + "root-default", + /* alpnProtocols= */ null, + staticCertValidationContext, + /* requireClientCert= */ true); + + Bootstrapper.BootstrapInfo bootstrapInfo = CommonBootstrapperTestUtils.getTestBootstrapInfo(); + serverSslContextProviderFactory = + new ServerSslContextProviderFactory( + bootstrapInfo, certProviderServerSslContextProviderFactory); + SslContextProvider sslContextProvider = + serverSslContextProviderFactory.create(downstreamTlsContext); + assertThat(sslContextProvider).isInstanceOf(CertProviderServerSslContextProvider.class); + verifyWatcher(sslContextProvider, watcherCaptor[0]); + verifyWatcher(sslContextProvider, watcherCaptor[1]); + } } From 7a9ceacafcf2488a18369c75d5a79b6e1cc7d60b Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Mon, 7 Feb 2022 13:11:04 -0800 Subject: [PATCH 39/68] benchmarks: Modernize client to use target and credentials This allows using LoadClient with xDS. The changes to SocketAddressValidator are a bit hacky, but we really don't care about cleanliness there. Eventually on client-side, we should be deleting the unix special case entirely, as we'll have a unix name resolver. Fixes #8877 --- benchmarks/build.gradle | 1 + .../benchmarks/SocketAddressValidator.java | 15 +++ .../java/io/grpc/benchmarks/Transport.java | 4 +- .../main/java/io/grpc/benchmarks/Utils.java | 98 ++++++++++--------- .../io/grpc/benchmarks/driver/LoadClient.java | 2 +- .../benchmarks/qps/ClientConfiguration.java | 18 +--- .../benchmarks/qps/ServerConfiguration.java | 2 +- 7 files changed, 74 insertions(+), 66 deletions(-) diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index 48b9030714f..c8a8669dd1f 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -27,6 +27,7 @@ dependencies { project(':grpc-stub'), project(':grpc-protobuf'), project(':grpc-testing'), + project(path: ':grpc-xds', configuration: 'shadow'), libraries.hdrhistogram, libraries.netty_tcnative, libraries.netty_epoll, diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/SocketAddressValidator.java b/benchmarks/src/main/java/io/grpc/benchmarks/SocketAddressValidator.java index 6c6d597cd5e..c35cc7e5c38 100644 --- a/benchmarks/src/main/java/io/grpc/benchmarks/SocketAddressValidator.java +++ b/benchmarks/src/main/java/io/grpc/benchmarks/SocketAddressValidator.java @@ -33,6 +33,11 @@ public interface SocketAddressValidator { public boolean isValidSocketAddress(SocketAddress address) { return address instanceof InetSocketAddress; } + + @Override + public boolean isValidSocketAddress(String address) { + return !address.startsWith("unix://"); + } }; /** @@ -43,10 +48,20 @@ public boolean isValidSocketAddress(SocketAddress address) { public boolean isValidSocketAddress(SocketAddress address) { return "DomainSocketAddress".equals(address.getClass().getSimpleName()); } + + @Override + public boolean isValidSocketAddress(String address) { + return address.startsWith("unix://"); + } }; /** * Returns {@code true} if the given address is valid. */ boolean isValidSocketAddress(SocketAddress address); + + /** + * Returns {@code true} if the given address is valid. + */ + boolean isValidSocketAddress(String address); } diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/Transport.java b/benchmarks/src/main/java/io/grpc/benchmarks/Transport.java index 0097bd9dd20..820b3ac1968 100644 --- a/benchmarks/src/main/java/io/grpc/benchmarks/Transport.java +++ b/benchmarks/src/main/java/io/grpc/benchmarks/Transport.java @@ -16,8 +16,6 @@ package io.grpc.benchmarks; -import java.net.SocketAddress; - /** * All of the supported transports. */ @@ -49,7 +47,7 @@ public enum Transport { * * @throws IllegalArgumentException if the given address is invalid for this transport. */ - public void validateSocketAddress(SocketAddress address) { + public void validateSocketAddress(String address) { if (!socketAddressValidator.isValidSocketAddress(address)) { throw new IllegalArgumentException( "Invalid address " + address + " for transport " + this); diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/Utils.java b/benchmarks/src/main/java/io/grpc/benchmarks/Utils.java index 9a3d1f6bc24..8087afbf406 100644 --- a/benchmarks/src/main/java/io/grpc/benchmarks/Utils.java +++ b/benchmarks/src/main/java/io/grpc/benchmarks/Utils.java @@ -18,20 +18,22 @@ import static java.util.concurrent.ForkJoinPool.defaultForkJoinWorkerThreadFactory; +import com.google.common.base.Preconditions; import com.google.common.util.concurrent.UncaughtExceptionHandlers; import com.google.protobuf.ByteString; +import io.grpc.ChannelCredentials; +import io.grpc.InsecureChannelCredentials; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.Status; +import io.grpc.TlsChannelCredentials; import io.grpc.benchmarks.proto.Messages; import io.grpc.benchmarks.proto.Messages.Payload; import io.grpc.benchmarks.proto.Messages.SimpleRequest; import io.grpc.benchmarks.proto.Messages.SimpleResponse; -import io.grpc.internal.testing.TestUtils; -import io.grpc.netty.GrpcSslContexts; import io.grpc.netty.NettyChannelBuilder; import io.grpc.okhttp.OkHttpChannelBuilder; -import io.grpc.okhttp.internal.Platform; +import io.grpc.testing.TlsTesting; import io.netty.channel.epoll.EpollDomainSocketChannel; import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.epoll.EpollSocketChannel; @@ -79,15 +81,10 @@ public static boolean parseBoolean(String value) { /** * Parse a {@link SocketAddress} from the given string. */ - public static SocketAddress parseSocketAddress(String value) { + public static SocketAddress parseServerSocketAddress(String value) { if (value.startsWith(UNIX_DOMAIN_SOCKET_PREFIX)) { - // Unix Domain Socket address. - // Create the underlying file for the Unix Domain Socket. - String filePath = value.substring(UNIX_DOMAIN_SOCKET_PREFIX.length()); - File file = new File(filePath); - if (!file.isAbsolute()) { - throw new IllegalArgumentException("File path must be absolute: " + filePath); - } + DomainSocketAddress domainAddress = parseUnixSocketAddress(value); + File file = new File(domainAddress.path()); try { if (file.createNewFile()) { // If this application created the file, delete it when the application exits. @@ -96,8 +93,7 @@ public static SocketAddress parseSocketAddress(String value) { } catch (IOException ex) { throw new RuntimeException(ex); } - // Create the SocketAddress referencing the file. - return new DomainSocketAddress(file); + return domainAddress; } else { // Standard TCP/IP address. String[] parts = value.split(":", 2); @@ -111,37 +107,24 @@ public static SocketAddress parseSocketAddress(String value) { } } - private static OkHttpChannelBuilder newOkHttpClientChannel( - SocketAddress address, boolean tls, boolean testca) { - InetSocketAddress addr = (InetSocketAddress) address; - OkHttpChannelBuilder builder = - OkHttpChannelBuilder.forAddress(addr.getHostName(), addr.getPort()); - if (!tls) { - builder.usePlaintext(); - } else if (testca) { - try { - builder.sslSocketFactory(TestUtils.newSslSocketFactoryForCa( - Platform.get().getProvider(), - TestUtils.loadCert("ca.pem"))); - } catch (Exception e) { - throw new RuntimeException(e); - } + private static DomainSocketAddress parseUnixSocketAddress(String value) { + Preconditions.checkArgument( + value.startsWith(UNIX_DOMAIN_SOCKET_PREFIX), + "Must start with %s: %s", UNIX_DOMAIN_SOCKET_PREFIX, value); + // Unix Domain Socket address. + // Create the underlying file for the Unix Domain Socket. + String filePath = value.substring(UNIX_DOMAIN_SOCKET_PREFIX.length()); + File file = new File(filePath); + if (!file.isAbsolute()) { + throw new IllegalArgumentException("File path must be absolute: " + filePath); } - return builder; + // Create the SocketAddress referencing the file. + return new DomainSocketAddress(file); } - private static NettyChannelBuilder newNettyClientChannel(Transport transport, - SocketAddress address, boolean tls, boolean testca, int flowControlWindow) - throws IOException { - NettyChannelBuilder builder = - NettyChannelBuilder.forAddress(address).flowControlWindow(flowControlWindow); - if (!tls) { - builder.usePlaintext(); - } else if (testca) { - File cert = TestUtils.loadCert("ca.pem"); - builder.sslContext(GrpcSslContexts.forClient().trustManager(cert).build()); - } - + private static NettyChannelBuilder configureNetty( + NettyChannelBuilder builder, Transport transport, int flowControlWindow) { + builder.flowControlWindow(flowControlWindow); DefaultThreadFactory tf = new DefaultThreadFactory("client-elg-", true /*daemon */); switch (transport) { case NETTY_NIO: @@ -194,17 +177,38 @@ public ForkJoinWorkerThread newThread(ForkJoinPool pool) { /** * Create a {@link ManagedChannel} for the given parameters. */ - public static ManagedChannel newClientChannel(Transport transport, SocketAddress address, + public static ManagedChannel newClientChannel(Transport transport, String target, boolean tls, boolean testca, @Nullable String authorityOverride, int flowControlWindow, boolean directExecutor) { + ChannelCredentials credentials; + if (tls) { + if (testca) { + try { + credentials = TlsChannelCredentials.newBuilder() + .trustManager(TlsTesting.loadCert("ca.pem")) + .build(); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } else { + credentials = TlsChannelCredentials.create(); + } + } else { + credentials = InsecureChannelCredentials.create(); + } ManagedChannelBuilder builder; if (transport == Transport.OK_HTTP) { - builder = newOkHttpClientChannel(address, tls, testca); + builder = OkHttpChannelBuilder.forTarget(target, credentials) + .flowControlWindow(flowControlWindow); } else { - try { - builder = newNettyClientChannel(transport, address, tls, testca, flowControlWindow); - } catch (Exception e) { - throw new RuntimeException(e); + if (target.startsWith(UNIX_DOMAIN_SOCKET_PREFIX)) { + builder = configureNetty( + NettyChannelBuilder.forAddress(parseUnixSocketAddress(target), credentials), + transport, flowControlWindow); + } else { + builder = configureNetty( + NettyChannelBuilder.forTarget(target, credentials), + transport, flowControlWindow); } } if (authorityOverride != null) { diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/driver/LoadClient.java b/benchmarks/src/main/java/io/grpc/benchmarks/driver/LoadClient.java index c25fb38fdec..c9a5812b6a6 100644 --- a/benchmarks/src/main/java/io/grpc/benchmarks/driver/LoadClient.java +++ b/benchmarks/src/main/java/io/grpc/benchmarks/driver/LoadClient.java @@ -82,7 +82,7 @@ class LoadClient { channels[i] = Utils.newClientChannel( Epoll.isAvailable() ? Transport.NETTY_EPOLL : Transport.NETTY_NIO, - Utils.parseSocketAddress(config.getServerTargets(i % config.getServerTargetsCount())), + config.getServerTargets(i % config.getServerTargetsCount()), config.hasSecurityParams(), config.hasSecurityParams() && config.getSecurityParams().getUseTestCa(), config.hasSecurityParams() diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/qps/ClientConfiguration.java b/benchmarks/src/main/java/io/grpc/benchmarks/qps/ClientConfiguration.java index f15779bdabc..3bafdb836ba 100644 --- a/benchmarks/src/main/java/io/grpc/benchmarks/qps/ClientConfiguration.java +++ b/benchmarks/src/main/java/io/grpc/benchmarks/qps/ClientConfiguration.java @@ -28,8 +28,6 @@ import io.grpc.benchmarks.proto.Messages.PayloadType; import io.grpc.internal.testing.TestUtils; import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.SocketAddress; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; @@ -47,7 +45,7 @@ public class ClientConfiguration implements Configuration { String authorityOverride = TestUtils.TEST_SERVER_HOST; boolean useDefaultCiphers; boolean directExecutor; - SocketAddress address; + String target; int channels = 4; int outstandingRpcsPerChannel = 10; int serverPayload; @@ -66,7 +64,7 @@ private ClientConfiguration() { } public ManagedChannel newChannel() throws IOException { - return Utils.newClientChannel(transport, address, tls, testca, authorityOverride, + return Utils.newClientChannel(transport, target, tls, testca, authorityOverride, flowControlWindow, directExecutor); } @@ -106,18 +104,10 @@ protected ClientConfiguration build0(ClientConfiguration config) { throw new IllegalArgumentException( "Transport " + config.transport.name().toLowerCase() + " does not support TLS."); } - - if (config.transport != Transport.OK_HTTP - && config.testca && config.address instanceof InetSocketAddress) { - // Override the socket address with the host from the testca. - InetSocketAddress address = (InetSocketAddress) config.address; - config.address = TestUtils.testServerAddress(address.getHostName(), - address.getPort()); - } } // Verify that the address type is correct for the transport type. - config.transport.validateSocketAddress(config.address); + config.transport.validateSocketAddress(config.target); return config; } @@ -136,7 +126,7 @@ enum ClientParam implements AbstractConfigurationBuilder.Param { + "(unix:///path/to/file), depending on the transport selected.", null, true) { @Override protected void setClientValue(ClientConfiguration config, String value) { - config.address = Utils.parseSocketAddress(value); + config.target = value; } }, CHANNELS("INT", "Number of Channels.", "" + DEFAULT.channels) { diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/qps/ServerConfiguration.java b/benchmarks/src/main/java/io/grpc/benchmarks/qps/ServerConfiguration.java index a8a097303f1..915c1da75eb 100644 --- a/benchmarks/src/main/java/io/grpc/benchmarks/qps/ServerConfiguration.java +++ b/benchmarks/src/main/java/io/grpc/benchmarks/qps/ServerConfiguration.java @@ -142,7 +142,7 @@ enum ServerParam implements AbstractConfigurationBuilder.Param { + "(unix:///path/to/file), depending on the transport selected.", null, true) { @Override protected void setServerValue(ServerConfiguration config, String value) { - SocketAddress address = Utils.parseSocketAddress(value); + SocketAddress address = Utils.parseServerSocketAddress(value); if (address instanceof InetSocketAddress) { InetSocketAddress addr = (InetSocketAddress) address; int port = addr.getPort() == 0 ? Utils.pickUnusedPort() : addr.getPort(); From a1c41e3d30b7621b0fa626107013597568c189e2 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Mon, 7 Feb 2022 13:29:09 -0800 Subject: [PATCH 40/68] xds/federation: fix percent encoding on server side Overlooked in #8857 on server side. Since `XdsNameResolver.percentEncodePath()` will be also used for server side, I moved the method to `XdsClient`. --- xds/src/main/java/io/grpc/xds/XdsClient.java | 10 ++++++++ .../java/io/grpc/xds/XdsNameResolver.java | 14 +---------- .../java/io/grpc/xds/XdsServerWrapper.java | 3 +-- .../io/grpc/xds/ClientXdsClientDataTest.java | 25 +++++++++++++++++++ .../java/io/grpc/xds/XdsNameResolverTest.java | 25 ------------------- 5 files changed, 37 insertions(+), 40 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/XdsClient.java b/xds/src/main/java/io/grpc/xds/XdsClient.java index ba9c58153a6..1c231b83a19 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClient.java +++ b/xds/src/main/java/io/grpc/xds/XdsClient.java @@ -24,6 +24,7 @@ import com.google.common.base.MoreObjects; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; +import com.google.common.net.UrlEscapers; import com.google.common.util.concurrent.ListenableFuture; import com.google.protobuf.Any; import io.grpc.Status; @@ -106,6 +107,15 @@ static String canonifyResourceName(String resourceName) { return resourceName.replace(rawQuery, canonifiedQuery); } + static String percentEncodePath(String input) { + Iterable pathSegs = Splitter.on('/').split(input); + List encodedSegs = new ArrayList<>(); + for (String pathSeg : pathSegs) { + encodedSegs.add(UrlEscapers.urlPathSegmentEscaper().escape(pathSeg)); + } + return Joiner.on('/').join(encodedSegs); + } + @AutoValue abstract static class LdsUpdate implements ResourceUpdate { // Http level api listener configuration. diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java index 56b3dbe0596..b15af622256 100644 --- a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java +++ b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java @@ -22,12 +22,10 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; -import com.google.common.base.Splitter; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; -import com.google.common.net.UrlEscapers; import com.google.gson.Gson; import com.google.protobuf.util.Durations; import io.grpc.Attributes; @@ -193,7 +191,7 @@ public void start(Listener2 listener) { } String replacement = serviceAuthority; if (listenerNameTemplate.startsWith(XDSTP_SCHEME)) { - replacement = percentEncodePath(replacement); + replacement = XdsClient.percentEncodePath(replacement); } String ldsResourceName = expandPercentS(listenerNameTemplate, replacement); if (!XdsClient.isResourceNameValid(ldsResourceName, ResourceType.LDS.typeUrl()) @@ -208,16 +206,6 @@ public void start(Listener2 listener) { resolveState.start(); } - @VisibleForTesting - static String percentEncodePath(String input) { - Iterable pathSegs = Splitter.on('/').split(input); - List encodedSegs = new ArrayList<>(); - for (String pathSeg : pathSegs) { - encodedSegs.add(UrlEscapers.urlPathSegmentEscaper().escape(pathSeg)); - } - return Joiner.on('/').join(encodedSegs); - } - private static String expandPercentS(String template, String replacement) { return template.replace("%s", replacement); } diff --git a/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java b/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java index 0946f621487..58c9692fa67 100644 --- a/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java +++ b/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java @@ -24,7 +24,6 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.net.UrlEscapers; import com.google.common.util.concurrent.SettableFuture; import io.grpc.Attributes; import io.grpc.InternalServerInterceptors; @@ -196,7 +195,7 @@ private void internalStart() { } String replacement = listenerAddress; if (listenerTemplate.startsWith(XDSTP_SCHEME)) { - replacement = UrlEscapers.urlFragmentEscaper().escape(replacement); + replacement = XdsClient.percentEncodePath(replacement); } discoveryState = new DiscoveryState(listenerTemplate.replaceAll("%s", replacement)); } diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java index c41773dfd97..b195a63eec4 100644 --- a/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java +++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java @@ -2619,6 +2619,31 @@ public void canonifyResourceName() { .isEqualTo(expectedCanonifiedName); } + /** + * Tests compliance with RFC 3986 section 3.3 + * https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 + */ + @Test + public void percentEncodePath() { + String unreserved = "aAzZ09-._~"; + assertThat(XdsClient.percentEncodePath(unreserved)).isEqualTo(unreserved); + + String subDelims = "!$&'(*+,;/="; + assertThat(XdsClient.percentEncodePath(subDelims)).isEqualTo(subDelims); + + String colonAndAt = ":@"; + assertThat(XdsClient.percentEncodePath(colonAndAt)).isEqualTo(colonAndAt); + + String needBeEncoded = "?#[]"; + assertThat(XdsClient.percentEncodePath(needBeEncoded)).isEqualTo("%3F%23%5B%5D"); + + String ipv4 = "0.0.0.0:8080"; + assertThat(XdsClient.percentEncodePath(ipv4)).isEqualTo(ipv4); + + String ipv6 = "[::1]:8080"; + assertThat(XdsClient.percentEncodePath(ipv6)).isEqualTo("%5B::1%5D:8080"); + } + private static Filter buildHttpConnectionManagerFilter(HttpFilter... httpFilters) { return Filter.newBuilder() .setName("envoy.http_connection_manager") diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java index d42cd07b1b6..a17a043af5e 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java @@ -1955,31 +1955,6 @@ public void routeMatching_withHeaders() { .isFalse(); } - /** - * Tests compliance with RFC 3986 section 3.3 - * https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 - */ - @Test - public void percentEncodePath() { - String unreserved = "aAzZ09-._~"; - assertThat(XdsNameResolver.percentEncodePath(unreserved)).isEqualTo(unreserved); - - String subDelims = "!$&'(*+,;/="; - assertThat(XdsNameResolver.percentEncodePath(subDelims)).isEqualTo(subDelims); - - String colonAndAt = ":@"; - assertThat(XdsNameResolver.percentEncodePath(colonAndAt)).isEqualTo(colonAndAt); - - String needBeEncoded = "?#[]"; - assertThat(XdsNameResolver.percentEncodePath(needBeEncoded)).isEqualTo("%3F%23%5B%5D"); - - String ipv4 = "0.0.0.0:8080"; - assertThat(XdsNameResolver.percentEncodePath(ipv4)).isEqualTo(ipv4); - - String ipv6 = "[::1]:8080"; - assertThat(XdsNameResolver.percentEncodePath(ipv6)).isEqualTo("%5B::1%5D:8080"); - } - private final class FakeXdsClientPoolFactory implements XdsClientPoolFactory { Map bootstrap; From 5b6ee1c1766165c85bb58e930a1b8dbbb705b629 Mon Sep 17 00:00:00 2001 From: DNVindhya Date: Tue, 8 Feb 2022 12:20:47 -0600 Subject: [PATCH 41/68] observability: implement GrpcLogRecord proto (#8882) * observability: implement GrpcLogRecord proto - add configureProtoCompilation() to build.gradle for compiling protos --- observability/build.gradle | 8 +- .../logging/LogRecordExtension.java | 40 ++++ .../v1/observabilitylog.proto | 178 ++++++++++++++++++ 3 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 observability/src/main/java/io/grpc/observability/logging/LogRecordExtension.java create mode 100644 observability/src/main/proto/grpc/observabilitylog/v1/observabilitylog.proto diff --git a/observability/build.gradle b/observability/build.gradle index 2b8f746dba2..1a31962dffc 100644 --- a/observability/build.gradle +++ b/observability/build.gradle @@ -2,6 +2,7 @@ plugins { id "java-library" id "maven-publish" + id "com.google.protobuf" id "ru.vyarus.animalsniffer" } @@ -9,7 +10,9 @@ description = "gRPC: Observability" dependencies { api project(':grpc-api') - implementation libraries.guava + implementation libraries.guava, + project(':grpc-protobuf'), + project(':grpc-stub') testImplementation project(':grpc-testing'), project(':grpc-testing-proto'), @@ -20,3 +23,6 @@ dependencies { signature "org.codehaus.mojo.signature:java18:1.0@signature" } + +configureProtoCompilation() + diff --git a/observability/src/main/java/io/grpc/observability/logging/LogRecordExtension.java b/observability/src/main/java/io/grpc/observability/logging/LogRecordExtension.java new file mode 100644 index 00000000000..f2ad6120316 --- /dev/null +++ b/observability/src/main/java/io/grpc/observability/logging/LogRecordExtension.java @@ -0,0 +1,40 @@ +/* + * 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.observability.logging; + +import io.grpc.Internal; +import io.grpc.observabilitylog.v1.GrpcLogRecord; +import java.util.logging.LogRecord; + +/** An extension of java.util.logging.LogRecord which includes gRPC observability logging + * specific fields. */ +@Internal +public final class LogRecordExtension extends LogRecord { + public final GrpcLogRecord grpcLogRecord; + + public LogRecordExtension(GrpcLogRecord record) { + super(null, null); + this.grpcLogRecord = record; + } + + public GrpcLogRecord getGrpcLogRecord() { + return grpcLogRecord; + } + + // Adding a serial version UID since base class i.e LogRecord is Serializable + private static final long serialVersionUID = 1L; +} diff --git a/observability/src/main/proto/grpc/observabilitylog/v1/observabilitylog.proto b/observability/src/main/proto/grpc/observabilitylog/v1/observabilitylog.proto new file mode 100644 index 00000000000..d2e72329cde --- /dev/null +++ b/observability/src/main/proto/grpc/observabilitylog/v1/observabilitylog.proto @@ -0,0 +1,178 @@ +/* + * 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. + */ + +syntax = "proto3"; + +package grpc.observabilitylog.v1; + +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; + +option java_multiple_files = true; +option java_package = "io.grpc.observabilitylog.v1"; +option java_outer_classname = "ObservabilityLogProto"; + +message GrpcLogRecord { + // List of event types + enum EventType { + GRPC_CALL_UNKNOWN = 0; + // Header sent from client to server + GRPC_CALL_REQUEST_HEADER = 1; + // Header sent from server to client + GRPC_CALL_RESPONSE_HEADER = 2; + // Message sent from client to server + GRPC_CALL_REQUEST_MESSAGE = 3; + // Message sent from server to client + GRPC_CALL_RESPONSE_MESSAGE = 4; + // Trailer indicates the end of the gRPC call + GRPC_CALL_TRAILER = 5; + // A signal that client is done sending + GRPC_CALL_HALF_CLOSE = 6; + // A signal that the rpc is canceled + GRPC_CALL_CANCEL = 7; + } + // The entity that generates the log entry + enum EventLogger { + LOGGER_UNKNOWN = 0; + LOGGER_CLIENT = 1; + LOGGER_SERVER = 2; + } + // The log severity level of the log entry + enum LogLevel { + LOG_LEVEL_UNKNOWN = 0; + LOG_LEVEL_TRACE = 1; + LOG_LEVEL_DEBUG = 2; + LOG_LEVEL_INFO = 3; + LOG_LEVEL_WARN = 4; + LOG_LEVEL_ERROR = 5; + LOG_LEVEL_CRITICAL = 6; + } + + // The timestamp of the log event + google.protobuf.Timestamp timestamp = 1; + + // Uniquely identifies a call. The value must not be 0 in order to disambiguate + // from an unset value. + // Each call may have several log entries. They will all have the same rpc_id. + // Nothing is guaranteed about their value other than they are unique across + // different RPCs in the same gRPC process. + uint64 rpc_id = 2; + + EventType event_type = 3; // one of the above EventType enum + EventLogger event_logger = 4; // one of the above EventLogger enum + + // the name of the service + string service_name = 5; + // the name of the RPC method + string method_name = 6; + + LogLevel log_level = 7; // one of the above LogLevel enum + + // Peer address information. On client side, peer is logged on server + // header event or trailer event (if trailer-only). On server side, peer + // is always logged on the client header event. + Address peer_address = 8; + + // the RPC timeout value + google.protobuf.Duration timeout = 11; + + // A single process may be used to run multiple virtual servers with + // different identities. + // The authority is the name of such a server identify. It is typically a + // portion of the URI in the form of or :. + string authority = 12; + + // Size of the message or metadata, depending on the event type, + // regardless of whether the full message or metadata is being logged + // (i.e. could be truncated or omitted). + uint32 payload_size = 13; + + // true if message or metadata field is either truncated or omitted due + // to config options + bool payload_truncated = 14; + + // Used by header event or trailer event + Metadata metadata = 15; + + // The entry sequence ID for this call. The first message has a value of 1, + // to disambiguate from an unset value. The purpose of this field is to + // detect missing entries in environments where durability or ordering is + // not guaranteed. + uint64 sequence_id = 16; + + // Used by message event + bytes message = 17; + + // The gRPC status code + uint32 status_code = 18; + + // The gRPC status message + string status_message = 19; + + // The value of the grpc-status-details-bin metadata key, if any. + // This is always an encoded google.rpc.Status message + bytes status_details = 20; + + // Attributes of the environment generating log record. The purpose of this + // field is to identify the source environment. + EnvironmentTags env_tags = 21; + + // A list of non-gRPC custom values specified by the application + repeated CustomTags custom_tags = 22; + + // A list of metadata pairs + message Metadata { + repeated MetadataEntry entry = 1; + } + + // One metadata key value pair + message MetadataEntry { + string key = 1; + bytes value = 2; + } + + // Address information + message Address { + enum Type { + TYPE_UNKNOWN = 0; + TYPE_IPV4 = 1; // in 1.2.3.4 form + TYPE_IPV6 = 2; // IPv6 canonical form (RFC5952 section 4) + TYPE_UNIX = 3; // UDS string + } + Type type = 1; + string address = 2; + // only for TYPE_IPV4 and TYPE_IPV6 + uint32 ip_port = 3; + } + + // Source Environment information + message EnvironmentTags { + string gcp_project_id = 1; + string gcp_numeric_project_id = 2; + string gce_instance_id = 3; + string gce_instance_hostname = 4; + string gce_instance_zone = 5; + string gke_cluster_uid = 6; + string gke_cluster_name = 7; + string gke_cluster_location = 8; + } + + // Custom key value pair + message CustomTags { + string key = 1; + string value = 2; + } +} From 39c8f4e5846e996389fb5638e5b164b8f1789ef3 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Tue, 8 Feb 2022 21:40:56 -0800 Subject: [PATCH 42/68] rls: migrate data types to AutoValue Refactor to use `@AutoValue` for data types. This reduces human mistakes on `equals()`, `hashCode()`, and `toString()` while we are constantly adding and changing member fields of the data type. --- .../java/io/grpc/rls/CachingRlsLbClient.java | 20 +- .../java/io/grpc/rls/RlsLoadBalancer.java | 4 +- .../java/io/grpc/rls/RlsProtoConverters.java | 57 +-- .../main/java/io/grpc/rls/RlsProtoData.java | 378 +++--------------- .../java/io/grpc/rls/RlsRequestFactory.java | 28 +- .../io/grpc/rls/CachingRlsLbClientTest.java | 59 +-- .../java/io/grpc/rls/RlsLoadBalancerTest.java | 8 +- .../io/grpc/rls/RlsProtoConvertersTest.java | 99 ++--- .../io/grpc/rls/RlsRequestFactoryTest.java | 57 +-- 9 files changed, 237 insertions(+), 473 deletions(-) diff --git a/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java b/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java index ad5abd02058..bbd8f6986c3 100644 --- a/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java +++ b/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java @@ -115,14 +115,14 @@ private CachingRlsLbClient(Builder builder) { synchronizationContext = helper.getSynchronizationContext(); lbPolicyConfig = checkNotNull(builder.lbPolicyConfig, "lbPolicyConfig"); RouteLookupConfig rlsConfig = lbPolicyConfig.getRouteLookupConfig(); - maxAgeNanos = rlsConfig.getMaxAgeInNanos(); - staleAgeNanos = rlsConfig.getStaleAgeInNanos(); - callTimeoutNanos = rlsConfig.getLookupServiceTimeoutInNanos(); + maxAgeNanos = rlsConfig.maxAgeInNanos(); + staleAgeNanos = rlsConfig.staleAgeInNanos(); + callTimeoutNanos = rlsConfig.lookupServiceTimeoutInNanos(); timeProvider = checkNotNull(builder.timeProvider, "timeProvider"); throttler = checkNotNull(builder.throttler, "throttler"); linkedHashLruCache = new RlsAsyncLruCache( - rlsConfig.getCacheSizeBytes(), + rlsConfig.cacheSizeBytes(), builder.evictionListener, scheduledExecutorService, timeProvider, @@ -147,7 +147,7 @@ private CachingRlsLbClient(Builder builder) { // will be looked up differently than the backends; overrideAuthority(helper.getAuthority()) is // called to impose the authority security restrictions. ManagedChannelBuilder rlsChannelBuilder = helper.createResolvingOobChannelBuilder( - rlsConfig.getLookupService(), helper.getUnsafeChannelCredentials()); + rlsConfig.lookupService(), helper.getUnsafeChannelCredentials()); rlsChannelBuilder.overrideAuthority(helper.getAuthority()); Map routeLookupChannelServiceConfig = lbPolicyConfig.getRouteLookupChannelServiceConfig(); @@ -518,7 +518,7 @@ final class DataCacheEntry extends CacheEntry { // TODO(creamsoup) fallback to other targets if first one is not available childPolicyWrapper = refCountedChildPolicyWrapperFactory - .createOrGet(response.getTargets().get(0)); + .createOrGet(response.targets().get(0)); long now = timeProvider.currentTimeNanos(); expireTime = now + maxAgeNanos; staleTime = now + staleAgeNanos; @@ -576,7 +576,7 @@ String getHeaderData() { int getSizeBytes() { // size of strings and java object overhead, actual memory usage is more than this. return - (response.getTargets().get(0).length() + response.getHeaderData().length()) * 2 + 38 * 2; + (response.targets().get(0).length() + response.getHeaderData().length()) * 2 + 38 * 2; } @Override @@ -893,7 +893,7 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { headers.discardAll(RLS_DATA_KEY); headers.put(RLS_DATA_KEY, response.getHeaderData()); } - String defaultTarget = lbPolicyConfig.getRouteLookupConfig().getDefaultTarget(); + String defaultTarget = lbPolicyConfig.getRouteLookupConfig().defaultTarget(); boolean hasFallback = defaultTarget != null && !defaultTarget.isEmpty(); if (response.hasData()) { ChildPolicyWrapper childPolicyWrapper = response.getChildPolicyWrapper(); @@ -933,7 +933,7 @@ private PickResult useFallback(PickSubchannelArgs args) { } private void startFallbackChildPolicy() { - String defaultTarget = lbPolicyConfig.getRouteLookupConfig().getDefaultTarget(); + String defaultTarget = lbPolicyConfig.getRouteLookupConfig().defaultTarget(); logger.log(ChannelLogLevel.DEBUG, "starting fallback to {0}", defaultTarget); synchronized (lock) { if (fallbackChildPolicyWrapper != null) { @@ -953,7 +953,7 @@ void close() { @Override public String toString() { return MoreObjects.toStringHelper(this) - .add("target", lbPolicyConfig.getRouteLookupConfig().getLookupService()) + .add("target", lbPolicyConfig.getRouteLookupConfig().lookupService()) .toString(); } } diff --git a/rls/src/main/java/io/grpc/rls/RlsLoadBalancer.java b/rls/src/main/java/io/grpc/rls/RlsLoadBalancer.java index 289098e2554..2aac96cadcf 100644 --- a/rls/src/main/java/io/grpc/rls/RlsLoadBalancer.java +++ b/rls/src/main/java/io/grpc/rls/RlsLoadBalancer.java @@ -56,8 +56,8 @@ public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { checkNotNull(lbPolicyConfiguration, "Missing rls lb config"); if (!lbPolicyConfiguration.equals(this.lbPolicyConfiguration)) { boolean needToConnect = this.lbPolicyConfiguration == null - || !this.lbPolicyConfiguration.getRouteLookupConfig().getLookupService().equals( - lbPolicyConfiguration.getRouteLookupConfig().getLookupService()); + || !this.lbPolicyConfiguration.getRouteLookupConfig().lookupService().equals( + lbPolicyConfiguration.getRouteLookupConfig().lookupService()); if (needToConnect) { if (routeLookupClient != null) { routeLookupClient.close(); diff --git a/rls/src/main/java/io/grpc/rls/RlsProtoConverters.java b/rls/src/main/java/io/grpc/rls/RlsProtoConverters.java index b4d3fcc3b29..14e3e50bd49 100644 --- a/rls/src/main/java/io/grpc/rls/RlsProtoConverters.java +++ b/rls/src/main/java/io/grpc/rls/RlsProtoConverters.java @@ -63,7 +63,8 @@ static final class RouteLookupRequestConverter @Override protected RlsProtoData.RouteLookupRequest doForward(RouteLookupRequest routeLookupRequest) { - return new RlsProtoData.RouteLookupRequest(routeLookupRequest.getKeyMapMap()); + return RlsProtoData.RouteLookupRequest.create( + ImmutableMap.copyOf(routeLookupRequest.getKeyMapMap())); } @Override @@ -71,7 +72,7 @@ protected RouteLookupRequest doBackward(RlsProtoData.RouteLookupRequest routeLoo return RouteLookupRequest.newBuilder() .setTargetType("grpc") - .putAllKeyMap(routeLookupRequest.getKeyMap()) + .putAllKeyMap(routeLookupRequest.keyMap()) .build(); } } @@ -86,15 +87,15 @@ static final class RouteLookupResponseConverter @Override protected RlsProtoData.RouteLookupResponse doForward(RouteLookupResponse routeLookupResponse) { return - new RlsProtoData.RouteLookupResponse( - routeLookupResponse.getTargetsList(), + RlsProtoData.RouteLookupResponse.create( + ImmutableList.copyOf(routeLookupResponse.getTargetsList()), routeLookupResponse.getHeaderData()); } @Override protected RouteLookupResponse doBackward(RlsProtoData.RouteLookupResponse routeLookupResponse) { return RouteLookupResponse.newBuilder() - .addAllTargets(routeLookupResponse.getTargets()) + .addAllTargets(routeLookupResponse.targets()) .setHeaderData(routeLookupResponse.getHeaderData()) .build(); } @@ -108,13 +109,13 @@ static final class RouteLookupConfigConverter @Override protected RouteLookupConfig doForward(Map json) { - List grpcKeyBuilders = + ImmutableList grpcKeyBuilders = GrpcKeyBuilderConverter.covertAll( checkNotNull(JsonUtil.getListOfObjects(json, "grpcKeyBuilders"), "grpcKeyBuilders")); checkArgument(!grpcKeyBuilders.isEmpty(), "must have at least one GrpcKeyBuilder"); Set names = new HashSet<>(); for (GrpcKeyBuilder keyBuilder : grpcKeyBuilders) { - for (Name name : keyBuilder.getNames()) { + for (Name name : keyBuilder.names()) { checkArgument(names.add(name), "duplicate names in grpc_keybuilders: " + name); } } @@ -145,14 +146,15 @@ protected RouteLookupConfig doForward(Map json) { checkArgument(cacheSize > 0, "cacheSize must be positive"); cacheSize = Math.min(cacheSize, MAX_CACHE_SIZE); String defaultTarget = Strings.emptyToNull(JsonUtil.getString(json, "defaultTarget")); - return new RouteLookupConfig( - grpcKeyBuilders, - lookupService, - /* lookupServiceTimeoutInNanos= */ timeout, - /* maxAgeInNanos= */ maxAge, - /* staleAgeInNanos= */ staleAge, - /* cacheSizeBytes= */ cacheSize, - defaultTarget); + return RouteLookupConfig.builder() + .grpcKeyBuilders(grpcKeyBuilders) + .lookupService(lookupService) + .lookupServiceTimeoutInNanos(timeout) + .maxAgeInNanos(maxAge) + .staleAgeInNanos(staleAge) + .cacheSizeBytes(cacheSize) + .defaultTarget(defaultTarget) + .build(); } private static T orDefault(@Nullable T value, T defaultValue) { @@ -169,12 +171,12 @@ protected Map doBackward(RouteLookupConfig routeLookupConfig) { } private static final class GrpcKeyBuilderConverter { - public static List covertAll(List> keyBuilders) { - List keyBuilderList = new ArrayList<>(); + public static ImmutableList covertAll(List> keyBuilders) { + ImmutableList.Builder keyBuilderList = ImmutableList.builder(); for (Map keyBuilder : keyBuilders) { keyBuilderList.add(convert(keyBuilder)); } - return keyBuilderList; + return keyBuilderList.build(); } @SuppressWarnings("unchecked") @@ -184,25 +186,26 @@ static GrpcKeyBuilder convert(Map keyBuilder) { rawRawNames != null && !rawRawNames.isEmpty(), "each keyBuilder must have at least one name"); List> rawNames = JsonUtil.checkObjectList(rawRawNames); - List names = new ArrayList<>(); + ImmutableList.Builder namesBuilder = ImmutableList.builder(); for (Map rawName : rawNames) { String serviceName = JsonUtil.getString(rawName, "service"); checkArgument(!Strings.isNullOrEmpty(serviceName), "service must not be empty or null"); - names.add(new Name(serviceName, JsonUtil.getString(rawName, "method"))); + namesBuilder.add(Name.create(serviceName, JsonUtil.getString(rawName, "method"))); } List rawRawHeaders = JsonUtil.getList(keyBuilder, "headers"); List> rawHeaders = rawRawHeaders == null ? new ArrayList>() : JsonUtil.checkObjectList(rawRawHeaders); - List nameMatchers = new ArrayList<>(); + ImmutableList.Builder nameMatchersBuilder = ImmutableList.builder(); for (Map rawHeader : rawHeaders) { Boolean requiredMatch = JsonUtil.getBoolean(rawHeader, "requiredMatch"); checkArgument( requiredMatch == null || !requiredMatch, "requiredMatch shouldn't be specified for gRPC"); - NameMatcher matcher = new NameMatcher( - JsonUtil.getString(rawHeader, "key"), (List) rawHeader.get("names")); - nameMatchers.add(matcher); + NameMatcher matcher = NameMatcher.create( + JsonUtil.getString(rawHeader, "key"), + ImmutableList.copyOf((List) rawHeader.get("names"))); + nameMatchersBuilder.add(matcher); } ExtraKeys extraKeys = ExtraKeys.DEFAULT; Map rawExtraKeys = @@ -216,8 +219,10 @@ static GrpcKeyBuilder convert(Map keyBuilder) { if (constantKeys == null) { constantKeys = ImmutableMap.of(); } + ImmutableList nameMatchers = nameMatchersBuilder.build(); checkUniqueKey(nameMatchers, constantKeys.keySet()); - return new GrpcKeyBuilder(names, nameMatchers, extraKeys, constantKeys); + return GrpcKeyBuilder.create( + namesBuilder.build(), nameMatchers, extraKeys, ImmutableMap.copyOf(constantKeys)); } } @@ -225,7 +230,7 @@ private static void checkUniqueKey(List nameMatchers, Set c Set keys = new HashSet<>(constantKeys); keys.addAll(EXTRA_KEY_NAMES); for (NameMatcher nameMatcher : nameMatchers) { - keys.add(nameMatcher.getKey()); + keys.add(nameMatcher.key()); } if (keys.size() != nameMatchers.size() + constantKeys.size() + EXTRA_KEY_NAMES.size()) { throw new IllegalArgumentException("keys in KeyBuilder must be unique"); diff --git a/rls/src/main/java/io/grpc/rls/RlsProtoData.java b/rls/src/main/java/io/grpc/rls/RlsProtoData.java index 4709894833c..929b7800759 100644 --- a/rls/src/main/java/io/grpc/rls/RlsProtoData.java +++ b/rls/src/main/java/io/grpc/rls/RlsProtoData.java @@ -16,16 +16,9 @@ package io.grpc.rls; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; - import com.google.auto.value.AutoValue; -import com.google.common.base.MoreObjects; -import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import java.util.List; -import java.util.Map; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; @@ -35,140 +28,46 @@ final class RlsProtoData { private RlsProtoData() {} /** A request object sent to route lookup service. */ + @AutoValue @Immutable - static final class RouteLookupRequest { - - private final ImmutableMap keyMap; - - RouteLookupRequest(Map keyMap) { - this.keyMap = ImmutableMap.copyOf(checkNotNull(keyMap, "keyMap")); - } + abstract static class RouteLookupRequest { /** Returns a map of key values extracted via key builders for the gRPC or HTTP request. */ - ImmutableMap getKeyMap() { - return keyMap; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - RouteLookupRequest that = (RouteLookupRequest) o; - return Objects.equal(keyMap, that.keyMap); - } - - @Override - public int hashCode() { - return Objects.hashCode(keyMap); - } + abstract ImmutableMap keyMap(); - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("keyMap", keyMap) - .toString(); + static RouteLookupRequest create(ImmutableMap keyMap) { + return new AutoValue_RlsProtoData_RouteLookupRequest(keyMap); } } /** A response from route lookup service. */ + @AutoValue @Immutable - static final class RouteLookupResponse { - - private final ImmutableList targets; - - private final String headerData; - - RouteLookupResponse(List targets, String headerData) { - checkState(targets != null && !targets.isEmpty(), "targets cannot be empty or null"); - this.targets = ImmutableList.copyOf(targets); - this.headerData = checkNotNull(headerData, "headerData"); - } + abstract static class RouteLookupResponse { /** * Returns list of targets. Prioritized list (best one first) of addressable entities to use for * routing, using syntax requested by the request target_type. The targets will be tried in * order until a healthy one is found. */ - ImmutableList getTargets() { - return targets; - } + abstract ImmutableList targets(); /** * Returns optional header data to pass along to AFE in the X-Google-RLS-Data header. Cached * with "target" and sent with all requests that match the request key. Allows the RLS to pass * its work product to the eventual target. */ - String getHeaderData() { - return headerData; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - RouteLookupResponse that = (RouteLookupResponse) o; - return java.util.Objects.equals(targets, that.targets) - && java.util.Objects.equals(headerData, that.headerData); - } + abstract String getHeaderData(); - @Override - public int hashCode() { - return java.util.Objects.hash(targets, headerData); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("targets", targets) - .add("headerData", headerData) - .toString(); + static RouteLookupResponse create(ImmutableList targets, String getHeaderData) { + return new AutoValue_RlsProtoData_RouteLookupResponse(targets, getHeaderData); } } /** A config object for gRPC RouteLookupService. */ + @AutoValue @Immutable - static final class RouteLookupConfig { - - private final ImmutableList grpcKeyBuilders; - - private final String lookupService; - - private final long lookupServiceTimeoutInNanos; - - private final long maxAgeInNanos; - - private final long staleAgeInNanos; - - private final long cacheSizeBytes; - - @Nullable - private final String defaultTarget; - - RouteLookupConfig( - List grpcKeyBuilders, - String lookupService, - long lookupServiceTimeoutInNanos, - long maxAgeInNanos, - long staleAgeInNanos, - long cacheSizeBytes, - @Nullable - String defaultTarget) { - this.grpcKeyBuilders = ImmutableList.copyOf(grpcKeyBuilders); - this.lookupService = lookupService; - this.lookupServiceTimeoutInNanos = lookupServiceTimeoutInNanos; - this.maxAgeInNanos = maxAgeInNanos; - this.staleAgeInNanos = staleAgeInNanos; - this.cacheSizeBytes = cacheSizeBytes; - this.defaultTarget = defaultTarget; - } + abstract static class RouteLookupConfig { /** * Returns unordered specifications for constructing keys for gRPC requests. All GrpcKeyBuilders @@ -176,36 +75,25 @@ static final class RouteLookupConfig { * keyed by name. If no GrpcKeyBuilder matches, an empty key_map will be sent to the lookup * service; it should likely reply with a global default route and raise an alert. */ - ImmutableList getGrpcKeyBuilders() { - return grpcKeyBuilders; - } + abstract ImmutableList grpcKeyBuilders(); /** * Returns the name of the lookup service as a gRPC URI. Typically, this will be a subdomain of * the target, such as "lookup.datastore.googleapis.com". */ - String getLookupService() { - return lookupService; - } + abstract String lookupService(); /** Returns the timeout value for lookup service requests. */ - long getLookupServiceTimeoutInNanos() { - return lookupServiceTimeoutInNanos; - } - + abstract long lookupServiceTimeoutInNanos(); /** Returns the maximum age the result will be cached. */ - long getMaxAgeInNanos() { - return maxAgeInNanos; - } + abstract long maxAgeInNanos(); /** * Returns the time when an entry will be in a staled status. When cache is accessed whgen the * entry is in staled status, it will */ - long getStaleAgeInNanos() { - return staleAgeInNanos; - } + abstract long staleAgeInNanos(); /** * Returns a rough indicator of amount of memory to use for the client cache. Some of the data @@ -213,9 +101,7 @@ long getStaleAgeInNanos() { * than this value. If this field is omitted or set to zero, a client default will be used. * The value may be capped to a lower amount based on client configuration. */ - long getCacheSizeBytes() { - return cacheSizeBytes; - } + abstract long cacheSizeBytes(); /** * Returns the default target to use if needed. If nonempty (implies request processing @@ -224,51 +110,30 @@ long getCacheSizeBytes() { * {@literal e.g.} "us_east_1.cloudbigtable.googleapis.com". */ @Nullable - String getDefaultTarget() { - return defaultTarget; - } + abstract String defaultTarget(); - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - RouteLookupConfig that = (RouteLookupConfig) o; - return lookupServiceTimeoutInNanos == that.lookupServiceTimeoutInNanos - && maxAgeInNanos == that.maxAgeInNanos - && staleAgeInNanos == that.staleAgeInNanos - && cacheSizeBytes == that.cacheSizeBytes - && Objects.equal(grpcKeyBuilders, that.grpcKeyBuilders) - && Objects.equal(lookupService, that.lookupService) - && Objects.equal(defaultTarget, that.defaultTarget); + static Builder builder() { + return new AutoValue_RlsProtoData_RouteLookupConfig.Builder(); } - @Override - public int hashCode() { - return Objects.hashCode( - grpcKeyBuilders, - lookupService, - lookupServiceTimeoutInNanos, - maxAgeInNanos, - staleAgeInNanos, - cacheSizeBytes, - defaultTarget); - } + @AutoValue.Builder + abstract static class Builder { - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("grpcKeyBuilders", grpcKeyBuilders) - .add("lookupService", lookupService) - .add("lookupServiceTimeoutInNanos", lookupServiceTimeoutInNanos) - .add("maxAgeInNanos", maxAgeInNanos) - .add("staleAgeInNanos", staleAgeInNanos) - .add("cacheSize", cacheSizeBytes) - .add("defaultTarget", defaultTarget) - .toString(); + abstract Builder grpcKeyBuilders(ImmutableList grpcKeyBuilders); + + abstract Builder lookupService(String lookupService); + + abstract Builder lookupServiceTimeoutInNanos(long lookupServiceTimeoutInNanos); + + abstract Builder maxAgeInNanos(long maxAgeInNanos); + + abstract Builder staleAgeInNanos(long staleAgeInNanos); + + abstract Builder cacheSizeBytes(long cacheSizeBytes); + + abstract Builder defaultTarget(@Nullable String defaultTarget); + + abstract RouteLookupConfig build(); } } @@ -277,74 +142,25 @@ public String toString() { * The name must match one of the names listed in the "name" field. If the "required_match" field * is true, one of the specified names must be present for the keybuilder to match. */ + @AutoValue @Immutable - static final class NameMatcher { - - private final String key; - - private final ImmutableList names; - - NameMatcher(String key, List names) { - this.key = checkNotNull(key, "key"); - this.names = ImmutableList.copyOf(checkNotNull(names, "names")); - } + abstract static class NameMatcher { /** The name that will be used in the RLS key_map to refer to this value. */ - String getKey() { - return key; - } + abstract String key(); /** Returns ordered list of names; the first non-empty value will be used. */ - ImmutableList names() { - return names; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - NameMatcher matcher = (NameMatcher) o; - return java.util.Objects.equals(key, matcher.key) - && java.util.Objects.equals(names, matcher.names); - } + abstract ImmutableList names(); - @Override - public int hashCode() { - return java.util.Objects.hash(key, names); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("key", key) - .add("names", names) - .toString(); + static NameMatcher create(String key, ImmutableList names) { + return new AutoValue_RlsProtoData_NameMatcher(key, names); } } /** GrpcKeyBuilder is a configuration to construct headers consumed by route lookup service. */ - static final class GrpcKeyBuilder { - - private final ImmutableList names; - - private final ImmutableList headers; - private final ExtraKeys extraKeys; - private final ImmutableMap constantKeys; - - /** Constructor. All args should be nonnull. Headers should head unique keys. */ - GrpcKeyBuilder( - List names, List headers, ExtraKeys extraKeys, - Map constantKeys) { - checkState(names != null && !names.isEmpty(), "names cannot be empty"); - this.names = ImmutableList.copyOf(names); - this.headers = ImmutableList.copyOf(headers); - this.extraKeys = checkNotNull(extraKeys, "extraKeys"); - this.constantKeys = ImmutableMap.copyOf(checkNotNull(constantKeys, "constantKeys")); - } + @AutoValue + @Immutable + abstract static class GrpcKeyBuilder { /** * Returns names. To match, one of the given Name fields must match; the service and method @@ -352,53 +168,23 @@ static final class GrpcKeyBuilder { * package name. The method name may be omitted, in which case any method on the given service * is matched. */ - ImmutableList getNames() { - return names; - } + abstract ImmutableList names(); /** * Returns a list of NameMatchers for header. Extract keys from all listed headers. For gRPC, it * is an error to specify "required_match" on the NameMatcher protos, and we ignore it if set. */ - ImmutableList getHeaders() { - return headers; - } + abstract ImmutableList headers(); - ExtraKeys getExtraKeys() { - return extraKeys; - } + abstract ExtraKeys extraKeys(); - ImmutableMap getConstantKeys() { - return constantKeys; - } + abstract ImmutableMap constantKeys(); - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - GrpcKeyBuilder that = (GrpcKeyBuilder) o; - return Objects.equal(names, that.names) && Objects.equal(headers, that.headers) - && Objects.equal(extraKeys, that.extraKeys) - && Objects.equal(constantKeys, that.constantKeys); - } - - @Override - public int hashCode() { - return Objects.hashCode(names, headers, extraKeys, constantKeys); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("names", names) - .add("headers", headers) - .add("extraKeys", extraKeys) - .add("constantKeys", constantKeys) - .toString(); + static GrpcKeyBuilder create( + ImmutableList names, + ImmutableList headers, + ExtraKeys extraKeys, ImmutableMap constantKeys) { + return new AutoValue_RlsProtoData_GrpcKeyBuilder(names, headers, extraKeys, constantKeys); } /** @@ -407,57 +193,23 @@ public String toString() { * required and includes the proto package name. The method name may be omitted, in which case * any method on the given service is matched. */ - static final class Name { - - private final String service; + @AutoValue + @Immutable + abstract static class Name { - @Nullable - private final String method; - - /** The primary constructor. */ - Name(String service, @Nullable String method) { - this.service = service; - this.method = method; - } - - String getService() { - return service; - } + abstract String service(); @Nullable - String getMethod() { - return method; - } + abstract String method(); - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Name name = (Name) o; - return Objects.equal(service, name.service) - && Objects.equal(method, name.method); - } - - @Override - public int hashCode() { - return Objects.hashCode(service, method); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("service", service) - .add("method", method) - .toString(); + static Name create(String service, @Nullable String method) { + return new AutoValue_RlsProtoData_GrpcKeyBuilder_Name(service, method); } } } @AutoValue + @Immutable abstract static class ExtraKeys { static final ExtraKeys DEFAULT = create(null, null, null); diff --git a/rls/src/main/java/io/grpc/rls/RlsRequestFactory.java b/rls/src/main/java/io/grpc/rls/RlsRequestFactory.java index d3aecabd034..4a32bece2b2 100644 --- a/rls/src/main/java/io/grpc/rls/RlsRequestFactory.java +++ b/rls/src/main/java/io/grpc/rls/RlsRequestFactory.java @@ -50,11 +50,11 @@ final class RlsRequestFactory { private static Map createKeyBuilderTable( RouteLookupConfig config) { Map table = new HashMap<>(); - for (GrpcKeyBuilder grpcKeyBuilder : config.getGrpcKeyBuilders()) { - for (Name name : grpcKeyBuilder.getNames()) { - boolean hasMethod = name.getMethod() == null || name.getMethod().isEmpty(); - String method = hasMethod ? "*" : name.getMethod(); - String path = "/" + name.getService() + "/" + method; + for (GrpcKeyBuilder grpcKeyBuilder : config.grpcKeyBuilders()) { + for (Name name : grpcKeyBuilder.names()) { + boolean hasMethod = name.method() == null || name.method().isEmpty(); + String method = hasMethod ? "*" : name.method(); + String path = "/" + name.service() + "/" + method; table.put(path, grpcKeyBuilder); } } @@ -73,12 +73,12 @@ RouteLookupRequest create(String service, String method, Metadata metadata) { grpcKeyBuilder = keyBuilderTable.get("/" + service + "/*"); } if (grpcKeyBuilder == null) { - return new RouteLookupRequest(ImmutableMap.of()); + return RouteLookupRequest.create(ImmutableMap.of()); } - Map rlsRequestHeaders = - createRequestHeaders(metadata, grpcKeyBuilder.getHeaders()); - ExtraKeys extraKeys = grpcKeyBuilder.getExtraKeys(); - Map constantKeys = grpcKeyBuilder.getConstantKeys(); + ImmutableMap.Builder rlsRequestHeaders = + createRequestHeaders(metadata, grpcKeyBuilder.headers()); + ExtraKeys extraKeys = grpcKeyBuilder.extraKeys(); + Map constantKeys = grpcKeyBuilder.constantKeys(); if (extraKeys.host() != null) { rlsRequestHeaders.put(extraKeys.host(), target); } @@ -89,12 +89,12 @@ RouteLookupRequest create(String service, String method, Metadata metadata) { rlsRequestHeaders.put(extraKeys.method(), method); } rlsRequestHeaders.putAll(constantKeys); - return new RouteLookupRequest(rlsRequestHeaders); + return RouteLookupRequest.create(rlsRequestHeaders.build()); } - private Map createRequestHeaders( + private ImmutableMap.Builder createRequestHeaders( Metadata metadata, List keyBuilder) { - Map rlsRequestHeaders = new HashMap<>(); + ImmutableMap.Builder rlsRequestHeaders = ImmutableMap.builder(); for (NameMatcher nameMatcher : keyBuilder) { String value = null; for (String requestHeaderName : nameMatcher.names()) { @@ -104,7 +104,7 @@ private Map createRequestHeaders( } } if (value != null) { - rlsRequestHeaders.put(nameMatcher.getKey(), value); + rlsRequestHeaders.put(nameMatcher.key(), value); } } return rlsRequestHeaders; diff --git a/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java b/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java index f5ad21d047a..588bddcaa61 100644 --- a/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java +++ b/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java @@ -188,12 +188,12 @@ public void run() { public void get_noError_lifeCycle() throws Exception { setUpRlsLbClient(); InOrder inOrder = inOrder(evictionListener); - RouteLookupRequest routeLookupRequest = new RouteLookupRequest(ImmutableMap.of( + RouteLookupRequest routeLookupRequest = RouteLookupRequest.create(ImmutableMap.of( "server", "bigtable.googleapis.com", "service-key", "foo", "method-key", "bar")); rlsServerImpl.setLookupTable( ImmutableMap.of( routeLookupRequest, - new RouteLookupResponse(ImmutableList.of("target"), "header"))); + RouteLookupResponse.create(ImmutableList.of("target"), "header"))); // initial request CachedRouteLookupResponse resp = getInSyncContext(routeLookupRequest); @@ -206,7 +206,7 @@ public void get_noError_lifeCycle() throws Exception { assertThat(resp.hasData()).isTrue(); // cache hit for staled entry - fakeTimeProvider.forwardTime(ROUTE_LOOKUP_CONFIG.getStaleAgeInNanos(), TimeUnit.NANOSECONDS); + fakeTimeProvider.forwardTime(ROUTE_LOOKUP_CONFIG.staleAgeInNanos(), TimeUnit.NANOSECONDS); resp = getInSyncContext(routeLookupRequest); assertThat(resp.hasData()).isTrue(); @@ -222,7 +222,7 @@ public void get_noError_lifeCycle() throws Exception { assertThat(resp.hasData()).isTrue(); // existing cache expired - fakeTimeProvider.forwardTime(ROUTE_LOOKUP_CONFIG.getMaxAgeInNanos(), TimeUnit.NANOSECONDS); + fakeTimeProvider.forwardTime(ROUTE_LOOKUP_CONFIG.maxAgeInNanos(), TimeUnit.NANOSECONDS); resp = getInSyncContext(routeLookupRequest); @@ -258,12 +258,12 @@ public void rls_withCustomRlsChannelServiceConfig() throws Exception { .setThrottler(fakeThrottler) .setTimeProvider(fakeTimeProvider) .build(); - RouteLookupRequest routeLookupRequest = new RouteLookupRequest(ImmutableMap.of( + RouteLookupRequest routeLookupRequest = RouteLookupRequest.create(ImmutableMap.of( "server", "bigtable.googleapis.com", "service-key", "foo", "method-key", "bar")); rlsServerImpl.setLookupTable( ImmutableMap.of( routeLookupRequest, - new RouteLookupResponse(ImmutableList.of("target"), "header"))); + RouteLookupResponse.create(ImmutableList.of("target"), "header"))); // initial request CachedRouteLookupResponse resp = getInSyncContext(routeLookupRequest); @@ -282,12 +282,12 @@ public void rls_withCustomRlsChannelServiceConfig() throws Exception { @Test public void get_throttledAndRecover() throws Exception { setUpRlsLbClient(); - RouteLookupRequest routeLookupRequest = new RouteLookupRequest(ImmutableMap.of( + RouteLookupRequest routeLookupRequest = RouteLookupRequest.create(ImmutableMap.of( "server", "bigtable.googleapis.com", "service-key", "foo", "method-key", "bar")); rlsServerImpl.setLookupTable( ImmutableMap.of( routeLookupRequest, - new RouteLookupResponse(ImmutableList.of("target"), "header"))); + RouteLookupResponse.create(ImmutableList.of("target"), "header"))); fakeThrottler.nextResult = true; fakeBackoffProvider.nextPolicy = createBackoffPolicy(10, TimeUnit.MILLISECONDS); @@ -325,12 +325,12 @@ public void get_throttledAndRecover() throws Exception { public void get_updatesLbState() throws Exception { setUpRlsLbClient(); InOrder inOrder = inOrder(helper); - RouteLookupRequest routeLookupRequest = new RouteLookupRequest(ImmutableMap.of( + RouteLookupRequest routeLookupRequest = RouteLookupRequest.create(ImmutableMap.of( "server", "bigtable.googleapis.com", "service-key", "service1", "method-key", "create")); rlsServerImpl.setLookupTable( ImmutableMap.of( routeLookupRequest, - new RouteLookupResponse( + RouteLookupResponse.create( ImmutableList.of("primary.cloudbigtable.googleapis.com"), "header-rls-data-value"))); @@ -366,7 +366,7 @@ public void get_updatesLbState() throws Exception { fakeBackoffProvider.nextPolicy = createBackoffPolicy(100, TimeUnit.MILLISECONDS); // try to get invalid RouteLookupRequest invalidRouteLookupRequest = - new RouteLookupRequest(ImmutableMap.of()); + RouteLookupRequest.create(ImmutableMap.of()); CachedRouteLookupResponse errorResp = getInSyncContext(invalidRouteLookupRequest); assertThat(errorResp.isPending()).isTrue(); fakeTimeProvider.forwardTime(SERVER_LATENCY_MILLIS, TimeUnit.MILLISECONDS); @@ -392,14 +392,16 @@ public void get_updatesLbState() throws Exception { @Test public void get_childPolicyWrapper_reusedForSameTarget() throws Exception { setUpRlsLbClient(); - RouteLookupRequest routeLookupRequest = new RouteLookupRequest(ImmutableMap.of( + RouteLookupRequest routeLookupRequest = RouteLookupRequest.create(ImmutableMap.of( "server", "bigtable.googleapis.com", "service-key", "foo", "method-key", "bar")); - RouteLookupRequest routeLookupRequest2 = new RouteLookupRequest(ImmutableMap.of( + RouteLookupRequest routeLookupRequest2 = RouteLookupRequest.create(ImmutableMap.of( "server", "bigtable.googleapis.com", "service-key", "foo", "method-key", "baz")); rlsServerImpl.setLookupTable( ImmutableMap.of( - routeLookupRequest, new RouteLookupResponse(ImmutableList.of("target"), "header"), - routeLookupRequest2, new RouteLookupResponse(ImmutableList.of("target"), "header2"))); + routeLookupRequest, + RouteLookupResponse.create(ImmutableList.of("target"), "header"), + routeLookupRequest2, + RouteLookupResponse.create(ImmutableList.of("target"), "header2"))); CachedRouteLookupResponse resp = getInSyncContext(routeLookupRequest); assertThat(resp.isPending()).isTrue(); @@ -425,21 +427,22 @@ routeLookupRequest, new RouteLookupResponse(ImmutableList.of("target"), "header" } private static RouteLookupConfig getRouteLookupConfig() { - return new RouteLookupConfig( - ImmutableList.of( - new GrpcKeyBuilder( - ImmutableList.of(new Name("service1", "create")), + return RouteLookupConfig.builder() + .grpcKeyBuilders(ImmutableList.of( + GrpcKeyBuilder.create( + ImmutableList.of(Name.create("service1", "create")), ImmutableList.of( - new NameMatcher("user", ImmutableList.of("User", "Parent")), - new NameMatcher("id", ImmutableList.of("X-Google-Id"))), + NameMatcher.create("user", ImmutableList.of("User", "Parent")), + NameMatcher.create("id", ImmutableList.of("X-Google-Id"))), ExtraKeys.create("server", "service-key", "method-key"), - ImmutableMap.of())), - /* lookupService= */ "service1", - /* lookupServiceTimeoutInNanos= */ TimeUnit.SECONDS.toNanos(2), - /* maxAgeInNanos= */ TimeUnit.SECONDS.toNanos(300), - /* staleAgeInNanos= */ TimeUnit.SECONDS.toNanos(240), - /* cacheSizeBytes= */ 1000, - DEFAULT_TARGET); + ImmutableMap.of()))) + .lookupService("service1") + .lookupServiceTimeoutInNanos(TimeUnit.SECONDS.toNanos(2)) + .maxAgeInNanos(TimeUnit.SECONDS.toNanos(300)) + .staleAgeInNanos(TimeUnit.SECONDS.toNanos(240)) + .cacheSizeBytes(1000) + .defaultTarget(DEFAULT_TARGET) + .build(); } private static BackoffPolicy createBackoffPolicy(final long delay, final TimeUnit unit) { diff --git a/rls/src/test/java/io/grpc/rls/RlsLoadBalancerTest.java b/rls/src/test/java/io/grpc/rls/RlsLoadBalancerTest.java index d03bb4bcf6f..5dfdf948d4b 100644 --- a/rls/src/test/java/io/grpc/rls/RlsLoadBalancerTest.java +++ b/rls/src/test/java/io/grpc/rls/RlsLoadBalancerTest.java @@ -140,16 +140,16 @@ public void setUp() throws Exception { .build(); fakeRlsServerImpl.setLookupTable( ImmutableMap.of( - new RouteLookupRequest(ImmutableMap.of( + RouteLookupRequest.create(ImmutableMap.of( "server", "fake-bigtable.googleapis.com", "service-key", "com.google", "method-key", "Search")), - new RouteLookupResponse(ImmutableList.of("wilderness"), "where are you?"), - new RouteLookupRequest(ImmutableMap.of( + RouteLookupResponse.create(ImmutableList.of("wilderness"), "where are you?"), + RouteLookupRequest.create(ImmutableMap.of( "server", "fake-bigtable.googleapis.com", "service-key", "com.google", "method-key", "Rescue")), - new RouteLookupResponse(ImmutableList.of("civilization"), "you are safe"))); + RouteLookupResponse.create(ImmutableList.of("civilization"), "you are safe"))); rlsLb = (RlsLoadBalancer) provider.newLoadBalancer(helper); rlsLb.cachingRlsLbClientBuilderProvider = new CachingRlsLbClientBuilderProvider() { diff --git a/rls/src/test/java/io/grpc/rls/RlsProtoConvertersTest.java b/rls/src/test/java/io/grpc/rls/RlsProtoConvertersTest.java index 958abea3926..215f8f2ac04 100644 --- a/rls/src/test/java/io/grpc/rls/RlsProtoConvertersTest.java +++ b/rls/src/test/java/io/grpc/rls/RlsProtoConvertersTest.java @@ -53,7 +53,7 @@ public void convert_toRequestProto() { RlsProtoData.RouteLookupRequest object = converter.convert(proto); - assertThat(object.getKeyMap()).containsExactly("key1", "val1"); + assertThat(object.keyMap()).containsExactly("key1", "val1"); } @Test @@ -61,7 +61,7 @@ public void convert_toRequestObject() { Converter converter = new RouteLookupRequestConverter().reverse(); RlsProtoData.RouteLookupRequest requestObject = - new RlsProtoData.RouteLookupRequest(ImmutableMap.of("key1", "val1")); + RlsProtoData.RouteLookupRequest.create(ImmutableMap.of("key1", "val1")); RouteLookupRequest proto = converter.convert(requestObject); @@ -80,7 +80,7 @@ public void convert_toResponseProto() { RlsProtoData.RouteLookupResponse object = converter.convert(proto); - assertThat(object.getTargets()).containsExactly("target"); + assertThat(object.targets()).containsExactly("target"); assertThat(object.getHeaderData()).isEqualTo("some header data"); } @@ -90,7 +90,7 @@ public void convert_toResponseObject() { new RouteLookupResponseConverter().reverse(); RlsProtoData.RouteLookupResponse object = - new RlsProtoData.RouteLookupResponse(ImmutableList.of("target"), "some header data"); + RlsProtoData.RouteLookupResponse.create(ImmutableList.of("target"), "some header data"); RouteLookupResponse proto = converter.convert(object); @@ -176,34 +176,35 @@ public void convert_jsonRlsConfig() throws IOException { + "}"; RouteLookupConfig expectedConfig = - new RouteLookupConfig( - ImmutableList.of( - new GrpcKeyBuilder( - ImmutableList.of(new Name("service1", "create")), + RouteLookupConfig.builder() + .grpcKeyBuilders(ImmutableList.of( + GrpcKeyBuilder.create( + ImmutableList.of(Name.create("service1", "create")), ImmutableList.of( - new NameMatcher("user", ImmutableList.of("User", "Parent")), - new NameMatcher("id", ImmutableList.of("X-Google-Id"))), + NameMatcher.create("user", ImmutableList.of("User", "Parent")), + NameMatcher.create("id", ImmutableList.of("X-Google-Id"))), ExtraKeys.DEFAULT, ImmutableMap.of()), - new GrpcKeyBuilder( - ImmutableList.of(new Name("service1", "*")), + GrpcKeyBuilder.create( + ImmutableList.of(Name.create("service1", "*")), ImmutableList.of( - new NameMatcher("user", ImmutableList.of("User", "Parent")), - new NameMatcher("password", ImmutableList.of("Password"))), + NameMatcher.create("user", ImmutableList.of("User", "Parent")), + NameMatcher.create("password", ImmutableList.of("Password"))), ExtraKeys.DEFAULT, ImmutableMap.of()), - new GrpcKeyBuilder( - ImmutableList.of(new Name("service3", "*")), + GrpcKeyBuilder.create( + ImmutableList.of(Name.create("service3", "*")), ImmutableList.of( - new NameMatcher("user", ImmutableList.of("User", "Parent"))), + NameMatcher.create("user", ImmutableList.of("User", "Parent"))), ExtraKeys.create("host-key", "service-key", "method-key"), - ImmutableMap.of("constKey1", "value1"))), - /* lookupService= */ "service1", - /* lookupServiceTimeoutInNanos= */ TimeUnit.SECONDS.toNanos(2), - /* maxAgeInNanos= */ TimeUnit.SECONDS.toNanos(300), - /* staleAgeInNanos= */ TimeUnit.SECONDS.toNanos(240), - /* cacheSizeBytes= */ 1000, - /* defaultTarget= */ "us_east_1.cloudbigtable.googleapis.com"); + ImmutableMap.of("constKey1", "value1")))) + .lookupService("service1") + .lookupServiceTimeoutInNanos(TimeUnit.SECONDS.toNanos(2)) + .maxAgeInNanos(TimeUnit.SECONDS.toNanos(300)) + .staleAgeInNanos(TimeUnit.SECONDS.toNanos(240)) + .cacheSizeBytes(1000) + .defaultTarget("us_east_1.cloudbigtable.googleapis.com") + .build(); RouteLookupConfigConverter converter = new RouteLookupConfigConverter(); @SuppressWarnings("unchecked") @@ -343,19 +344,20 @@ public void convert_jsonRlsConfig_defaultValues() throws IOException { + "}"; RouteLookupConfig expectedConfig = - new RouteLookupConfig( - ImmutableList.of( - new GrpcKeyBuilder( - ImmutableList.of(new Name("service1", null)), + RouteLookupConfig.builder() + .grpcKeyBuilders(ImmutableList.of( + GrpcKeyBuilder.create( + ImmutableList.of(Name.create("service1", null)), ImmutableList.of(), ExtraKeys.DEFAULT, - ImmutableMap.of())), - /* lookupService= */ "service1", - /* lookupServiceTimeoutInNanos= */ TimeUnit.SECONDS.toNanos(10), - /* maxAgeInNanos= */ TimeUnit.MINUTES.toNanos(5), - /* staleAgeInNanos= */ TimeUnit.MINUTES.toNanos(5), - /* cacheSizeBytes= */ 5 * 1024 * 1024, - /* defaultTarget= */ "us_east_1.cloudbigtable.googleapis.com"); + ImmutableMap.of()))) + .lookupService("service1") + .lookupServiceTimeoutInNanos(TimeUnit.SECONDS.toNanos(10)) + .maxAgeInNanos(TimeUnit.MINUTES.toNanos(5)) + .staleAgeInNanos(TimeUnit.MINUTES.toNanos(5)) + .cacheSizeBytes(5 * 1024 * 1024) + .defaultTarget("us_east_1.cloudbigtable.googleapis.com") + .build(); RouteLookupConfigConverter converter = new RouteLookupConfigConverter(); @SuppressWarnings("unchecked") @@ -399,21 +401,22 @@ public void convert_jsonRlsConfig_staleAgeCappedByMaxAge() throws IOException { + "}"; RouteLookupConfig expectedConfig = - new RouteLookupConfig( - ImmutableList.of( - new GrpcKeyBuilder( - ImmutableList.of(new Name("service1", "create")), + RouteLookupConfig.builder() + .grpcKeyBuilders(ImmutableList.of( + GrpcKeyBuilder.create( + ImmutableList.of(Name.create("service1", "create")), ImmutableList.of( - new NameMatcher("user", ImmutableList.of("User", "Parent")), - new NameMatcher("id", ImmutableList.of("X-Google-Id"))), + NameMatcher.create("user", ImmutableList.of("User", "Parent")), + NameMatcher.create("id", ImmutableList.of("X-Google-Id"))), ExtraKeys.DEFAULT, - ImmutableMap.of())), - /* lookupService= */ "service1", - /* lookupServiceTimeoutInNanos= */ TimeUnit.SECONDS.toNanos(2), - /* maxAgeInNanos= */ TimeUnit.SECONDS.toNanos(300), - /* staleAgeInNanos= */ TimeUnit.SECONDS.toNanos(300), - /* cacheSizeBytes= */ 1000, - /* defaultTarget= */ "us_east_1.cloudbigtable.googleapis.com"); + ImmutableMap.of()))) + .lookupService("service1") + .lookupServiceTimeoutInNanos(TimeUnit.SECONDS.toNanos(2)) + .maxAgeInNanos(TimeUnit.SECONDS.toNanos(300)) + .staleAgeInNanos(TimeUnit.SECONDS.toNanos(300)) + .cacheSizeBytes(1000) + .defaultTarget("us_east_1.cloudbigtable.googleapis.com") + .build(); RouteLookupConfigConverter converter = new RouteLookupConfigConverter(); @SuppressWarnings("unchecked") diff --git a/rls/src/test/java/io/grpc/rls/RlsRequestFactoryTest.java b/rls/src/test/java/io/grpc/rls/RlsRequestFactoryTest.java index 9b66944d787..82e8416563f 100644 --- a/rls/src/test/java/io/grpc/rls/RlsRequestFactoryTest.java +++ b/rls/src/test/java/io/grpc/rls/RlsRequestFactoryTest.java @@ -36,40 +36,41 @@ public class RlsRequestFactoryTest { private static final RouteLookupConfig RLS_CONFIG = - new RouteLookupConfig( - ImmutableList.of( - new GrpcKeyBuilder( - ImmutableList.of(new Name("com.google.service1", "Create")), + RouteLookupConfig.builder() + .grpcKeyBuilders(ImmutableList.of( + GrpcKeyBuilder.create( + ImmutableList.of(Name.create("com.google.service1", "Create")), ImmutableList.of( - new NameMatcher("user", ImmutableList.of("User", "Parent")), - new NameMatcher("id", ImmutableList.of("X-Google-Id"))), + NameMatcher.create("user", ImmutableList.of("User", "Parent")), + NameMatcher.create("id", ImmutableList.of("X-Google-Id"))), ExtraKeys.create("server-1", null, null), ImmutableMap.of("const-key-1", "const-value-1")), - new GrpcKeyBuilder( - ImmutableList.of(new Name("com.google.service1", "*")), + GrpcKeyBuilder.create( + ImmutableList.of(Name.create("com.google.service1", "*")), ImmutableList.of( - new NameMatcher("user", ImmutableList.of("User", "Parent")), - new NameMatcher("password", ImmutableList.of("Password"))), + NameMatcher.create("user", ImmutableList.of("User", "Parent")), + NameMatcher.create("password", ImmutableList.of("Password"))), ExtraKeys.create(null, "service-2", null), ImmutableMap.of("const-key-2", "const-value-2")), - new GrpcKeyBuilder( - ImmutableList.of(new Name("com.google.service2", "*")), + GrpcKeyBuilder.create( + ImmutableList.of(Name.create("com.google.service2", "*")), ImmutableList.of( - new NameMatcher("password", ImmutableList.of("Password"))), + NameMatcher.create("password", ImmutableList.of("Password"))), ExtraKeys.create(null, "service-3", "method-3"), ImmutableMap.of()), - new GrpcKeyBuilder( - ImmutableList.of(new Name("com.google.service3", "*")), + GrpcKeyBuilder.create( + ImmutableList.of(Name.create("com.google.service3", "*")), ImmutableList.of( - new NameMatcher("user", ImmutableList.of("User", "Parent"))), + NameMatcher.create("user", ImmutableList.of("User", "Parent"))), ExtraKeys.create(null, null, null), - ImmutableMap.of("const-key-4", "const-value-4"))), - /* lookupService= */ "bigtable-rls.googleapis.com", - /* lookupServiceTimeoutInNanos= */ TimeUnit.SECONDS.toNanos(2), - /* maxAgeInNanos= */ TimeUnit.SECONDS.toNanos(300), - /* staleAgeInNanos= */ TimeUnit.SECONDS.toNanos(240), - /* cacheSizeBytes= */ 1000, - /* defaultTarget= */ "us_east_1.cloudbigtable.googleapis.com"); + ImmutableMap.of("const-key-4", "const-value-4")))) + .lookupService("bigtable-rls.googleapis.com") + .lookupServiceTimeoutInNanos(TimeUnit.SECONDS.toNanos(2)) + .maxAgeInNanos(TimeUnit.SECONDS.toNanos(300)) + .staleAgeInNanos(TimeUnit.SECONDS.toNanos(240)) + .cacheSizeBytes(1000) + .defaultTarget("us_east_1.cloudbigtable.googleapis.com") + .build(); private final RlsRequestFactory factory = new RlsRequestFactory( RLS_CONFIG, "bigtable.googleapis.com"); @@ -82,7 +83,7 @@ public void create_pathMatches() { metadata.put(Metadata.Key.of("foo", Metadata.ASCII_STRING_MARSHALLER), "bar"); RouteLookupRequest request = factory.create("com.google.service1", "Create", metadata); - assertThat(request.getKeyMap()).containsExactly( + assertThat(request.keyMap()).containsExactly( "user", "test", "id", "123", "server-1", "bigtable.googleapis.com", @@ -98,7 +99,7 @@ public void create_pathFallbackMatches() { RouteLookupRequest request = factory.create("com.google.service1" , "Update", metadata); - assertThat(request.getKeyMap()).containsExactly( + assertThat(request.keyMap()).containsExactly( "user", "test", "password", "hunter2", "service-2", "com.google.service1", @@ -114,7 +115,7 @@ public void create_pathFallbackMatches_optionalHeaderMissing() { RouteLookupRequest request = factory.create("com.google.service1", "Update", metadata); - assertThat(request.getKeyMap()).containsExactly( + assertThat(request.keyMap()).containsExactly( "user", "test", "service-2", "com.google.service1", "const-key-2", "const-value-2"); @@ -128,7 +129,7 @@ public void create_unknownPath() { metadata.put(Metadata.Key.of("foo", Metadata.ASCII_STRING_MARSHALLER), "bar"); RouteLookupRequest request = factory.create("abc.def.service999", "Update", metadata); - assertThat(request.getKeyMap()).isEmpty(); + assertThat(request.keyMap()).isEmpty(); } @Test @@ -140,7 +141,7 @@ public void create_noMethodInRlsConfig() { RouteLookupRequest request = factory.create("com.google.service3", "Update", metadata); - assertThat(request.getKeyMap()).containsExactly( + assertThat(request.keyMap()).containsExactly( "user", "test", "const-key-4", "const-value-4"); } } From 7a35e3bbde78e63a1875e76d87dc3de29103b9e9 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Wed, 9 Feb 2022 08:37:54 -0800 Subject: [PATCH 43/68] android: fix code style of onBlockedStatusChanged() --- .../main/java/io/grpc/android/AndroidChannelBuilder.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/io/grpc/android/AndroidChannelBuilder.java b/android/src/main/java/io/grpc/android/AndroidChannelBuilder.java index e76b9d4ff83..dadab1c830c 100644 --- a/android/src/main/java/io/grpc/android/AndroidChannelBuilder.java +++ b/android/src/main/java/io/grpc/android/AndroidChannelBuilder.java @@ -297,10 +297,12 @@ private class DefaultNetworkCallback extends ConnectivityManager.NetworkCallback public void onAvailable(Network network) { delegate.enterIdle(); } + @Override - public void onBlockedStatusChanged (Network network, boolean blocked) { - if (!blocked) + public void onBlockedStatusChanged(Network network, boolean blocked) { + if (!blocked) { delegate.enterIdle(); + } } } From dc6eacccbf5a8c3c6212a79dcbefa1f1d44bfe3f Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Wed, 9 Feb 2022 13:20:07 -0800 Subject: [PATCH 44/68] core: limit total number of local-only transparent retries Limit the total number of local-only transparent retries per RPC for the moment to mitigate any potential bug that would trigger infinite loop of transparent retries. If the limit is exceeded, fail the RPC. --- .../io/grpc/internal/RetriableStream.java | 19 +++++++++++++++++++ .../io/grpc/internal/RetriableStreamTest.java | 17 +++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/core/src/main/java/io/grpc/internal/RetriableStream.java b/core/src/main/java/io/grpc/internal/RetriableStream.java index 8451ea6b2d7..d0430b5edbd 100644 --- a/core/src/main/java/io/grpc/internal/RetriableStream.java +++ b/core/src/main/java/io/grpc/internal/RetriableStream.java @@ -106,6 +106,7 @@ public void uncaughtException(Thread t, Throwable e) { *

Note that local-only transparent retries are unlimited. */ private final AtomicBoolean noMoreTransparentRetry = new AtomicBoolean(); + private final AtomicInteger localOnlyTransparentRetries = new AtomicInteger(); // Used for recording the share of buffer used for the current call out of the channel buffer. // This field would not be necessary if there is no channel buffer limit. @@ -851,6 +852,24 @@ public void run() { } return; } + if (rpcProgress == RpcProgress.MISCARRIED + && localOnlyTransparentRetries.incrementAndGet() > 10_000) { + commitAndRun(substream); + if (state.winningSubstream == substream) { + Status tooManyTransparentRetries = Status.INTERNAL + .withDescription("Too many transparent retries. Might be a bug in gRPC") + .withCause(status.asRuntimeException()); + listenerSerializeExecutor.execute( + new Runnable() { + @Override + public void run() { + isClosed = true; + masterListener.closed(tooManyTransparentRetries, rpcProgress, trailers); + } + }); + } + return; + } if (state.winningSubstream == null) { if (rpcProgress == RpcProgress.MISCARRIED diff --git a/core/src/test/java/io/grpc/internal/RetriableStreamTest.java b/core/src/test/java/io/grpc/internal/RetriableStreamTest.java index fe147a843a8..025c4f23e80 100644 --- a/core/src/test/java/io/grpc/internal/RetriableStreamTest.java +++ b/core/src/test/java/io/grpc/internal/RetriableStreamTest.java @@ -1709,6 +1709,23 @@ public void transparentRetry_unlimitedTimesOnMiscarried() { inOrder.verifyNoMoreInteractions(); verify(retriableStreamRecorder, never()).postCommit(); assertEquals(0, fakeClock.numPendingTasks()); + + ArgumentCaptor sublistenerCaptor = sublistenerCaptor3; + for (int i = 0; i < 9999; i++) { + ClientStream mockStream = mock(ClientStream.class); + doReturn(mockStream).when(retriableStreamRecorder).newSubstream(0); + sublistenerCaptor.getValue() + .closed(Status.fromCode(NON_RETRIABLE_STATUS_CODE), MISCARRIED, new Metadata()); + if (i == 9998) { + verify(retriableStreamRecorder).postCommit(); + verify(masterListener) + .closed(any(Status.class), any(RpcProgress.class), any(Metadata.class)); + } else { + verify(retriableStreamRecorder, never()).postCommit(); + sublistenerCaptor = ArgumentCaptor.forClass(ClientStreamListener.class); + verify(mockStream).start(sublistenerCaptor.capture()); + } + } } @Test From b35506b144873a9457a1c14433c3b99856ef7554 Mon Sep 17 00:00:00 2001 From: nafeabd <7875504+nafeabd@users.noreply.github.com> Date: Wed, 9 Feb 2022 15:21:36 -0600 Subject: [PATCH 45/68] netty: Include both x86 and Arm64 epoll as dependency for grpc-netty-shaded --- build.gradle | 1 + netty/shaded/build.gradle | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 4447ae1fdfe..c20d82c1f55 100644 --- a/build.gradle +++ b/build.gradle @@ -176,6 +176,7 @@ subprojects { netty: "io.netty:netty-codec-http2:[${nettyVersion}]", netty_epoll: "io.netty:netty-transport-native-epoll:${nettyVersion}:linux-x86_64", + netty_epoll_arm64: "io.netty:netty-transport-native-epoll:${nettyVersion}:linux-aarch_64", netty_proxy_handler: "io.netty:netty-handler-proxy:${nettyVersion}", // Keep the following references of tcnative version in sync whenever it's updated diff --git a/netty/shaded/build.gradle b/netty/shaded/build.gradle index 3bd1b087d85..e2409a86d99 100644 --- a/netty/shaded/build.gradle +++ b/netty/shaded/build.gradle @@ -18,7 +18,8 @@ sourceSets { testShadow {} } dependencies { implementation project(':grpc-netty') runtimeOnly libraries.netty_tcnative, - libraries.netty_epoll + libraries.netty_epoll, + libraries.netty_epoll_arm64 testShadowImplementation files(shadowJar), project(':grpc-testing-proto'), project(':grpc-testing'), From da617e6ecd186694e5234e651fcea6dc44aae92e Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Wed, 9 Feb 2022 15:36:08 -0800 Subject: [PATCH 46/68] rls: fix service name and method name separation Should consistently use the last '/' in the full method name as the separator between service name and method name in MethodDescriptor. --- rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java b/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java index bbd8f6986c3..da388be0503 100644 --- a/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java +++ b/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java @@ -880,13 +880,14 @@ final class RlsPicker extends SubchannelPicker { @Override public PickResult pickSubchannel(PickSubchannelArgs args) { - String[] methodName = args.getMethodDescriptor().getFullMethodName().split("/", 2); + String serviceName = args.getMethodDescriptor().getServiceName(); + String methodName = args.getMethodDescriptor().getBareMethodName(); RouteLookupRequest request = - requestFactory.create(methodName[0], methodName[1], args.getHeaders()); + requestFactory.create(serviceName, methodName, args.getHeaders()); final CachedRouteLookupResponse response = CachingRlsLbClient.this.get(request); logger.log(ChannelLogLevel.DEBUG, "Got route lookup cache entry for service={0}, method={1}, headers={2}:\n {3}", - new Object[]{methodName[0], methodName[1], args.getHeaders(), response}); + new Object[]{serviceName, methodName, args.getHeaders(), response}); if (response.getHeaderData() != null && !response.getHeaderData().isEmpty()) { Metadata headers = args.getHeaders(); From f987de7497b8cd0618fbbb6df1f1289387b7b33f Mon Sep 17 00:00:00 2001 From: "Penn (Dapeng) Zhang" Date: Mon, 7 Feb 2022 14:26:23 -0800 Subject: [PATCH 47/68] xds: migrate EnvoyServerProtoData.Listener data types to AutoValue --- .../java/io/grpc/xds/ClientXdsClient.java | 130 +-- .../io/grpc/xds/EnvoyServerProtoData.java | 312 ++----- ...ilterChainMatchingProtocolNegotiators.java | 37 +- .../java/io/grpc/xds/XdsServerWrapper.java | 24 +- .../io/grpc/xds/ClientXdsClientDataTest.java | 2 +- .../io/grpc/xds/ClientXdsClientTestBase.java | 28 +- ...rChainMatchingProtocolNegotiatorsTest.java | 783 +++++++++--------- .../XdsClientWrapperForServerSdsTestMisc.java | 11 +- .../io/grpc/xds/XdsSdsClientServerTest.java | 23 +- .../java/io/grpc/xds/XdsServerTestHelper.java | 48 +- .../io/grpc/xds/XdsServerWrapperTest.java | 46 +- 11 files changed, 635 insertions(+), 809 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java index 202bfedcca2..95b3bbfcfdf 100644 --- a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java +++ b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java @@ -111,7 +111,6 @@ import java.net.URI; import java.net.UnknownHostException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; @@ -398,7 +397,7 @@ static EnvoyServerProtoData.Listener parseServerSideListener( } } - List filterChains = new ArrayList<>(); + ImmutableList.Builder filterChains = ImmutableList.builder(); Set uniqueSet = new HashSet<>(); for (io.envoyproxy.envoy.config.listener.v3.FilterChain fc : proto.getFilterChainsList()) { filterChains.add( @@ -412,8 +411,8 @@ static EnvoyServerProtoData.Listener parseServerSideListener( null, certProviderInstances, parseHttpFilter); } - return new EnvoyServerProtoData.Listener( - proto.getName(), address, Collections.unmodifiableList(filterChains), defaultFilterChain); + return EnvoyServerProtoData.Listener.create( + proto.getName(), address, filterChains.build(), defaultFilterChain); } @VisibleForTesting @@ -470,7 +469,7 @@ static FilterChain parseFilterChain( FilterChainMatch filterChainMatch = parseFilterChainMatch(proto.getFilterChainMatch()); checkForUniqueness(uniqueSet, filterChainMatch); - return new FilterChain( + return FilterChain.create( proto.getName(), filterChainMatch, httpConnectionManager, @@ -673,18 +672,18 @@ private static List getCrossProduct(FilterChainMatch filterCha private static List expandOnPrefixRange(FilterChainMatch filterChainMatch) { ArrayList expandedList = new ArrayList<>(); - if (filterChainMatch.getPrefixRanges().isEmpty()) { + if (filterChainMatch.prefixRanges().isEmpty()) { expandedList.add(filterChainMatch); } else { - for (EnvoyServerProtoData.CidrRange cidrRange : filterChainMatch.getPrefixRanges()) { - expandedList.add(new FilterChainMatch(filterChainMatch.getDestinationPort(), - Arrays.asList(cidrRange), - Collections.unmodifiableList(filterChainMatch.getApplicationProtocols()), - Collections.unmodifiableList(filterChainMatch.getSourcePrefixRanges()), - filterChainMatch.getConnectionSourceType(), - Collections.unmodifiableList(filterChainMatch.getSourcePorts()), - Collections.unmodifiableList(filterChainMatch.getServerNames()), - filterChainMatch.getTransportProtocol())); + 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; @@ -694,18 +693,18 @@ private static List expandOnApplicationProtocols( Collection set) { ArrayList expandedList = new ArrayList<>(); for (FilterChainMatch filterChainMatch : set) { - if (filterChainMatch.getApplicationProtocols().isEmpty()) { + if (filterChainMatch.applicationProtocols().isEmpty()) { expandedList.add(filterChainMatch); } else { - for (String applicationProtocol : filterChainMatch.getApplicationProtocols()) { - expandedList.add(new FilterChainMatch(filterChainMatch.getDestinationPort(), - Collections.unmodifiableList(filterChainMatch.getPrefixRanges()), - Arrays.asList(applicationProtocol), - Collections.unmodifiableList(filterChainMatch.getSourcePrefixRanges()), - filterChainMatch.getConnectionSourceType(), - Collections.unmodifiableList(filterChainMatch.getSourcePorts()), - Collections.unmodifiableList(filterChainMatch.getServerNames()), - filterChainMatch.getTransportProtocol())); + 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())); } } } @@ -716,18 +715,18 @@ private static List expandOnSourcePrefixRange( Collection set) { ArrayList expandedList = new ArrayList<>(); for (FilterChainMatch filterChainMatch : set) { - if (filterChainMatch.getSourcePrefixRanges().isEmpty()) { + if (filterChainMatch.sourcePrefixRanges().isEmpty()) { expandedList.add(filterChainMatch); } else { - for (EnvoyServerProtoData.CidrRange cidrRange : filterChainMatch.getSourcePrefixRanges()) { - expandedList.add(new FilterChainMatch(filterChainMatch.getDestinationPort(), - Collections.unmodifiableList(filterChainMatch.getPrefixRanges()), - Collections.unmodifiableList(filterChainMatch.getApplicationProtocols()), - Arrays.asList(cidrRange), - filterChainMatch.getConnectionSourceType(), - Collections.unmodifiableList(filterChainMatch.getSourcePorts()), - Collections.unmodifiableList(filterChainMatch.getServerNames()), - filterChainMatch.getTransportProtocol())); + 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())); } } } @@ -737,18 +736,18 @@ private static List expandOnSourcePrefixRange( private static List expandOnSourcePorts(Collection set) { ArrayList expandedList = new ArrayList<>(); for (FilterChainMatch filterChainMatch : set) { - if (filterChainMatch.getSourcePorts().isEmpty()) { + if (filterChainMatch.sourcePorts().isEmpty()) { expandedList.add(filterChainMatch); } else { - for (Integer sourcePort : filterChainMatch.getSourcePorts()) { - expandedList.add(new FilterChainMatch(filterChainMatch.getDestinationPort(), - Collections.unmodifiableList(filterChainMatch.getPrefixRanges()), - Collections.unmodifiableList(filterChainMatch.getApplicationProtocols()), - Collections.unmodifiableList(filterChainMatch.getSourcePrefixRanges()), - filterChainMatch.getConnectionSourceType(), - Arrays.asList(sourcePort), - Collections.unmodifiableList(filterChainMatch.getServerNames()), - filterChainMatch.getTransportProtocol())); + 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())); } } } @@ -758,18 +757,18 @@ private static List expandOnSourcePorts(Collection expandOnServerNames(Collection set) { ArrayList expandedList = new ArrayList<>(); for (FilterChainMatch filterChainMatch : set) { - if (filterChainMatch.getServerNames().isEmpty()) { + if (filterChainMatch.serverNames().isEmpty()) { expandedList.add(filterChainMatch); } else { - for (String serverName : filterChainMatch.getServerNames()) { - expandedList.add(new FilterChainMatch(filterChainMatch.getDestinationPort(), - Collections.unmodifiableList(filterChainMatch.getPrefixRanges()), - Collections.unmodifiableList(filterChainMatch.getApplicationProtocols()), - Collections.unmodifiableList(filterChainMatch.getSourcePrefixRanges()), - filterChainMatch.getConnectionSourceType(), - Collections.unmodifiableList(filterChainMatch.getSourcePorts()), - Arrays.asList(serverName), - filterChainMatch.getTransportProtocol())); + 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())); } } } @@ -779,16 +778,17 @@ private static List expandOnServerNames(Collection prefixRanges = new ArrayList<>(); - List sourcePrefixRanges = new ArrayList<>(); + ImmutableList.Builder prefixRanges = ImmutableList.builder(); + ImmutableList.Builder sourcePrefixRanges = ImmutableList.builder(); try { for (io.envoyproxy.envoy.config.core.v3.CidrRange range : proto.getPrefixRangesList()) { - prefixRanges.add(new CidrRange(range.getAddressPrefix(), range.getPrefixLen().getValue())); + prefixRanges.add( + CidrRange.create(range.getAddressPrefix(), range.getPrefixLen().getValue())); } for (io.envoyproxy.envoy.config.core.v3.CidrRange range : proto.getSourcePrefixRangesList()) { sourcePrefixRanges.add( - new CidrRange(range.getAddressPrefix(), range.getPrefixLen().getValue())); + CidrRange.create(range.getAddressPrefix(), range.getPrefixLen().getValue())); } } catch (UnknownHostException e) { throw new ResourceInvalidException("Failed to create CidrRange", e); @@ -807,14 +807,14 @@ private static FilterChainMatch parseFilterChainMatch( default: throw new ResourceInvalidException("Unknown source-type: " + proto.getSourceType()); } - return new FilterChainMatch( + return FilterChainMatch.create( proto.getDestinationPort().getValue(), - prefixRanges, - proto.getApplicationProtocolsList(), - sourcePrefixRanges, + prefixRanges.build(), + ImmutableList.copyOf(proto.getApplicationProtocolsList()), + sourcePrefixRanges.build(), sourceType, - proto.getSourcePortsList(), - proto.getServerNamesList(), + ImmutableList.copyOf(proto.getSourcePortsList()), + ImmutableList.copyOf(proto.getServerNamesList()), proto.getTransportProtocol()); } diff --git a/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java b/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java index 09318a8c150..2fa6b868c65 100644 --- a/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java +++ b/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java @@ -16,17 +16,14 @@ package io.grpc.xds; -import static com.google.common.base.Preconditions.checkNotNull; - +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 io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; import io.grpc.Internal; import io.grpc.xds.internal.sds.SslContextProviderSupplier; import java.net.InetAddress; import java.net.UnknownHostException; -import java.util.Collections; -import java.util.List; import java.util.Objects; import javax.annotation.Nullable; @@ -142,47 +139,16 @@ public int hashCode() { } } - static final class CidrRange { - private final InetAddress addressPrefix; - private final int prefixLen; - - CidrRange(String addressPrefix, int prefixLen) throws UnknownHostException { - this.addressPrefix = InetAddress.getByName(addressPrefix); - this.prefixLen = prefixLen; - } - - public InetAddress getAddressPrefix() { - return addressPrefix; - } - - public int getPrefixLen() { - return prefixLen; - } + @AutoValue + abstract static class CidrRange { - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - CidrRange cidrRange = (CidrRange) o; - return prefixLen == cidrRange.prefixLen - && java.util.Objects.equals(addressPrefix, cidrRange.addressPrefix); - } + abstract InetAddress addressPrefix(); - @Override - public int hashCode() { - return java.util.Objects.hash(addressPrefix, prefixLen); - } + abstract int prefixLen(); - @Override - public String toString() { - return "CidrRange{" - + "addressPrefix='" + addressPrefix + '\'' - + ", prefixLen=" + prefixLen - + '}'; + static CidrRange create(String addressPrefix, int prefixLen) throws UnknownHostException { + return new AutoValue_EnvoyServerProtoData_CidrRange( + InetAddress.getByName(addressPrefix), prefixLen); } } @@ -201,259 +167,91 @@ enum ConnectionSourceType { * Corresponds to Envoy proto message * {@link io.envoyproxy.envoy.api.v2.listener.FilterChainMatch}. */ - static final class FilterChainMatch { - private final int destinationPort; - private final List prefixRanges; - private final List applicationProtocols; - private final List sourcePrefixRanges; - private final ConnectionSourceType sourceType; - private final List sourcePorts; - private final List serverNames; - private final String transportProtocol; - - @VisibleForTesting - FilterChainMatch( - int destinationPort, - List prefixRanges, - List applicationProtocols, - List sourcePrefixRanges, - ConnectionSourceType sourceType, - List sourcePorts, - List serverNames, - String transportProtocol) { - this.destinationPort = destinationPort; - this.prefixRanges = Collections.unmodifiableList(prefixRanges); - this.applicationProtocols = Collections.unmodifiableList(applicationProtocols); - this.sourcePrefixRanges = sourcePrefixRanges; - this.sourceType = sourceType; - this.sourcePorts = sourcePorts; - this.serverNames = Collections.unmodifiableList(serverNames); - this.transportProtocol = transportProtocol; - } - - public int getDestinationPort() { - return destinationPort; - } + @AutoValue + abstract static class FilterChainMatch { - public List getPrefixRanges() { - return prefixRanges; - } + abstract int destinationPort(); - public List getApplicationProtocols() { - return applicationProtocols; - } + abstract ImmutableList prefixRanges(); - public List getSourcePrefixRanges() { - return sourcePrefixRanges; - } + abstract ImmutableList applicationProtocols(); - public ConnectionSourceType getConnectionSourceType() { - return sourceType; - } + abstract ImmutableList sourcePrefixRanges(); - public List getSourcePorts() { - return sourcePorts; - } + abstract ConnectionSourceType connectionSourceType(); - public List getServerNames() { - return serverNames; - } + abstract ImmutableList sourcePorts(); - public String getTransportProtocol() { - return transportProtocol; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - FilterChainMatch that = (FilterChainMatch) o; - return destinationPort == that.destinationPort - && Objects.equals(prefixRanges, that.prefixRanges) - && Objects.equals(applicationProtocols, that.applicationProtocols) - && Objects.equals(sourcePrefixRanges, that.sourcePrefixRanges) - && sourceType == that.sourceType - && Objects.equals(sourcePorts, that.sourcePorts) - && Objects.equals(serverNames, that.serverNames) - && Objects.equals(transportProtocol, that.transportProtocol); - } + abstract ImmutableList serverNames(); - @Override - public int hashCode() { - return Objects.hash( - destinationPort, - prefixRanges, - applicationProtocols, - sourcePrefixRanges, - sourceType, - sourcePorts, - serverNames, - transportProtocol); - } + abstract String transportProtocol(); - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("destinationPort", destinationPort) - .add("prefixRanges", prefixRanges) - .add("applicationProtocols", applicationProtocols) - .add("sourcePrefixRanges", sourcePrefixRanges) - .add("sourceType", sourceType) - .add("sourcePorts", sourcePorts) - .add("serverNames", serverNames) - .add("transportProtocol", transportProtocol) - .toString(); + public static FilterChainMatch create(int destinationPort, + ImmutableList prefixRanges, + ImmutableList applicationProtocols, ImmutableList sourcePrefixRanges, + ConnectionSourceType connectionSourceType, ImmutableList sourcePorts, + ImmutableList serverNames, String transportProtocol) { + return new AutoValue_EnvoyServerProtoData_FilterChainMatch( + destinationPort, prefixRanges, applicationProtocols, sourcePrefixRanges, + connectionSourceType, sourcePorts, serverNames, transportProtocol); } } /** * Corresponds to Envoy proto message {@link io.envoyproxy.envoy.api.v2.listener.FilterChain}. */ - static final class FilterChain { + @AutoValue + abstract static class FilterChain { + // possibly empty - private final String name; + abstract String name(); + // TODO(sanjaypujare): flatten structure by moving FilterChainMatch class members here. - private final FilterChainMatch filterChainMatch; - private final HttpConnectionManager httpConnectionManager; + abstract FilterChainMatch filterChainMatch(); + + abstract HttpConnectionManager httpConnectionManager(); + @Nullable - private final SslContextProviderSupplier sslContextProviderSupplier; + abstract SslContextProviderSupplier sslContextProviderSupplier(); - FilterChain( + static FilterChain create( String name, FilterChainMatch filterChainMatch, HttpConnectionManager httpConnectionManager, @Nullable DownstreamTlsContext downstreamTlsContext, TlsContextManager tlsContextManager) { - SslContextProviderSupplier sslContextProviderSupplier1 = downstreamTlsContext == null ? null - : new SslContextProviderSupplier(downstreamTlsContext, tlsContextManager); - this.name = checkNotNull(name, "name"); - // TODO(chengyuanzhang): enforce non-null, change tests to use a default/empty - // FilterChainMatch instead of null, as that's how the proto is converted. - this.filterChainMatch = filterChainMatch; - this.sslContextProviderSupplier = sslContextProviderSupplier1; - this.httpConnectionManager = checkNotNull(httpConnectionManager, "httpConnectionManager"); - } - - String getName() { - return name; - } - - public FilterChainMatch getFilterChainMatch() { - return filterChainMatch; - } - - HttpConnectionManager getHttpConnectionManager() { - return httpConnectionManager; - } - - @Nullable - public SslContextProviderSupplier getSslContextProviderSupplier() { - return sslContextProviderSupplier; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - FilterChain that = (FilterChain) o; - return Objects.equals(name, that.name) - && Objects.equals(filterChainMatch, that.filterChainMatch) - && Objects.equals(httpConnectionManager, that.httpConnectionManager) - && Objects.equals(sslContextProviderSupplier, that.sslContextProviderSupplier); - } - - @Override - public int hashCode() { - return Objects.hash( + SslContextProviderSupplier sslContextProviderSupplier = + downstreamTlsContext == null + ? null : new SslContextProviderSupplier(downstreamTlsContext, tlsContextManager); + return new AutoValue_EnvoyServerProtoData_FilterChain( name, filterChainMatch, httpConnectionManager, sslContextProviderSupplier); } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("name", name) - .add("filterChainMatch", filterChainMatch) - .add("httpConnectionManager", httpConnectionManager) - .add("sslContextProviderSupplier", sslContextProviderSupplier) - .toString(); - } } /** * Corresponds to Envoy proto message {@link io.envoyproxy.envoy.api.v2.Listener} & related * classes. */ - public static final class Listener { - private final String name; - @Nullable - private final String address; - private final List filterChains; - @Nullable - private final FilterChain defaultFilterChain; - - /** Construct a Listener. */ - public Listener(String name, @Nullable String address, - List filterChains, @Nullable FilterChain defaultFilterChain) { - this.name = checkNotNull(name, "name"); - this.address = address; - this.filterChains = Collections.unmodifiableList(checkNotNull(filterChains, "filterChains")); - this.defaultFilterChain = defaultFilterChain; - } + @AutoValue + abstract static class Listener { - public String getName() { - return name; - } + abstract String name(); @Nullable - public String getAddress() { - return address; - } + abstract String address(); - public List getFilterChains() { - return filterChains; - } + abstract ImmutableList filterChains(); @Nullable - public FilterChain getDefaultFilterChain() { - return defaultFilterChain; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Listener listener = (Listener) o; - return Objects.equals(name, listener.name) - && Objects.equals(address, listener.address) - && Objects.equals(filterChains, listener.filterChains) - && Objects.equals(defaultFilterChain, listener.defaultFilterChain); - } + abstract FilterChain defaultFilterChain(); - @Override - public int hashCode() { - return Objects.hash(name, address, filterChains, defaultFilterChain); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("name", name) - .add("address", address) - .add("filterChains", filterChains) - .add("defaultFilterChain", defaultFilterChain) - .toString(); + static Listener create( + String name, + @Nullable String address, + ImmutableList filterChains, + @Nullable FilterChain defaultFilterChain) { + return new AutoValue_EnvoyServerProtoData_Listener(name, address, filterChains, + defaultFilterChain); } } } diff --git a/xds/src/main/java/io/grpc/xds/FilterChainMatchingProtocolNegotiators.java b/xds/src/main/java/io/grpc/xds/FilterChainMatchingProtocolNegotiators.java index 5d72151db50..e75440225dc 100644 --- a/xds/src/main/java/io/grpc/xds/FilterChainMatchingProtocolNegotiators.java +++ b/xds/src/main/java/io/grpc/xds/FilterChainMatchingProtocolNegotiators.java @@ -39,7 +39,6 @@ import io.grpc.xds.EnvoyServerProtoData.ConnectionSourceType; import io.grpc.xds.EnvoyServerProtoData.FilterChain; import io.grpc.xds.EnvoyServerProtoData.FilterChainMatch; -import io.grpc.xds.FilterChainMatchingProtocolNegotiators.FilterChainMatchingHandler.FilterChainSelector; import io.grpc.xds.XdsServerWrapper.ServerRoutingConfig; import io.grpc.xds.internal.Matchers.CidrMatcher; import io.grpc.xds.internal.sds.SslContextProviderSupplier; @@ -189,7 +188,7 @@ SelectedConfig select(InetSocketAddress localAddr, InetSocketAddress remoteAddr) if (filterChains.size() == 1) { FilterChain selected = Iterables.getOnlyElement(filterChains); return new SelectedConfig( - routingConfigs.get(selected), selected.getSslContextProviderSupplier()); + routingConfigs.get(selected), selected.sslContextProviderSupplier()); } if (defaultRoutingConfig.get() != null) { return new SelectedConfig(defaultRoutingConfig, defaultSslContextProviderSupplier); @@ -202,9 +201,9 @@ private static Collection filterOnApplicationProtocols( Collection filterChains) { ArrayList filtered = new ArrayList<>(filterChains.size()); for (FilterChain filterChain : filterChains) { - FilterChainMatch filterChainMatch = filterChain.getFilterChainMatch(); + FilterChainMatch filterChainMatch = filterChain.filterChainMatch(); - if (filterChainMatch.getApplicationProtocols().isEmpty()) { + if (filterChainMatch.applicationProtocols().isEmpty()) { filtered.add(filterChain); } } @@ -216,9 +215,9 @@ private static Collection filterOnTransportProtocol( Collection filterChains) { ArrayList filtered = new ArrayList<>(filterChains.size()); for (FilterChain filterChain : filterChains) { - FilterChainMatch filterChainMatch = filterChain.getFilterChainMatch(); + FilterChainMatch filterChainMatch = filterChain.filterChainMatch(); - String transportProtocol = filterChainMatch.getTransportProtocol(); + String transportProtocol = filterChainMatch.transportProtocol(); if (Strings.isNullOrEmpty(transportProtocol) || "raw_buffer".equals(transportProtocol)) { filtered.add(filterChain); } @@ -231,9 +230,9 @@ private static Collection filterOnServerNames( Collection filterChains) { ArrayList filtered = new ArrayList<>(filterChains.size()); for (FilterChain filterChain : filterChains) { - FilterChainMatch filterChainMatch = filterChain.getFilterChainMatch(); + FilterChainMatch filterChainMatch = filterChain.filterChainMatch(); - if (filterChainMatch.getServerNames().isEmpty()) { + if (filterChainMatch.serverNames().isEmpty()) { filtered.add(filterChain); } } @@ -245,9 +244,9 @@ private static Collection filterOnDestinationPort( Collection filterChains) { ArrayList filtered = new ArrayList<>(filterChains.size()); for (FilterChain filterChain : filterChains) { - FilterChainMatch filterChainMatch = filterChain.getFilterChainMatch(); + FilterChainMatch filterChainMatch = filterChain.filterChainMatch(); - if (filterChainMatch.getDestinationPort() + if (filterChainMatch.destinationPort() == UInt32Value.getDefaultInstance().getValue()) { filtered.add(filterChain); } @@ -260,9 +259,9 @@ private static Collection filterOnSourcePort( ArrayList filteredOnMatch = new ArrayList<>(filterChains.size()); ArrayList filteredOnEmpty = new ArrayList<>(filterChains.size()); for (FilterChain filterChain : filterChains) { - FilterChainMatch filterChainMatch = filterChain.getFilterChainMatch(); + FilterChainMatch filterChainMatch = filterChain.filterChainMatch(); - List sourcePortsToMatch = filterChainMatch.getSourcePorts(); + List sourcePortsToMatch = filterChainMatch.sourcePorts(); if (sourcePortsToMatch.isEmpty()) { filteredOnEmpty.add(filterChain); } else if (sourcePortsToMatch.contains(sourcePort)) { @@ -278,9 +277,9 @@ private static Collection filterOnSourceType( InetAddress destAddress) { ArrayList filtered = new ArrayList<>(filterChains.size()); for (FilterChain filterChain : filterChains) { - FilterChainMatch filterChainMatch = filterChain.getFilterChainMatch(); + FilterChainMatch filterChainMatch = filterChain.filterChainMatch(); ConnectionSourceType sourceType = - filterChainMatch.getConnectionSourceType(); + filterChainMatch.connectionSourceType(); boolean matching = false; if (sourceType == ConnectionSourceType.SAME_IP_OR_LOOPBACK) { @@ -305,18 +304,18 @@ private static int getMatchingPrefixLength( boolean isIPv6 = address instanceof Inet6Address; List cidrRanges = forDestination - ? filterChainMatch.getPrefixRanges() - : filterChainMatch.getSourcePrefixRanges(); + ? filterChainMatch.prefixRanges() + : filterChainMatch.sourcePrefixRanges(); int matchingPrefixLength; if (cidrRanges.isEmpty()) { // if there is no CidrRange assume 0-length match matchingPrefixLength = 0; } else { matchingPrefixLength = -1; for (CidrRange cidrRange : cidrRanges) { - InetAddress cidrAddr = cidrRange.getAddressPrefix(); + InetAddress cidrAddr = cidrRange.addressPrefix(); boolean cidrIsIpv6 = cidrAddr instanceof Inet6Address; if (isIPv6 == cidrIsIpv6) { - int prefixLen = cidrRange.getPrefixLen(); + int prefixLen = cidrRange.prefixLen(); CidrMatcher matcher = CidrMatcher.create(cidrAddr, prefixLen); if (matcher.matches(address) && prefixLen > matchingPrefixLength) { matchingPrefixLength = prefixLen; @@ -335,7 +334,7 @@ private static Collection filterOnIpAddress( int topMatchingPrefixLen = -1; for (FilterChain filterChain : filterChains) { int currentMatchingPrefixLen = getMatchingPrefixLength( - filterChain.getFilterChainMatch(), address, forDestination); + filterChain.filterChainMatch(), address, forDestination); if (currentMatchingPrefixLen >= 0) { if (currentMatchingPrefixLen < topMatchingPrefixLen) { diff --git a/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java b/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java index 58c9692fa67..eef4a9c2fb3 100644 --- a/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java +++ b/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java @@ -386,8 +386,8 @@ public void run() { releaseSuppliersInFlight(); pendingRds.clear(); } - filterChains = update.listener().getFilterChains(); - defaultFilterChain = update.listener().getDefaultFilterChain(); + filterChains = update.listener().filterChains(); + defaultFilterChain = update.listener().defaultFilterChain(); List allFilterChains = filterChains; if (defaultFilterChain != null) { allFilterChains = new ArrayList<>(filterChains); @@ -395,7 +395,7 @@ public void run() { } Set allRds = new HashSet<>(); for (FilterChain filterChain : allFilterChains) { - HttpConnectionManager hcm = filterChain.getHttpConnectionManager(); + HttpConnectionManager hcm = filterChain.httpConnectionManager(); if (hcm.virtualHosts() == null) { RouteDiscoveryState rdsState = routeDiscoveryStates.get(hcm.rdsName()); if (rdsState == null) { @@ -478,7 +478,7 @@ private void updateSelector() { } FilterChainSelector selector = new FilterChainSelector( Collections.unmodifiableMap(filterChainRouting), - defaultFilterChain == null ? null : defaultFilterChain.getSslContextProviderSupplier(), + defaultFilterChain == null ? null : defaultFilterChain.sslContextProviderSupplier(), defaultFilterChain == null ? new AtomicReference() : generateRoutingConfig(defaultFilterChain)); List toRelease = getSuppliersInUse(); @@ -491,7 +491,7 @@ private void updateSelector() { } private AtomicReference generateRoutingConfig(FilterChain filterChain) { - HttpConnectionManager hcm = filterChain.getHttpConnectionManager(); + HttpConnectionManager hcm = filterChain.httpConnectionManager(); if (hcm.virtualHosts() != null) { ImmutableMap interceptors = generatePerRouteInterceptors( hcm.httpFilterConfigs(), hcm.virtualHosts()); @@ -602,8 +602,8 @@ private List getSuppliersInUse() { FilterChainSelector selector = filterChainSelectorManager.getSelectorToUpdateSelector(); if (selector != null) { for (FilterChain f: selector.getRoutingConfigs().keySet()) { - if (f.getSslContextProviderSupplier() != null) { - toRelease.add(f.getSslContextProviderSupplier()); + if (f.sslContextProviderSupplier() != null) { + toRelease.add(f.sslContextProviderSupplier()); } } SslContextProviderSupplier defaultSupplier = @@ -618,13 +618,13 @@ private List getSuppliersInUse() { private void releaseSuppliersInFlight() { SslContextProviderSupplier supplier; for (FilterChain filterChain : filterChains) { - supplier = filterChain.getSslContextProviderSupplier(); + supplier = filterChain.sslContextProviderSupplier(); if (supplier != null) { supplier.close(); } } if (defaultFilterChain != null - && (supplier = defaultFilterChain.getSslContextProviderSupplier()) != null) { + && (supplier = defaultFilterChain.sslContextProviderSupplier()) != null) { supplier.close(); } } @@ -689,20 +689,20 @@ public void run() { private void updateRdsRoutingConfig() { for (FilterChain filterChain : savedRdsRoutingConfigRef.keySet()) { - if (resourceName.equals(filterChain.getHttpConnectionManager().rdsName())) { + if (resourceName.equals(filterChain.httpConnectionManager().rdsName())) { ServerRoutingConfig updatedRoutingConfig; if (savedVirtualHosts == null) { updatedRoutingConfig = ServerRoutingConfig.FAILING_ROUTING_CONFIG; } else { ImmutableMap updatedInterceptors = generatePerRouteInterceptors( - filterChain.getHttpConnectionManager().httpFilterConfigs(), + filterChain.httpConnectionManager().httpFilterConfigs(), savedVirtualHosts); updatedRoutingConfig = ServerRoutingConfig.create(savedVirtualHosts, updatedInterceptors); } logger.log(Level.FINEST, "Updating filter chain {0} rds routing config: {1}", - new Object[]{filterChain.getName(), updatedRoutingConfig}); + new Object[]{filterChain.name(), updatedRoutingConfig}); savedRdsRoutingConfigRef.get(filterChain).set(updatedRoutingConfig); } } diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java index b195a63eec4..2b75c02d4dc 100644 --- a/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java +++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java @@ -2180,7 +2180,7 @@ public void parseFilterChain_noName() throws ResourceInvalidException { EnvoyServerProtoData.FilterChain parsedFilterChain2 = ClientXdsClient.parseFilterChain( filterChain2, new HashSet(), null, filterRegistry, null, null, true /* does not matter */); - assertThat(parsedFilterChain1.getName()).isEqualTo(parsedFilterChain2.getName()); + assertThat(parsedFilterChain1.name()).isEqualTo(parsedFilterChain2.name()); } @Test diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java index c0ad0b47c55..ba182be76d1 100644 --- a/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java +++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java @@ -1282,10 +1282,10 @@ public void rdsResourcesDeletedByLdsTcpListener() { call.sendResponse(LDS, packedListener, VERSION_1, "0000"); verify(ldsResourceWatcher).onChanged(ldsUpdateCaptor.capture()); - assertThat(ldsUpdateCaptor.getValue().listener().getFilterChains()).hasSize(1); + assertThat(ldsUpdateCaptor.getValue().listener().filterChains()).hasSize(1); FilterChain parsedFilterChain = Iterables.getOnlyElement( - ldsUpdateCaptor.getValue().listener().getFilterChains()); - assertThat(parsedFilterChain.getHttpConnectionManager().rdsName()).isEqualTo(RDS_RESOURCE); + ldsUpdateCaptor.getValue().listener().filterChains()); + assertThat(parsedFilterChain.httpConnectionManager().rdsName()).isEqualTo(RDS_RESOURCE); verifyResourceMetadataAcked(LDS, LISTENER_RESOURCE, packedListener, VERSION_1, TIME_INCREMENT); verifyResourceMetadataRequested(RDS, RDS_RESOURCE); verifySubscribedResourcesMetadataSizes(1, 0, 1, 0); @@ -1310,10 +1310,10 @@ public void rdsResourcesDeletedByLdsTcpListener() { Any.pack(mf.buildListenerWithFilterChain(LISTENER_RESOURCE, 7000, "0.0.0.0", filterChain)); call.sendResponse(LDS, packedListener, VERSION_2, "0001"); verify(ldsResourceWatcher, times(2)).onChanged(ldsUpdateCaptor.capture()); - assertThat(ldsUpdateCaptor.getValue().listener().getFilterChains()).hasSize(1); + assertThat(ldsUpdateCaptor.getValue().listener().filterChains()).hasSize(1); parsedFilterChain = Iterables.getOnlyElement( - ldsUpdateCaptor.getValue().listener().getFilterChains()); - assertThat(parsedFilterChain.getHttpConnectionManager().virtualHosts()).hasSize(VHOST_SIZE); + ldsUpdateCaptor.getValue().listener().filterChains()); + assertThat(parsedFilterChain.httpConnectionManager().virtualHosts()).hasSize(VHOST_SIZE); verify(rdsResourceWatcher).onResourceDoesNotExist(RDS_RESOURCE); verifyResourceMetadataDoesNotExist(RDS, RDS_RESOURCE); verifyResourceMetadataAcked( @@ -2608,15 +2608,15 @@ public void serverSideListenerFound() { ResourceType.LDS, Collections.singletonList(LISTENER_RESOURCE), "0", "0000", NODE); verify(ldsResourceWatcher).onChanged(ldsUpdateCaptor.capture()); EnvoyServerProtoData.Listener parsedListener = ldsUpdateCaptor.getValue().listener(); - assertThat(parsedListener.getName()).isEqualTo(LISTENER_RESOURCE); - assertThat(parsedListener.getAddress()).isEqualTo("0.0.0.0:7000"); - assertThat(parsedListener.getDefaultFilterChain()).isNull(); - assertThat(parsedListener.getFilterChains()).hasSize(1); - FilterChain parsedFilterChain = Iterables.getOnlyElement(parsedListener.getFilterChains()); - assertThat(parsedFilterChain.getFilterChainMatch().getApplicationProtocols()).isEmpty(); - assertThat(parsedFilterChain.getHttpConnectionManager().rdsName()) + assertThat(parsedListener.name()).isEqualTo(LISTENER_RESOURCE); + assertThat(parsedListener.address()).isEqualTo("0.0.0.0:7000"); + assertThat(parsedListener.defaultFilterChain()).isNull(); + assertThat(parsedListener.filterChains()).hasSize(1); + FilterChain parsedFilterChain = Iterables.getOnlyElement(parsedListener.filterChains()); + assertThat(parsedFilterChain.filterChainMatch().applicationProtocols()).isEmpty(); + assertThat(parsedFilterChain.httpConnectionManager().rdsName()) .isEqualTo("route-foo.googleapis.com"); - assertThat(parsedFilterChain.getHttpConnectionManager().httpFilterConfigs().get(0).filterConfig) + assertThat(parsedFilterChain.httpConnectionManager().httpFilterConfigs().get(0).filterConfig) .isEqualTo(RouterFilter.ROUTER_CONFIG); assertThat(fakeClock.getPendingTasks(LDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); diff --git a/xds/src/test/java/io/grpc/xds/FilterChainMatchingProtocolNegotiatorsTest.java b/xds/src/test/java/io/grpc/xds/FilterChainMatchingProtocolNegotiatorsTest.java index 32a7bc19b32..a4efb226ce9 100644 --- a/xds/src/test/java/io/grpc/xds/FilterChainMatchingProtocolNegotiatorsTest.java +++ b/xds/src/test/java/io/grpc/xds/FilterChainMatchingProtocolNegotiatorsTest.java @@ -60,7 +60,6 @@ import java.net.SocketAddress; import java.net.UnknownHostException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -90,6 +89,16 @@ public class FilterChainMatchingProtocolNegotiatorsTest { @Mock private ProtocolNegotiator mockDelegate; private FilterChainSelectorManager selectorManager = new FilterChainSelectorManager(); + private static final EnvoyServerProtoData.FilterChainMatch DEFAULT_FILTER_CHAIN_MATCH = + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of(), + EnvoyServerProtoData.ConnectionSourceType.ANY, + ImmutableList.of(), + ImmutableList.of(), + ""); private static final HttpConnectionManager HTTP_CONNECTION_MANAGER = createRds("routing-config"); private static final String LOCAL_IP = "10.1.2.3"; // dest private static final String REMOTE_IP = "10.4.2.3"; // source @@ -188,18 +197,18 @@ public void filterSelectorChange_drainsConnection() { @Test public void singleFilterChainWithoutAlpn() throws Exception { EnvoyServerProtoData.FilterChainMatch filterChainMatch = - new EnvoyServerProtoData.FilterChainMatch( - 0, - Arrays.asList(), - Arrays.asList(), - Arrays.asList(), - EnvoyServerProtoData.ConnectionSourceType.ANY, - Arrays.asList(), - Arrays.asList(), - null); + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of(), + EnvoyServerProtoData.ConnectionSourceType.ANY, + ImmutableList.of(), + ImmutableList.of(), + ""); EnvoyServerProtoData.DownstreamTlsContext tlsContext = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT1", "VA1"); - EnvoyServerProtoData.FilterChain filterChain = new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChain filterChain = EnvoyServerProtoData.FilterChain.create( "filter-chain-foo", filterChainMatch, HTTP_CONNECTION_MANAGER, tlsContext, tlsContextManager); @@ -213,7 +222,7 @@ public void singleFilterChainWithoutAlpn() throws Exception { pipeline.fireUserEventTriggered(event); channel.runPendingTasks(); assertThat(sslSet.isDone()).isTrue(); - assertThat(sslSet.get()).isEqualTo(filterChain.getSslContextProviderSupplier()); + assertThat(sslSet.get()).isEqualTo(filterChain.sslContextProviderSupplier()); assertThat(routingSettable.get()).isEqualTo(noopConfig); assertThat(sslSet.get().getTlsContext()).isSameInstanceAs(tlsContext); } @@ -221,28 +230,28 @@ public void singleFilterChainWithoutAlpn() throws Exception { @Test public void singleFilterChainWithAlpn() throws Exception { EnvoyServerProtoData.FilterChainMatch filterChainMatch = - new EnvoyServerProtoData.FilterChainMatch( - 0, - Arrays.asList(), - Arrays.asList("managed-mtls"), - Arrays.asList(), - EnvoyServerProtoData.ConnectionSourceType.ANY, - Arrays.asList(), - Arrays.asList(), - null); + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of(), + ImmutableList.of("managed-mtls"), + ImmutableList.of(), + EnvoyServerProtoData.ConnectionSourceType.ANY, + ImmutableList.of(), + ImmutableList.of(), + ""); EnvoyServerProtoData.DownstreamTlsContext tlsContext = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT1", "VA1"); - EnvoyServerProtoData.FilterChain filterChain = new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChain filterChain = EnvoyServerProtoData.FilterChain.create( "filter-chain-foo", filterChainMatch, HTTP_CONNECTION_MANAGER, tlsContext, tlsContextManager); EnvoyServerProtoData.DownstreamTlsContext defaultTlsContext = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT2", "VA2"); - EnvoyServerProtoData.FilterChain defaultFilterChain = new EnvoyServerProtoData.FilterChain( - "filter-chain-bar", null, HTTP_CONNECTION_MANAGER, defaultTlsContext, - tlsContextManager); + EnvoyServerProtoData.FilterChain defaultFilterChain = EnvoyServerProtoData.FilterChain.create( + "filter-chain-bar", DEFAULT_FILTER_CHAIN_MATCH, HTTP_CONNECTION_MANAGER, defaultTlsContext, + tlsContextManager); selectorManager.updateSelector(new FilterChainSelector( ImmutableMap.of(filterChain, randomConfig("no-match")), - defaultFilterChain.getSslContextProviderSupplier(), noopConfig)); + defaultFilterChain.sslContextProviderSupplier(), noopConfig)); FilterChainMatchingHandler filterChainMatchingHandler = new FilterChainMatchingHandler(grpcHandler, selectorManager, mockDelegate); @@ -251,7 +260,7 @@ public void singleFilterChainWithAlpn() throws Exception { setupChannel(LOCAL_IP, REMOTE_IP, 15000, filterChainMatchingHandler); pipeline.fireUserEventTriggered(event); channel.runPendingTasks(); - assertThat(sslSet.get()).isEqualTo(defaultFilterChain.getSslContextProviderSupplier()); + assertThat(sslSet.get()).isEqualTo(defaultFilterChain.sslContextProviderSupplier()); assertThat(routingSettable.get()).isEqualTo(noopConfig); assertThat(sslSet.get().getTlsContext()).isSameInstanceAs(defaultTlsContext); } @@ -261,24 +270,24 @@ public void destPortFails_returnDefaultFilterChain() throws Exception { EnvoyServerProtoData.DownstreamTlsContext tlsContextWithDestPort = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT1", "VA1"); EnvoyServerProtoData.FilterChainMatch filterChainMatchWithDestPort = - new EnvoyServerProtoData.FilterChainMatch( - PORT, - Arrays.asList(), - Arrays.asList("managed-mtls"), - Arrays.asList(), - EnvoyServerProtoData.ConnectionSourceType.ANY, - Arrays.asList(), - Arrays.asList(), - null); + EnvoyServerProtoData.FilterChainMatch.create( + PORT, + ImmutableList.of(), + ImmutableList.of("managed-mtls"), + ImmutableList.of(), + EnvoyServerProtoData.ConnectionSourceType.ANY, + ImmutableList.of(), + ImmutableList.of(), + ""); EnvoyServerProtoData.FilterChain filterChainWithDestPort = - new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChain.create( "filter-chain-foo", filterChainMatchWithDestPort, HTTP_CONNECTION_MANAGER, tlsContextWithDestPort, tlsContextManager); EnvoyServerProtoData.DownstreamTlsContext tlsContextForDefaultFilterChain = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT2", "VA2"); EnvoyServerProtoData.FilterChain defaultFilterChain = - new EnvoyServerProtoData.FilterChain( - "filter-chain-bar", null, HTTP_CONNECTION_MANAGER, + EnvoyServerProtoData.FilterChain.create( + "filter-chain-bar", DEFAULT_FILTER_CHAIN_MATCH, HTTP_CONNECTION_MANAGER, tlsContextForDefaultFilterChain, tlsContextManager); ServerRoutingConfig routingConfig = ServerRoutingConfig.create( @@ -287,7 +296,7 @@ public void destPortFails_returnDefaultFilterChain() throws Exception { selectorManager.updateSelector(new FilterChainSelector( ImmutableMap.of(filterChainWithDestPort, new AtomicReference(routingConfig)), - defaultFilterChain.getSslContextProviderSupplier(), noopConfig)); + defaultFilterChain.sslContextProviderSupplier(), noopConfig)); FilterChainMatchingHandler filterChainMatchingHandler = new FilterChainMatchingHandler(grpcHandler, selectorManager, mockDelegate); @@ -297,7 +306,7 @@ public void destPortFails_returnDefaultFilterChain() throws Exception { setupChannel(LOCAL_IP, REMOTE_IP, 15000, filterChainMatchingHandler); pipeline.fireUserEventTriggered(event); channel.runPendingTasks(); - assertThat(sslSet.get()).isEqualTo(defaultFilterChain.getSslContextProviderSupplier()); + assertThat(sslSet.get()).isEqualTo(defaultFilterChain.sslContextProviderSupplier()); assertThat(routingSettable.get()).isEqualTo(noopConfig); assertThat(sslSet.get().getTlsContext()) .isSameInstanceAs(tlsContextForDefaultFilterChain); @@ -308,27 +317,27 @@ public void destPrefixRangeMatch() throws Exception { EnvoyServerProtoData.DownstreamTlsContext tlsContextMatch = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT1", "VA1"); EnvoyServerProtoData.FilterChainMatch filterChainMatchWithMatch = - new EnvoyServerProtoData.FilterChainMatch( - 0, - Arrays.asList(new EnvoyServerProtoData.CidrRange("10.1.2.0", 24)), - Arrays.asList(), - Arrays.asList(), - EnvoyServerProtoData.ConnectionSourceType.ANY, - Arrays.asList(), - Arrays.asList(), - null); - EnvoyServerProtoData.FilterChain filterChainWithMatch = new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of(EnvoyServerProtoData.CidrRange.create("10.1.2.0", 24)), + ImmutableList.of(), + ImmutableList.of(), + EnvoyServerProtoData.ConnectionSourceType.ANY, + ImmutableList.of(), + ImmutableList.of(), + ""); + EnvoyServerProtoData.FilterChain filterChainWithMatch = EnvoyServerProtoData.FilterChain.create( "filter-chain-foo", filterChainMatchWithMatch, HTTP_CONNECTION_MANAGER, tlsContextMatch, tlsContextManager); EnvoyServerProtoData.DownstreamTlsContext tlsContextForDefaultFilterChain = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT2", "VA2"); - EnvoyServerProtoData.FilterChain defaultFilterChain = new EnvoyServerProtoData.FilterChain( - "filter-chain-bar", null, HTTP_CONNECTION_MANAGER, + EnvoyServerProtoData.FilterChain defaultFilterChain = EnvoyServerProtoData.FilterChain.create( + "filter-chain-bar", DEFAULT_FILTER_CHAIN_MATCH, HTTP_CONNECTION_MANAGER, tlsContextForDefaultFilterChain, tlsContextManager); selectorManager.updateSelector(new FilterChainSelector( ImmutableMap.of(filterChainWithMatch, noopConfig), - defaultFilterChain.getSslContextProviderSupplier(), randomConfig("no-match"))); + defaultFilterChain.sslContextProviderSupplier(), randomConfig("no-match"))); FilterChainMatchingHandler filterChainMatchingHandler = new FilterChainMatchingHandler(grpcHandler, selectorManager, mockDelegate); @@ -338,7 +347,7 @@ public void destPrefixRangeMatch() throws Exception { setupChannel(LOCAL_IP, REMOTE_IP, 15000, filterChainMatchingHandler); pipeline.fireUserEventTriggered(event); channel.runPendingTasks(); - assertThat(sslSet.get()).isEqualTo(filterChainWithMatch.getSslContextProviderSupplier()); + assertThat(sslSet.get()).isEqualTo(filterChainWithMatch.sslContextProviderSupplier()); assertThat(routingSettable.get()).isEqualTo(noopConfig); assertThat(sslSet.get().getTlsContext()).isSameInstanceAs(tlsContextMatch); } @@ -350,27 +359,27 @@ public void destPrefixRangeMismatch_returnDefaultFilterChain() CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT1", "VA1"); // 10.2.2.0/24 doesn't match LOCAL_IP EnvoyServerProtoData.FilterChainMatch filterChainMatchWithMismatch = - new EnvoyServerProtoData.FilterChainMatch( - 0, - Arrays.asList(new EnvoyServerProtoData.CidrRange("10.2.2.0", 24)), - Arrays.asList(), - Arrays.asList(), - EnvoyServerProtoData.ConnectionSourceType.ANY, - Arrays.asList(), - Arrays.asList(), - null); + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of(EnvoyServerProtoData.CidrRange.create("10.2.2.0", 24)), + ImmutableList.of(), + ImmutableList.of(), + EnvoyServerProtoData.ConnectionSourceType.ANY, + ImmutableList.of(), + ImmutableList.of(), + ""); EnvoyServerProtoData.FilterChain filterChainWithMismatch = - new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChain.create( "filter-chain-foo", filterChainMatchWithMismatch, HTTP_CONNECTION_MANAGER, tlsContextMismatch, tlsContextManager); EnvoyServerProtoData.DownstreamTlsContext tlsContextForDefaultFilterChain = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT2", "VA2"); - EnvoyServerProtoData.FilterChain defaultFilterChain = new EnvoyServerProtoData.FilterChain( - "filter-chain-bar", null, HTTP_CONNECTION_MANAGER, + EnvoyServerProtoData.FilterChain defaultFilterChain = EnvoyServerProtoData.FilterChain.create( + "filter-chain-bar", DEFAULT_FILTER_CHAIN_MATCH, HTTP_CONNECTION_MANAGER, tlsContextForDefaultFilterChain, tlsContextManager); selectorManager.updateSelector(new FilterChainSelector( ImmutableMap.of(filterChainWithMismatch, randomConfig("no-match")), - defaultFilterChain.getSslContextProviderSupplier(), noopConfig)); + defaultFilterChain.sslContextProviderSupplier(), noopConfig)); FilterChainMatchingHandler filterChainMatchingHandler = new FilterChainMatchingHandler(grpcHandler, selectorManager, mockDelegate); @@ -381,7 +390,7 @@ public void destPrefixRangeMismatch_returnDefaultFilterChain() pipeline.fireUserEventTriggered(event); channel.runPendingTasks(); assertThat(sslSet.isDone()).isTrue(); - assertThat(sslSet.get()).isEqualTo(defaultFilterChain.getSslContextProviderSupplier()); + assertThat(sslSet.get()).isEqualTo(defaultFilterChain.sslContextProviderSupplier()); assertThat(routingSettable.get()).isEqualTo(noopConfig); assertThat(sslSet.get().getTlsContext()).isSameInstanceAs(tlsContextForDefaultFilterChain); } @@ -393,27 +402,27 @@ public void dest0LengthPrefixRange() CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT1", "VA1"); // 10.2.2.0/24 doesn't match LOCAL_IP EnvoyServerProtoData.FilterChainMatch filterChainMatch0Length = - new EnvoyServerProtoData.FilterChainMatch( - 0, - Arrays.asList(new EnvoyServerProtoData.CidrRange("10.2.2.0", 0)), - Arrays.asList(), - Arrays.asList(), - EnvoyServerProtoData.ConnectionSourceType.ANY, - Arrays.asList(), - Arrays.asList(), - null); - EnvoyServerProtoData.FilterChain filterChain0Length = new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of(EnvoyServerProtoData.CidrRange.create("10.2.2.0", 0)), + ImmutableList.of(), + ImmutableList.of(), + EnvoyServerProtoData.ConnectionSourceType.ANY, + ImmutableList.of(), + ImmutableList.of(), + ""); + EnvoyServerProtoData.FilterChain filterChain0Length = EnvoyServerProtoData.FilterChain.create( "filter-chain-foo", filterChainMatch0Length, HTTP_CONNECTION_MANAGER, tlsContext0Length, tlsContextManager); EnvoyServerProtoData.DownstreamTlsContext tlsContextForDefaultFilterChain = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT2", "VA2"); - EnvoyServerProtoData.FilterChain defaultFilterChain = new EnvoyServerProtoData.FilterChain( - "filter-chain-bar", null, HTTP_CONNECTION_MANAGER, + EnvoyServerProtoData.FilterChain defaultFilterChain = EnvoyServerProtoData.FilterChain.create( + "filter-chain-bar", DEFAULT_FILTER_CHAIN_MATCH, HTTP_CONNECTION_MANAGER, tlsContextForDefaultFilterChain, tlsContextManager); selectorManager.updateSelector(new FilterChainSelector( ImmutableMap.of(filterChain0Length, noopConfig), - defaultFilterChain.getSslContextProviderSupplier(), + defaultFilterChain.sslContextProviderSupplier(), new AtomicReference())); FilterChainMatchingHandler filterChainMatchingHandler = new FilterChainMatchingHandler(grpcHandler, selectorManager, mockDelegate); @@ -423,7 +432,7 @@ public void dest0LengthPrefixRange() setupChannel(LOCAL_IP, REMOTE_IP, 15000, filterChainMatchingHandler); pipeline.fireUserEventTriggered(event); channel.runPendingTasks(); - assertThat(sslSet.get()).isEqualTo(filterChain0Length.getSslContextProviderSupplier()); + assertThat(sslSet.get()).isEqualTo(filterChain0Length.sslContextProviderSupplier()); assertThat(routingSettable.get()).isEqualTo(noopConfig); assertThat(sslSet.get().getTlsContext()).isSameInstanceAs(tlsContext0Length); } @@ -434,43 +443,44 @@ public void destPrefixRange_moreSpecificWins() EnvoyServerProtoData.DownstreamTlsContext tlsContextLessSpecific = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT1", "VA1"); EnvoyServerProtoData.FilterChainMatch filterChainMatchLessSpecific = - new EnvoyServerProtoData.FilterChainMatch( - 0, - Arrays.asList(new EnvoyServerProtoData.CidrRange("10.1.2.0", 24)), - Arrays.asList(), - Arrays.asList(), - EnvoyServerProtoData.ConnectionSourceType.ANY, - Arrays.asList(), - Arrays.asList(), - null); + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of(EnvoyServerProtoData.CidrRange.create("10.1.2.0", 24)), + ImmutableList.of(), + ImmutableList.of(), + EnvoyServerProtoData.ConnectionSourceType.ANY, + ImmutableList.of(), + ImmutableList.of(), + ""); EnvoyServerProtoData.FilterChain filterChainLessSpecific = - new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChain.create( "filter-chain-foo", filterChainMatchLessSpecific, HTTP_CONNECTION_MANAGER, tlsContextLessSpecific, tlsContextManager); EnvoyServerProtoData.DownstreamTlsContext tlsContextMoreSpecific = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT2", "VA2"); EnvoyServerProtoData.FilterChainMatch filterChainMatchMoreSpecific = - new EnvoyServerProtoData.FilterChainMatch( - 0, - Arrays.asList(new EnvoyServerProtoData.CidrRange("10.1.2.2", 31)), - Arrays.asList(), - Arrays.asList(), - EnvoyServerProtoData.ConnectionSourceType.ANY, - Arrays.asList(), - Arrays.asList(), - null); + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of(EnvoyServerProtoData.CidrRange.create("10.1.2.2", 31)), + ImmutableList.of(), + ImmutableList.of(), + EnvoyServerProtoData.ConnectionSourceType.ANY, + ImmutableList.of(), + ImmutableList.of(), + ""); EnvoyServerProtoData.FilterChain filterChainMoreSpecific = - new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChain.create( "filter-chain-bar", filterChainMatchMoreSpecific, HTTP_CONNECTION_MANAGER, tlsContextMoreSpecific, tlsContextManager); - EnvoyServerProtoData.FilterChain defaultFilterChain = new EnvoyServerProtoData.FilterChain( - "filter-chain-baz", null, HTTP_CONNECTION_MANAGER, null, tlsContextManager); + EnvoyServerProtoData.FilterChain defaultFilterChain = EnvoyServerProtoData.FilterChain.create( + "filter-chain-baz", DEFAULT_FILTER_CHAIN_MATCH, HTTP_CONNECTION_MANAGER, null, + tlsContextManager); selectorManager.updateSelector(new FilterChainSelector( ImmutableMap.of(filterChainLessSpecific, randomConfig("no-match"), filterChainMoreSpecific, noopConfig), - defaultFilterChain.getSslContextProviderSupplier(), randomConfig("default"))); + defaultFilterChain.sslContextProviderSupplier(), randomConfig("default"))); FilterChainMatchingHandler filterChainMatchingHandler = new FilterChainMatchingHandler(grpcHandler, selectorManager, mockDelegate); @@ -480,7 +490,7 @@ public void destPrefixRange_moreSpecificWins() setupChannel(LOCAL_IP, REMOTE_IP, 15000, filterChainMatchingHandler); pipeline.fireUserEventTriggered(event); channel.runPendingTasks(); - assertThat(sslSet.get()).isEqualTo(filterChainMoreSpecific.getSslContextProviderSupplier()); + assertThat(sslSet.get()).isEqualTo(filterChainMoreSpecific.sslContextProviderSupplier()); assertThat(routingSettable.get()).isEqualTo(noopConfig); assertThat(sslSet.get().getTlsContext()).isSameInstanceAs(tlsContextMoreSpecific); } @@ -491,43 +501,44 @@ public void destPrefixRange_emptyListLessSpecific() EnvoyServerProtoData.DownstreamTlsContext tlsContextLessSpecific = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT1", "VA1"); EnvoyServerProtoData.FilterChainMatch filterChainMatchLessSpecific = - new EnvoyServerProtoData.FilterChainMatch( - 0, - Arrays.asList(), - Arrays.asList(), - Arrays.asList(), - EnvoyServerProtoData.ConnectionSourceType.ANY, - Arrays.asList(), - Arrays.asList(), - null); + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of(), + EnvoyServerProtoData.ConnectionSourceType.ANY, + ImmutableList.of(), + ImmutableList.of(), + ""); EnvoyServerProtoData.FilterChain filterChainLessSpecific = - new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChain.create( "filter-chain-foo", filterChainMatchLessSpecific, HTTP_CONNECTION_MANAGER, tlsContextLessSpecific, tlsContextManager); EnvoyServerProtoData.DownstreamTlsContext tlsContextMoreSpecific = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT2", "VA2"); EnvoyServerProtoData.FilterChainMatch filterChainMatchMoreSpecific = - new EnvoyServerProtoData.FilterChainMatch( - 0, - Arrays.asList(new EnvoyServerProtoData.CidrRange("8.0.0.0", 5)), - Arrays.asList(), - Arrays.asList(), - EnvoyServerProtoData.ConnectionSourceType.ANY, - Arrays.asList(), - Arrays.asList(), - null); + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of(EnvoyServerProtoData.CidrRange.create("8.0.0.0", 5)), + ImmutableList.of(), + ImmutableList.of(), + EnvoyServerProtoData.ConnectionSourceType.ANY, + ImmutableList.of(), + ImmutableList.of(), + ""); EnvoyServerProtoData.FilterChain filterChainMoreSpecific = - new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChain.create( "filter-chain-bar", filterChainMatchMoreSpecific, HTTP_CONNECTION_MANAGER, tlsContextMoreSpecific, tlsContextManager); - EnvoyServerProtoData.FilterChain defaultFilterChain = new EnvoyServerProtoData.FilterChain( - "filter-chain-baz", null, HTTP_CONNECTION_MANAGER, null, tlsContextManager); + EnvoyServerProtoData.FilterChain defaultFilterChain = EnvoyServerProtoData.FilterChain.create( + "filter-chain-baz", DEFAULT_FILTER_CHAIN_MATCH, HTTP_CONNECTION_MANAGER, null, + tlsContextManager); selectorManager.updateSelector(new FilterChainSelector( ImmutableMap.of(filterChainLessSpecific, randomConfig("no-match"), filterChainMoreSpecific, noopConfig), - defaultFilterChain.getSslContextProviderSupplier(), randomConfig("default"))); + defaultFilterChain.sslContextProviderSupplier(), randomConfig("default"))); FilterChainMatchingHandler filterChainMatchingHandler = new FilterChainMatchingHandler(grpcHandler, selectorManager, mockDelegate); @@ -536,7 +547,7 @@ public void destPrefixRange_emptyListLessSpecific() setupChannel(LOCAL_IP, REMOTE_IP, 15000, filterChainMatchingHandler); pipeline.fireUserEventTriggered(event); channel.runPendingTasks(); - assertThat(sslSet.get()).isEqualTo(filterChainMoreSpecific.getSslContextProviderSupplier()); + assertThat(sslSet.get()).isEqualTo(filterChainMoreSpecific.sslContextProviderSupplier()); assertThat(routingSettable.get()).isEqualTo(noopConfig); assertThat(sslSet.get().getTlsContext()).isSameInstanceAs(tlsContextMoreSpecific); } @@ -547,42 +558,44 @@ public void destPrefixRangeIpv6_moreSpecificWins() EnvoyServerProtoData.DownstreamTlsContext tlsContextLessSpecific = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT1", "VA1"); EnvoyServerProtoData.FilterChainMatch filterChainMatchLessSpecific = - new EnvoyServerProtoData.FilterChainMatch( - 0, - Arrays.asList(new EnvoyServerProtoData.CidrRange("FE80:0:0:0:0:0:0:0", 60)), - Arrays.asList(), - Arrays.asList(), - EnvoyServerProtoData.ConnectionSourceType.ANY, - Arrays.asList(), - Arrays.asList(), - null); + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of(EnvoyServerProtoData.CidrRange.create("FE80:0:0:0:0:0:0:0", 60)), + ImmutableList.of(), + ImmutableList.of(), + EnvoyServerProtoData.ConnectionSourceType.ANY, + ImmutableList.of(), + ImmutableList.of(), + ""); EnvoyServerProtoData.FilterChain filterChainLessSpecific = - new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChain.create( "filter-chain-foo", filterChainMatchLessSpecific, HTTP_CONNECTION_MANAGER, tlsContextLessSpecific, tlsContextManager); EnvoyServerProtoData.DownstreamTlsContext tlsContextMoreSpecific = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT2", "VA2"); EnvoyServerProtoData.FilterChainMatch filterChainMatchMoreSpecific = - new EnvoyServerProtoData.FilterChainMatch( + EnvoyServerProtoData.FilterChainMatch.create( 0, - Arrays.asList(new EnvoyServerProtoData.CidrRange("FE80:0000:0000:0000:0202:0:0:0", 80)), - Arrays.asList(), - Arrays.asList(), + ImmutableList.of( + EnvoyServerProtoData.CidrRange.create("FE80:0000:0000:0000:0202:0:0:0", 80)), + ImmutableList.of(), + ImmutableList.of(), EnvoyServerProtoData.ConnectionSourceType.ANY, - Arrays.asList(), - Arrays.asList(), - null); + ImmutableList.of(), + ImmutableList.of(), + ""); EnvoyServerProtoData.FilterChain filterChainMoreSpecific = - new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChain.create( "filter-chain-bar", filterChainMatchMoreSpecific, HTTP_CONNECTION_MANAGER, tlsContextMoreSpecific, tlsContextManager); - EnvoyServerProtoData.FilterChain defaultFilterChain = new EnvoyServerProtoData.FilterChain( - "filter-chain-baz", null, HTTP_CONNECTION_MANAGER, null, tlsContextManager); + EnvoyServerProtoData.FilterChain defaultFilterChain = EnvoyServerProtoData.FilterChain.create( + "filter-chain-baz", DEFAULT_FILTER_CHAIN_MATCH, HTTP_CONNECTION_MANAGER, null, + tlsContextManager); selectorManager.updateSelector(new FilterChainSelector( ImmutableMap.of(filterChainLessSpecific, randomConfig("no-match"), filterChainMoreSpecific, noopConfig), - defaultFilterChain.getSslContextProviderSupplier(), randomConfig("default"))); + defaultFilterChain.sslContextProviderSupplier(), randomConfig("default"))); FilterChainMatchingHandler filterChainMatchingHandler = new FilterChainMatchingHandler(grpcHandler, selectorManager, mockDelegate); @@ -594,7 +607,7 @@ public void destPrefixRangeIpv6_moreSpecificWins() 15000, filterChainMatchingHandler); pipeline.fireUserEventTriggered(event); channel.runPendingTasks(); - assertThat(sslSet.get()).isEqualTo(filterChainMoreSpecific.getSslContextProviderSupplier()); + assertThat(sslSet.get()).isEqualTo(filterChainMoreSpecific.sslContextProviderSupplier()); assertThat(routingSettable.get()).isEqualTo(noopConfig); assertThat(sslSet.get().getTlsContext()).isSameInstanceAs(tlsContextMoreSpecific); } @@ -605,45 +618,46 @@ public void destPrefixRange_moreSpecificWith2Wins() EnvoyServerProtoData.DownstreamTlsContext tlsContextMoreSpecificWith2 = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT1", "VA1"); EnvoyServerProtoData.FilterChainMatch filterChainMatchMoreSpecificWith2 = - new EnvoyServerProtoData.FilterChainMatch( - 0, - Arrays.asList( - new EnvoyServerProtoData.CidrRange("10.1.2.0", 24), - new EnvoyServerProtoData.CidrRange(LOCAL_IP, 32)), - Arrays.asList(), - Arrays.asList(), - EnvoyServerProtoData.ConnectionSourceType.ANY, - Arrays.asList(), - Arrays.asList(), - null); + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of( + EnvoyServerProtoData.CidrRange.create("10.1.2.0", 24), + EnvoyServerProtoData.CidrRange.create(LOCAL_IP, 32)), + ImmutableList.of(), + ImmutableList.of(), + EnvoyServerProtoData.ConnectionSourceType.ANY, + ImmutableList.of(), + ImmutableList.of(), + ""); EnvoyServerProtoData.FilterChain filterChainMoreSpecificWith2 = - new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChain.create( "filter-chain-foo", filterChainMatchMoreSpecificWith2, HTTP_CONNECTION_MANAGER, tlsContextMoreSpecificWith2, tlsContextManager); EnvoyServerProtoData.DownstreamTlsContext tlsContextLessSpecific = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT2", "VA2"); EnvoyServerProtoData.FilterChainMatch filterChainMatchLessSpecific = - new EnvoyServerProtoData.FilterChainMatch( - 0, - Arrays.asList(new EnvoyServerProtoData.CidrRange("10.1.2.2", 31)), - Arrays.asList(), - Arrays.asList(), - EnvoyServerProtoData.ConnectionSourceType.ANY, - Arrays.asList(), - Arrays.asList(), - null); + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of(EnvoyServerProtoData.CidrRange.create("10.1.2.2", 31)), + ImmutableList.of(), + ImmutableList.of(), + EnvoyServerProtoData.ConnectionSourceType.ANY, + ImmutableList.of(), + ImmutableList.of(), + ""); EnvoyServerProtoData.FilterChain filterChainLessSpecific = - new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChain.create( "filter-chain-bar", filterChainMatchLessSpecific, HTTP_CONNECTION_MANAGER, tlsContextLessSpecific, tlsContextManager); - EnvoyServerProtoData.FilterChain defaultFilterChain = new EnvoyServerProtoData.FilterChain( - "filter-chain-baz", null, HTTP_CONNECTION_MANAGER, null, tlsContextManager); + EnvoyServerProtoData.FilterChain defaultFilterChain = EnvoyServerProtoData.FilterChain.create( + "filter-chain-baz", DEFAULT_FILTER_CHAIN_MATCH, HTTP_CONNECTION_MANAGER, null, + tlsContextManager); selectorManager.updateSelector(new FilterChainSelector( ImmutableMap.of(filterChainMoreSpecificWith2, noopConfig, filterChainLessSpecific, randomConfig("no-match")), - defaultFilterChain.getSslContextProviderSupplier(), randomConfig("default"))); + defaultFilterChain.sslContextProviderSupplier(), randomConfig("default"))); FilterChainMatchingHandler filterChainMatchingHandler = new FilterChainMatchingHandler(grpcHandler, selectorManager, mockDelegate); @@ -653,7 +667,7 @@ filterChainLessSpecific, randomConfig("no-match")), pipeline.fireUserEventTriggered(event); channel.runPendingTasks(); assertThat(sslSet.get()).isEqualTo( - filterChainMoreSpecificWith2.getSslContextProviderSupplier()); + filterChainMoreSpecificWith2.sslContextProviderSupplier()); assertThat(routingSettable.get()).isEqualTo(noopConfig); assertThat(sslSet.get().getTlsContext()).isSameInstanceAs(tlsContextMoreSpecificWith2); } @@ -663,27 +677,27 @@ public void sourceTypeMismatch_returnDefaultFilterChain() throws Exception { EnvoyServerProtoData.DownstreamTlsContext tlsContextMismatch = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT1", "VA1"); EnvoyServerProtoData.FilterChainMatch filterChainMatchWithMismatch = - new EnvoyServerProtoData.FilterChainMatch( - 0, - Arrays.asList(), - Arrays.asList(), - Arrays.asList(), - EnvoyServerProtoData.ConnectionSourceType.SAME_IP_OR_LOOPBACK, - Arrays.asList(), - Arrays.asList(), - null); + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of(), + EnvoyServerProtoData.ConnectionSourceType.SAME_IP_OR_LOOPBACK, + ImmutableList.of(), + ImmutableList.of(), + ""); EnvoyServerProtoData.FilterChain filterChainWithMismatch = - new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChain.create( "filter-chain-foo", filterChainMatchWithMismatch, HTTP_CONNECTION_MANAGER, tlsContextMismatch, tlsContextManager); EnvoyServerProtoData.DownstreamTlsContext tlsContextForDefaultFilterChain = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT2", "VA2"); - EnvoyServerProtoData.FilterChain defaultFilterChain = new EnvoyServerProtoData.FilterChain( - "filter-chain-bar", null, HTTP_CONNECTION_MANAGER,tlsContextForDefaultFilterChain, - tlsContextManager); + EnvoyServerProtoData.FilterChain defaultFilterChain = EnvoyServerProtoData.FilterChain.create( + "filter-chain-bar", DEFAULT_FILTER_CHAIN_MATCH, HTTP_CONNECTION_MANAGER, + tlsContextForDefaultFilterChain, tlsContextManager); selectorManager.updateSelector(new FilterChainSelector( ImmutableMap.of(filterChainWithMismatch, randomConfig("no-match")), - defaultFilterChain.getSslContextProviderSupplier(), noopConfig)); + defaultFilterChain.sslContextProviderSupplier(), noopConfig)); FilterChainMatchingHandler filterChainMatchingHandler = new FilterChainMatchingHandler(grpcHandler, selectorManager, mockDelegate); @@ -693,7 +707,7 @@ public void sourceTypeMismatch_returnDefaultFilterChain() throws Exception { setupChannel(LOCAL_IP, REMOTE_IP, 15000, filterChainMatchingHandler); pipeline.fireUserEventTriggered(event); channel.runPendingTasks(); - assertThat(sslSet.get()).isEqualTo(defaultFilterChain.getSslContextProviderSupplier()); + assertThat(sslSet.get()).isEqualTo(defaultFilterChain.sslContextProviderSupplier()); assertThat(routingSettable.get()).isEqualTo(noopConfig); assertThat(sslSet.get().getTlsContext()).isSameInstanceAs(tlsContextForDefaultFilterChain); } @@ -705,33 +719,33 @@ public void sourceTypeLocal() throws Exception { EnvoyServerProtoData.DownstreamTlsContext tlsContextMatch = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT1", "VA1"); EnvoyServerProtoData.FilterChainMatch filterChainMatchWithMatch = - new EnvoyServerProtoData.FilterChainMatch( - 0, - Arrays.asList(), - Arrays.asList(), - Arrays.asList(), - EnvoyServerProtoData.ConnectionSourceType.SAME_IP_OR_LOOPBACK, - Arrays.asList(), - Arrays.asList(), - null); - EnvoyServerProtoData.FilterChain filterChainWithMatch = new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of(), + EnvoyServerProtoData.ConnectionSourceType.SAME_IP_OR_LOOPBACK, + ImmutableList.of(), + ImmutableList.of(), + ""); + EnvoyServerProtoData.FilterChain filterChainWithMatch = EnvoyServerProtoData.FilterChain.create( "filter-chain-foo", filterChainMatchWithMatch, HTTP_CONNECTION_MANAGER, tlsContextMatch, tlsContextManager); EnvoyServerProtoData.DownstreamTlsContext tlsContextForDefaultFilterChain = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT2", "VA2"); - EnvoyServerProtoData.FilterChain defaultFilterChain = new EnvoyServerProtoData.FilterChain( - "filter-chain-bar", null, HTTP_CONNECTION_MANAGER, tlsContextForDefaultFilterChain, - tlsContextManager); + EnvoyServerProtoData.FilterChain defaultFilterChain = EnvoyServerProtoData.FilterChain.create( + "filter-chain-bar", DEFAULT_FILTER_CHAIN_MATCH, HTTP_CONNECTION_MANAGER, + tlsContextForDefaultFilterChain, tlsContextManager); selectorManager.updateSelector(new FilterChainSelector( ImmutableMap.of(filterChainWithMatch, noopConfig), - defaultFilterChain.getSslContextProviderSupplier(), randomConfig("default"))); + defaultFilterChain.sslContextProviderSupplier(), randomConfig("default"))); FilterChainMatchingHandler filterChainMatchingHandler = new FilterChainMatchingHandler(grpcHandler, selectorManager, mockDelegate); setupChannel(LOCAL_IP, LOCAL_IP, 15000, filterChainMatchingHandler); pipeline.fireUserEventTriggered(event); channel.runPendingTasks(); - assertThat(sslSet.get()).isEqualTo(filterChainWithMatch.getSslContextProviderSupplier()); + assertThat(sslSet.get()).isEqualTo(filterChainWithMatch.sslContextProviderSupplier()); assertThat(routingSettable.get()).isEqualTo(noopConfig); assertThat(sslSet.get().getTlsContext()).isSameInstanceAs(tlsContextMatch); } @@ -745,45 +759,46 @@ public void sourcePrefixRange_moreSpecificWith2Wins() EnvoyServerProtoData.DownstreamTlsContext tlsContextMoreSpecificWith2 = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT1", "VA1"); EnvoyServerProtoData.FilterChainMatch filterChainMatchMoreSpecificWith2 = - new EnvoyServerProtoData.FilterChainMatch( - 0, - Arrays.asList(), - Arrays.asList(), - Arrays.asList( - new EnvoyServerProtoData.CidrRange("10.4.2.0", 24), - new EnvoyServerProtoData.CidrRange(REMOTE_IP, 32)), - EnvoyServerProtoData.ConnectionSourceType.ANY, - Arrays.asList(), - Arrays.asList(), - null); + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of( + EnvoyServerProtoData.CidrRange.create("10.4.2.0", 24), + EnvoyServerProtoData.CidrRange.create(REMOTE_IP, 32)), + EnvoyServerProtoData.ConnectionSourceType.ANY, + ImmutableList.of(), + ImmutableList.of(), + ""); EnvoyServerProtoData.FilterChain filterChainMoreSpecificWith2 = - new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChain.create( "filter-chain-foo", filterChainMatchMoreSpecificWith2, HTTP_CONNECTION_MANAGER, tlsContextMoreSpecificWith2, tlsContextManager); EnvoyServerProtoData.DownstreamTlsContext tlsContextLessSpecific = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT2", "VA2"); EnvoyServerProtoData.FilterChainMatch filterChainMatchLessSpecific = - new EnvoyServerProtoData.FilterChainMatch( - 0, - Arrays.asList(), - Arrays.asList(), - Arrays.asList(new EnvoyServerProtoData.CidrRange("10.4.2.2", 31)), - EnvoyServerProtoData.ConnectionSourceType.ANY, - Arrays.asList(), - Arrays.asList(), - null); + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of(EnvoyServerProtoData.CidrRange.create("10.4.2.2", 31)), + EnvoyServerProtoData.ConnectionSourceType.ANY, + ImmutableList.of(), + ImmutableList.of(), + ""); EnvoyServerProtoData.FilterChain filterChainLessSpecific = - new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChain.create( "filter-chain-bar", filterChainMatchLessSpecific, HTTP_CONNECTION_MANAGER, tlsContextLessSpecific, tlsContextManager); - EnvoyServerProtoData.FilterChain defaultFilterChain = new EnvoyServerProtoData.FilterChain( - "filter-chain-baz", null, HTTP_CONNECTION_MANAGER, null, tlsContextManager); + EnvoyServerProtoData.FilterChain defaultFilterChain = EnvoyServerProtoData.FilterChain.create( + "filter-chain-baz", DEFAULT_FILTER_CHAIN_MATCH, HTTP_CONNECTION_MANAGER, null, + tlsContextManager); selectorManager.updateSelector(new FilterChainSelector( ImmutableMap.of(filterChainMoreSpecificWith2, noopConfig, filterChainLessSpecific, randomConfig("no-match")), - defaultFilterChain.getSslContextProviderSupplier(), randomConfig("default"))); + defaultFilterChain.sslContextProviderSupplier(), randomConfig("default"))); FilterChainMatchingHandler filterChainMatchingHandler = new FilterChainMatchingHandler(grpcHandler, selectorManager, mockDelegate); @@ -791,7 +806,7 @@ filterChainLessSpecific, randomConfig("no-match")), pipeline.fireUserEventTriggered(event); channel.runPendingTasks(); assertThat(sslSet.get()).isEqualTo( - filterChainMoreSpecificWith2.getSslContextProviderSupplier()); + filterChainMoreSpecificWith2.sslContextProviderSupplier()); assertThat(routingSettable.get()).isEqualTo(noopConfig); assertThat(sslSet.get().getTlsContext()).isSameInstanceAs(tlsContextMoreSpecificWith2); } @@ -812,42 +827,42 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { EnvoyServerProtoData.DownstreamTlsContext tlsContext1 = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT1", "VA1"); EnvoyServerProtoData.FilterChainMatch filterChainMatch1 = - new EnvoyServerProtoData.FilterChainMatch( - 0, - Arrays.asList(), - Arrays.asList(), - Arrays.asList( - new EnvoyServerProtoData.CidrRange("10.4.2.0", 24), - new EnvoyServerProtoData.CidrRange("192.168.10.2", 32)), - EnvoyServerProtoData.ConnectionSourceType.ANY, - Arrays.asList(), - Arrays.asList(), - null); - EnvoyServerProtoData.FilterChain filterChain1 = new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of( + EnvoyServerProtoData.CidrRange.create("10.4.2.0", 24), + EnvoyServerProtoData.CidrRange.create("192.168.10.2", 32)), + EnvoyServerProtoData.ConnectionSourceType.ANY, + ImmutableList.of(), + ImmutableList.of(), + ""); + EnvoyServerProtoData.FilterChain filterChain1 = EnvoyServerProtoData.FilterChain.create( "filter-chain-foo", filterChainMatch1, HTTP_CONNECTION_MANAGER, tlsContext1, tlsContextManager); EnvoyServerProtoData.DownstreamTlsContext tlsContext2 = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT2", "VA2"); EnvoyServerProtoData.FilterChainMatch filterChainMatch2 = - new EnvoyServerProtoData.FilterChainMatch( - 0, - Arrays.asList(), - Arrays.asList(), - Arrays.asList(new EnvoyServerProtoData.CidrRange("10.4.2.0", 24)), - EnvoyServerProtoData.ConnectionSourceType.ANY, - Arrays.asList(), - Arrays.asList(), - null); - EnvoyServerProtoData.FilterChain filterChain2 = new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of(EnvoyServerProtoData.CidrRange.create("10.4.2.0", 24)), + EnvoyServerProtoData.ConnectionSourceType.ANY, + ImmutableList.of(), + ImmutableList.of(), + ""); + EnvoyServerProtoData.FilterChain filterChain2 = EnvoyServerProtoData.FilterChain.create( "filter-chain-bar", filterChainMatch2, HTTP_CONNECTION_MANAGER, tlsContext2, tlsContextManager); - EnvoyServerProtoData.FilterChain defaultFilterChain = new EnvoyServerProtoData.FilterChain( - "filter-chain-baz", null, HTTP_CONNECTION_MANAGER, null, null); + EnvoyServerProtoData.FilterChain defaultFilterChain = EnvoyServerProtoData.FilterChain.create( + "filter-chain-baz", DEFAULT_FILTER_CHAIN_MATCH, HTTP_CONNECTION_MANAGER, null, null); selectorManager.updateSelector(new FilterChainSelector( ImmutableMap.of(filterChain1, noopConfig, filterChain2, noopConfig), - defaultFilterChain.getSslContextProviderSupplier(), noopConfig)); + defaultFilterChain.sslContextProviderSupplier(), noopConfig)); FilterChainMatchingHandler filterChainMatchingHandler = new FilterChainMatchingHandler(grpcHandler, selectorManager, mockDelegate); @@ -871,45 +886,46 @@ public void sourcePortMatch_exactMatchWinsOverEmptyList() throws Exception { EnvoyServerProtoData.DownstreamTlsContext tlsContextEmptySourcePorts = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT1", "VA1"); EnvoyServerProtoData.FilterChainMatch filterChainMatchEmptySourcePorts = - new EnvoyServerProtoData.FilterChainMatch( - 0, - Arrays.asList(), - Arrays.asList(), - Arrays.asList( - new EnvoyServerProtoData.CidrRange("10.4.2.0", 24), - new EnvoyServerProtoData.CidrRange("10.4.2.2", 31)), - EnvoyServerProtoData.ConnectionSourceType.ANY, - Arrays.asList(), - Arrays.asList(), - null); + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of( + EnvoyServerProtoData.CidrRange.create("10.4.2.0", 24), + EnvoyServerProtoData.CidrRange.create("10.4.2.2", 31)), + EnvoyServerProtoData.ConnectionSourceType.ANY, + ImmutableList.of(), + ImmutableList.of(), + ""); EnvoyServerProtoData.FilterChain filterChainEmptySourcePorts = - new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChain.create( "filter-chain-foo", filterChainMatchEmptySourcePorts, HTTP_CONNECTION_MANAGER, tlsContextEmptySourcePorts, tlsContextManager); EnvoyServerProtoData.DownstreamTlsContext tlsContextSourcePortMatch = CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT2", "VA2"); EnvoyServerProtoData.FilterChainMatch filterChainMatchSourcePortMatch = - new EnvoyServerProtoData.FilterChainMatch( - 0, - Arrays.asList(), - Arrays.asList(), - Arrays.asList(new EnvoyServerProtoData.CidrRange("10.4.2.2", 31)), - EnvoyServerProtoData.ConnectionSourceType.ANY, - Arrays.asList(7000, 15000), - Arrays.asList(), - null); + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of(EnvoyServerProtoData.CidrRange.create("10.4.2.2", 31)), + EnvoyServerProtoData.ConnectionSourceType.ANY, + ImmutableList.of(7000, 15000), + ImmutableList.of(), + ""); EnvoyServerProtoData.FilterChain filterChainSourcePortMatch = - new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChain.create( "filter-chain-bar", filterChainMatchSourcePortMatch, HTTP_CONNECTION_MANAGER, tlsContextSourcePortMatch, tlsContextManager); - EnvoyServerProtoData.FilterChain defaultFilterChain = new EnvoyServerProtoData.FilterChain( - "filter-chain-baz", null, HTTP_CONNECTION_MANAGER, null, tlsContextManager); + EnvoyServerProtoData.FilterChain defaultFilterChain = EnvoyServerProtoData.FilterChain.create( + "filter-chain-baz", DEFAULT_FILTER_CHAIN_MATCH, HTTP_CONNECTION_MANAGER, null, + tlsContextManager); selectorManager.updateSelector(new FilterChainSelector( ImmutableMap.of(filterChainEmptySourcePorts, randomConfig("no-match"), filterChainSourcePortMatch, noopConfig), - defaultFilterChain.getSslContextProviderSupplier(), randomConfig("default"))); + defaultFilterChain.sslContextProviderSupplier(), randomConfig("default"))); FilterChainMatchingHandler filterChainMatchingHandler = new FilterChainMatchingHandler(grpcHandler, selectorManager, mockDelegate); @@ -918,7 +934,7 @@ public void sourcePortMatch_exactMatchWinsOverEmptyList() throws Exception { setupChannel(LOCAL_IP, REMOTE_IP, 15000, filterChainMatchingHandler); pipeline.fireUserEventTriggered(event); channel.runPendingTasks(); - assertThat(sslSet.get()).isEqualTo(filterChainSourcePortMatch.getSslContextProviderSupplier()); + assertThat(sslSet.get()).isEqualTo(filterChainSourcePortMatch.sslContextProviderSupplier()); assertThat(routingSettable.get()).isEqualTo(noopConfig); assertThat(sslSet.get().getTlsContext()).isSameInstanceAs(tlsContextSourcePortMatch); } @@ -947,16 +963,16 @@ public void filterChain_5stepMatch() throws Exception { // has dest port and specific prefix ranges: gets eliminated in step 1 EnvoyServerProtoData.FilterChainMatch filterChainMatch1 = - new EnvoyServerProtoData.FilterChainMatch( - PORT, - Arrays.asList(), - Arrays.asList(), - Arrays.asList(new EnvoyServerProtoData.CidrRange(REMOTE_IP, 32)), - EnvoyServerProtoData.ConnectionSourceType.ANY, - Arrays.asList(), - Arrays.asList(), - null); - EnvoyServerProtoData.FilterChain filterChain1 = new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChainMatch.create( + PORT, + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of(EnvoyServerProtoData.CidrRange.create(REMOTE_IP, 32)), + EnvoyServerProtoData.ConnectionSourceType.ANY, + ImmutableList.of(), + ImmutableList.of(), + ""); + EnvoyServerProtoData.FilterChain filterChain1 = EnvoyServerProtoData.FilterChain.create( "filter-chain-1", filterChainMatch1, HTTP_CONNECTION_MANAGER, tlsContext1, tlsContextManager); @@ -964,94 +980,95 @@ public void filterChain_5stepMatch() throws Exception { // has single prefix range: and less specific source prefix range: gets eliminated in step 4 EnvoyServerProtoData.FilterChainMatch filterChainMatch2 = - new EnvoyServerProtoData.FilterChainMatch( - 0, - Arrays.asList(new EnvoyServerProtoData.CidrRange("10.1.2.0", 30)), - Arrays.asList(), - Arrays.asList(new EnvoyServerProtoData.CidrRange("10.4.0.0", 16)), - EnvoyServerProtoData.ConnectionSourceType.ANY, - Arrays.asList(), - Arrays.asList(), - null); - EnvoyServerProtoData.FilterChain filterChain2 = new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of(EnvoyServerProtoData.CidrRange.create("10.1.2.0", 30)), + ImmutableList.of(), + ImmutableList.of(EnvoyServerProtoData.CidrRange.create("10.4.0.0", 16)), + EnvoyServerProtoData.ConnectionSourceType.ANY, + ImmutableList.of(), + ImmutableList.of(), + ""); + EnvoyServerProtoData.FilterChain filterChain2 = EnvoyServerProtoData.FilterChain.create( "filter-chain-2", filterChainMatch2, HTTP_CONNECTION_MANAGER, tlsContext2, tlsContextManager); // has prefix ranges with one not matching and source type local: gets eliminated in step 3 EnvoyServerProtoData.FilterChainMatch filterChainMatch3 = - new EnvoyServerProtoData.FilterChainMatch( - 0, - Arrays.asList( - new EnvoyServerProtoData.CidrRange("192.168.2.0", 24), - new EnvoyServerProtoData.CidrRange("10.1.2.0", 30)), - Arrays.asList(), - Arrays.asList(), - EnvoyServerProtoData.ConnectionSourceType.SAME_IP_OR_LOOPBACK, - Arrays.asList(), - Arrays.asList(), - null); - EnvoyServerProtoData.FilterChain filterChain3 = new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of( + EnvoyServerProtoData.CidrRange.create("192.168.2.0", 24), + EnvoyServerProtoData.CidrRange.create("10.1.2.0", 30)), + ImmutableList.of(), + ImmutableList.of(), + EnvoyServerProtoData.ConnectionSourceType.SAME_IP_OR_LOOPBACK, + ImmutableList.of(), + ImmutableList.of(), + ""); + EnvoyServerProtoData.FilterChain filterChain3 = EnvoyServerProtoData.FilterChain.create( "filter-chain-3", filterChainMatch3, HTTP_CONNECTION_MANAGER, tlsContext3, tlsContextManager); // has prefix ranges with both matching and source type external but non matching source port: // gets eliminated in step 5 EnvoyServerProtoData.FilterChainMatch filterChainMatch4 = - new EnvoyServerProtoData.FilterChainMatch( - 0, - Arrays.asList( - new EnvoyServerProtoData.CidrRange("10.1.0.0", 16), - new EnvoyServerProtoData.CidrRange("10.1.2.0", 30)), - Arrays.asList(), - Arrays.asList(new EnvoyServerProtoData.CidrRange("10.4.2.0", 24)), - EnvoyServerProtoData.ConnectionSourceType.EXTERNAL, - Arrays.asList(16000, 9000), - Arrays.asList(), - null); + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of( + EnvoyServerProtoData.CidrRange.create("10.1.0.0", 16), + EnvoyServerProtoData.CidrRange.create("10.1.2.0", 30)), + ImmutableList.of(), + ImmutableList.of(EnvoyServerProtoData.CidrRange.create("10.4.2.0", 24)), + EnvoyServerProtoData.ConnectionSourceType.EXTERNAL, + ImmutableList.of(16000, 9000), + ImmutableList.of(), + ""); EnvoyServerProtoData.FilterChain filterChain4 = - new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChain.create( "filter-chain-4", filterChainMatch4, HTTP_CONNECTION_MANAGER, tlsContext4, tlsContextManager); // has prefix ranges with both matching and source type external and matching source port: this // gets selected EnvoyServerProtoData.FilterChainMatch filterChainMatch5 = - new EnvoyServerProtoData.FilterChainMatch( - 0, - Arrays.asList( - new EnvoyServerProtoData.CidrRange("10.1.0.0", 16), - new EnvoyServerProtoData.CidrRange("10.1.2.0", 30)), - Arrays.asList(), - Arrays.asList( - new EnvoyServerProtoData.CidrRange("10.4.2.0", 24), - new EnvoyServerProtoData.CidrRange("192.168.2.0", 24)), - EnvoyServerProtoData.ConnectionSourceType.ANY, - Arrays.asList(15000, 8000), - Arrays.asList(), - null); + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of( + EnvoyServerProtoData.CidrRange.create("10.1.0.0", 16), + EnvoyServerProtoData.CidrRange.create("10.1.2.0", 30)), + ImmutableList.of(), + ImmutableList.of( + EnvoyServerProtoData.CidrRange.create("10.4.2.0", 24), + EnvoyServerProtoData.CidrRange.create("192.168.2.0", 24)), + EnvoyServerProtoData.ConnectionSourceType.ANY, + ImmutableList.of(15000, 8000), + ImmutableList.of(), + ""); EnvoyServerProtoData.FilterChain filterChain5 = - new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChain.create( "filter-chain-5", filterChainMatch5, HTTP_CONNECTION_MANAGER, tlsContext5, tlsContextManager); // has prefix range with prefixLen of 29: gets eliminated in step 2 EnvoyServerProtoData.FilterChainMatch filterChainMatch6 = - new EnvoyServerProtoData.FilterChainMatch( - 0, - Arrays.asList(new EnvoyServerProtoData.CidrRange("10.1.2.0", 29)), - Arrays.asList(), - Arrays.asList(), - EnvoyServerProtoData.ConnectionSourceType.ANY, - Arrays.asList(), - Arrays.asList(), - null); + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of(EnvoyServerProtoData.CidrRange.create("10.1.2.0", 29)), + ImmutableList.of(), + ImmutableList.of(), + EnvoyServerProtoData.ConnectionSourceType.ANY, + ImmutableList.of(), + ImmutableList.of(), + ""); EnvoyServerProtoData.FilterChain filterChain6 = - new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChain.create( "filter-chain-6", filterChainMatch6, HTTP_CONNECTION_MANAGER, tlsContext6, tlsContextManager); - EnvoyServerProtoData.FilterChain defaultFilterChain = new EnvoyServerProtoData.FilterChain( - "filter-chain-7", null, HTTP_CONNECTION_MANAGER, null, tlsContextManager); + EnvoyServerProtoData.FilterChain defaultFilterChain = EnvoyServerProtoData.FilterChain.create( + "filter-chain-7", DEFAULT_FILTER_CHAIN_MATCH, HTTP_CONNECTION_MANAGER, null, + tlsContextManager); Map> map = new HashMap<>(); map.put(filterChain1, randomConfig("1")); @@ -1061,7 +1078,7 @@ public void filterChain_5stepMatch() throws Exception { map.put(filterChain5, noopConfig); map.put(filterChain6, randomConfig("6")); selectorManager.updateSelector(new FilterChainSelector( - map, defaultFilterChain.getSslContextProviderSupplier(), randomConfig("default"))); + map, defaultFilterChain.sslContextProviderSupplier(), randomConfig("default"))); FilterChainMatchingHandler filterChainMatchingHandler = new FilterChainMatchingHandler(grpcHandler, selectorManager, mockDelegate); @@ -1071,7 +1088,7 @@ public void filterChain_5stepMatch() throws Exception { setupChannel(LOCAL_IP, REMOTE_IP, 15000, filterChainMatchingHandler); pipeline.fireUserEventTriggered(event); channel.runPendingTasks(); - assertThat(sslSet.get()).isEqualTo(filterChain5.getSslContextProviderSupplier()); + assertThat(sslSet.get()).isEqualTo(filterChain5.sslContextProviderSupplier()); assertThat(routingSettable.get()).isEqualTo(noopConfig); assertThat(sslSet.get().getTlsContext()).isSameInstanceAs(tlsContext5); } @@ -1087,54 +1104,54 @@ public void filterChainMatch_unsupportedMatchers() throws Exception { CommonTlsContextTestsUtil.buildTestInternalDownstreamTlsContext("CERT3", "ROOTCA"); EnvoyServerProtoData.FilterChainMatch filterChainMatch1 = - new EnvoyServerProtoData.FilterChainMatch( + EnvoyServerProtoData.FilterChainMatch.create( 0 /* destinationPort */, - Collections.singletonList( - new EnvoyServerProtoData.CidrRange("10.1.0.0", 16)) /* prefixRange */, - Arrays.asList("managed-mtls", "h2") /* applicationProtocol */, - Collections.emptyList() /* sourcePrefixRanges */, + ImmutableList.of( + EnvoyServerProtoData.CidrRange.create("10.1.0.0", 16)) /* prefixRange */, + ImmutableList.of("managed-mtls", "h2") /* applicationProtocol */, + ImmutableList.of() /* sourcePrefixRanges */, EnvoyServerProtoData.ConnectionSourceType.ANY /* sourceType */, - Collections.emptyList() /* sourcePorts */, - Arrays.asList("server1", "server2") /* serverNames */, + ImmutableList.of() /* sourcePorts */, + ImmutableList.of("server1", "server2") /* serverNames */, "tls" /* transportProtocol */); EnvoyServerProtoData.FilterChainMatch filterChainMatch2 = - new EnvoyServerProtoData.FilterChainMatch( + EnvoyServerProtoData.FilterChainMatch.create( 0 /* destinationPort */, - Collections.singletonList( - new EnvoyServerProtoData.CidrRange("10.0.0.0", 8)) /* prefixRange */, - Collections.emptyList() /* applicationProtocol */, - Collections.emptyList() /* sourcePrefixRanges */, + ImmutableList.of( + EnvoyServerProtoData.CidrRange.create("10.0.0.0", 8)) /* prefixRange */, + ImmutableList.of() /* applicationProtocol */, + ImmutableList.of() /* sourcePrefixRanges */, EnvoyServerProtoData.ConnectionSourceType.ANY /* sourceType */, - Collections.emptyList() /* sourcePorts */, - Collections.emptyList() /* serverNames */, + ImmutableList.of() /* sourcePorts */, + ImmutableList.of() /* serverNames */, "" /* transportProtocol */); EnvoyServerProtoData.FilterChainMatch defaultFilterChainMatch = - new EnvoyServerProtoData.FilterChainMatch( + EnvoyServerProtoData.FilterChainMatch.create( 0 /* destinationPort */, - Collections.emptyList() /* prefixRange */, - Collections.emptyList() /* applicationProtocol */, - Collections.emptyList() /* sourcePrefixRanges */, + ImmutableList.of() /* prefixRange */, + ImmutableList.of() /* applicationProtocol */, + ImmutableList.of() /* sourcePrefixRanges */, EnvoyServerProtoData.ConnectionSourceType.ANY /* sourceType */, - Collections.emptyList() /* sourcePorts */, - Collections.emptyList() /* serverNames */, + ImmutableList.of() /* sourcePorts */, + ImmutableList.of() /* serverNames */, "" /* transportProtocol */); - EnvoyServerProtoData.FilterChain filterChain1 = new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChain filterChain1 = EnvoyServerProtoData.FilterChain.create( "filter-chain-foo", filterChainMatch1, HTTP_CONNECTION_MANAGER, tlsContext1, mock(TlsContextManager.class)); - EnvoyServerProtoData.FilterChain filterChain2 = new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChain filterChain2 = EnvoyServerProtoData.FilterChain.create( "filter-chain-bar", filterChainMatch2, HTTP_CONNECTION_MANAGER, tlsContext2, mock(TlsContextManager.class)); - EnvoyServerProtoData.FilterChain defaultFilterChain = new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChain defaultFilterChain = EnvoyServerProtoData.FilterChain.create( "filter-chain-baz", defaultFilterChainMatch, HTTP_CONNECTION_MANAGER, tlsContext3, mock(TlsContextManager.class)); selectorManager.updateSelector(new FilterChainSelector( ImmutableMap.of(filterChain1, randomConfig("1"), filterChain2, randomConfig("2")), - defaultFilterChain.getSslContextProviderSupplier(), noopConfig)); + defaultFilterChain.sslContextProviderSupplier(), noopConfig)); FilterChainMatchingHandler filterChainMatchingHandler = new FilterChainMatchingHandler(grpcHandler, selectorManager, mockDelegate); @@ -1143,7 +1160,7 @@ public void filterChainMatch_unsupportedMatchers() throws Exception { setupChannel(LOCAL_IP, REMOTE_IP, 15000, filterChainMatchingHandler); pipeline.fireUserEventTriggered(event); channel.runPendingTasks(); - assertThat(sslSet.get()).isEqualTo(defaultFilterChain.getSslContextProviderSupplier()); + assertThat(sslSet.get()).isEqualTo(defaultFilterChain.sslContextProviderSupplier()); assertThat(routingSettable.get()).isEqualTo(noopConfig); assertThat(sslSet.get().getTlsContext().getCommonTlsContext() .getTlsCertificateCertificateProviderInstance() diff --git a/xds/src/test/java/io/grpc/xds/XdsClientWrapperForServerSdsTestMisc.java b/xds/src/test/java/io/grpc/xds/XdsClientWrapperForServerSdsTestMisc.java index a39a5495c09..e7d090b6cd1 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientWrapperForServerSdsTestMisc.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientWrapperForServerSdsTestMisc.java @@ -29,6 +29,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.SettableFuture; import io.grpc.Server; import io.grpc.ServerBuilder; @@ -66,8 +67,6 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; -import java.util.Arrays; -import java.util.Collections; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -160,10 +159,10 @@ public void run() { assertThat(ldsWatched).isEqualTo("grpc/server?udpa.resource.listening_address=0.0.0.0:" + PORT); EnvoyServerProtoData.Listener listener = - new EnvoyServerProtoData.Listener( + EnvoyServerProtoData.Listener.create( "listener1", "10.1.2.3", - Collections.emptyList(), + ImmutableList.of(), null); LdsUpdate listenerUpdate = LdsUpdate.forTcpListener(listener); xdsClient.ldsWatcher.onChanged(listenerUpdate); @@ -268,7 +267,7 @@ public void releaseOldSupplierOnChangedOnShutdown_verifyClose() throws Exception assertThat(returnedSupplier.getTlsContext()).isSameInstanceAs(tlsContext1); callUpdateSslContext(returnedSupplier); XdsServerTestHelper - .generateListenerUpdate(xdsClient, Arrays.asList(1234), tlsContext2, + .generateListenerUpdate(xdsClient, ImmutableList.of(1234), tlsContext2, tlsContext3, tlsContextManager); returnedSupplier = getSslContextProviderSupplier(selectorManager.getSelectorToUpdateSelector()); assertThat(returnedSupplier.getTlsContext()).isSameInstanceAs(tlsContext2); @@ -379,7 +378,7 @@ public void run() { }); xdsClient.ldsResource.get(5, TimeUnit.SECONDS); XdsServerTestHelper - .generateListenerUpdate(xdsClient, Arrays.asList(), tlsContext, + .generateListenerUpdate(xdsClient, ImmutableList.of(), tlsContext, tlsContextForDefaultFilterChain, tlsContextManager); start.get(5, TimeUnit.SECONDS); InetAddress ipRemoteAddress = InetAddress.getByName("10.4.5.6"); diff --git a/xds/src/test/java/io/grpc/xds/XdsSdsClientServerTest.java b/xds/src/test/java/io/grpc/xds/XdsSdsClientServerTest.java index 579542a2777..36669537255 100644 --- a/xds/src/test/java/io/grpc/xds/XdsSdsClientServerTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsSdsClientServerTest.java @@ -49,6 +49,7 @@ import io.grpc.testing.protobuf.SimpleRequest; import io.grpc.testing.protobuf.SimpleResponse; import io.grpc.testing.protobuf.SimpleServiceGrpc; +import io.grpc.xds.EnvoyServerProtoData.ConnectionSourceType; import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.Filter.FilterConfig; @@ -364,15 +365,15 @@ static EnvoyServerProtoData.Listener buildListener( String name, String address, DownstreamTlsContext tlsContext, TlsContextManager tlsContextManager) { EnvoyServerProtoData.FilterChainMatch filterChainMatch = - new EnvoyServerProtoData.FilterChainMatch( + EnvoyServerProtoData.FilterChainMatch.create( 0, - Arrays.asList(), - Arrays.asList(), - Arrays.asList(), - null, - Arrays.asList(), - Arrays.asList(), - null); + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of(), + ConnectionSourceType.ANY, + ImmutableList.of(), + ImmutableList.of(), + ""); String fullPath = "/" + SimpleServiceGrpc.SERVICE_NAME + "/" + "UnaryRpc"; RouteMatch routeMatch = RouteMatch.create( @@ -386,11 +387,11 @@ static EnvoyServerProtoData.Listener buildListener( HttpConnectionManager httpConnectionManager = HttpConnectionManager.forVirtualHosts( 0L, Collections.singletonList(virtualHost), new ArrayList()); - EnvoyServerProtoData.FilterChain defaultFilterChain = new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChain defaultFilterChain = EnvoyServerProtoData.FilterChain.create( "filter-chain-foo", filterChainMatch, httpConnectionManager, tlsContext, tlsContextManager); - EnvoyServerProtoData.Listener listener = - new EnvoyServerProtoData.Listener(name, address, Arrays.asList(defaultFilterChain), null); + EnvoyServerProtoData.Listener listener = EnvoyServerProtoData.Listener.create( + name, address, ImmutableList.of(defaultFilterChain), null); return listener; } diff --git a/xds/src/test/java/io/grpc/xds/XdsServerTestHelper.java b/xds/src/test/java/io/grpc/xds/XdsServerTestHelper.java index 66b3d00a84b..15868ba414e 100644 --- a/xds/src/test/java/io/grpc/xds/XdsServerTestHelper.java +++ b/xds/src/test/java/io/grpc/xds/XdsServerTestHelper.java @@ -18,11 +18,13 @@ import static com.google.common.truth.Truth.assertThat; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.SettableFuture; import io.grpc.InsecureChannelCredentials; import io.grpc.internal.ObjectPool; import io.grpc.xds.Bootstrapper.BootstrapInfo; +import io.grpc.xds.EnvoyServerProtoData.ConnectionSourceType; import io.grpc.xds.EnvoyServerProtoData.FilterChain; import io.grpc.xds.EnvoyServerProtoData.Listener; import io.grpc.xds.Filter.FilterConfig; @@ -61,13 +63,13 @@ static void generateListenerUpdate(FakeXdsClient xdsClient, EnvoyServerProtoData.DownstreamTlsContext tlsContext, TlsContextManager tlsContextManager) { EnvoyServerProtoData.Listener listener = buildTestListener("listener1", "10.1.2.3", - Arrays.asList(), tlsContext, null, tlsContextManager); + ImmutableList.of(), tlsContext, null, tlsContextManager); LdsUpdate listenerUpdate = LdsUpdate.forTcpListener(listener); xdsClient.deliverLdsUpdate(listenerUpdate); } static void generateListenerUpdate( - FakeXdsClient xdsClient, List sourcePorts, + FakeXdsClient xdsClient, ImmutableList sourcePorts, EnvoyServerProtoData.DownstreamTlsContext tlsContext, EnvoyServerProtoData.DownstreamTlsContext tlsContextForDefaultFilterChain, TlsContextManager tlsContextManager) { @@ -78,35 +80,45 @@ static void generateListenerUpdate( } static EnvoyServerProtoData.Listener buildTestListener( - String name, String address, List sourcePorts, + String name, String address, ImmutableList sourcePorts, EnvoyServerProtoData.DownstreamTlsContext tlsContext, EnvoyServerProtoData.DownstreamTlsContext tlsContextForDefaultFilterChain, TlsContextManager tlsContextManager) { EnvoyServerProtoData.FilterChainMatch filterChainMatch1 = - new EnvoyServerProtoData.FilterChainMatch( + EnvoyServerProtoData.FilterChainMatch.create( 0, - Arrays.asList(), - Arrays.asList(), - Arrays.asList(), - null, + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of(), + ConnectionSourceType.ANY, sourcePorts, - Arrays.asList(), - null); + ImmutableList.of(), + ""); + EnvoyServerProtoData.FilterChainMatch defaultFilterChainMatch = + EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of(), + ConnectionSourceType.ANY, + ImmutableList.of(), + ImmutableList.of(), + ""); VirtualHost virtualHost = VirtualHost.create( "virtual-host", Collections.singletonList("auth"), new ArrayList(), ImmutableMap.of()); HttpConnectionManager httpConnectionManager = HttpConnectionManager.forVirtualHosts( 0L, Collections.singletonList(virtualHost), new ArrayList()); - EnvoyServerProtoData.FilterChain filterChain1 = new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChain filterChain1 = EnvoyServerProtoData.FilterChain.create( "filter-chain-foo", filterChainMatch1, httpConnectionManager, tlsContext, tlsContextManager); - EnvoyServerProtoData.FilterChain defaultFilterChain = new EnvoyServerProtoData.FilterChain( - "filter-chain-bar", null, httpConnectionManager, tlsContextForDefaultFilterChain, - tlsContextManager); + EnvoyServerProtoData.FilterChain defaultFilterChain = EnvoyServerProtoData.FilterChain.create( + "filter-chain-bar", defaultFilterChainMatch, httpConnectionManager, + tlsContextForDefaultFilterChain, tlsContextManager); EnvoyServerProtoData.Listener listener = - new EnvoyServerProtoData.Listener( - name, address, Arrays.asList(filterChain1), defaultFilterChain); + EnvoyServerProtoData.Listener.create( + name, address, ImmutableList.of(filterChain1), defaultFilterChain); return listener; } @@ -202,8 +214,8 @@ boolean isShutDown() { void deliverLdsUpdate(List filterChains, FilterChain defaultFilterChain) { - ldsWatcher.onChanged(LdsUpdate.forTcpListener(new Listener( - "listener", "0.0.0.0:1", filterChains, defaultFilterChain))); + ldsWatcher.onChanged(LdsUpdate.forTcpListener(Listener.create( + "listener", "0.0.0.0:1", ImmutableList.copyOf(filterChains), defaultFilterChain))); } void deliverLdsUpdate(LdsUpdate ldsUpdate) { diff --git a/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java b/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java index ac7f41e65a9..f8b8ca2e105 100644 --- a/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java @@ -234,8 +234,8 @@ public void run() { assertThat(xdsClient.ldsResource).isNull(); assertThat(xdsClient.shutdown).isTrue(); verify(mockServer).shutdown(); - assertThat(f0.getSslContextProviderSupplier().isShutdown()).isTrue(); - assertThat(f1.getSslContextProviderSupplier().isShutdown()).isTrue(); + assertThat(f0.sslContextProviderSupplier().isShutdown()).isTrue(); + assertThat(f1.sslContextProviderSupplier().isShutdown()).isTrue(); when(mockServer.isTerminated()).thenReturn(true); when(mockServer.awaitTermination(anyLong(), any(TimeUnit.class))).thenReturn(true); assertThat(xdsServerWrapper.awaitTermination(5, TimeUnit.SECONDS)).isTrue(); @@ -276,8 +276,8 @@ public void run() { assertThat(xdsClient.ldsResource).isNull(); assertThat(xdsClient.shutdown).isTrue(); verify(mockServer).shutdown(); - assertThat(f0.getSslContextProviderSupplier().isShutdown()).isTrue(); - assertThat(f1.getSslContextProviderSupplier().isShutdown()).isTrue(); + assertThat(f0.sslContextProviderSupplier().isShutdown()).isTrue(); + assertThat(f1.sslContextProviderSupplier().isShutdown()).isTrue(); assertThat(start.isDone()).isFalse(); //shall we set initialStatus when shutdown? } @@ -335,7 +335,7 @@ public void run() { xdsClient.ldsResource.get(5, TimeUnit.SECONDS); when(mockServer.start()).thenThrow(new IOException("error!")); FilterChain filterChain = createFilterChain("filter-chain-1", createRds("rds")); - SslContextProviderSupplier sslSupplier = filterChain.getSslContextProviderSupplier(); + SslContextProviderSupplier sslSupplier = filterChain.sslContextProviderSupplier(); xdsClient.deliverLdsUpdate(Collections.singletonList(filterChain), null); xdsClient.rdsCount.await(5, TimeUnit.SECONDS); xdsClient.deliverRdsUpdate("rds", @@ -437,7 +437,7 @@ public void run() { ImmutableMap.of()); HttpConnectionManager httpConnectionManager = HttpConnectionManager.forVirtualHosts( 0L, Collections.singletonList(virtualHost), new ArrayList()); - EnvoyServerProtoData.FilterChain filterChain = new EnvoyServerProtoData.FilterChain( + EnvoyServerProtoData.FilterChain filterChain = EnvoyServerProtoData.FilterChain.create( "filter-chain-foo", createMatch(), httpConnectionManager, createTls(), mock(TlsContextManager.class)); xdsClient.deliverLdsUpdate(Collections.singletonList(filterChain), null); @@ -509,7 +509,7 @@ public void run() { assertThat(realConfig.virtualHosts()).isEqualTo( Collections.singletonList(createVirtualHost("virtual-host-2"))); assertThat(selectorManager.getSelectorToUpdateSelector().getDefaultSslContextProviderSupplier()) - .isEqualTo(f3.getSslContextProviderSupplier()); + .isEqualTo(f3.sslContextProviderSupplier()); } @Test @@ -557,7 +557,7 @@ public void run() { Collections.singletonList(createVirtualHost("virtual-host-0"))); assertThat(realConfig.interceptors()).isEqualTo(ImmutableMap.of()); assertThat(selectorManager.getSelectorToUpdateSelector().getDefaultSslContextProviderSupplier()) - .isSameInstanceAs(f2.getSslContextProviderSupplier()); + .isSameInstanceAs(f2.sslContextProviderSupplier()); EnvoyServerProtoData.FilterChain f3 = createFilterChain("filter-chain-3", createRds("r0")); EnvoyServerProtoData.FilterChain f4 = createFilterChain("filter-chain-4", createRds("r1")); @@ -587,7 +587,7 @@ public void run() { assertThat(realConfig.interceptors()).isEqualTo(ImmutableMap.of()); assertThat(selectorManager.getSelectorToUpdateSelector().getDefaultSslContextProviderSupplier()) - .isSameInstanceAs(f4.getSslContextProviderSupplier()); + .isSameInstanceAs(f4.sslContextProviderSupplier()); verify(mockServer, times(1)).start(); xdsServerWrapper.shutdown(); verify(mockServer, times(1)).shutdown(); @@ -675,7 +675,7 @@ public void run() { verify(listener, times(1)).onNotServing(any(StatusException.class)); verify(mockBuilder, times(1)).build(); FilterChain filterChain0 = createFilterChain("filter-chain-0", createRds("rds")); - SslContextProviderSupplier sslSupplier0 = filterChain0.getSslContextProviderSupplier(); + SslContextProviderSupplier sslSupplier0 = filterChain0.sslContextProviderSupplier(); xdsClient.deliverLdsUpdate(Collections.singletonList(filterChain0), null); xdsClient.ldsWatcher.onError(Status.INTERNAL); assertThat(selectorManager.getSelectorToUpdateSelector()) @@ -688,7 +688,7 @@ public void run() { when(mockServer.start()).thenThrow(new IOException("error!")) .thenReturn(mockServer); FilterChain filterChain1 = createFilterChain("filter-chain-1", createRds("rds")); - SslContextProviderSupplier sslSupplier1 = filterChain1.getSslContextProviderSupplier(); + SslContextProviderSupplier sslSupplier1 = filterChain1.sslContextProviderSupplier(); xdsClient.deliverLdsUpdate(Collections.singletonList(filterChain1), null); assertThat(sslSupplier0.isShutdown()).isTrue(); xdsClient.deliverRdsUpdate("rds", @@ -752,7 +752,7 @@ public void run() { .thenThrow(new IOException("error2!")) .thenReturn(mockServer); FilterChain filterChain2 = createFilterChain("filter-chain-2", createRds("rds")); - SslContextProviderSupplier sslSupplier2 = filterChain2.getSslContextProviderSupplier(); + SslContextProviderSupplier sslSupplier2 = filterChain2.sslContextProviderSupplier(); xdsClient.deliverLdsUpdate(Collections.singletonList(filterChain2), null); xdsClient.deliverRdsUpdate("rds", Collections.singletonList(createVirtualHost("virtual-host-1"))); @@ -780,7 +780,7 @@ public void run() { // serving after not serving FilterChain filterChain3 = createFilterChain("filter-chain-2", createRds("rds")); - SslContextProviderSupplier sslSupplier3 = filterChain3.getSslContextProviderSupplier(); + SslContextProviderSupplier sslSupplier3 = filterChain3.sslContextProviderSupplier(); xdsClient.deliverLdsUpdate(Collections.singletonList(filterChain3), null); xdsClient.deliverRdsUpdate("rds", Collections.singletonList(createVirtualHost("virtual-host-1"))); @@ -1187,7 +1187,7 @@ public ServerCall.Listener interceptCall(ServerCallasList(), - Arrays.asList(), - Arrays.asList(), - EnvoyServerProtoData.ConnectionSourceType.ANY, - Arrays.asList(), - Arrays.asList(), - null); + return EnvoyServerProtoData.FilterChainMatch.create( + 0, + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of(), + EnvoyServerProtoData.ConnectionSourceType.ANY, + ImmutableList.of(), + ImmutableList.of(), + ""); } private static ServerRoutingConfig createRoutingConfig(String path, String domain, From fbb1dbf7a519671331decf5ab3e0ed9716e1823a Mon Sep 17 00:00:00 2001 From: "Penn (Dapeng) Zhang" Date: Wed, 9 Feb 2022 18:20:59 -0800 Subject: [PATCH 48/68] xds: update javadoc to reference v3 proto instead of v2 --- xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java b/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java index 2fa6b868c65..e53439755be 100644 --- a/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java +++ b/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java @@ -165,7 +165,7 @@ enum ConnectionSourceType { /** * Corresponds to Envoy proto message - * {@link io.envoyproxy.envoy.api.v2.listener.FilterChainMatch}. + * {@link io.envoyproxy.envoy.config.listener.v3.FilterChainMatch}. */ @AutoValue abstract static class FilterChainMatch { @@ -198,7 +198,7 @@ public static FilterChainMatch create(int destinationPort, } /** - * Corresponds to Envoy proto message {@link io.envoyproxy.envoy.api.v2.listener.FilterChain}. + * Corresponds to Envoy proto message {@link io.envoyproxy.envoy.config.listener.v3.FilterChain}. */ @AutoValue abstract static class FilterChain { @@ -229,8 +229,8 @@ static FilterChain create( } /** - * Corresponds to Envoy proto message {@link io.envoyproxy.envoy.api.v2.Listener} & related - * classes. + * Corresponds to Envoy proto message {@link io.envoyproxy.envoy.config.listener.v3.Listener} and + * related classes. */ @AutoValue abstract static class Listener { From 560a7fb08437e9b538e5e1751f12caf6207df530 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Fri, 11 Feb 2022 13:02:23 -0800 Subject: [PATCH 49/68] okhttp: local-only transparent retry for okhttp The OKHttp counterpart for #8878. --- .../io/grpc/okhttp/OkHttpClientTransport.java | 9 +++-- .../okhttp/OkHttpClientTransportTest.java | 36 ++++++++++++++++++- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java index 121093716db..cf7cae19d8a 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java @@ -419,7 +419,7 @@ public OkHttpClientStream newStream( void streamReadyToStart(OkHttpClientStream clientStream) { if (goAwayStatus != null) { clientStream.transportState().transportReportStatus( - goAwayStatus, RpcProgress.REFUSED, true, new Metadata()); + goAwayStatus, RpcProgress.MISCARRIED, true, new Metadata()); } else if (streams.size() >= maxConcurrentStreams) { pendingStreams.add(clientStream); setInUse(clientStream); @@ -811,7 +811,10 @@ public void shutdownNow(Status reason) { } for (OkHttpClientStream stream : pendingStreams) { - stream.transportState().transportReportStatus(reason, true, new Metadata()); + // in cases such as the connection fails to ACK keep-alive, pending streams should have a + // chance to retry and be routed to another connection. + stream.transportState().transportReportStatus( + reason, RpcProgress.MISCARRIED, true, new Metadata()); maybeClearInUse(stream); } pendingStreams.clear(); @@ -894,7 +897,7 @@ private void startGoAway(int lastKnownStreamId, ErrorCode errorCode, Status stat for (OkHttpClientStream stream : pendingStreams) { stream.transportState().transportReportStatus( - status, RpcProgress.REFUSED, true, new Metadata()); + status, RpcProgress.MISCARRIED, true, new Metadata()); maybeClearInUse(stream); } pendingStreams.clear(); diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java index b70b832a797..1628df4d3c3 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java @@ -18,6 +18,7 @@ import static com.google.common.base.Charsets.UTF_8; import static com.google.common.truth.Truth.assertThat; +import static io.grpc.internal.ClientStreamListener.RpcProgress.MISCARRIED; import static io.grpc.internal.ClientStreamListener.RpcProgress.PROCESSED; import static io.grpc.internal.ClientStreamListener.RpcProgress.REFUSED; import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE; @@ -2080,7 +2081,7 @@ public void goAway_streamListenerRpcProgress() throws Exception { listener3.waitUntilStreamClosed(); assertNull(listener1.rpcProgress); assertEquals(REFUSED, listener2.rpcProgress); - assertEquals(REFUSED, listener3.rpcProgress); + assertEquals(MISCARRIED, listener3.rpcProgress); assertEquals(1, activeStreamCount()); assertContainStream(DEFAULT_START_STREAM_ID); @@ -2133,6 +2134,39 @@ public void reset_streamListenerRpcProgress() throws Exception { shutdownAndVerify(); } + @Test + public void shutdownNow_streamListenerRpcProgress() throws Exception { + initTransport(); + setMaxConcurrentStreams(2); + MockStreamListener listener1 = new MockStreamListener(); + MockStreamListener listener2 = new MockStreamListener(); + MockStreamListener listener3 = new MockStreamListener(); + OkHttpClientStream stream1 = + clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); + stream1.start(listener1); + OkHttpClientStream stream2 = + clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); + stream2.start(listener2); + OkHttpClientStream stream3 = + clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT, tracers); + stream3.start(listener3); + waitForStreamPending(1); + + assertEquals(2, activeStreamCount()); + assertContainStream(DEFAULT_START_STREAM_ID); + assertContainStream(DEFAULT_START_STREAM_ID + 2); + + clientTransport.shutdownNow(Status.INTERNAL); + + listener1.waitUntilStreamClosed(); + listener2.waitUntilStreamClosed(); + listener3.waitUntilStreamClosed(); + + assertEquals(PROCESSED, listener1.rpcProgress); + assertEquals(PROCESSED, listener2.rpcProgress); + assertEquals(MISCARRIED, listener3.rpcProgress); + } + private int activeStreamCount() { return clientTransport.getActiveStreams().length; } From 7eeb411b1fbaad3a74c1c0e0d0a44772f1a70c8e Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Fri, 11 Feb 2022 15:07:09 -0800 Subject: [PATCH 50/68] doc: add comment about callOptions executor behavior (#8913) --- api/src/main/java/io/grpc/CallOptions.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/api/src/main/java/io/grpc/CallOptions.java b/api/src/main/java/io/grpc/CallOptions.java index c7504a6506a..5c05d5b7bd7 100644 --- a/api/src/main/java/io/grpc/CallOptions.java +++ b/api/src/main/java/io/grpc/CallOptions.java @@ -358,6 +358,11 @@ public T getOption(Key key) { return key.defaultValue; } + /** + * Returns the executor override to use for this specific call, or {@code null} if there is no + * override. The executor is only for servicing this one call, so is not safe to use after + * {@link ClientCall.Listener#onClose}. + */ @Nullable public Executor getExecutor() { return executor; From bb3365731fa3cd6c082e6346b81d0ea14646ffc1 Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Fri, 11 Feb 2022 15:08:11 -0800 Subject: [PATCH 51/68] stub: Have ClientCalls.ThreadlessExecutor reject Runnables after end of RPC (#8847) Changes originally proposed as part of #7106. Fixes #3557 Co-authored-by: Nick Hill --- .../main/java/io/grpc/stub/ClientCalls.java | 42 +++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/stub/src/main/java/io/grpc/stub/ClientCalls.java b/stub/src/main/java/io/grpc/stub/ClientCalls.java index 04ed83f083a..84417ee7ebf 100644 --- a/stub/src/main/java/io/grpc/stub/ClientCalls.java +++ b/stub/src/main/java/io/grpc/stub/ClientCalls.java @@ -39,6 +39,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.locks.LockSupport; import java.util.logging.Level; import java.util.logging.Logger; @@ -161,6 +162,7 @@ public static RespT blockingUnaryCall( // Something very bad happened. All bets are off; it may be dangerous to wait for onClose(). throw cancelThrow(call, e); } finally { + executor.shutdown(); if (interrupt) { Thread.currentThread().interrupt(); } @@ -626,6 +628,9 @@ private Object waitForNext() { // Now wait for onClose() to be called, so interceptors can clean up } } + if (next == this || next instanceof StatusRuntimeException) { + threadless.shutdown(); + } return next; } } finally { @@ -712,7 +717,10 @@ private static final class ThreadlessExecutor extends ConcurrentLinkedQueue Date: Mon, 14 Feb 2022 07:10:18 -0800 Subject: [PATCH 52/68] authz: translate gRPC authz policy to Envoy RBAC proto (#8710) --- authz/build.gradle | 77 +++ .../authz/AuthorizationPolicyTranslator.java | 198 ++++++ .../AuthorizationPolicyTranslatorTest.java | 574 ++++++++++++++++++ settings.gradle | 2 + 4 files changed, 851 insertions(+) create mode 100644 authz/build.gradle create mode 100644 authz/src/main/java/io/grpc/authz/AuthorizationPolicyTranslator.java create mode 100644 authz/src/test/java/io/grpc/authz/AuthorizationPolicyTranslatorTest.java diff --git a/authz/build.gradle b/authz/build.gradle new file mode 100644 index 00000000000..f6110a5850c --- /dev/null +++ b/authz/build.gradle @@ -0,0 +1,77 @@ +plugins { + id "java-library" + id "maven-publish" + + id "com.github.johnrengelman.shadow" + id "com.google.protobuf" + id "ru.vyarus.animalsniffer" +} + +description = "gRPC: Authorization" + +dependencies { + implementation project(':grpc-protobuf'), + project(':grpc-core') + + annotationProcessor libraries.autovalue + compileOnly libraries.javax_annotation + + testImplementation project(':grpc-testing'), + project(':grpc-testing-proto') + testImplementation (libraries.guava_testlib) { + exclude group: 'junit', module: 'junit' + } + + def xdsDependency = implementation project(':grpc-xds') + shadow configurations.implementation.getDependencies().minus([xdsDependency]) + shadow project(path: ':grpc-xds', configuration: 'shadow') + + signature "org.codehaus.mojo.signature:java17:1.0@signature" +} + +jar { + classifier = 'original' +} + +// TODO(ashithasantosh): Remove javadoc exclusion on adding authorization +// interceptor implementations. +javadoc { + exclude "io/grpc/authz/*" +} + +shadowJar { + classifier = null + dependencies { + exclude(dependency {true}) + } + relocate 'io.grpc.xds', 'io.grpc.xds.shaded.io.grpc.xds' + relocate 'udpa.annotations', 'io.grpc.xds.shaded.udpa.annotations' + relocate 'com.github.udpa', 'io.grpc.xds.shaded.com.github.udpa' + relocate 'envoy.annotations', 'io.grpc.xds.shaded.envoy.annotations' + relocate 'io.envoyproxy', 'io.grpc.xds.shaded.io.envoyproxy' + relocate 'com.google.api.expr', 'io.grpc.xds.shaded.com.google.api.expr' +} + +publishing { + publications { + maven(MavenPublication) { + // We want this to throw an exception if it isn't working + def originalJar = artifacts.find { dep -> dep.classifier == 'original'} + artifacts.remove(originalJar) + + pom.withXml { + def dependenciesNode = new Node(null, 'dependencies') + project.configurations.shadow.allDependencies.each { dep -> + def dependencyNode = dependenciesNode.appendNode('dependency') + dependencyNode.appendNode('groupId', dep.group) + dependencyNode.appendNode('artifactId', dep.name) + dependencyNode.appendNode('version', dep.version) + dependencyNode.appendNode('scope', 'compile') + } + asNode().dependencies[0].replaceNode(dependenciesNode) + } + } + } +} + +[publishMavenPublicationToMavenRepository]*.onlyIf {false} diff --git a/authz/src/main/java/io/grpc/authz/AuthorizationPolicyTranslator.java b/authz/src/main/java/io/grpc/authz/AuthorizationPolicyTranslator.java new file mode 100644 index 00000000000..1637af737ad --- /dev/null +++ b/authz/src/main/java/io/grpc/authz/AuthorizationPolicyTranslator.java @@ -0,0 +1,198 @@ +/* + * Copyright 2021 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.authz; + +import com.google.common.collect.ImmutableList; +import io.envoyproxy.envoy.config.rbac.v3.Permission; +import io.envoyproxy.envoy.config.rbac.v3.Policy; +import io.envoyproxy.envoy.config.rbac.v3.Principal; +import io.envoyproxy.envoy.config.rbac.v3.Principal.Authenticated; +import io.envoyproxy.envoy.config.rbac.v3.RBAC; +import io.envoyproxy.envoy.config.rbac.v3.RBAC.Action; +import io.envoyproxy.envoy.config.route.v3.HeaderMatcher; +import io.envoyproxy.envoy.type.matcher.v3.PathMatcher; +import io.envoyproxy.envoy.type.matcher.v3.RegexMatcher; +import io.envoyproxy.envoy.type.matcher.v3.StringMatcher; +import io.grpc.internal.JsonParser; +import io.grpc.internal.JsonUtil; +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Translates a gRPC authorization policy in JSON string to Envoy RBAC policies. + */ +class AuthorizationPolicyTranslator { + private static final ImmutableList UNSUPPORTED_HEADERS = ImmutableList.of( + "host", "connection", "keep-alive", "proxy-authenticate", "proxy-authorization", + "te", "trailer", "transfer-encoding", "upgrade"); + + private static StringMatcher getStringMatcher(String value) { + if (value.equals("*")) { + return StringMatcher.newBuilder().setSafeRegex( + RegexMatcher.newBuilder().setRegex(".+").build()).build(); + } else if (value.startsWith("*")) { + return StringMatcher.newBuilder().setSuffix(value.substring(1)).build(); + } else if (value.endsWith("*")) { + return StringMatcher.newBuilder().setPrefix(value.substring(0, value.length() - 1)).build(); + } + return StringMatcher.newBuilder().setExact(value).build(); + } + + private static Principal parseSource(Map source) { + List principalsList = JsonUtil.getListOfStrings(source, "principals"); + if (principalsList == null || principalsList.isEmpty()) { + return Principal.newBuilder().setAny(true).build(); + } + Principal.Set.Builder principalsSet = Principal.Set.newBuilder(); + for (String principal: principalsList) { + principalsSet.addIds( + Principal.newBuilder().setAuthenticated( + Authenticated.newBuilder().setPrincipalName( + getStringMatcher(principal)).build()).build()); + } + return Principal.newBuilder().setOrIds(principalsSet.build()).build(); + } + + private static Permission parseHeader(Map header) throws IllegalArgumentException { + String key = JsonUtil.getString(header, "key"); + if (key == null || key.isEmpty()) { + throw new IllegalArgumentException("\"key\" is absent or empty"); + } + if (key.charAt(0) == ':' + || key.startsWith("grpc-") + || UNSUPPORTED_HEADERS.contains(key.toLowerCase())) { + throw new IllegalArgumentException(String.format("Unsupported \"key\" %s", key)); + } + List valuesList = JsonUtil.getListOfStrings(header, "values"); + if (valuesList == null || valuesList.isEmpty()) { + throw new IllegalArgumentException("\"values\" is absent or empty"); + } + Permission.Set.Builder orSet = Permission.Set.newBuilder(); + for (String value: valuesList) { + orSet.addRules( + Permission.newBuilder().setHeader( + HeaderMatcher.newBuilder() + .setName(key) + .setStringMatch(getStringMatcher(value)).build()).build()); + } + return Permission.newBuilder().setOrRules(orSet.build()).build(); + } + + private static Permission parseRequest(Map request) throws IllegalArgumentException { + Permission.Set.Builder andSet = Permission.Set.newBuilder(); + List pathsList = JsonUtil.getListOfStrings(request, "paths"); + if (pathsList != null && !pathsList.isEmpty()) { + Permission.Set.Builder pathsSet = Permission.Set.newBuilder(); + for (String path: pathsList) { + pathsSet.addRules( + Permission.newBuilder().setUrlPath( + PathMatcher.newBuilder().setPath( + getStringMatcher(path)).build()).build()); + } + andSet.addRules(Permission.newBuilder().setOrRules(pathsSet.build()).build()); + } + List> headersList = JsonUtil.getListOfObjects(request, "headers"); + if (headersList != null && !headersList.isEmpty()) { + Permission.Set.Builder headersSet = Permission.Set.newBuilder(); + for (Map header: headersList) { + headersSet.addRules(parseHeader(header)); + } + andSet.addRules(Permission.newBuilder().setAndRules(headersSet.build()).build()); + } + if (andSet.getRulesCount() == 0) { + return Permission.newBuilder().setAny(true).build(); + } + return Permission.newBuilder().setAndRules(andSet.build()).build(); + } + + private static Map parseRules( + List> objects, String name) throws IllegalArgumentException { + Map policies = new LinkedHashMap(); + for (Map object: objects) { + String policyName = JsonUtil.getString(object, "name"); + if (policyName == null || policyName.isEmpty()) { + throw new IllegalArgumentException("rule \"name\" is absent or empty"); + } + List principals = new ArrayList<>(); + Map source = JsonUtil.getObject(object, "source"); + if (source != null) { + principals.add(parseSource(source)); + } else { + principals.add(Principal.newBuilder().setAny(true).build()); + } + List permissions = new ArrayList<>(); + Map request = JsonUtil.getObject(object, "request"); + if (request != null) { + permissions.add(parseRequest(request)); + } else { + permissions.add(Permission.newBuilder().setAny(true).build()); + } + Policy policy = + Policy.newBuilder() + .addAllPermissions(permissions) + .addAllPrincipals(principals) + .build(); + policies.put(name + "_" + policyName, policy); + } + return policies; + } + + /** + * Translates a gRPC authorization policy in JSON string to Envoy RBAC policies. + * On success, will return one of the following - + * 1. One allow RBAC policy or, + * 2. Two RBAC policies, deny policy followed by allow policy. + * If the policy cannot be parsed or is invalid, an exception will be thrown. + */ + public static List translate(String authorizationPolicy) + throws IllegalArgumentException, IOException { + Object jsonObject = JsonParser.parse(authorizationPolicy); + if (!(jsonObject instanceof Map)) { + throw new IllegalArgumentException( + "Authorization policy should be a JSON object. Found: " + + (jsonObject == null ? null : jsonObject.getClass())); + } + @SuppressWarnings("unchecked") + Map json = (Map)jsonObject; + String name = JsonUtil.getString(json, "name"); + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException("\"name\" is absent or empty"); + } + List rbacs = new ArrayList<>(); + List> objects = JsonUtil.getListOfObjects(json, "deny_rules"); + if (objects != null && !objects.isEmpty()) { + rbacs.add( + RBAC.newBuilder() + .setAction(Action.DENY) + .putAllPolicies(parseRules(objects, name)) + .build()); + } + objects = JsonUtil.getListOfObjects(json, "allow_rules"); + if (objects == null || objects.isEmpty()) { + throw new IllegalArgumentException("\"allow_rules\" is absent"); + } + rbacs.add( + RBAC.newBuilder() + .setAction(Action.ALLOW) + .putAllPolicies(parseRules(objects, name)) + .build()); + return rbacs; + } +} diff --git a/authz/src/test/java/io/grpc/authz/AuthorizationPolicyTranslatorTest.java b/authz/src/test/java/io/grpc/authz/AuthorizationPolicyTranslatorTest.java new file mode 100644 index 00000000000..b957bf283e7 --- /dev/null +++ b/authz/src/test/java/io/grpc/authz/AuthorizationPolicyTranslatorTest.java @@ -0,0 +1,574 @@ +/* + * Copyright 2021 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.authz; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import io.envoyproxy.envoy.config.rbac.v3.Permission; +import io.envoyproxy.envoy.config.rbac.v3.Policy; +import io.envoyproxy.envoy.config.rbac.v3.Principal; +import io.envoyproxy.envoy.config.rbac.v3.Principal.Authenticated; +import io.envoyproxy.envoy.config.rbac.v3.RBAC; +import io.envoyproxy.envoy.config.rbac.v3.RBAC.Action; +import io.envoyproxy.envoy.config.route.v3.HeaderMatcher; +import io.envoyproxy.envoy.type.matcher.v3.PathMatcher; +import io.envoyproxy.envoy.type.matcher.v3.RegexMatcher; +import io.envoyproxy.envoy.type.matcher.v3.StringMatcher; +import java.io.IOException; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class AuthorizationPolicyTranslatorTest { + @Test + public void invalidPolicy() throws Exception { + String policy = "{ \"name\": \"abc\",, }"; + try { + AuthorizationPolicyTranslator.translate(policy); + fail("exception expected"); + } catch (IOException ioe) { + assertThat(ioe).hasMessageThat().isEqualTo( + "Use JsonReader.setLenient(true) to accept malformed JSON" + + " at line 1 column 18 path $.name"); + } catch (Exception e) { + throw new AssertionError("the test failed ", e); + } + } + + @Test + public void missingAuthorizationPolicyName() throws Exception { + String policy = "{}"; + try { + AuthorizationPolicyTranslator.translate(policy); + fail("exception expected"); + } catch (IllegalArgumentException iae) { + assertThat(iae).hasMessageThat().isEqualTo("\"name\" is absent or empty"); + } catch (Exception e) { + throw new AssertionError("the test failed ", e); + } + } + + @Test + public void incorrectAuthorizationPolicyName() throws Exception { + String policy = "{ \"name\": [\"abc\"] }"; + try { + AuthorizationPolicyTranslator.translate(policy); + fail("exception expected"); + } catch (ClassCastException cce) { + assertThat(cce).hasMessageThat().isEqualTo( + "value '[abc]' for key 'name' in '{name=[abc]}' is not String"); + } catch (Exception e) { + throw new AssertionError("the test failed ", e); + } + } + + @Test + public void missingAllowRules() throws Exception { + String policy = "{ \"name\": \"authz\" }"; + try { + AuthorizationPolicyTranslator.translate(policy); + fail("exception expected"); + } catch (IllegalArgumentException iae) { + assertThat(iae).hasMessageThat().isEqualTo("\"allow_rules\" is absent"); + } catch (Exception e) { + throw new AssertionError("the test failed ", e); + } + } + + @Test + public void missingRuleName() throws Exception { + String policy = "{" + + " \"name\" : \"abc\" ," + + " \"allow_rules\" : [" + + " {}" + + " ]" + + "}"; + try { + AuthorizationPolicyTranslator.translate(policy); + fail("exception expected"); + } catch (IllegalArgumentException iae) { + assertThat(iae).hasMessageThat().isEqualTo("rule \"name\" is absent or empty"); + } catch (Exception e) { + throw new AssertionError("the test failed ", e); + } + } + + @Test + public void missingSourceAndRequest() throws Exception { + String policy = "{" + + " \"name\" : \"authz\" ," + + " \"allow_rules\" : [" + + " {" + + " \"name\": \"allow_all\"" + + " }" + + " ]" + + "}"; + List rbacs = AuthorizationPolicyTranslator.translate(policy); + assertEquals(1, rbacs.size()); + RBAC expected_rbac = + RBAC.newBuilder() + .setAction(Action.ALLOW) + .putPolicies("authz_allow_all", + Policy.newBuilder() + .addPrincipals(Principal.newBuilder().setAny(true)) + .addPermissions(Permission.newBuilder().setAny(true)) + .build()) + .build(); + assertEquals(expected_rbac, rbacs.get(0)); + } + + @Test + public void emptySourceAndRequest() throws Exception { + String policy = "{" + + " \"name\" : \"authz\" ," + + " \"allow_rules\" : [" + + " {" + + " \"name\": \"allow_all\"," + + " \"source\": {}," + + " \"request\": {}" + + " }" + + " ]" + + "}"; + List rbacs = AuthorizationPolicyTranslator.translate(policy); + assertEquals(1, rbacs.size()); + RBAC expected_rbac = + RBAC.newBuilder() + .setAction(Action.ALLOW) + .putPolicies("authz_allow_all", + Policy.newBuilder() + .addPrincipals(Principal.newBuilder().setAny(true)) + .addPermissions(Permission.newBuilder().setAny(true)) + .build()) + .build(); + assertEquals(expected_rbac, rbacs.get(0)); + } + + @Test + public void incorrectRulesType() throws Exception { + String policy = "{" + + " \"name\" : \"abc\" ," + + " \"allow_rules\" : {}" + + "}"; + try { + AuthorizationPolicyTranslator.translate(policy); + fail("exception expected"); + } catch (ClassCastException cce) { + assertThat(cce).hasMessageThat().isEqualTo( + "value '{}' for key 'allow_rules' in '{name=abc, allow_rules={}}' is not List"); + } catch (Exception e) { + throw new AssertionError("the test failed ", e); + } + } + + @Test + public void parseSourceSuccess() throws Exception { + String policy = "{" + + " \"name\" : \"authz\" ," + + " \"deny_rules\": [" + + " {" + + " \"name\": \"deny_users\"," + + " \"source\": {" + + " \"principals\": [" + + " \"spiffe://foo.com\"," + + " \"spiffe://bar*\"," + + " \"*baz\"," + + " \"spiffe://*.com\"" + + " ]" + + " }" + + " }" + + " ]," + + " \"allow_rules\": [" + + " {" + + " \"name\": \"allow_any\"," + + " \"source\": {" + + " \"principals\": [" + + " \"*\"" + + " ]" + + " }" + + " }" + + " ]" + + "}"; + List rbacs = AuthorizationPolicyTranslator.translate(policy); + assertEquals(2, rbacs.size()); + RBAC expected_deny_rbac = + RBAC.newBuilder() + .setAction(Action.DENY) + .putPolicies("authz_deny_users", + Policy.newBuilder() + .addPrincipals(Principal.newBuilder() + .setOrIds( + Principal.Set.newBuilder() + .addIds(Principal.newBuilder() + .setAuthenticated(Authenticated.newBuilder() + .setPrincipalName(StringMatcher.newBuilder() + .setExact("spiffe://foo.com").build()).build()).build()) + .addIds(Principal.newBuilder() + .setAuthenticated(Authenticated.newBuilder() + .setPrincipalName(StringMatcher.newBuilder() + .setPrefix("spiffe://bar").build()).build()).build()) + .addIds(Principal.newBuilder() + .setAuthenticated(Authenticated.newBuilder() + .setPrincipalName(StringMatcher.newBuilder() + .setSuffix("baz").build()).build()).build()) + .addIds(Principal.newBuilder() + .setAuthenticated(Authenticated.newBuilder() + .setPrincipalName(StringMatcher.newBuilder() + .setExact("spiffe://*.com").build()).build()).build()) + .build()).build()) + .addPermissions(Permission.newBuilder().setAny(true)) + .build()).build(); + RBAC expected_allow_rbac = + RBAC.newBuilder() + .setAction(Action.ALLOW) + .putPolicies("authz_allow_any", + Policy.newBuilder() + .addPrincipals(Principal.newBuilder() + .setOrIds( + Principal.Set.newBuilder() + .addIds(Principal.newBuilder() + .setAuthenticated(Authenticated.newBuilder() + .setPrincipalName(StringMatcher.newBuilder() + .setSafeRegex(RegexMatcher.newBuilder() + .setRegex(".+").build()).build()).build()) + .build()) + .build()).build()) + .addPermissions(Permission.newBuilder().setAny(true)) + .build()).build(); + assertEquals(expected_deny_rbac, rbacs.get(0)); + assertEquals(expected_allow_rbac, rbacs.get(1)); + } + + @Test + public void unsupportedPseudoHeaders() throws Exception { + String policy = "{" + + " \"name\" : \"authz\" ," + + " \"allow_rules\": [" + + " {" + + " \"name\": \"allow_access\"," + + " \"request\": {" + + " \"headers\": [" + + " {" + + " \"key\": \":method\"," + + " \"values\": [" + + " \"foo\"" + + " ]" + + " }" + + " ]" + + " }" + + " }" + + " ]" + + "}"; + try { + AuthorizationPolicyTranslator.translate(policy); + fail("exception expected"); + } catch (IllegalArgumentException iae) { + assertThat(iae).hasMessageThat().isEqualTo("Unsupported \"key\" :method"); + } catch (Exception e) { + throw new AssertionError("the test failed ", e); + } + } + + @Test + public void unsupportedGrpcPrefixHeaders() throws Exception { + String policy = "{" + + " \"name\" : \"authz\" ," + + " \"allow_rules\": [" + + " {" + + " \"name\": \"allow_access\"," + + " \"request\": {" + + " \"headers\": [" + + " {" + + " \"key\": \"grpc-xxx\"," + + " \"values\": [" + + " \"foo\"" + + " ]" + + " }" + + " ]" + + " }" + + " }" + + " ]" + + "}"; + try { + AuthorizationPolicyTranslator.translate(policy); + fail("exception expected"); + } catch (IllegalArgumentException iae) { + assertThat(iae).hasMessageThat().isEqualTo("Unsupported \"key\" grpc-xxx"); + } catch (Exception e) { + throw new AssertionError("the test failed ", e); + } + } + + @Test + public void unsupportedHostHeaders() throws Exception { + String policy = "{" + + " \"name\" : \"authz\" ," + + " \"allow_rules\": [" + + " {" + + " \"name\": \"allow_access\"," + + " \"request\": {" + + " \"headers\": [" + + " {" + + " \"key\": \"Host\"," + + " \"values\": [" + + " \"foo\"" + + " ]" + + " }" + + " ]" + + " }" + + " }" + + " ]" + + "}"; + try { + AuthorizationPolicyTranslator.translate(policy); + fail("exception expected"); + } catch (IllegalArgumentException iae) { + assertThat(iae).hasMessageThat().isEqualTo("Unsupported \"key\" Host"); + } catch (Exception e) { + throw new AssertionError("the test failed ", e); + } + } + + @Test + public void missingHeaderKey() throws Exception { + String policy = "{" + + " \"name\" : \"authz\" ," + + " \"allow_rules\": [" + + " {" + + " \"name\": \"allow_dev\"," + + " \"request\": {" + + " \"headers\": [" + + " {}" + + " ]" + + " }" + + " }" + + " ]" + + "}"; + try { + AuthorizationPolicyTranslator.translate(policy); + fail("exception expected"); + } catch (IllegalArgumentException iae) { + assertThat(iae).hasMessageThat().isEqualTo("\"key\" is absent or empty"); + } catch (Exception e) { + throw new AssertionError("the test failed ", e); + } + } + + @Test + public void missingHeaderValues() throws Exception { + String policy = "{" + + " \"name\" : \"authz\" ," + + " \"allow_rules\": [" + + " {" + + " \"name\": \"allow_dev\"," + + " \"request\": {" + + " \"headers\": [" + + " {" + + " \"key\": \"dev-path\"" + + " }" + + " ]" + + " }" + + " }" + + " ]" + + "}"; + try { + AuthorizationPolicyTranslator.translate(policy); + fail("exception expected"); + } catch (IllegalArgumentException iae) { + assertThat(iae).hasMessageThat().isEqualTo("\"values\" is absent or empty"); + } catch (Exception e) { + throw new AssertionError("the test failed ", e); + } + } + + @Test + public void emptyHeaderValues() throws Exception { + String policy = "{" + + " \"name\" : \"authz\" ," + + " \"allow_rules\": [" + + " {" + + " \"name\": \"allow_dev\"," + + " \"request\": {" + + " \"headers\": [" + + " {" + + " \"key\": \"dev-path\"," + + " \"values\": []" + + " }" + + " ]" + + " }" + + " }" + + " ]" + + "}"; + try { + AuthorizationPolicyTranslator.translate(policy); + fail("exception expected"); + } catch (IllegalArgumentException iae) { + assertThat(iae).hasMessageThat().isEqualTo("\"values\" is absent or empty"); + } catch (Exception e) { + throw new AssertionError("the test failed ", e); + } + } + + @Test + public void parseRequestSuccess() throws Exception { + String policy = "{" + + " \"name\" : \"authz\" ," + + " \"deny_rules\": [" + + " {" + + " \"name\": \"deny_access\"," + + " \"request\": {" + + " \"paths\": [" + + " \"/pkg.service/foo\"," + + " \"/pkg.service/bar*\"" + + " ]," + + " \"headers\": [" + + " {" + + " \"key\": \"dev-path\"," + + " \"values\": [\"/dev/path/*\"]" + + " }" + + " ]" + + " }" + + " }" + + " ]," + + " \"allow_rules\": [" + + " {" + + " \"name\": \"allow_access1\"," + + " \"request\": {" + + " \"headers\": [" + + " {" + + " \"key\": \"key-1\"," + + " \"values\": [" + + " \"foo\"," + + " \"*bar\"" + + " ]" + + " }," + + " {" + + " \"key\": \"key-2\"," + + " \"values\": [" + + " \"*\"" + + " ]" + + " }" + + " ]" + + " }" + + " }," + + " {" + + " \"name\": \"allow_access2\"," + + " \"request\": {" + + " \"paths\": [" + + " \"*baz\"" + + " ]" + + " }" + + " }" + + " ]" + + "}"; + List rbacs = AuthorizationPolicyTranslator.translate(policy); + assertEquals(2, rbacs.size()); + RBAC expected_deny_rbac = + RBAC.newBuilder() + .setAction(Action.DENY) + .putPolicies("authz_deny_access", + Policy.newBuilder() + .addPermissions(Permission.newBuilder() + .setAndRules(Permission.Set.newBuilder() + .addRules(Permission.newBuilder() + .setOrRules(Permission.Set.newBuilder() + .addRules(Permission.newBuilder() + .setUrlPath(PathMatcher.newBuilder() + .setPath(StringMatcher.newBuilder() + .setExact("/pkg.service/foo").build()).build()).build()) + .addRules(Permission.newBuilder() + .setUrlPath(PathMatcher.newBuilder() + .setPath(StringMatcher.newBuilder() + .setPrefix("/pkg.service/bar").build()).build()).build()) + .build()).build()) + .addRules(Permission.newBuilder() + .setAndRules(Permission.Set.newBuilder() + .addRules(Permission.newBuilder() + .setOrRules(Permission.Set.newBuilder() + .addRules(Permission.newBuilder() + .setHeader(HeaderMatcher.newBuilder() + .setName("dev-path") + .setStringMatch(StringMatcher.newBuilder() + .setPrefix("/dev/path/").build()) + .build()) + .build()) + .build()).build()) + .build()).build()) + .build())) + .addPrincipals(Principal.newBuilder().setAny(true)) + .build()).build(); + RBAC expected_allow_rbac = + RBAC.newBuilder() + .setAction(Action.ALLOW) + .putPolicies("authz_allow_access1", + Policy.newBuilder() + .addPermissions(Permission.newBuilder() + .setAndRules(Permission.Set.newBuilder() + .addRules(Permission.newBuilder() + .setAndRules(Permission.Set.newBuilder() + .addRules(Permission.newBuilder() + .setOrRules(Permission.Set.newBuilder() + .addRules(Permission.newBuilder() + .setHeader(HeaderMatcher.newBuilder() + .setName("key-1") + .setStringMatch(StringMatcher.newBuilder() + .setExact("foo").build()) + .build()) + .build()) + .addRules(Permission.newBuilder() + .setHeader(HeaderMatcher.newBuilder() + .setName("key-1") + .setStringMatch(StringMatcher.newBuilder() + .setSuffix("bar").build()) + .build()) + .build()) + .build()).build()) + .addRules(Permission.newBuilder() + .setOrRules(Permission.Set.newBuilder() + .addRules(Permission.newBuilder() + .setHeader(HeaderMatcher.newBuilder() + .setName("key-2") + .setStringMatch(StringMatcher.newBuilder() + .setSafeRegex(RegexMatcher.newBuilder() + .setRegex(".+").build()).build()) + .build()) + .build()) + .build()).build()).build()).build()).build())) + .addPrincipals(Principal.newBuilder().setAny(true)) + .build()) + .putPolicies("authz_allow_access2", + Policy.newBuilder() + .addPermissions(Permission.newBuilder() + .setAndRules(Permission.Set.newBuilder() + .addRules(Permission.newBuilder() + .setOrRules(Permission.Set.newBuilder() + .addRules(Permission.newBuilder() + .setUrlPath(PathMatcher.newBuilder() + .setPath(StringMatcher.newBuilder() + .setSuffix("baz").build()).build()).build()) + .build()).build()) + .build())) + .addPrincipals(Principal.newBuilder().setAny(true)) + .build()) + .build(); + assertEquals(expected_deny_rbac, rbacs.get(0)); + assertEquals(expected_allow_rbac, rbacs.get(1)); + } +} diff --git a/settings.gradle b/settings.gradle index 3da79fce13c..cd50337d5c2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -49,6 +49,7 @@ include ":grpc-services" include ":grpc-xds" include ":grpc-bom" include ":grpc-rls" +include ":grpc-authz" include ":grpc-observability" project(':grpc-api').projectDir = "$rootDir/api" as File @@ -74,6 +75,7 @@ project(':grpc-services').projectDir = "$rootDir/services" as File project(':grpc-xds').projectDir = "$rootDir/xds" as File project(':grpc-bom').projectDir = "$rootDir/bom" as File project(':grpc-rls').projectDir = "$rootDir/rls" as File +project(':grpc-authz').projectDir = "$rootDir/authz" as File project(':grpc-observability').projectDir = "$rootDir/observability" as File if (settings.hasProperty('skipCodegen') && skipCodegen.toBoolean()) { From df6db6fe8bc9e62f0f94c02b11260ed3361e715f Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Mon, 14 Feb 2022 12:17:56 -0800 Subject: [PATCH 53/68] bom: Include binder and exclude authz Binder has had release artifacts since 0d25d8f7. --- bom/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/build.gradle b/bom/build.gradle index 49fcb3cbd90..be2722d4bc0 100644 --- a/bom/build.gradle +++ b/bom/build.gradle @@ -14,7 +14,7 @@ publishing { // Generate bom using subprojects def internalProjects = [ project.name, - 'grpc-binder', + 'grpc-authz', 'grpc-compiler', 'grpc-gae-interop-testing-jdk8', ] From bfb970ce031ec319b3cb6c03a44dfc2d16e87ce5 Mon Sep 17 00:00:00 2001 From: Laurent Goujon Date: Tue, 15 Feb 2022 12:52:24 -0800 Subject: [PATCH 54/68] testing: fix GrpcCleanupRule issue when retrying tests (#8918) Fix an issue in GrpcCleanupRule when tests are retried and the teardown() method is invoked multiple times, causing Stopwatch instance to throw an IllegalStateException. fixes #8917. --- testing/src/main/java/io/grpc/testing/GrpcCleanupRule.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/testing/src/main/java/io/grpc/testing/GrpcCleanupRule.java b/testing/src/main/java/io/grpc/testing/GrpcCleanupRule.java index 329d051ceb3..47a3416d4d3 100644 --- a/testing/src/main/java/io/grpc/testing/GrpcCleanupRule.java +++ b/testing/src/main/java/io/grpc/testing/GrpcCleanupRule.java @@ -149,6 +149,7 @@ public Statement apply(final Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { + firstException = null; try { base.evaluate(); } catch (Throwable t) { @@ -175,6 +176,7 @@ public void evaluate() throws Throwable { * Releases all the registered resources. */ private void teardown() { + stopwatch.reset(); stopwatch.start(); if (firstException == null) { From 3b6a58ee4db4ea7fedeb5231a2a33bde04a396bd Mon Sep 17 00:00:00 2001 From: Eduard Wirch Date: Wed, 16 Feb 2022 20:36:54 +0100 Subject: [PATCH 55/68] doc: fix outdated external link Page is not available on ibm.com anymore. Linking to web archive. --- stub/src/main/java/io/grpc/stub/StreamObserver.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stub/src/main/java/io/grpc/stub/StreamObserver.java b/stub/src/main/java/io/grpc/stub/StreamObserver.java index cf7cc258961..3a5c756e73e 100644 --- a/stub/src/main/java/io/grpc/stub/StreamObserver.java +++ b/stub/src/main/java/io/grpc/stub/StreamObserver.java @@ -26,8 +26,8 @@ * {@code StreamObserver} and passes it to the GRPC library for receiving. * *

Implementations are not required to be thread-safe (but should be - * thread-compatible). - * Separate {@code StreamObserver}s do + * + * thread-compatible). Separate {@code StreamObserver}s do * not need to be synchronized together; incoming and outgoing directions are independent. * Since individual {@code StreamObserver}s are not thread-safe, if multiple threads will be * writing to a {@code StreamObserver} concurrently, the application must synchronize calls. From a8a81e709d8d6c6a05d87932a13117c301dce3ab Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Wed, 16 Feb 2022 15:47:24 -0800 Subject: [PATCH 56/68] Bump Google Auth, Guava, Auto Value The Google Auth version is getting quite old. The new version pulls in newer Guava and Auto Value. Two require Java 8: Google Auth since 1.x, Guava since 31.x. Google Auth only needs Auto Value 1.8.2, but this bumps to the latest, so all three are at their latest versions. --- build.gradle | 6 +++--- context/build.gradle | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index c20d82c1f55..efd9640e0ba 100644 --- a/build.gradle +++ b/build.gradle @@ -56,12 +56,12 @@ subprojects { javaPluginPath = "$rootDir/compiler/build/exe/java_plugin/$protocPluginBaseName$exeSuffix" nettyVersion = '4.1.72.Final' - guavaVersion = '30.1.1-android' - googleauthVersion = '0.22.2' + guavaVersion = '31.0.1-android' + googleauthVersion = '1.4.0' protobufVersion = '3.19.2' protocVersion = protobufVersion opencensusVersion = '0.28.0' - autovalueVersion = '1.7.4' + autovalueVersion = '1.9' configureProtoCompilation = { String generatedSourcePath = "${projectDir}/src/generated" diff --git a/context/build.gradle b/context/build.gradle index 1e69296c825..5815374aeb3 100644 --- a/context/build.gradle +++ b/context/build.gradle @@ -14,7 +14,10 @@ targetCompatibility = 1.7 dependencies { testImplementation libraries.jsr305 - testImplementation (libraries.guava_testlib) { + // Explicitly choose the guava version to stay Java 7-compatible. The rest of gRPC can move + // forward to Java 8-requiring versions. This is also only used for testing, so is unlikely to + // cause problems. + testImplementation ('com.google.guava:guava-testlib:30.1.1-android') { exclude group: 'junit', module: 'junit' } signature "org.codehaus.mojo.signature:java17:1.0@signature" From 2ef38150da12ac3faab3cbe8218d9bf9febf9f41 Mon Sep 17 00:00:00 2001 From: DNVindhya Date: Fri, 18 Feb 2022 12:46:21 -0600 Subject: [PATCH 57/68] observability: implement observability.logging.CloudLoggingHandler() (#8922) --- observability/build.gradle | 20 +- .../logging/CloudLoggingHandler.java | 181 ++++++++++++++++++ .../logging/LogRecordExtension.java | 16 +- 3 files changed, 206 insertions(+), 11 deletions(-) create mode 100644 observability/src/main/java/io/grpc/observability/logging/CloudLoggingHandler.java diff --git a/observability/build.gradle b/observability/build.gradle index 1a31962dffc..4d04a09912a 100644 --- a/observability/build.gradle +++ b/observability/build.gradle @@ -8,11 +8,22 @@ plugins { description = "gRPC: Observability" dependencies { - api project(':grpc-api') + def cloudLoggingVersion = '3.6.1' - implementation libraries.guava, - project(':grpc-protobuf'), - project(':grpc-stub') + api project(':grpc-api'), + project(':grpc-alts') + + implementation project(':grpc-protobuf'), + project(':grpc-stub'), + ('com.google.guava:guava:31.0.1-jre'), + ('com.google.errorprone:error_prone_annotations:2.11.0'), + ('com.google.auth:google-auth-library-credentials:1.4.0'), + ('org.checkerframework:checker-qual:3.20.0'), + ('com.google.auto.value:auto-value-annotations:1.9'), + ('com.google.http-client:google-http-client:1.41.0'), + ('com.google.http-client:google-http-client-gson:1.41.0'), + ('com.google.api.grpc:proto-google-common-protos:2.7.1'), + ("com.google.cloud:google-cloud-logging:${cloudLoggingVersion}") testImplementation project(':grpc-testing'), project(':grpc-testing-proto'), @@ -25,4 +36,3 @@ dependencies { } configureProtoCompilation() - diff --git a/observability/src/main/java/io/grpc/observability/logging/CloudLoggingHandler.java b/observability/src/main/java/io/grpc/observability/logging/CloudLoggingHandler.java new file mode 100644 index 00000000000..f325b9d8383 --- /dev/null +++ b/observability/src/main/java/io/grpc/observability/logging/CloudLoggingHandler.java @@ -0,0 +1,181 @@ +/* + * 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.observability.logging; + +import com.google.cloud.MonitoredResource; +import com.google.cloud.logging.LogEntry; +import com.google.cloud.logging.Logging; +import com.google.cloud.logging.LoggingOptions; +import com.google.cloud.logging.Payload.JsonPayload; +import com.google.cloud.logging.Severity; +import com.google.common.base.Strings; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; +import io.grpc.Internal; +import io.grpc.internal.JsonParser; +import io.grpc.observabilitylog.v1.GrpcLogRecord; +import java.io.IOException; +import java.util.Collections; +import java.util.Map; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +/** + * Custom logging handler that outputs logs generated using {@link java.util.logging.Logger} to + * Cloud Logging. + */ +// TODO(vindhyan): replace custom JUL handler with internal sink implementation to eliminate +// JUL dependency +@Internal +public class CloudLoggingHandler extends Handler { + + private static final String DEFAULT_LOG_NAME = "grpc-observability"; + private static final Level DEFAULT_LOG_LEVEL = Level.ALL; + + private final LoggingOptions loggingOptions; + private final Logging loggingClient; + private final Level baseLevel; + private final String cloudLogName; + + /** + * Creates a custom logging handler that publishes message to Cloud logging. Default log level is + * set to Level.FINEST if level is not passed. + */ + public CloudLoggingHandler() { + this(DEFAULT_LOG_LEVEL, null, null); + } + + /** + * Creates a custom logging handler that publishes message to Cloud logging. + * + * @param level set the level for which message levels will be logged by the custom logger + */ + public CloudLoggingHandler(Level level) { + this(level, null, null); + } + + /** + * Creates a custom logging handler that publishes message to Cloud logging. + * + * @param level set the level for which message levels will be logged by the custom logger + * @param logName the name of the log to which log entries are written + */ + public CloudLoggingHandler(Level level, String logName) { + this(level, logName, null); + } + + /** + * Creates a custom logging handler that publishes message to Cloud logging. + * + * @param level set the level for which message levels will be logged by the custom logger + * @param logName the name of the log to which log entries are written + * @param destinationProjectId the value of cloud project id to which logs are sent to by the + * custom logger + */ + public CloudLoggingHandler(Level level, String logName, String destinationProjectId) { + baseLevel = + (level != null) ? (level.equals(DEFAULT_LOG_LEVEL) ? Level.FINEST : level) : Level.FINEST; + setLevel(baseLevel); + cloudLogName = logName != null ? logName : DEFAULT_LOG_NAME; + + // TODO(dnvindhya) read the value from config instead of taking it as an argument + if (Strings.isNullOrEmpty(destinationProjectId)) { + loggingOptions = LoggingOptions.getDefaultInstance(); + } else { + loggingOptions = LoggingOptions.newBuilder().setProjectId(destinationProjectId).build(); + } + loggingClient = loggingOptions.getService(); + } + + @Override + public void publish(LogRecord record) { + if (!(record instanceof LogRecordExtension)) { + throw new IllegalArgumentException("Expected record of type LogRecordExtension"); + } + Level logLevel = record.getLevel(); + GrpcLogRecord protoRecord = ((LogRecordExtension) record).getGrpcLogRecord(); + writeLog(protoRecord, logLevel); + } + + private void writeLog(GrpcLogRecord logProto, Level logLevel) { + if (loggingClient == null) { + throw new IllegalStateException("Logging client not initialized"); + } + try { + Severity cloudLogLevel = getCloudLoggingLevel(logLevel); + Map mapPayload = protoToMapConverter(logProto); + + // TODO(vindhyan): make sure all (int, long) values are not displayed as double + LogEntry grpcLogEntry = + LogEntry.newBuilder(JsonPayload.of(mapPayload)) + .setSeverity(cloudLogLevel) + .setLogName(cloudLogName) + .setResource(MonitoredResource.newBuilder("global").build()) + .build(); + loggingClient.write(Collections.singleton(grpcLogEntry)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @SuppressWarnings("unchecked") + private Map protoToMapConverter(GrpcLogRecord logProto) + throws InvalidProtocolBufferException, IOException { + JsonFormat.Printer printer = JsonFormat.printer().preservingProtoFieldNames(); + String recordJson = printer.print(logProto); + return (Map) JsonParser.parse(recordJson); + } + + @Override + public void flush() { + if (loggingClient == null) { + throw new IllegalStateException("Logging client not initialized"); + } + loggingClient.flush(); + } + + @Override + public synchronized void close() throws SecurityException { + if (loggingClient == null) { + throw new IllegalStateException("Logging client not initialized"); + } + try { + loggingClient.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private Severity getCloudLoggingLevel(Level recordLevel) { + switch (recordLevel.intValue()) { + case 300: // FINEST + case 400: // FINER + case 500: // FINE + return Severity.DEBUG; + case 700: // CONFIG + case 800: // INFO + return Severity.INFO; + case 900: // WARNING + return Severity.WARNING; + case 1000: // SEVERE + return Severity.ERROR; + default: + return Severity.DEFAULT; + } + } +} diff --git a/observability/src/main/java/io/grpc/observability/logging/LogRecordExtension.java b/observability/src/main/java/io/grpc/observability/logging/LogRecordExtension.java index f2ad6120316..a1220659302 100644 --- a/observability/src/main/java/io/grpc/observability/logging/LogRecordExtension.java +++ b/observability/src/main/java/io/grpc/observability/logging/LogRecordExtension.java @@ -18,19 +18,23 @@ import io.grpc.Internal; import io.grpc.observabilitylog.v1.GrpcLogRecord; +import java.util.logging.Level; import java.util.logging.LogRecord; -/** An extension of java.util.logging.LogRecord which includes gRPC observability logging - * specific fields. */ +/** + * An extension of java.util.logging.LogRecord which includes gRPC observability logging specific + * fields. + */ @Internal public final class LogRecordExtension extends LogRecord { - public final GrpcLogRecord grpcLogRecord; - public LogRecordExtension(GrpcLogRecord record) { - super(null, null); + private final GrpcLogRecord grpcLogRecord; + + public LogRecordExtension(Level recordLevel, GrpcLogRecord record) { + super(recordLevel, null); this.grpcLogRecord = record; } - + public GrpcLogRecord getGrpcLogRecord() { return grpcLogRecord; } From 80a2ca686f15634e1bb1a299ab72f108e6b71a38 Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Fri, 18 Feb 2022 16:30:42 -0800 Subject: [PATCH 58/68] Update README etc to reference 1.44.1 (#8932) --- README.md | 30 ++++++++++++------------ cronet/README.md | 2 +- documentation/android-channel-builder.md | 4 ++-- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index f80bcc3f7a8..d19c0868269 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.44.0/examples) and the -[Android example](https://github.com/grpc/grpc-java/tree/v1.44.0/examples/android) +The [examples](https://github.com/grpc/grpc-java/tree/v1.44.1/examples) and the +[Android example](https://github.com/grpc/grpc-java/tree/v1.44.1/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.44.0 + 1.44.1 runtime io.grpc grpc-protobuf - 1.44.0 + 1.44.1 io.grpc grpc-stub - 1.44.0 + 1.44.1 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.44.0' -implementation 'io.grpc:grpc-protobuf:1.44.0' -implementation 'io.grpc:grpc-stub:1.44.0' +runtimeOnly 'io.grpc:grpc-netty-shaded:1.44.1' +implementation 'io.grpc:grpc-protobuf:1.44.1' +implementation 'io.grpc:grpc-stub:1.44.1' compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+ ``` For Android client, use `grpc-okhttp` instead of `grpc-netty-shaded` and `grpc-protobuf-lite` instead of `grpc-protobuf`: ```gradle -implementation 'io.grpc:grpc-okhttp:1.44.0' -implementation 'io.grpc:grpc-protobuf-lite:1.44.0' -implementation 'io.grpc:grpc-stub:1.44.0' +implementation 'io.grpc:grpc-okhttp:1.44.1' +implementation 'io.grpc:grpc-protobuf-lite:1.44.1' +implementation 'io.grpc:grpc-stub:1.44.1' 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.44.0 +https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.44.1 Development snapshots are available in [Sonatypes's snapshot repository](https://oss.sonatype.org/content/repositories/snapshots/). @@ -114,7 +114,7 @@ For protobuf-based codegen integrated with the Maven build system, you can use com.google.protobuf:protoc:3.19.2:exe:${os.detected.classifier} grpc-java - io.grpc:protoc-gen-grpc-java:1.44.0:exe:${os.detected.classifier} + io.grpc:protoc-gen-grpc-java:1.44.1:exe:${os.detected.classifier} @@ -144,7 +144,7 @@ protobuf { } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.44.0' + artifact = 'io.grpc:protoc-gen-grpc-java:1.44.1' } } generateProtoTasks { @@ -177,7 +177,7 @@ protobuf { } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.44.0' + artifact = 'io.grpc:protoc-gen-grpc-java:1.44.1' } } generateProtoTasks { diff --git a/cronet/README.md b/cronet/README.md index e02cab401b5..fb65ca23e5a 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.44.0' +implementation 'io.grpc:grpc-cronet:1.44.1' 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 41d3b650151..fcc42603cb2 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.44.0' -implementation 'io.grpc:grpc-okhttp:1.44.0' +implementation 'io.grpc:grpc-android:1.44.1' +implementation 'io.grpc:grpc-okhttp:1.44.1' ``` You also need permission to access the device's network state in your From 6559ef88a197e0195d1c4cc58daa5e963981385d Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Fri, 18 Feb 2022 16:59:33 -0800 Subject: [PATCH 59/68] Revert "stub: Have ClientCalls.ThreadlessExecutor reject Runnables after end of RPC (#8847)" (#8933) This reverts commit bb3365731fa3cd6c082e6346b81d0ea14646ffc1. --- .../main/java/io/grpc/stub/ClientCalls.java | 42 ++++--------------- 1 file changed, 7 insertions(+), 35 deletions(-) diff --git a/stub/src/main/java/io/grpc/stub/ClientCalls.java b/stub/src/main/java/io/grpc/stub/ClientCalls.java index 84417ee7ebf..04ed83f083a 100644 --- a/stub/src/main/java/io/grpc/stub/ClientCalls.java +++ b/stub/src/main/java/io/grpc/stub/ClientCalls.java @@ -39,7 +39,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.Future; -import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.locks.LockSupport; import java.util.logging.Level; import java.util.logging.Logger; @@ -162,7 +161,6 @@ public static RespT blockingUnaryCall( // Something very bad happened. All bets are off; it may be dangerous to wait for onClose(). throw cancelThrow(call, e); } finally { - executor.shutdown(); if (interrupt) { Thread.currentThread().interrupt(); } @@ -628,9 +626,6 @@ private Object waitForNext() { // Now wait for onClose() to be called, so interceptors can clean up } } - if (next == this || next instanceof StatusRuntimeException) { - threadless.shutdown(); - } return next; } } finally { @@ -717,10 +712,7 @@ private static final class ThreadlessExecutor extends ConcurrentLinkedQueue Date: Tue, 22 Feb 2022 13:54:48 -0800 Subject: [PATCH 60/68] observability: do not publish our artifact to maven just yet (#8938) --- observability/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/observability/build.gradle b/observability/build.gradle index 4d04a09912a..137e5f6978a 100644 --- a/observability/build.gradle +++ b/observability/build.gradle @@ -36,3 +36,5 @@ dependencies { } configureProtoCompilation() + +[publishMavenPublicationToMavenRepository]*.onlyIf { false } From 1b1b284c92948b3a8edd005c0e4d57533335a99a Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Wed, 23 Feb 2022 13:55:02 -0800 Subject: [PATCH 61/68] xds: Squelch ADS reconnection error logs Workaround for #8886, as we wait on a real fix. The regular load balancing disconnections are confusing users and will train users to start ignoring gRPC warnings. At present, it is better to have no log than excessively log. --- xds/src/main/java/io/grpc/xds/XdsNameResolver.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java index b15af622256..01014027772 100644 --- a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java +++ b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java @@ -129,6 +129,9 @@ final class XdsNameResolver extends NameResolver { private XdsClient xdsClient; private CallCounterProvider callCounterProvider; private ResolveState resolveState; + // Workaround for https://github.com/grpc/grpc-java/issues/8886 . This should be handled in + // XdsClient instead of here. + private boolean receivedConfig; XdsNameResolver( @Nullable String targetAuthority, String name, ServiceConfigParser serviceConfigParser, @@ -293,6 +296,7 @@ private void updateResolutionResult() { .setServiceConfig(parsedServiceConfig) .build(); listener.onResult(result); + receivedConfig = true; } @VisibleForTesting @@ -715,7 +719,7 @@ public void onError(final Status error) { syncContext.execute(new Runnable() { @Override public void run() { - if (stopped) { + if (stopped || receivedConfig) { return; } listener.onError(error); @@ -865,6 +869,7 @@ private void cleanUpRoutes() { } routingConfig = RoutingConfig.empty; listener.onResult(emptyResult); + receivedConfig = true; } private void cleanUpRouteDiscoveryState() { @@ -912,7 +917,7 @@ public void onError(final Status error) { syncContext.execute(new Runnable() { @Override public void run() { - if (RouteDiscoveryState.this != routeDiscoveryState) { + if (RouteDiscoveryState.this != routeDiscoveryState || receivedConfig) { return; } listener.onError(error); From 421be66003367a6fbaa55b840e40731d4a7ae527 Mon Sep 17 00:00:00 2001 From: "Penn (Dapeng) Zhang" Date: Wed, 16 Feb 2022 21:22:34 -0800 Subject: [PATCH 62/68] xds: improve PriorityLoadBalancerTest --- .../io/grpc/xds/PriorityLoadBalancerTest.java | 58 +++++-------------- 1 file changed, 14 insertions(+), 44 deletions(-) diff --git a/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java index 420e92cf9cd..3b10060dfee 100644 --- a/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java @@ -425,16 +425,10 @@ public void typicalPriorityFailOverFlowWithIdleUpdate() { Helper helper0 = Iterables.getOnlyElement(fooHelpers); // p0 gets IDLE. - final Subchannel subchannel0 = mock(Subchannel.class); helper0.updateBalancingState( IDLE, - new SubchannelPicker() { - @Override - public PickResult pickSubchannel(PickSubchannelArgs args) { - return PickResult.withSubchannel(subchannel0); - } - }); - assertCurrentPickerPicksIdleSubchannel(subchannel0); + BUFFER_PICKER); + assertCurrentPickerIsBufferPicker(); // p0 fails over to p1 immediately. helper0.updateBalancingState(TRANSIENT_FAILURE, new ErrorPicker(Status.ABORTED)); @@ -452,32 +446,20 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { Helper helper2 = Iterables.getLast(fooHelpers); // p2 gets IDLE - final Subchannel subchannel1 = mock(Subchannel.class); helper2.updateBalancingState( IDLE, - new SubchannelPicker() { - @Override - public PickResult pickSubchannel(PickSubchannelArgs args) { - return PickResult.withSubchannel(subchannel1); - } - }); - assertCurrentPickerPicksIdleSubchannel(subchannel1); + BUFFER_PICKER); + assertCurrentPickerIsBufferPicker(); // p0 gets back to IDLE - final Subchannel subchannel2 = mock(Subchannel.class); helper0.updateBalancingState( IDLE, - new SubchannelPicker() { - @Override - public PickResult pickSubchannel(PickSubchannelArgs args) { - return PickResult.withSubchannel(subchannel2); - } - }); - assertCurrentPickerPicksIdleSubchannel(subchannel2); + BUFFER_PICKER); + assertCurrentPickerIsBufferPicker(); // p2 fails but does not affect overall picker helper2.updateBalancingState(TRANSIENT_FAILURE, new ErrorPicker(Status.UNAVAILABLE)); - assertCurrentPickerPicksIdleSubchannel(subchannel2); + assertCurrentPickerIsBufferPicker(); // p0 fails over to p3 immediately since p1 already timeout and p2 already in TRANSIENT_FAILURE. helper0.updateBalancingState(TRANSIENT_FAILURE, new ErrorPicker(Status.UNAVAILABLE)); @@ -497,32 +479,20 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { assertCurrentPickerReturnsError(Status.Code.DATA_LOSS, "foo"); // p2 gets back to IDLE - final Subchannel subchannel3 = mock(Subchannel.class); helper2.updateBalancingState( IDLE, - new SubchannelPicker() { - @Override - public PickResult pickSubchannel(PickSubchannelArgs args) { - return PickResult.withSubchannel(subchannel3); - } - }); - assertCurrentPickerPicksIdleSubchannel(subchannel3); + BUFFER_PICKER); + assertCurrentPickerIsBufferPicker(); // p0 gets back to IDLE - final Subchannel subchannel4 = mock(Subchannel.class); helper0.updateBalancingState( IDLE, - new SubchannelPicker() { - @Override - public PickResult pickSubchannel(PickSubchannelArgs args) { - return PickResult.withSubchannel(subchannel4); - } - }); - assertCurrentPickerPicksIdleSubchannel(subchannel4); + BUFFER_PICKER); + assertCurrentPickerIsBufferPicker(); // p0 fails over to p2 and picker is updated to p2's existing picker. helper0.updateBalancingState(TRANSIENT_FAILURE, new ErrorPicker(Status.UNAVAILABLE)); - assertCurrentPickerPicksIdleSubchannel(subchannel3); + assertCurrentPickerIsBufferPicker(); // Deactivate child balancer get deleted. fakeClock.forwardTime(15, TimeUnit.MINUTES); @@ -607,9 +577,9 @@ private void assertCurrentPickerPicksSubchannel(Subchannel expectedSubchannelToP assertThat(pickResult.getSubchannel()).isEqualTo(expectedSubchannelToPick); } - private void assertCurrentPickerPicksIdleSubchannel(Subchannel expectedSubchannelToPick) { + private void assertCurrentPickerIsBufferPicker() { assertLatestConnectivityState(IDLE); PickResult pickResult = pickerCaptor.getValue().pickSubchannel(mock(PickSubchannelArgs.class)); - assertThat(pickResult.getSubchannel()).isEqualTo(expectedSubchannelToPick); + assertThat(pickResult).isEqualTo(PickResult.withNoResult()); } } From 75c7c6b32d880ca4b657a4be12b255af4593f704 Mon Sep 17 00:00:00 2001 From: "Penn (Dapeng) Zhang" Date: Wed, 16 Feb 2022 21:28:11 -0800 Subject: [PATCH 63/68] xds: Do not failoverpriority when IDLE->CONNECTING --- .../io/grpc/xds/PriorityLoadBalancer.java | 25 +++-- .../io/grpc/xds/PriorityLoadBalancerTest.java | 93 +++++++++++++++++++ 2 files changed, 111 insertions(+), 7 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java b/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java index f29239331b2..0678ffc34b6 100644 --- a/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java @@ -41,6 +41,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -66,6 +67,7 @@ final class PriorityLoadBalancer extends LoadBalancer { private List priorityNames; // Config for each priority. private Map priorityConfigs; + @Nullable private String currentPriority; private ConnectivityState currentConnectivityState; private SubchannelPicker currentPicker; @@ -113,7 +115,7 @@ public void handleNameResolutionError(Status error) { } } if (gotoTransientFailure) { - updateOverallState(TRANSIENT_FAILURE, new ErrorPicker(error)); + updateOverallState(null, TRANSIENT_FAILURE, new ErrorPicker(error)); } } @@ -134,14 +136,14 @@ private void tryNextPriority(boolean reportConnecting) { new ChildLbState(priority, priorityConfigs.get(priority).ignoreReresolution); children.put(priority, child); child.updateResolvedAddresses(); - updateOverallState(CONNECTING, BUFFER_PICKER); + updateOverallState(priority, CONNECTING, BUFFER_PICKER); return; // Give priority i time to connect. } ChildLbState child = children.get(priority); child.reactivate(); if (child.connectivityState.equals(READY) || child.connectivityState.equals(IDLE)) { logger.log(XdsLogLevel.DEBUG, "Shifted to priority {0}", priority); - updateOverallState(child.connectivityState, child.picker); + updateOverallState(priority, child.connectivityState, child.picker); for (int j = i + 1; j < priorityNames.size(); j++) { String p = priorityNames.get(j); if (children.containsKey(p)) { @@ -152,20 +154,28 @@ private void tryNextPriority(boolean reportConnecting) { } if (child.failOverTimer != null && child.failOverTimer.isPending()) { if (reportConnecting) { - updateOverallState(CONNECTING, BUFFER_PICKER); + updateOverallState(priority, CONNECTING, BUFFER_PICKER); } return; // Give priority i time to connect. } + if (priority.equals(currentPriority) && child.connectivityState != TRANSIENT_FAILURE) { + // If the current priority is not changed into TRANSIENT_FAILURE, keep using it. + updateOverallState(priority, child.connectivityState, child.picker); + return; + } } // TODO(zdapeng): Include error details of each priority. logger.log(XdsLogLevel.DEBUG, "All priority failed"); String lastPriority = priorityNames.get(priorityNames.size() - 1); SubchannelPicker errorPicker = children.get(lastPriority).picker; - updateOverallState(TRANSIENT_FAILURE, errorPicker); + updateOverallState(lastPriority, TRANSIENT_FAILURE, errorPicker); } - private void updateOverallState(ConnectivityState state, SubchannelPicker picker) { - if (!state.equals(currentConnectivityState) || !picker.equals(currentPicker)) { + private void updateOverallState( + @Nullable String priority, ConnectivityState state, SubchannelPicker picker) { + if (!Objects.equals(priority, currentPriority) || !state.equals(currentConnectivityState) + || !picker.equals(currentPicker)) { + currentPriority = priority; currentConnectivityState = state; currentPicker = picker; helper.updateBalancingState(state, picker); @@ -201,6 +211,7 @@ public void run() { picker = new ErrorPicker( 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); } } diff --git a/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java index 3b10060dfee..9a23770512c 100644 --- a/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java @@ -399,6 +399,99 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { verify(balancer3).shutdown(); } + @Test + public void idleToConnectingDoesNotTriggerFailOver() { + PriorityChildConfig priorityChildConfig0 = + new PriorityChildConfig(new PolicySelection(fooLbProvider, new Object()), true); + PriorityChildConfig priorityChildConfig1 = + new PriorityChildConfig(new PolicySelection(fooLbProvider, new Object()), true); + PriorityLbConfig priorityLbConfig = + new PriorityLbConfig( + ImmutableMap.of("p0", priorityChildConfig0, "p1", priorityChildConfig1), + ImmutableList.of("p0", "p1")); + priorityLb.handleResolvedAddresses( + ResolvedAddresses.newBuilder() + .setAddresses(ImmutableList.of()) + .setLoadBalancingPolicyConfig(priorityLbConfig) + .build()); + assertThat(fooBalancers).hasSize(1); + assertThat(fooHelpers).hasSize(1); + Helper helper0 = Iterables.getOnlyElement(fooHelpers); + + // p0 gets IDLE. + helper0.updateBalancingState( + IDLE, + BUFFER_PICKER); + assertCurrentPickerIsBufferPicker(); + + // p0 goes to CONNECTING + helper0.updateBalancingState( + IDLE, + BUFFER_PICKER); + assertCurrentPickerIsBufferPicker(); + + // no failover happened + assertThat(fooBalancers).hasSize(1); + assertThat(fooHelpers).hasSize(1); + } + + @Test + public void readyToConnectDoesNotFailOverButUpdatesPicker() { + PriorityChildConfig priorityChildConfig0 = + new PriorityChildConfig(new PolicySelection(fooLbProvider, new Object()), true); + PriorityChildConfig priorityChildConfig1 = + new PriorityChildConfig(new PolicySelection(fooLbProvider, new Object()), true); + PriorityLbConfig priorityLbConfig = + new PriorityLbConfig( + ImmutableMap.of("p0", priorityChildConfig0, "p1", priorityChildConfig1), + ImmutableList.of("p0", "p1")); + priorityLb.handleResolvedAddresses( + ResolvedAddresses.newBuilder() + .setAddresses(ImmutableList.of()) + .setLoadBalancingPolicyConfig(priorityLbConfig) + .build()); + assertThat(fooBalancers).hasSize(1); + assertThat(fooHelpers).hasSize(1); + Helper helper0 = Iterables.getOnlyElement(fooHelpers); + + // p0 gets READY. + final Subchannel subchannel0 = mock(Subchannel.class); + helper0.updateBalancingState( + READY, + new SubchannelPicker() { + @Override + public PickResult pickSubchannel(PickSubchannelArgs args) { + return PickResult.withSubchannel(subchannel0); + } + }); + assertCurrentPickerPicksSubchannel(subchannel0); + + // p0 goes to CONNECTING + helper0.updateBalancingState( + IDLE, + BUFFER_PICKER); + assertCurrentPickerIsBufferPicker(); + + // no failover happened + assertThat(fooBalancers).hasSize(1); + assertThat(fooHelpers).hasSize(1); + + // resolution update without priority change does not trigger failover + Attributes.Key fooKey = Attributes.Key.create("fooKey"); + priorityLb.handleResolvedAddresses( + ResolvedAddresses.newBuilder() + .setAddresses(ImmutableList.of()) + .setLoadBalancingPolicyConfig(priorityLbConfig) + .setAttributes(Attributes.newBuilder().set(fooKey, "barVal").build()) + .build()); + + assertCurrentPickerIsBufferPicker(); + + // no failover happened + assertThat(fooBalancers).hasSize(1); + assertThat(fooHelpers).hasSize(1); + } + @Test public void typicalPriorityFailOverFlowWithIdleUpdate() { PriorityChildConfig priorityChildConfig0 = From 9a99ca4913caf667e51427f2d62b3b3cfc1164ff Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Thu, 3 Mar 2022 14:40:49 -0800 Subject: [PATCH 64/68] xds: fix XdsNameResolver blindly propagates XdsClient errors (#8953) (#8964) --- .../java/io/grpc/xds/XdsNameResolver.java | 8 ++++-- .../java/io/grpc/xds/XdsNameResolverTest.java | 28 +++++++++++++++---- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java index 01014027772..ed7b849f8be 100644 --- a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java +++ b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java @@ -722,7 +722,9 @@ public void run() { if (stopped || receivedConfig) { return; } - listener.onError(error); + listener.onError(Status.UNAVAILABLE.withCause(error.getCause()).withDescription( + String.format("Unable to load LDS %s. xDS server returned: %s: %s.", + ldsResourceName, error.getCode(), error.getDescription()))); } }); } @@ -920,7 +922,9 @@ public void run() { if (RouteDiscoveryState.this != routeDiscoveryState || receivedConfig) { return; } - listener.onError(error); + listener.onError(Status.UNAVAILABLE.withCause(error.getCause()).withDescription( + String.format("Unable to load RDS %s. xDS server returned: %s: %s.", + resourceName, error.getCode(), error.getDescription()))); } }); } diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java index a17a043af5e..8cc78b7447c 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java @@ -431,7 +431,21 @@ public void resolving_encounterErrorLdsWatcherOnly() { verify(mockListener).onError(errorCaptor.capture()); Status error = errorCaptor.getValue(); assertThat(error.getCode()).isEqualTo(Code.UNAVAILABLE); - assertThat(error.getDescription()).isEqualTo("server unreachable"); + assertThat(error.getDescription()).isEqualTo("Unable to load LDS " + AUTHORITY + + ". xDS server returned: UNAVAILABLE: server unreachable."); + } + + @Test + public void resolving_translateErrorLds() { + resolver.start(mockListener); + FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); + xdsClient.deliverError(Status.NOT_FOUND.withDescription("server unreachable")); + verify(mockListener).onError(errorCaptor.capture()); + Status error = errorCaptor.getValue(); + assertThat(error.getCode()).isEqualTo(Code.UNAVAILABLE); + assertThat(error.getDescription()).isEqualTo("Unable to load LDS " + AUTHORITY + + ". xDS server returned: NOT_FOUND: server unreachable."); + assertThat(error.getCause()).isNull(); } @Test @@ -441,10 +455,14 @@ public void resolving_encounterErrorLdsAndRdsWatchers() { xdsClient.deliverLdsUpdateForRdsName(RDS_RESOURCE_NAME); xdsClient.deliverError(Status.UNAVAILABLE.withDescription("server unreachable")); verify(mockListener, times(2)).onError(errorCaptor.capture()); - for (Status error : errorCaptor.getAllValues()) { - assertThat(error.getCode()).isEqualTo(Code.UNAVAILABLE); - assertThat(error.getDescription()).isEqualTo("server unreachable"); - } + Status error = errorCaptor.getAllValues().get(0); + assertThat(error.getCode()).isEqualTo(Code.UNAVAILABLE); + assertThat(error.getDescription()).isEqualTo("Unable to load LDS " + AUTHORITY + + ". xDS server returned: UNAVAILABLE: server unreachable."); + error = errorCaptor.getAllValues().get(1); + assertThat(error.getCode()).isEqualTo(Code.UNAVAILABLE); + assertThat(error.getDescription()).isEqualTo("Unable to load RDS " + RDS_RESOURCE_NAME + + ". xDS server returned: UNAVAILABLE: server unreachable."); } @Test From d3a5925e7b0ce4671c8451d8c750725035f026a0 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 3 Mar 2022 10:46:42 -0800 Subject: [PATCH 65/68] googleapis: Move GoogleCloudToProdNameResolver from xds GoogleCloudToProdNameResolver has a hard dependency on alts whereas xds only has a weak dependency on alts that can be solved by a ChannelCredentialsRegistry. So split out the code to a separate artifact. --- googleapis/build.gradle | 36 +++++++++++++++++++ .../GoogleCloudToProdNameResolver.java | 21 ++++++----- ...GoogleCloudToProdNameResolverProvider.java | 14 ++++++-- .../services/io.grpc.NameResolverProvider | 1 + ...leCloudToProdNameResolverProviderTest.java | 2 +- .../GoogleCloudToProdNameResolverTest.java | 29 +++++---------- settings.gradle | 2 ++ .../InternalSharedXdsClientPoolProvider.java | 33 +++++++++++++++++ .../services/io.grpc.NameResolverProvider | 1 - 9 files changed, 105 insertions(+), 34 deletions(-) create mode 100644 googleapis/build.gradle rename {xds/src/main/java/io/grpc/xds => googleapis/src/main/java/io/grpc/googleapis}/GoogleCloudToProdNameResolver.java (94%) rename {xds/src/main/java/io/grpc/xds => googleapis/src/main/java/io/grpc/googleapis}/GoogleCloudToProdNameResolverProvider.java (75%) create mode 100644 googleapis/src/main/resources/META-INF/services/io.grpc.NameResolverProvider rename {xds/src/test/java/io/grpc/xds => googleapis/src/test/java/io/grpc/googleapis}/GoogleCloudToProdNameResolverProviderTest.java (98%) rename {xds/src/test/java/io/grpc/xds => googleapis/src/test/java/io/grpc/googleapis}/GoogleCloudToProdNameResolverTest.java (90%) create mode 100644 xds/src/main/java/io/grpc/xds/InternalSharedXdsClientPoolProvider.java diff --git a/googleapis/build.gradle b/googleapis/build.gradle new file mode 100644 index 00000000000..69449e8a02b --- /dev/null +++ b/googleapis/build.gradle @@ -0,0 +1,36 @@ +plugins { + id "java-library" + id "maven-publish" + + id "ru.vyarus.animalsniffer" +} + +description = 'gRPC: googleapis' + +dependencies { + api project(':grpc-api') + implementation project(':grpc-alts'), + project(':grpc-core'), + project(':grpc-xds'), + libraries.guava + testImplementation project(':grpc-core').sourceSets.test.output + + signature "org.codehaus.mojo.signature:java17:1.0@signature" +} + +publishing { + publications { + maven(MavenPublication) { + pom { + withXml { + // Since internal APIs are used, pin the version. + asNode().dependencies.'*'.findAll() { dep -> + dep.artifactId.text() in ['grpc-alts', 'grpc-xds'] + }.each() { core -> + core.version*.value = "[" + core.version.text() + "]" + } + } + } + } + } +} diff --git a/xds/src/main/java/io/grpc/xds/GoogleCloudToProdNameResolver.java b/googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdNameResolver.java similarity index 94% rename from xds/src/main/java/io/grpc/xds/GoogleCloudToProdNameResolver.java rename to googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdNameResolver.java index 3ec0434a22a..fb330b82bde 100644 --- a/xds/src/main/java/io/grpc/xds/GoogleCloudToProdNameResolver.java +++ b/googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdNameResolver.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds; +package io.grpc.googleapis; import static com.google.common.base.Preconditions.checkNotNull; @@ -32,7 +32,6 @@ import io.grpc.internal.GrpcUtil; import io.grpc.internal.SharedResourceHolder; import io.grpc.internal.SharedResourceHolder.Resource; -import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; @@ -40,6 +39,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.util.Map; import java.util.Random; import java.util.concurrent.Executor; @@ -70,7 +70,7 @@ final class GoogleCloudToProdNameResolver extends NameResolver { private final String authority; private final SynchronizationContext syncContext; private final Resource executorResource; - private final XdsClientPoolFactory xdsClientPoolFactory; + private final BootstrapSetter bootstrapSetter; private final NameResolver delegate; private final Random rand; private final boolean usingExecutorResource; @@ -84,17 +84,16 @@ final class GoogleCloudToProdNameResolver extends NameResolver { private boolean shutdown; GoogleCloudToProdNameResolver(URI targetUri, Args args, Resource executorResource, - XdsClientPoolFactory xdsClientPoolFactory) { - this(targetUri, args, executorResource, new Random(), xdsClientPoolFactory, + BootstrapSetter bootstrapSetter) { + this(targetUri, args, executorResource, new Random(), bootstrapSetter, NameResolverRegistry.getDefaultRegistry().asFactory()); } @VisibleForTesting GoogleCloudToProdNameResolver(URI targetUri, Args args, Resource executorResource, - Random rand, XdsClientPoolFactory xdsClientPoolFactory, - NameResolver.Factory nameResolverFactory) { + Random rand, BootstrapSetter bootstrapSetter, NameResolver.Factory nameResolverFactory) { this.executorResource = checkNotNull(executorResource, "executorResource"); - this.xdsClientPoolFactory = checkNotNull(xdsClientPoolFactory, "xdsClientPoolFactory"); + this.bootstrapSetter = checkNotNull(bootstrapSetter, "bootstrapSetter"); this.rand = checkNotNull(rand, "rand"); String targetPath = checkNotNull(checkNotNull(targetUri, "targetUri").getPath(), "targetPath"); Preconditions.checkArgument( @@ -159,7 +158,7 @@ public void run() { @Override public void run() { if (!shutdown && finalRawBootstrap != null) { - xdsClientPoolFactory.setBootstrapOverride(finalRawBootstrap); + bootstrapSetter.setBootstrap(finalRawBootstrap); delegate.start(listener); succeeded = true; } @@ -284,4 +283,8 @@ public HttpURLConnection createConnection(String url) throws IOException { interface HttpConnectionProvider { HttpURLConnection createConnection(String url) throws IOException; } + + public interface BootstrapSetter { + void setBootstrap(Map bootstrap); + } } diff --git a/xds/src/main/java/io/grpc/xds/GoogleCloudToProdNameResolverProvider.java b/googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdNameResolverProvider.java similarity index 75% rename from xds/src/main/java/io/grpc/xds/GoogleCloudToProdNameResolverProvider.java rename to googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdNameResolverProvider.java index e7f9cb45ff8..ac39ab1e625 100644 --- a/xds/src/main/java/io/grpc/xds/GoogleCloudToProdNameResolverProvider.java +++ b/googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdNameResolverProvider.java @@ -14,14 +14,16 @@ * limitations under the License. */ -package io.grpc.xds; +package io.grpc.googleapis; import io.grpc.Internal; import io.grpc.NameResolver; import io.grpc.NameResolver.Args; import io.grpc.NameResolverProvider; import io.grpc.internal.GrpcUtil; +import io.grpc.xds.InternalSharedXdsClientPoolProvider; import java.net.URI; +import java.util.Map; /** * A provider for {@link GoogleCloudToProdNameResolver}. @@ -36,7 +38,7 @@ public NameResolver newNameResolver(URI targetUri, Args args) { if (SCHEME.equals(targetUri.getScheme())) { return new GoogleCloudToProdNameResolver( targetUri, args, GrpcUtil.SHARED_CHANNEL_EXECUTOR, - SharedXdsClientPoolProvider.getDefaultProvider()); + new SharedXdsClientPoolProviderBootstrapSetter()); } return null; } @@ -55,4 +57,12 @@ protected boolean isAvailable() { protected int priority() { return 4; } + + private static final class SharedXdsClientPoolProviderBootstrapSetter + implements GoogleCloudToProdNameResolver.BootstrapSetter { + @Override + public void setBootstrap(Map bootstrap) { + InternalSharedXdsClientPoolProvider.setDefaultProviderBootstrapOverride(bootstrap); + } + } } diff --git a/googleapis/src/main/resources/META-INF/services/io.grpc.NameResolverProvider b/googleapis/src/main/resources/META-INF/services/io.grpc.NameResolverProvider new file mode 100644 index 00000000000..553e5409180 --- /dev/null +++ b/googleapis/src/main/resources/META-INF/services/io.grpc.NameResolverProvider @@ -0,0 +1 @@ +io.grpc.googleapis.GoogleCloudToProdNameResolverProvider diff --git a/xds/src/test/java/io/grpc/xds/GoogleCloudToProdNameResolverProviderTest.java b/googleapis/src/test/java/io/grpc/googleapis/GoogleCloudToProdNameResolverProviderTest.java similarity index 98% rename from xds/src/test/java/io/grpc/xds/GoogleCloudToProdNameResolverProviderTest.java rename to googleapis/src/test/java/io/grpc/googleapis/GoogleCloudToProdNameResolverProviderTest.java index cc6621028c8..e6ef1696dc0 100644 --- a/xds/src/test/java/io/grpc/xds/GoogleCloudToProdNameResolverProviderTest.java +++ b/googleapis/src/test/java/io/grpc/googleapis/GoogleCloudToProdNameResolverProviderTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds; +package io.grpc.googleapis; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; diff --git a/xds/src/test/java/io/grpc/xds/GoogleCloudToProdNameResolverTest.java b/googleapis/src/test/java/io/grpc/googleapis/GoogleCloudToProdNameResolverTest.java similarity index 90% rename from xds/src/test/java/io/grpc/xds/GoogleCloudToProdNameResolverTest.java rename to googleapis/src/test/java/io/grpc/googleapis/GoogleCloudToProdNameResolverTest.java index 7c777b84bf7..562798c0183 100644 --- a/xds/src/test/java/io/grpc/xds/GoogleCloudToProdNameResolverTest.java +++ b/googleapis/src/test/java/io/grpc/googleapis/GoogleCloudToProdNameResolverTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds; +package io.grpc.googleapis; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; @@ -33,12 +33,10 @@ import io.grpc.Status; import io.grpc.Status.Code; import io.grpc.SynchronizationContext; +import io.grpc.googleapis.GoogleCloudToProdNameResolver.HttpConnectionProvider; import io.grpc.internal.FakeClock; import io.grpc.internal.GrpcUtil; -import io.grpc.internal.ObjectPool; import io.grpc.internal.SharedResourceHolder.Resource; -import io.grpc.xds.GoogleCloudToProdNameResolver.HttpConnectionProvider; -import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory; import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.HttpURLConnection; @@ -50,7 +48,6 @@ import java.util.Random; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicReference; -import javax.annotation.Nullable; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -88,7 +85,7 @@ public void uncaughtException(Thread t, Throwable e) { .setChannelLogger(mock(ChannelLogger.class)) .build(); private final FakeClock fakeExecutor = new FakeClock(); - private final FakeXdsClientPoolFactory fakeXdsClientPoolFactory = new FakeXdsClientPoolFactory(); + private final FakeBootstrapSetter fakeBootstrapSetter = new FakeBootstrapSetter(); private final Resource fakeExecutorResource = new Resource() { @Override public Executor create() { @@ -144,7 +141,7 @@ public HttpURLConnection createConnection(String url) throws IOException { } }; resolver = new GoogleCloudToProdNameResolver( - TARGET_URI, args, fakeExecutorResource, random, fakeXdsClientPoolFactory, + TARGET_URI, args, fakeExecutorResource, random, fakeBootstrapSetter, nsRegistry.asFactory()); resolver.setHttpConnectionProvider(httpConnections); } @@ -178,7 +175,7 @@ public void onGcpAndNoProvidedBootstrapDelegateToXds() { fakeExecutor.runDueTasks(); assertThat(delegatedResolver.keySet()).containsExactly("xds"); verify(Iterables.getOnlyElement(delegatedResolver.values())).start(mockListener); - Map bootstrap = fakeXdsClientPoolFactory.bootstrapRef.get(); + Map bootstrap = fakeBootstrapSetter.bootstrapRef.get(); Map node = (Map) bootstrap.get("node"); assertThat(node).containsExactly( "id", "C2P-991614323", @@ -246,23 +243,13 @@ public String getDefaultScheme() { } } - private static final class FakeXdsClientPoolFactory implements XdsClientPoolFactory { + private static final class FakeBootstrapSetter + implements GoogleCloudToProdNameResolver.BootstrapSetter { private final AtomicReference> bootstrapRef = new AtomicReference<>(); @Override - public void setBootstrapOverride(Map bootstrap) { + public void setBootstrap(Map bootstrap) { bootstrapRef.set(bootstrap); } - - @Override - @Nullable - public ObjectPool get() { - throw new UnsupportedOperationException("Should not be called"); - } - - @Override - public ObjectPool getOrCreate() { - throw new UnsupportedOperationException("Should not be called"); - } } } diff --git a/settings.gradle b/settings.gradle index cd50337d5c2..8612f3c03cd 100644 --- a/settings.gradle +++ b/settings.gradle @@ -37,6 +37,7 @@ include ":grpc-protobuf" include ":grpc-protobuf-lite" include ":grpc-netty" include ":grpc-netty-shaded" +include ":grpc-googleapis" include ":grpc-grpclb" include ":grpc-testing" include ":grpc-testing-proto" @@ -63,6 +64,7 @@ project(':grpc-protobuf').projectDir = "$rootDir/protobuf" as File project(':grpc-protobuf-lite').projectDir = "$rootDir/protobuf-lite" as File project(':grpc-netty').projectDir = "$rootDir/netty" as File project(':grpc-netty-shaded').projectDir = "$rootDir/netty/shaded" as File +project(':grpc-googleapis').projectDir = "$rootDir/googleapis" as File project(':grpc-grpclb').projectDir = "$rootDir/grpclb" as File project(':grpc-testing').projectDir = "$rootDir/testing" as File project(':grpc-testing-proto').projectDir = "$rootDir/testing-proto" as File diff --git a/xds/src/main/java/io/grpc/xds/InternalSharedXdsClientPoolProvider.java b/xds/src/main/java/io/grpc/xds/InternalSharedXdsClientPoolProvider.java new file mode 100644 index 00000000000..114300c9281 --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/InternalSharedXdsClientPoolProvider.java @@ -0,0 +1,33 @@ +/* + * 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 io.grpc.Internal; +import java.util.Map; + +/** + * Accessor for global factory for managing XdsClient instance. + */ +@Internal +public final class InternalSharedXdsClientPoolProvider { + // Prevent instantiation + private InternalSharedXdsClientPoolProvider() {} + + public static void setDefaultProviderBootstrapOverride(Map bootstrap) { + SharedXdsClientPoolProvider.getDefaultProvider().setBootstrapOverride(bootstrap); + } +} diff --git a/xds/src/main/resources/META-INF/services/io.grpc.NameResolverProvider b/xds/src/main/resources/META-INF/services/io.grpc.NameResolverProvider index c1f2c40e7ee..269cdd38801 100644 --- a/xds/src/main/resources/META-INF/services/io.grpc.NameResolverProvider +++ b/xds/src/main/resources/META-INF/services/io.grpc.NameResolverProvider @@ -1,2 +1 @@ io.grpc.xds.XdsNameResolverProvider -io.grpc.xds.GoogleCloudToProdNameResolverProvider From b13d0c229470b9f862b443877a32aae3a50c7bf7 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 3 Mar 2022 13:20:50 -0800 Subject: [PATCH 66/68] xds: BootstrapperImpl should not be public It isn't used outside the package and is showing up in Javadoc. Instead of excluding it from the Javadoc, just make it package-private. --- xds/src/main/java/io/grpc/xds/BootstrapperImpl.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/BootstrapperImpl.java b/xds/src/main/java/io/grpc/xds/BootstrapperImpl.java index 23b14357096..4796f5e0361 100644 --- a/xds/src/main/java/io/grpc/xds/BootstrapperImpl.java +++ b/xds/src/main/java/io/grpc/xds/BootstrapperImpl.java @@ -22,7 +22,6 @@ import com.google.common.collect.ImmutableMap; import io.grpc.ChannelCredentials; import io.grpc.InsecureChannelCredentials; -import io.grpc.Internal; import io.grpc.InternalLogId; import io.grpc.TlsChannelCredentials; import io.grpc.alts.GoogleDefaultChannelCredentials; @@ -44,8 +43,7 @@ /** * A {@link Bootstrapper} implementation that reads xDS configurations from local file system. */ -@Internal -public class BootstrapperImpl extends Bootstrapper { +class BootstrapperImpl extends Bootstrapper { private static final String BOOTSTRAP_PATH_SYS_ENV_VAR = "GRPC_XDS_BOOTSTRAP"; @VisibleForTesting From 27024b40bc735cc471c4bfc09d5398b1148d5ad4 Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Tue, 8 Mar 2022 19:01:20 +0000 Subject: [PATCH 67/68] Update README etc to reference 1.45.0 --- README.md | 30 ++++++++++++------------ cronet/README.md | 2 +- documentation/android-channel-builder.md | 4 ++-- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index d19c0868269..9ebc824c8da 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.44.1/examples) and the -[Android example](https://github.com/grpc/grpc-java/tree/v1.44.1/examples/android) +The [examples](https://github.com/grpc/grpc-java/tree/v1.45.0/examples) and the +[Android example](https://github.com/grpc/grpc-java/tree/v1.45.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.44.1 + 1.45.0 runtime io.grpc grpc-protobuf - 1.44.1 + 1.45.0 io.grpc grpc-stub - 1.44.1 + 1.45.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.44.1' -implementation 'io.grpc:grpc-protobuf:1.44.1' -implementation 'io.grpc:grpc-stub:1.44.1' +runtimeOnly 'io.grpc:grpc-netty-shaded:1.45.0' +implementation 'io.grpc:grpc-protobuf:1.45.0' +implementation 'io.grpc:grpc-stub:1.45.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.44.1' -implementation 'io.grpc:grpc-protobuf-lite:1.44.1' -implementation 'io.grpc:grpc-stub:1.44.1' +implementation 'io.grpc:grpc-okhttp:1.45.0' +implementation 'io.grpc:grpc-protobuf-lite:1.45.0' +implementation 'io.grpc:grpc-stub:1.45.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.44.1 +https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.45.0 Development snapshots are available in [Sonatypes's snapshot repository](https://oss.sonatype.org/content/repositories/snapshots/). @@ -114,7 +114,7 @@ For protobuf-based codegen integrated with the Maven build system, you can use com.google.protobuf:protoc:3.19.2:exe:${os.detected.classifier} grpc-java - io.grpc:protoc-gen-grpc-java:1.44.1:exe:${os.detected.classifier} + io.grpc:protoc-gen-grpc-java:1.45.0:exe:${os.detected.classifier} @@ -144,7 +144,7 @@ protobuf { } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.44.1' + artifact = 'io.grpc:protoc-gen-grpc-java:1.45.0' } } generateProtoTasks { @@ -177,7 +177,7 @@ protobuf { } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.44.1' + artifact = 'io.grpc:protoc-gen-grpc-java:1.45.0' } } generateProtoTasks { diff --git a/cronet/README.md b/cronet/README.md index fb65ca23e5a..d8861e5fc4b 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.44.1' +implementation 'io.grpc:grpc-cronet:1.45.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 fcc42603cb2..2fb3f11d4dc 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.44.1' -implementation 'io.grpc:grpc-okhttp:1.44.1' +implementation 'io.grpc:grpc-android:1.45.0' +implementation 'io.grpc:grpc-okhttp:1.45.0' ``` You also need permission to access the device's network state in your From 8eff2630828a7ec6f4980b5b46f30f875070a4e4 Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Tue, 8 Mar 2022 19:22:33 +0000 Subject: [PATCH 68/68] Bump version to 1.45.0 --- build.gradle | 2 +- .../src/test/golden/TestDeprecatedService.java.txt | 2 +- compiler/src/test/golden/TestService.java.txt | 2 +- .../src/testLite/golden/TestDeprecatedService.java.txt | 2 +- compiler/src/testLite/golden/TestService.java.txt | 2 +- core/src/main/java/io/grpc/internal/GrpcUtil.java | 2 +- examples/android/clientcache/app/build.gradle | 10 +++++----- examples/android/helloworld/app/build.gradle | 8 ++++---- examples/android/routeguide/app/build.gradle | 8 ++++---- examples/android/strictmode/app/build.gradle | 8 ++++---- examples/build.gradle | 2 +- examples/example-alts/build.gradle | 2 +- examples/example-gauth/build.gradle | 2 +- examples/example-gauth/pom.xml | 4 ++-- examples/example-hostname/build.gradle | 2 +- examples/example-hostname/pom.xml | 4 ++-- examples/example-jwt-auth/build.gradle | 2 +- examples/example-jwt-auth/pom.xml | 4 ++-- examples/example-tls/build.gradle | 2 +- examples/example-tls/pom.xml | 4 ++-- examples/example-xds/build.gradle | 2 +- examples/pom.xml | 4 ++-- 22 files changed, 40 insertions(+), 40 deletions(-) diff --git a/build.gradle b/build.gradle index efd9640e0ba..51dbffae770 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ subprojects { apply plugin: "net.ltgt.errorprone" group = "io.grpc" - version = "1.45.0-SNAPSHOT" // CURRENT_GRPC_VERSION + version = "1.45.0" // CURRENT_GRPC_VERSION repositories { maven { // The google mirror is less flaky than mavenCentral() diff --git a/compiler/src/test/golden/TestDeprecatedService.java.txt b/compiler/src/test/golden/TestDeprecatedService.java.txt index bae773cf5cb..eda5a4de6ab 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.45.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.45.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 ac5f7b1e84a..6bb5aaebfc9 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.45.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.45.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 70b348a33b0..ab35321fe87 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.45.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.45.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 2ca755211f3..e72fa752e52 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.45.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.45.0)", comments = "Source: grpc/testing/compiler/test.proto") @io.grpc.stub.annotations.GrpcGenerated public final class TestServiceGrpc { diff --git a/core/src/main/java/io/grpc/internal/GrpcUtil.java b/core/src/main/java/io/grpc/internal/GrpcUtil.java index ab4bf7f7657..6762c7853fb 100644 --- a/core/src/main/java/io/grpc/internal/GrpcUtil.java +++ b/core/src/main/java/io/grpc/internal/GrpcUtil.java @@ -203,7 +203,7 @@ public byte[] parseAsciiString(byte[] serialized) { public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults(); - private static final String IMPLEMENTATION_VERSION = "1.45.0-SNAPSHOT"; // CURRENT_GRPC_VERSION + private static final String IMPLEMENTATION_VERSION = "1.45.0"; // CURRENT_GRPC_VERSION /** * The default timeout in nanos for a keepalive ping request. diff --git a/examples/android/clientcache/app/build.gradle b/examples/android/clientcache/app/build.gradle index 65803334c58..0d190b6f55c 100644 --- a/examples/android/clientcache/app/build.gradle +++ b/examples/android/clientcache/app/build.gradle @@ -34,7 +34,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.19.2' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.45.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.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.45.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.45.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.45.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.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION + testImplementation 'io.grpc:grpc-testing:1.45.0' // CURRENT_GRPC_VERSION } diff --git a/examples/android/helloworld/app/build.gradle b/examples/android/helloworld/app/build.gradle index 12b6899d881..d4ba7520acc 100644 --- a/examples/android/helloworld/app/build.gradle +++ b/examples/android/helloworld/app/build.gradle @@ -32,7 +32,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.19.2' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.45.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.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.45.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.45.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.45.0' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/android/routeguide/app/build.gradle b/examples/android/routeguide/app/build.gradle index f7e9c1a4bb6..463f0b4ee28 100644 --- a/examples/android/routeguide/app/build.gradle +++ b/examples/android/routeguide/app/build.gradle @@ -32,7 +32,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.19.2' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.45.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.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.45.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.45.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.45.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 9638eb38a08..06e88663bc7 100644 --- a/examples/android/strictmode/app/build.gradle +++ b/examples/android/strictmode/app/build.gradle @@ -33,7 +33,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.19.2' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.45.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.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.45.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.45.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.45.0' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/build.gradle b/examples/build.gradle index 8a5599f0a65..76bd1082811 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -22,7 +22,7 @@ targetCompatibility = 1.8 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.45.0' // CURRENT_GRPC_VERSION def protobufVersion = '3.19.2' def protocVersion = protobufVersion diff --git a/examples/example-alts/build.gradle b/examples/example-alts/build.gradle index b29c159e574..887357c0334 100644 --- a/examples/example-alts/build.gradle +++ b/examples/example-alts/build.gradle @@ -23,7 +23,7 @@ targetCompatibility = 1.8 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.45.0' // CURRENT_GRPC_VERSION def protocVersion = '3.19.2' dependencies { diff --git a/examples/example-gauth/build.gradle b/examples/example-gauth/build.gradle index 40bf8968b79..cd617c03681 100644 --- a/examples/example-gauth/build.gradle +++ b/examples/example-gauth/build.gradle @@ -23,7 +23,7 @@ targetCompatibility = 1.8 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.45.0' // CURRENT_GRPC_VERSION def protobufVersion = '3.19.2' def protocVersion = protobufVersion diff --git a/examples/example-gauth/pom.xml b/examples/example-gauth/pom.xml index 5d56b44ab1e..273bf4fe640 100644 --- a/examples/example-gauth/pom.xml +++ b/examples/example-gauth/pom.xml @@ -6,13 +6,13 @@ jar - 1.45.0-SNAPSHOT + 1.45.0 example-gauth https://github.com/grpc/grpc-java UTF-8 - 1.45.0-SNAPSHOT + 1.45.0 3.19.2 1.7 diff --git a/examples/example-hostname/build.gradle b/examples/example-hostname/build.gradle index abf4a4e5af6..bc04e0da888 100644 --- a/examples/example-hostname/build.gradle +++ b/examples/example-hostname/build.gradle @@ -21,7 +21,7 @@ targetCompatibility = 1.8 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.45.0' // CURRENT_GRPC_VERSION def protobufVersion = '3.19.2' dependencies { diff --git a/examples/example-hostname/pom.xml b/examples/example-hostname/pom.xml index 53d399e2460..01b58578fba 100644 --- a/examples/example-hostname/pom.xml +++ b/examples/example-hostname/pom.xml @@ -6,13 +6,13 @@ jar - 1.45.0-SNAPSHOT + 1.45.0 example-hostname https://github.com/grpc/grpc-java UTF-8 - 1.45.0-SNAPSHOT + 1.45.0 3.19.2 1.7 diff --git a/examples/example-jwt-auth/build.gradle b/examples/example-jwt-auth/build.gradle index 1e4acd3deb9..b1ba3b84662 100644 --- a/examples/example-jwt-auth/build.gradle +++ b/examples/example-jwt-auth/build.gradle @@ -22,7 +22,7 @@ targetCompatibility = 1.8 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.45.0' // CURRENT_GRPC_VERSION def protobufVersion = '3.19.2' def protocVersion = protobufVersion diff --git a/examples/example-jwt-auth/pom.xml b/examples/example-jwt-auth/pom.xml index d78e4e6a630..7724d7309b0 100644 --- a/examples/example-jwt-auth/pom.xml +++ b/examples/example-jwt-auth/pom.xml @@ -7,13 +7,13 @@ jar - 1.45.0-SNAPSHOT + 1.45.0 example-jwt-auth https://github.com/grpc/grpc-java UTF-8 - 1.45.0-SNAPSHOT + 1.45.0 3.19.2 3.19.2 diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index 2eddaf6afbe..07656db6b59 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -23,7 +23,7 @@ targetCompatibility = 1.8 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.45.0' // CURRENT_GRPC_VERSION def protocVersion = '3.19.2' dependencies { diff --git a/examples/example-tls/pom.xml b/examples/example-tls/pom.xml index a135dc51d94..fb6fe00a0c1 100644 --- a/examples/example-tls/pom.xml +++ b/examples/example-tls/pom.xml @@ -6,13 +6,13 @@ jar - 1.45.0-SNAPSHOT + 1.45.0 example-tls https://github.com/grpc/grpc-java UTF-8 - 1.45.0-SNAPSHOT + 1.45.0 3.19.2 2.0.34.Final diff --git a/examples/example-xds/build.gradle b/examples/example-xds/build.gradle index 56fef5ef12d..880f8a0014f 100644 --- a/examples/example-xds/build.gradle +++ b/examples/example-xds/build.gradle @@ -22,7 +22,7 @@ targetCompatibility = 1.8 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.45.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.45.0' // CURRENT_GRPC_VERSION def nettyTcNativeVersion = '2.0.31.Final' def protocVersion = '3.19.2' diff --git a/examples/pom.xml b/examples/pom.xml index 20c67251689..9a140e482f2 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -6,13 +6,13 @@ jar - 1.45.0-SNAPSHOT + 1.45.0 examples https://github.com/grpc/grpc-java UTF-8 - 1.45.0-SNAPSHOT + 1.45.0 3.19.2 3.19.2