diff --git a/BUILD.bazel b/BUILD.bazel index a6d0e43dec3..40c04022673 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -64,5 +64,8 @@ java_library( exported_plugins = [":auto_value"], neverlink = 1, visibility = ["//:__subpackages__"], - exports = ["@com_google_auto_value_auto_value_annotations//jar"], + exports = [ + "@com_google_auto_value_auto_value_annotations//jar", + "@org_apache_tomcat_annotations_api//jar", # @Generated for Java 9+ + ], ) diff --git a/MAINTAINERS.md b/MAINTAINERS.md index a81b51bc3dc..f05542e1987 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -9,7 +9,9 @@ for general contribution guidelines. ## Maintainers (in alphabetical order) - [ejona86](https://github.com/ejona86), Google LLC +- [jdcormie](https://github.com/jdcormie), Google LLC - [larry-safran](https://github.com/larry-safran), Google LLC +- [markb74](https://github.com/markb74), Google LLC - [ran-su](https://github.com/ran-su), Google LLC - [sanjaypujare](https://github.com/sanjaypujare), Google LLC - [sergiitk](https://github.com/sergiitk), Google LLC diff --git a/README.md b/README.md index f4397b34353..6761e9fc741 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,6 @@ gRPC-Java - An RPC library and framework ======================================== -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. - -TLS usage typically requires using Java 8, or Play Services Dynamic Security -Provider on Android. Please see the [Security Readme](SECURITY.md). - @@ -24,6 +17,26 @@ Provider on Android. Please see the [Security Readme](SECURITY.md). [![Line Coverage Status](https://coveralls.io/repos/grpc/grpc-java/badge.svg?branch=master&service=github)](https://coveralls.io/github/grpc/grpc-java?branch=master) [![Branch-adjusted Line Coverage Status](https://codecov.io/gh/grpc/grpc-java/branch/master/graph/badge.svg)](https://codecov.io/gh/grpc/grpc-java) +Supported Platforms +------------------- + +gRPC-Java supports Java 8 and later. Android minSdkVersion 19 (KitKat) and +later are supported with [Java 8 language desugaring][android-java-8]. + +TLS usage on Android typically requires Play Services Dynamic Security Provider. +Please see the [Security Readme](SECURITY.md). + +Older Java versions are not directly supported, but a branch remains available +for fixes and releases. See [gRFC P5 JDK Version Support +Policy][P5-jdk-version-support]. + +Java version | gRPC Branch +------------ | ----------- +7 | 1.41.x + +[android-java-8]: https://developer.android.com/studio/write/java8-support#supported_features +[P5-jdk-version-support]: https://github.com/grpc/proposal/blob/master/P5-jdk-version-support.md#proposal + Getting Started --------------- @@ -31,8 +44,8 @@ For a guided tour, take a look at the [quick start guide](https://grpc.io/docs/languages/java/quickstart) or the more explanatory [gRPC basics](https://grpc.io/docs/languages/java/basics). -The [examples](https://github.com/grpc/grpc-java/tree/v1.50.2/examples) and the -[Android example](https://github.com/grpc/grpc-java/tree/v1.50.2/examples/android) +The [examples](https://github.com/grpc/grpc-java/tree/v1.52.0/examples) and the +[Android example](https://github.com/grpc/grpc-java/tree/v1.52.0/examples/android) are standalone projects that showcase the usage of gRPC. Download @@ -43,18 +56,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`: io.grpc grpc-netty-shaded - 1.50.2 + 1.52.0 runtime io.grpc grpc-protobuf - 1.50.2 + 1.52.0 io.grpc grpc-stub - 1.50.2 + 1.52.0 org.apache.tomcat @@ -66,23 +79,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.50.2' -implementation 'io.grpc:grpc-protobuf:1.50.2' -implementation 'io.grpc:grpc-stub:1.50.2' +runtimeOnly 'io.grpc:grpc-netty-shaded:1.52.0' +implementation 'io.grpc:grpc-protobuf:1.52.0' +implementation 'io.grpc:grpc-stub:1.52.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.50.2' -implementation 'io.grpc:grpc-protobuf-lite:1.50.2' -implementation 'io.grpc:grpc-stub:1.50.2' +implementation 'io.grpc:grpc-okhttp:1.52.0' +implementation 'io.grpc:grpc-protobuf-lite:1.52.0' +implementation 'io.grpc:grpc-stub:1.52.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.50.2 +https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.52.0 Development snapshots are available in [Sonatypes's snapshot repository](https://oss.sonatype.org/content/repositories/snapshots/). @@ -114,7 +127,7 @@ For protobuf-based codegen integrated with the Maven build system, you can use com.google.protobuf:protoc:3.21.7:exe:${os.detected.classifier} grpc-java - io.grpc:protoc-gen-grpc-java:1.50.2:exe:${os.detected.classifier} + io.grpc:protoc-gen-grpc-java:1.52.0:exe:${os.detected.classifier} @@ -135,7 +148,7 @@ For non-Android protobuf-based codegen integrated with the Gradle build system, you can use [protobuf-gradle-plugin][]: ```gradle plugins { - id 'com.google.protobuf' version '0.8.18' + id 'com.google.protobuf' version '0.8.19' } protobuf { @@ -144,7 +157,7 @@ protobuf { } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.50.2' + artifact = 'io.grpc:protoc-gen-grpc-java:1.52.0' } } generateProtoTasks { @@ -168,7 +181,7 @@ use protobuf-gradle-plugin but specify the 'lite' options: ```gradle plugins { - id 'com.google.protobuf' version '0.8.18' + id 'com.google.protobuf' version '0.8.19' } protobuf { @@ -177,7 +190,7 @@ protobuf { } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.50.2' + artifact = 'io.grpc:protoc-gen-grpc-java:1.52.0' } } generateProtoTasks { @@ -243,12 +256,15 @@ wire. The interfaces to it are abstract just enough to allow plugging in of different implementations. Note the transport layer API is considered internal to gRPC and has weaker API guarantees than the core API under package `io.grpc`. -gRPC comes with three Transport implementations: +gRPC comes with multiple Transport implementations: -1. The Netty-based transport is the main transport implementation based on - [Netty](https://netty.io). It is for both the client and the server. -2. The OkHttp-based transport is a lightweight transport based on - [OkHttp](https://square.github.io/okhttp/). It is mainly for use on Android - and is for client only. +1. The Netty-based HTTP/2 transport is the main transport implementation based + on [Netty](https://netty.io). It is not officially supported on Android. +2. The OkHttp-based HTTP/2 transport is a lightweight transport based on + [Okio](https://square.github.io/okio/) and forked low-level parts of + [OkHttp](https://square.github.io/okhttp/). It is mainly for use on Android. 3. The in-process transport is for when a server is in the same process as the - client. It is useful for testing, while also being safe for production use. + client. It is used frequently for testing, while also being safe for + production use. +4. The Binder transport is for Android cross-process communication on a single + device. diff --git a/api/src/main/java/io/grpc/CallOptions.java b/api/src/main/java/io/grpc/CallOptions.java index 5c05d5b7bd7..4b180c56e07 100644 --- a/api/src/main/java/io/grpc/CallOptions.java +++ b/api/src/main/java/io/grpc/CallOptions.java @@ -41,42 +41,75 @@ public final class CallOptions { /** * A blank {@code CallOptions} that all fields are not set. */ - public static final CallOptions DEFAULT = new CallOptions(); + public static final CallOptions DEFAULT; + + static { + Builder b = new Builder(); + b.customOptions = new Object[0][2]; + b.streamTracerFactories = Collections.emptyList(); + DEFAULT = b.build(); + } - // Although {@code CallOptions} is immutable, its fields are not final, so that we can initialize - // them outside of constructor. Otherwise the constructor will have a potentially long list of - // unnamed arguments, which is undesirable. @Nullable - private Deadline deadline; - + private final Deadline deadline; + @Nullable - private Executor executor; + private final Executor executor; @Nullable - private String authority; + private final String authority; @Nullable - private CallCredentials credentials; + private final CallCredentials credentials; @Nullable - private String compressorName; + private final String compressorName; - private Object[][] customOptions; + private final Object[][] customOptions; - // Unmodifiable list - private List streamTracerFactories = Collections.emptyList(); + private final List streamTracerFactories; /** * Opposite to fail fast. */ @Nullable - private Boolean waitForReady; + private final Boolean waitForReady; @Nullable - private Integer maxInboundMessageSize; + private final Integer maxInboundMessageSize; @Nullable - private Integer maxOutboundMessageSize; + private final Integer maxOutboundMessageSize; + + private CallOptions(Builder builder) { + this.deadline = builder.deadline; + this.executor = builder.executor; + this.authority = builder.authority; + this.credentials = builder.credentials; + this.compressorName = builder.compressorName; + this.customOptions = builder.customOptions; + this.streamTracerFactories = builder.streamTracerFactories; + this.waitForReady = builder.waitForReady; + this.maxInboundMessageSize = builder.maxInboundMessageSize; + this.maxOutboundMessageSize = builder.maxOutboundMessageSize; + } + static class Builder { + Deadline deadline; + Executor executor; + String authority; + CallCredentials credentials; + String compressorName; + Object[][] customOptions; + // Unmodifiable list + List streamTracerFactories; + Boolean waitForReady; + Integer maxInboundMessageSize; + Integer maxOutboundMessageSize; + + private CallOptions build() { + return new CallOptions(this); + } + } /** * Override the HTTP/2 authority the channel claims to be connecting to. This is not @@ -89,18 +122,18 @@ public final class CallOptions { */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1767") public CallOptions withAuthority(@Nullable String authority) { - CallOptions newOptions = new CallOptions(this); - newOptions.authority = authority; - return newOptions; + Builder builder = toBuilder(this); + builder.authority = authority; + return builder.build(); } /** * Returns a new {@code CallOptions} with the given call credentials. */ public CallOptions withCallCredentials(@Nullable CallCredentials credentials) { - CallOptions newOptions = new CallOptions(this); - newOptions.credentials = credentials; - return newOptions; + Builder builder = toBuilder(this); + builder.credentials = credentials; + return builder.build(); } /** @@ -113,9 +146,9 @@ public CallOptions withCallCredentials(@Nullable CallCredentials credentials) { */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1704") public CallOptions withCompression(@Nullable String compressorName) { - CallOptions newOptions = new CallOptions(this); - newOptions.compressorName = compressorName; - return newOptions; + Builder builder = toBuilder(this); + builder.compressorName = compressorName; + return builder.build(); } /** @@ -127,9 +160,9 @@ public CallOptions withCompression(@Nullable String compressorName) { * @param deadline the deadline or {@code null} for unsetting the deadline. */ public CallOptions withDeadline(@Nullable Deadline deadline) { - CallOptions newOptions = new CallOptions(this); - newOptions.deadline = deadline; - return newOptions; + Builder builder = toBuilder(this); + builder.deadline = deadline; + return builder.build(); } /** @@ -156,9 +189,9 @@ public Deadline getDeadline() { * fails RPCs without sending them if unable to connect. */ public CallOptions withWaitForReady() { - CallOptions newOptions = new CallOptions(this); - newOptions.waitForReady = Boolean.TRUE; - return newOptions; + Builder builder = toBuilder(this); + builder.waitForReady = Boolean.TRUE; + return builder.build(); } /** @@ -166,9 +199,9 @@ public CallOptions withWaitForReady() { * This method should be rarely used because the default is without 'wait for ready'. */ public CallOptions withoutWaitForReady() { - CallOptions newOptions = new CallOptions(this); - newOptions.waitForReady = Boolean.FALSE; - return newOptions; + Builder builder = toBuilder(this); + builder.waitForReady = Boolean.FALSE; + return builder.build(); } /** @@ -208,9 +241,9 @@ public CallCredentials getCredentials() { * executor specified with {@link ManagedChannelBuilder#executor}. */ public CallOptions withExecutor(@Nullable Executor executor) { - CallOptions newOptions = new CallOptions(this); - newOptions.executor = executor; - return newOptions; + Builder builder = toBuilder(this); + builder.executor = executor; + return builder.build(); } /** @@ -221,13 +254,13 @@ public CallOptions withExecutor(@Nullable Executor executor) { */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2861") public CallOptions withStreamTracerFactory(ClientStreamTracer.Factory factory) { - CallOptions newOptions = new CallOptions(this); ArrayList newList = new ArrayList<>(streamTracerFactories.size() + 1); newList.addAll(streamTracerFactories); newList.add(factory); - newOptions.streamTracerFactories = Collections.unmodifiableList(newList); - return newOptions; + Builder builder = toBuilder(this); + builder.streamTracerFactories = Collections.unmodifiableList(newList); + return builder.build(); } /** @@ -319,7 +352,7 @@ public CallOptions withOption(Key key, T value) { Preconditions.checkNotNull(key, "key"); Preconditions.checkNotNull(value, "value"); - CallOptions newOptions = new CallOptions(this); + Builder builder = toBuilder(this); int existingIdx = -1; for (int i = 0; i < customOptions.length; i++) { if (key.equals(customOptions[i][0])) { @@ -328,18 +361,18 @@ public CallOptions withOption(Key key, T value) { } } - newOptions.customOptions = new Object[customOptions.length + (existingIdx == -1 ? 1 : 0)][2]; - System.arraycopy(customOptions, 0, newOptions.customOptions, 0, customOptions.length); + builder.customOptions = new Object[customOptions.length + (existingIdx == -1 ? 1 : 0)][2]; + System.arraycopy(customOptions, 0, builder.customOptions, 0, customOptions.length); if (existingIdx == -1) { // Add a new option - newOptions.customOptions[customOptions.length] = new Object[] {key, value}; + builder.customOptions[customOptions.length] = new Object[] {key, value}; } else { // Replace an existing option - newOptions.customOptions[existingIdx] = new Object[] {key, value}; + builder.customOptions[existingIdx] = new Object[] {key, value}; } - return newOptions; + return builder.build(); } /** @@ -368,10 +401,6 @@ public Executor getExecutor() { return executor; } - private CallOptions() { - customOptions = new Object[0][2]; - } - /** * Returns whether * 'wait for ready' option is enabled for the call. 'Fail fast' is the default option for gRPC @@ -392,9 +421,9 @@ Boolean getWaitForReady() { @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2563") public CallOptions withMaxInboundMessageSize(int maxSize) { checkArgument(maxSize >= 0, "invalid maxsize %s", maxSize); - CallOptions newOptions = new CallOptions(this); - newOptions.maxInboundMessageSize = maxSize; - return newOptions; + Builder builder = toBuilder(this); + builder.maxInboundMessageSize = maxSize; + return builder.build(); } /** @@ -403,9 +432,9 @@ public CallOptions withMaxInboundMessageSize(int maxSize) { @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2563") public CallOptions withMaxOutboundMessageSize(int maxSize) { checkArgument(maxSize >= 0, "invalid maxsize %s", maxSize); - CallOptions newOptions = new CallOptions(this); - newOptions.maxOutboundMessageSize = maxSize; - return newOptions; + Builder builder = toBuilder(this); + builder.maxOutboundMessageSize = maxSize; + return builder.build(); } /** @@ -427,19 +456,21 @@ public Integer getMaxOutboundMessageSize() { } /** - * Copy constructor. + * Copy CallOptions. */ - private CallOptions(CallOptions other) { - deadline = other.deadline; - authority = other.authority; - credentials = other.credentials; - executor = other.executor; - compressorName = other.compressorName; - customOptions = other.customOptions; - waitForReady = other.waitForReady; - maxInboundMessageSize = other.maxInboundMessageSize; - maxOutboundMessageSize = other.maxOutboundMessageSize; - streamTracerFactories = other.streamTracerFactories; + private static Builder toBuilder(CallOptions other) { + Builder builder = new Builder(); + builder.deadline = other.deadline; + builder.executor = other.executor; + builder.authority = other.authority; + builder.credentials = other.credentials; + builder.compressorName = other.compressorName; + builder.customOptions = other.customOptions; + builder.streamTracerFactories = other.streamTracerFactories; + builder.waitForReady = other.waitForReady; + builder.maxInboundMessageSize = other.maxInboundMessageSize; + builder.maxOutboundMessageSize = other.maxOutboundMessageSize; + return builder; } @Override diff --git a/api/src/main/java/io/grpc/LoadBalancerRegistry.java b/api/src/main/java/io/grpc/LoadBalancerRegistry.java index a215c4108d1..f6b69f978b8 100644 --- a/api/src/main/java/io/grpc/LoadBalancerRegistry.java +++ b/api/src/main/java/io/grpc/LoadBalancerRegistry.java @@ -107,9 +107,7 @@ public static synchronized LoadBalancerRegistry getDefaultRegistry() { instance = new LoadBalancerRegistry(); for (LoadBalancerProvider provider : providerList) { logger.fine("Service loader found " + provider); - if (provider.isAvailable()) { - instance.addProvider(provider); - } + instance.addProvider(provider); } instance.refreshProviderMap(); } diff --git a/api/src/main/java/io/grpc/ManagedChannelRegistry.java b/api/src/main/java/io/grpc/ManagedChannelRegistry.java index 52a19d55ecb..04bdc6b0d57 100644 --- a/api/src/main/java/io/grpc/ManagedChannelRegistry.java +++ b/api/src/main/java/io/grpc/ManagedChannelRegistry.java @@ -106,9 +106,7 @@ public static synchronized ManagedChannelRegistry getDefaultRegistry() { instance = new ManagedChannelRegistry(); for (ManagedChannelProvider provider : providerList) { logger.fine("Service loader found " + provider); - if (provider.isAvailable()) { - instance.addProvider(provider); - } + instance.addProvider(provider); } instance.refreshProviders(); } diff --git a/api/src/main/java/io/grpc/NameResolverRegistry.java b/api/src/main/java/io/grpc/NameResolverRegistry.java index 2e12bb77483..ab8a1e803eb 100644 --- a/api/src/main/java/io/grpc/NameResolverRegistry.java +++ b/api/src/main/java/io/grpc/NameResolverRegistry.java @@ -124,9 +124,7 @@ public static synchronized NameResolverRegistry getDefaultRegistry() { instance = new NameResolverRegistry(); for (NameResolverProvider provider : providerList) { logger.fine("Service loader found " + provider); - if (provider.isAvailable()) { - instance.addProvider(provider); - } + instance.addProvider(provider); } instance.refreshProviders(); } diff --git a/api/src/main/java/io/grpc/ServerRegistry.java b/api/src/main/java/io/grpc/ServerRegistry.java index e40039fb34c..e6a067ce87f 100644 --- a/api/src/main/java/io/grpc/ServerRegistry.java +++ b/api/src/main/java/io/grpc/ServerRegistry.java @@ -98,9 +98,7 @@ public static synchronized ServerRegistry getDefaultRegistry() { instance = new ServerRegistry(); for (ServerProvider provider : providerList) { logger.fine("Service loader found " + provider); - if (provider.isAvailable()) { - instance.addProvider(provider); - } + instance.addProvider(provider); } instance.refreshProviders(); } diff --git a/binder/src/androidTest/java/io/grpc/binder/BinderSecurityTest.java b/binder/src/androidTest/java/io/grpc/binder/BinderSecurityTest.java index 56e8f93c140..e3b9978fb36 100644 --- a/binder/src/androidTest/java/io/grpc/binder/BinderSecurityTest.java +++ b/binder/src/androidTest/java/io/grpc/binder/BinderSecurityTest.java @@ -19,26 +19,25 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; -import android.app.Service; import android.content.Context; -import android.content.Intent; -import android.os.IBinder; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.common.base.Function; import com.google.protobuf.Empty; import io.grpc.CallOptions; import io.grpc.ManagedChannel; +import io.grpc.Metadata; import io.grpc.MethodDescriptor; import io.grpc.Server; +import io.grpc.ServerCall; import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; import io.grpc.ServerServiceDefinition; import io.grpc.Status; import io.grpc.StatusRuntimeException; import io.grpc.protobuf.lite.ProtoLiteUtils; import io.grpc.stub.ClientCalls; import io.grpc.stub.ServerCalls; -import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -59,6 +58,7 @@ public final class BinderSecurityTest { @Nullable ManagedChannel channel; Map> methods = new HashMap<>(); List> calls = new ArrayList<>(); + CountingServerInterceptor countingServerInterceptor; @Before public void setupServiceDefinitionsAndMethods() { @@ -86,6 +86,7 @@ public void setupServiceDefinitionsAndMethods() { } serviceDefinitions.add(builder.build()); } + countingServerInterceptor = new CountingServerInterceptor(); } @After @@ -120,6 +121,7 @@ private Server buildServer( ServerSecurityPolicy serverPolicy) { BinderServerBuilder serverBuilder = BinderServerBuilder.forAddress(listenAddr, receiver); serverBuilder.securityPolicy(serverPolicy); + serverBuilder.intercept(countingServerInterceptor); for (ServerServiceDefinition serviceDefinition : serviceDefinitions) { serverBuilder.addService(serviceDefinition); @@ -195,6 +197,27 @@ public void testPerServicePolicy() throws Exception { } } + @Test + public void testSecurityInterceptorIsClosestToTransport() throws Exception { + createChannel( + ServerSecurityPolicy.newBuilder() + .servicePolicy("foo", policy((uid) -> true)) + .servicePolicy("bar", policy((uid) -> false)) + .servicePolicy("baz", policy((uid) -> false)) + .build(), + SecurityPolicies.internalOnly()); + assertThat(countingServerInterceptor.numInterceptedCalls).isEqualTo(0); + for (MethodDescriptor method : methods.values()) { + try { + ClientCalls.blockingUnaryCall(channel, method, CallOptions.DEFAULT, null); + } catch (StatusRuntimeException sre) { + // Ignore. + } + } + // Only the foo calls should have made it to the user interceptor. + assertThat(countingServerInterceptor.numInterceptedCalls).isEqualTo(2); + } + private static SecurityPolicy policy(Function func) { return new SecurityPolicy() { @Override @@ -203,4 +226,17 @@ public Status checkAuthorization(int uid) { } }; } + + private final class CountingServerInterceptor implements ServerInterceptor { + int numInterceptedCalls; + + @Override + public ServerCall.Listener interceptCall( + ServerCall call, + Metadata headers, + ServerCallHandler next) { + numInterceptedCalls += 1; + return next.startCall(call, headers); + } + } } diff --git a/binder/src/main/java/io/grpc/binder/AndroidComponentAddress.java b/binder/src/main/java/io/grpc/binder/AndroidComponentAddress.java index cb4f7f794cf..90197ee8382 100644 --- a/binder/src/main/java/io/grpc/binder/AndroidComponentAddress.java +++ b/binder/src/main/java/io/grpc/binder/AndroidComponentAddress.java @@ -22,7 +22,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import io.grpc.ExperimentalApi; import java.net.SocketAddress; /** @@ -41,8 +40,7 @@ * fields, namely, an action of {@link ApiConstants#ACTION_BIND}, an empty category set and null * type and data URI. */ -@ExperimentalApi("https://github.com/grpc/grpc-java/issues/8022") -public class AndroidComponentAddress extends SocketAddress { // NOTE: Only temporarily non-final. +public final class AndroidComponentAddress extends SocketAddress { private static final long serialVersionUID = 0L; private final Intent bindIntent; // An "explicit" Intent. In other words, getComponent() != null. @@ -103,6 +101,11 @@ public static AndroidComponentAddress forComponent(ComponentName component) { new Intent(ApiConstants.ACTION_BIND).setComponent(component)); } + /** + * Returns the Authority which is the package name of the target app. + * + *

See {@link android.content.ComponentName}. + */ public String getAuthority() { return getComponent().getPackageName(); } diff --git a/binder/src/main/java/io/grpc/binder/BinderChannelBuilder.java b/binder/src/main/java/io/grpc/binder/BinderChannelBuilder.java index 91e4e8f1c76..214eb6dc4c5 100644 --- a/binder/src/main/java/io/grpc/binder/BinderChannelBuilder.java +++ b/binder/src/main/java/io/grpc/binder/BinderChannelBuilder.java @@ -17,16 +17,13 @@ package io.grpc.binder; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; -import android.app.Application; -import android.content.ComponentName; import android.content.Context; import androidx.core.content.ContextCompat; import com.google.errorprone.annotations.DoNotCall; import io.grpc.ChannelCredentials; import io.grpc.ChannelLogger; -import io.grpc.CompressorRegistry; -import io.grpc.DecompressorRegistry; import io.grpc.ExperimentalApi; import io.grpc.ForwardingChannelBuilder; import io.grpc.ManagedChannel; @@ -124,6 +121,7 @@ public static BinderChannelBuilder forTarget(String target) { private SecurityPolicy securityPolicy; private InboundParcelablePolicy inboundParcelablePolicy; private BindServiceFlags bindServiceFlags; + private boolean strictLifecycleManagement; private BinderChannelBuilder( @Nullable AndroidComponentAddress directAddress, @@ -164,6 +162,7 @@ public ClientTransportFactory buildClientTransportFactory() { new BinderChannelTransportFactoryBuilder(), null); } + idleTimeout(60, TimeUnit.SECONDS); } @Override @@ -224,6 +223,25 @@ public BinderChannelBuilder inboundParcelablePolicy( return this; } + /** + * Disables the channel idle timeout and prevents it from being enabled. This + * allows a centralized application method to configure the channel builder + * and return it, without worrying about another part of the application + * accidentally enabling the idle timeout. + */ + public BinderChannelBuilder strictLifecycleManagement() { + strictLifecycleManagement = true; + super.idleTimeout(1000, TimeUnit.DAYS); // >30 days disables timeouts entirely. + return this; + } + + @Override + public BinderChannelBuilder idleTimeout(long value, TimeUnit unit) { + checkState(!strictLifecycleManagement, "Idle timeouts are not supported when strict lifecycle management is enabled"); + super.idleTimeout(value, unit); + return this; + } + /** Creates new binder transports. */ private static final class TransportFactory implements ClientTransportFactory { private final Context sourceContext; diff --git a/binder/src/main/java/io/grpc/binder/BinderInternal.java b/binder/src/main/java/io/grpc/binder/BinderInternal.java new file mode 100644 index 00000000000..34f7793714f --- /dev/null +++ b/binder/src/main/java/io/grpc/binder/BinderInternal.java @@ -0,0 +1,34 @@ +/* + * 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.binder; + +import android.os.IBinder; +import io.grpc.Internal; + +/** + * Helper class to expose IBinderReceiver methods for legacy internal builders. + */ +@Internal +public class BinderInternal { + + /** + * Sets the receiver's {@link IBinder} using {@link IBinderReceiver#set(IBinder)}. + */ + public static void setIBinder(IBinderReceiver receiver, IBinder binder) { + receiver.set(binder); + } +} diff --git a/binder/src/main/java/io/grpc/binder/BinderServerBuilder.java b/binder/src/main/java/io/grpc/binder/BinderServerBuilder.java index 383bd3f8e49..eaa94bffc45 100644 --- a/binder/src/main/java/io/grpc/binder/BinderServerBuilder.java +++ b/binder/src/main/java/io/grpc/binder/BinderServerBuilder.java @@ -21,34 +21,24 @@ import android.app.Service; import android.os.IBinder; -import com.google.common.base.Supplier; import com.google.errorprone.annotations.DoNotCall; -import io.grpc.CompressorRegistry; -import io.grpc.DecompressorRegistry; import io.grpc.ExperimentalApi; import io.grpc.Server; import io.grpc.ServerBuilder; -import io.grpc.ServerStreamTracer; import io.grpc.binder.internal.BinderServer; import io.grpc.binder.internal.BinderTransportSecurity; import io.grpc.ForwardingServerBuilder; import io.grpc.internal.FixedObjectPool; import io.grpc.internal.GrpcUtil; -import io.grpc.internal.InternalServer; import io.grpc.internal.ServerImplBuilder; -import io.grpc.internal.ServerImplBuilder.ClientTransportServersBuilder; import io.grpc.internal.ObjectPool; import io.grpc.internal.SharedResourcePool; import java.io.File; -import java.io.IOException; -import java.util.List; import java.util.concurrent.ScheduledExecutorService; -import javax.annotation.Nullable; /** * Builder for a server that services requests from an Android Service. */ -@ExperimentalApi("https://github.com/grpc/grpc-java/issues/8022") public final class BinderServerBuilder extends ForwardingServerBuilder { @@ -81,6 +71,7 @@ public static BinderServerBuilder forPort(int port) { SharedResourcePool.forResource(GrpcUtil.TIMER_SERVICE); private ServerSecurityPolicy securityPolicy; private InboundParcelablePolicy inboundParcelablePolicy; + private boolean isBuilt; private BinderServerBuilder( AndroidComponentAddress listenAddress, @@ -95,20 +86,13 @@ private BinderServerBuilder( streamTracerFactories, securityPolicy, inboundParcelablePolicy); - binderReceiver.set(server.getHostBinder()); + BinderInternal.setIBinder(binderReceiver, server.getHostBinder()); return server; }); - // Disable compression by default, since there's little benefit when all communication is - // on-device, and it means sending supported-encoding headers with every call. - decompressorRegistry(DecompressorRegistry.emptyInstance()); - compressorRegistry(CompressorRegistry.newEmptyInstance()); - // Disable stats and tracing by default. serverImplBuilder.setStatsEnabled(false); serverImplBuilder.setTracingEnabled(false); - - BinderTransportSecurity.installAuthInterceptor(this); } @Override @@ -117,12 +101,14 @@ protected ServerBuilder delegate() { } /** Enable stats collection using census. */ + @ExperimentalApi("https://github.com/grpc/grpc-java/issues/8022") public BinderServerBuilder enableStats() { serverImplBuilder.setStatsEnabled(true); return this; } /** Enable tracing using census. */ + @ExperimentalApi("https://github.com/grpc/grpc-java/issues/8022") public BinderServerBuilder enableTracing() { serverImplBuilder.setTracingEnabled(true); return this; @@ -157,12 +143,16 @@ public BinderServerBuilder securityPolicy(ServerSecurityPolicy securityPolicy) { } /** Sets the policy for inbound parcelable objects. */ + @ExperimentalApi("https://github.com/grpc/grpc-java/issues/8022") public BinderServerBuilder inboundParcelablePolicy( InboundParcelablePolicy inboundParcelablePolicy) { this.inboundParcelablePolicy = checkNotNull(inboundParcelablePolicy, "inboundParcelablePolicy"); return this; } + /** + * Always fails. TLS is not supported in BinderServer. + */ @Override public BinderServerBuilder useTransportSecurity(File certChain, File privateKey) { throw new UnsupportedOperationException("TLS not supported in BinderServer"); @@ -177,6 +167,11 @@ public BinderServerBuilder useTransportSecurity(File certChain, File privateKey) */ @Override // For javadoc refinement only. public Server build() { + // Since we install a final interceptor here, we need to ensure we're only built once. + checkState(!isBuilt, "BinderServerBuilder can only be used to build one server instance."); + isBuilt = true; + // We install the security interceptor last, so it's closest to the transport. + BinderTransportSecurity.installAuthInterceptor(this); return super.build(); } } diff --git a/binder/src/main/java/io/grpc/binder/IBinderReceiver.java b/binder/src/main/java/io/grpc/binder/IBinderReceiver.java index bd8e1f50af9..adf4a0d3d8e 100644 --- a/binder/src/main/java/io/grpc/binder/IBinderReceiver.java +++ b/binder/src/main/java/io/grpc/binder/IBinderReceiver.java @@ -17,24 +17,22 @@ package io.grpc.binder; import android.os.IBinder; -import io.grpc.ExperimentalApi; import javax.annotation.Nullable; /** A container for at most one instance of {@link IBinder}, useful as an "out parameter". */ -@ExperimentalApi("https://github.com/grpc/grpc-java/issues/8022") public final class IBinderReceiver { - @Nullable private IBinder value; + @Nullable private volatile IBinder value; /** Constructs a new, initially empty, container. */ public IBinderReceiver() {} /** Returns the contents of this container or null if it is empty. */ @Nullable - public synchronized IBinder get() { + public IBinder get() { return value; } - public synchronized void set(IBinder value) { + protected void set(IBinder value) { this.value = value; } } diff --git a/binder/src/main/java/io/grpc/binder/ParcelableUtils.java b/binder/src/main/java/io/grpc/binder/ParcelableUtils.java index 164de7de8b8..969344ea68d 100644 --- a/binder/src/main/java/io/grpc/binder/ParcelableUtils.java +++ b/binder/src/main/java/io/grpc/binder/ParcelableUtils.java @@ -17,7 +17,6 @@ package io.grpc.binder; import android.os.Parcelable; -import io.grpc.ExperimentalApi; import io.grpc.Metadata; import io.grpc.binder.internal.MetadataHelper; @@ -26,7 +25,6 @@ * *

This class models the same pattern as the {@code ProtoLiteUtils} class. */ -@ExperimentalApi("https://github.com/grpc/grpc-java/issues/8022") public final class ParcelableUtils { private ParcelableUtils() {} diff --git a/binder/src/main/java/io/grpc/binder/SecurityPolicies.java b/binder/src/main/java/io/grpc/binder/SecurityPolicies.java index 653ae90bd77..1a31ef823d3 100644 --- a/binder/src/main/java/io/grpc/binder/SecurityPolicies.java +++ b/binder/src/main/java/io/grpc/binder/SecurityPolicies.java @@ -42,7 +42,6 @@ /** Static factory methods for creating standard security policies. */ @CheckReturnValue -@ExperimentalApi("https://github.com/grpc/grpc-java/issues/8022") public final class SecurityPolicies { private static final int MY_UID = Process.myUid(); @@ -50,10 +49,15 @@ public final class SecurityPolicies { private SecurityPolicies() {} + @ExperimentalApi("https://github.com/grpc/grpc-java/issues/8022") public static ServerSecurityPolicy serverInternalOnly() { return new ServerSecurityPolicy(); } + /** + * Creates a default {@link SecurityPolicy} that allows access only to callers with the same UID + * as the current process. + */ public static SecurityPolicy internalOnly() { return new SecurityPolicy() { @Override @@ -66,6 +70,7 @@ public Status checkAuthorization(int uid) { }; } + @ExperimentalApi("https://github.com/grpc/grpc-java/issues/8022") public static SecurityPolicy permissionDenied(String description) { Status denied = Status.PERMISSION_DENIED.withDescription(description); return new SecurityPolicy() { @@ -84,6 +89,7 @@ public Status checkAuthorization(int uid) { * @param requiredSignature the allowed signature of the allowed package. * @throws NullPointerException if any of the inputs are {@code null}. */ + @ExperimentalApi("https://github.com/grpc/grpc-java/issues/8022") public static SecurityPolicy hasSignature( PackageManager packageManager, String packageName, Signature requiredSignature) { return oneOfSignatures( @@ -99,6 +105,7 @@ public static SecurityPolicy hasSignature( * @throws NullPointerException if any of the inputs are {@code null}. * @throws IllegalArgumentException if {@code requiredSignatureSha256Hash} is not of length 32. */ + @ExperimentalApi("https://github.com/grpc/grpc-java/issues/8022") public static SecurityPolicy hasSignatureSha256Hash( PackageManager packageManager, String packageName, byte[] requiredSignatureSha256Hash) { return oneOfSignatureSha256Hash( @@ -114,6 +121,7 @@ public static SecurityPolicy hasSignatureSha256Hash( * @throws NullPointerException if any of the inputs are {@code null}. * @throws IllegalArgumentException if {@code requiredSignatures} is empty. */ + @ExperimentalApi("https://github.com/grpc/grpc-java/issues/8022") public static SecurityPolicy oneOfSignatures( PackageManager packageManager, String packageName, diff --git a/binder/src/main/java/io/grpc/binder/SecurityPolicy.java b/binder/src/main/java/io/grpc/binder/SecurityPolicy.java index d13f3a863fd..6b0fb40310a 100644 --- a/binder/src/main/java/io/grpc/binder/SecurityPolicy.java +++ b/binder/src/main/java/io/grpc/binder/SecurityPolicy.java @@ -16,7 +16,6 @@ package io.grpc.binder; -import io.grpc.ExperimentalApi; import io.grpc.Status; import javax.annotation.CheckReturnValue; @@ -37,7 +36,6 @@ * re-installation of the applications involved. */ @CheckReturnValue -@ExperimentalApi("https://github.com/grpc/grpc-java/issues/8022") public abstract class SecurityPolicy { protected SecurityPolicy() {} diff --git a/binder/src/main/java/io/grpc/binder/ServerSecurityPolicy.java b/binder/src/main/java/io/grpc/binder/ServerSecurityPolicy.java index 46a124e1f47..d91a487a57c 100644 --- a/binder/src/main/java/io/grpc/binder/ServerSecurityPolicy.java +++ b/binder/src/main/java/io/grpc/binder/ServerSecurityPolicy.java @@ -17,7 +17,6 @@ package io.grpc.binder; import com.google.common.collect.ImmutableMap; -import io.grpc.ExperimentalApi; import io.grpc.Status; import java.util.HashMap; import java.util.Map; @@ -28,7 +27,6 @@ * * Contains a default policy, and optional policies for each server. */ -@ExperimentalApi("https://github.com/grpc/grpc-java/issues/8022") public final class ServerSecurityPolicy { private final SecurityPolicy defaultPolicy; diff --git a/binder/src/test/java/io/grpc/binder/BinderChannelBuilderTest.java b/binder/src/test/java/io/grpc/binder/BinderChannelBuilderTest.java new file mode 100644 index 00000000000..5dd7e13107e --- /dev/null +++ b/binder/src/test/java/io/grpc/binder/BinderChannelBuilderTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.binder; + +import static org.junit.Assert.fail; + +import android.content.Context; +import androidx.test.core.app.ApplicationProvider; +import java.util.concurrent.TimeUnit; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public final class BinderChannelBuilderTest { + private final Context appContext = ApplicationProvider.getApplicationContext(); + private final AndroidComponentAddress addr = AndroidComponentAddress.forContext(appContext); + + @Test + public void strictLifecycleManagementForbidsIdleTimers() { + BinderChannelBuilder builder = BinderChannelBuilder.forAddress(addr, appContext); + builder.strictLifecycleManagement(); + try { + builder.idleTimeout(10, TimeUnit.SECONDS); + fail(); + } catch (IllegalStateException ise) { + // Expected. + } + } +} diff --git a/build.gradle b/build.gradle index 21068ba522d..04ab724a2e3 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ subprojects { apply plugin: "net.ltgt.errorprone" group = "io.grpc" - version = "1.51.0-SNAPSHOT" // CURRENT_GRPC_VERSION + version = "1.52.0" // CURRENT_GRPC_VERSION repositories { maven { // The google mirror is less flaky than mavenCentral() diff --git a/census/src/main/java/io/grpc/census/CensusStatsModule.java b/census/src/main/java/io/grpc/census/CensusStatsModule.java index d05368f3ee5..03eaf73570d 100644 --- a/census/src/main/java/io/grpc/census/CensusStatsModule.java +++ b/census/src/main/java/io/grpc/census/CensusStatsModule.java @@ -751,10 +751,7 @@ public void streamClosed(Status status) { @Override public Context filterContext(Context context) { - if (!module.tagger.empty().equals(parentCtx)) { - return ContextUtils.withValue(context, parentCtx); - } - return context; + return ContextUtils.withValue(context, parentCtx); } } diff --git a/compiler/src/test/golden/TestDeprecatedService.java.txt b/compiler/src/test/golden/TestDeprecatedService.java.txt index 7c8fa57714d..8b541b7c3f0 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.51.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.52.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 d87bc631256..9ef5e4a0327 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.51.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.52.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 dcd6f4e4bd5..2a9f951e913 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.51.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.52.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 fdf75a9bf9a..d5495156c4d 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.51.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.52.0)", comments = "Source: grpc/testing/compiler/test.proto") @io.grpc.stub.annotations.GrpcGenerated public final class TestServiceGrpc { diff --git a/context/src/test/java/io/grpc/testing/DeadlineSubject.java b/context/src/test/java/io/grpc/testing/DeadlineSubject.java index 820f91248a1..5d4e86fac15 100644 --- a/context/src/test/java/io/grpc/testing/DeadlineSubject.java +++ b/context/src/test/java/io/grpc/testing/DeadlineSubject.java @@ -19,12 +19,12 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.Fact.fact; import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; import com.google.common.truth.ComparableSubject; import com.google.common.truth.FailureMetadata; import com.google.common.truth.Subject; import io.grpc.Deadline; -import java.math.BigInteger; import java.util.concurrent.TimeUnit; import javax.annotation.CheckReturnValue; import javax.annotation.Nullable; @@ -32,6 +32,7 @@ /** Propositions for {@link Deadline} subjects. */ @SuppressWarnings("rawtypes") // Generics in this class are going away in a subsequent Truth. public final class DeadlineSubject extends ComparableSubject { + public static final double NANOSECONDS_IN_A_SECOND = SECONDS.toNanos(1) * 1.0; private static final Subject.Factory deadlineFactory = new Factory(); @@ -60,14 +61,14 @@ public void of(Deadline expected) { checkNotNull(actual, "actual value cannot be null. expected=%s", expected); // This is probably overkill, but easier than thinking about overflow. - BigInteger actualTimeRemaining = BigInteger.valueOf(actual.timeRemaining(NANOSECONDS)); - BigInteger expectedTimeRemaining = BigInteger.valueOf(expected.timeRemaining(NANOSECONDS)); - BigInteger deltaNanos = BigInteger.valueOf(timeUnit.toNanos(delta)); - if (actualTimeRemaining.subtract(expectedTimeRemaining).abs().compareTo(deltaNanos) > 0) { + long actualNanos = actual.timeRemaining(NANOSECONDS); + long expectedNanos = expected.timeRemaining(NANOSECONDS); + long deltaNanos = timeUnit.toNanos(delta) ; + if (Math.abs(actualNanos - expectedNanos) > deltaNanos) { failWithoutActual( - fact("expected", expected), - fact("but was", actual), - fact("outside tolerance in ns", deltaNanos)); + fact("expected", expectedNanos / NANOSECONDS_IN_A_SECOND), + fact("but was", expectedNanos / NANOSECONDS_IN_A_SECOND), + fact("outside tolerance in seconds", deltaNanos / NANOSECONDS_IN_A_SECOND)); } } }; diff --git a/core/BUILD.bazel b/core/BUILD.bazel index 60a08798d58..3ca51e66c94 100644 --- a/core/BUILD.bazel +++ b/core/BUILD.bazel @@ -65,13 +65,12 @@ java_library( ) # Mirrors the dependencies included in the artifact on Maven Central for usage -# with maven_install's override_targets. Purposefully does not export any -# symbols, as it should only be used as a dep for pre-compiled binaries on -# Maven Central. +# with maven_install's override_targets. Should only be used as a dep for +# pre-compiled binaries on Maven Central. java_library( name = "core_maven", visibility = ["//visibility:public"], - runtime_deps = [ + exports = [ ":inprocess", ":internal", ":util", diff --git a/core/src/main/java/io/grpc/internal/ClientCallImpl.java b/core/src/main/java/io/grpc/internal/ClientCallImpl.java index 31286c2bd7b..000ede77057 100644 --- a/core/src/main/java/io/grpc/internal/ClientCallImpl.java +++ b/core/src/main/java/io/grpc/internal/ClientCallImpl.java @@ -72,6 +72,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(Charset.forName("US-ASCII")); + private static final double NANO_TO_SECS = 1.0 * TimeUnit.SECONDS.toNanos(1); private final MethodDescriptor method; private final Tag tag; @@ -259,10 +260,12 @@ public void runInContext() { } else { ClientStreamTracer[] tracers = GrpcUtil.getClientStreamTracers(callOptions, headers, 0, false); - stream = new FailingClientStream( - DEADLINE_EXCEEDED.withDescription( - "ClientCall started after deadline exceeded: " + effectiveDeadline), - tracers); + String deadlineName = + isFirstMin(callOptions.getDeadline(), context.getDeadline()) ? "CallOptions" : "Context"; + String description = String.format( + "ClientCall started after %s deadline was exceeded .9%f seconds ago", deadlineName, + effectiveDeadline.timeRemaining(TimeUnit.NANOSECONDS) / NANO_TO_SECS); + stream = new FailingClientStream(DEADLINE_EXCEEDED.withDescription(description), tracers); } if (callExecutorIsDirect) { @@ -431,6 +434,16 @@ private static Deadline min(@Nullable Deadline deadline0, @Nullable Deadline dea return deadline0.minimum(deadline1); } + private static boolean isFirstMin(@Nullable Deadline deadline0, @Nullable Deadline deadline1) { + if (deadline0 == null) { + return false; + } + if (deadline1 == null) { + return true; + } + return deadline0.isBefore(deadline1); + } + @Override public void request(int numMessages) { PerfMark.startTask("ClientCall.request", tag); diff --git a/core/src/main/java/io/grpc/internal/DelayedClientCall.java b/core/src/main/java/io/grpc/internal/DelayedClientCall.java index 42cedddd0e7..ee600e52d68 100644 --- a/core/src/main/java/io/grpc/internal/DelayedClientCall.java +++ b/core/src/main/java/io/grpc/internal/DelayedClientCall.java @@ -81,6 +81,17 @@ protected DelayedClientCall( initialDeadlineMonitor = scheduleDeadlineIfNeeded(scheduler, deadline); } + // If one argument is null, consider the other the "Before" + private boolean isAbeforeB(@Nullable Deadline a, @Nullable Deadline b) { + if (b == null) { + return true; + } else if (a == null) { + return false; + } + + return a.isBefore(b); + } + @Nullable private ScheduledFuture scheduleDeadlineIfNeeded( ScheduledExecutorService scheduler, @Nullable Deadline deadline) { @@ -90,8 +101,9 @@ private ScheduledFuture scheduleDeadlineIfNeeded( } long remainingNanos = Long.MAX_VALUE; if (deadline != null) { - remainingNanos = Math.min(remainingNanos, deadline.timeRemaining(NANOSECONDS)); + remainingNanos = deadline.timeRemaining(NANOSECONDS); } + if (contextDeadline != null && contextDeadline.timeRemaining(NANOSECONDS) < remainingNanos) { remainingNanos = contextDeadline.timeRemaining(NANOSECONDS); if (logger.isLoggable(Level.FINE)) { @@ -110,13 +122,19 @@ private ScheduledFuture scheduleDeadlineIfNeeded( logger.fine(builder.toString()); } } + long seconds = Math.abs(remainingNanos) / TimeUnit.SECONDS.toNanos(1); long nanos = Math.abs(remainingNanos) % TimeUnit.SECONDS.toNanos(1); final StringBuilder buf = new StringBuilder(); + String deadlineName = isAbeforeB(contextDeadline, deadline) ? "Context" : "CallOptions"; if (remainingNanos < 0) { - buf.append("ClientCall started after deadline exceeded. Deadline exceeded after -"); + buf.append("ClientCall started after "); + buf.append(deadlineName); + buf.append(" deadline was exceeded. Deadline has been exceeded for "); } else { - buf.append("Deadline exceeded after "); + buf.append("Deadline "); + buf.append(deadlineName); + buf.append(" will be exceeded in "); } buf.append(seconds); buf.append(String.format(Locale.US, ".%09d", nanos)); diff --git a/core/src/main/java/io/grpc/internal/GrpcUtil.java b/core/src/main/java/io/grpc/internal/GrpcUtil.java index 0159a0b5275..3d3677eb134 100644 --- a/core/src/main/java/io/grpc/internal/GrpcUtil.java +++ b/core/src/main/java/io/grpc/internal/GrpcUtil.java @@ -217,7 +217,7 @@ public byte[] parseAsciiString(byte[] serialized) { public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults(); - private static final String IMPLEMENTATION_VERSION = "1.51.0-SNAPSHOT"; // CURRENT_GRPC_VERSION + private static final String IMPLEMENTATION_VERSION = "1.52.0"; // CURRENT_GRPC_VERSION /** * The default timeout in nanos for a keepalive ping request. diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java index 9cc7d70d1e7..0311a6d2e34 100644 --- a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java +++ b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java @@ -1942,13 +1942,14 @@ private final class SubchannelImpl extends AbstractSubchannel { ScheduledHandle delayedShutdownTask; SubchannelImpl(CreateSubchannelArgs args, LbHelperImpl helper) { + checkNotNull(args, "args"); addressGroups = args.getAddresses(); if (authorityOverride != null) { List eagsWithoutOverrideAttr = stripOverrideAuthorityAttributes(args.getAddresses()); args = args.toBuilder().setAddresses(eagsWithoutOverrideAttr).build(); } - this.args = checkNotNull(args, "args"); + this.args = args; this.helper = checkNotNull(helper, "helper"); subchannelLogId = InternalLogId.allocate("Subchannel", /*details=*/ authority()); subchannelTracer = new ChannelTracer( diff --git a/core/src/main/java/io/grpc/internal/RetriableStream.java b/core/src/main/java/io/grpc/internal/RetriableStream.java index cb94195cce1..46da45aebd4 100644 --- a/core/src/main/java/io/grpc/internal/RetriableStream.java +++ b/core/src/main/java/io/grpc/internal/RetriableStream.java @@ -107,6 +107,8 @@ public void uncaughtException(Thread t, Throwable e) { */ private final AtomicBoolean noMoreTransparentRetry = new AtomicBoolean(); private final AtomicInteger localOnlyTransparentRetries = new AtomicInteger(); + private final AtomicInteger inFlightSubStreams = new AtomicInteger(); + private Status savedCancellationReason; // 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. @@ -220,7 +222,16 @@ private void commitAndRun(Substream winningSubstream) { } } + @Nullable // returns null when cancelled private Substream createSubstream(int previousAttemptCount, boolean isTransparentRetry) { + // increment only when >= 0, i.e. not cancelled + int inFlight; + do { + inFlight = inFlightSubStreams.get(); + if (inFlight < 0) { + return null; + } + } while (!inFlightSubStreams.compareAndSet(inFlight, inFlight + 1)); Substream sub = new Substream(previousAttemptCount); // one tracer per substream final ClientStreamTracer bufferSizeTracer = new BufferSizeTracer(sub); @@ -367,6 +378,9 @@ public final void start(ClientStreamListener listener) { } Substream substream = createSubstream(0, false); + if (substream == null) { + return; + } if (isHedging) { FutureCanceller scheduledHedgingRef = null; @@ -434,16 +448,19 @@ private final class HedgingRunnable implements Runnable { @Override public void run() { + // It's safe to read state.hedgingAttemptCount here. + // If this run is not cancelled, the value of state.hedgingAttemptCount won't change + // until state.addActiveHedge() is called subsequently, even the state could possibly + // change. + Substream newSubstream = createSubstream(state.hedgingAttemptCount, false); + if (newSubstream == null) { + return; + } callExecutor.execute( new Runnable() { @SuppressWarnings("GuardedBy") @Override public void run() { - // It's safe to read state.hedgingAttemptCount here. - // If this run is not cancelled, the value of state.hedgingAttemptCount won't change - // until state.addActiveHedge() is called subsequently, even the state could possibly - // change. - Substream newSubstream = createSubstream(state.hedgingAttemptCount, false); boolean cancelled = false; FutureCanceller future = null; @@ -489,16 +506,11 @@ public final void cancel(final Status reason) { Runnable runnable = commit(noopSubstream); if (runnable != null) { + savedCancellationReason = reason; runnable.run(); - listenerSerializeExecutor.execute( - new Runnable() { - @Override - public void run() { - isClosed = true; - masterListener.closed(reason, RpcProgress.PROCESSED, new Metadata()); - - } - }); + if (inFlightSubStreams.addAndGet(Integer.MIN_VALUE) == Integer.MIN_VALUE) { + safeCloseMasterListener(reason, RpcProgress.PROCESSED, new Metadata()); + } return; } @@ -803,6 +815,17 @@ private void freezeHedging() { } } + private void safeCloseMasterListener(Status status, RpcProgress progress, Metadata metadata) { + listenerSerializeExecutor.execute( + new Runnable() { + @Override + public void run() { + isClosed = true; + masterListener.closed(status, progress, metadata); + } + }); + } + private interface BufferEntry { /** Replays the buffer entry with the given stream. */ void runWith(Substream substream); @@ -840,19 +863,18 @@ public void closed( closedSubstreamsInsight.append(status.getCode()); } + if (inFlightSubStreams.decrementAndGet() == Integer.MIN_VALUE) { + assert savedCancellationReason != null; + safeCloseMasterListener(savedCancellationReason, RpcProgress.PROCESSED, new Metadata()); + return; + } + // handle a race between buffer limit exceeded and closed, when setting // substream.bufferLimitExceeded = true happens before state.substreamClosed(substream). if (substream.bufferLimitExceeded) { commitAndRun(substream); if (state.winningSubstream == substream) { - listenerSerializeExecutor.execute( - new Runnable() { - @Override - public void run() { - isClosed = true; - masterListener.closed(status, rpcProgress, trailers); - } - }); + safeCloseMasterListener(status, rpcProgress, trailers); } return; } @@ -863,14 +885,7 @@ public void run() { 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); - } - }); + safeCloseMasterListener(tooManyTransparentRetries, rpcProgress, trailers); } return; } @@ -881,6 +896,9 @@ public void run() { && noMoreTransparentRetry.compareAndSet(false, true))) { // transparent retry final Substream newSubstream = createSubstream(substream.previousAttemptCount, true); + if (newSubstream == null) { + return; + } if (isHedging) { boolean commit = false; synchronized (lock) { @@ -942,6 +960,11 @@ public void run() { } else { RetryPlan retryPlan = makeRetryDecision(status, trailers); if (retryPlan.shouldRetry) { + // retry + Substream newSubstream = createSubstream(substream.previousAttemptCount + 1, false); + if (newSubstream == null) { + return; + } // The check state.winningSubstream == null, checking if is not already committed, is // racy, but is still safe b/c the retry will also handle committed/cancellation FutureCanceller scheduledRetryCopy; @@ -955,10 +978,6 @@ public void run() { new Runnable() { @Override public void run() { - // retry - Substream newSubstream = createSubstream( - substream.previousAttemptCount + 1, - false); drain(newSubstream); } }); @@ -978,14 +997,7 @@ public void run() { commitAndRun(substream); if (state.winningSubstream == substream) { - listenerSerializeExecutor.execute( - new Runnable() { - @Override - public void run() { - isClosed = true; - masterListener.closed(status, rpcProgress, trailers); - } - }); + safeCloseMasterListener(status, rpcProgress, trailers); } } diff --git a/core/src/test/java/io/grpc/internal/ClientCallImplTest.java b/core/src/test/java/io/grpc/internal/ClientCallImplTest.java index e409f2f9df4..687a89abdd8 100644 --- a/core/src/test/java/io/grpc/internal/ClientCallImplTest.java +++ b/core/src/test/java/io/grpc/internal/ClientCallImplTest.java @@ -786,7 +786,7 @@ public void deadlineExceededBeforeCallStarted() { verify(callListener, timeout(1000)).onClose(statusCaptor.capture(), any(Metadata.class)); assertEquals(Status.Code.DEADLINE_EXCEEDED, statusCaptor.getValue().getCode()); assertThat(statusCaptor.getValue().getDescription()) - .startsWith("ClientCall started after deadline exceeded"); + .startsWith("ClientCall started after CallOptions deadline was exceeded"); verifyNoInteractions(clientStreamProvider); } diff --git a/core/src/test/java/io/grpc/internal/RetriableStreamTest.java b/core/src/test/java/io/grpc/internal/RetriableStreamTest.java index f20e772e92b..12bf697027c 100644 --- a/core/src/test/java/io/grpc/internal/RetriableStreamTest.java +++ b/core/src/test/java/io/grpc/internal/RetriableStreamTest.java @@ -283,6 +283,7 @@ public Void answer(InvocationOnMock in) { doReturn(mockStream2).when(retriableStreamRecorder).newSubstream(1); sublistenerCaptor1.getValue().closed( Status.fromCode(RETRIABLE_STATUS_CODE_2), PROCESSED, new Metadata()); + inOrder.verify(retriableStreamRecorder).newSubstream(1); assertEquals(1, fakeClock.numPendingTasks()); // send more messages during backoff @@ -294,7 +295,6 @@ public Void answer(InvocationOnMock in) { assertEquals(1, fakeClock.numPendingTasks()); fakeClock.forwardTime(1L, TimeUnit.SECONDS); assertEquals(0, fakeClock.numPendingTasks()); - inOrder.verify(retriableStreamRecorder).newSubstream(1); inOrder.verify(mockStream2).setAuthority(AUTHORITY); inOrder.verify(mockStream2).setCompressor(COMPRESSOR); inOrder.verify(mockStream2).setDecompressorRegistry(DECOMPRESSOR_REGISTRY); @@ -339,6 +339,7 @@ public Void answer(InvocationOnMock in) { doReturn(mockStream3).when(retriableStreamRecorder).newSubstream(2); sublistenerCaptor2.getValue().closed( Status.fromCode(RETRIABLE_STATUS_CODE_1), PROCESSED, new Metadata()); + inOrder.verify(retriableStreamRecorder).newSubstream(2); assertEquals(1, fakeClock.numPendingTasks()); // send more messages during backoff @@ -353,7 +354,6 @@ public Void answer(InvocationOnMock in) { assertEquals(1, fakeClock.numPendingTasks()); fakeClock.forwardTime(1L, TimeUnit.SECONDS); assertEquals(0, fakeClock.numPendingTasks()); - inOrder.verify(retriableStreamRecorder).newSubstream(2); inOrder.verify(mockStream3).setAuthority(AUTHORITY); inOrder.verify(mockStream3).setCompressor(COMPRESSOR); inOrder.verify(mockStream3).setDecompressorRegistry(DECOMPRESSOR_REGISTRY); @@ -792,6 +792,8 @@ public boolean isReady() { public void cancelWhileDraining() { ArgumentCaptor sublistenerCaptor1 = ArgumentCaptor.forClass(ClientStreamListener.class); + ArgumentCaptor sublistenerCaptor2 = + ArgumentCaptor.forClass(ClientStreamListener.class); ClientStream mockStream1 = mock(ClientStream.class); ClientStream mockStream2 = mock( @@ -818,7 +820,7 @@ public void request(int numMessages) { Status.fromCode(RETRIABLE_STATUS_CODE_1), PROCESSED, new Metadata()); fakeClock.forwardTime((long) (INITIAL_BACKOFF_IN_SECONDS * FAKE_RANDOM), TimeUnit.SECONDS); - inOrder.verify(mockStream2).start(any(ClientStreamListener.class)); + inOrder.verify(mockStream2).start(sublistenerCaptor2.capture()); inOrder.verify(mockStream2).request(3); inOrder.verify(retriableStreamRecorder).postCommit(); ArgumentCaptor statusCaptor = ArgumentCaptor.forClass(Status.class); @@ -826,6 +828,7 @@ public void request(int numMessages) { assertThat(statusCaptor.getValue().getCode()).isEqualTo(Code.CANCELLED); assertThat(statusCaptor.getValue().getDescription()) .isEqualTo("Stream thrown away because RetriableStream committed"); + sublistenerCaptor2.getValue().closed(Status.CANCELLED, PROCESSED, new Metadata()); verify(masterListener).closed( statusCaptor.capture(), any(RpcProgress.class), any(Metadata.class)); assertThat(statusCaptor.getValue().getCode()).isEqualTo(Code.CANCELLED); @@ -848,6 +851,8 @@ public void start(ClientStreamListener listener) { Status.CANCELLED.withDescription("cancelled while retry start")); } })); + ArgumentCaptor sublistenerCaptor2 = + ArgumentCaptor.forClass(ClientStreamListener.class); InOrder inOrder = inOrder(retriableStreamRecorder, mockStream1, mockStream2); doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0); @@ -860,13 +865,14 @@ public void start(ClientStreamListener listener) { Status.fromCode(RETRIABLE_STATUS_CODE_1), PROCESSED, new Metadata()); fakeClock.forwardTime((long) (INITIAL_BACKOFF_IN_SECONDS * FAKE_RANDOM), TimeUnit.SECONDS); - inOrder.verify(mockStream2).start(any(ClientStreamListener.class)); + inOrder.verify(mockStream2).start(sublistenerCaptor2.capture()); inOrder.verify(retriableStreamRecorder).postCommit(); ArgumentCaptor statusCaptor = ArgumentCaptor.forClass(Status.class); inOrder.verify(mockStream2).cancel(statusCaptor.capture()); assertThat(statusCaptor.getValue().getCode()).isEqualTo(Code.CANCELLED); assertThat(statusCaptor.getValue().getDescription()) .isEqualTo("Stream thrown away because RetriableStream committed"); + sublistenerCaptor2.getValue().closed(Status.CANCELLED, PROCESSED, new Metadata()); verify(masterListener).closed( statusCaptor.capture(), any(RpcProgress.class), any(Metadata.class)); assertThat(statusCaptor.getValue().getCode()).isEqualTo(Code.CANCELLED); @@ -1121,7 +1127,6 @@ public void perRpcBufferLimitExceededDuringBackoff() { sublistenerCaptor1.getValue().closed( Status.fromCode(RETRIABLE_STATUS_CODE_1), PROCESSED, new Metadata()); - // bufferSizeTracer.outboundWireSize() quits immediately while backoff b/c substream1 is closed assertEquals(1, fakeClock.numPendingTasks()); bufferSizeTracer.outboundWireSize(2); verify(retriableStreamRecorder, never()).postCommit(); @@ -1132,8 +1137,6 @@ public void perRpcBufferLimitExceededDuringBackoff() { // bufferLimitExceeded bufferSizeTracer.outboundWireSize(PER_RPC_BUFFER_LIMIT - 1); - verify(retriableStreamRecorder, never()).postCommit(); - bufferSizeTracer.outboundWireSize(2); verify(retriableStreamRecorder).postCommit(); verifyNoMoreInteractions(mockStream1); @@ -2464,6 +2467,8 @@ public void hedging_cancelled() { assertEquals(CANCELLED_BECAUSE_COMMITTED, statusCaptor.getValue().getDescription()); inOrder.verify(retriableStreamRecorder).postCommit(); + sublistenerCaptor1.getValue().closed(Status.CANCELLED, PROCESSED, new Metadata()); + sublistenerCaptor2.getValue().closed(Status.CANCELLED, PROCESSED, new Metadata()); inOrder.verify(masterListener).closed( any(Status.class), any(RpcProgress.class), any(Metadata.class)); inOrder.verifyNoMoreInteractions(); diff --git a/cronet/README.md b/cronet/README.md index e871a64ffbe..1b4be783faf 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.50.2' +implementation 'io.grpc:grpc-cronet:1.52.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 46dd67504f2..24c1e3890d8 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.50.2' -implementation 'io.grpc:grpc-okhttp:1.50.2' +implementation 'io.grpc:grpc-android:1.52.0' +implementation 'io.grpc:grpc-okhttp:1.52.0' ``` You also need permission to access the device's network state in your diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel index e7f00381ad1..2a5ed52b35c 100644 --- a/examples/BUILD.bazel +++ b/examples/BUILD.bazel @@ -135,3 +135,40 @@ java_binary( ":examples", ], ) + +java_binary( + name = "load-balance-client", + testonly = 1, + main_class = "io.grpc.examples.loadbalance.LoadBalanceClient", + runtime_deps = [ + ":examples", + ], +) + +java_binary( + name = "load-balance-server", + testonly = 1, + main_class = "io.grpc.examples.loadbalance.LoadBalanceServer", + runtime_deps = [ + ":examples", + ], +) + +java_binary( + name = "name-resolve-client", + testonly = 1, + main_class = "io.grpc.examples.nameresolve.NameResolveClient", + runtime_deps = [ + ":examples", + ], +) + +java_binary( + name = "name-resolve-server", + testonly = 1, + main_class = "io.grpc.examples.nameresolve.NameResolveServer", + runtime_deps = [ + ":examples", + ], +) + diff --git a/examples/android/clientcache/app/build.gradle b/examples/android/clientcache/app/build.gradle index 827609afe8d..502b34a8310 100644 --- a/examples/android/clientcache/app/build.gradle +++ b/examples/android/clientcache/app/build.gradle @@ -34,7 +34,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.21.7' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.51.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.52.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.51.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.51.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.51.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.52.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.52.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.52.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.51.0-SNAPSHOT' // CURRENT_GRPC_VERSION + testImplementation 'io.grpc:grpc-testing:1.52.0' // CURRENT_GRPC_VERSION } diff --git a/examples/android/clientcache/build.gradle b/examples/android/clientcache/build.gradle index 8a94a30191e..d033a64046d 100644 --- a/examples/android/clientcache/build.gradle +++ b/examples/android/clientcache/build.gradle @@ -7,7 +7,7 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:4.2.0' - classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.18" + classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.19" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/examples/android/helloworld/app/build.gradle b/examples/android/helloworld/app/build.gradle index 94e86df7f30..93adc585e6b 100644 --- a/examples/android/helloworld/app/build.gradle +++ b/examples/android/helloworld/app/build.gradle @@ -32,7 +32,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.21.7' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.51.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.52.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.51.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.51.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.51.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.52.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.52.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.52.0' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/android/helloworld/build.gradle b/examples/android/helloworld/build.gradle index 8a94a30191e..d033a64046d 100644 --- a/examples/android/helloworld/build.gradle +++ b/examples/android/helloworld/build.gradle @@ -7,7 +7,7 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:4.2.0' - classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.18" + classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.19" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/examples/android/routeguide/app/build.gradle b/examples/android/routeguide/app/build.gradle index 953a14c2a73..91c8ad658c7 100644 --- a/examples/android/routeguide/app/build.gradle +++ b/examples/android/routeguide/app/build.gradle @@ -32,7 +32,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.21.7' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.51.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.52.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.51.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.51.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.51.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.52.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.52.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.52.0' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/android/routeguide/build.gradle b/examples/android/routeguide/build.gradle index b1083bb867a..7a37b9c0648 100644 --- a/examples/android/routeguide/build.gradle +++ b/examples/android/routeguide/build.gradle @@ -7,7 +7,7 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:4.2.0' - classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.18" + classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.19" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/examples/android/strictmode/app/build.gradle b/examples/android/strictmode/app/build.gradle index fa70153bcb4..e77f7c8fdbe 100644 --- a/examples/android/strictmode/app/build.gradle +++ b/examples/android/strictmode/app/build.gradle @@ -33,7 +33,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.21.7' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.51.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.52.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.51.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.51.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.51.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.52.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.52.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.52.0' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/android/strictmode/build.gradle b/examples/android/strictmode/build.gradle index 8a94a30191e..d033a64046d 100644 --- a/examples/android/strictmode/build.gradle +++ b/examples/android/strictmode/build.gradle @@ -7,7 +7,7 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:4.2.0' - classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.18" + classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.19" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/examples/build.gradle b/examples/build.gradle index 89e066d0680..4b80e67f607 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -2,7 +2,7 @@ plugins { // Provide convenience executables for trying out the examples. id 'application' // ASSUMES GRADLE 5.6 OR HIGHER. Use plugin version 0.8.10 with earlier gradle versions - id 'com.google.protobuf' version '0.8.18' + id 'com.google.protobuf' version '0.8.19' // Generate IntelliJ IDEA's .idea & .iml project files id 'idea' } @@ -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.51.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.52.0' // CURRENT_GRPC_VERSION def protobufVersion = '3.21.7' def protocVersion = protobufVersion @@ -140,6 +140,34 @@ task manualFlowControlServer(type: CreateStartScripts) { classpath = startScripts.classpath } +task loadBalanceServer(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.loadbalance.LoadBalanceServer' + applicationName = 'load-balance-server' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + +task loadBalanceClient(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.loadbalance.LoadBalanceClient' + applicationName = 'load-balance-client' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + +task nameResolveServer(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.nameresolve.NameResolveServer' + applicationName = 'name-resolve-server' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + +task nameResolveClient(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.nameresolve.NameResolveClient' + applicationName = 'name-resolve-client' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + applicationDistribution.into('bin') { from(routeGuideServer) from(routeGuideClient) @@ -152,5 +180,9 @@ applicationDistribution.into('bin') { from(compressingHelloWorldClient) from(manualFlowControlClient) from(manualFlowControlServer) + from(loadBalanceServer) + from(loadBalanceClient) + from(nameResolveServer) + from(nameResolveClient) fileMode = 0755 } diff --git a/examples/example-alts/build.gradle b/examples/example-alts/build.gradle index eb8fa418068..0fc499d3d99 100644 --- a/examples/example-alts/build.gradle +++ b/examples/example-alts/build.gradle @@ -2,7 +2,7 @@ plugins { // Provide convenience executables for trying out the examples. id 'application' // ASSUMES GRADLE 5.6 OR HIGHER. Use plugin version 0.8.10 with earlier gradle versions - id 'com.google.protobuf' version '0.8.17' + id 'com.google.protobuf' version '0.8.19' // Generate IntelliJ IDEA's .idea & .iml project files id 'idea' } @@ -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.51.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.52.0' // CURRENT_GRPC_VERSION def protocVersion = '3.21.7' dependencies { diff --git a/examples/example-alts/src/main/java/io/grpc/examples/alts/HelloWorldAltsClient.java b/examples/example-alts/src/main/java/io/grpc/examples/alts/HelloWorldAltsClient.java index 96351808b25..991ad777309 100644 --- a/examples/example-alts/src/main/java/io/grpc/examples/alts/HelloWorldAltsClient.java +++ b/examples/example-alts/src/main/java/io/grpc/examples/alts/HelloWorldAltsClient.java @@ -16,7 +16,8 @@ package io.grpc.examples.alts; -import io.grpc.alts.AltsChannelBuilder; +import io.grpc.alts.AltsChannelCredentials; +import io.grpc.Grpc; import io.grpc.ManagedChannel; import io.grpc.examples.helloworld.GreeterGrpc; import io.grpc.examples.helloworld.HelloReply; @@ -81,7 +82,8 @@ private void parseArgs(String[] args) { private void run(String[] args) throws InterruptedException { parseArgs(args); ExecutorService executor = Executors.newFixedThreadPool(1); - ManagedChannel channel = AltsChannelBuilder.forTarget(serverAddress).executor(executor).build(); + ManagedChannel channel = Grpc.newChannelBuilder(serverAddress, AltsChannelCredentials.create()) + .executor(executor).build(); try { GreeterGrpc.GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel); HelloReply resp = stub.sayHello(HelloRequest.newBuilder().setName("Waldo").build()); diff --git a/examples/example-alts/src/main/java/io/grpc/examples/alts/HelloWorldAltsServer.java b/examples/example-alts/src/main/java/io/grpc/examples/alts/HelloWorldAltsServer.java index fd662069cfd..6bc5226bf59 100644 --- a/examples/example-alts/src/main/java/io/grpc/examples/alts/HelloWorldAltsServer.java +++ b/examples/example-alts/src/main/java/io/grpc/examples/alts/HelloWorldAltsServer.java @@ -16,7 +16,8 @@ package io.grpc.examples.alts; -import io.grpc.alts.AltsServerBuilder; +import io.grpc.alts.AltsServerCredentials; +import io.grpc.Grpc; import io.grpc.Server; import io.grpc.examples.helloworld.GreeterGrpc.GreeterImplBase; import io.grpc.examples.helloworld.HelloReply; @@ -82,7 +83,7 @@ private void parseArgs(String[] args) { private void start(String[] args) throws IOException, InterruptedException { parseArgs(args); server = - AltsServerBuilder.forPort(port) + Grpc.newServerBuilderForPort(port, AltsServerCredentials.create()) .addService(this) .executor(Executors.newFixedThreadPool(1)) .build(); diff --git a/examples/example-gauth/build.gradle b/examples/example-gauth/build.gradle index 448ec374fdf..2da074f4f38 100644 --- a/examples/example-gauth/build.gradle +++ b/examples/example-gauth/build.gradle @@ -2,7 +2,7 @@ plugins { // Provide convenience executables for trying out the examples. id 'application' // ASSUMES GRADLE 5.6 OR HIGHER. Use plugin version 0.8.10 with earlier gradle versions - id 'com.google.protobuf' version '0.8.17' + id 'com.google.protobuf' version '0.8.19' // Generate IntelliJ IDEA's .idea & .iml project files id 'idea' } @@ -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.51.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.52.0' // CURRENT_GRPC_VERSION def protobufVersion = '3.21.7' def protocVersion = protobufVersion diff --git a/examples/example-gauth/pom.xml b/examples/example-gauth/pom.xml index 9f36e0e98b2..2cd72aa6ef7 100644 --- a/examples/example-gauth/pom.xml +++ b/examples/example-gauth/pom.xml @@ -6,13 +6,13 @@ jar - 1.51.0-SNAPSHOT + 1.52.0 example-gauth https://github.com/grpc/grpc-java UTF-8 - 1.51.0-SNAPSHOT + 1.52.0 3.21.7 1.7 diff --git a/examples/example-hostname/build.gradle b/examples/example-hostname/build.gradle index c3051b99264..f02b30af04e 100644 --- a/examples/example-hostname/build.gradle +++ b/examples/example-hostname/build.gradle @@ -2,7 +2,7 @@ plugins { id 'application' // Provide convenience executables for trying out the examples. id 'java' - id "com.google.protobuf" version "0.8.17" + id "com.google.protobuf" version "0.8.19" id 'com.google.cloud.tools.jib' version '3.1.4' // For releasing to Docker Hub } @@ -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.51.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.52.0' // CURRENT_GRPC_VERSION def protobufVersion = '3.21.7' dependencies { diff --git a/examples/example-hostname/pom.xml b/examples/example-hostname/pom.xml index e111c244f6f..b6384fad254 100644 --- a/examples/example-hostname/pom.xml +++ b/examples/example-hostname/pom.xml @@ -6,13 +6,13 @@ jar - 1.51.0-SNAPSHOT + 1.52.0 example-hostname https://github.com/grpc/grpc-java UTF-8 - 1.51.0-SNAPSHOT + 1.52.0 3.21.7 1.7 diff --git a/examples/example-hostname/src/main/java/io/grpc/examples/hostname/HostnameServer.java b/examples/example-hostname/src/main/java/io/grpc/examples/hostname/HostnameServer.java index a6f2175914e..3c63296d7fa 100644 --- a/examples/example-hostname/src/main/java/io/grpc/examples/hostname/HostnameServer.java +++ b/examples/example-hostname/src/main/java/io/grpc/examples/hostname/HostnameServer.java @@ -16,6 +16,8 @@ package io.grpc.examples.hostname; +import io.grpc.Grpc; +import io.grpc.InsecureServerCredentials; import io.grpc.Server; import io.grpc.ServerBuilder; import io.grpc.health.v1.HealthCheckResponse.ServingStatus; @@ -49,7 +51,7 @@ public static void main(String[] args) throws IOException, InterruptedException hostname = args[1]; } HealthStatusManager health = new HealthStatusManager(); - final Server server = ServerBuilder.forPort(port) + final Server server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()) .addService(new HostnameGreeter(hostname)) .addService(ProtoReflectionService.newInstance()) .addService(health.getHealthService()) diff --git a/examples/example-jwt-auth/build.gradle b/examples/example-jwt-auth/build.gradle index 968ad9ca385..f2373fa4b65 100644 --- a/examples/example-jwt-auth/build.gradle +++ b/examples/example-jwt-auth/build.gradle @@ -2,7 +2,7 @@ plugins { // Provide convenience executables for trying out the examples. id 'application' // ASSUMES GRADLE 5.6 OR HIGHER. Use plugin version 0.8.10 with earlier gradle versions - id 'com.google.protobuf' version '0.8.17' + id 'com.google.protobuf' version '0.8.19' // Generate IntelliJ IDEA's .idea & .iml project files id 'idea' } @@ -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.51.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.52.0' // CURRENT_GRPC_VERSION def protobufVersion = '3.21.7' def protocVersion = protobufVersion diff --git a/examples/example-jwt-auth/pom.xml b/examples/example-jwt-auth/pom.xml index d6223b1e0d0..4b3abec4ec2 100644 --- a/examples/example-jwt-auth/pom.xml +++ b/examples/example-jwt-auth/pom.xml @@ -7,13 +7,13 @@ jar - 1.51.0-SNAPSHOT + 1.52.0 example-jwt-auth https://github.com/grpc/grpc-java UTF-8 - 1.51.0-SNAPSHOT + 1.52.0 3.21.7 3.21.7 diff --git a/examples/example-jwt-auth/src/main/java/io/grpc/examples/jwtauth/AuthClient.java b/examples/example-jwt-auth/src/main/java/io/grpc/examples/jwtauth/AuthClient.java index f6ea4c57e45..c5769625807 100644 --- a/examples/example-jwt-auth/src/main/java/io/grpc/examples/jwtauth/AuthClient.java +++ b/examples/example-jwt-auth/src/main/java/io/grpc/examples/jwtauth/AuthClient.java @@ -17,8 +17,9 @@ package io.grpc.examples.jwtauth; import io.grpc.CallCredentials; +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; import io.grpc.ManagedChannel; -import io.grpc.ManagedChannelBuilder; import io.grpc.examples.helloworld.GreeterGrpc; import io.grpc.examples.helloworld.HelloReply; import io.grpc.examples.helloworld.HelloRequest; @@ -42,12 +43,9 @@ public class AuthClient { AuthClient(CallCredentials callCredentials, String host, int port) { this( callCredentials, - ManagedChannelBuilder - .forAddress(host, port) - // Channels are secure by default (via SSL/TLS). For this example we disable TLS - // to avoid needing certificates, but it is recommended to use a secure channel - // while passing credentials. - .usePlaintext() + // For this example we use plaintext to avoid needing certificates, but it is + // recommended to use TlsChannelCredentials. + Grpc.newChannelBuilderForAddress(host, port, InsecureChannelCredentials.create()) .build()); } diff --git a/examples/example-jwt-auth/src/main/java/io/grpc/examples/jwtauth/AuthServer.java b/examples/example-jwt-auth/src/main/java/io/grpc/examples/jwtauth/AuthServer.java index 90e7dff1458..208645e4fad 100644 --- a/examples/example-jwt-auth/src/main/java/io/grpc/examples/jwtauth/AuthServer.java +++ b/examples/example-jwt-auth/src/main/java/io/grpc/examples/jwtauth/AuthServer.java @@ -16,8 +16,9 @@ package io.grpc.examples.jwtauth; +import io.grpc.Grpc; +import io.grpc.InsecureServerCredentials; import io.grpc.Server; -import io.grpc.ServerBuilder; import io.grpc.examples.helloworld.GreeterGrpc; import io.grpc.examples.helloworld.HelloReply; import io.grpc.examples.helloworld.HelloRequest; @@ -41,7 +42,7 @@ public AuthServer(int port) { } private void start() throws IOException { - server = ServerBuilder.forPort(port) + server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()) .addService(new GreeterImpl()) .intercept(new JwtServerInterceptor()) // add the JwtServerInterceptor .build() diff --git a/examples/example-orca/build.gradle b/examples/example-orca/build.gradle index ba2acf222bf..8b247c4b37f 100644 --- a/examples/example-orca/build.gradle +++ b/examples/example-orca/build.gradle @@ -1,7 +1,7 @@ plugins { id 'application' // Provide convenience executables for trying out the examples. // ASSUMES GRADLE 5.6 OR HIGHER. Use plugin version 0.8.10 with earlier gradle versions - id 'com.google.protobuf' version '0.8.17' + id 'com.google.protobuf' version '0.8.19' // Generate IntelliJ IDEA's .idea & .iml project files id 'idea' id 'java' @@ -17,7 +17,7 @@ repositories { sourceCompatibility = 1.8 targetCompatibility = 1.8 -def grpcVersion = '1.51.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.52.0' // CURRENT_GRPC_VERSION def protocVersion = '3.21.7' dependencies { diff --git a/examples/example-orca/src/main/java/io/grpc/examples/orca/CustomBackendMetricsClient.java b/examples/example-orca/src/main/java/io/grpc/examples/orca/CustomBackendMetricsClient.java index da231bd1f16..66143b44364 100644 --- a/examples/example-orca/src/main/java/io/grpc/examples/orca/CustomBackendMetricsClient.java +++ b/examples/example-orca/src/main/java/io/grpc/examples/orca/CustomBackendMetricsClient.java @@ -19,9 +19,10 @@ import static io.grpc.examples.orca.CustomBackendMetricsLoadBalancerProvider.EXAMPLE_LOAD_BALANCER; import io.grpc.Channel; +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; import io.grpc.LoadBalancerRegistry; import io.grpc.ManagedChannel; -import io.grpc.ManagedChannelBuilder; import io.grpc.StatusRuntimeException; import io.grpc.examples.helloworld.GreeterGrpc; import io.grpc.examples.helloworld.HelloReply; @@ -91,9 +92,8 @@ public static void main(String[] args) throws Exception { LoadBalancerRegistry.getDefaultRegistry().register( new CustomBackendMetricsLoadBalancerProvider()); - ManagedChannel channel = ManagedChannelBuilder.forTarget(target) + ManagedChannel channel = Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()) .defaultLoadBalancingPolicy(EXAMPLE_LOAD_BALANCER) - .usePlaintext() .build(); try { CustomBackendMetricsClient client = new CustomBackendMetricsClient(channel); diff --git a/examples/example-orca/src/main/java/io/grpc/examples/orca/CustomBackendMetricsServer.java b/examples/example-orca/src/main/java/io/grpc/examples/orca/CustomBackendMetricsServer.java index da76b3b5b2c..b04664da363 100644 --- a/examples/example-orca/src/main/java/io/grpc/examples/orca/CustomBackendMetricsServer.java +++ b/examples/example-orca/src/main/java/io/grpc/examples/orca/CustomBackendMetricsServer.java @@ -21,8 +21,9 @@ import io.grpc.examples.helloworld.GreeterGrpc; import io.grpc.examples.helloworld.HelloReply; import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.Grpc; +import io.grpc.InsecureServerCredentials; import io.grpc.Server; -import io.grpc.ServerBuilder; import io.grpc.services.CallMetricRecorder; import io.grpc.services.InternalCallMetricRecorder; import io.grpc.services.MetricRecorder; @@ -57,7 +58,7 @@ private void start() throws IOException { // configuration to be as short as 1s, suitable for test demonstration. BindableService orcaOobService = OrcaServiceImpl.createService(executor, metricRecorder, 1, TimeUnit.SECONDS); - server = ServerBuilder.forPort(port) + server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()) .addService(new GreeterImpl()) // Enable OOB custom backend metrics reporting. .addService(orcaOobService) diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index 66365ce2a70..76940b9a95d 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -2,7 +2,7 @@ plugins { // Provide convenience executables for trying out the examples. id 'application' // ASSUMES GRADLE 5.6 OR HIGHER. Use plugin version 0.8.10 with earlier gradle versions - id 'com.google.protobuf' version '0.8.17' + id 'com.google.protobuf' version '0.8.19' // Generate IntelliJ IDEA's .idea & .iml project files id 'idea' } @@ -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.51.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.52.0' // CURRENT_GRPC_VERSION def protocVersion = '3.21.7' dependencies { diff --git a/examples/example-tls/pom.xml b/examples/example-tls/pom.xml index e3573aaa25d..de9d8b4ae3c 100644 --- a/examples/example-tls/pom.xml +++ b/examples/example-tls/pom.xml @@ -6,13 +6,13 @@ jar - 1.51.0-SNAPSHOT + 1.52.0 example-tls https://github.com/grpc/grpc-java UTF-8 - 1.51.0-SNAPSHOT + 1.52.0 3.21.7 2.0.54.Final diff --git a/examples/example-xds/build.gradle b/examples/example-xds/build.gradle index 6acbaa35f59..14c8dd1e89e 100644 --- a/examples/example-xds/build.gradle +++ b/examples/example-xds/build.gradle @@ -1,7 +1,7 @@ plugins { id 'application' // Provide convenience executables for trying out the examples. // ASSUMES GRADLE 5.6 OR HIGHER. Use plugin version 0.8.10 with earlier gradle versions - id 'com.google.protobuf' version '0.8.17' + id 'com.google.protobuf' version '0.8.19' // Generate IntelliJ IDEA's .idea & .iml project files id 'idea' id 'java' @@ -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.51.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.52.0' // CURRENT_GRPC_VERSION def nettyTcNativeVersion = '2.0.31.Final' def protocVersion = '3.21.7' diff --git a/examples/pom.xml b/examples/pom.xml index b6631152bd9..e90c527d7fa 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -6,18 +6,18 @@ jar - 1.51.0-SNAPSHOT + 1.52.0 examples https://github.com/grpc/grpc-java UTF-8 - 1.51.0-SNAPSHOT + 1.52.0 3.21.7 3.21.7 - - 1.7 - 1.7 + + 1.8 + 1.8 diff --git a/examples/src/main/java/io/grpc/examples/advanced/HelloJsonClient.java b/examples/src/main/java/io/grpc/examples/advanced/HelloJsonClient.java index 264fe76b7d3..9291e45bafe 100644 --- a/examples/src/main/java/io/grpc/examples/advanced/HelloJsonClient.java +++ b/examples/src/main/java/io/grpc/examples/advanced/HelloJsonClient.java @@ -20,8 +20,9 @@ import io.grpc.CallOptions; import io.grpc.Channel; +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; import io.grpc.ManagedChannel; -import io.grpc.ManagedChannelBuilder; import io.grpc.MethodDescriptor; import io.grpc.StatusRuntimeException; import io.grpc.examples.helloworld.GreeterGrpc; @@ -49,8 +50,7 @@ public final class HelloJsonClient { /** Construct client connecting to HelloWorld server at {@code host:port}. */ public HelloJsonClient(String host, int port) { - channel = ManagedChannelBuilder.forAddress(host, port) - .usePlaintext() + channel = Grpc.newChannelBuilderForAddress(host, port, InsecureChannelCredentials.create()) .build(); blockingStub = new HelloJsonStub(channel); } diff --git a/examples/src/main/java/io/grpc/examples/advanced/HelloJsonServer.java b/examples/src/main/java/io/grpc/examples/advanced/HelloJsonServer.java index 0a656dd52b8..0f4e5d28f16 100644 --- a/examples/src/main/java/io/grpc/examples/advanced/HelloJsonServer.java +++ b/examples/src/main/java/io/grpc/examples/advanced/HelloJsonServer.java @@ -19,8 +19,9 @@ import static io.grpc.stub.ServerCalls.asyncUnaryCall; import io.grpc.BindableService; +import io.grpc.Grpc; +import io.grpc.InsecureServerCredentials; import io.grpc.Server; -import io.grpc.ServerBuilder; import io.grpc.ServerServiceDefinition; import io.grpc.examples.helloworld.GreeterGrpc; import io.grpc.examples.helloworld.HelloReply; @@ -50,7 +51,7 @@ public class HelloJsonServer { private void start() throws IOException { /* The port on which the server should run */ int port = 50051; - server = ServerBuilder.forPort(port) + server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()) .addService(new GreeterImpl()) .build() .start(); diff --git a/examples/src/main/java/io/grpc/examples/errorhandling/DetailErrorSample.java b/examples/src/main/java/io/grpc/examples/errorhandling/DetailErrorSample.java index b743b46b471..b026b6f32dc 100644 --- a/examples/src/main/java/io/grpc/examples/errorhandling/DetailErrorSample.java +++ b/examples/src/main/java/io/grpc/examples/errorhandling/DetailErrorSample.java @@ -27,11 +27,12 @@ import com.google.rpc.DebugInfo; import io.grpc.CallOptions; import io.grpc.ClientCall; +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; +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.Status; import io.grpc.examples.helloworld.GreeterGrpc; import io.grpc.examples.helloworld.GreeterGrpc.GreeterBlockingStub; @@ -73,7 +74,8 @@ public static void main(String[] args) throws Exception { private ManagedChannel channel; void run() throws Exception { - Server server = ServerBuilder.forPort(0).addService(new GreeterGrpc.GreeterImplBase() { + Server server = Grpc.newServerBuilderForPort(0, InsecureServerCredentials.create()) + .addService(new GreeterGrpc.GreeterImplBase() { @Override public void sayHello(HelloRequest request, StreamObserver responseObserver) { Metadata trailers = new Metadata(); @@ -82,8 +84,8 @@ public void sayHello(HelloRequest request, StreamObserver responseOb .asRuntimeException(trailers)); } }).build().start(); - channel = - ManagedChannelBuilder.forAddress("localhost", server.getPort()).usePlaintext().build(); + channel = Grpc.newChannelBuilderForAddress( + "localhost", server.getPort(), InsecureChannelCredentials.create()).build(); blockingCall(); futureCallDirect(); diff --git a/examples/src/main/java/io/grpc/examples/errorhandling/ErrorHandlingClient.java b/examples/src/main/java/io/grpc/examples/errorhandling/ErrorHandlingClient.java index 4afd39b08fa..7e310433a90 100644 --- a/examples/src/main/java/io/grpc/examples/errorhandling/ErrorHandlingClient.java +++ b/examples/src/main/java/io/grpc/examples/errorhandling/ErrorHandlingClient.java @@ -25,11 +25,12 @@ import com.google.common.util.concurrent.Uninterruptibles; import io.grpc.CallOptions; import io.grpc.ClientCall; +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; +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.Status; import io.grpc.examples.helloworld.GreeterGrpc; import io.grpc.examples.helloworld.GreeterGrpc.GreeterBlockingStub; @@ -55,15 +56,16 @@ public static void main(String [] args) throws Exception { void run() throws Exception { // Port 0 means that the operating system will pick an available port to use. - Server server = ServerBuilder.forPort(0).addService(new GreeterGrpc.GreeterImplBase() { + Server server = Grpc.newServerBuilderForPort(0, InsecureServerCredentials.create()) + .addService(new GreeterGrpc.GreeterImplBase() { @Override public void sayHello(HelloRequest request, StreamObserver responseObserver) { responseObserver.onError(Status.INTERNAL .withDescription("Eggplant Xerxes Crybaby Overbite Narwhal").asRuntimeException()); } }).build().start(); - channel = - ManagedChannelBuilder.forAddress("localhost", server.getPort()).usePlaintext().build(); + channel = Grpc.newChannelBuilderForAddress( + "localhost", server.getPort(), InsecureChannelCredentials.create()).build(); blockingCall(); futureCallDirect(); diff --git a/examples/src/main/java/io/grpc/examples/experimental/CompressingHelloWorldClient.java b/examples/src/main/java/io/grpc/examples/experimental/CompressingHelloWorldClient.java index 410b0c7c14c..49e9cb36d53 100644 --- a/examples/src/main/java/io/grpc/examples/experimental/CompressingHelloWorldClient.java +++ b/examples/src/main/java/io/grpc/examples/experimental/CompressingHelloWorldClient.java @@ -16,8 +16,9 @@ package io.grpc.examples.experimental; +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; import io.grpc.ManagedChannel; -import io.grpc.ManagedChannelBuilder; import io.grpc.StatusRuntimeException; import io.grpc.examples.helloworld.GreeterGrpc; import io.grpc.examples.helloworld.HelloReply; @@ -42,8 +43,7 @@ public class CompressingHelloWorldClient { /** Construct client connecting to HelloWorld server at {@code host:port}. */ public CompressingHelloWorldClient(String host, int port) { - channel = ManagedChannelBuilder.forAddress(host, port) - .usePlaintext() + channel = Grpc.newChannelBuilderForAddress(host, port, InsecureChannelCredentials.create()) .build(); blockingStub = GreeterGrpc.newBlockingStub(channel); } diff --git a/examples/src/main/java/io/grpc/examples/experimental/CompressingHelloWorldServerAllMethods.java b/examples/src/main/java/io/grpc/examples/experimental/CompressingHelloWorldServerAllMethods.java index 23c51a6d26c..794d7196f35 100644 --- a/examples/src/main/java/io/grpc/examples/experimental/CompressingHelloWorldServerAllMethods.java +++ b/examples/src/main/java/io/grpc/examples/experimental/CompressingHelloWorldServerAllMethods.java @@ -20,9 +20,10 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Logger; +import io.grpc.Grpc; +import io.grpc.InsecureServerCredentials; import io.grpc.Metadata; import io.grpc.Server; -import io.grpc.ServerBuilder; import io.grpc.ServerCall; import io.grpc.ServerCall.Listener; import io.grpc.ServerCallHandler; @@ -44,7 +45,7 @@ public class CompressingHelloWorldServerAllMethods { private void start() throws IOException { /* The port on which the server should run */ int port = 50051; - server = ServerBuilder.forPort(port) + server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()) /* This method call adds the Interceptor to enable compressed server responses for all RPCs */ .intercept(new ServerInterceptor() { @Override diff --git a/examples/src/main/java/io/grpc/examples/experimental/CompressingHelloWorldServerPerMethod.java b/examples/src/main/java/io/grpc/examples/experimental/CompressingHelloWorldServerPerMethod.java index 0ccf38184d3..b7faa96d7b4 100644 --- a/examples/src/main/java/io/grpc/examples/experimental/CompressingHelloWorldServerPerMethod.java +++ b/examples/src/main/java/io/grpc/examples/experimental/CompressingHelloWorldServerPerMethod.java @@ -20,8 +20,9 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Logger; +import io.grpc.Grpc; +import io.grpc.InsecureServerCredentials; import io.grpc.Server; -import io.grpc.ServerBuilder; import io.grpc.examples.helloworld.GreeterGrpc; import io.grpc.examples.helloworld.HelloReply; import io.grpc.examples.helloworld.HelloRequest; @@ -40,7 +41,7 @@ public class CompressingHelloWorldServerPerMethod { private void start() throws IOException { /* The port on which the server should run */ int port = 50051; - server = ServerBuilder.forPort(port) + server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()) .addService(new GreeterImpl()) .build() .start(); diff --git a/examples/src/main/java/io/grpc/examples/header/CustomHeaderClient.java b/examples/src/main/java/io/grpc/examples/header/CustomHeaderClient.java index 93d106dba9e..52287040ba0 100644 --- a/examples/src/main/java/io/grpc/examples/header/CustomHeaderClient.java +++ b/examples/src/main/java/io/grpc/examples/header/CustomHeaderClient.java @@ -19,8 +19,9 @@ import io.grpc.Channel; import io.grpc.ClientInterceptor; import io.grpc.ClientInterceptors; +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; import io.grpc.ManagedChannel; -import io.grpc.ManagedChannelBuilder; import io.grpc.StatusRuntimeException; import io.grpc.examples.helloworld.GreeterGrpc; import io.grpc.examples.helloworld.HelloReply; @@ -43,8 +44,8 @@ public class CustomHeaderClient { * A custom client. */ private CustomHeaderClient(String host, int port) { - originChannel = ManagedChannelBuilder.forAddress(host, port) - .usePlaintext() + originChannel = Grpc + .newChannelBuilderForAddress(host, port, InsecureChannelCredentials.create()) .build(); ClientInterceptor interceptor = new HeaderClientInterceptor(); Channel channel = ClientInterceptors.intercept(originChannel, interceptor); diff --git a/examples/src/main/java/io/grpc/examples/header/CustomHeaderServer.java b/examples/src/main/java/io/grpc/examples/header/CustomHeaderServer.java index ae80045603c..75a3d24934f 100644 --- a/examples/src/main/java/io/grpc/examples/header/CustomHeaderServer.java +++ b/examples/src/main/java/io/grpc/examples/header/CustomHeaderServer.java @@ -16,8 +16,9 @@ package io.grpc.examples.header; +import io.grpc.Grpc; +import io.grpc.InsecureServerCredentials; import io.grpc.Server; -import io.grpc.ServerBuilder; import io.grpc.ServerInterceptors; import io.grpc.examples.helloworld.GreeterGrpc; import io.grpc.examples.helloworld.HelloReply; @@ -39,7 +40,7 @@ public class CustomHeaderServer { private Server server; private void start() throws IOException { - server = ServerBuilder.forPort(PORT) + server = Grpc.newServerBuilderForPort(PORT, InsecureServerCredentials.create()) .addService(ServerInterceptors.intercept(new GreeterImpl(), new HeaderServerInterceptor())) .build() .start(); diff --git a/examples/src/main/java/io/grpc/examples/hedging/HedgingHelloWorldClient.java b/examples/src/main/java/io/grpc/examples/hedging/HedgingHelloWorldClient.java index 30f7beb49a0..429cceb50c3 100644 --- a/examples/src/main/java/io/grpc/examples/hedging/HedgingHelloWorldClient.java +++ b/examples/src/main/java/io/grpc/examples/hedging/HedgingHelloWorldClient.java @@ -20,6 +20,8 @@ import com.google.gson.Gson; import com.google.gson.stream.JsonReader; +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.StatusRuntimeException; @@ -51,11 +53,8 @@ public class HedgingHelloWorldClient { /** Construct client connecting to HelloWorld server at {@code host:port}. */ public HedgingHelloWorldClient(String host, int port, boolean hedging) { - - ManagedChannelBuilder channelBuilder = ManagedChannelBuilder.forAddress(host, port) - // Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid - // needing certificates. - .usePlaintext(); + ManagedChannelBuilder channelBuilder + = Grpc.newChannelBuilderForAddress(host, port, InsecureChannelCredentials.create()); if (hedging) { Map hedgingServiceConfig = new Gson() diff --git a/examples/src/main/java/io/grpc/examples/hedging/HedgingHelloWorldServer.java b/examples/src/main/java/io/grpc/examples/hedging/HedgingHelloWorldServer.java index b934e8514ac..784269eea1c 100644 --- a/examples/src/main/java/io/grpc/examples/hedging/HedgingHelloWorldServer.java +++ b/examples/src/main/java/io/grpc/examples/hedging/HedgingHelloWorldServer.java @@ -16,9 +16,10 @@ package io.grpc.examples.hedging; +import io.grpc.Grpc; +import io.grpc.InsecureServerCredentials; import io.grpc.Metadata; import io.grpc.Server; -import io.grpc.ServerBuilder; import io.grpc.ServerCall; import io.grpc.ServerCall.Listener; import io.grpc.ServerCallHandler; @@ -43,7 +44,7 @@ public class HedgingHelloWorldServer { private void start() throws IOException { /* The port on which the server should run */ int port = 50051; - server = ServerBuilder.forPort(port) + server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()) .addService(new GreeterImpl()) .intercept(new LatencyInjectionInterceptor()) .build() diff --git a/examples/src/main/java/io/grpc/examples/helloworld/HelloWorldClient.java b/examples/src/main/java/io/grpc/examples/helloworld/HelloWorldClient.java index d00bca1e216..6b186facf46 100644 --- a/examples/src/main/java/io/grpc/examples/helloworld/HelloWorldClient.java +++ b/examples/src/main/java/io/grpc/examples/helloworld/HelloWorldClient.java @@ -17,8 +17,9 @@ package io.grpc.examples.helloworld; import io.grpc.Channel; +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; import io.grpc.ManagedChannel; -import io.grpc.ManagedChannelBuilder; import io.grpc.StatusRuntimeException; import java.util.concurrent.TimeUnit; import java.util.logging.Level; @@ -81,10 +82,10 @@ public static void main(String[] args) throws Exception { // Create a communication channel to the server, known as a Channel. Channels are thread-safe // and reusable. It is common to create channels at the beginning of your application and reuse // them until the application shuts down. - ManagedChannel channel = ManagedChannelBuilder.forTarget(target) - // Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid - // needing certificates. - .usePlaintext() + // + // For the example we use plaintext insecure credentials to avoid needing TLS certificates. To + // use TLS, use TlsChannelCredentials instead. + ManagedChannel channel = Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()) .build(); try { HelloWorldClient client = new HelloWorldClient(channel); diff --git a/examples/src/main/java/io/grpc/examples/helloworld/HelloWorldServer.java b/examples/src/main/java/io/grpc/examples/helloworld/HelloWorldServer.java index 12836a4c828..81027587031 100644 --- a/examples/src/main/java/io/grpc/examples/helloworld/HelloWorldServer.java +++ b/examples/src/main/java/io/grpc/examples/helloworld/HelloWorldServer.java @@ -16,8 +16,9 @@ package io.grpc.examples.helloworld; +import io.grpc.Grpc; +import io.grpc.InsecureServerCredentials; import io.grpc.Server; -import io.grpc.ServerBuilder; import io.grpc.stub.StreamObserver; import java.io.IOException; import java.util.concurrent.TimeUnit; @@ -34,7 +35,7 @@ public class HelloWorldServer { private void start() throws IOException { /* The port on which the server should run */ int port = 50051; - server = ServerBuilder.forPort(port) + server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()) .addService(new GreeterImpl()) .build() .start(); diff --git a/examples/src/main/java/io/grpc/examples/loadbalance/ExampleNameResolver.java b/examples/src/main/java/io/grpc/examples/loadbalance/ExampleNameResolver.java new file mode 100644 index 00000000000..f562f0ac107 --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/loadbalance/ExampleNameResolver.java @@ -0,0 +1,110 @@ +/* + * 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.examples.loadbalance; + +import com.google.common.collect.ImmutableMap; +import io.grpc.EquivalentAddressGroup; +import io.grpc.NameResolver; +import io.grpc.Status; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.URI; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static io.grpc.examples.loadbalance.LoadBalanceClient.exampleServiceName; + +public class ExampleNameResolver extends NameResolver { + + private Listener2 listener; + + private final URI uri; + + private final Map> addrStore; + + public ExampleNameResolver(URI targetUri) { + this.uri = targetUri; + // This is a fake name resolver, so we just hard code the address here. + addrStore = ImmutableMap.>builder() + .put(exampleServiceName, + Stream.iterate(LoadBalanceServer.startPort,p->p+1) + .limit(LoadBalanceServer.serverCount) + .map(port->new InetSocketAddress("localhost",port)) + .collect(Collectors.toList()) + ) + .build(); + } + + @Override + public String getServiceAuthority() { + // Be consistent with behavior in grpc-go, authority is saved in Host field of URI. + if (uri.getHost() != null) { + return uri.getHost(); + } + return "no host"; + } + + @Override + public void shutdown() { + } + + @Override + public void start(Listener2 listener) { + this.listener = listener; + this.resolve(); + } + + @Override + public void refresh() { + this.resolve(); + } + + private void resolve() { + List addresses = addrStore.get(uri.getPath().substring(1)); + try { + List equivalentAddressGroup = addresses.stream() + // convert to socket address + .map(this::toSocketAddress) + // every socket address is a single EquivalentAddressGroup, so they can be accessed randomly + .map(Arrays::asList) + .map(this::addrToEquivalentAddressGroup) + .collect(Collectors.toList()); + + ResolutionResult resolutionResult = ResolutionResult.newBuilder() + .setAddresses(equivalentAddressGroup) + .build(); + + this.listener.onResult(resolutionResult); + + } catch (Exception e){ + // when error occurs, notify listener + this.listener.onError(Status.UNAVAILABLE.withDescription("Unable to resolve host ").withCause(e)); + } + } + + private SocketAddress toSocketAddress(InetSocketAddress address) { + return new InetSocketAddress(address.getHostName(), address.getPort()); + } + + private EquivalentAddressGroup addrToEquivalentAddressGroup(List addrList) { + return new EquivalentAddressGroup(addrList); + } +} diff --git a/examples/src/main/java/io/grpc/examples/loadbalance/ExampleNameResolverProvider.java b/examples/src/main/java/io/grpc/examples/loadbalance/ExampleNameResolverProvider.java new file mode 100644 index 00000000000..ee966fd044c --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/loadbalance/ExampleNameResolverProvider.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.examples.loadbalance; + +import io.grpc.NameResolver; +import io.grpc.NameResolverProvider; + +import java.net.URI; + +import static io.grpc.examples.loadbalance.LoadBalanceClient.exampleScheme; + +public class ExampleNameResolverProvider extends NameResolverProvider { + @Override + public NameResolver newNameResolver(URI targetUri, NameResolver.Args args) { + return new ExampleNameResolver(targetUri); + } + + @Override + protected boolean isAvailable() { + return true; + } + + @Override + protected int priority() { + return 5; + } + + @Override + // gRPC choose the first NameResolverProvider that supports the target URI scheme. + public String getDefaultScheme() { + return exampleScheme; + } +} diff --git a/examples/src/main/java/io/grpc/examples/loadbalance/LoadBalanceClient.java b/examples/src/main/java/io/grpc/examples/loadbalance/LoadBalanceClient.java new file mode 100644 index 00000000000..97444922871 --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/loadbalance/LoadBalanceClient.java @@ -0,0 +1,85 @@ +/* + * 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.examples.loadbalance; + +import io.grpc.*; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; + +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class LoadBalanceClient { + private static final Logger logger = Logger.getLogger(LoadBalanceClient.class.getName()); + + public static final String exampleScheme = "example"; + public static final String exampleServiceName = "lb.example.grpc.io"; + + private final GreeterGrpc.GreeterBlockingStub blockingStub; + + public LoadBalanceClient(Channel channel) { + blockingStub = GreeterGrpc.newBlockingStub(channel); + } + + public void greet(String name) { + HelloRequest request = HelloRequest.newBuilder().setName(name).build(); + HelloReply response; + try { + response = blockingStub.sayHello(request); + } catch (StatusRuntimeException e) { + logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); + return; + } + logger.info("Greeting: " + response.getMessage()); + } + + + public static void main(String[] args) throws Exception { + NameResolverRegistry.getDefaultRegistry().register(new ExampleNameResolverProvider()); + + String target = String.format("%s:///%s", exampleScheme, exampleServiceName); + + logger.info("Use default first_pick load balance policy"); + ManagedChannel channel = ManagedChannelBuilder.forTarget(target) + .usePlaintext() + .build(); + try { + LoadBalanceClient client = new LoadBalanceClient(channel); + for (int i = 0; i < 5; i++) { + client.greet("request" + i); + } + } finally { + channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + } + + logger.info("Change to round_robin policy"); + channel = ManagedChannelBuilder.forTarget(target) + .defaultLoadBalancingPolicy("round_robin") + .usePlaintext() + .build(); + try { + LoadBalanceClient client = new LoadBalanceClient(channel); + for (int i = 0; i < 5; i++) { + client.greet("request" + i); + } + } finally { + channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + } + } +} diff --git a/examples/src/main/java/io/grpc/examples/loadbalance/LoadBalanceServer.java b/examples/src/main/java/io/grpc/examples/loadbalance/LoadBalanceServer.java new file mode 100644 index 00000000000..c97d209497a --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/loadbalance/LoadBalanceServer.java @@ -0,0 +1,94 @@ +/* + * 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.examples.loadbalance; + +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.stub.StreamObserver; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +public class LoadBalanceServer { + private static final Logger logger = Logger.getLogger(LoadBalanceServer.class.getName()); + static public final int serverCount = 3; + static public final int startPort = 50051; + private Server[] servers; + + private void start() throws IOException { + servers = new Server[serverCount]; + for (int i = 0; i < serverCount; i++) { + int port = startPort + i; + servers[i] = ServerBuilder.forPort(port) + .addService(new GreeterImpl(port)) + .build() + .start(); + logger.info("Server started, listening on " + port); + } + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + System.err.println("*** shutting down gRPC server since JVM is shutting down"); + try { + LoadBalanceServer.this.stop(); + } catch (InterruptedException e) { + e.printStackTrace(System.err); + } + System.err.println("*** server shut down"); + })); + } + + private void stop() throws InterruptedException { + for (int i = 0; i < serverCount; i++) { + if (servers[i] != null) { + servers[i].shutdown().awaitTermination(30, TimeUnit.SECONDS); + } + } + } + + private void blockUntilShutdown() throws InterruptedException { + for (int i = 0; i < serverCount; i++) { + if (servers[i] != null) { + servers[i].awaitTermination(); + } + } + } + + public static void main(String[] args) throws IOException, InterruptedException { + final LoadBalanceServer server = new LoadBalanceServer(); + server.start(); + server.blockUntilShutdown(); + } + + static class GreeterImpl extends GreeterGrpc.GreeterImplBase { + + int port; + + public GreeterImpl(int port) { + this.port = port; + } + + @Override + public void sayHello(HelloRequest req, StreamObserver responseObserver) { + HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName() + " from server<" + this.port + ">").build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + } +} diff --git a/examples/src/main/java/io/grpc/examples/nameresolve/ExampleNameResolver.java b/examples/src/main/java/io/grpc/examples/nameresolve/ExampleNameResolver.java new file mode 100644 index 00000000000..95bf20dd580 --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/nameresolve/ExampleNameResolver.java @@ -0,0 +1,108 @@ +/* + * 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.examples.nameresolve; + +import com.google.common.collect.ImmutableMap; +import io.grpc.EquivalentAddressGroup; +import io.grpc.NameResolver; +import io.grpc.Status; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.URI; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static io.grpc.examples.loadbalance.LoadBalanceClient.exampleServiceName; + +public class ExampleNameResolver extends NameResolver { + + private final URI uri; + private final Map> addrStore; + private Listener2 listener; + + public ExampleNameResolver(URI targetUri) { + this.uri = targetUri; + // This is a fake name resolver, so we just hard code the address here. + addrStore = ImmutableMap.>builder() + .put(exampleServiceName, + Stream.iterate(NameResolveServer.startPort, p -> p + 1) + .limit(NameResolveServer.serverCount) + .map(port -> new InetSocketAddress("localhost", port)) + .collect(Collectors.toList()) + ) + .build(); + } + + @Override + public String getServiceAuthority() { + // Be consistent with behavior in grpc-go, authority is saved in Host field of URI. + if (uri.getHost() != null) { + return uri.getHost(); + } + return "no host"; + } + + @Override + public void shutdown() { + } + + @Override + public void start(Listener2 listener) { + this.listener = listener; + this.resolve(); + } + + @Override + public void refresh() { + this.resolve(); + } + + private void resolve() { + List addresses = addrStore.get(uri.getPath().substring(1)); + try { + List equivalentAddressGroup = addresses.stream() + // convert to socket address + .map(this::toSocketAddress) + // every socket address is a single EquivalentAddressGroup, so they can be accessed randomly + .map(Arrays::asList) + .map(this::addrToEquivalentAddressGroup) + .collect(Collectors.toList()); + + ResolutionResult resolutionResult = ResolutionResult.newBuilder() + .setAddresses(equivalentAddressGroup) + .build(); + + this.listener.onResult(resolutionResult); + + } catch (Exception e) { + // when error occurs, notify listener + this.listener.onError(Status.UNAVAILABLE.withDescription("Unable to resolve host ").withCause(e)); + } + } + + private SocketAddress toSocketAddress(InetSocketAddress address) { + return new InetSocketAddress(address.getHostName(), address.getPort()); + } + + private EquivalentAddressGroup addrToEquivalentAddressGroup(List addrList) { + return new EquivalentAddressGroup(addrList); + } +} diff --git a/examples/src/main/java/io/grpc/examples/nameresolve/ExampleNameResolverProvider.java b/examples/src/main/java/io/grpc/examples/nameresolve/ExampleNameResolverProvider.java new file mode 100644 index 00000000000..cd05f3214f6 --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/nameresolve/ExampleNameResolverProvider.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.examples.nameresolve; + +import io.grpc.NameResolver; +import io.grpc.NameResolverProvider; + +import java.net.URI; + +import static io.grpc.examples.loadbalance.LoadBalanceClient.exampleScheme; + +public class ExampleNameResolverProvider extends NameResolverProvider { + @Override + public NameResolver newNameResolver(URI targetUri, NameResolver.Args args) { + return new ExampleNameResolver(targetUri); + } + + @Override + protected boolean isAvailable() { + return true; + } + + @Override + protected int priority() { + return 5; + } + + @Override + // gRPC choose the first NameResolverProvider that supports the target URI scheme. + public String getDefaultScheme() { + return exampleScheme; + } +} diff --git a/examples/src/main/java/io/grpc/examples/nameresolve/NameResolveClient.java b/examples/src/main/java/io/grpc/examples/nameresolve/NameResolveClient.java new file mode 100644 index 00000000000..ac6fdd32549 --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/nameresolve/NameResolveClient.java @@ -0,0 +1,85 @@ +/* + * 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.examples.nameresolve; + +import io.grpc.*; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; + +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class NameResolveClient { + public static final String exampleScheme = "example"; + public static final String exampleServiceName = "lb.example.grpc.io"; + private static final Logger logger = Logger.getLogger(NameResolveClient.class.getName()); + private final GreeterGrpc.GreeterBlockingStub blockingStub; + + public NameResolveClient(Channel channel) { + blockingStub = GreeterGrpc.newBlockingStub(channel); + } + + public static void main(String[] args) throws Exception { + NameResolverRegistry.getDefaultRegistry().register(new ExampleNameResolverProvider()); + + logger.info("Use default DNS resolver"); + ManagedChannel channel = ManagedChannelBuilder.forTarget("localhost:50051") + .usePlaintext() + .build(); + try { + NameResolveClient client = new NameResolveClient(channel); + for (int i = 0; i < 5; i++) { + client.greet("request" + i); + } + } finally { + channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + } + + logger.info("Change to use example name resolver"); + /* + Dial to "example:///resolver.example.grpc.io", use {@link ExampleNameResolver} to create connection + "resolver.example.grpc.io" is converted to {@link java.net.URI.path} + */ + channel = ManagedChannelBuilder.forTarget( + String.format("%s:///%s", exampleScheme, exampleServiceName)) + .defaultLoadBalancingPolicy("round_robin") + .usePlaintext() + .build(); + try { + NameResolveClient client = new NameResolveClient(channel); + for (int i = 0; i < 5; i++) { + client.greet("request" + i); + } + } finally { + channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + } + } + + public void greet(String name) { + HelloRequest request = HelloRequest.newBuilder().setName(name).build(); + HelloReply response; + try { + response = blockingStub.sayHello(request); + } catch (StatusRuntimeException e) { + logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); + return; + } + logger.info("Greeting: " + response.getMessage()); + } +} diff --git a/examples/src/main/java/io/grpc/examples/nameresolve/NameResolveServer.java b/examples/src/main/java/io/grpc/examples/nameresolve/NameResolveServer.java new file mode 100644 index 00000000000..0a402485906 --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/nameresolve/NameResolveServer.java @@ -0,0 +1,94 @@ +/* + * 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.examples.nameresolve; + +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.stub.StreamObserver; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +public class NameResolveServer { + static public final int serverCount = 3; + static public final int startPort = 50051; + private static final Logger logger = Logger.getLogger(NameResolveServer.class.getName()); + private Server[] servers; + + public static void main(String[] args) throws IOException, InterruptedException { + final NameResolveServer server = new NameResolveServer(); + server.start(); + server.blockUntilShutdown(); + } + + private void start() throws IOException { + servers = new Server[serverCount]; + for (int i = 0; i < serverCount; i++) { + int port = startPort + i; + servers[i] = ServerBuilder.forPort(port) + .addService(new GreeterImpl(port)) + .build() + .start(); + logger.info("Server started, listening on " + port); + } + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + System.err.println("*** shutting down gRPC server since JVM is shutting down"); + try { + NameResolveServer.this.stop(); + } catch (InterruptedException e) { + e.printStackTrace(System.err); + } + System.err.println("*** server shut down"); + })); + } + + private void stop() throws InterruptedException { + for (int i = 0; i < serverCount; i++) { + if (servers[i] != null) { + servers[i].shutdown().awaitTermination(30, TimeUnit.SECONDS); + } + } + } + + private void blockUntilShutdown() throws InterruptedException { + for (int i = 0; i < serverCount; i++) { + if (servers[i] != null) { + servers[i].awaitTermination(); + } + } + } + + static class GreeterImpl extends GreeterGrpc.GreeterImplBase { + + int port; + + public GreeterImpl(int port) { + this.port = port; + } + + @Override + public void sayHello(HelloRequest req, StreamObserver responseObserver) { + HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName() + " from server<" + this.port + ">").build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + } +} diff --git a/examples/src/main/java/io/grpc/examples/retrying/RetryingHelloWorldClient.java b/examples/src/main/java/io/grpc/examples/retrying/RetryingHelloWorldClient.java index d8f2e72441b..20c9e5893fb 100644 --- a/examples/src/main/java/io/grpc/examples/retrying/RetryingHelloWorldClient.java +++ b/examples/src/main/java/io/grpc/examples/retrying/RetryingHelloWorldClient.java @@ -20,6 +20,8 @@ import com.google.gson.Gson; import com.google.gson.stream.JsonReader; +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.StatusRuntimeException; @@ -64,10 +66,8 @@ public class RetryingHelloWorldClient { */ public RetryingHelloWorldClient(String host, int port, boolean enableRetries) { - ManagedChannelBuilder channelBuilder = ManagedChannelBuilder.forAddress(host, port) - // Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid - // needing certificates. - .usePlaintext(); + ManagedChannelBuilder channelBuilder + = Grpc.newChannelBuilderForAddress(host, port, InsecureChannelCredentials.create()); if (enableRetries) { Map serviceConfig = getRetryingServiceConfig(); logger.info("Client started with retrying configuration: " + serviceConfig); diff --git a/examples/src/main/java/io/grpc/examples/retrying/RetryingHelloWorldServer.java b/examples/src/main/java/io/grpc/examples/retrying/RetryingHelloWorldServer.java index 0bff00a6988..165cc72ffa3 100644 --- a/examples/src/main/java/io/grpc/examples/retrying/RetryingHelloWorldServer.java +++ b/examples/src/main/java/io/grpc/examples/retrying/RetryingHelloWorldServer.java @@ -19,8 +19,9 @@ import java.text.DecimalFormat; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; +import io.grpc.Grpc; +import io.grpc.InsecureServerCredentials; import io.grpc.Server; -import io.grpc.ServerBuilder; import io.grpc.Status; import io.grpc.examples.helloworld.GreeterGrpc; import io.grpc.examples.helloworld.HelloReply; @@ -43,7 +44,7 @@ public class RetryingHelloWorldServer { private void start() throws IOException { /* The port on which the server should run */ int port = 50051; - server = ServerBuilder.forPort(port) + server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()) .addService(new GreeterImpl()) .build() .start(); diff --git a/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideClient.java b/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideClient.java index 6958c643da7..f65b1215359 100644 --- a/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideClient.java +++ b/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideClient.java @@ -19,8 +19,9 @@ import com.google.common.annotations.VisibleForTesting; import com.google.protobuf.Message; import io.grpc.Channel; +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; import io.grpc.ManagedChannel; -import io.grpc.ManagedChannelBuilder; import io.grpc.Status; import io.grpc.StatusRuntimeException; import io.grpc.examples.routeguide.RouteGuideGrpc.RouteGuideBlockingStub; @@ -259,7 +260,8 @@ public static void main(String[] args) throws InterruptedException { return; } - ManagedChannel channel = ManagedChannelBuilder.forTarget(target).usePlaintext().build(); + ManagedChannel channel = Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()) + .build(); try { RouteGuideClient client = new RouteGuideClient(channel); // Looking for a valid feature diff --git a/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideServer.java b/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideServer.java index c91544ae45d..b39b06a6f92 100644 --- a/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideServer.java +++ b/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideServer.java @@ -25,6 +25,8 @@ import static java.lang.Math.toRadians; import static java.util.concurrent.TimeUnit.NANOSECONDS; +import io.grpc.Grpc; +import io.grpc.InsecureServerCredentials; import io.grpc.Server; import io.grpc.ServerBuilder; import io.grpc.stub.StreamObserver; @@ -55,7 +57,8 @@ public RouteGuideServer(int port) throws IOException { /** Create a RouteGuide server listening on {@code port} using {@code featureFile} database. */ public RouteGuideServer(int port, URL featureFile) throws IOException { - this(ServerBuilder.forPort(port), port, RouteGuideUtil.parseFeatures(featureFile)); + this(Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()), + port, RouteGuideUtil.parseFeatures(featureFile)); } /** Create a RouteGuide server using serverBuilder as a base and features as data. */ diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java b/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java index bd11bac8a9c..d04873c5a92 100644 --- a/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java +++ b/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java @@ -1241,7 +1241,7 @@ public void deadlineInPast() throws Exception { } catch (StatusRuntimeException ex) { assertEquals(Status.Code.DEADLINE_EXCEEDED, ex.getStatus().getCode()); assertThat(ex.getStatus().getDescription()) - .startsWith("ClientCall started after deadline exceeded"); + .startsWith("ClientCall started after CallOptions deadline was exceeded"); } // CensusStreamTracerModule record final status in the interceptor, thus is guaranteed to be @@ -1274,7 +1274,7 @@ public void deadlineInPast() throws Exception { } catch (StatusRuntimeException ex) { assertEquals(Status.Code.DEADLINE_EXCEEDED, ex.getStatus().getCode()); assertThat(ex.getStatus().getDescription()) - .startsWith("ClientCall started after deadline exceeded"); + .startsWith("ClientCall started after CallOptions deadline was exceeded"); } if (metricsExpected()) { MetricsRecord clientStartRecord = clientStatsRecorder.pollRecord(5, TimeUnit.SECONDS); 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 157fd79524e..eca563fb7c1 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 @@ -351,8 +351,8 @@ public void statsRecorded() throws Exception { call.request(1); assertInboundMessageRecorded(); assertInboundWireSizeRecorded(1); - assertRpcStatusRecorded(Status.Code.OK, 2000, 2); - assertRetryStatsRecorded(1, 0, 10_000); + assertRpcStatusRecorded(Status.Code.OK, 12000, 2); + assertRetryStatsRecorded(1, 0, 0); } @Test @@ -410,13 +410,14 @@ public void streamClosed(Status status) { serverCall.request(2); assertOutboundWireSizeRecorded(message.length()); fakeClock.forwardTime(7, SECONDS); - call.cancel("Cancelled before commit", null); // A noop substream will commit. - // The call listener is closed, but the netty substream listener is not yet closed. - verify(mockCallListener, timeout(5000)).onClose(any(Status.class), any(Metadata.class)); + // A noop substream will commit. But call is not yet closed. + call.cancel("Cancelled before commit", null); // Let the netty substream listener be closed. streamClosedLatch.countDown(); - assertRetryStatsRecorded(1, 0, 10_000); - assertRpcStatusRecorded(Code.CANCELLED, 7_000, 1); + // The call listener is closed. + verify(mockCallListener, timeout(5000)).onClose(any(Status.class), any(Metadata.class)); + assertRpcStatusRecorded(Code.CANCELLED, 17_000, 1); + assertRetryStatsRecorded(1, 0, 0); } @Test diff --git a/java_grpc_library.bzl b/java_grpc_library.bzl index 237fd2fb7a5..11d6e393e98 100644 --- a/java_grpc_library.bzl +++ b/java_grpc_library.bzl @@ -30,12 +30,12 @@ java_rpc_toolchain = rule( providers = [JavaInfo], ), "plugin": attr.label( - cfg = "host", + cfg = "exec", executable = True, ), "plugin_arg": attr.string(), "_protoc": attr.label( - cfg = "host", + cfg = "exec", default = Label("@com_google_protobuf//:protoc"), executable = True, ), @@ -85,7 +85,7 @@ def _java_rpc_library_impl(ctx): args = ctx.actions.args() args.add(toolchain.plugin, format = "--plugin=protoc-gen-rpc-plugin=%s") args.add("--rpc-plugin_out={0}:{1}".format(toolchain.plugin_arg, srcjar.path)) - args.add_joined("--descriptor_set_in", descriptor_set_in, join_with = ctx.host_configuration.host_path_separator) + args.add_joined("--descriptor_set_in", descriptor_set_in, join_with = ctx.configuration.host_path_separator) args.add_all(srcs, map_each = _path_ignoring_repository) ctx.actions.run( diff --git a/netty/BUILD.bazel b/netty/BUILD.bazel index c1768b61e75..d2497d065ec 100644 --- a/netty/BUILD.bazel +++ b/netty/BUILD.bazel @@ -20,20 +20,21 @@ java_library( "@io_netty_netty_codec_http2//jar", "@io_netty_netty_codec_socks//jar", "@io_netty_netty_common//jar", - "@io_netty_netty_transport_native_unix_common//jar", "@io_netty_netty_handler//jar", "@io_netty_netty_handler_proxy//jar", "@io_netty_netty_resolver//jar", "@io_netty_netty_transport//jar", + "@io_netty_netty_transport_native_unix_common//jar", "@io_perfmark_perfmark_api//jar", ], ) # Mirrors the dependencies included in the artifact on Maven Central for usage -# with maven_install's override_targets. Purposefully does not export any -# symbols, as it should only be used as a dep for pre-compiled binaries on -# Maven Central. Not actually shaded; libraries should not be referencing -# unstable APIs so there should not be any references to the shaded package. +# with maven_install's override_targets. Should only be used as a dep for +# pre-compiled binaries on Maven Central. +# +# Not actually shaded; libraries should not be referencing unstable APIs so +# there should not be any references to the shaded package. java_library( name = "shaded_maven", visibility = ["//visibility:public"], diff --git a/okhttp/build.gradle b/okhttp/build.gradle index a044503510d..439abaa3373 100644 --- a/okhttp/build.gradle +++ b/okhttp/build.gradle @@ -21,6 +21,7 @@ dependencies { testImplementation project(':grpc-core').sourceSets.test.output, project(':grpc-api').sourceSets.test.output, project(':grpc-testing'), + project(':grpc-testing-proto'), libraries.netty.codec.http2, libraries.okhttp signature libraries.signature.java diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java index f0e8bf41ff9..45d6b9efc54 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java @@ -40,7 +40,10 @@ import io.grpc.internal.SharedResourcePool; import io.grpc.internal.TransportTracer; import io.grpc.okhttp.internal.Platform; +import java.io.IOException; +import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.Socket; import java.net.SocketAddress; import java.security.GeneralSecurityException; import java.util.EnumSet; @@ -54,6 +57,8 @@ import javax.net.ServerSocketFactory; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; /** @@ -422,9 +427,26 @@ static HandshakerSocketFactoryResult handshakerSocketFactoryFrom(ServerCredentia } catch (GeneralSecurityException gse) { throw new RuntimeException("TLS Provider failure", gse); } + SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); + switch (tlsCreds.getClientAuth()) { + case OPTIONAL: + sslSocketFactory = new ClientCertRequestingSocketFactory(sslSocketFactory, false); + break; + + case REQUIRE: + sslSocketFactory = new ClientCertRequestingSocketFactory(sslSocketFactory, true); + break; + + case NONE: + // NOOP; this is the SSLContext default + break; + + default: + return HandshakerSocketFactoryResult.error( + "Unknown TlsServerCredentials.ClientAuth value: " + tlsCreds.getClientAuth()); + } return HandshakerSocketFactoryResult.factory(new TlsServerHandshakerSocketFactory( - new SslSocketFactoryServerCredentials.ServerCredentials( - sslContext.getSocketFactory()))); + new SslSocketFactoryServerCredentials.ServerCredentials(sslSocketFactory))); } else if (creds instanceof InsecureServerCredentials) { return HandshakerSocketFactoryResult.factory(new PlaintextHandshakerSocketFactory()); @@ -473,4 +495,59 @@ public static HandshakerSocketFactoryResult factory(HandshakerSocketFactory fact Preconditions.checkNotNull(factory, "factory"), null); } } + + static final class ClientCertRequestingSocketFactory extends SSLSocketFactory { + private final SSLSocketFactory socketFactory; + private final boolean required; + + public ClientCertRequestingSocketFactory(SSLSocketFactory socketFactory, boolean required) { + this.socketFactory = Preconditions.checkNotNull(socketFactory, "socketFactory"); + this.required = required; + } + + private Socket apply(Socket s) throws IOException { + if (!(s instanceof SSLSocket)) { + throw new IOException( + "SocketFactory " + socketFactory + " did not produce an SSLSocket: " + s.getClass()); + } + SSLSocket sslSocket = (SSLSocket) s; + if (required) { + sslSocket.setNeedClientAuth(true); + } else { + sslSocket.setWantClientAuth(true); + } + return sslSocket; + } + + @Override public Socket createSocket(Socket s, String host, int port, boolean autoClose) + throws IOException { + return apply(socketFactory.createSocket(s, host, port, autoClose)); + } + + @Override public Socket createSocket(String host, int port) throws IOException { + return apply(socketFactory.createSocket(host, port)); + } + + @Override public Socket createSocket( + String host, int port, InetAddress localHost, int localPort) throws IOException { + return apply(socketFactory.createSocket(host, port, localHost, localPort)); + } + + @Override public Socket createSocket(InetAddress host, int port) throws IOException { + return apply(socketFactory.createSocket(host, port)); + } + + @Override public Socket createSocket( + InetAddress host, int port, InetAddress localAddress, int localPort) throws IOException { + return apply(socketFactory.createSocket(host, port, localAddress, localPort)); + } + + @Override public String[] getDefaultCipherSuites() { + return socketFactory.getDefaultCipherSuites(); + } + + @Override public String[] getSupportedCipherSuites() { + return socketFactory.getSupportedCipherSuites(); + } + } } diff --git a/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java b/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java new file mode 100644 index 00000000000..cc86c81d970 --- /dev/null +++ b/okhttp/src/test/java/io/grpc/okhttp/TlsTest.java @@ -0,0 +1,271 @@ +/* + * Copyright 2015 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.okhttp; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import com.google.common.base.Throwables; +import io.grpc.ChannelCredentials; +import io.grpc.ConnectivityState; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import io.grpc.Server; +import io.grpc.ServerCredentials; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import io.grpc.TlsChannelCredentials; +import io.grpc.TlsServerCredentials; +import io.grpc.internal.testing.TestUtils; +import io.grpc.stub.StreamObserver; +import io.grpc.testing.GrpcCleanupRule; +import io.grpc.testing.TlsTesting; +import io.grpc.testing.protobuf.SimpleRequest; +import io.grpc.testing.protobuf.SimpleResponse; +import io.grpc.testing.protobuf.SimpleServiceGrpc; +import java.io.IOException; +import java.io.InputStream; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Verify OkHttp's TLS integration. */ +@RunWith(JUnit4.class) +public class TlsTest { + @Rule + public final GrpcCleanupRule grpcCleanupRule = new GrpcCleanupRule(); + + @Before + public void checkForAlpnApi() throws Exception { + // This checks for the "Java 9 ALPN API" which was backported to Java 8u252. The Kokoro Windows + // CI is on too old of a JDK for us to assume this is available. + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, null, null); + SSLEngine engine = context.createSSLEngine(); + try { + SSLEngine.class.getMethod("getApplicationProtocol").invoke(engine); + } catch (NoSuchMethodException | UnsupportedOperationException ex) { + Assume.assumeNoException(ex); + } + } + + @Test + public void mtls_succeeds() throws Exception { + ServerCredentials serverCreds; + try (InputStream serverCert = TlsTesting.loadCert("server1.pem"); + InputStream serverPrivateKey = TlsTesting.loadCert("server1.key"); + InputStream caCert = TlsTesting.loadCert("ca.pem")) { + serverCreds = TlsServerCredentials.newBuilder() + .keyManager(serverCert, serverPrivateKey) + .trustManager(caCert) + .clientAuth(TlsServerCredentials.ClientAuth.REQUIRE) + .build(); + } + ChannelCredentials channelCreds; + try (InputStream clientCertChain = TlsTesting.loadCert("client.pem"); + InputStream clientPrivateKey = TlsTesting.loadCert("client.key"); + InputStream caCert = TlsTesting.loadCert("ca.pem")) { + channelCreds = TlsChannelCredentials.newBuilder() + .keyManager(clientCertChain, clientPrivateKey) + .trustManager(caCert) + .build(); + } + Server server = grpcCleanupRule.register(server(serverCreds)); + ManagedChannel channel = grpcCleanupRule.register(clientChannel(server, channelCreds)); + + SimpleServiceGrpc.newBlockingStub(channel).unaryRpc(SimpleRequest.getDefaultInstance()); + } + + @Test + public void untrustedClient_fails() throws Exception { + ServerCredentials serverCreds; + try (InputStream serverCert = TlsTesting.loadCert("server1.pem"); + InputStream serverPrivateKey = TlsTesting.loadCert("server1.key"); + InputStream caCert = TlsTesting.loadCert("ca.pem")) { + serverCreds = TlsServerCredentials.newBuilder() + .keyManager(serverCert, serverPrivateKey) + .trustManager(caCert) + .clientAuth(TlsServerCredentials.ClientAuth.REQUIRE) + .build(); + } + ChannelCredentials channelCreds; + try (InputStream clientCertChain = TlsTesting.loadCert("badclient.pem"); + InputStream clientPrivateKey = TlsTesting.loadCert("badclient.key"); + InputStream caCert = TlsTesting.loadCert("ca.pem")) { + channelCreds = TlsChannelCredentials.newBuilder() + .keyManager(clientCertChain, clientPrivateKey) + .trustManager(caCert) + .build(); + } + Server server = grpcCleanupRule.register(server(serverCreds)); + ManagedChannel channel = grpcCleanupRule.register(clientChannel(server, channelCreds)); + + assertRpcFails(channel); + } + + @Test + public void missingOptionalClientCert_succeeds() throws Exception { + ServerCredentials serverCreds; + try (InputStream serverCert = TlsTesting.loadCert("server1.pem"); + InputStream serverPrivateKey = TlsTesting.loadCert("server1.key"); + InputStream caCert = TlsTesting.loadCert("ca.pem")) { + serverCreds = TlsServerCredentials.newBuilder() + .keyManager(serverCert, serverPrivateKey) + .trustManager(caCert) + .clientAuth(TlsServerCredentials.ClientAuth.OPTIONAL) + .build(); + } + ChannelCredentials channelCreds; + try (InputStream caCert = TlsTesting.loadCert("ca.pem")) { + channelCreds = TlsChannelCredentials.newBuilder() + .trustManager(caCert) + .build(); + } + Server server = grpcCleanupRule.register(server(serverCreds)); + ManagedChannel channel = grpcCleanupRule.register(clientChannel(server, channelCreds)); + + SimpleServiceGrpc.newBlockingStub(channel).unaryRpc(SimpleRequest.getDefaultInstance()); + } + + @Test + public void missingRequiredClientCert_fails() throws Exception { + ServerCredentials serverCreds; + try (InputStream serverCert = TlsTesting.loadCert("server1.pem"); + InputStream serverPrivateKey = TlsTesting.loadCert("server1.key"); + InputStream caCert = TlsTesting.loadCert("ca.pem")) { + serverCreds = TlsServerCredentials.newBuilder() + .keyManager(serverCert, serverPrivateKey) + .trustManager(caCert) + .clientAuth(TlsServerCredentials.ClientAuth.REQUIRE) + .build(); + } + ChannelCredentials channelCreds; + try (InputStream caCert = TlsTesting.loadCert("ca.pem")) { + channelCreds = TlsChannelCredentials.newBuilder() + .trustManager(caCert) + .build(); + } + Server server = grpcCleanupRule.register(server(serverCreds)); + ManagedChannel channel = grpcCleanupRule.register(clientChannel(server, channelCreds)); + + assertRpcFails(channel); + } + + @Test + public void untrustedServer_fails() throws Exception { + ServerCredentials serverCreds; + try (InputStream serverCert = TlsTesting.loadCert("badserver.pem"); + InputStream serverPrivateKey = TlsTesting.loadCert("badserver.key"); + InputStream caCert = TlsTesting.loadCert("ca.pem")) { + serverCreds = TlsServerCredentials.newBuilder() + .keyManager(serverCert, serverPrivateKey) + .trustManager(caCert) + .build(); + } + ChannelCredentials channelCreds; + try (InputStream clientCertChain = TlsTesting.loadCert("client.pem"); + InputStream clientPrivateKey = TlsTesting.loadCert("client.key"); + InputStream caCert = TlsTesting.loadCert("ca.pem")) { + channelCreds = TlsChannelCredentials.newBuilder() + .keyManager(clientCertChain, clientPrivateKey) + .trustManager(caCert) + .build(); + } + Server server = grpcCleanupRule.register(server(serverCreds)); + ManagedChannel channel = grpcCleanupRule.register(clientChannel(server, channelCreds)); + + assertRpcFails(channel); + } + + @Test + public void unmatchedServerSubjectAlternativeNames_fails() throws Exception { + ServerCredentials serverCreds; + try (InputStream serverCert = TlsTesting.loadCert("server1.pem"); + InputStream serverPrivateKey = TlsTesting.loadCert("server1.key"); + InputStream caCert = TlsTesting.loadCert("ca.pem")) { + serverCreds = TlsServerCredentials.newBuilder() + .keyManager(serverCert, serverPrivateKey) + .trustManager(caCert) + .build(); + } + ChannelCredentials channelCreds; + try (InputStream clientCertChain = TlsTesting.loadCert("client.pem"); + InputStream clientPrivateKey = TlsTesting.loadCert("client.key"); + InputStream caCert = TlsTesting.loadCert("ca.pem")) { + channelCreds = TlsChannelCredentials.newBuilder() + .keyManager(clientCertChain, clientPrivateKey) + .trustManager(caCert) + .build(); + } + Server server = grpcCleanupRule.register(server(serverCreds)); + ManagedChannel channel = grpcCleanupRule.register(clientChannelBuilder(server, channelCreds) + .overrideAuthority("notgonnamatch.example.com") + .build()); + + assertRpcFails(channel); + } + + private static Server server(ServerCredentials creds) throws IOException { + return OkHttpServerBuilder.forPort(0, creds) + .directExecutor() + .addService(new SimpleServiceImpl()) + .build() + .start(); + } + + private static ManagedChannelBuilder clientChannelBuilder( + Server server, ChannelCredentials creds) { + return OkHttpChannelBuilder.forAddress("localhost", server.getPort(), creds) + .directExecutor() + .overrideAuthority(TestUtils.TEST_SERVER_HOST); + } + + private static ManagedChannel clientChannel(Server server, ChannelCredentials creds) { + return clientChannelBuilder(server, creds).build(); + } + + private static void assertRpcFails(ManagedChannel channel) { + SimpleServiceGrpc.SimpleServiceBlockingStub stub = SimpleServiceGrpc.newBlockingStub(channel); + try { + stub.unaryRpc(SimpleRequest.getDefaultInstance()); + assertWithMessage("TLS handshake should have failed, but didn't; received RPC response") + .fail(); + } catch (StatusRuntimeException e) { + assertWithMessage(Throwables.getStackTraceAsString(e)) + .that(e.getStatus().getCode()).isEqualTo(Status.Code.UNAVAILABLE); + } + // We really want to see TRANSIENT_FAILURE here, but if the test runs slowly the 1s backoff + // may be exceeded by the time the failure happens (since it counts from the start of the + // attempt). Even so, CONNECTING is a strong indicator that the handshake failed; otherwise we'd + // expect READY or IDLE. + assertThat(channel.getState(false)) + .isAnyOf(ConnectivityState.TRANSIENT_FAILURE, ConnectivityState.CONNECTING); + } + + private static final class SimpleServiceImpl extends SimpleServiceGrpc.SimpleServiceImplBase { + @Override + public void unaryRpc(SimpleRequest req, StreamObserver respOb) { + respOb.onNext(SimpleResponse.getDefaultInstance()); + respOb.onCompleted(); + } + } +} diff --git a/services/BUILD.bazel b/services/BUILD.bazel index 2854b666ba1..f8cc6ad7620 100644 --- a/services/BUILD.bazel +++ b/services/BUILD.bazel @@ -3,12 +3,11 @@ load("//:java_grpc_library.bzl", "java_grpc_library") package(default_visibility = ["//visibility:public"]) # Mirrors the dependencies included in the artifact on Maven Central for usage -# with maven_install's override_targets. Purposefully does not export any -# symbols, as it should only be used as a dep for pre-compiled binaries on -# Maven Central. +# with maven_install's override_targets. Should only be used as a dep for +# pre-compiled binaries on Maven Central. java_library( name = "services_maven", - runtime_deps = [ + exports = [ ":admin", ":binarylog", ":channelz", diff --git a/xds/BUILD.bazel b/xds/BUILD.bazel index 6e228e636be..e62b183f9e8 100644 --- a/xds/BUILD.bazel +++ b/xds/BUILD.bazel @@ -1,13 +1,12 @@ load("//:java_grpc_library.bzl", "java_grpc_library") # Mirrors the dependencies included in the artifact on Maven Central for usage -# with maven_install's override_targets. Purposefully does not export any -# symbols, as it should only be used as a dep for pre-compiled binaries on -# Maven Central. +# with maven_install's override_targets. Should only be used as a dep for +# pre-compiled binaries on Maven Central. java_library( name = "xds_maven", visibility = ["//visibility:public"], - runtime_deps = [ + exports = [ ":orca", ":xds", ], diff --git a/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java index 2482085adfb..b225b01af7a 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java @@ -194,17 +194,7 @@ public void updateBalancingState(ConnectivityState newState, SubchannelPicker ne @Override public Subchannel createSubchannel(CreateSubchannelArgs args) { - List addresses = new ArrayList<>(); - for (EquivalentAddressGroup eag : args.getAddresses()) { - Attributes.Builder attrBuilder = eag.getAttributes().toBuilder().set( - InternalXdsAttributes.ATTR_CLUSTER_NAME, cluster); - if (enableSecurity && sslContextProviderSupplier != null) { - attrBuilder.set( - InternalXdsAttributes.ATTR_SSL_CONTEXT_PROVIDER_SUPPLIER, - sslContextProviderSupplier); - } - addresses.add(new EquivalentAddressGroup(eag.getAddresses(), attrBuilder.build())); - } + List addresses = withAdditionalAttributes(args.getAddresses()); Locality locality = args.getAddresses().get(0).getAttributes().get( InternalXdsAttributes.ATTR_LOCALITY); // all addresses should be in the same locality // Endpoint addresses resolved by ClusterResolverLoadBalancer should always contain @@ -229,6 +219,11 @@ public void shutdown() { delegate().shutdown(); } + @Override + public void updateAddresses(List addresses) { + delegate().updateAddresses(withAdditionalAttributes(addresses)); + } + @Override protected Subchannel delegate() { return subchannel; @@ -236,6 +231,22 @@ protected Subchannel delegate() { }; } + private List withAdditionalAttributes( + List addresses) { + List newAddresses = new ArrayList<>(); + for (EquivalentAddressGroup eag : addresses) { + Attributes.Builder attrBuilder = eag.getAttributes().toBuilder().set( + InternalXdsAttributes.ATTR_CLUSTER_NAME, cluster); + if (enableSecurity && sslContextProviderSupplier != null) { + attrBuilder.set( + InternalXdsAttributes.ATTR_SSL_CONTEXT_PROVIDER_SUPPLIER, + sslContextProviderSupplier); + } + newAddresses.add(new EquivalentAddressGroup(eag.getAddresses(), attrBuilder.build())); + } + return newAddresses; + } + @Override protected Helper delegate() { return helper; diff --git a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java index ca481e5691e..3af58ef93cb 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java @@ -216,7 +216,6 @@ private void handleEndpointResourceUpdate() { List addresses = new ArrayList<>(); Map priorityChildConfigs = new HashMap<>(); List priorities = new ArrayList<>(); // totally ordered priority list - Map localityWeights = new HashMap<>(); Status endpointNotFound = Status.OK; for (String cluster : clusters) { @@ -229,7 +228,6 @@ private void handleEndpointResourceUpdate() { addresses.addAll(state.result.addresses); priorityChildConfigs.putAll(state.result.priorityChildConfigs); priorities.addAll(state.result.priorities); - localityWeights.putAll(state.result.localityWeights); } else { endpointNotFound = state.status; } @@ -260,9 +258,6 @@ private void handleEndpointResourceUpdate() { resolvedAddresses.toBuilder() .setLoadBalancingPolicyConfig(childConfig) .setAddresses(Collections.unmodifiableList(addresses)) - .setAttributes(resolvedAddresses.getAttributes().toBuilder() - .set(InternalXdsAttributes.ATTR_LOCALITY_WEIGHTS, - Collections.unmodifiableMap(localityWeights)).build()) .build()); } @@ -396,7 +391,6 @@ public void run() { } Map localityLbEndpoints = update.localityLbEndpointsMap; - Map localityWeights = new HashMap<>(); List dropOverloads = update.dropPolicies; List addresses = new ArrayList<>(); Map> prioritizedLocalityWeights = new HashMap<>(); @@ -415,6 +409,8 @@ public void run() { Attributes attr = endpoint.eag().getAttributes().toBuilder() .set(InternalXdsAttributes.ATTR_LOCALITY, locality) + .set(InternalXdsAttributes.ATTR_LOCALITY_WEIGHT, + localityLbInfo.localityWeight()) .set(InternalXdsAttributes.ATTR_SERVER_WEIGHT, weight) .build(); EquivalentAddressGroup eag = new EquivalentAddressGroup( @@ -429,7 +425,6 @@ public void run() { "Discard locality {0} with 0 healthy endpoints", locality); continue; } - localityWeights.put(locality, localityLbInfo.localityWeight()); if (!prioritizedLocalityWeights.containsKey(priorityName)) { prioritizedLocalityWeights.put(priorityName, new HashMap()); } @@ -450,7 +445,7 @@ public void run() { status = Status.OK; resolved = true; result = new ClusterResolutionResult(addresses, priorityChildConfigs, - sortedPriorityNames, localityWeights); + sortedPriorityNames); handleEndpointResourceUpdate(); } } @@ -690,23 +685,18 @@ private static class ClusterResolutionResult { private final Map priorityChildConfigs; // List of priority names ordered in descending priorities. private final List priorities; - // Most recent view on how localities in the cluster should be wighted. Only set for EDS - // clusters that support the concept. - private final Map localityWeights; ClusterResolutionResult(List addresses, String priority, PriorityChildConfig config) { this(addresses, Collections.singletonMap(priority, config), - Collections.singletonList(priority), Collections.emptyMap()); + Collections.singletonList(priority)); } ClusterResolutionResult(List addresses, - Map configs, List priorities, - Map localityWeights) { + Map configs, List priorities) { this.addresses = addresses; this.priorityChildConfigs = configs; this.priorities = priorities; - this.localityWeights = localityWeights; } } diff --git a/xds/src/main/java/io/grpc/xds/InternalXdsAttributes.java b/xds/src/main/java/io/grpc/xds/InternalXdsAttributes.java index 448e5fbd258..bd21a8ac13e 100644 --- a/xds/src/main/java/io/grpc/xds/InternalXdsAttributes.java +++ b/xds/src/main/java/io/grpc/xds/InternalXdsAttributes.java @@ -24,7 +24,6 @@ import io.grpc.internal.ObjectPool; import io.grpc.xds.XdsNameResolverProvider.CallCounterProvider; import io.grpc.xds.internal.security.SslContextProviderSupplier; -import java.util.Map; /** * Internal attributes used for xDS implementation. Do not use. @@ -58,8 +57,8 @@ public final class InternalXdsAttributes { * Map from localities to their weights. */ @NameResolver.ResolutionResultAttr - static final Attributes.Key> ATTR_LOCALITY_WEIGHTS = - Attributes.Key.create("io.grpc.xds.InternalXdsAttributes.localityWeights"); + static final Attributes.Key ATTR_LOCALITY_WEIGHT = + Attributes.Key.create("io.grpc.xds.InternalXdsAttributes.localityWeight"); /** * Name of the cluster that provides this EquivalentAddressGroup. diff --git a/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java b/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java index 0a9def370e3..e833b3777b8 100644 --- a/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java @@ -37,6 +37,8 @@ import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig.PriorityChildConfig; import io.grpc.xds.XdsLogger.XdsLogLevel; import io.grpc.xds.XdsSubchannelPickers.ErrorPicker; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -59,6 +61,8 @@ final class PriorityLoadBalancer extends LoadBalancer { // Includes all active and deactivated children. Mutable. New entries are only added from priority // 0 up to the selected priority. An entry is only deleted 15 minutes after its deactivation. + // Note that calling into a child can cause the child to call back into the LB policy and modify + // the map. Therefore copy values before looping over them. private final Map children = new HashMap<>(); // Following fields are only null initially. @@ -91,15 +95,20 @@ public boolean acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { priorityNames = config.priorities; priorityConfigs = config.childConfigs; Set prioritySet = new HashSet<>(config.priorities); - for (String priority : children.keySet()) { + ArrayList childKeys = new ArrayList<>(children.keySet()); + for (String priority : childKeys) { if (!prioritySet.contains(priority)) { - children.get(priority).deactivate(); + ChildLbState childLbState = children.get(priority); + if (childLbState != null) { + childLbState.deactivate(); + } } } handlingResolvedAddresses = true; for (String priority : priorityNames) { - if (children.containsKey(priority)) { - children.get(priority).updateResolvedAddresses(); + ChildLbState childLbState = children.get(priority); + if (childLbState != null) { + childLbState.updateResolvedAddresses(); } } handlingResolvedAddresses = false; @@ -111,7 +120,8 @@ public boolean acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { public void handleNameResolutionError(Status error) { logger.log(XdsLogLevel.WARNING, "Received name resolution error: {0}", error); boolean gotoTransientFailure = true; - for (ChildLbState child : children.values()) { + Collection childValues = new ArrayList<>(children.values()); + for (ChildLbState child : childValues) { if (priorityNames.contains(child.priority)) { child.lb.handleNameResolutionError(error); gotoTransientFailure = false; @@ -125,7 +135,8 @@ public void handleNameResolutionError(Status error) { @Override public void shutdown() { logger.log(XdsLogLevel.INFO, "Shutdown"); - for (ChildLbState child : children.values()) { + Collection childValues = new ArrayList<>(children.values()); + for (ChildLbState child : childValues) { child.tearDown(); } children.clear(); diff --git a/xds/src/main/java/io/grpc/xds/RingHashLoadBalancerProvider.java b/xds/src/main/java/io/grpc/xds/RingHashLoadBalancerProvider.java index 84c6dedf631..5bba0dc9b0a 100644 --- a/xds/src/main/java/io/grpc/xds/RingHashLoadBalancerProvider.java +++ b/xds/src/main/java/io/grpc/xds/RingHashLoadBalancerProvider.java @@ -26,6 +26,7 @@ import io.grpc.Status; import io.grpc.internal.JsonUtil; import io.grpc.xds.RingHashLoadBalancer.RingHashConfig; +import io.grpc.xds.RingHashOptions; import java.util.Map; /** @@ -39,11 +40,7 @@ public final class RingHashLoadBalancerProvider extends LoadBalancerProvider { static final long DEFAULT_MIN_RING_SIZE = 1024L; // Same as ClientXdsClient.DEFAULT_RING_HASH_LB_POLICY_MAX_RING_SIZE @VisibleForTesting - static final long DEFAULT_MAX_RING_SIZE = 8 * 1024 * 1024L; - // Maximum number of ring entries allowed. Setting this too large can result in slow - // ring construction and OOM error. - // Same as ClientXdsClient.MAX_RING_HASH_LB_POLICY_RING_SIZE - static final long MAX_RING_SIZE = 8 * 1024 * 1024L; + static final long DEFAULT_MAX_RING_SIZE = 4 * 1024L; private static final boolean enableRingHash = Strings.isNullOrEmpty(System.getenv("GRPC_XDS_EXPERIMENTAL_ENABLE_RING_HASH")) @@ -73,14 +70,20 @@ public String getPolicyName() { public ConfigOrError parseLoadBalancingPolicyConfig(Map rawLoadBalancingPolicyConfig) { Long minRingSize = JsonUtil.getNumberAsLong(rawLoadBalancingPolicyConfig, "minRingSize"); Long maxRingSize = JsonUtil.getNumberAsLong(rawLoadBalancingPolicyConfig, "maxRingSize"); + long maxRingSizeCap = RingHashOptions.getRingSizeCap(); if (minRingSize == null) { minRingSize = DEFAULT_MIN_RING_SIZE; } if (maxRingSize == null) { maxRingSize = DEFAULT_MAX_RING_SIZE; } - if (minRingSize <= 0 || maxRingSize <= 0 || minRingSize > maxRingSize - || maxRingSize > MAX_RING_SIZE) { + if (minRingSize > maxRingSizeCap) { + minRingSize = maxRingSizeCap; + } + if (maxRingSize > maxRingSizeCap) { + maxRingSize = maxRingSizeCap; + } + if (minRingSize <= 0 || maxRingSize <= 0 || minRingSize > maxRingSize) { return ConfigOrError.fromError(Status.UNAVAILABLE.withDescription( "Invalid 'mingRingSize'/'maxRingSize'")); } diff --git a/xds/src/main/java/io/grpc/xds/RingHashOptions.java b/xds/src/main/java/io/grpc/xds/RingHashOptions.java new file mode 100644 index 00000000000..6bb3fc7887e --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/RingHashOptions.java @@ -0,0 +1,62 @@ +/* + * 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.ExperimentalApi; + +/** + * Utility class that provides a way to configure ring hash size limits. This is applicable + * for clients that use the ring hash load balancing policy. Note that size limits involve + * a tradeoff between client memory consumption and accuracy of load balancing weight + * representations. Also see https://github.com/grpc/proposal/pull/338. + */ +@ExperimentalApi("https://github.com/grpc/grpc-java/issues/9718") +public final class RingHashOptions { + // Same as ClientXdsClient.DEFAULT_RING_HASH_LB_POLICY_MAX_RING_SIZE + @VisibleForTesting + static final long MAX_RING_SIZE_CAP = 8 * 1024 * 1024L; + @VisibleForTesting + // Same as RingHashLoadBalancerProvider.DEFAULT_MAX_RING_SIZE + static final long DEFAULT_RING_SIZE_CAP = 4 * 1024L; + + // Limits ring hash sizes to restrict client memory usage. + private static volatile long ringSizeCap = DEFAULT_RING_SIZE_CAP; + + private RingHashOptions() {} // Prevent instantiation + + /** + * Set the global limit for the min and max number of ring hash entries per ring. + * Note that this limit is clamped between 1 entry and 8,388,608 entries, and new + * limits lying outside that range will be silently moved to the nearest number within + * that range. Defaults initially to 4096 entries. + */ + @ExperimentalApi("https://github.com/grpc/grpc-java/issues/9718") + public static void setRingSizeCap(long ringSizeCap) { + ringSizeCap = Math.max(1, ringSizeCap); + ringSizeCap = Math.min(MAX_RING_SIZE_CAP, ringSizeCap); + RingHashOptions.ringSizeCap = ringSizeCap; + } + + /** + * Get the global limit for min and max ring hash sizes. + */ + @ExperimentalApi("https://github.com/grpc/grpc-java/issues/9718") + public static long getRingSizeCap() { + return RingHashOptions.ringSizeCap; + } +} diff --git a/xds/src/main/java/io/grpc/xds/WrrLocalityLoadBalancer.java b/xds/src/main/java/io/grpc/xds/WrrLocalityLoadBalancer.java index a961db02cce..b9196492624 100644 --- a/xds/src/main/java/io/grpc/xds/WrrLocalityLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/WrrLocalityLoadBalancer.java @@ -21,6 +21,8 @@ import static io.grpc.xds.XdsLbPolicies.WEIGHTED_TARGET_POLICY_NAME; import com.google.common.base.MoreObjects; +import io.grpc.Attributes; +import io.grpc.EquivalentAddressGroup; import io.grpc.InternalLogId; import io.grpc.LoadBalancer; import io.grpc.LoadBalancerRegistry; @@ -68,15 +70,33 @@ public boolean acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { // to produce the weighted target LB config. WrrLocalityConfig wrrLocalityConfig = (WrrLocalityConfig) resolvedAddresses.getLoadBalancingPolicyConfig(); - Map localityWeights = resolvedAddresses.getAttributes() - .get(InternalXdsAttributes.ATTR_LOCALITY_WEIGHTS); - - // Not having locality weights is a misconfiguration, and we have to return with an error. - if (localityWeights == null) { - Status unavailable = - Status.UNAVAILABLE.withDescription("wrr_locality error: no locality weights provided"); - helper.updateBalancingState(TRANSIENT_FAILURE, new ErrorPicker(unavailable)); - return false; + + // A map of locality weights is built up from the locality weight attributes in each address. + Map localityWeights = new HashMap<>(); + for (EquivalentAddressGroup eag : resolvedAddresses.getAddresses()) { + Attributes eagAttrs = eag.getAttributes(); + Locality locality = eagAttrs.get(InternalXdsAttributes.ATTR_LOCALITY); + Integer localityWeight = eagAttrs.get(InternalXdsAttributes.ATTR_LOCALITY_WEIGHT); + + if (locality == null) { + helper.updateBalancingState(TRANSIENT_FAILURE, new ErrorPicker( + Status.UNAVAILABLE.withDescription("wrr_locality error: no locality provided"))); + return false; + } + if (localityWeight == null) { + helper.updateBalancingState(TRANSIENT_FAILURE, new ErrorPicker( + Status.UNAVAILABLE.withDescription( + "wrr_locality error: no weight provided for locality " + locality))); + return false; + } + + if (!localityWeights.containsKey(locality)) { + localityWeights.put(locality, localityWeight); + } else if (!localityWeights.get(locality).equals(localityWeight)) { + logger.log(XdsLogLevel.WARNING, + "Locality {0} has both weights {1} and {2}, using weight {1}", locality, + localityWeights.get(locality), localityWeight); + } } // Weighted target LB expects a WeightedPolicySelection for each locality as it will create a @@ -88,14 +108,6 @@ public boolean acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { wrrLocalityConfig.childPolicy)); } - // Remove the locality weights attribute now that we have consumed it. This is done simply for - // ease of debugging for the unsupported (and unlikely) scenario where WrrLocalityConfig has - // another wrr_locality as the child policy. The missing locality weight attribute would make - // the child wrr_locality fail early. - resolvedAddresses = resolvedAddresses.toBuilder() - .setAttributes(resolvedAddresses.getAttributes().toBuilder() - .discard(InternalXdsAttributes.ATTR_LOCALITY_WEIGHTS).build()).build(); - switchLb.switchTo(lbRegistry.getProvider(WEIGHTED_TARGET_POLICY_NAME)); switchLb.handleResolvedAddresses( resolvedAddresses.toBuilder() diff --git a/xds/src/main/java/io/grpc/xds/XdsClientImpl.java b/xds/src/main/java/io/grpc/xds/XdsClientImpl.java index a30b4755a16..26392acc733 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClientImpl.java +++ b/xds/src/main/java/io/grpc/xds/XdsClientImpl.java @@ -48,8 +48,6 @@ import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats; import io.grpc.xds.XdsClient.ResourceStore; import io.grpc.xds.XdsClient.XdsResponseHandler; -import io.grpc.xds.XdsClusterResource.CdsUpdate; -import io.grpc.xds.XdsListenerResource.LdsUpdate; import io.grpc.xds.XdsLogger.XdsLogLevel; import java.net.URI; import java.util.Collection; @@ -62,6 +60,8 @@ import java.util.Set; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.annotation.Nullable; /** @@ -69,6 +69,10 @@ */ final class XdsClientImpl extends XdsClient implements XdsResponseHandler, ResourceStore { + private static boolean LOG_XDS_NODE_ID = Boolean.parseBoolean( + System.getenv("GRPC_LOG_XDS_NODE_ID")); + private static final Logger classLogger = Logger.getLogger(XdsClientImpl.class.getName()); + // Longest time to wait, since the subscription to some resource, for concluding its absence. @VisibleForTesting static final int INITIAL_RESOURCE_FETCH_TIMEOUT_SEC = 15; @@ -107,7 +111,6 @@ public void uncaughtException(Thread t, Throwable e) { private final XdsLogger logger; private volatile boolean isShutdown; - // TODO(zdapeng): rename to XdsClientImpl XdsClientImpl( XdsChannelFactory xdsChannelFactory, Bootstrapper.BootstrapInfo bootstrapInfo, @@ -129,6 +132,9 @@ public void uncaughtException(Thread t, Throwable e) { logId = InternalLogId.allocate("xds-client", null); logger = XdsLogger.withLogId(logId); logger.log(XdsLogLevel.INFO, "Created"); + if (LOG_XDS_NODE_ID) { + classLogger.log(Level.INFO, "xDS node ID: {0}", bootstrapInfo.node().getId()); + } } private void maybeCreateXdsChannelWithLrs(ServerInfo serverInfo) { @@ -398,7 +404,6 @@ private void handleResourceUpdate(XdsResourceType.Arg xdsResourceType.typeName(), args.versionInfo, args.nonce, result.unpackedResources); Map> parsedResources = result.parsedResources; Set invalidResources = result.invalidResources; - Set retainedResources = result.retainedResources; List errors = result.errors; String errorDetail = null; if (errors.isEmpty()) { @@ -432,16 +437,14 @@ private void handleResourceUpdate(XdsResourceType.Arg } // Nothing else to do for incremental ADS resources. - if (xdsResourceType.dependentResource() == null) { + if (!xdsResourceType.isFullStateOfTheWorld()) { continue; } // Handle State of the World ADS: invalid resources. if (invalidResources.contains(resourceName)) { // The resource is missing. Reuse the cached resource if possible. - if (subscriber.data != null) { - retainDependentResource(subscriber, retainedResources); - } else { + if (subscriber.data == null) { // No cached data. Notify the watchers of an invalid update. subscriber.onError(Status.UNAVAILABLE.withDescription(errorDetail)); } @@ -451,49 +454,6 @@ private void handleResourceUpdate(XdsResourceType.Arg // For State of the World services, notify watchers when their watched resource is missing // from the ADS update. subscriber.onAbsent(); - // Retain any dependent resources if the resource deletion is ignored - // per bootstrap ignore_resource_deletion server feature. - if (!subscriber.absent) { - retainDependentResource(subscriber, retainedResources); - } - } - - // LDS/CDS responses represents the state of the world, RDS/EDS resources not referenced in - // LDS/CDS resources should be deleted. - if (xdsResourceType.dependentResource() != null) { - XdsResourceType dependency = xdsResourceType.dependentResource(); - Map> dependentSubscribers = - resourceSubscribers.get(dependency); - if (dependentSubscribers == null) { - return; - } - for (String resource : dependentSubscribers.keySet()) { - if (!retainedResources.contains(resource)) { - dependentSubscribers.get(resource).onAbsent(); - } - } - } - } - - private void retainDependentResource( - ResourceSubscriber subscriber, Set retainedResources) { - if (subscriber.data == null) { - return; - } - String resourceName = null; - if (subscriber.type == XdsListenerResource.getInstance()) { - LdsUpdate ldsUpdate = (LdsUpdate) subscriber.data; - io.grpc.xds.HttpConnectionManager hcm = ldsUpdate.httpConnectionManager(); - if (hcm != null) { - resourceName = hcm.rdsName(); - } - } else if (subscriber.type == XdsClusterResource.getInstance()) { - CdsUpdate cdsUpdate = (CdsUpdate) subscriber.data; - resourceName = cdsUpdate.edsServiceName(); - } - - if (resourceName != null) { - retainedResources.add(resourceName); } } @@ -657,9 +617,7 @@ void onAbsent() { // and the resource is reusable. boolean ignoreResourceDeletionEnabled = serverInfo != null && serverInfo.ignoreResourceDeletion(); - boolean isStateOfTheWorld = (type == XdsListenerResource.getInstance() - || type == XdsClusterResource.getInstance()); - if (ignoreResourceDeletionEnabled && isStateOfTheWorld && data != null) { + if (ignoreResourceDeletionEnabled && type.isFullStateOfTheWorld() && data != null) { if (!resourceDeletionIgnored) { logger.log(XdsLogLevel.FORCE_WARNING, "xds server {0}: ignoring deletion for resource type {1} name {2}}", diff --git a/xds/src/main/java/io/grpc/xds/XdsClusterResource.java b/xds/src/main/java/io/grpc/xds/XdsClusterResource.java index 82a977f7df4..c0d6ebeefd4 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClusterResource.java +++ b/xds/src/main/java/io/grpc/xds/XdsClusterResource.java @@ -88,10 +88,9 @@ String typeUrlV2() { return ADS_TYPE_URL_CDS_V2; } - @Nullable @Override - XdsResourceType dependentResource() { - return XdsEndpointResource.getInstance(); + boolean isFullStateOfTheWorld() { + return true; } @Override @@ -101,8 +100,7 @@ Class unpackedClassName() { } @Override - CdsUpdate doParse(Args args, Message unpackedMessage, - Set retainedResources, boolean isResourceV3) + CdsUpdate doParse(Args args, Message unpackedMessage, boolean isResourceV3) throws ResourceInvalidException { if (!(unpackedMessage instanceof Cluster)) { throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass()); @@ -111,12 +109,12 @@ CdsUpdate doParse(Args args, Message unpackedMessage, if (args.bootstrapInfo != null && args.bootstrapInfo.certProviders() != null) { certProviderInstances = args.bootstrapInfo.certProviders().keySet(); } - return processCluster((Cluster) unpackedMessage, retainedResources, certProviderInstances, + return processCluster((Cluster) unpackedMessage, certProviderInstances, args.serverInfo, args.loadBalancerRegistry); } @VisibleForTesting - static CdsUpdate processCluster(Cluster cluster, Set retainedEdsResources, + static CdsUpdate processCluster(Cluster cluster, Set certProviderInstances, Bootstrapper.ServerInfo serverInfo, LoadBalancerRegistry loadBalancerRegistry) @@ -124,7 +122,7 @@ static CdsUpdate processCluster(Cluster cluster, Set retainedEdsResource StructOrError structOrError; switch (cluster.getClusterDiscoveryTypeCase()) { case TYPE: - structOrError = parseNonAggregateCluster(cluster, retainedEdsResources, + structOrError = parseNonAggregateCluster(cluster, certProviderInstances, serverInfo); break; case CLUSTER_TYPE: @@ -178,8 +176,7 @@ private static StructOrError parseAggregateCluster(Cluster cl } private static StructOrError parseNonAggregateCluster( - Cluster cluster, Set edsResources, Set certProviderInstances, - Bootstrapper.ServerInfo serverInfo) { + Cluster cluster, Set certProviderInstances, Bootstrapper.ServerInfo serverInfo) { String clusterName = cluster.getName(); Bootstrapper.ServerInfo lrsServerInfo = null; Long maxConcurrentRequests = null; @@ -249,9 +246,6 @@ private static StructOrError parseNonAggregateCluster( // If the service_name field is set, that value will be used for the EDS request. if (!edsClusterConfig.getServiceName().isEmpty()) { edsServiceName = edsClusterConfig.getServiceName(); - edsResources.add(edsServiceName); - } else { - edsResources.add(clusterName); } return StructOrError.fromStruct(CdsUpdate.forEds( clusterName, edsServiceName, lrsServerInfo, maxConcurrentRequests, upstreamTlsContext, diff --git a/xds/src/main/java/io/grpc/xds/XdsEndpointResource.java b/xds/src/main/java/io/grpc/xds/XdsEndpointResource.java index c126d643311..a44fea0c575 100644 --- a/xds/src/main/java/io/grpc/xds/XdsEndpointResource.java +++ b/xds/src/main/java/io/grpc/xds/XdsEndpointResource.java @@ -78,10 +78,9 @@ String typeUrlV2() { return ADS_TYPE_URL_EDS_V2; } - @Nullable @Override - XdsResourceType dependentResource() { - return null; + boolean isFullStateOfTheWorld() { + return false; } @Override @@ -90,8 +89,7 @@ Class unpackedClassName() { } @Override - EdsUpdate doParse(Args args, Message unpackedMessage, - Set retainedResources, boolean isResourceV3) + EdsUpdate doParse(Args args, Message unpackedMessage, boolean isResourceV3) throws ResourceInvalidException { if (!(unpackedMessage instanceof ClusterLoadAssignment)) { throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass()); diff --git a/xds/src/main/java/io/grpc/xds/XdsListenerResource.java b/xds/src/main/java/io/grpc/xds/XdsListenerResource.java index 5f7d6a27aa4..f367bd96908 100644 --- a/xds/src/main/java/io/grpc/xds/XdsListenerResource.java +++ b/xds/src/main/java/io/grpc/xds/XdsListenerResource.java @@ -97,15 +97,13 @@ String typeUrlV2() { return ADS_TYPE_URL_LDS_V2; } - @Nullable @Override - XdsResourceType dependentResource() { - return XdsRouteConfigureResource.getInstance(); + boolean isFullStateOfTheWorld() { + return true; } @Override - LdsUpdate doParse(Args args, Message unpackedMessage, Set retainedResources, - boolean isResourceV3) + LdsUpdate doParse(Args args, Message unpackedMessage, boolean isResourceV3) throws ResourceInvalidException { if (!(unpackedMessage instanceof Listener)) { throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass()); @@ -114,15 +112,14 @@ LdsUpdate doParse(Args args, Message unpackedMessage, Set retainedResour if (listener.hasApiListener()) { return processClientSideListener( - listener, retainedResources, args, enableFaultInjection && isResourceV3); + listener, args, enableFaultInjection && isResourceV3); } else { return processServerSideListener( - listener, retainedResources, args, enableRbac && isResourceV3); + listener, args, enableRbac && isResourceV3); } } - private LdsUpdate processClientSideListener( - Listener listener, Set rdsResources, Args args, boolean parseHttpFilter) + private LdsUpdate processClientSideListener(Listener listener, Args args, boolean parseHttpFilter) throws ResourceInvalidException { // Unpack HttpConnectionManager from the Listener. HttpConnectionManager hcm; @@ -135,24 +132,22 @@ private LdsUpdate processClientSideListener( "Could not parse HttpConnectionManager config from ApiListener", e); } return LdsUpdate.forApiListener(parseHttpConnectionManager( - hcm, rdsResources, args.filterRegistry, parseHttpFilter, true /* isForClient */)); + hcm, args.filterRegistry, parseHttpFilter, true /* isForClient */)); } - private LdsUpdate processServerSideListener( - Listener proto, Set rdsResources, Args args, boolean parseHttpFilter) + private LdsUpdate processServerSideListener(Listener proto, Args args, boolean parseHttpFilter) throws ResourceInvalidException { Set certProviderInstances = null; if (args.bootstrapInfo != null && args.bootstrapInfo.certProviders() != null) { certProviderInstances = args.bootstrapInfo.certProviders().keySet(); } - return LdsUpdate.forTcpListener(parseServerSideListener( - proto, rdsResources, args.tlsContextManager, args.filterRegistry, certProviderInstances, - parseHttpFilter)); + return LdsUpdate.forTcpListener(parseServerSideListener(proto, args.tlsContextManager, + args.filterRegistry, certProviderInstances, parseHttpFilter)); } @VisibleForTesting static EnvoyServerProtoData.Listener parseServerSideListener( - Listener proto, Set rdsResources, TlsContextManager tlsContextManager, + Listener proto, TlsContextManager tlsContextManager, FilterRegistry filterRegistry, Set certProviderInstances, boolean parseHttpFilter) throws ResourceInvalidException { if (!proto.getTrafficDirection().equals(TrafficDirection.INBOUND) @@ -190,13 +185,13 @@ static EnvoyServerProtoData.Listener parseServerSideListener( Set uniqueSet = new HashSet<>(); for (io.envoyproxy.envoy.config.listener.v3.FilterChain fc : proto.getFilterChainsList()) { filterChains.add( - parseFilterChain(fc, rdsResources, tlsContextManager, filterRegistry, uniqueSet, + parseFilterChain(fc, tlsContextManager, filterRegistry, uniqueSet, certProviderInstances, parseHttpFilter)); } FilterChain defaultFilterChain = null; if (proto.hasDefaultFilterChain()) { defaultFilterChain = parseFilterChain( - proto.getDefaultFilterChain(), rdsResources, tlsContextManager, filterRegistry, + proto.getDefaultFilterChain(), tlsContextManager, filterRegistry, null, certProviderInstances, parseHttpFilter); } @@ -206,7 +201,7 @@ static EnvoyServerProtoData.Listener parseServerSideListener( @VisibleForTesting static FilterChain parseFilterChain( - io.envoyproxy.envoy.config.listener.v3.FilterChain proto, Set rdsResources, + io.envoyproxy.envoy.config.listener.v3.FilterChain proto, TlsContextManager tlsContextManager, FilterRegistry filterRegistry, Set uniqueSet, Set certProviderInstances, boolean parseHttpFilters) throws ResourceInvalidException { @@ -235,7 +230,7 @@ static FilterChain parseFilterChain( + filter.getName() + " failed to unpack message", e); } io.grpc.xds.HttpConnectionManager httpConnectionManager = parseHttpConnectionManager( - hcmProto, rdsResources, filterRegistry, parseHttpFilters, false /* isForClient */); + hcmProto, filterRegistry, parseHttpFilters, false /* isForClient */); EnvoyServerProtoData.DownstreamTlsContext downstreamTlsContext = null; if (proto.hasTransportSocket()) { @@ -466,7 +461,7 @@ private static FilterChainMatch parseFilterChainMatch( @VisibleForTesting static io.grpc.xds.HttpConnectionManager parseHttpConnectionManager( - HttpConnectionManager proto, Set rdsResources, FilterRegistry filterRegistry, + HttpConnectionManager proto, FilterRegistry filterRegistry, boolean parseHttpFilter, boolean isForClient) throws ResourceInvalidException { if (enableRbac && proto.getXffNumTrustedHops() != 0) { throw new ResourceInvalidException( @@ -541,8 +536,6 @@ static io.grpc.xds.HttpConnectionManager parseHttpConnectionManager( throw new ResourceInvalidException( "HttpConnectionManager contains invalid RDS: must specify ADS or self ConfigSource"); } - // Collect the RDS resource referenced by this HttpConnectionManager. - rdsResources.add(rds.getRouteConfigName()); return io.grpc.xds.HttpConnectionManager.forRdsName( maxStreamDuration, rds.getRouteConfigName(), filterConfigs); } diff --git a/xds/src/main/java/io/grpc/xds/XdsResourceType.java b/xds/src/main/java/io/grpc/xds/XdsResourceType.java index a377ee35d7a..987151d899d 100644 --- a/xds/src/main/java/io/grpc/xds/XdsResourceType.java +++ b/xds/src/main/java/io/grpc/xds/XdsResourceType.java @@ -85,9 +85,12 @@ abstract class XdsResourceType { abstract String typeUrlV2(); - // Non-null for State of the World resources. - @Nullable - abstract XdsResourceType dependentResource(); + // Do not confuse with the SotW approach: it is the mechanism in which the client must specify all + // resource names it is interested in with each request. Different resource types may behave + // differently in this approach. For LDS and CDS resources, the server must return all resources + // that the client has subscribed to in each request. For RDS and EDS, the server may only return + // the resources that need an update. + abstract boolean isFullStateOfTheWorld(); static class Args { final ServerInfo serverInfo; @@ -125,7 +128,6 @@ ValidatedResourceUpdate parse(Args args, List resources) { Set unpackedResources = new HashSet<>(resources.size()); Set invalidResources = new HashSet<>(); List errors = new ArrayList<>(); - Set retainedResources = new HashSet<>(); for (int i = 0; i < resources.size(); i++) { Any resource = resources.get(i); @@ -156,7 +158,7 @@ ValidatedResourceUpdate parse(Args args, List resources) { T resourceUpdate; try { - resourceUpdate = doParse(args, unpackedMessage, retainedResources, isResourceV3); + resourceUpdate = doParse(args, unpackedMessage, isResourceV3); } catch (XdsClientImpl.ResourceInvalidException e) { errors.add(String.format("%s response %s '%s' validation error: %s", typeName(), unpackedClassName().getSimpleName(), cname, e.getMessage())); @@ -168,12 +170,11 @@ ValidatedResourceUpdate parse(Args args, List resources) { parsedResources.put(cname, new ParsedResource(resourceUpdate, resource)); } return new ValidatedResourceUpdate(parsedResources, unpackedResources, invalidResources, - errors, retainedResources); + errors); } - abstract T doParse(Args args, Message unpackedMessage, Set retainedResources, - boolean isResourceV3) + abstract T doParse(Args args, Message unpackedMessage, boolean isResourceV3) throws ResourceInvalidException; /** @@ -231,19 +232,16 @@ static final class ValidatedResourceUpdate { Set unpackedResources; Set invalidResources; List errors; - Set retainedResources; // validated resource update public ValidatedResourceUpdate(Map> parsedResources, Set unpackedResources, Set invalidResources, - List errors, - Set retainedResources) { + List errors) { this.parsedResources = parsedResources; this.unpackedResources = unpackedResources; this.invalidResources = invalidResources; this.errors = errors; - this.retainedResources = retainedResources; } } diff --git a/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java b/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java index 166809c87e1..e6c73d2df28 100644 --- a/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java +++ b/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java @@ -107,10 +107,9 @@ String typeUrlV2() { return ADS_TYPE_URL_RDS_V2; } - @Nullable @Override - XdsResourceType dependentResource() { - return null; + boolean isFullStateOfTheWorld() { + return false; } @Override @@ -119,8 +118,7 @@ Class unpackedClassName() { } @Override - RdsUpdate doParse(XdsResourceType.Args args, Message unpackedMessage, - Set retainedResources, boolean isResourceV3) + RdsUpdate doParse(XdsResourceType.Args args, Message unpackedMessage, boolean isResourceV3) throws ResourceInvalidException { if (!(unpackedMessage instanceof RouteConfiguration)) { throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass()); @@ -513,6 +511,7 @@ static StructOrError parseRouteAction( return StructOrError.fromError("No cluster found in weighted cluster list"); } List weightedClusters = new ArrayList<>(); + int clusterWeightSum = 0; for (io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight clusterWeight : clusterWeights) { StructOrError clusterWeightOrError = @@ -521,9 +520,12 @@ static StructOrError parseRouteAction( return StructOrError.fromError("RouteAction contains invalid ClusterWeight: " + clusterWeightOrError.getErrorDetail()); } + clusterWeightSum += clusterWeight.getWeight().getValue(); weightedClusters.add(clusterWeightOrError.getStruct()); } - // TODO(chengyuanzhang): validate if the sum of weights equals to total weight. + if (clusterWeightSum <= 0) { + return StructOrError.fromError("Sum of cluster weights should be above 0."); + } return StructOrError.fromStruct(VirtualHost.Route.RouteAction.forWeightedClusters( weightedClusters, hashPolicies, timeoutNano, retryPolicy)); case CLUSTER_SPECIFIER_PLUGIN: diff --git a/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java index 142786280a8..6d19e166a0b 100644 --- a/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java @@ -470,6 +470,7 @@ public void endpointAddressesAttachedWithClusterName() { assertThat(downstreamBalancers).hasSize(1); // one leaf balancer FakeLoadBalancer leafBalancer = Iterables.getOnlyElement(downstreamBalancers); assertThat(leafBalancer.name).isEqualTo("round_robin"); + // Simulates leaf load balancer creating subchannels. CreateSubchannelArgs args = CreateSubchannelArgs.newBuilder() @@ -480,6 +481,13 @@ public void endpointAddressesAttachedWithClusterName() { assertThat(eag.getAttributes().get(InternalXdsAttributes.ATTR_CLUSTER_NAME)) .isEqualTo(CLUSTER); } + + // An address update should also retain the cluster attribute. + subchannel.updateAddresses(leafBalancer.addresses); + for (EquivalentAddressGroup eag : subchannel.getAllAddresses()) { + assertThat(eag.getAttributes().get(InternalXdsAttributes.ATTR_CLUSTER_NAME)) + .isEqualTo(CLUSTER); + } } @Test @@ -761,6 +769,10 @@ public List getAllAddresses() { public Attributes getAttributes() { return attrs; } + + @Override + public void updateAddresses(List addrs) { + } } private final class FakeXdsClient extends XdsClient { diff --git a/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java index b4b709d2ae2..4d58f88e0e8 100644 --- a/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java @@ -327,8 +327,8 @@ public void edsClustersWithLeastRequestEndpointLbPolicy() { "least_request_experimental"); assertThat( - childBalancer.attributes.get(InternalXdsAttributes.ATTR_LOCALITY_WEIGHTS)).containsEntry( - locality1, 100); + childBalancer.addresses.get(0).getAttributes() + .get(InternalXdsAttributes.ATTR_LOCALITY_WEIGHT)).isEqualTo(100); } @Test @@ -410,8 +410,8 @@ public void edsClustersWithOutlierDetection() { "least_request_experimental"); assertThat( - childBalancer.attributes.get(InternalXdsAttributes.ATTR_LOCALITY_WEIGHTS)).containsEntry( - locality1, 100); + childBalancer.addresses.get(0).getAttributes() + .get(InternalXdsAttributes.ATTR_LOCALITY_WEIGHT)).isEqualTo(100); } @@ -507,11 +507,20 @@ public void onlyEdsClusters_receivedEndpoints() { assertThat(wrrLocalityConfig3.childPolicy.getProvider().getPolicyName()).isEqualTo( "round_robin"); - Map localityWeights = childBalancer.attributes.get( - InternalXdsAttributes.ATTR_LOCALITY_WEIGHTS); - assertThat(localityWeights).containsEntry(locality1, 70); - assertThat(localityWeights).containsEntry(locality2, 10); - assertThat(localityWeights).containsEntry(locality3, 20); + for (EquivalentAddressGroup eag : childBalancer.addresses) { + if (eag.getAttributes().get(InternalXdsAttributes.ATTR_LOCALITY) == locality1) { + assertThat(eag.getAttributes().get(InternalXdsAttributes.ATTR_LOCALITY_WEIGHT)) + .isEqualTo(70); + } + if (eag.getAttributes().get(InternalXdsAttributes.ATTR_LOCALITY) == locality2) { + assertThat(eag.getAttributes().get(InternalXdsAttributes.ATTR_LOCALITY_WEIGHT)) + .isEqualTo(10); + } + if (eag.getAttributes().get(InternalXdsAttributes.ATTR_LOCALITY) == locality3) { + assertThat(eag.getAttributes().get(InternalXdsAttributes.ATTR_LOCALITY_WEIGHT)) + .isEqualTo(20); + } + } } @SuppressWarnings("unchecked") @@ -687,9 +696,9 @@ public void handleEdsResource_ignoreLocalitiesWithNoHealthyEndpoints() { ImmutableMap.of(locality1, localityLbEndpoints1, locality2, localityLbEndpoints2)); FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers); - Map localityWeights = childBalancer.attributes.get( - InternalXdsAttributes.ATTR_LOCALITY_WEIGHTS); - assertThat(localityWeights.keySet()).containsExactly(locality2); + for (EquivalentAddressGroup eag : childBalancer.addresses) { + assertThat(eag.getAttributes().get(InternalXdsAttributes.ATTR_LOCALITY)).isEqualTo(locality2); + } } @Test @@ -1315,7 +1324,6 @@ private final class FakeLoadBalancer extends LoadBalancer { private final Helper helper; private List addresses; private Object config; - private Attributes attributes; private Status upstreamError; private boolean shutdown; @@ -1328,7 +1336,6 @@ private final class FakeLoadBalancer extends LoadBalancer { public boolean acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { addresses = resolvedAddresses.getAddresses(); config = resolvedAddresses.getLoadBalancingPolicyConfig(); - attributes = resolvedAddresses.getAttributes(); return true; } diff --git a/xds/src/test/java/io/grpc/xds/FakeControlPlaneXdsIntegrationTest.java b/xds/src/test/java/io/grpc/xds/FakeControlPlaneXdsIntegrationTest.java index 3f0927fb8d3..0c3cf61b28e 100644 --- a/xds/src/test/java/io/grpc/xds/FakeControlPlaneXdsIntegrationTest.java +++ b/xds/src/test/java/io/grpc/xds/FakeControlPlaneXdsIntegrationTest.java @@ -51,6 +51,22 @@ /** * Xds integration tests using a local control plane, implemented in {@link * XdsTestControlPlaneService}. Test cases can inject xds configs to the control plane for testing. + * + *

Test components: + * 1) A Control Plane {@link XdsTestControlPlaneService} accepts xds requests from multiple clients + * from the Data Plane, see {@link ControlPlaneRule}. + * 2) A test xDS server {@link XdsServerWrapper}, see {@link DataPlaneRule}. + * 3) A test xDS client that uses a testing scheme {@link XdsNameResolverProvider#createForTest}, + * see {@link DataPlaneRule}. + * + *

The configuration dependency and ephemeral port allocation requires the components to + * be initialized in a certain order: + * 1) Start the Control Plane server {@link XdsTestControlPlaneService}. After start the bootstrap + * information (w/ Control Plane's address) can be constructed for the Data Plane to initialize. + * 2) Set LDS and RDS config at the Control Plane. Get the bootstrap file from the Control + * Plane from 1). And then start the test xDS server (requires LDS/RDS and bootstrap file to start). + * 3) Construct EDS config w/ test server address from 2). Set CDS and EDS Config at the Control + * Plane. Then start the test xDS client (requires EDS to do xDS name resolution). */ @RunWith(JUnit4.class) public class FakeControlPlaneXdsIntegrationTest { diff --git a/xds/src/test/java/io/grpc/xds/RingHashLoadBalancerProviderTest.java b/xds/src/test/java/io/grpc/xds/RingHashLoadBalancerProviderTest.java index 9f972eaef92..87615a125c0 100644 --- a/xds/src/test/java/io/grpc/xds/RingHashLoadBalancerProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/RingHashLoadBalancerProviderTest.java @@ -29,6 +29,7 @@ import io.grpc.SynchronizationContext; import io.grpc.internal.JsonParser; import io.grpc.xds.RingHashLoadBalancer.RingHashConfig; +import io.grpc.xds.RingHashOptions; import java.io.IOException; import java.lang.Thread.UncaughtExceptionHandler; import java.util.Locale; @@ -116,16 +117,84 @@ public void parseLoadBalancingConfig_invalid_minGreaterThanMax() throws IOExcept } @Test - public void parseLoadBalancingConfig_invalid_ringTooLarge() throws IOException { - long ringSize = RingHashLoadBalancerProvider.MAX_RING_SIZE + 1; + public void parseLoadBalancingConfig_ringTooLargeUsesCap() throws IOException { + long ringSize = RingHashOptions.MAX_RING_SIZE_CAP + 1; String lbConfig = String.format(Locale.US, "{\"minRingSize\" : 10, \"maxRingSize\" : %d}", ringSize); ConfigOrError configOrError = provider.parseLoadBalancingPolicyConfig(parseJsonObject(lbConfig)); - assertThat(configOrError.getError()).isNotNull(); - assertThat(configOrError.getError().getCode()).isEqualTo(Code.UNAVAILABLE); - assertThat(configOrError.getError().getDescription()) - .isEqualTo("Invalid 'mingRingSize'/'maxRingSize'"); + assertThat(configOrError.getConfig()).isNotNull(); + RingHashConfig config = (RingHashConfig) configOrError.getConfig(); + assertThat(config.minRingSize).isEqualTo(10); + assertThat(config.maxRingSize).isEqualTo(RingHashOptions.DEFAULT_RING_SIZE_CAP); + } + + @Test + public void parseLoadBalancingConfig_ringCapCanBeRaised() throws IOException { + RingHashOptions.setRingSizeCap(RingHashOptions.MAX_RING_SIZE_CAP); + long ringSize = RingHashOptions.MAX_RING_SIZE_CAP; + String lbConfig = + String.format( + Locale.US, "{\"minRingSize\" : %d, \"maxRingSize\" : %d}", ringSize, ringSize); + ConfigOrError configOrError = + provider.parseLoadBalancingPolicyConfig(parseJsonObject(lbConfig)); + assertThat(configOrError.getConfig()).isNotNull(); + RingHashConfig config = (RingHashConfig) configOrError.getConfig(); + assertThat(config.minRingSize).isEqualTo(RingHashOptions.MAX_RING_SIZE_CAP); + assertThat(config.maxRingSize).isEqualTo(RingHashOptions.MAX_RING_SIZE_CAP); + // Reset to avoid affecting subsequent test cases + RingHashOptions.setRingSizeCap(RingHashOptions.DEFAULT_RING_SIZE_CAP); + } + + @Test + public void parseLoadBalancingConfig_ringCapIsClampedTo8M() throws IOException { + RingHashOptions.setRingSizeCap(RingHashOptions.MAX_RING_SIZE_CAP + 1); + long ringSize = RingHashOptions.MAX_RING_SIZE_CAP + 1; + String lbConfig = + String.format( + Locale.US, "{\"minRingSize\" : %d, \"maxRingSize\" : %d}", ringSize, ringSize); + ConfigOrError configOrError = + provider.parseLoadBalancingPolicyConfig(parseJsonObject(lbConfig)); + assertThat(configOrError.getConfig()).isNotNull(); + RingHashConfig config = (RingHashConfig) configOrError.getConfig(); + assertThat(config.minRingSize).isEqualTo(RingHashOptions.MAX_RING_SIZE_CAP); + assertThat(config.maxRingSize).isEqualTo(RingHashOptions.MAX_RING_SIZE_CAP); + // Reset to avoid affecting subsequent test cases + RingHashOptions.setRingSizeCap(RingHashOptions.DEFAULT_RING_SIZE_CAP); + } + + @Test + public void parseLoadBalancingConfig_ringCapCanBeLowered() throws IOException { + RingHashOptions.setRingSizeCap(1); + long ringSize = 2; + String lbConfig = + String.format( + Locale.US, "{\"minRingSize\" : %d, \"maxRingSize\" : %d}", ringSize, ringSize); + ConfigOrError configOrError = + provider.parseLoadBalancingPolicyConfig(parseJsonObject(lbConfig)); + assertThat(configOrError.getConfig()).isNotNull(); + RingHashConfig config = (RingHashConfig) configOrError.getConfig(); + assertThat(config.minRingSize).isEqualTo(1); + assertThat(config.maxRingSize).isEqualTo(1); + // Reset to avoid affecting subsequent test cases + RingHashOptions.setRingSizeCap(RingHashOptions.DEFAULT_RING_SIZE_CAP); + } + + @Test + public void parseLoadBalancingConfig_ringCapLowerLimitIs1() throws IOException { + RingHashOptions.setRingSizeCap(0); + long ringSize = 2; + String lbConfig = + String.format( + Locale.US, "{\"minRingSize\" : %d, \"maxRingSize\" : %d}", ringSize, ringSize); + ConfigOrError configOrError = + provider.parseLoadBalancingPolicyConfig(parseJsonObject(lbConfig)); + assertThat(configOrError.getConfig()).isNotNull(); + RingHashConfig config = (RingHashConfig) configOrError.getConfig(); + assertThat(config.minRingSize).isEqualTo(1); + assertThat(config.maxRingSize).isEqualTo(1); + // Reset to avoid affecting subsequent test cases + RingHashOptions.setRingSizeCap(RingHashOptions.DEFAULT_RING_SIZE_CAP); } @Test diff --git a/xds/src/test/java/io/grpc/xds/WrrLocalityLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/WrrLocalityLoadBalancerTest.java index 29777a5284f..344876aa348 100644 --- a/xds/src/test/java/io/grpc/xds/WrrLocalityLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/WrrLocalityLoadBalancerTest.java @@ -25,7 +25,6 @@ import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.common.testing.EqualsTester; import io.grpc.Attributes; import io.grpc.ConnectivityState; @@ -44,7 +43,9 @@ import io.grpc.xds.WrrLocalityLoadBalancer.WrrLocalityConfig; import io.grpc.xds.XdsSubchannelPickers.ErrorPicker; import java.net.SocketAddress; -import java.util.Map; +import java.util.Collections; +import java.util.List; +import java.util.Objects; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -74,8 +75,6 @@ public class WrrLocalityLoadBalancerTest { private LoadBalancer mockChildLb; @Mock private Helper mockHelper; - @Mock - private SocketAddress mockSocketAddress; @Captor private ArgumentCaptor resolvedAddressesCaptor; @@ -84,8 +83,6 @@ public class WrrLocalityLoadBalancerTest { @Captor private ArgumentCaptor errorPickerCaptor; - private final EquivalentAddressGroup eag = new EquivalentAddressGroup(mockSocketAddress); - private WrrLocalityLoadBalancer loadBalancer; private LoadBalancerRegistry lbRegistry = new LoadBalancerRegistry(); @@ -124,8 +121,10 @@ public void handleResolvedAddresses() { // The child config is delivered wrapped in the wrr_locality config and the locality weights // in a ResolvedAddresses attribute. WrrLocalityConfig wlConfig = new WrrLocalityConfig(childPolicy); - Map localityWeights = ImmutableMap.of(localityOne, 1, localityTwo, 2); - deliverAddresses(wlConfig, localityWeights); + deliverAddresses(wlConfig, + ImmutableList.of( + makeAddress("addr1", localityOne, 1), + makeAddress("addr2", localityTwo, 2))); // Assert that the child policy and the locality weights were correctly mapped to a // WeightedTargetConfig. @@ -148,7 +147,8 @@ public void handleResolvedAddresses_noLocalityWeights() { // The child config is delivered wrapped in the wrr_locality config and the locality weights // in a ResolvedAddresses attribute. WrrLocalityConfig wlConfig = new WrrLocalityConfig(childPolicy); - deliverAddresses(wlConfig, null); + deliverAddresses(wlConfig, ImmutableList.of( + makeAddress("addr", Locality.create("test-region", "test-zone", "test-subzone"), null))); // With no locality weights, we should get a TRANSIENT_FAILURE. verify(mockHelper).getAuthority(); @@ -170,8 +170,8 @@ public void handleNameResolutionError_noChildLb() { @Test public void handleNameResolutionError_withChildLb() { deliverAddresses(new WrrLocalityConfig(new PolicySelection(mockChildProvider, null)), - ImmutableMap.of( - Locality.create("region", "zone", "subzone"), 1)); + ImmutableList.of( + makeAddress("addr1", Locality.create("test-region1", "test-zone", "test-subzone"), 1))); loadBalancer.handleNameResolutionError(Status.DEADLINE_EXCEEDED); verify(mockHelper, never()).updateBalancingState(isA(ConnectivityState.class), @@ -181,25 +181,25 @@ public void handleNameResolutionError_withChildLb() { @Test public void localityWeightAttributeNotPropagated() { - Locality locality = Locality.create("region1", "zone1", "subzone1"); PolicySelection childPolicy = new PolicySelection(mockChildProvider, null); WrrLocalityConfig wlConfig = new WrrLocalityConfig(childPolicy); - Map localityWeights = ImmutableMap.of(locality, 1); - deliverAddresses(wlConfig, localityWeights); + deliverAddresses(wlConfig, ImmutableList.of( + makeAddress("addr1", Locality.create("test-region1", "test-zone", "test-subzone"), 1))); // Assert that the child policy and the locality weights were correctly mapped to a // WeightedTargetConfig. verify(mockWeightedTargetLb).handleResolvedAddresses(resolvedAddressesCaptor.capture()); - assertThat(resolvedAddressesCaptor.getValue().getAttributes() - .get(InternalXdsAttributes.ATTR_LOCALITY_WEIGHTS)).isNull(); + + //assertThat(resolvedAddressesCaptor.getValue().getAttributes() + // .get(InternalXdsAttributes.ATTR_LOCALITY_WEIGHTS)).isNull(); } @Test public void shutdown() { deliverAddresses(new WrrLocalityConfig(new PolicySelection(mockChildProvider, null)), - ImmutableMap.of( - Locality.create("region", "zone", "subzone"), 1)); + ImmutableList.of( + makeAddress("addr", Locality.create("test-region", "test-zone", "test-subzone"), 1))); loadBalancer.shutdown(); verify(mockWeightedTargetLb).shutdown(); @@ -218,11 +218,55 @@ public void configEquality() { .testEquals(); } - private void deliverAddresses(WrrLocalityConfig config, Map localityWeights) { + private void deliverAddresses(WrrLocalityConfig config, List addresses) { loadBalancer.handleResolvedAddresses( - ResolvedAddresses.newBuilder().setAddresses(ImmutableList.of(eag)).setAttributes( - Attributes.newBuilder() - .set(InternalXdsAttributes.ATTR_LOCALITY_WEIGHTS, localityWeights).build()) - .setLoadBalancingPolicyConfig(config).build()); + ResolvedAddresses.newBuilder().setAddresses(addresses).setLoadBalancingPolicyConfig(config) + .build()); + } + + /** + * Create a locality-labeled address. + */ + private static EquivalentAddressGroup makeAddress(final String name, Locality locality, + Integer localityWeight) { + class FakeSocketAddress extends SocketAddress { + private final String name; + + private FakeSocketAddress(String name) { + this.name = name; + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof FakeSocketAddress)) { + return false; + } + FakeSocketAddress that = (FakeSocketAddress) o; + return Objects.equals(name, that.name); + } + + @Override + public String toString() { + return name; + } + } + + Attributes.Builder attrBuilder = Attributes.newBuilder() + .set(InternalXdsAttributes.ATTR_LOCALITY, locality); + if (localityWeight != null) { + attrBuilder.set(InternalXdsAttributes.ATTR_LOCALITY_WEIGHT, localityWeight); + } + + EquivalentAddressGroup eag = new EquivalentAddressGroup(new FakeSocketAddress(name), + attrBuilder.build()); + return AddressFilter.setPathFilter(eag, Collections.singletonList(locality.toString())); } } diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplDataTest.java b/xds/src/test/java/io/grpc/xds/XdsClientImplDataTest.java index 993fb910f3e..f47239c123d 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplDataTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplDataTest.java @@ -135,10 +135,8 @@ import io.grpc.xds.internal.Matchers.HeaderMatcher; import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; @@ -523,6 +521,28 @@ public void parseRouteAction_withWeightedCluster() { ClusterWeight.create("cluster-bar", 70, ImmutableMap.of())); } + @Test + public void parseRouteAction_weightedClusterSum() { + io.envoyproxy.envoy.config.route.v3.RouteAction proto = + io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() + .setWeightedClusters( + WeightedCluster.newBuilder() + .addClusters( + WeightedCluster.ClusterWeight + .newBuilder() + .setName("cluster-foo") + .setWeight(UInt32Value.newBuilder().setValue(0))) + .addClusters(WeightedCluster.ClusterWeight + .newBuilder() + .setName("cluster-bar") + .setWeight(UInt32Value.newBuilder().setValue(0)))) + .build(); + StructOrError struct = + XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, + ImmutableMap.of(), ImmutableSet.of()); + assertThat(struct.getErrorDetail()).isEqualTo("Sum of cluster weights should be above 0."); + } + @Test public void parseRouteAction_withTimeoutByGrpcTimeoutHeaderMax() { io.envoyproxy.envoy.config.route.v3.RouteAction proto = @@ -1301,7 +1321,7 @@ public void parseHttpConnectionManager_xffNumTrustedHopsUnsupported() thrown.expect(ResourceInvalidException.class); thrown.expectMessage("HttpConnectionManager with xff_num_trusted_hops unsupported"); XdsListenerResource.parseHttpConnectionManager( - hcm, new HashSet(), filterRegistry, false /* does not matter */, + hcm, filterRegistry, false /* does not matter */, true /* does not matter */); } @@ -1315,7 +1335,7 @@ public void parseHttpConnectionManager_OriginalIpDetectionExtensionsMustEmpty() thrown.expect(ResourceInvalidException.class); thrown.expectMessage("HttpConnectionManager with original_ip_detection_extensions unsupported"); XdsListenerResource.parseHttpConnectionManager( - hcm, new HashSet(), filterRegistry, false /* does not matter */, false); + hcm, filterRegistry, false /* does not matter */, false); } @Test @@ -1330,7 +1350,7 @@ public void parseHttpConnectionManager_missingRdsAndInlinedRouteConfiguration() thrown.expect(ResourceInvalidException.class); thrown.expectMessage("HttpConnectionManager neither has inlined route_config nor RDS"); XdsListenerResource.parseHttpConnectionManager( - hcm, new HashSet(), filterRegistry, false /* does not matter */, + hcm, filterRegistry, false /* does not matter */, true /* does not matter */); } @@ -1349,7 +1369,7 @@ public void parseHttpConnectionManager_duplicateHttpFilters() throws ResourceInv thrown.expect(ResourceInvalidException.class); thrown.expectMessage("HttpConnectionManager contains duplicate HttpFilter: envoy.filter.foo"); XdsListenerResource.parseHttpConnectionManager( - hcm, new HashSet(), filterRegistry, true /* parseHttpFilter */, + hcm, filterRegistry, true /* parseHttpFilter */, true /* does not matter */); } @@ -1367,7 +1387,7 @@ public void parseHttpConnectionManager_lastNotTerminal() throws ResourceInvalidE thrown.expect(ResourceInvalidException.class); thrown.expectMessage("The last HttpFilter must be a terminal filter: envoy.filter.bar"); XdsListenerResource.parseHttpConnectionManager( - hcm, new HashSet(), filterRegistry, true /* parseHttpFilter */, + hcm, filterRegistry, true /* parseHttpFilter */, true /* does not matter */); } @@ -1385,7 +1405,7 @@ public void parseHttpConnectionManager_terminalNotLast() throws ResourceInvalidE thrown.expect(ResourceInvalidException.class); thrown.expectMessage("A terminal HttpFilter must be the last filter: terminal"); XdsListenerResource.parseHttpConnectionManager( - hcm, new HashSet(), filterRegistry, true /* parseHttpFilter */, + hcm, filterRegistry, true /* parseHttpFilter */, true); } @@ -1401,7 +1421,7 @@ public void parseHttpConnectionManager_unknownFilters() throws ResourceInvalidEx thrown.expect(ResourceInvalidException.class); thrown.expectMessage("The last HttpFilter must be a terminal filter: envoy.filter.bar"); XdsListenerResource.parseHttpConnectionManager( - hcm, new HashSet(), filterRegistry, true /* parseHttpFilter */, + hcm, filterRegistry, true /* parseHttpFilter */, true /* does not matter */); } @@ -1413,7 +1433,7 @@ public void parseHttpConnectionManager_emptyFilters() throws ResourceInvalidExce thrown.expect(ResourceInvalidException.class); thrown.expectMessage("Missing HttpFilter in HttpConnectionManager."); XdsListenerResource.parseHttpConnectionManager( - hcm, new HashSet(), filterRegistry, true /* parseHttpFilter */, + hcm, filterRegistry, true /* parseHttpFilter */, true /* does not matter */); } @@ -1459,7 +1479,7 @@ public void parseHttpConnectionManager_clusterSpecifierPlugin() throws Exception .build(); io.grpc.xds.HttpConnectionManager parsedHcm = XdsListenerResource.parseHttpConnectionManager( - hcm, new HashSet(), filterRegistry, false /* parseHttpFilter */, + hcm, filterRegistry, false /* parseHttpFilter */, true /* does not matter */); VirtualHost virtualHost = Iterables.getOnlyElement(parsedHcm.virtualHosts()); @@ -1536,7 +1556,7 @@ public void parseHttpConnectionManager_duplicatePluginName() throws Exception { thrown.expectMessage("Multiple ClusterSpecifierPlugins with the same name: rls-plugin-1"); XdsListenerResource.parseHttpConnectionManager( - hcm, new HashSet(), filterRegistry, false /* parseHttpFilter */, + hcm, filterRegistry, false /* parseHttpFilter */, true /* does not matter */); } @@ -1585,7 +1605,7 @@ public void parseHttpConnectionManager_pluginNameNotFound() throws Exception { thrown.expectMessage("ClusterSpecifierPlugin for [invalid-plugin-name] not found"); XdsListenerResource.parseHttpConnectionManager( - hcm, new HashSet(), filterRegistry, false /* parseHttpFilter */, + hcm, filterRegistry, false /* parseHttpFilter */, true /* does not matter */); } @@ -1655,8 +1675,8 @@ public void parseHttpConnectionManager_optionalPlugin() throws ResourceInvalidEx .addRoutes(optionalRoute)) .build(); io.grpc.xds.HttpConnectionManager parsedHcm = XdsListenerResource.parseHttpConnectionManager( - HttpConnectionManager.newBuilder().setRouteConfig(routeConfig).build(), - new HashSet<>(), filterRegistry, false /* parseHttpFilter */, true /* does not matter */); + HttpConnectionManager.newBuilder().setRouteConfig(routeConfig).build(), filterRegistry, + false /* parseHttpFilter */, true /* does not matter */); // Verify that the only route left is the one with the registered RLS plugin `rls-plugin-1`, // while the route with unregistered optional `optional-plugin-`1 has been skipped. @@ -1671,7 +1691,6 @@ public void parseHttpConnectionManager_optionalPlugin() throws ResourceInvalidEx @Test public void parseHttpConnectionManager_validateRdsConfigSource() throws Exception { XdsResourceType.enableRouteLookup = true; - Set rdsResources = new HashSet<>(); HttpConnectionManager hcm1 = HttpConnectionManager.newBuilder() @@ -1681,7 +1700,7 @@ public void parseHttpConnectionManager_validateRdsConfigSource() throws Exceptio ConfigSource.newBuilder().setAds(AggregatedConfigSource.getDefaultInstance()))) .build(); XdsListenerResource.parseHttpConnectionManager( - hcm1, rdsResources, filterRegistry, false /* parseHttpFilter */, + hcm1, filterRegistry, false /* parseHttpFilter */, true /* does not matter */); HttpConnectionManager hcm2 = @@ -1692,7 +1711,7 @@ public void parseHttpConnectionManager_validateRdsConfigSource() throws Exceptio ConfigSource.newBuilder().setSelf(SelfConfigSource.getDefaultInstance()))) .build(); XdsListenerResource.parseHttpConnectionManager( - hcm2, rdsResources, filterRegistry, false /* parseHttpFilter */, + hcm2, filterRegistry, false /* parseHttpFilter */, true /* does not matter */); HttpConnectionManager hcm3 = @@ -1707,7 +1726,7 @@ public void parseHttpConnectionManager_validateRdsConfigSource() throws Exceptio thrown.expectMessage( "HttpConnectionManager contains invalid RDS: must specify ADS or self ConfigSource"); XdsListenerResource.parseHttpConnectionManager( - hcm3, rdsResources, filterRegistry, false /* parseHttpFilter */, + hcm3, filterRegistry, false /* parseHttpFilter */, true /* does not matter */); } @@ -1892,7 +1911,7 @@ public void parseCluster_ringHashLbPolicy_defaultLbConfig() throws ResourceInval .build(); CdsUpdate update = XdsClusterResource.processCluster( - cluster, new HashSet(), null, LRS_SERVER_INFO, + cluster, null, LRS_SERVER_INFO, LoadBalancerRegistry.getDefaultRegistry()); LbConfig lbConfig = ServiceConfigUtil.unwrapLoadBalancingConfig(update.lbPolicyConfig()); assertThat(lbConfig.getPolicyName()).isEqualTo("ring_hash_experimental"); @@ -1914,7 +1933,7 @@ public void parseCluster_leastRequestLbPolicy_defaultLbConfig() throws ResourceI .build(); CdsUpdate update = XdsClusterResource.processCluster( - cluster, new HashSet(), null, LRS_SERVER_INFO, + cluster, null, LRS_SERVER_INFO, LoadBalancerRegistry.getDefaultRegistry()); LbConfig lbConfig = ServiceConfigUtil.unwrapLoadBalancingConfig(update.lbPolicyConfig()); assertThat(lbConfig.getPolicyName()).isEqualTo("wrr_locality_experimental"); @@ -1942,13 +1961,12 @@ public void parseCluster_transportSocketMatches_exception() throws ResourceInval thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "Cluster cluster-foo.googleapis.com: transport-socket-matches not supported."); - XdsClusterResource.processCluster(cluster, new HashSet(), null, LRS_SERVER_INFO, + XdsClusterResource.processCluster(cluster, null, LRS_SERVER_INFO, LoadBalancerRegistry.getDefaultRegistry()); } @Test public void parseCluster_validateEdsSourceConfig() throws ResourceInvalidException { - Set retainedEdsResources = new HashSet<>(); Cluster cluster1 = Cluster.newBuilder() .setName("cluster-foo.googleapis.com") .setType(DiscoveryType.EDS) @@ -1960,7 +1978,7 @@ public void parseCluster_validateEdsSourceConfig() throws ResourceInvalidExcepti .setServiceName("service-foo.googleapis.com")) .setLbPolicy(LbPolicy.ROUND_ROBIN) .build(); - XdsClusterResource.processCluster(cluster1, retainedEdsResources, null, LRS_SERVER_INFO, + XdsClusterResource.processCluster(cluster1, null, LRS_SERVER_INFO, LoadBalancerRegistry.getDefaultRegistry()); Cluster cluster2 = Cluster.newBuilder() @@ -1974,7 +1992,7 @@ public void parseCluster_validateEdsSourceConfig() throws ResourceInvalidExcepti .setServiceName("service-foo.googleapis.com")) .setLbPolicy(LbPolicy.ROUND_ROBIN) .build(); - XdsClusterResource.processCluster(cluster2, retainedEdsResources, null, LRS_SERVER_INFO, + XdsClusterResource.processCluster(cluster2, null, LRS_SERVER_INFO, LoadBalancerRegistry.getDefaultRegistry()); Cluster cluster3 = Cluster.newBuilder() @@ -1993,7 +2011,7 @@ public void parseCluster_validateEdsSourceConfig() throws ResourceInvalidExcepti thrown.expectMessage( "Cluster cluster-foo.googleapis.com: field eds_cluster_config must be set to indicate to" + " use EDS over ADS or self ConfigSource"); - XdsClusterResource.processCluster(cluster3, retainedEdsResources, null, LRS_SERVER_INFO, + XdsClusterResource.processCluster(cluster3, null, LRS_SERVER_INFO, LoadBalancerRegistry.getDefaultRegistry()); } @@ -2007,7 +2025,7 @@ public void parseServerSideListener_invalidTrafficDirection() throws ResourceInv thrown.expect(ResourceInvalidException.class); thrown.expectMessage("Listener listener1 with invalid traffic direction: OUTBOUND"); XdsListenerResource.parseServerSideListener( - listener, new HashSet(), null, filterRegistry, null, true /* does not matter */); + listener, null, filterRegistry, null, true /* does not matter */); } @Test @@ -2017,7 +2035,7 @@ public void parseServerSideListener_noTrafficDirection() throws ResourceInvalidE .setName("listener1") .build(); XdsListenerResource.parseServerSideListener( - listener, new HashSet(), null, filterRegistry, null, true /* does not matter */); + listener, null, filterRegistry, null, true /* does not matter */); } @Test @@ -2031,7 +2049,7 @@ public void parseServerSideListener_listenerFiltersPresent() throws ResourceInva thrown.expect(ResourceInvalidException.class); thrown.expectMessage("Listener listener1 cannot have listener_filters"); XdsListenerResource.parseServerSideListener( - listener, new HashSet(), null, filterRegistry, null, true /* does not matter */); + listener, null, filterRegistry, null, true /* does not matter */); } @Test @@ -2045,7 +2063,7 @@ public void parseServerSideListener_useOriginalDst() throws ResourceInvalidExcep thrown.expect(ResourceInvalidException.class); thrown.expectMessage("Listener listener1 cannot have use_original_dst set to true"); XdsListenerResource.parseServerSideListener( - listener, new HashSet(), null, filterRegistry, null, true /* does not matter */); + listener,null, filterRegistry, null, true /* does not matter */); } @Test @@ -2094,7 +2112,7 @@ public void parseServerSideListener_nonUniqueFilterChainMatch() throws ResourceI thrown.expect(ResourceInvalidException.class); thrown.expectMessage("FilterChainMatch must be unique. Found duplicate:"); XdsListenerResource.parseServerSideListener( - listener, new HashSet(), null, filterRegistry, null, true /* does not matter */); + listener, null, filterRegistry, null, true /* does not matter */); } @Test @@ -2143,7 +2161,7 @@ public void parseServerSideListener_nonUniqueFilterChainMatch_sameFilter() thrown.expect(ResourceInvalidException.class); thrown.expectMessage("FilterChainMatch must be unique. Found duplicate:"); XdsListenerResource.parseServerSideListener( - listener, new HashSet(), null, filterRegistry, null, true /* does not matter */); + listener,null, filterRegistry, null, true /* does not matter */); } @Test @@ -2192,7 +2210,7 @@ public void parseServerSideListener_uniqueFilterChainMatch() throws ResourceInva .addAllFilterChains(Arrays.asList(filterChain1, filterChain2)) .build(); XdsListenerResource.parseServerSideListener( - listener, new HashSet(), null, filterRegistry, null, true /* does not matter */); + listener, null, filterRegistry, null, true /* does not matter */); } @Test @@ -2207,7 +2225,7 @@ public void parseFilterChain_noHcm() throws ResourceInvalidException { thrown.expectMessage( "FilterChain filter-chain-foo should contain exact one HttpConnectionManager filter"); XdsListenerResource.parseFilterChain( - filterChain, new HashSet(), null, filterRegistry, null, null, + filterChain, null, filterRegistry, null, null, true /* does not matter */); } @@ -2226,7 +2244,7 @@ public void parseFilterChain_duplicateFilter() throws ResourceInvalidException { thrown.expectMessage( "FilterChain filter-chain-foo should contain exact one HttpConnectionManager filter"); XdsListenerResource.parseFilterChain( - filterChain, new HashSet(), null, filterRegistry, null, null, + filterChain, null, filterRegistry, null, null, true /* does not matter */); } @@ -2245,7 +2263,7 @@ public void parseFilterChain_filterMissingTypedConfig() throws ResourceInvalidEx "FilterChain filter-chain-foo contains filter envoy.http_connection_manager " + "without typed_config"); XdsListenerResource.parseFilterChain( - filterChain, new HashSet(), null, filterRegistry, null, null, + filterChain, null, filterRegistry, null, null, true /* does not matter */); } @@ -2268,7 +2286,7 @@ public void parseFilterChain_unsupportedFilter() throws ResourceInvalidException "FilterChain filter-chain-foo contains filter unsupported with unsupported " + "typed_config type unsupported-type-url"); XdsListenerResource.parseFilterChain( - filterChain, new HashSet(), null, filterRegistry, null, null, + filterChain, null, filterRegistry, null, null, true /* does not matter */); } @@ -2296,10 +2314,10 @@ public void parseFilterChain_noName() throws ResourceInvalidException { .build(); EnvoyServerProtoData.FilterChain parsedFilterChain1 = XdsListenerResource.parseFilterChain( - filterChain1, new HashSet(), null, filterRegistry, null, + filterChain1, null, filterRegistry, null, null, true /* does not matter */); EnvoyServerProtoData.FilterChain parsedFilterChain2 = XdsListenerResource.parseFilterChain( - filterChain2, new HashSet(), null, filterRegistry, null, + filterChain2, null, filterRegistry, null, null, true /* does not matter */); assertThat(parsedFilterChain1.name()).isEqualTo(parsedFilterChain2.name()); } diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTestBase.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTestBase.java index 31d10abd841..bc91f39cb2f 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTestBase.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTestBase.java @@ -40,6 +40,7 @@ import com.google.protobuf.util.Durations; import io.envoyproxy.envoy.config.cluster.v3.OutlierDetection; import io.envoyproxy.envoy.config.route.v3.FilterConfig; +import io.envoyproxy.envoy.config.route.v3.WeightedCluster; import io.envoyproxy.envoy.extensions.filters.http.router.v3.Router; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateProviderPluginInstance; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; @@ -806,19 +807,14 @@ public void ldsResponseErrorHandling_subscribedResourceInvalid_withRdsSubscripti verifyResourceMetadataAcked(LDS, "C", resourcesV1.get("C"), VERSION_1, TIME_INCREMENT); } call.verifyRequestNack(LDS, subscribedResourceNames, VERSION_1, "0001", NODE, errorsV2); - // {A.1} -> does not exist, missing from {A} + // {A.1} -> version 1 // {B.1} -> version 1 // {C.1} -> does not exist because {C} does not exist - verifyResourceMetadataDoesNotExist(RDS, "A.1"); + verifyResourceMetadataAcked(RDS, "A.1", resourcesV11.get("A.1"), VERSION_1, TIME_INCREMENT * 2); verifyResourceMetadataAcked(RDS, "B.1", resourcesV11.get("B.1"), VERSION_1, TIME_INCREMENT * 2); - if (!ignoreResourceDeletion()) { - verifyResourceMetadataDoesNotExist(RDS, "C.1"); - } else { - // When resource deletion is disabled, {C.1} is not deleted when {C} is deleted. - // Verify {C.1} stays in the previous version VERSION_1. - verifyResourceMetadataAcked(RDS, "C.1", resourcesV11.get("C.1"), VERSION_1, - TIME_INCREMENT * 2); - } + // Verify {C.1} stays in the previous version VERSION_1, no matter {C} is deleted or not. + verifyResourceMetadataAcked(RDS, "C.1", resourcesV11.get("C.1"), VERSION_1, + TIME_INCREMENT * 2); } @Test @@ -1368,6 +1364,51 @@ public void rdsResponseErrorHandling_someResourcesFailedUnpack() { verify(rdsResourceWatcher).onChanged(any(RdsUpdate.class)); } + @Test + public void rdsResponseErrorHandling_nackWeightedSumZero() { + DiscoveryRpcCall call = startResourceWatcher(XdsRouteConfigureResource.getInstance(), + RDS_RESOURCE, rdsResourceWatcher); + verifyResourceMetadataRequested(RDS, RDS_RESOURCE); + + io.envoyproxy.envoy.config.route.v3.RouteAction routeAction = + io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() + .setWeightedClusters( + WeightedCluster.newBuilder() + .addClusters( + WeightedCluster.ClusterWeight + .newBuilder() + .setName("cluster-foo") + .setWeight(UInt32Value.newBuilder().setValue(0))) + .addClusters(WeightedCluster.ClusterWeight + .newBuilder() + .setName("cluster-bar") + .setWeight(UInt32Value.newBuilder().setValue(0)))) + .build(); + io.envoyproxy.envoy.config.route.v3.Route route = + io.envoyproxy.envoy.config.route.v3.Route.newBuilder() + .setName("route-blade") + .setMatch( + io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder() + .setPath("/service/method")) + .setRoute(routeAction) + .build(); + + Any zeroWeightSum = Any.pack(mf.buildRouteConfiguration(RDS_RESOURCE, + Arrays.asList(mf.buildVirtualHost(Arrays.asList(route), ImmutableMap.of())))); + List resources = ImmutableList.of(zeroWeightSum); + call.sendResponse(RDS, resources, VERSION_1, "0000"); + + List errors = ImmutableList.of( + "RDS response RouteConfiguration \'route-configuration.googleapis.com\' validation error: " + + "RouteConfiguration contains invalid virtual host: Virtual host [do not care] " + + "contains invalid route : Route [route-blade] contains invalid RouteAction: " + + "Sum of cluster weights should be above 0."); + verifySubscribedResourcesMetadataSizes(0, 0, 1, 0); + // The response is NACKed with the same error message. + call.verifyRequestNack(RDS, RDS_RESOURCE, "", "0000", NODE, errors); + verify(rdsResourceWatcher, never()).onChanged(any(RdsUpdate.class)); + } + /** * Tests a subscribed RDS resource transitioned to and from the invalid state. * @@ -1556,8 +1597,8 @@ public void rdsResourceDeletedByLdsApiListener() { call.sendResponse(LDS, testListenerVhosts, VERSION_2, "0001"); verify(ldsResourceWatcher, times(2)).onChanged(ldsUpdateCaptor.capture()); verifyGoldenListenerVhosts(ldsUpdateCaptor.getValue()); - verify(rdsResourceWatcher).onResourceDoesNotExist(RDS_RESOURCE); - verifyResourceMetadataDoesNotExist(RDS, RDS_RESOURCE); + verifyNoMoreInteractions(rdsResourceWatcher); + verifyResourceMetadataAcked(RDS, RDS_RESOURCE, testRouteConfig, VERSION_1, TIME_INCREMENT * 2); verifyResourceMetadataAcked( LDS, LDS_RESOURCE, testListenerVhosts, VERSION_2, TIME_INCREMENT * 3); verifySubscribedResourcesMetadataSizes(1, 0, 1, 0); @@ -1624,8 +1665,8 @@ public void rdsResourcesDeletedByLdsTcpListener() { parsedFilterChain = Iterables.getOnlyElement( ldsUpdateCaptor.getValue().listener().filterChains()); assertThat(parsedFilterChain.httpConnectionManager().virtualHosts()).hasSize(VHOST_SIZE); - verify(rdsResourceWatcher).onResourceDoesNotExist(RDS_RESOURCE); - verifyResourceMetadataDoesNotExist(RDS, RDS_RESOURCE); + verify(rdsResourceWatcher, never()).onResourceDoesNotExist(RDS_RESOURCE); + verifyResourceMetadataAcked(RDS, RDS_RESOURCE, testRouteConfig, VERSION_1, TIME_INCREMENT * 2); verifyResourceMetadataAcked( LDS, LISTENER_RESOURCE, packedListener, VERSION_2, TIME_INCREMENT * 3); verifySubscribedResourcesMetadataSizes(1, 0, 1, 0); @@ -1896,19 +1937,14 @@ public void cdsResponseErrorHandling_subscribedResourceInvalid_withEdsSubscripti verifyResourceMetadataAcked(CDS, "C", resourcesV1.get("C"), VERSION_1, TIME_INCREMENT); } call.verifyRequestNack(CDS, subscribedResourceNames, VERSION_1, "0001", NODE, errorsV2); - // {A.1} -> does not exist, missing from {A} + // {A.1} -> version 1 // {B.1} -> version 1 // {C.1} -> does not exist because {C} does not exist - verifyResourceMetadataDoesNotExist(EDS, "A.1"); + verifyResourceMetadataAcked(EDS, "A.1", resourcesV11.get("A.1"), VERSION_1, TIME_INCREMENT * 2); verifyResourceMetadataAcked(EDS, "B.1", resourcesV11.get("B.1"), VERSION_1, TIME_INCREMENT * 2); - if (!ignoreResourceDeletion()) { - verifyResourceMetadataDoesNotExist(EDS, "C.1"); - } else { - // When resource deletion is disabled, {C.1} is not deleted when {C} is deleted. - // Verify {C.1} stays in the previous version VERSION_1. - verifyResourceMetadataAcked(EDS, "C.1", resourcesV11.get("C.1"), VERSION_1, - TIME_INCREMENT * 2); - } + // Verify {C.1} stays in the previous version VERSION_1. {C1} deleted or not does not matter. + verifyResourceMetadataAcked(EDS, "C.1", resourcesV11.get("C.1"), VERSION_1, + TIME_INCREMENT * 2); } @Test @@ -3026,10 +3062,11 @@ public void edsResourceDeletedByCds() { assertThat(cdsUpdateCaptor.getValue().edsServiceName()).isNull(); // Note that the endpoint must be deleted even if the ignore_resource_deletion feature. // This happens because the cluster CDS_RESOURCE is getting replaced, and not deleted. - verify(edsResourceWatcher).onResourceDoesNotExist(EDS_RESOURCE); + verify(edsResourceWatcher, never()).onResourceDoesNotExist(EDS_RESOURCE); verify(edsResourceWatcher, never()).onResourceDoesNotExist(resource); verifyNoMoreInteractions(cdsWatcher, edsWatcher); - verifyResourceMetadataDoesNotExist(EDS, EDS_RESOURCE); + verifyResourceMetadataAcked( + EDS, EDS_RESOURCE, clusterLoadAssignments.get(0), VERSION_1, TIME_INCREMENT * 2); verifyResourceMetadataAcked( EDS, resource, clusterLoadAssignments.get(1), VERSION_1, TIME_INCREMENT * 2); // no change verifyResourceMetadataAcked(CDS, resource, clusters.get(0), VERSION_2, TIME_INCREMENT * 3); diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java index 2deab4ae688..f4475ebe37a 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java @@ -1736,7 +1736,7 @@ public long nanoTime() { assertThat(testCall).isNull(); verifyRpcDelayedThenAborted(observer, 4000L, Status.DEADLINE_EXCEEDED.withDescription( "Deadline exceeded after up to 5000 ns of fault-injected delay:" - + " Deadline exceeded after 0.000004000s. ")); + + " Deadline CallOptions will be exceeded in 0.000004000s. ")); } @Test diff --git a/xds/src/test/java/io/grpc/xds/XdsTestControlPlaneService.java b/xds/src/test/java/io/grpc/xds/XdsTestControlPlaneService.java index 82791a5582c..9a1ce3350de 100644 --- a/xds/src/test/java/io/grpc/xds/XdsTestControlPlaneService.java +++ b/xds/src/test/java/io/grpc/xds/XdsTestControlPlaneService.java @@ -33,6 +33,26 @@ import java.util.logging.Level; import java.util.logging.Logger; +/** +* A bidi-stream service that acts as a local xDS Control Plane. + * It accepts xDS config injection through a method call {@link #setXdsConfig}. Handling AdsStream + * response or updating xds config are run in syncContext. + * + *

The service maintains lookup tables: + * Subscriber table: map from each resource type, to a map from each client to subscribed resource + * names set. + * Resources table: store the resources in raw proto message. + * + *

xDS protocol requires version/nonce to avoid various race conditions. In this impl: + * Version stores the latest version number per each resource type. It is simply bumped up on each + * xds config set. + * Nonce stores the nonce number for each resource type and for each client. Incoming xDS requests + * share the same proto message type but may at different resources update phases: + * 1) Original: an initial xDS request. + * 2) NACK an xDS response. + * 3) ACK an xDS response. + * The service is capable of distinguish these cases when handling the request. + */ final class XdsTestControlPlaneService extends AggregatedDiscoveryServiceGrpc.AggregatedDiscoveryServiceImplBase { private static final Logger logger = Logger.getLogger(XdsTestControlPlaneService.class.getName());

Homepage: