>
/**
* Creates a channel with the target's address and port number.
*
+ * Note that there is an open JDK bug on {@link java.net.URI} class parsing an ipv6 scope ID:
+ * bugs.openjdk.org/browse/JDK-8199396. This method is exposed to this bug. If you experience an
+ * issue, a work-around is to convert the scope ID to its numeric form (e.g. by using
+ * Inet6Address.getScopeId()) before calling this method.
+ *
* @see #forTarget(String)
* @since 1.0.0
*/
@@ -70,6 +75,11 @@ public static ManagedChannelBuilder> forAddress(String name, int port) {
*
{@code "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443"}
*
*
+ * Note that there is an open JDK bug on {@link java.net.URI} class parsing an ipv6 scope ID:
+ * bugs.openjdk.org/browse/JDK-8199396. This method is exposed to this bug. If you experience an
+ * issue, a work-around is to convert the scope ID to its numeric form (e.g. by using
+ * Inet6Address.getScopeId()) before calling this method.
+ *
* @since 1.0.0
*/
public static ManagedChannelBuilder> forTarget(String target) {
@@ -352,6 +362,8 @@ public T maxInboundMetadataSize(int bytes) {
* small of a value as necessary.
*
* @throws UnsupportedOperationException if unsupported
+ * @see gRFC A8
+ * Client-side Keepalive
* @since 1.7.0
*/
public T keepAliveTime(long keepAliveTime, TimeUnit timeUnit) {
@@ -366,6 +378,8 @@ public T keepAliveTime(long keepAliveTime, TimeUnit timeUnit) {
*
This value should be at least multiple times the RTT to allow for lost packets.
*
* @throws UnsupportedOperationException if unsupported
+ * @see gRFC A8
+ * Client-side Keepalive
* @since 1.7.0
*/
public T keepAliveTimeout(long keepAliveTimeout, TimeUnit timeUnit) {
@@ -383,6 +397,8 @@ public T keepAliveTimeout(long keepAliveTimeout, TimeUnit timeUnit) {
*
* @throws UnsupportedOperationException if unsupported
* @see #keepAliveTime(long, TimeUnit)
+ * @see gRFC A8
+ * Client-side Keepalive
* @since 1.7.0
*/
public T keepAliveWithoutCalls(boolean enable) {
@@ -571,10 +587,10 @@ public T proxyDetector(ProxyDetector proxyDetector) {
* return o;
* }}
*
+ * @return this
* @throws IllegalArgumentException When the given serviceConfig is invalid or the current version
* of grpc library can not parse it gracefully. The state of the builder is unchanged if
* an exception is thrown.
- * @return this
* @since 1.20.0
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/5189")
diff --git a/api/src/main/java/io/grpc/MethodDescriptor.java b/api/src/main/java/io/grpc/MethodDescriptor.java
index c85be6b64781..983ec4858c5b 100644
--- a/api/src/main/java/io/grpc/MethodDescriptor.java
+++ b/api/src/main/java/io/grpc/MethodDescriptor.java
@@ -324,7 +324,6 @@ public InputStream streamResponse(RespT response) {
*
* @since 1.1.0
*/
- @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2592")
public Marshaller getRequestMarshaller() {
return requestMarshaller;
}
@@ -334,7 +333,6 @@ public Marshaller getRequestMarshaller() {
*
* @since 1.1.0
*/
- @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2592")
public Marshaller getResponseMarshaller() {
return responseMarshaller;
}
diff --git a/api/src/main/java/io/grpc/PartialForwardingServerCall.java b/api/src/main/java/io/grpc/PartialForwardingServerCall.java
index 7b760c468496..a7da647308b1 100644
--- a/api/src/main/java/io/grpc/PartialForwardingServerCall.java
+++ b/api/src/main/java/io/grpc/PartialForwardingServerCall.java
@@ -54,7 +54,6 @@ public boolean isCancelled() {
}
@Override
- @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1703")
public void setMessageCompression(boolean enabled) {
delegate().setMessageCompression(enabled);
}
diff --git a/api/src/main/java/io/grpc/ServerBuilder.java b/api/src/main/java/io/grpc/ServerBuilder.java
index 2df644e61aee..c2ad566f90f3 100644
--- a/api/src/main/java/io/grpc/ServerBuilder.java
+++ b/api/src/main/java/io/grpc/ServerBuilder.java
@@ -230,14 +230,13 @@ public T useTransportSecurity(InputStream certChain, InputStream privateKey) {
/**
* Sets the permitted time for new connections to complete negotiation handshakes before being
- * killed.
+ * killed. The default value is 2 minutes.
*
* @return this
* @throws IllegalArgumentException if timeout is negative
* @throws UnsupportedOperationException if unsupported
* @since 1.8.0
*/
- @ExperimentalApi("https://github.com/grpc/grpc-java/issues/3706")
public T handshakeTimeout(long timeout, TimeUnit unit) {
throw new UnsupportedOperationException();
}
@@ -249,6 +248,8 @@ public T handshakeTimeout(long timeout, TimeUnit unit) {
*
* @throws IllegalArgumentException if time is not positive
* @throws UnsupportedOperationException if unsupported
+ * @see gRFC A9
+ * Server-side Connection Management
* @since 1.47.0
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/9009")
@@ -265,6 +266,8 @@ public T keepAliveTime(long keepAliveTime, TimeUnit timeUnit) {
*
* @throws IllegalArgumentException if timeout is not positive
* @throws UnsupportedOperationException if unsupported
+ * @see gRFC A9
+ * Server-side Connection Management
* @since 1.47.0
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/9009")
@@ -281,6 +284,8 @@ public T keepAliveTimeout(long keepAliveTimeout, TimeUnit timeUnit) {
*
* @throws IllegalArgumentException if idle is not positive
* @throws UnsupportedOperationException if unsupported
+ * @see gRFC A9
+ * Server-side Connection Management
* @since 1.47.0
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/9009")
@@ -296,6 +301,8 @@ public T maxConnectionIdle(long maxConnectionIdle, TimeUnit timeUnit) {
*
* @throws IllegalArgumentException if age is not positive
* @throws UnsupportedOperationException if unsupported
+ * @see gRFC A9
+ * Server-side Connection Management
* @since 1.47.0
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/9009")
@@ -312,6 +319,8 @@ public T maxConnectionAge(long maxConnectionAge, TimeUnit timeUnit) {
* @throws IllegalArgumentException if grace is negative
* @throws UnsupportedOperationException if unsupported
* @see #maxConnectionAge(long, TimeUnit)
+ * @see gRFC A9
+ * Server-side Connection Management
* @since 1.47.0
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/9009")
@@ -333,6 +342,8 @@ public T maxConnectionAgeGrace(long maxConnectionAgeGrace, TimeUnit timeUnit) {
* @throws IllegalArgumentException if time is negative
* @throws UnsupportedOperationException if unsupported
* @see #permitKeepAliveWithoutCalls(boolean)
+ * @see gRFC A8
+ * Client-side Keepalive
* @since 1.47.0
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/9009")
@@ -346,6 +357,8 @@ public T permitKeepAliveTime(long keepAliveTime, TimeUnit timeUnit) {
*
* @throws UnsupportedOperationException if unsupported
* @see #permitKeepAliveTime(long, TimeUnit)
+ * @see gRFC A8
+ * Client-side Keepalive
* @since 1.47.0
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/9009")
diff --git a/api/src/main/java/io/grpc/ServerCall.java b/api/src/main/java/io/grpc/ServerCall.java
index bf0e6da8aef3..7408479a2305 100644
--- a/api/src/main/java/io/grpc/ServerCall.java
+++ b/api/src/main/java/io/grpc/ServerCall.java
@@ -242,7 +242,6 @@ public Attributes getAttributes() {
*
* @return the authority string. {@code null} if not available.
*/
- @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2924")
@Nullable
public String getAuthority() {
return null;
diff --git a/api/src/main/java/io/grpc/ServerInterceptors.java b/api/src/main/java/io/grpc/ServerInterceptors.java
index b44a7b6c8866..0bc6d07c83c1 100644
--- a/api/src/main/java/io/grpc/ServerInterceptors.java
+++ b/api/src/main/java/io/grpc/ServerInterceptors.java
@@ -291,23 +291,23 @@ public ServerCall.Listener startCall(
final Metadata headers) {
final ServerCall unwrappedCall =
new PartialForwardingServerCall() {
- @Override
- protected ServerCall delegate() {
- return call;
- }
+ @Override
+ protected ServerCall delegate() {
+ return call;
+ }
- @Override
- public void sendMessage(ORespT message) {
- final InputStream is = originalMethod.streamResponse(message);
- final WRespT wrappedMessage = wrappedMethod.parseResponse(is);
- delegate().sendMessage(wrappedMessage);
- }
+ @Override
+ public void sendMessage(ORespT message) {
+ final InputStream is = originalMethod.streamResponse(message);
+ final WRespT wrappedMessage = wrappedMethod.parseResponse(is);
+ delegate().sendMessage(wrappedMessage);
+ }
- @Override
- public MethodDescriptor getMethodDescriptor() {
- return originalMethod;
- }
- };
+ @Override
+ public MethodDescriptor getMethodDescriptor() {
+ return originalMethod;
+ }
+ };
final ServerCall.Listener originalListener = originalHandler
.startCall(unwrappedCall, headers);
diff --git a/api/src/main/java/io/grpc/ServerRegistry.java b/api/src/main/java/io/grpc/ServerRegistry.java
index e6a067ce87f6..70fd36573075 100644
--- a/api/src/main/java/io/grpc/ServerRegistry.java
+++ b/api/src/main/java/io/grpc/ServerRegistry.java
@@ -23,6 +23,7 @@
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
@@ -92,7 +93,7 @@ public static synchronized ServerRegistry getDefaultRegistry() {
if (instance == null) {
List providerList = ServiceProviders.loadAll(
ServerProvider.class,
- Collections.>emptyList(),
+ getHardCodedClasses(),
ServerProvider.class.getClassLoader(),
new ServerPriorityAccessor());
instance = new ServerRegistry();
@@ -119,6 +120,20 @@ ServerProvider provider() {
return providers.isEmpty() ? null : providers.get(0);
}
+ @VisibleForTesting
+ static List> getHardCodedClasses() {
+ // Class.forName(String) is used to remove the need for ProGuard configuration. Note that
+ // ProGuard does not detect usages of Class.forName(String, boolean, ClassLoader):
+ // https://sourceforge.net/p/proguard/bugs/418/
+ List> list = new ArrayList<>();
+ try {
+ list.add(Class.forName("io.grpc.okhttp.OkHttpServerProvider"));
+ } catch (ClassNotFoundException e) {
+ logger.log(Level.FINE, "Unable to find OkHttpServerProvider", e);
+ }
+ return Collections.unmodifiableList(list);
+ }
+
ServerBuilder> newServerBuilderForPort(int port, ServerCredentials creds) {
List providers = providers();
if (providers.isEmpty()) {
diff --git a/api/src/main/java/io/grpc/Status.java b/api/src/main/java/io/grpc/Status.java
index 28388424e176..c5c7eed402ef 100644
--- a/api/src/main/java/io/grpc/Status.java
+++ b/api/src/main/java/io/grpc/Status.java
@@ -418,7 +418,6 @@ public static Status fromThrowable(Throwable t) {
* @return the trailers or {@code null} if not found.
*/
@Nullable
- @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4683")
public static Metadata trailersFromThrowable(Throwable t) {
Throwable cause = checkNotNull(t, "t");
while (cause != null) {
@@ -534,7 +533,6 @@ public StatusRuntimeException asRuntimeException() {
* Same as {@link #asRuntimeException()} but includes the provided trailers in the returned
* exception.
*/
- @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4683")
public StatusRuntimeException asRuntimeException(@Nullable Metadata trailers) {
return new StatusRuntimeException(this, trailers);
}
diff --git a/context/src/test/java/io/grpc/ContextTest.java b/api/src/test/java/io/grpc/ContextTest.java
similarity index 100%
rename from context/src/test/java/io/grpc/ContextTest.java
rename to api/src/test/java/io/grpc/ContextTest.java
diff --git a/context/src/test/java/io/grpc/DeadlineTest.java b/api/src/test/java/io/grpc/DeadlineTest.java
similarity index 100%
rename from context/src/test/java/io/grpc/DeadlineTest.java
rename to api/src/test/java/io/grpc/DeadlineTest.java
diff --git a/api/src/test/java/io/grpc/ForwardingServerCallTest.java b/api/src/test/java/io/grpc/ForwardingServerCallTest.java
index e6cb63fdd2b8..8030b65ecc7e 100644
--- a/api/src/test/java/io/grpc/ForwardingServerCallTest.java
+++ b/api/src/test/java/io/grpc/ForwardingServerCallTest.java
@@ -48,7 +48,7 @@ public void setUp() {
protected ServerCall delegate() {
return serverCall;
}
- };
+ };
}
@Test
diff --git a/api/src/test/java/io/grpc/MetadataTest.java b/api/src/test/java/io/grpc/MetadataTest.java
index 51aecc638e75..073a505c8240 100644
--- a/api/src/test/java/io/grpc/MetadataTest.java
+++ b/api/src/test/java/io/grpc/MetadataTest.java
@@ -54,16 +54,16 @@ public class MetadataTest {
private static final Metadata.BinaryMarshaller FISH_MARSHALLER =
new Metadata.BinaryMarshaller() {
- @Override
- public byte[] toBytes(Fish fish) {
- return fish.name.getBytes(UTF_8);
- }
+ @Override
+ public byte[] toBytes(Fish fish) {
+ return fish.name.getBytes(UTF_8);
+ }
- @Override
- public Fish parseBytes(byte[] serialized) {
- return new Fish(new String(serialized, UTF_8));
- }
- };
+ @Override
+ public Fish parseBytes(byte[] serialized) {
+ return new Fish(new String(serialized, UTF_8));
+ }
+ };
private static class FishStreamMarsaller implements Metadata.BinaryStreamMarshaller {
@Override
@@ -100,16 +100,16 @@ public int read() throws IOException {
private static final Metadata.BinaryStreamMarshaller IMMUTABLE_FISH_MARSHALLER =
new Metadata.BinaryStreamMarshaller() {
- @Override
- public InputStream toStream(Fish fish) {
- return new FakeFishStream(fish);
- }
+ @Override
+ public InputStream toStream(Fish fish) {
+ return new FakeFishStream(fish);
+ }
- @Override
- public Fish parseStream(InputStream stream) {
- return ((FakeFishStream) stream).fish;
- }
- };
+ @Override
+ public Fish parseStream(InputStream stream) {
+ return ((FakeFishStream) stream).fish;
+ }
+ };
private static final String LANCE = "lance";
private static final byte[] LANCE_BYTES = LANCE.getBytes(US_ASCII);
@@ -313,6 +313,7 @@ public void verifyToString_usingBinary() {
}
@Test
+ @SuppressWarnings("StringCaseLocaleUsage") // System locale is exactly what we're testing.
public void testKeyCaseHandling() {
Locale originalLocale = Locale.getDefault();
Locale.setDefault(new Locale("tr", "TR"));
diff --git a/context/src/test/java/io/grpc/PersistentHashArrayMappedTrieTest.java b/api/src/test/java/io/grpc/PersistentHashArrayMappedTrieTest.java
similarity index 100%
rename from context/src/test/java/io/grpc/PersistentHashArrayMappedTrieTest.java
rename to api/src/test/java/io/grpc/PersistentHashArrayMappedTrieTest.java
diff --git a/api/src/test/java/io/grpc/StatusRuntimeExceptionTest.java b/api/src/test/java/io/grpc/StatusRuntimeExceptionTest.java
index 2c3bf7e9c818..ab20c1112540 100644
--- a/api/src/test/java/io/grpc/StatusRuntimeExceptionTest.java
+++ b/api/src/test/java/io/grpc/StatusRuntimeExceptionTest.java
@@ -54,15 +54,13 @@ public void extendPreservesStack() {
@Test
public void extendAndOverridePreservesStack() {
final StackTraceElement element = new StackTraceElement("a", "b", "c", 4);
- StatusRuntimeException exception =
- new StatusRuntimeException(Status.CANCELLED, new Metadata()) {
-
+ StatusRuntimeException error = new StatusRuntimeException(Status.CANCELLED, new Metadata()) {
@Override
public synchronized Throwable fillInStackTrace() {
setStackTrace(new StackTraceElement[]{element});
return this;
}
};
- assertThat(exception.getStackTrace()).asList().containsExactly(element);
+ assertThat(error.getStackTrace()).asList().containsExactly(element);
}
}
diff --git a/context/src/test/java/io/grpc/ThreadLocalContextStorageTest.java b/api/src/test/java/io/grpc/ThreadLocalContextStorageTest.java
similarity index 100%
rename from context/src/test/java/io/grpc/ThreadLocalContextStorageTest.java
rename to api/src/test/java/io/grpc/ThreadLocalContextStorageTest.java
diff --git a/api/src/testFixtures/java/io/grpc/ServerRegistryAccessor.java b/api/src/testFixtures/java/io/grpc/ServerRegistryAccessor.java
new file mode 100644
index 000000000000..b15d4dfde192
--- /dev/null
+++ b/api/src/testFixtures/java/io/grpc/ServerRegistryAccessor.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2023 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;
+
+/** Accesses test-only methods of {@link ServerRegistry}. */
+public final class ServerRegistryAccessor {
+ private ServerRegistryAccessor() {}
+
+ public static Iterable> getHardCodedClasses() {
+ return ServerRegistry.getHardCodedClasses();
+ }
+}
diff --git a/context/src/testFixtures/java/io/grpc/StaticTestingClassLoader.java b/api/src/testFixtures/java/io/grpc/StaticTestingClassLoader.java
similarity index 100%
rename from context/src/testFixtures/java/io/grpc/StaticTestingClassLoader.java
rename to api/src/testFixtures/java/io/grpc/StaticTestingClassLoader.java
diff --git a/context/src/testFixtures/java/io/grpc/testing/DeadlineSubject.java b/api/src/testFixtures/java/io/grpc/testing/DeadlineSubject.java
similarity index 100%
rename from context/src/testFixtures/java/io/grpc/testing/DeadlineSubject.java
rename to api/src/testFixtures/java/io/grpc/testing/DeadlineSubject.java
diff --git a/auth/build.gradle b/auth/build.gradle
index 73c108d5203e..3e9646533eeb 100644
--- a/auth/build.gradle
+++ b/auth/build.gradle
@@ -7,6 +7,13 @@ plugins {
}
description = "gRPC: Auth"
+
+tasks.named("jar").configure {
+ manifest {
+ attributes('Automatic-Module-Name': 'io.grpc.auth')
+ }
+}
+
dependencies {
api project(':grpc-api'),
libraries.google.auth.credentials
diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle
index 90251e6692ff..49871e28aa7b 100644
--- a/benchmarks/build.gradle
+++ b/benchmarks/build.gradle
@@ -18,6 +18,12 @@ configurations {
alpnagent
}
+tasks.named("jar").configure {
+ manifest {
+ attributes('Automatic-Module-Name': 'io.grpc.benchmarks')
+ }
+}
+
dependencies {
implementation project(':grpc-core'),
project(':grpc-netty'),
@@ -99,12 +105,14 @@ def benchmark_worker = tasks.register("benchmark_worker", CreateStartScripts) {
classpath = startScripts.classpath
}
-applicationDistribution.into("bin") {
- from(qps_client)
- from(openloop_client)
- from(qps_server)
- from(benchmark_worker)
- fileMode = 0755
+application {
+ applicationDistribution.into("bin") {
+ from(qps_client)
+ from(openloop_client)
+ from(qps_server)
+ from(benchmark_worker)
+ fileMode = 0755
+ }
}
publishing {
diff --git a/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/AbstractBenchmark.java b/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/AbstractBenchmark.java
index 3edc92ac02aa..d68e66561a84 100644
--- a/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/AbstractBenchmark.java
+++ b/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/AbstractBenchmark.java
@@ -78,6 +78,7 @@ public enum MessageSize {
SMALL(10), MEDIUM(1024), LARGE(65536), JUMBO(1048576);
private final int bytes;
+
MessageSize(int bytes) {
this.bytes = bytes;
}
@@ -94,6 +95,7 @@ public enum FlowWindowSize {
SMALL(16383), MEDIUM(65535), LARGE(1048575), JUMBO(8388607);
private final int bytes;
+
FlowWindowSize(int bytes) {
this.bytes = bytes;
}
diff --git a/binder/build.gradle b/binder/build.gradle
index 0dd50827699f..0d1c5a033950 100644
--- a/binder/build.gradle
+++ b/binder/build.gradle
@@ -13,7 +13,7 @@ android {
targetCompatibility 1.8
}
defaultConfig {
- minSdkVersion 19
+ minSdkVersion 21
targetSdkVersion 33
versionCode 1
versionName "1.0"
@@ -75,6 +75,7 @@ tasks.withType(JavaCompile).configureEach {
options.compilerArgs += [
"-Xlint:-cast"
]
+ options.compilerArgs -= ["-Werror"] // https://github.com/grpc/grpc-java/issues/10297
appendToProperty(it.options.errorprone.excludedPaths, ".*/R.java", "|")
}
diff --git a/binder/src/androidTest/java/io/grpc/binder/internal/BinderClientTransportTest.java b/binder/src/androidTest/java/io/grpc/binder/internal/BinderClientTransportTest.java
index dbacf351780c..44454fbf7f17 100644
--- a/binder/src/androidTest/java/io/grpc/binder/internal/BinderClientTransportTest.java
+++ b/binder/src/androidTest/java/io/grpc/binder/internal/BinderClientTransportTest.java
@@ -35,6 +35,7 @@
import io.grpc.Status.Code;
import io.grpc.binder.AndroidComponentAddress;
import io.grpc.binder.BindServiceFlags;
+import io.grpc.binder.BinderChannelCredentials;
import io.grpc.binder.BinderServerBuilder;
import io.grpc.binder.HostServices;
import io.grpc.binder.InboundParcelablePolicy;
@@ -146,7 +147,9 @@ public BinderClientTransportBuilder setSecurityPolicy(SecurityPolicy securityPol
public BinderTransport.BinderClientTransport build() {
return new BinderTransport.BinderClientTransport(
appContext,
+ BinderChannelCredentials.forDefault(),
serverAddress,
+ null,
BindServiceFlags.DEFAULTS,
ContextCompat.getMainExecutor(appContext),
executorServicePool,
diff --git a/binder/src/androidTest/java/io/grpc/binder/internal/BinderTransportTest.java b/binder/src/androidTest/java/io/grpc/binder/internal/BinderTransportTest.java
index 5a1b302f768e..5140057d72e8 100644
--- a/binder/src/androidTest/java/io/grpc/binder/internal/BinderTransportTest.java
+++ b/binder/src/androidTest/java/io/grpc/binder/internal/BinderTransportTest.java
@@ -24,6 +24,7 @@
import io.grpc.ServerStreamTracer;
import io.grpc.binder.AndroidComponentAddress;
import io.grpc.binder.BindServiceFlags;
+import io.grpc.binder.BinderChannelCredentials;
import io.grpc.binder.HostServices;
import io.grpc.binder.InboundParcelablePolicy;
import io.grpc.binder.SecurityPolicies;
@@ -95,7 +96,9 @@ protected ManagedClientTransport newClientTransport(InternalServer server) {
AndroidComponentAddress addr = (AndroidComponentAddress) server.getListenSocketAddress();
return new BinderTransport.BinderClientTransport(
appContext,
+ BinderChannelCredentials.forDefault(),
addr,
+ null,
BindServiceFlags.DEFAULTS,
ContextCompat.getMainExecutor(appContext),
executorServicePool,
diff --git a/binder/src/main/java/io/grpc/binder/BinderChannelBuilder.java b/binder/src/main/java/io/grpc/binder/BinderChannelBuilder.java
index 214eb6dc4c5e..e714761312ab 100644
--- a/binder/src/main/java/io/grpc/binder/BinderChannelBuilder.java
+++ b/binder/src/main/java/io/grpc/binder/BinderChannelBuilder.java
@@ -20,6 +20,7 @@
import static com.google.common.base.Preconditions.checkState;
import android.content.Context;
+import android.os.UserHandle;
import androidx.core.content.ContextCompat;
import com.google.errorprone.annotations.DoNotCall;
import io.grpc.ChannelCredentials;
@@ -71,7 +72,37 @@ public final class BinderChannelBuilder
public static BinderChannelBuilder forAddress(
AndroidComponentAddress directAddress, Context sourceContext) {
return new BinderChannelBuilder(
- checkNotNull(directAddress, "directAddress"), null, sourceContext);
+ checkNotNull(directAddress, "directAddress"),
+ null,
+ sourceContext,
+ BinderChannelCredentials.forDefault());
+ }
+
+ /**
+ * Creates a channel builder that will bind to a remote Android service with provided
+ * BinderChannelCredentials.
+ *
+ * The underlying Android binding will be torn down when the channel becomes idle. This happens
+ * after 30 minutes without use by default but can be configured via {@link
+ * ManagedChannelBuilder#idleTimeout(long, TimeUnit)} or triggered manually with {@link
+ * ManagedChannel#enterIdle()}.
+ *
+ *
You the caller are responsible for managing the lifecycle of any channels built by the
+ * resulting builder. They will not be shut down automatically.
+ *
+ * @param directAddress the {@link AndroidComponentAddress} referencing the service to bind to.
+ * @param sourceContext the context to bind from (e.g. The current Activity or Application).
+ * @param channelCredentials the arbitrary binder specific channel credentials to be used to
+ * establish a binder connection.
+ * @return a new builder
+ */
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/10173")
+ public static BinderChannelBuilder forAddress(
+ AndroidComponentAddress directAddress,
+ Context sourceContext,
+ BinderChannelCredentials channelCredentials) {
+ return new BinderChannelBuilder(
+ checkNotNull(directAddress, "directAddress"), null, sourceContext, channelCredentials);
}
/**
@@ -92,7 +123,37 @@ public static BinderChannelBuilder forAddress(
* @return a new builder
*/
public static BinderChannelBuilder forTarget(String target, Context sourceContext) {
- return new BinderChannelBuilder(null, checkNotNull(target, "target"), sourceContext);
+ return new BinderChannelBuilder(
+ null,
+ checkNotNull(target, "target"),
+ sourceContext,
+ BinderChannelCredentials.forDefault());
+ }
+
+ /**
+ * Creates a channel builder that will bind to a remote Android service, via a string target name
+ * which will be resolved.
+ *
+ *
The underlying Android binding will be torn down when the channel becomes idle. This happens
+ * after 30 minutes without use by default but can be configured via {@link
+ * ManagedChannelBuilder#idleTimeout(long, TimeUnit)} or triggered manually with {@link
+ * ManagedChannel#enterIdle()}.
+ *
+ *
You the caller are responsible for managing the lifecycle of any channels built by the
+ * resulting builder. They will not be shut down automatically.
+ *
+ * @param target A target uri which should resolve into an {@link AndroidComponentAddress}
+ * referencing the service to bind to.
+ * @param sourceContext the context to bind from (e.g. The current Activity or Application).
+ * @param channelCredentials the arbitrary binder specific channel credentials to be used to
+ * establish a binder connection.
+ * @return a new builder
+ */
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/10173")
+ public static BinderChannelBuilder forTarget(
+ String target, Context sourceContext, BinderChannelCredentials channelCredentials) {
+ return new BinderChannelBuilder(
+ null, checkNotNull(target, "target"), sourceContext, channelCredentials);
}
/**
@@ -121,12 +182,14 @@ public static BinderChannelBuilder forTarget(String target) {
private SecurityPolicy securityPolicy;
private InboundParcelablePolicy inboundParcelablePolicy;
private BindServiceFlags bindServiceFlags;
+ @Nullable private UserHandle targetUserHandle;
private boolean strictLifecycleManagement;
private BinderChannelBuilder(
@Nullable AndroidComponentAddress directAddress,
@Nullable String target,
- Context sourceContext) {
+ Context sourceContext,
+ BinderChannelCredentials channelCredentials) {
mainThreadExecutor =
ContextCompat.getMainExecutor(checkNotNull(sourceContext, "sourceContext"));
securityPolicy = SecurityPolicies.internalOnly();
@@ -139,10 +202,12 @@ final class BinderChannelTransportFactoryBuilder
public ClientTransportFactory buildClientTransportFactory() {
return new TransportFactory(
sourceContext,
+ channelCredentials,
mainThreadExecutor,
schedulerPool,
managedChannelImplBuilder.getOffloadExecutorPool(),
securityPolicy,
+ targetUserHandle,
bindServiceFlags,
inboundParcelablePolicy);
}
@@ -216,6 +281,23 @@ public BinderChannelBuilder securityPolicy(SecurityPolicy securityPolicy) {
return this;
}
+/**
+ * Provides the target {@UserHandle} of the remote Android service.
+ *
+ *
When targetUserHandle is set, Context.bindServiceAsUser will used and additional Android
+ * permissions will be required. If your usage does not require cross-user communications, please
+ * do not set this field. It is the caller's responsibility to make sure that it holds the
+ * corresponding permissions.
+ *
+ * @param targetUserHandle the target user to bind into.
+ * @return this
+ */
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/10173")
+ public BinderChannelBuilder bindAsUser(UserHandle targetUserHandle) {
+ this.targetUserHandle = targetUserHandle;
+ return this;
+ }
+
/** Sets the policy for inbound parcelable objects. */
public BinderChannelBuilder inboundParcelablePolicy(
InboundParcelablePolicy inboundParcelablePolicy) {
@@ -245,12 +327,14 @@ public BinderChannelBuilder idleTimeout(long value, TimeUnit unit) {
/** Creates new binder transports. */
private static final class TransportFactory implements ClientTransportFactory {
private final Context sourceContext;
+ private final BinderChannelCredentials channelCredentials;
private final Executor mainThreadExecutor;
private final ObjectPool scheduledExecutorPool;
private final ObjectPool extends Executor> offloadExecutorPool;
private final SecurityPolicy securityPolicy;
- private final InboundParcelablePolicy inboundParcelablePolicy;
+ @Nullable private final UserHandle targetUserHandle;
private final BindServiceFlags bindServiceFlags;
+ private final InboundParcelablePolicy inboundParcelablePolicy;
private ScheduledExecutorService executorService;
private Executor offloadExecutor;
@@ -258,17 +342,21 @@ private static final class TransportFactory implements ClientTransportFactory {
TransportFactory(
Context sourceContext,
+ BinderChannelCredentials channelCredentials,
Executor mainThreadExecutor,
ObjectPool scheduledExecutorPool,
ObjectPool extends Executor> offloadExecutorPool,
SecurityPolicy securityPolicy,
+ @Nullable UserHandle targetUserHandle,
BindServiceFlags bindServiceFlags,
InboundParcelablePolicy inboundParcelablePolicy) {
this.sourceContext = sourceContext;
+ this.channelCredentials = channelCredentials;
this.mainThreadExecutor = mainThreadExecutor;
this.scheduledExecutorPool = scheduledExecutorPool;
this.offloadExecutorPool = offloadExecutorPool;
this.securityPolicy = securityPolicy;
+ this.targetUserHandle = targetUserHandle;
this.bindServiceFlags = bindServiceFlags;
this.inboundParcelablePolicy = inboundParcelablePolicy;
@@ -284,7 +372,9 @@ public ConnectionClientTransport newClientTransport(
}
return new BinderTransport.BinderClientTransport(
sourceContext,
+ channelCredentials,
(AndroidComponentAddress) addr,
+ targetUserHandle,
bindServiceFlags,
mainThreadExecutor,
scheduledExecutorPool,
diff --git a/binder/src/main/java/io/grpc/binder/BinderChannelCredentials.java b/binder/src/main/java/io/grpc/binder/BinderChannelCredentials.java
new file mode 100644
index 000000000000..1fa2136e4a56
--- /dev/null
+++ b/binder/src/main/java/io/grpc/binder/BinderChannelCredentials.java
@@ -0,0 +1,70 @@
+/*
+ * 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 static com.google.common.base.Preconditions.checkNotNull;
+
+import android.content.ComponentName;
+import io.grpc.ChannelCredentials;
+import io.grpc.ExperimentalApi;
+import javax.annotation.Nullable;
+
+/** Additional arbitrary arguments to establish a Android binder connection channel. */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/10173")
+public final class BinderChannelCredentials extends ChannelCredentials {
+
+ /**
+ * Creates the default BinderChannelCredentials.
+ *
+ * @return a BinderChannelCredentials
+ */
+ public static BinderChannelCredentials forDefault() {
+ return new BinderChannelCredentials(null);
+ }
+
+ /**
+ * Creates a BinderChannelCredentials to be used with DevicePolicyManager API.
+ *
+ * @param devicePolicyAdminComponentName the admin component to be specified with
+ * DevicePolicyManager.bindDeviceAdminServiceAsUser API.
+ * @return a BinderChannelCredentials
+ */
+ public static BinderChannelCredentials forDevicePolicyAdmin(
+ ComponentName devicePolicyAdminComponentName) {
+ return new BinderChannelCredentials(devicePolicyAdminComponentName);
+ }
+
+ @Nullable private final ComponentName devicePolicyAdminComponentName;
+
+ private BinderChannelCredentials(@Nullable ComponentName devicePolicyAdminComponentName) {
+ this.devicePolicyAdminComponentName = devicePolicyAdminComponentName;
+ }
+
+ @Override
+ public ChannelCredentials withoutBearerTokens() {
+ return this;
+ }
+
+ /**
+ * Returns the admin component to be specified with DevicePolicyManager
+ * bindDeviceAdminServiceAsUser API.
+ */
+ @Nullable
+ public ComponentName getDevicePolicyAdminComponentName() {
+ return devicePolicyAdminComponentName;
+ }
+}
diff --git a/binder/src/main/java/io/grpc/binder/SecurityPolicies.java b/binder/src/main/java/io/grpc/binder/SecurityPolicies.java
index 1a31ef823d3f..1357fb217ae8 100644
--- a/binder/src/main/java/io/grpc/binder/SecurityPolicies.java
+++ b/binder/src/main/java/io/grpc/binder/SecurityPolicies.java
@@ -26,6 +26,7 @@
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Process;
+import androidx.annotation.RequiresApi;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
@@ -188,12 +189,12 @@ public Status checkAuthorization(int uid) {
* Creates {@link SecurityPolicy} which checks if the app is a device owner app. See
* {@link DevicePolicyManager}.
*/
- public static SecurityPolicy isDeviceOwner(Context applicationContext) {
+ @androidx.annotation.RequiresApi(18)
+ public static io.grpc.binder.SecurityPolicy isDeviceOwner(Context applicationContext) {
DevicePolicyManager devicePolicyManager =
(DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
return anyPackageWithUidSatisfies(
- applicationContext,
- pkg -> VERSION.SDK_INT >= 18 && devicePolicyManager.isDeviceOwnerApp(pkg),
+ applicationContext, pkg -> devicePolicyManager.isDeviceOwnerApp(pkg),
"Rejected by device owner policy. No packages found for UID.",
"Rejected by device owner policy");
}
diff --git a/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java b/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java
index 70b89165174f..cb2fe5be3e5d 100644
--- a/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java
+++ b/binder/src/main/java/io/grpc/binder/internal/BinderTransport.java
@@ -28,6 +28,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.TransactionTooLargeException;
+import android.os.UserHandle;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Ticker;
import com.google.common.util.concurrent.ListenableFuture;
@@ -46,6 +47,7 @@
import io.grpc.StatusException;
import io.grpc.binder.AndroidComponentAddress;
import io.grpc.binder.BindServiceFlags;
+import io.grpc.binder.BinderChannelCredentials;
import io.grpc.binder.InboundParcelablePolicy;
import io.grpc.binder.SecurityPolicy;
import io.grpc.internal.ClientStream;
@@ -568,7 +570,9 @@ public static final class BinderClientTransport extends BinderTransport
public BinderClientTransport(
Context sourceContext,
+ BinderChannelCredentials channelCredentials,
AndroidComponentAddress targetAddress,
+ @Nullable UserHandle targetUserHandle,
BindServiceFlags bindServiceFlags,
Executor mainThreadExecutor,
ObjectPool executorServicePool,
@@ -590,7 +594,9 @@ public BinderClientTransport(
new ServiceBinding(
mainThreadExecutor,
sourceContext,
+ channelCredentials,
targetAddress.asBindIntent(),
+ targetUserHandle,
bindServiceFlags.toInteger(),
this);
}
diff --git a/binder/src/main/java/io/grpc/binder/internal/ServiceBinding.java b/binder/src/main/java/io/grpc/binder/internal/ServiceBinding.java
index 650ead9bcdbf..32d0e7a4add6 100644
--- a/binder/src/main/java/io/grpc/binder/internal/ServiceBinding.java
+++ b/binder/src/main/java/io/grpc/binder/internal/ServiceBinding.java
@@ -16,15 +16,20 @@
package io.grpc.binder.internal;
+import static com.google.common.base.Preconditions.checkState;
+
+import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
+import android.os.UserHandle;
import androidx.annotation.AnyThread;
import androidx.annotation.MainThread;
import com.google.common.annotations.VisibleForTesting;
import io.grpc.Status;
+import io.grpc.binder.BinderChannelCredentials;
import java.util.concurrent.Executor;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -56,7 +61,26 @@ private enum State {
UNBOUND,
}
+ // Type of the method used when binding the service.
+ private enum BindMethodType {
+ BIND_SERVICE("bindService"),
+ BIND_SERVICE_AS_USER("bindServiceAsUser"),
+ DEVICE_POLICY_BIND_SEVICE_ADMIN("DevicePolicyManager.bindDeviceAdminServiceAsUser");
+
+ private final String methodName;
+
+ BindMethodType(String methodName) {
+ this.methodName = methodName;
+ }
+
+ public String methodName() {
+ return methodName;
+ }
+ }
+
+ private final BinderChannelCredentials channelCredentials;
private final Intent bindIntent;
+ @Nullable private final UserHandle targetUserHandle;
private final int bindFlags;
private final Observer observer;
private final Executor mainThreadExecutor;
@@ -76,7 +100,9 @@ private enum State {
ServiceBinding(
Executor mainThreadExecutor,
Context sourceContext,
+ BinderChannelCredentials channelCredentials,
Intent bindIntent,
+ @Nullable UserHandle targetUserHandle,
int bindFlags,
Observer observer) {
// We need to synchronize here ensure other threads see all
@@ -87,6 +113,8 @@ private enum State {
this.observer = observer;
this.sourceContext = sourceContext;
this.mainThreadExecutor = mainThreadExecutor;
+ this.channelCredentials = channelCredentials;
+ this.targetUserHandle = targetUserHandle;
state = State.NOT_BINDING;
reportedState = State.NOT_BINDING;
}
@@ -117,7 +145,14 @@ private void notifyUnbound(Status reason) {
public synchronized void bind() {
if (state == State.NOT_BINDING) {
state = State.BINDING;
- Status bindResult = bindInternal(sourceContext, bindIntent, this, bindFlags);
+ Status bindResult =
+ bindInternal(
+ sourceContext,
+ bindIntent,
+ this,
+ bindFlags,
+ channelCredentials,
+ targetUserHandle);
if (!bindResult.isOk()) {
handleBindServiceFailure(sourceContext, this);
state = State.UNBOUND;
@@ -127,19 +162,57 @@ public synchronized void bind() {
}
private static Status bindInternal(
- Context context, Intent bindIntent, ServiceConnection conn, int flags) {
+ Context context,
+ Intent bindIntent,
+ ServiceConnection conn,
+ int flags,
+ BinderChannelCredentials channelCredentials,
+ @Nullable UserHandle targetUserHandle) {
+ BindMethodType bindMethodType = BindMethodType.BIND_SERVICE;
try {
- if (!context.bindService(bindIntent, conn, flags)) {
+ if (targetUserHandle == null) {
+ checkState(
+ channelCredentials.getDevicePolicyAdminComponentName() == null,
+ "BindingChannelCredentials is expected to have null devicePolicyAdmin when"
+ + " targetUserHandle is not set");
+ } else {
+ if (channelCredentials.getDevicePolicyAdminComponentName() != null) {
+ bindMethodType = BindMethodType.DEVICE_POLICY_BIND_SEVICE_ADMIN;
+ } else {
+ bindMethodType = BindMethodType.BIND_SERVICE_AS_USER;
+ }
+ }
+ boolean bindResult = false;
+ switch (bindMethodType) {
+ case BIND_SERVICE:
+ bindResult = context.bindService(bindIntent, conn, flags);
+ break;
+ case BIND_SERVICE_AS_USER:
+ bindResult = context.bindServiceAsUser(bindIntent, conn, flags, targetUserHandle);
+ break;
+ case DEVICE_POLICY_BIND_SEVICE_ADMIN:
+ DevicePolicyManager devicePolicyManager =
+ (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ bindResult = devicePolicyManager.bindDeviceAdminServiceAsUser(
+ channelCredentials.getDevicePolicyAdminComponentName(),
+ bindIntent,
+ conn,
+ flags,
+ targetUserHandle);
+ break;
+ }
+ if (!bindResult) {
return Status.UNIMPLEMENTED.withDescription(
- "bindService(" + bindIntent + ") returned false");
+ bindMethodType.methodName() + "(" + bindIntent + ") returned false");
}
return Status.OK;
} catch (SecurityException e) {
- return Status.PERMISSION_DENIED.withCause(e).withDescription(
- "SecurityException from bindService");
+ return Status.PERMISSION_DENIED
+ .withCause(e)
+ .withDescription("SecurityException from " + bindMethodType.methodName());
} catch (RuntimeException e) {
return Status.INTERNAL.withCause(e).withDescription(
- "RuntimeException from bindService");
+ "RuntimeException from " + bindMethodType.methodName());
}
}
diff --git a/binder/src/test/java/io/grpc/binder/BinderChannelCredentialsTest.java b/binder/src/test/java/io/grpc/binder/BinderChannelCredentialsTest.java
new file mode 100644
index 000000000000..d31065dfe52b
--- /dev/null
+++ b/binder/src/test/java/io/grpc/binder/BinderChannelCredentialsTest.java
@@ -0,0 +1,32 @@
+package io.grpc.binder;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import android.content.Context;
+import androidx.test.core.app.ApplicationProvider;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class BinderChannelCredentialsTest {
+ private final Context appContext = ApplicationProvider.getApplicationContext();
+
+ @Test
+ public void defaultBinderChannelCredentials() {
+ BinderChannelCredentials channelCredentials = BinderChannelCredentials.forDefault();
+ assertThat(channelCredentials.getDevicePolicyAdminComponentName()).isNull();
+ }
+
+ @Test
+ public void binderChannelCredentialsForDevicePolicyAdmin() {
+ String deviceAdminClassName = "DevicePolicyAdmin";
+ BinderChannelCredentials channelCredentials =
+ BinderChannelCredentials.forDevicePolicyAdmin(
+ new ComponentName(appContext, deviceAdminClassName));
+ assertThat(channelCredentials.getDevicePolicyAdminComponentName()).isNotNull();
+ assertThat(channelCredentials.getDevicePolicyAdminComponentName().getClassName())
+ .isEqualTo(deviceAdminClassName);
+ }
+}
diff --git a/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java b/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java
index 6a6920905495..eda1b286a722 100644
--- a/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java
+++ b/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java
@@ -30,9 +30,6 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
-import android.os.Build;
-import android.os.Build.VERSION;
-import android.os.Build.VERSION_CODES;
import android.os.Process;
import androidx.test.core.app.ApplicationProvider;
import com.google.common.collect.ImmutableList;
@@ -333,7 +330,7 @@ public void testHasPermissions_failsIfPackageDoesNotHavePermissions() throws Exc
}
@Test
- @Config(sdk = 18)
+ @Config(sdk = 19)
public void testIsDeviceOwner_succeedsForDeviceOwner() throws Exception {
PackageInfo info =
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
@@ -348,7 +345,7 @@ public void testIsDeviceOwner_succeedsForDeviceOwner() throws Exception {
}
@Test
- @Config(sdk = 18)
+ @Config(sdk = 19)
public void testIsDeviceOwner_failsForNotDeviceOwner() throws Exception {
PackageInfo info =
newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
@@ -361,25 +358,13 @@ public void testIsDeviceOwner_failsForNotDeviceOwner() throws Exception {
}
@Test
- @Config(sdk = 18)
+ @Config(sdk = 19)
public void testIsDeviceOwner_failsWhenNoPackagesForUid() throws Exception {
policy = SecurityPolicies.isDeviceOwner(appContext);
assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.UNAUTHENTICATED.getCode());
}
- @Test
- @Config(sdk = 17)
- public void testIsDeviceOwner_failsForSdkLevelTooLow() throws Exception {
- PackageInfo info =
- newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
-
- installPackages(OTHER_UID, info);
-
- policy = SecurityPolicies.isDeviceOwner(appContext);
-
- assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
- }
@Test
@Config(sdk = 21)
diff --git a/binder/src/test/java/io/grpc/binder/internal/ServiceBindingTest.java b/binder/src/test/java/io/grpc/binder/internal/ServiceBindingTest.java
index 967e91b820d6..3ec65624fb8b 100644
--- a/binder/src/test/java/io/grpc/binder/internal/ServiceBindingTest.java
+++ b/binder/src/test/java/io/grpc/binder/internal/ServiceBindingTest.java
@@ -24,15 +24,21 @@
import static org.robolectric.annotation.LooperMode.Mode.PAUSED;
import android.app.Application;
+import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
+import android.os.Parcel;
+import android.os.UserHandle;
import androidx.core.content.ContextCompat;
import androidx.test.core.app.ApplicationProvider;
import io.grpc.Status;
import io.grpc.Status.Code;
+import io.grpc.binder.BinderChannelCredentials;
import io.grpc.binder.internal.Bindable.Observer;
+import java.util.Arrays;
+import javax.annotation.Nullable;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -44,6 +50,7 @@
import org.robolectric.annotation.Config;
import org.robolectric.annotation.LooperMode;
import org.robolectric.shadows.ShadowApplication;
+import org.robolectric.shadows.ShadowDevicePolicyManager;
@LooperMode(PAUSED)
@RunWith(RobolectricTestRunner.class)
@@ -256,6 +263,48 @@ public void testCallsAfterUnbindDontCrash() throws Exception {
shadowOf(getMainLooper()).idle();
}
+ @Test
+ @Config(sdk = 30)
+ public void testBindWithTargetUserHandle() throws Exception {
+ binding =
+ newBuilder().setTargetUserHandle(generateUserHandle(/* userId= */ 0)).build();
+ shadowOf(getMainLooper()).idle();
+
+ binding.bind();
+ shadowOf(getMainLooper()).idle();
+
+ assertThat(shadowApplication.getBoundServiceConnections()).isNotEmpty();
+ assertThat(observer.gotBoundEvent).isTrue();
+ assertThat(observer.binder).isSameInstanceAs(mockBinder);
+ assertThat(observer.gotUnboundEvent).isFalse();
+ assertThat(binding.isSourceContextCleared()).isFalse();
+ }
+
+ @Test
+ @Config(sdk = 30)
+ public void testBindWithDeviceAdmin() throws Exception {
+ String deviceAdminClassName = "DevicePolicyAdmin";
+ ComponentName adminComponent = new ComponentName(appContext, deviceAdminClassName);
+ allowBindDeviceAdminForUser(appContext, adminComponent, /* userId= */ 0);
+ binding =
+ newBuilder()
+ .setTargetUserHandle(UserHandle.getUserHandleForUid(/* userId= */ 0))
+ .setTargetUserHandle(generateUserHandle(/* userId= */ 0))
+ .setChannelCredentials(
+ BinderChannelCredentials.forDevicePolicyAdmin(adminComponent))
+ .build();
+ shadowOf(getMainLooper()).idle();
+
+ binding.bind();
+ shadowOf(getMainLooper()).idle();
+
+ assertThat(shadowApplication.getBoundServiceConnections()).isNotEmpty();
+ assertThat(observer.gotBoundEvent).isTrue();
+ assertThat(observer.binder).isSameInstanceAs(mockBinder);
+ assertThat(observer.gotUnboundEvent).isFalse();
+ assertThat(binding.isSourceContextCleared()).isFalse();
+ }
+
private void assertNoLockHeld() {
try {
binding.wait(1);
@@ -268,6 +317,28 @@ private void assertNoLockHeld() {
}
}
+ private static void allowBindDeviceAdminForUser(Context context, ComponentName admin, int userId) {
+ ShadowDevicePolicyManager devicePolicyManager =
+ shadowOf(context.getSystemService(DevicePolicyManager.class));
+ devicePolicyManager.setDeviceOwner(admin);
+ devicePolicyManager.setBindDeviceAdminTargetUsers(
+ Arrays.asList(UserHandle.getUserHandleForUid(userId)));
+ shadowOf((DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE));
+ devicePolicyManager.setDeviceOwner(admin);
+ devicePolicyManager.setBindDeviceAdminTargetUsers(
+ Arrays.asList(generateUserHandle(userId)));
+ }
+
+ /** Generate UserHandles the hard way. */
+ private static UserHandle generateUserHandle(int userId) {
+ Parcel userParcel = Parcel.obtain();
+ userParcel.writeInt(userId);
+ userParcel.setDataPosition(0);
+ UserHandle userHandle = new UserHandle(userParcel);
+ userParcel.recycle();
+ return userHandle;
+ }
+
private class TestObserver implements Bindable.Observer {
public boolean gotBoundEvent;
@@ -298,9 +369,11 @@ private static class ServiceBindingBuilder {
private Observer observer;
private Intent bindIntent = new Intent();
private int bindServiceFlags;
+ @Nullable private UserHandle targetUserHandle = null;
+ private BinderChannelCredentials channelCredentials = BinderChannelCredentials.forDefault();
public ServiceBindingBuilder setSourceContext(Context sourceContext) {
- this.sourceContext = sourceContext;
+ this.sourceContext = sourceContext;
return this;
}
@@ -324,11 +397,24 @@ public ServiceBindingBuilder setObserver(Observer observer) {
return this;
}
+ public ServiceBindingBuilder setTargetUserHandle(UserHandle targetUserHandle) {
+ this.targetUserHandle = targetUserHandle;
+ return this;
+ }
+
+ public ServiceBindingBuilder setChannelCredentials(
+ BinderChannelCredentials channelCredentials) {
+ this.channelCredentials = channelCredentials;
+ return this;
+ }
+
public ServiceBinding build() {
return new ServiceBinding(
ContextCompat.getMainExecutor(sourceContext),
sourceContext,
+ channelCredentials,
bindIntent,
+ targetUserHandle,
bindServiceFlags,
observer);
}
diff --git a/build.gradle b/build.gradle
index 42e008e80d66..779f10309697 100644
--- a/build.gradle
+++ b/build.gradle
@@ -20,7 +20,7 @@ subprojects {
apply plugin: "net.ltgt.errorprone"
group = "io.grpc"
- version = "1.57.0-SNAPSHOT" // CURRENT_GRPC_VERSION
+ version = "1.59.0-SNAPSHOT" // CURRENT_GRPC_VERSION
repositories {
maven { // The google mirror is less flaky than mavenCentral()
@@ -37,6 +37,9 @@ subprojects {
"-Xlint:-try"
]
it.options.encoding = "UTF-8"
+ // Avoid Gradle OOM.
+ // https://docs.gradle.org/current/userguide/performance.html#run_the_compiler_as_a_separate_process
+ it.options.fork = true
if (rootProject.hasProperty('failOnWarnings') && rootProject.failOnWarnings.toBoolean()) {
it.options.compilerArgs += ["-Werror"]
}
@@ -68,11 +71,6 @@ subprojects {
}
generateProtoTasks {
all().each { task ->
- // Recompile protos when build.gradle has been changed, because
- // it's possible the version of protoc has been changed.
- task.inputs.file("${rootProject.projectDir}/build.gradle")
- .withPathSensitivity(PathSensitivity.RELATIVE)
- .withPropertyName('root build.gradle')
if (isAndroid) {
task.builtins {
java { option 'lite' }
@@ -164,7 +162,8 @@ subprojects {
checkstyle {
configDirectory = file("$rootDir/buildscripts")
- toolVersion = libs.checkstyle.get().version
+ toolVersion = JavaVersion.current().isJava11Compatible() ? libs.checkstyle.get().version : libs.checkstylejava8.get().version
+
ignoreFailures = false
if (rootProject.hasProperty("checkstyle.ignoreFailures")) {
ignoreFailures = rootProject.properties["checkstyle.ignoreFailures"].toBoolean()
@@ -183,9 +182,6 @@ subprojects {
}
plugins.withId("java") {
- sourceCompatibility = 1.8
- targetCompatibility = 1.8
-
dependencies {
testImplementation libraries.junit,
libraries.mockito.core,
@@ -241,12 +237,25 @@ subprojects {
}
}
+ tasks.withType(JavaCompile).configureEach {
+ if (JavaVersion.current().isJava9Compatible()) {
+ options.release = 8
+ } else {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ }
tasks.named("compileJava").configure {
// This project targets Java 7 (no time.Duration class)
options.errorprone.check("PreferJavaTimeOverload", CheckSeverity.OFF)
options.errorprone.check("JavaUtilDate", CheckSeverity.OFF)
// The warning fails to provide a source location
options.errorprone.check("MissingSummary", CheckSeverity.OFF)
+
+ // TODO(https://github.com/grpc/grpc-java/issues/10372): remove when fixed.
+ if (JavaVersion.current().isJava11Compatible()) {
+ options.errorprone.check("StringCaseLocaleUsage", CheckSeverity.OFF)
+ }
}
tasks.named("compileTestJava").configure {
// LinkedList doesn't hurt much in tests and has lots of usages
@@ -273,7 +282,7 @@ subprojects {
requireUpperBoundDepsMatch(configurations.runtimeClasspath, project)
}
}
- tasks.named('compileJava').configure {
+ tasks.named('assemble').configure {
dependsOn checkUpperBoundDeps
}
}
@@ -281,10 +290,6 @@ subprojects {
plugins.withId("me.champeau.jmh") {
// invoke jmh on a single benchmark class like so:
// ./gradlew -PjmhIncludeSingleClass=StatsTraceContextBenchmark clean :grpc-core:jmh
- tasks.named("compileJmhJava").configure {
- sourceCompatibility = 1.8
- targetCompatibility = 1.8
- }
tasks.named("jmh").configure {
warmupIterations = 10
iterations = 10
@@ -412,31 +417,25 @@ subprojects {
def baselineGrpcVersion = '1.6.1'
// Get the baseline version's jar for this subproject
- File baselineArtifact = null
- // Use a detached configuration, otherwise the current version's jar will take precedence
- // over the baseline jar.
+ configurations {
+ baselineArtifact
+ }
// A necessary hack, the intuitive thing does NOT work:
// https://discuss.gradle.org/t/is-the-default-configuration-leaking-into-independent-configurations/2088/6
def oldGroup = project.group
try {
project.group = 'virtual_group_for_japicmp'
- String depModule = "io.grpc:${project.name}:${baselineGrpcVersion}@jar"
- String depJar = "${project.name}-${baselineGrpcVersion}.jar"
- Configuration configuration = configurations.detachedConfiguration(
- dependencies.create(depModule)
- )
- baselineArtifact = files(configuration.files).filter {
- it.name.equals(depJar)
- }.singleFile
+ dependencies {
+ baselineArtifact "io.grpc:${project.name}:${baselineGrpcVersion}@jar"
+ }
} finally {
project.group = oldGroup
}
// Add a japicmp task that compares the current .jar with baseline .jar
tasks.register("japicmp", me.champeau.gradle.japicmp.JapicmpTask) {
- dependsOn jar
- oldClasspath = files(baselineArtifact)
- newClasspath = files(jar.archiveFile)
+ oldClasspath.from configurations.baselineArtifact
+ newClasspath.from tasks.named("jar")
onlyBinaryIncompatibleModified = false
// Be quiet about things that did not change
onlyModified = true
@@ -449,12 +448,10 @@ subprojects {
// Also break on source incompatible changes, not just binary.
// Eg adding abstract method to public class.
- // TODO(zpencer): enable after japicmp-gradle-plugin/pull/14
- // breakOnSourceIncompatibility = true
+ failOnSourceIncompatibility = true
// Ignore any classes or methods marked @ExperimentalApi
- // TODO(zpencer): enable after japicmp-gradle-plugin/pull/15
- // annotationExcludes = ['@io.grpc.ExperimentalApi']
+ annotationExcludes = ['@io.grpc.ExperimentalApi']
}
}
}
@@ -506,6 +503,11 @@ def requireUpperBoundDepsMatch(Configuration conf, Project project) {
+ "to diagnose")
}
result.selected.dependencies.each {
+ // Category.CATEGORY_ATTRIBUTE is the inappropriate Attribute because it is "desugared".
+ // https://github.com/gradle/gradle/issues/8854
+ Attribute category = Attribute.of('org.gradle.category', String)
+ if (it.resolvedVariant.attributes.getAttribute(category) != Category.LIBRARY)
+ return
queue.add(new DepAndParents(
dep: it, parents: depAndParents.parents + [artifact + ":" + version]))
}
@@ -570,7 +572,7 @@ tasks.register('checkForUpdates') {
if (oldResolved != newResolved) {
def oldId = oldResolved.id.componentIdentifier
def newId = newResolved.id.componentIdentifier
- println("${newId.group}:${newId.module} ${oldId.version} -> ${newId.version}")
+ println("libs.${name} = ${newId.group}:${newId.module} ${oldId.version} -> ${newId.version}")
}
}
}
diff --git a/buildscripts/checkstyle.xml b/buildscripts/checkstyle.xml
index a5aded93a80d..960fa162ed1d 100644
--- a/buildscripts/checkstyle.xml
+++ b/buildscripts/checkstyle.xml
@@ -199,7 +199,7 @@
-
+
- 1.57.0-SNAPSHOT
+ 1.59.0-SNAPSHOT
example-debug
https://github.com/grpc/grpc-java
UTF-8
- 1.57.0-SNAPSHOT
- 3.22.3
+ 1.59.0-SNAPSHOT
+ 3.24.0
1.8
1.8
diff --git a/examples/example-gauth/build.gradle b/examples/example-gauth/build.gradle
index 917d5ae0cf1d..d7e6a24a7257 100644
--- a/examples/example-gauth/build.gradle
+++ b/examples/example-gauth/build.gradle
@@ -1,8 +1,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.9.4'
// Generate IntelliJ IDEA's .idea & .iml project files
id 'idea'
}
@@ -15,16 +14,18 @@ repositories {
mavenLocal()
}
-sourceCompatibility = 1.8
-targetCompatibility = 1.8
+java {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+}
// IMPORTANT: You probably want the non-SNAPSHOT version of gRPC. Make sure you
// are looking at a tagged version of the example and not "master"!
// Feel free to delete the comment at the next line. It is just for safely
// updating the version in our release process.
-def grpcVersion = '1.57.0-SNAPSHOT' // CURRENT_GRPC_VERSION
-def protobufVersion = '3.22.3'
+def grpcVersion = '1.59.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def protobufVersion = '3.24.0'
def protocVersion = protobufVersion
@@ -67,7 +68,9 @@ task googleAuthClient(type: CreateStartScripts) {
classpath = startScripts.classpath
}
-applicationDistribution.into('bin') {
- from(googleAuthClient)
- fileMode = 0755
+application {
+ applicationDistribution.into('bin') {
+ from(googleAuthClient)
+ fileMode = 0755
+ }
}
diff --git a/examples/example-gauth/pom.xml b/examples/example-gauth/pom.xml
index ce4bfd7afa90..282ac4a3b8e5 100644
--- a/examples/example-gauth/pom.xml
+++ b/examples/example-gauth/pom.xml
@@ -6,14 +6,14 @@
jar
- 1.57.0-SNAPSHOT
+ 1.59.0-SNAPSHOT
example-gauth
https://github.com/grpc/grpc-java
UTF-8
- 1.57.0-SNAPSHOT
- 3.22.3
+ 1.59.0-SNAPSHOT
+ 3.24.0
1.8
1.8
diff --git a/examples/example-gcp-observability/build.gradle b/examples/example-gcp-observability/build.gradle
index 5c44087c6bbe..ed5acb81c39e 100644
--- a/examples/example-gcp-observability/build.gradle
+++ b/examples/example-gcp-observability/build.gradle
@@ -1,8 +1,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.9.4'
// Generate IntelliJ IDEA's .idea & .iml project files
id 'idea'
id 'java'
@@ -16,16 +15,18 @@ repositories {
mavenLocal()
}
-sourceCompatibility = 1.8
-targetCompatibility = 1.8
+java {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+}
// IMPORTANT: You probably want the non-SNAPSHOT version of gRPC. Make sure you
// are looking at a tagged version of the example and not "master"!
// Feel free to delete the comment at the next line. It is just for safely
// updating the version in our release process.
-def grpcVersion = '1.57.0-SNAPSHOT' // CURRENT_GRPC_VERSION
-def protocVersion = '3.22.3'
+def grpcVersion = '1.59.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def protocVersion = '3.24.0'
dependencies {
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
@@ -61,8 +62,10 @@ task ObservabilityHelloWorldClient(type: CreateStartScripts) {
classpath = startScripts.classpath
}
-applicationDistribution.into('bin') {
- from(ObservabilityHelloWorldServer)
- from(ObservabilityHelloWorldClient)
- fileMode = 0755
+application {
+ applicationDistribution.into('bin') {
+ from(ObservabilityHelloWorldServer)
+ from(ObservabilityHelloWorldClient)
+ fileMode = 0755
+ }
}
diff --git a/examples/example-hostname/build.gradle b/examples/example-hostname/build.gradle
index 6f65a3103c17..9784fbb531a6 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.9.4"
id 'com.google.cloud.tools.jib' version '3.1.4' // For releasing to Docker Hub
}
@@ -13,16 +13,18 @@ repositories {
mavenLocal()
}
-sourceCompatibility = 1.8
-targetCompatibility = 1.8
+java {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+}
// IMPORTANT: You probably want the non-SNAPSHOT version of gRPC. Make sure you
// are looking at a tagged version of the example and not "master"!
// Feel free to delete the comment at the next line. It is just for safely
// updating the version in our release process.
-def grpcVersion = '1.57.0-SNAPSHOT' // CURRENT_GRPC_VERSION
-def protobufVersion = '3.22.3'
+def grpcVersion = '1.59.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def protobufVersion = '3.24.0'
dependencies {
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
diff --git a/examples/example-hostname/pom.xml b/examples/example-hostname/pom.xml
index 5520ffe201a1..a8bc7c485a06 100644
--- a/examples/example-hostname/pom.xml
+++ b/examples/example-hostname/pom.xml
@@ -6,14 +6,14 @@
jar
- 1.57.0-SNAPSHOT
+ 1.59.0-SNAPSHOT
example-hostname
https://github.com/grpc/grpc-java
UTF-8
- 1.57.0-SNAPSHOT
- 3.22.3
+ 1.59.0-SNAPSHOT
+ 3.24.0
1.8
1.8
diff --git a/examples/example-jwt-auth/build.gradle b/examples/example-jwt-auth/build.gradle
index f9ebf8509437..bfab22662762 100644
--- a/examples/example-jwt-auth/build.gradle
+++ b/examples/example-jwt-auth/build.gradle
@@ -1,8 +1,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.9.4'
// Generate IntelliJ IDEA's .idea & .iml project files
id 'idea'
}
@@ -14,16 +13,18 @@ repositories {
mavenLocal()
}
-sourceCompatibility = 1.8
-targetCompatibility = 1.8
+java {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+}
// IMPORTANT: You probably want the non-SNAPSHOT version of gRPC. Make sure you
// are looking at a tagged version of the example and not "master"!
// Feel free to delete the comment at the next line. It is just for safely
// updating the version in our release process.
-def grpcVersion = '1.57.0-SNAPSHOT' // CURRENT_GRPC_VERSION
-def protobufVersion = '3.22.3'
+def grpcVersion = '1.59.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def protobufVersion = '3.24.0'
def protocVersion = protobufVersion
dependencies {
@@ -77,8 +78,10 @@ task hellowWorldJwtAuthClient(type: CreateStartScripts) {
classpath = startScripts.classpath
}
-applicationDistribution.into('bin') {
- from(hellowWorldJwtAuthServer)
- from(hellowWorldJwtAuthClient)
- fileMode = 0755
+application {
+ applicationDistribution.into('bin') {
+ from(hellowWorldJwtAuthServer)
+ from(hellowWorldJwtAuthClient)
+ fileMode = 0755
+ }
}
diff --git a/examples/example-jwt-auth/pom.xml b/examples/example-jwt-auth/pom.xml
index 55b01c914b6f..2ba3e0ae9c15 100644
--- a/examples/example-jwt-auth/pom.xml
+++ b/examples/example-jwt-auth/pom.xml
@@ -7,15 +7,15 @@
jar
- 1.57.0-SNAPSHOT
+ 1.59.0-SNAPSHOT
example-jwt-auth
https://github.com/grpc/grpc-java
UTF-8
- 1.57.0-SNAPSHOT
- 3.22.3
- 3.22.3
+ 1.59.0-SNAPSHOT
+ 3.24.0
+ 3.24.0
1.8
1.8
diff --git a/examples/example-jwt-auth/src/main/java/io/grpc/examples/jwtauth/JwtCredential.java b/examples/example-jwt-auth/src/main/java/io/grpc/examples/jwtauth/JwtCredential.java
index 4975b4a0ab32..bf6243224d20 100644
--- a/examples/example-jwt-auth/src/main/java/io/grpc/examples/jwtauth/JwtCredential.java
+++ b/examples/example-jwt-auth/src/main/java/io/grpc/examples/jwtauth/JwtCredential.java
@@ -60,9 +60,4 @@ public void run() {
}
});
}
-
- @Override
- public void thisUsesUnstableApi() {
- // noop
- }
}
diff --git a/examples/example-orca/build.gradle b/examples/example-orca/build.gradle
index a9a0066d1166..a9a6f7539727 100644
--- a/examples/example-orca/build.gradle
+++ b/examples/example-orca/build.gradle
@@ -1,7 +1,6 @@
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.9.4'
// Generate IntelliJ IDEA's .idea & .iml project files
id 'idea'
id 'java'
@@ -14,11 +13,13 @@ repositories {
mavenLocal()
}
-sourceCompatibility = 1.8
-targetCompatibility = 1.8
+java {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+}
-def grpcVersion = '1.57.0-SNAPSHOT' // CURRENT_GRPC_VERSION
-def protocVersion = '3.22.3'
+def grpcVersion = '1.59.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def protocVersion = '3.24.0'
dependencies {
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
@@ -55,8 +56,10 @@ task CustomBackendMetricsServer(type: CreateStartScripts) {
classpath = startScripts.classpath
}
-applicationDistribution.into('bin') {
- from(CustomBackendMetricsClient)
- from(CustomBackendMetricsServer)
- fileMode = 0755
+application {
+ applicationDistribution.into('bin') {
+ from(CustomBackendMetricsClient)
+ from(CustomBackendMetricsServer)
+ fileMode = 0755
+ }
}
diff --git a/examples/example-reflection/build.gradle b/examples/example-reflection/build.gradle
index bda681ece274..3c81364b0793 100644
--- a/examples/example-reflection/build.gradle
+++ b/examples/example-reflection/build.gradle
@@ -1,7 +1,6 @@
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.9.4'
// Generate IntelliJ IDEA's .idea & .iml project files
id 'idea'
id 'java'
@@ -14,11 +13,13 @@ repositories {
mavenLocal()
}
-sourceCompatibility = 1.8
-targetCompatibility = 1.8
+java {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+}
-def grpcVersion = '1.57.0-SNAPSHOT' // CURRENT_GRPC_VERSION
-def protocVersion = '3.22.3'
+def grpcVersion = '1.59.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def protocVersion = '3.24.0'
dependencies {
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
@@ -48,7 +49,9 @@ task ReflectionServer(type: CreateStartScripts) {
classpath = startScripts.classpath
}
-applicationDistribution.into('bin') {
- from(ReflectionServer)
- fileMode = 0755
+application {
+ applicationDistribution.into('bin') {
+ from(ReflectionServer)
+ fileMode = 0755
+ }
}
diff --git a/examples/example-servlet/build.gradle b/examples/example-servlet/build.gradle
index 0ea93dd70083..39d518274ea8 100644
--- a/examples/example-servlet/build.gradle
+++ b/examples/example-servlet/build.gradle
@@ -1,6 +1,5 @@
plugins {
- // 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.9.4'
// Generate IntelliJ IDEA's .idea & .iml project files
id 'idea'
id 'war'
@@ -12,11 +11,13 @@ repositories {
mavenLocal()
}
-sourceCompatibility = 1.8
-targetCompatibility = 1.8
+java {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+}
-def grpcVersion = '1.57.0-SNAPSHOT' // CURRENT_GRPC_VERSION
-def protocVersion = '3.22.3'
+def grpcVersion = '1.59.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def protocVersion = '3.24.0'
dependencies {
implementation "io.grpc:grpc-protobuf:${grpcVersion}",
diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle
index ea70f249904c..90c941e66b67 100644
--- a/examples/example-tls/build.gradle
+++ b/examples/example-tls/build.gradle
@@ -1,8 +1,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.9.4'
// Generate IntelliJ IDEA's .idea & .iml project files
id 'idea'
}
@@ -15,16 +14,18 @@ repositories {
mavenLocal()
}
-sourceCompatibility = 1.8
-targetCompatibility = 1.8
+java {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+}
// IMPORTANT: You probably want the non-SNAPSHOT version of gRPC. Make sure you
// are looking at a tagged version of the example and not "master"!
// Feel free to delete the comment at the next line. It is just for safely
// updating the version in our release process.
-def grpcVersion = '1.57.0-SNAPSHOT' // CURRENT_GRPC_VERSION
-def protocVersion = '3.22.3'
+def grpcVersion = '1.59.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def protocVersion = '3.24.0'
dependencies {
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
@@ -69,8 +70,10 @@ task helloWorldTlsClient(type: CreateStartScripts) {
classpath = startScripts.classpath
}
-applicationDistribution.into('bin') {
- from(helloWorldTlsServer)
- from(helloWorldTlsClient)
- fileMode = 0755
+application {
+ applicationDistribution.into('bin') {
+ from(helloWorldTlsServer)
+ from(helloWorldTlsClient)
+ fileMode = 0755
+ }
}
diff --git a/examples/example-tls/pom.xml b/examples/example-tls/pom.xml
index 3476f8934277..854790546130 100644
--- a/examples/example-tls/pom.xml
+++ b/examples/example-tls/pom.xml
@@ -6,14 +6,14 @@
jar
- 1.57.0-SNAPSHOT
+ 1.59.0-SNAPSHOT
example-tls
https://github.com/grpc/grpc-java
UTF-8
- 1.57.0-SNAPSHOT
- 3.22.3
+ 1.59.0-SNAPSHOT
+ 3.24.0
1.8
1.8
diff --git a/examples/example-xds/build.gradle b/examples/example-xds/build.gradle
index a772edafee02..de449268b9c1 100644
--- a/examples/example-xds/build.gradle
+++ b/examples/example-xds/build.gradle
@@ -1,7 +1,6 @@
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.9.4'
// Generate IntelliJ IDEA's .idea & .iml project files
id 'idea'
id 'java'
@@ -14,16 +13,18 @@ repositories {
mavenLocal()
}
-sourceCompatibility = 1.8
-targetCompatibility = 1.8
+java {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+}
// IMPORTANT: You probably want the non-SNAPSHOT version of gRPC. Make sure you
// are looking at a tagged version of the example and not "master"!
// Feel free to delete the comment at the next line. It is just for safely
// updating the version in our release process.
-def grpcVersion = '1.57.0-SNAPSHOT' // CURRENT_GRPC_VERSION
-def protocVersion = '3.22.3'
+def grpcVersion = '1.59.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def protocVersion = '3.24.0'
dependencies {
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
@@ -61,8 +62,10 @@ task xdsHelloWorldServer(type: CreateStartScripts) {
classpath = startScripts.classpath
}
-applicationDistribution.into('bin') {
- from(xdsHelloWorldClient)
- from(xdsHelloWorldServer)
- fileMode = 0755
+application {
+ applicationDistribution.into('bin') {
+ from(xdsHelloWorldClient)
+ from(xdsHelloWorldServer)
+ fileMode = 0755
+ }
}
diff --git a/examples/gradle/wrapper/gradle-wrapper.properties b/examples/gradle/wrapper/gradle-wrapper.properties
index 070cb702f09e..db9a6b825d7f 100644
--- a/examples/gradle/wrapper/gradle-wrapper.properties
+++ b/examples/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/examples/pom.xml b/examples/pom.xml
index b928e5be0742..8316e9a5d4ee 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -6,15 +6,15 @@
jar
- 1.57.0-SNAPSHOT
+ 1.59.0-SNAPSHOT
examples
https://github.com/grpc/grpc-java
UTF-8
- 1.57.0-SNAPSHOT
- 3.22.3
- 3.22.3
+ 1.59.0-SNAPSHOT
+ 3.24.0
+ 3.24.0
1.8
1.8
diff --git a/examples/src/main/java/io/grpc/examples/preserialized/ByteArrayMarshaller.java b/examples/src/main/java/io/grpc/examples/preserialized/ByteArrayMarshaller.java
new file mode 100644
index 000000000000..c6f099280f90
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/preserialized/ByteArrayMarshaller.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2023 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.preserialized;
+
+import com.google.common.io.ByteStreams;
+import io.grpc.MethodDescriptor;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A marshaller that produces a byte[] instead of decoding into typical POJOs. It can be used for
+ * any message type.
+ */
+final class ByteArrayMarshaller implements MethodDescriptor.Marshaller {
+ @Override
+ public byte[] parse(InputStream stream) {
+ try {
+ return ByteStreams.toByteArray(stream);
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ @Override
+ public InputStream stream(byte[] b) {
+ return new ByteArrayInputStream(b);
+ }
+}
diff --git a/examples/src/main/java/io/grpc/examples/preserialized/PreSerializedClient.java b/examples/src/main/java/io/grpc/examples/preserialized/PreSerializedClient.java
new file mode 100644
index 000000000000..511e8a177f8a
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/preserialized/PreSerializedClient.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2023 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.preserialized;
+
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.Grpc;
+import io.grpc.InsecureChannelCredentials;
+import io.grpc.ManagedChannel;
+import io.grpc.MethodDescriptor;
+import io.grpc.StatusRuntimeException;
+import io.grpc.examples.helloworld.GreeterGrpc;
+import io.grpc.examples.helloworld.HelloReply;
+import io.grpc.examples.helloworld.HelloRequest;
+import io.grpc.stub.ClientCalls;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A client that requests a greeting from a hello-world server, but using a pre-serialized request.
+ * This is a performance optimization that can be useful if you read the request from on-disk or a
+ * database where it is already serialized, or if you need to send the same complicated message to
+ * many servers. The same approach can avoid deserializing responses, to be stored in a database.
+ * This adjustment is client-side only; the server is unable to detect the difference, so this
+ * client is fully-compatible with the normal {@link HelloWorldServer}.
+ */
+public class PreSerializedClient {
+ private static final Logger logger = Logger.getLogger(PreSerializedClient.class.getName());
+
+ /**
+ * Modified sayHello() descriptor with bytes as the request, instead of HelloRequest. By adjusting
+ * toBuilder() you can choose which of the request and response are bytes.
+ */
+ private static final MethodDescriptor SAY_HELLO
+ = GreeterGrpc.getSayHelloMethod()
+ .toBuilder(new ByteArrayMarshaller(), GreeterGrpc.getSayHelloMethod().getResponseMarshaller())
+ .build();
+
+ private final Channel channel;
+
+ /** Construct client for accessing hello-world server using the existing channel. */
+ public PreSerializedClient(Channel channel) {
+ this.channel = channel;
+ }
+
+ /** Say hello to server. */
+ public void greet(String name) {
+ logger.info("Will try to greet " + name + " ...");
+ byte[] request = HelloRequest.newBuilder().setName(name).build().toByteArray();
+ HelloReply response;
+ try {
+ // Stubs use ClientCalls to send RPCs. Since the generated stub won't have byte[] in its
+ // method signature, this uses ClientCalls directly. It isn't as convenient, but it behaves
+ // the same as a normal stub.
+ response = ClientCalls.blockingUnaryCall(channel, SAY_HELLO, CallOptions.DEFAULT, request);
+ } catch (StatusRuntimeException e) {
+ logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
+ return;
+ }
+ logger.info("Greeting: " + response.getMessage());
+ }
+
+ /**
+ * Greet server. If provided, the first element of {@code args} is the name to use in the
+ * greeting. The second argument is the target server.
+ */
+ public static void main(String[] args) throws Exception {
+ String user = "world";
+ String target = "localhost:50051";
+ if (args.length > 0) {
+ if ("--help".equals(args[0])) {
+ System.err.println("Usage: [name [target]]");
+ System.err.println("");
+ System.err.println(" name The name you wish to be greeted by. Defaults to " + user);
+ System.err.println(" target The server to connect to. Defaults to " + target);
+ System.exit(1);
+ }
+ user = args[0];
+ }
+ if (args.length > 1) {
+ target = args[1];
+ }
+
+ ManagedChannel channel = Grpc.newChannelBuilder(target, InsecureChannelCredentials.create())
+ .build();
+ try {
+ PreSerializedClient client = new PreSerializedClient(channel);
+ client.greet(user);
+ } finally {
+ channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
+ }
+ }
+}
diff --git a/examples/src/main/java/io/grpc/examples/preserialized/PreSerializedServer.java b/examples/src/main/java/io/grpc/examples/preserialized/PreSerializedServer.java
new file mode 100644
index 000000000000..51beca57386c
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/preserialized/PreSerializedServer.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2023 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.preserialized;
+
+import io.grpc.BindableService;
+import io.grpc.Grpc;
+import io.grpc.InsecureServerCredentials;
+import io.grpc.MethodDescriptor;
+import io.grpc.Server;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerMethodDefinition;
+import io.grpc.ServerServiceDefinition;
+import io.grpc.ServiceDescriptor;
+import io.grpc.examples.helloworld.GreeterGrpc;
+import io.grpc.examples.helloworld.HelloReply;
+import io.grpc.examples.helloworld.HelloRequest;
+import io.grpc.stub.ServerCalls;
+import io.grpc.stub.StreamObserver;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+
+/**
+ * Server that provides a {@code Greeter} service, but that uses a pre-serialized response. This is
+ * a performance optimization that can be useful if you read the response from on-disk or a database
+ * where it is already serialized, or if you need to send the same complicated message to many
+ * clients. The same approach can avoid deserializing requests, to be stored in a database. This
+ * adjustment is server-side only; the client is unable to detect the differences, so this server is
+ * fully-compatible with the normal {@link HelloWorldClient}.
+ */
+public class PreSerializedServer {
+ private static final Logger logger = Logger.getLogger(PreSerializedServer.class.getName());
+
+ private Server server;
+
+ private void start() throws IOException {
+ int port = 50051;
+ server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create())
+ .addService(new GreeterImpl())
+ .build()
+ .start();
+ logger.info("Server started, listening on " + port);
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ @Override
+ public void run() {
+ // Use stderr here since the logger may have been reset by its JVM shutdown hook.
+ System.err.println("*** shutting down gRPC server since JVM is shutting down");
+ try {
+ PreSerializedServer.this.stop();
+ } catch (InterruptedException e) {
+ e.printStackTrace(System.err);
+ }
+ System.err.println("*** server shut down");
+ }
+ });
+ }
+
+ private void stop() throws InterruptedException {
+ if (server != null) {
+ server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
+ }
+ }
+
+ /**
+ * Await termination on the main thread since the grpc library uses daemon threads.
+ */
+ private void blockUntilShutdown() throws InterruptedException {
+ if (server != null) {
+ server.awaitTermination();
+ }
+ }
+
+ /**
+ * Main launches the server from the command line.
+ */
+ public static void main(String[] args) throws IOException, InterruptedException {
+ final PreSerializedServer server = new PreSerializedServer();
+ server.start();
+ server.blockUntilShutdown();
+ }
+
+ static class GreeterImpl implements GreeterGrpc.AsyncService, BindableService {
+
+ public void byteSayHello(HelloRequest req, StreamObserver responseObserver) {
+ HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
+ responseObserver.onNext(reply.toByteArray());
+ responseObserver.onCompleted();
+ }
+
+ @Override
+ public ServerServiceDefinition bindService() {
+ MethodDescriptor sayHello = GreeterGrpc.getSayHelloMethod();
+ // Modifying the method descriptor to use bytes as the response, instead of HelloReply. By
+ // adjusting toBuilder() you can choose which of the request and response are bytes.
+ MethodDescriptor byteSayHello = sayHello
+ .toBuilder(sayHello.getRequestMarshaller(), new ByteArrayMarshaller())
+ .build();
+ // GreeterGrpc.bindService() will bind every service method, including sayHello(). (Although
+ // Greeter only has one method, this approach would work for any service.) AsyncService
+ // provides a default implementation of sayHello() that returns UNIMPLEMENTED, and that
+ // implementation will be used by bindService(). replaceMethod() will rewrite that method to
+ // use our byte-based method instead.
+ //
+ // The generated bindService() uses ServerCalls to make RPC handlers. Since the generated
+ // bindService() won't expect byte[] in the AsyncService, this uses ServerCalls directly. It
+ // isn't as convenient, but it behaves the same as a normal RPC handler.
+ return replaceMethod(
+ GreeterGrpc.bindService(this),
+ byteSayHello,
+ ServerCalls.asyncUnaryCall(this::byteSayHello));
+ }
+
+ /** Rewrites the ServerServiceDefinition replacing one method's definition. */
+ private static ServerServiceDefinition replaceMethod(
+ ServerServiceDefinition def,
+ MethodDescriptor newDesc,
+ ServerCallHandler newHandler) {
+ // There are two data structures involved. The first is the "descriptor" which describes the
+ // service and methods as a schema. This is the same on client and server. The second is the
+ // "definition" which includes the handlers to execute methods. This is specific to the server
+ // and is generated by "bind." This adjusts both the descriptor and definition.
+
+ // Descriptor
+ ServiceDescriptor desc = def.getServiceDescriptor();
+ ServiceDescriptor.Builder descBuilder = ServiceDescriptor.newBuilder(desc.getName())
+ .setSchemaDescriptor(desc.getSchemaDescriptor())
+ .addMethod(newDesc); // Add the modified method
+ // Copy methods other than the modified one
+ for (MethodDescriptor,?> md : desc.getMethods()) {
+ if (newDesc.getFullMethodName().equals(md.getFullMethodName())) {
+ continue;
+ }
+ descBuilder.addMethod(md);
+ }
+
+ // Definition
+ ServerServiceDefinition.Builder defBuilder =
+ ServerServiceDefinition.builder(descBuilder.build())
+ .addMethod(newDesc, newHandler); // Add the modified method
+ // Copy methods other than the modified one
+ for (ServerMethodDefinition,?> smd : def.getMethods()) {
+ if (newDesc.getFullMethodName().equals(smd.getMethodDescriptor().getFullMethodName())) {
+ continue;
+ }
+ defBuilder.addMethod(smd);
+ }
+ return defBuilder.build();
+ }
+ }
+}
diff --git a/gae-interop-testing/gae-jdk8/build.gradle b/gae-interop-testing/gae-jdk8/build.gradle
index 78cadc60582d..f3ff765ddfb0 100644
--- a/gae-interop-testing/gae-jdk8/build.gradle
+++ b/gae-interop-testing/gae-jdk8/build.gradle
@@ -90,9 +90,6 @@ appengine {
group = 'io.grpc' // Generated output GroupId
version = '1.0-SNAPSHOT' // Version in generated output
-sourceCompatibility = 1.8
-targetCompatibility = 1.8
-
/** Returns the service name. */
String getGaeProject() {
def stream = new ByteArrayOutputStream()
diff --git a/gcp-observability/build.gradle b/gcp-observability/build.gradle
index 9cef17fcd84c..69bff88bf0fd 100644
--- a/gcp-observability/build.gradle
+++ b/gcp-observability/build.gradle
@@ -19,9 +19,13 @@ tasks.named("compileJava").configure {
"|")
}
-dependencies {
- def cloudLoggingVersion = '3.14.5'
+tasks.named("jar").configure {
+ manifest {
+ attributes('Automatic-Module-Name': 'io.grpc.gcp.observability')
+ }
+}
+dependencies {
annotationProcessor libraries.auto.value
api project(':grpc-api')
@@ -32,7 +36,7 @@ dependencies {
project(':grpc-census'),
libraries.opencensus.contrib.grpc.metrics
// Avoid gradle using project dependencies without configuration: shadow
- implementation ("com.google.cloud:google-cloud-logging:${cloudLoggingVersion}") {
+ implementation (libraries.google.cloud.logging) {
exclude group: 'io.grpc', module: 'grpc-alts'
exclude group: 'io.grpc', module: 'grpc-netty-shaded'
exclude group: 'io.grpc', module: 'grpc-xds'
@@ -57,13 +61,13 @@ dependencies {
project(':grpc-grpclb'), // Align grpc versions
project(':grpc-services'), // Align grpc versions
libraries.animalsniffer.annotations, // Use our newer version
+ libraries.auto.value.annotations, // Use our newer version
libraries.guava.jre, // Use our newer version
libraries.protobuf.java.util, // Use our newer version
libraries.re2j, // Use our newer version
- libraries.checker.qual, // Explicit dependency to keep in step with version used by guava
libraries.j2objc.annotations // Explicit dependency to keep in step with version used by guava
- testImplementation testFixtures(project(':grpc-context')),
+ testImplementation testFixtures(project(':grpc-api')),
project(':grpc-testing'),
project(':grpc-testing-proto')
testImplementation (libraries.guava.testlib) {
diff --git a/googleapis/build.gradle b/googleapis/build.gradle
index 72f5b9794060..435e552d47d1 100644
--- a/googleapis/build.gradle
+++ b/googleapis/build.gradle
@@ -7,6 +7,12 @@ plugins {
description = 'gRPC: googleapis'
+tasks.named("jar").configure {
+ manifest {
+ attributes('Automatic-Module-Name': 'io.grpc.googleapis')
+ }
+}
+
dependencies {
api project(':grpc-api')
implementation project(path: ':grpc-alts', configuration: 'shadow'),
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 6f3d93ffe8f6..9ed986c9aa2c 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -2,18 +2,28 @@
# Compatibility problem with internal version getting onto 1.5.3.
# https://github.com/grpc/grpc-java/pull/9118
googleauth = "1.4.0"
-guava = "32.0.1-android"
-netty = '4.1.87.Final'
+# Update notes / 2023-07-19 sergiitk:
+# Couldn't update to 32.1.1 because Guava 32.1.0 broke gradle metadata:
+# https://github.com/google/guava/releases/tag/v32.1.0
+# 32.1.1 partially fixed this, but our build still breaks with:
+# Could not resolve com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava.
+#
+# TODO(any release manager): attempt removing runtimeOnly dependencies when guava upgraded:
+# - okhttp: errorprone.annotations
+#
+# Allowed to be different from guava-jre.
+guava = '32.0.1-android'
+netty = '4.1.93.Final'
# Keep the following references of tcnative version in sync whenever it's updated:
# SECURITY.md
nettytcnative = '2.0.61.Final'
opencensus = "0.31.1"
-protobuf = "3.22.3"
+protobuf = "3.24.0"
[libraries]
android-annotations = "com.google.android:annotations:4.1.1.4"
androidx-annotation = "androidx.annotation:annotation:1.6.0"
-androidx-core = "androidx.core:core:1.10.0"
+androidx-core = "androidx.core:core:1.10.1"
androidx-lifecycle-common = "androidx.lifecycle:lifecycle-common:2.6.1"
androidx-lifecycle-service = "androidx.lifecycle:lifecycle-service:2.6.1"
androidx-test-core = "androidx.test:core:1.5.0"
@@ -21,24 +31,26 @@ androidx-test-ext-junit = "androidx.test.ext:junit:1.1.5"
androidx-test-rules = "androidx.test:rules:1.5.0"
animalsniffer = "org.codehaus.mojo:animal-sniffer:1.23"
animalsniffer-annotations = "org.codehaus.mojo:animal-sniffer-annotations:1.23"
-auto-value = "com.google.auto.value:auto-value:1.10.1"
-auto-value-annotations = "com.google.auto.value:auto-value-annotations:1.10.1"
-checker-qual = "org.checkerframework:checker-qual:3.33.0"
-checkstyle = "com.puppycrawl.tools:checkstyle:8.28"
+auto-value = "com.google.auto.value:auto-value:1.10.2"
+auto-value-annotations = "com.google.auto.value:auto-value-annotations:1.10.2"
+checkstyle = "com.puppycrawl.tools:checkstyle:10.12.1"
commons-math3 = "org.apache.commons:commons-math3:3.6.1"
conscrypt = "org.conscrypt:conscrypt-openjdk-uber:2.5.2"
cronet-api = "org.chromium.net:cronet-api:108.5359.79"
cronet-embedded = "org.chromium.net:cronet-embedded:108.5359.79"
-errorprone-annotations = "com.google.errorprone:error_prone_annotations:2.18.0"
-errorprone-corejava8 = "com.google.errorprone:error_prone_core:2.10.0"
-errorprone-core = "com.google.errorprone:error_prone_core:2.18.0"
-google-api-protos = "com.google.api.grpc:proto-google-common-protos:2.17.0"
+errorprone-annotations = "com.google.errorprone:error_prone_annotations:2.20.0"
+errorprone-core = "com.google.errorprone:error_prone_core:2.20.0"
+google-api-protos = "com.google.api.grpc:proto-google-common-protos:2.22.0"
google-auth-credentials = { module = "com.google.auth:google-auth-library-credentials", version.ref = "googleauth" }
google-auth-oauth2Http = { module = "com.google.auth:google-auth-library-oauth2-http", version.ref = "googleauth" }
+# Release notes: https://cloud.google.com/logging/docs/release-notes
+google-cloud-logging = "com.google.cloud:google-cloud-logging:3.15.5"
gson = "com.google.code.gson:gson:2.10.1"
guava = { module = "com.google.guava:guava", version.ref = "guava" }
guava-betaChecker = "com.google.guava:guava-beta-checker:1.0"
guava-testlib = { module = "com.google.guava:guava-testlib", version.ref = "guava" }
+# JRE version is needed for projects where its a transitive dependency, f.e. gcp-observability.
+# May be different from the -android version.
guava-jre = "com.google.guava:guava:32.0.1-jre"
hdrhistogram = "org.hdrhistogram:HdrHistogram:2.1.12"
javax-annotation = "org.apache.tomcat:annotations-api:6.0.53"
@@ -46,8 +58,11 @@ j2objc-annotations = " com.google.j2objc:j2objc-annotations:2.8"
jetty-alpn-agent = "org.mortbay.jetty.alpn:jetty-alpn-agent:2.0.10"
jsr305 = "com.google.code.findbugs:jsr305:3.0.2"
junit = "junit:junit:4.13.2"
-mockito-android = "org.mockito:mockito-android:3.12.4"
-mockito-core = "org.mockito:mockito-core:3.12.4"
+# Update notes / 2023-07-19 sergiitk:
+# Couldn't update to 5.4.0, updated to the last in 4.x line. Version 5.x breaks some tests.
+# Error log: https://github.com/grpc/grpc-java/pull/10359#issuecomment-1632834435
+mockito-android = "org.mockito:mockito-android:4.11.0"
+mockito-core = "org.mockito:mockito-core:4.11.0"
netty-codec-http2 = { module = "io.netty:netty-codec-http2", version.ref = "netty" }
netty-handler-proxy = { module = "io.netty:netty-handler-proxy", version.ref = "netty" }
netty-tcnative = { module = "io.netty:netty-tcnative-boringssl-static", version.ref = "nettytcnative" }
@@ -55,7 +70,7 @@ netty-tcnative-classes = { module = "io.netty:netty-tcnative-classes", version.r
netty-transport-epoll = { module = "io.netty:netty-transport-native-epoll", version.ref = "netty" }
netty-unix-common = { module = "io.netty:netty-transport-native-unix-common", version.ref = "netty" }
okhttp = "com.squareup.okhttp:okhttp:2.7.5"
-okio = "com.squareup.okio:okio:1.17.5"
+okio = "com.squareup.okio:okio:2.10.0"
opencensus-api = { module = "io.opencensus:opencensus-api", version.ref = "opencensus" }
opencensus-contrib-grpc-metrics = { module = "io.opencensus:opencensus-contrib-grpc-metrics", version.ref = "opencensus" }
opencensus-exporter-stats-stackdriver = { module = "io.opencensus:opencensus-exporter-stats-stackdriver", version.ref = "opencensus" }
@@ -68,10 +83,13 @@ protobuf-java-util = { module = "com.google.protobuf:protobuf-java-util", versio
protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobuf" }
protobuf-protoc = { module = "com.google.protobuf:protoc", version.ref = "protobuf" }
re2j = "com.google.re2j:re2j:1.7"
-# Compilation failed with 4.10.2 due to native graphics, or something. We don't
-# use it, but it seemed the compiler felt it needed the definition
-robolectric = "org.robolectric:robolectric:4.9.2"
+robolectric = "org.robolectric:robolectric:4.10.3"
signature-android = "net.sf.androidscents.signature:android-api-level-19:4.4.2_r4"
signature-java = "org.codehaus.mojo.signature:java18:1.0"
-# 1.1+ requires Java 8, but we still use Java 7 with grpc-context
-truth = "com.google.truth:truth:1.0.1"
+truth = "com.google.truth:truth:1.1.5"
+
+# Do not update: Pinned to the last version supporting Java 8.
+# See https://checkstyle.sourceforge.io/releasenotes.html#Release_10.1
+checkstylejava8 = "com.puppycrawl.tools:checkstyle:9.3"
+# See https://github.com/google/error-prone/releases/tag/v2.11.0
+errorprone-corejava8 = "com.google.errorprone:error_prone_core:2.10.0"
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 070cb702f09e..db9a6b825d7f 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/grpclb/BUILD.bazel b/grpclb/BUILD.bazel
index 0ca4d695bb40..e82d8022bd2a 100644
--- a/grpclb/BUILD.bazel
+++ b/grpclb/BUILD.bazel
@@ -14,7 +14,7 @@ java_library(
"//api",
"//context",
"//core:internal",
- "//core:util",
+ "//util",
"//stub",
"@com_google_code_findbugs_jsr305//jar",
"@com_google_guava_guava//jar",
diff --git a/grpclb/build.gradle b/grpclb/build.gradle
index 7aa2d62b0f41..cea599828f5a 100644
--- a/grpclb/build.gradle
+++ b/grpclb/build.gradle
@@ -9,6 +9,12 @@ plugins {
description = "gRPC: GRPCLB LoadBalancer plugin"
+tasks.named("jar").configure {
+ manifest {
+ attributes('Automatic-Module-Name': 'io.grpc.grpclb')
+ }
+}
+
dependencies {
implementation project(':grpc-core'),
project(':grpc-protobuf'),
@@ -19,6 +25,7 @@ dependencies {
runtimeOnly libraries.errorprone.annotations
compileOnly libraries.javax.annotation
testImplementation libraries.truth,
+ project(':grpc-inprocess'),
testFixtures(project(':grpc-core'))
signature libraries.signature.java
@@ -28,6 +35,7 @@ configureProtoCompilation()
tasks.named("javadoc").configure {
exclude 'io/grpc/grpclb/Internal*'
+ exclude 'io/grpc/grpclb/*Provider.java'
}
tasks.named("jacocoTestReport").configure {
diff --git a/grpclb/src/main/java/io/grpc/grpclb/SecretGrpclbNameResolverProvider.java b/grpclb/src/main/java/io/grpc/grpclb/SecretGrpclbNameResolverProvider.java
index da5b7c3353e9..3970c281e1be 100644
--- a/grpclb/src/main/java/io/grpc/grpclb/SecretGrpclbNameResolverProvider.java
+++ b/grpclb/src/main/java/io/grpc/grpclb/SecretGrpclbNameResolverProvider.java
@@ -53,6 +53,9 @@ public static final class Provider extends NameResolverProvider {
private static final String SCHEME = "dns";
+ private static final boolean IS_ANDROID = InternalServiceProviders
+ .isAndroid(SecretGrpclbNameResolverProvider.class.getClassLoader());
+
@Override
public GrpclbNameResolver newNameResolver(URI targetUri, Args args) {
if (SCHEME.equals(targetUri.getScheme())) {
@@ -68,7 +71,7 @@ public GrpclbNameResolver newNameResolver(URI targetUri, Args args) {
args,
GrpcUtil.SHARED_CHANNEL_EXECUTOR,
Stopwatch.createUnstarted(),
- InternalServiceProviders.isAndroid(getClass().getClassLoader()));
+ IS_ANDROID);
} else {
return null;
}
diff --git a/inprocess/BUILD.bazel b/inprocess/BUILD.bazel
new file mode 100644
index 000000000000..65f2adceda1d
--- /dev/null
+++ b/inprocess/BUILD.bazel
@@ -0,0 +1,16 @@
+java_library(
+ name = "inprocess",
+ srcs = glob([
+ "src/main/java/io/grpc/inprocess/*.java",
+ ]),
+ visibility = ["//visibility:public"],
+ deps = [
+ "//core:internal",
+ "//api",
+ "//context",
+ "@com_google_code_findbugs_jsr305//jar",
+ "@com_google_errorprone_error_prone_annotations//jar",
+ "@com_google_guava_guava//jar",
+ "@com_google_j2objc_j2objc_annotations//jar",
+ ],
+)
diff --git a/inprocess/build.gradle b/inprocess/build.gradle
new file mode 100644
index 000000000000..11a2be722cce
--- /dev/null
+++ b/inprocess/build.gradle
@@ -0,0 +1,30 @@
+plugins {
+ id "java-library"
+ id "maven-publish"
+
+ id "ru.vyarus.animalsniffer"
+}
+
+description = 'gRPC: Inprocess'
+
+tasks.named("jar").configure {
+ manifest {
+ attributes('Automatic-Module-Name': 'io.grpc.inprocess')
+ }
+}
+
+dependencies {
+ api project(':grpc-core')
+
+ implementation libraries.guava
+ testImplementation project(':grpc-testing'),
+ testFixtures(project(':grpc-core'))
+ testImplementation libraries.guava.testlib
+
+ signature libraries.signature.java
+ signature libraries.signature.android
+}
+
+tasks.named("javadoc").configure {
+ exclude 'io/grpc/inprocess/Internal*'
+}
diff --git a/core/src/main/java/io/grpc/inprocess/AnonymousInProcessSocketAddress.java b/inprocess/src/main/java/io/grpc/inprocess/AnonymousInProcessSocketAddress.java
similarity index 100%
rename from core/src/main/java/io/grpc/inprocess/AnonymousInProcessSocketAddress.java
rename to inprocess/src/main/java/io/grpc/inprocess/AnonymousInProcessSocketAddress.java
diff --git a/core/src/main/java/io/grpc/inprocess/InProcessChannelBuilder.java b/inprocess/src/main/java/io/grpc/inprocess/InProcessChannelBuilder.java
similarity index 100%
rename from core/src/main/java/io/grpc/inprocess/InProcessChannelBuilder.java
rename to inprocess/src/main/java/io/grpc/inprocess/InProcessChannelBuilder.java
diff --git a/core/src/main/java/io/grpc/inprocess/InProcessServer.java b/inprocess/src/main/java/io/grpc/inprocess/InProcessServer.java
similarity index 100%
rename from core/src/main/java/io/grpc/inprocess/InProcessServer.java
rename to inprocess/src/main/java/io/grpc/inprocess/InProcessServer.java
diff --git a/core/src/main/java/io/grpc/inprocess/InProcessServerBuilder.java b/inprocess/src/main/java/io/grpc/inprocess/InProcessServerBuilder.java
similarity index 100%
rename from core/src/main/java/io/grpc/inprocess/InProcessServerBuilder.java
rename to inprocess/src/main/java/io/grpc/inprocess/InProcessServerBuilder.java
diff --git a/core/src/main/java/io/grpc/inprocess/InProcessSocketAddress.java b/inprocess/src/main/java/io/grpc/inprocess/InProcessSocketAddress.java
similarity index 100%
rename from core/src/main/java/io/grpc/inprocess/InProcessSocketAddress.java
rename to inprocess/src/main/java/io/grpc/inprocess/InProcessSocketAddress.java
diff --git a/core/src/main/java/io/grpc/inprocess/InProcessTransport.java b/inprocess/src/main/java/io/grpc/inprocess/InProcessTransport.java
similarity index 99%
rename from core/src/main/java/io/grpc/inprocess/InProcessTransport.java
rename to inprocess/src/main/java/io/grpc/inprocess/InProcessTransport.java
index 1c2ac3df22b7..dd1028fde7dd 100644
--- a/core/src/main/java/io/grpc/inprocess/InProcessTransport.java
+++ b/inprocess/src/main/java/io/grpc/inprocess/InProcessTransport.java
@@ -117,7 +117,7 @@ public void uncaughtException(Thread t, Throwable e) {
}
throw new RuntimeException(e);
}
- };
+ };
@GuardedBy("this")
diff --git a/core/src/main/java/io/grpc/inprocess/InternalInProcess.java b/inprocess/src/main/java/io/grpc/inprocess/InternalInProcess.java
similarity index 100%
rename from core/src/main/java/io/grpc/inprocess/InternalInProcess.java
rename to inprocess/src/main/java/io/grpc/inprocess/InternalInProcess.java
diff --git a/core/src/main/java/io/grpc/inprocess/InternalInProcessChannelBuilder.java b/inprocess/src/main/java/io/grpc/inprocess/InternalInProcessChannelBuilder.java
similarity index 100%
rename from core/src/main/java/io/grpc/inprocess/InternalInProcessChannelBuilder.java
rename to inprocess/src/main/java/io/grpc/inprocess/InternalInProcessChannelBuilder.java
diff --git a/core/src/main/java/io/grpc/inprocess/InternalInProcessServerBuilder.java b/inprocess/src/main/java/io/grpc/inprocess/InternalInProcessServerBuilder.java
similarity index 100%
rename from core/src/main/java/io/grpc/inprocess/InternalInProcessServerBuilder.java
rename to inprocess/src/main/java/io/grpc/inprocess/InternalInProcessServerBuilder.java
diff --git a/core/src/main/java/io/grpc/inprocess/package-info.java b/inprocess/src/main/java/io/grpc/inprocess/package-info.java
similarity index 100%
rename from core/src/main/java/io/grpc/inprocess/package-info.java
rename to inprocess/src/main/java/io/grpc/inprocess/package-info.java
diff --git a/core/src/test/java/io/grpc/inprocess/AnonymousInProcessSocketAddressTest.java b/inprocess/src/test/java/io/grpc/inprocess/AnonymousInProcessSocketAddressTest.java
similarity index 100%
rename from core/src/test/java/io/grpc/inprocess/AnonymousInProcessSocketAddressTest.java
rename to inprocess/src/test/java/io/grpc/inprocess/AnonymousInProcessSocketAddressTest.java
diff --git a/core/src/test/java/io/grpc/inprocess/AnonymousInProcessTransportTest.java b/inprocess/src/test/java/io/grpc/inprocess/AnonymousInProcessTransportTest.java
similarity index 100%
rename from core/src/test/java/io/grpc/inprocess/AnonymousInProcessTransportTest.java
rename to inprocess/src/test/java/io/grpc/inprocess/AnonymousInProcessTransportTest.java
diff --git a/core/src/test/java/io/grpc/inprocess/InProcessChannelBuilderTest.java b/inprocess/src/test/java/io/grpc/inprocess/InProcessChannelBuilderTest.java
similarity index 100%
rename from core/src/test/java/io/grpc/inprocess/InProcessChannelBuilderTest.java
rename to inprocess/src/test/java/io/grpc/inprocess/InProcessChannelBuilderTest.java
diff --git a/core/src/test/java/io/grpc/inprocess/InProcessClientTransportFactoryTest.java b/inprocess/src/test/java/io/grpc/inprocess/InProcessClientTransportFactoryTest.java
similarity index 100%
rename from core/src/test/java/io/grpc/inprocess/InProcessClientTransportFactoryTest.java
rename to inprocess/src/test/java/io/grpc/inprocess/InProcessClientTransportFactoryTest.java
diff --git a/core/src/test/java/io/grpc/inprocess/InProcessServerBuilderTest.java b/inprocess/src/test/java/io/grpc/inprocess/InProcessServerBuilderTest.java
similarity index 100%
rename from core/src/test/java/io/grpc/inprocess/InProcessServerBuilderTest.java
rename to inprocess/src/test/java/io/grpc/inprocess/InProcessServerBuilderTest.java
diff --git a/core/src/test/java/io/grpc/inprocess/InProcessServerTest.java b/inprocess/src/test/java/io/grpc/inprocess/InProcessServerTest.java
similarity index 100%
rename from core/src/test/java/io/grpc/inprocess/InProcessServerTest.java
rename to inprocess/src/test/java/io/grpc/inprocess/InProcessServerTest.java
diff --git a/core/src/test/java/io/grpc/inprocess/InProcessSocketAddressTest.java b/inprocess/src/test/java/io/grpc/inprocess/InProcessSocketAddressTest.java
similarity index 100%
rename from core/src/test/java/io/grpc/inprocess/InProcessSocketAddressTest.java
rename to inprocess/src/test/java/io/grpc/inprocess/InProcessSocketAddressTest.java
diff --git a/core/src/test/java/io/grpc/inprocess/InProcessTransportTest.java b/inprocess/src/test/java/io/grpc/inprocess/InProcessTransportTest.java
similarity index 100%
rename from core/src/test/java/io/grpc/inprocess/InProcessTransportTest.java
rename to inprocess/src/test/java/io/grpc/inprocess/InProcessTransportTest.java
diff --git a/core/src/test/java/io/grpc/inprocess/StandaloneInProcessTransportTest.java b/inprocess/src/test/java/io/grpc/inprocess/StandaloneInProcessTransportTest.java
similarity index 100%
rename from core/src/test/java/io/grpc/inprocess/StandaloneInProcessTransportTest.java
rename to inprocess/src/test/java/io/grpc/inprocess/StandaloneInProcessTransportTest.java
diff --git a/interop-testing/build.gradle b/interop-testing/build.gradle
index 96d4ca361441..9967fdfc10d3 100644
--- a/interop-testing/build.gradle
+++ b/interop-testing/build.gradle
@@ -1,6 +1,6 @@
plugins {
id "application"
- id "java"
+ id "java-library"
id "maven-publish"
id "com.google.protobuf"
@@ -21,17 +21,19 @@ dependencies {
project(':grpc-googleapis'),
project(':grpc-netty'),
project(':grpc-okhttp'),
- project(':grpc-protobuf'),
project(':grpc-rls'),
project(':grpc-services'),
- project(':grpc-stub'),
project(':grpc-testing'),
project(path: ':grpc-xds', configuration: 'shadow'),
libraries.hdrhistogram,
- libraries.junit,
libraries.truth,
libraries.opencensus.contrib.grpc.metrics,
- libraries.google.auth.oauth2Http
+ libraries.google.auth.oauth2Http,
+ libraries.guava.jre // Fix checkUpperBoundDeps using -android
+ api project(':grpc-api'),
+ project(':grpc-stub'),
+ project(':grpc-protobuf'),
+ libraries.junit
compileOnly libraries.javax.annotation
// TODO(sergiitk): replace with com.google.cloud:google-cloud-logging
// Used instead of google-cloud-logging because it's failing
@@ -45,8 +47,7 @@ dependencies {
libraries.netty.tcnative.classes,
project(':grpc-grpclb'),
project(':grpc-rls')
- testImplementation testFixtures(project(':grpc-context')),
- testFixtures(project(':grpc-api')),
+ testImplementation testFixtures(project(':grpc-api')),
testFixtures(project(':grpc-core')),
libraries.mockito.core,
libraries.okhttp
@@ -159,22 +160,24 @@ def xds_federation_test_client = tasks.register("xds_federation_test_client", Cr
classpath = startScripts.classpath
}
-applicationDistribution.into("bin") {
- from(test_client)
- from(test_server)
- from(reconnect_test_client)
- from(stresstest_client)
- from(http2_client)
- from(grpclb_long_lived_affinity_test_client)
- from(grpclb_fallback_test_client)
- from(xds_test_client)
- from(xds_test_server)
- from(xds_federation_test_client)
- fileMode = 0755
-}
+application {
+ applicationDistribution.into("bin") {
+ from(test_client)
+ from(test_server)
+ from(reconnect_test_client)
+ from(stresstest_client)
+ from(http2_client)
+ from(grpclb_long_lived_affinity_test_client)
+ from(grpclb_fallback_test_client)
+ from(xds_test_client)
+ from(xds_test_server)
+ from(xds_federation_test_client)
+ fileMode = 0755
+ }
-applicationDistribution.into("lib") {
- from(configurations.alpnagent)
+ applicationDistribution.into("lib") {
+ from(configurations.alpnagent)
+ }
}
publishing {
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 aeb866b35283..e12e4ffa5533 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
@@ -1249,7 +1249,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 CallOptions deadline was exceeded");
+ .startsWith("ClientCall started after CallOptions deadline was exceeded");
}
// CensusStreamTracerModule record final status in the interceptor, thus is guaranteed to be
@@ -1282,7 +1282,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 CallOptions deadline was exceeded");
+ .startsWith("ClientCall started after CallOptions deadline was exceeded");
}
if (metricsExpected()) {
MetricsRecord clientStartRecord = clientStatsRecorder.pollRecord(5, TimeUnit.SECONDS);
@@ -2007,18 +2007,21 @@ public Status getStatus() {
}
private SoakIterationResult performOneSoakIteration(
- TestServiceGrpc.TestServiceBlockingStub soakStub) throws Exception {
+ TestServiceGrpc.TestServiceBlockingStub soakStub, int soakRequestSize, int soakResponseSize)
+ throws Exception {
long startNs = System.nanoTime();
Status status = Status.OK;
try {
final SimpleRequest request =
SimpleRequest.newBuilder()
- .setResponseSize(314159)
- .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[271828])))
+ .setResponseSize(soakResponseSize)
+ .setPayload(
+ Payload.newBuilder().setBody(ByteString.copyFrom(new byte[soakRequestSize])))
.build();
final SimpleResponse goldenResponse =
SimpleResponse.newBuilder()
- .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[314159])))
+ .setPayload(
+ Payload.newBuilder().setBody(ByteString.copyFrom(new byte[soakResponseSize])))
.build();
assertResponse(goldenResponse, soakStub.unaryCall(request));
} catch (StatusRuntimeException e) {
@@ -2039,7 +2042,9 @@ public void performSoakTest(
int maxFailures,
int maxAcceptablePerIterationLatencyMs,
int minTimeMsBetweenRpcs,
- int overallTimeoutSeconds)
+ int overallTimeoutSeconds,
+ int soakRequestSize,
+ int soakResponseSize)
throws Exception {
int iterationsDone = 0;
int totalFailures = 0;
@@ -2063,7 +2068,8 @@ public void performSoakTest(
.newBlockingStub(soakChannel)
.withInterceptors(recordClientCallInterceptor(clientCallCapture));
}
- SoakIterationResult result = performOneSoakIteration(soakStub);
+ SoakIterationResult result =
+ performOneSoakIteration(soakStub, soakRequestSize, soakResponseSize);
SocketAddress peer = clientCallCapture
.get().getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR);
StringBuilder logStr = new StringBuilder(
@@ -2124,7 +2130,7 @@ public void performSoakTest(
assertTrue(tooManyFailuresErrorMessage, totalFailures <= maxFailures);
}
- protected static void assertSuccess(StreamRecorder> recorder) {
+ private static void assertSuccess(StreamRecorder> recorder) {
if (recorder.getError() != null) {
throw new AssertionError(recorder.getError());
}
diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/StressTestClient.java b/interop-testing/src/main/java/io/grpc/testing/integration/StressTestClient.java
index 91e7e9c6ae3c..d2a5f6474f38 100644
--- a/interop-testing/src/main/java/io/grpc/testing/integration/StressTestClient.java
+++ b/interop-testing/src/main/java/io/grpc/testing/integration/StressTestClient.java
@@ -32,24 +32,22 @@
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
+import io.grpc.ChannelCredentials;
+import io.grpc.Grpc;
+import io.grpc.InsecureChannelCredentials;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.Status;
import io.grpc.StatusException;
-import io.grpc.netty.GrpcSslContexts;
-import io.grpc.netty.NegotiationType;
-import io.grpc.netty.NettyChannelBuilder;
+import io.grpc.TlsChannelCredentials;
import io.grpc.stub.StreamObserver;
import io.grpc.testing.TlsTesting;
-import io.netty.handler.ssl.SslContext;
import java.io.IOException;
-import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
-import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
@@ -296,19 +294,9 @@ private List parseServerAddresses(String addressesStr) {
List addresses = new ArrayList<>();
for (List namePort : parseCommaSeparatedTuples(addressesStr)) {
- InetAddress address;
String name = namePort.get(0);
int port = Integer.valueOf(namePort.get(1));
- try {
- address = InetAddress.getByName(name);
- if (serverHostOverride != null) {
- // Force the hostname to match the cert the server uses.
- address = InetAddress.getByAddress(serverHostOverride, address.getAddress());
- }
- } catch (UnknownHostException ex) {
- throw new RuntimeException(ex);
- }
- addresses.add(new InetSocketAddress(address, port));
+ addresses.add(new InetSocketAddress(name, port));
}
return addresses;
@@ -341,19 +329,28 @@ private static List> parseCommaSeparatedTuples(String str) {
}
private ManagedChannel createChannel(InetSocketAddress address) {
- SslContext sslContext = null;
- if (useTestCa) {
- try {
- sslContext = GrpcSslContexts.forClient().trustManager(
- TlsTesting.loadCert("ca.pem")).build();
- } catch (Exception ex) {
- throw new RuntimeException(ex);
+ ChannelCredentials channelCredentials;
+ if (useTls) {
+ if (useTestCa) {
+ try {
+ channelCredentials = TlsChannelCredentials.newBuilder()
+ .trustManager(TlsTesting.loadCert("ca.pem"))
+ .build();
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ } else {
+ channelCredentials = TlsChannelCredentials.create();
}
+ } else {
+ channelCredentials = InsecureChannelCredentials.create();
+ }
+ ManagedChannelBuilder> builder = Grpc.newChannelBuilderForAddress(
+ address.getHostString(), address.getPort(), channelCredentials);
+ if (serverHostOverride != null) {
+ builder.overrideAuthority(serverHostOverride);
}
- return NettyChannelBuilder.forAddress(address)
- .negotiationType(useTls ? NegotiationType.TLS : NegotiationType.PLAINTEXT)
- .sslContext(sslContext)
- .build();
+ return builder.build();
}
private static String serverAddressesToString(List addresses) {
diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceClient.java b/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceClient.java
index a6e2c4f3bf84..768a32be62c5 100644
--- a/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceClient.java
+++ b/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceClient.java
@@ -97,6 +97,8 @@ public static void main(String[] args) throws Exception {
private int soakMinTimeMsBetweenRpcs = 0;
private int soakOverallTimeoutSeconds =
soakIterations * soakPerIterationMaxAcceptableLatencyMs / 1000;
+ private int soakRequestSize = 271828;
+ private int soakResponseSize = 314159;
private String additionalMetadata = "";
private static LoadBalancerProvider customBackendMetricsLoadBalancerProvider;
@@ -175,6 +177,10 @@ void parseArgs(String[] args) throws Exception {
soakMinTimeMsBetweenRpcs = Integer.parseInt(value);
} else if ("soak_overall_timeout_seconds".equals(key)) {
soakOverallTimeoutSeconds = Integer.parseInt(value);
+ } else if ("soak_request_size".equals(key)) {
+ soakRequestSize = Integer.parseInt(value);
+ } else if ("soak_response_size".equals(key)) {
+ soakResponseSize = Integer.parseInt(value);
} else if ("additional_metadata".equals(key)) {
additionalMetadata = value;
} else {
@@ -247,6 +253,12 @@ void parseArgs(String[] args) throws Exception {
+ "\n should stop and fail, if the desired number of "
+ "\n iterations have not yet completed. Default "
+ c.soakOverallTimeoutSeconds
+ + "\n --soak_request_size "
+ + "\n The request size in a soak RPC. Default "
+ + c.soakRequestSize
+ + "\n --soak_response_size "
+ + "\n The response size in a soak RPC. Default "
+ + c.soakResponseSize
+ "\n --additional_metadata "
+ "\n Additional metadata to send in each request, as a "
+ "\n semicolon-separated list of key:value pairs. Default "
@@ -481,7 +493,9 @@ private void runTest(TestCases testCase) throws Exception {
soakMaxFailures,
soakPerIterationMaxAcceptableLatencyMs,
soakMinTimeMsBetweenRpcs,
- soakOverallTimeoutSeconds);
+ soakOverallTimeoutSeconds,
+ soakRequestSize,
+ soakResponseSize);
break;
}
@@ -493,7 +507,9 @@ private void runTest(TestCases testCase) throws Exception {
soakMaxFailures,
soakPerIterationMaxAcceptableLatencyMs,
soakMinTimeMsBetweenRpcs,
- soakOverallTimeoutSeconds);
+ soakOverallTimeoutSeconds,
+ soakRequestSize,
+ soakResponseSize);
break;
}
diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/XdsFederationTestClient.java b/interop-testing/src/main/java/io/grpc/testing/integration/XdsFederationTestClient.java
index 8f166b6affa7..9b01df0d18d6 100644
--- a/interop-testing/src/main/java/io/grpc/testing/integration/XdsFederationTestClient.java
+++ b/interop-testing/src/main/java/io/grpc/testing/integration/XdsFederationTestClient.java
@@ -74,6 +74,8 @@ public void run() {
private int soakPerIterationMaxAcceptableLatencyMs = 1000;
private int soakOverallTimeoutSeconds = 10;
private int soakMinTimeMsBetweenRpcs = 0;
+ private int soakRequestSize = 271828;
+ private int soakResponseSize = 314159;
private String testCase = "rpc_soak";
private final ArrayList clients = new ArrayList<>();
@@ -122,6 +124,12 @@ private void parseArgs(String[] args) {
case "soak_min_time_ms_between_rpcs":
soakMinTimeMsBetweenRpcs = Integer.parseInt(value);
break;
+ case "soak_request_size":
+ soakRequestSize = Integer.parseInt(value);
+ break;
+ case "soak_response_size":
+ soakResponseSize = Integer.parseInt(value);
+ break;
default:
System.err.println("Unknown argument: " + key);
usage = true;
@@ -175,6 +183,14 @@ private void parseArgs(String[] args) {
+ "\n channel_soak: sends --soak_iterations RPCs, rebuilding the channel "
+ "each time."
+ "\n Default: " + c.testCase
+ + "\n --soak_request_size "
+ + "\n The request size in a soak RPC. Default "
+ + c.soakRequestSize
+ + "\n"
+ + " --soak_response_size \n"
+ + " The response size in a soak RPC. Default"
+ + " "
+ + c.soakResponseSize
);
System.exit(1);
}
@@ -249,7 +265,9 @@ public void run() {
soakMaxFailures,
soakPerIterationMaxAcceptableLatencyMs,
soakMinTimeMsBetweenRpcs,
- soakOverallTimeoutSeconds);
+ soakOverallTimeoutSeconds,
+ soakRequestSize,
+ soakResponseSize);
logger.info("Test case: " + testCase + " done for server: " + serverUri);
runSucceeded = true;
} catch (Exception e) {
diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/StressTestClientTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/StressTestClientTest.java
index f32d00a42d76..66a3f2da8826 100644
--- a/interop-testing/src/test/java/io/grpc/testing/integration/StressTestClientTest.java
+++ b/interop-testing/src/test/java/io/grpc/testing/integration/StressTestClientTest.java
@@ -32,8 +32,6 @@
import java.util.Arrays;
import java.util.List;
import java.util.Set;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.LockSupport;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;
@@ -103,27 +101,19 @@ public void allCommandlineSwitchesAreSupported() {
assertEquals(9090, client.metricsPort());
}
- @Test
- public void serverHostOverrideShouldBeApplied() {
- StressTestClient client = new StressTestClient();
- client.parseArgs(new String[] {
- "--server_addresses=localhost:8080",
- "--server_host_override=foo.test.google.fr",
- });
-
- assertEquals("foo.test.google.fr", client.addresses().get(0).getHostName());
- }
-
@Test
public void gaugesShouldBeExported() throws Exception {
TestServiceServer server = new TestServiceServer();
- server.parseArgs(new String[]{"--port=" + 0, "--use_tls=false"});
+ server.parseArgs(new String[]{"--port=" + 0, "--use_tls=true"});
server.start();
StressTestClient client = new StressTestClient();
client.parseArgs(new String[] {"--test_cases=empty_unary:1",
"--server_addresses=localhost:" + server.getPort(), "--metrics_port=" + 0,
+ "--server_host_override=foo.test.google.fr",
+ "--use_tls=true",
+ "--use_test_ca=true",
"--num_stubs_per_channel=2"});
client.startMetricsService();
client.runStressTest();
@@ -142,7 +132,7 @@ public void gaugesShouldBeExported() throws Exception {
List allGauges =
ImmutableList.copyOf(stub.getAllGauges(EmptyMessage.getDefaultInstance()));
while (allGauges.size() < gaugeNames.size()) {
- LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100));
+ Thread.sleep(100);
allGauges = ImmutableList.copyOf(stub.getAllGauges(EmptyMessage.getDefaultInstance()));
}
diff --git a/istio-interop-testing/build.gradle b/istio-interop-testing/build.gradle
index b4495002af5f..204996ca9d6c 100644
--- a/istio-interop-testing/build.gradle
+++ b/istio-interop-testing/build.gradle
@@ -26,8 +26,7 @@ dependencies {
runtimeOnly libraries.netty.tcnative,
libraries.netty.tcnative.classes
- testImplementation testFixtures(project(':grpc-context')),
- testFixtures(project(':grpc-api')),
+ testImplementation testFixtures(project(':grpc-api')),
testFixtures(project(':grpc-core')),
libraries.mockito.core,
libraries.junit,
diff --git a/java_grpc_library.bzl b/java_grpc_library.bzl
index 4c7a7f2791b5..88ad8b889a3a 100644
--- a/java_grpc_library.bzl
+++ b/java_grpc_library.bzl
@@ -3,6 +3,7 @@
_JavaRpcToolchainInfo = provider(
fields = [
"java_toolchain",
+ "java_plugins",
"plugin",
"plugin_arg",
"protoc",
@@ -14,6 +15,7 @@ def _java_rpc_toolchain_impl(ctx):
return [
_JavaRpcToolchainInfo(
java_toolchain = ctx.attr._java_toolchain,
+ java_plugins = ctx.attr.java_plugins,
plugin = ctx.executable.plugin,
plugin_arg = ctx.attr.plugin_arg,
protoc = ctx.executable._protoc,
@@ -39,6 +41,10 @@ java_rpc_toolchain = rule(
default = Label("@com_google_protobuf//:protoc"),
executable = True,
),
+ "java_plugins": attr.label_list(
+ default = [],
+ providers = [JavaPluginInfo],
+ ),
"_java_toolchain": attr.label(
default = Label("@bazel_tools//tools/jdk:current_java_toolchain"),
),
@@ -104,6 +110,7 @@ def _java_rpc_library_impl(ctx):
source_jars = [srcjar],
output = ctx.outputs.jar,
output_source_jar = ctx.outputs.srcjar,
+ plugins = [plugin[JavaPluginInfo] for plugin in toolchain.java_plugins],
deps = [
java_common.make_non_strict(deps_java_info),
] + [dep[JavaInfo] for dep in toolchain.runtime],
diff --git a/netty/build.gradle b/netty/build.gradle
index 0f22d2774657..3150e8069b5d 100644
--- a/netty/build.gradle
+++ b/netty/build.gradle
@@ -13,6 +13,12 @@ configurations {
alpnagent
}
+tasks.named("jar").configure {
+ manifest {
+ attributes('Automatic-Module-Name': 'io.grpc.netty')
+ }
+}
+
dependencies {
api project(':grpc-core'),
libraries.netty.codec.http2
@@ -75,7 +81,10 @@ import net.ltgt.gradle.errorprone.CheckSeverity
tasks.named("javadoc").configure {
options.links 'http://netty.io/4.1/api/'
+ exclude 'io/grpc/netty/*Provider.java'
+ exclude 'io/grpc/netty/GrpcHttp2ConnectionHandler.java'
exclude 'io/grpc/netty/Internal*'
+ exclude 'io/grpc/netty/ProtocolNegotiationEvent.java'
}
tasks.named("test").configure {
diff --git a/netty/shaded/build.gradle b/netty/shaded/build.gradle
index 10410c959d65..3e52c3e0d953 100644
--- a/netty/shaded/build.gradle
+++ b/netty/shaded/build.gradle
@@ -1,9 +1,9 @@
import com.github.jengelman.gradle.plugins.shadow.transformers.Transformer
import com.github.jengelman.gradle.plugins.shadow.transformers.CacheableTransformer
import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext
+import org.apache.tools.zip.ZipOutputStream
+import org.apache.tools.zip.ZipEntry
import org.gradle.api.file.FileTreeElement
-import shadow.org.apache.tools.zip.ZipOutputStream
-import shadow.org.apache.tools.zip.ZipEntry
plugins {
id "java"
@@ -70,6 +70,9 @@ dependencies {
tasks.named("jar").configure {
// Must use a different archiveClassifier to avoid conflicting with shadowJar
archiveClassifier = 'original'
+ manifest {
+ attributes('Automatic-Module-Name': 'io.grpc.netty.shaded')
+ }
}
tasks.named("shadowJar").configure {
diff --git a/netty/src/jmh/java/io/grpc/netty/MethodDescriptorBenchmark.java b/netty/src/jmh/java/io/grpc/netty/MethodDescriptorBenchmark.java
index 921299671811..96844016c5c0 100644
--- a/netty/src/jmh/java/io/grpc/netty/MethodDescriptorBenchmark.java
+++ b/netty/src/jmh/java/io/grpc/netty/MethodDescriptorBenchmark.java
@@ -38,16 +38,16 @@ public class MethodDescriptorBenchmark {
private static final MethodDescriptor.Marshaller marshaller =
new MethodDescriptor.Marshaller() {
- @Override
- public InputStream stream(Void value) {
- return new ByteArrayInputStream(new byte[]{});
- }
+ @Override
+ public InputStream stream(Void value) {
+ return new ByteArrayInputStream(new byte[]{});
+ }
- @Override
- public Void parse(InputStream stream) {
- return null;
- }
- };
+ @Override
+ public Void parse(InputStream stream) {
+ return null;
+ }
+ };
MethodDescriptor method = MethodDescriptor.newBuilder()
.setType(MethodDescriptor.MethodType.UNARY)
diff --git a/netty/src/main/java/io/grpc/netty/NettyServer.java b/netty/src/main/java/io/grpc/netty/NettyServer.java
index 4c16ac50a262..fe7913870faf 100644
--- a/netty/src/main/java/io/grpc/netty/NettyServer.java
+++ b/netty/src/main/java/io/grpc/netty/NettyServer.java
@@ -272,9 +272,7 @@ public void initChannel(Channel ch) {
transportListener = listener.transportCreated(transport);
}
- /**
- * Releases the event loop if the channel is "done", possibly due to the channel closing.
- */
+ /* Releases the event loop if the channel is "done", possibly due to the channel closing. */
final class LoopReleaser implements ChannelFutureListener {
private boolean done;
diff --git a/netty/src/main/java/io/grpc/netty/NettyServerHandler.java b/netty/src/main/java/io/grpc/netty/NettyServerHandler.java
index fd053125d5c7..bbf2f17748b1 100644
--- a/netty/src/main/java/io/grpc/netty/NettyServerHandler.java
+++ b/netty/src/main/java/io/grpc/netty/NettyServerHandler.java
@@ -512,6 +512,9 @@ private void onDataRead(int streamId, ByteBuf data, int padding, boolean endOfSt
flowControlPing().onDataRead(data.readableBytes(), padding);
try {
NettyServerStream.TransportState stream = serverStream(requireHttp2Stream(streamId));
+ if (stream == null) {
+ return;
+ }
try (TaskCloseable ignore = PerfMark.traceTask("NettyServerHandler.onDataRead")) {
PerfMark.attachTag(stream.tag());
stream.inboundDataReceived(data, endOfStream);
@@ -679,12 +682,14 @@ void returnProcessedBytes(Http2Stream http2Stream, int bytes) {
private void closeStreamWhenDone(ChannelPromise promise, int streamId) throws Http2Exception {
final NettyServerStream.TransportState stream = serverStream(requireHttp2Stream(streamId));
- promise.addListener(new ChannelFutureListener() {
- @Override
- public void operationComplete(ChannelFuture future) {
- stream.complete();
- }
- });
+ if (stream != null) {
+ promise.addListener(new ChannelFutureListener() {
+ @Override
+ public void operationComplete(ChannelFuture future) {
+ stream.complete();
+ }
+ });
+ }
}
/**
diff --git a/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java b/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java
index 6aa29dea6baf..9cb2c043e541 100644
--- a/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java
+++ b/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java
@@ -185,8 +185,8 @@ public Void answer(InvocationOnMock invocation) throws Throwable {
return null;
}
})
- .when(streamListener)
- .messagesAvailable(ArgumentMatchers.any());
+ .when(streamListener)
+ .messagesAvailable(ArgumentMatchers.any());
lifecycleManager = new ClientTransportLifecycleManager(listener);
// This mocks the keepalive manager only for there's in which we verify it. For other tests
diff --git a/netty/src/test/java/io/grpc/netty/NettyClientStreamTest.java b/netty/src/test/java/io/grpc/netty/NettyClientStreamTest.java
index 61525d8369aa..96551d173a47 100644
--- a/netty/src/test/java/io/grpc/netty/NettyClientStreamTest.java
+++ b/netty/src/test/java/io/grpc/netty/NettyClientStreamTest.java
@@ -124,8 +124,8 @@ public Void answer(InvocationOnMock invocation) throws Throwable {
return null;
}
})
- .when(listener)
- .messagesAvailable(ArgumentMatchers.any());
+ .when(listener)
+ .messagesAvailable(ArgumentMatchers.any());
}
@Override
diff --git a/netty/src/test/java/io/grpc/netty/NettyServerHandlerTest.java b/netty/src/test/java/io/grpc/netty/NettyServerHandlerTest.java
index 2f92de5a891e..368b0600f9ec 100644
--- a/netty/src/test/java/io/grpc/netty/NettyServerHandlerTest.java
+++ b/netty/src/test/java/io/grpc/netty/NettyServerHandlerTest.java
@@ -179,8 +179,8 @@ public Void answer(InvocationOnMock invocation) throws Throwable {
return null;
}
})
- .when(streamListener)
- .messagesAvailable(any(StreamListener.MessageProducer.class));
+ .when(streamListener)
+ .messagesAvailable(any(StreamListener.MessageProducer.class));
}
@Override
@@ -634,6 +634,34 @@ public void headersWithMultipleHostsShouldFail() throws Exception {
any(ChannelPromise.class));
}
+ @Test
+ public void headersWithErrAndEndStreamReturnErrorButNotThrowNpe() throws Exception {
+ manualSetUp();
+ Http2Headers headers = new DefaultHttp2Headers()
+ .method(HTTP_METHOD)
+ .add(AsciiString.of("host"), AsciiString.of("example.com"))
+ .path(new AsciiString("/foo/bar"));
+ ByteBuf headersFrame = headersFrame(STREAM_ID, headers);
+ channelRead(headersFrame);
+ channelRead(emptyGrpcFrame(STREAM_ID, true));
+
+ Http2Headers responseHeaders = new DefaultHttp2Headers()
+ .set(InternalStatus.CODE_KEY.name(), String.valueOf(Code.INTERNAL.value()))
+ .set(InternalStatus.MESSAGE_KEY.name(), "Content-Type is missing from the request")
+ .status("" + 415)
+ .set(CONTENT_TYPE_HEADER, "text/plain; charset=utf-8");
+
+ verifyWrite()
+ .writeHeaders(
+ eq(ctx()),
+ eq(STREAM_ID),
+ eq(responseHeaders),
+ eq(0),
+ eq(false),
+ any(ChannelPromise.class));
+
+ }
+
@Test
public void headersWithAuthorityAndHostUsesAuthority() throws Exception {
manualSetUp();
diff --git a/netty/src/test/java/io/grpc/netty/NettyServerStreamTest.java b/netty/src/test/java/io/grpc/netty/NettyServerStreamTest.java
index fe9cd8e03a69..35f525e68eac 100644
--- a/netty/src/test/java/io/grpc/netty/NettyServerStreamTest.java
+++ b/netty/src/test/java/io/grpc/netty/NettyServerStreamTest.java
@@ -93,8 +93,8 @@ public Void answer(InvocationOnMock invocation) throws Throwable {
return null;
}
})
- .when(serverListener)
- .messagesAvailable(ArgumentMatchers.any());
+ .when(serverListener)
+ .messagesAvailable(ArgumentMatchers.any());
}
@Test
diff --git a/okhttp/BUILD.bazel b/okhttp/BUILD.bazel
index e550634aca05..30a77b11465f 100644
--- a/okhttp/BUILD.bazel
+++ b/okhttp/BUILD.bazel
@@ -11,7 +11,7 @@ java_library(
deps = [
"//api",
"//core:internal",
- "//core:util",
+ "//util",
"@com_google_code_findbugs_jsr305//jar",
"@com_google_errorprone_error_prone_annotations//jar",
"@com_google_guava_guava//jar",
diff --git a/okhttp/build.gradle b/okhttp/build.gradle
index 37d033350c90..99f799ec97f5 100644
--- a/okhttp/build.gradle
+++ b/okhttp/build.gradle
@@ -8,13 +8,25 @@ plugins {
description = "gRPC: OkHttp"
+tasks.named("jar").configure {
+ manifest {
+ attributes('Automatic-Module-Name': 'io.grpc.okhttp')
+ }
+}
+
dependencies {
- api project(':grpc-core')
+ api project(':grpc-util')
implementation libraries.okio,
libraries.guava,
libraries.perfmark.api
// Make okhttp dependencies compile only
compileOnly libraries.okhttp
+
+ // Needed because com.google.guava:guava:32.0.1-android requires
+ // com.google.errorprone:error_prone_annotations version 2.18.0, while we are running newer.
+ // TODO(any release manager): remove when guava is updated
+ runtimeOnly libraries.errorprone.annotations
+
// Tests depend on base class defined by core module.
testImplementation testFixtures(project(':grpc-core')),
testFixtures(project(':grpc-api')),
@@ -38,6 +50,7 @@ tasks.named("checkstyleMain").configure {
tasks.named("javadoc").configure {
options.links 'http://square.github.io/okhttp/2.x/okhttp/'
exclude 'io/grpc/okhttp/Internal*'
+ exclude 'io/grpc/okhttp/*Provider.java'
exclude 'io/grpc/okhttp/internal/**'
}
diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientStream.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientStream.java
index 11cd177a8407..50de8c7002fa 100644
--- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientStream.java
+++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientStream.java
@@ -321,11 +321,12 @@ public void transportHeadersReceived(List headers, boolean endOfStream)
* Must be called with holding the transport lock.
*/
@GuardedBy("lock")
- public void transportDataReceived(okio.Buffer frame, boolean endOfStream) {
+ public void transportDataReceived(okio.Buffer frame, boolean endOfStream, int paddingLen) {
// We only support 16 KiB frames, and the max permitted in HTTP/2 is 16 MiB. This is verified
// in OkHttp's Http2 deframer. In addition, this code is after the data has been read.
int length = (int) frame.size();
- window -= length;
+ window -= length + paddingLen;
+ processedWindow -= paddingLen;
if (window < 0) {
frameWriter.rstStream(id(), ErrorCode.FLOW_CONTROL_ERROR);
transport.finishStream(
diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java
index 6eaaf832a6b0..ea3bf77e990d 100644
--- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java
+++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java
@@ -1140,7 +1140,8 @@ public void run() {
*/
@SuppressWarnings("GuardedBy")
@Override
- public void data(boolean inFinished, int streamId, BufferedSource in, int length)
+ public void data(boolean inFinished, int streamId, BufferedSource in, int length,
+ int paddedLength)
throws IOException {
logger.logData(OkHttpFrameLogger.Direction.INBOUND,
streamId, in.getBuffer(), length, inFinished);
@@ -1166,12 +1167,12 @@ public void data(boolean inFinished, int streamId, BufferedSource in, int length
synchronized (lock) {
// TODO(b/145386688): This access should be guarded by 'stream.transportState().lock';
// instead found: 'OkHttpClientTransport.this.lock'
- stream.transportState().transportDataReceived(buf, inFinished);
+ stream.transportState().transportDataReceived(buf, inFinished, paddedLength - length);
}
}
// connection window update
- connectionUnacknowledgedBytesRead += length;
+ connectionUnacknowledgedBytesRead += paddedLength;
if (connectionUnacknowledgedBytesRead >= initialWindowSize * DEFAULT_WINDOW_UPDATE_RATIO) {
synchronized (lock) {
frameWriter.windowUpdate(0, connectionUnacknowledgedBytesRead);
diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java
index 45d6b9efc54c..8269a8ddf0fc 100644
--- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java
+++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java
@@ -18,7 +18,6 @@
import static com.google.common.base.Preconditions.checkArgument;
-import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.DoNotCall;
@@ -89,7 +88,7 @@ public final class OkHttpServerBuilder extends ForwardingServerBuilder headerBlock) {
* Handle an HTTP2 DATA frame.
*/
@Override
- public void data(boolean inFinished, int streamId, BufferedSource in, int length)
+ public void data(boolean inFinished, int streamId, BufferedSource in, int length,
+ int paddedLength)
throws IOException {
frameLogger.logData(
OkHttpFrameLogger.Direction.INBOUND, streamId, in.getBuffer(), length, inFinished);
@@ -853,7 +854,7 @@ public void data(boolean inFinished, int streamId, BufferedSource in, int length
"Received DATA for half-closed (remote) stream. RFC7540 section 5.1");
return;
}
- if (stream.inboundWindowAvailable() < length) {
+ if (stream.inboundWindowAvailable() < paddedLength) {
in.skip(length);
streamError(streamId, ErrorCode.FLOW_CONTROL_ERROR,
"Received DATA size exceeded window size. RFC7540 section 6.9");
@@ -861,11 +862,11 @@ public void data(boolean inFinished, int streamId, BufferedSource in, int length
}
Buffer buf = new Buffer();
buf.write(in.getBuffer(), length);
- stream.inboundDataReceived(buf, length, inFinished);
+ stream.inboundDataReceived(buf, length, paddedLength - length, inFinished);
}
// connection window update
- connectionUnacknowledgedBytesRead += length;
+ connectionUnacknowledgedBytesRead += paddedLength;
if (connectionUnacknowledgedBytesRead
>= config.flowControlWindow * Utils.DEFAULT_WINDOW_UPDATE_RATIO) {
synchronized (lock) {
@@ -1064,7 +1065,7 @@ private void respondWithHttpError(
}
streams.put(streamId, stream);
if (inFinished) {
- stream.inboundDataReceived(new Buffer(), 0, true);
+ stream.inboundDataReceived(new Buffer(), 0, 0, true);
}
frameWriter.headers(streamId, headers);
outboundFlow.data(
@@ -1122,7 +1123,7 @@ public void onPingTimeout() {
interface StreamState {
/** Must be holding 'lock' when calling. */
- void inboundDataReceived(Buffer frame, int windowConsumed, boolean endOfStream);
+ void inboundDataReceived(Buffer frame, int dataLength, int paddingLength, boolean endOfStream);
/** Must be holding 'lock' when calling. */
boolean hasReceivedEndOfStream();
@@ -1159,12 +1160,12 @@ static class Http2ErrorStreamState implements StreamState, OutboundFlowControlle
@Override public void onSentBytes(int frameBytes) {}
@Override public void inboundDataReceived(
- Buffer frame, int windowConsumed, boolean endOfStream) {
+ Buffer frame, int dataLength, int paddingLength, boolean endOfStream) {
synchronized (lock) {
if (endOfStream) {
receivedEndOfStream = true;
}
- window -= windowConsumed;
+ window -= dataLength + paddingLength;
try {
frame.skip(frame.size()); // Recycle segments
} catch (IOException ex) {
diff --git a/okhttp/src/main/resources/META-INF/services/io.grpc.ServerProvider b/okhttp/src/main/resources/META-INF/services/io.grpc.ServerProvider
new file mode 100644
index 000000000000..263ea4b68f66
--- /dev/null
+++ b/okhttp/src/main/resources/META-INF/services/io.grpc.ServerProvider
@@ -0,0 +1 @@
+io.grpc.okhttp.OkHttpServerProvider
diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java
index 644ba27b50ff..7347399bfe5d 100644
--- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java
+++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java
@@ -291,14 +291,16 @@ public void close() throws SecurityException {
final String message = "Hello Client";
Buffer buffer = createMessageFrame(message);
- frameHandler().data(false, 3, buffer, (int) buffer.size());
+ frameHandler().data(false, 3, buffer, (int) buffer.size(),
+ (int) buffer.size());
assertThat(logs).hasSize(1);
log = logs.remove(0);
assertThat(log.getMessage()).startsWith(Direction.INBOUND + " DATA: streamId=" + 3);
assertThat(log.getLevel()).isEqualTo(Level.FINE);
// At most 64 bytes of data frame will be logged.
- frameHandler().data(false, 3, createMessageFrame(new String(new char[1000])), 1000);
+ frameHandler().data(false, 3, createMessageFrame(new String(new char[1000])),
+ 1000, 1000);
assertThat(logs).hasSize(1);
log = logs.remove(0);
String data = log.getMessage();
@@ -377,7 +379,8 @@ public void maxMessageSizeShouldBeEnforced() throws Exception {
// Receive the message.
final String message = "Hello Client";
Buffer buffer = createMessageFrame(message);
- frameHandler().data(false, 3, buffer, (int) buffer.size());
+ frameHandler().data(false, 3, buffer, (int) buffer.size(),
+ (int) buffer.size());
listener.waitUntilStreamClosed();
assertEquals(Code.RESOURCE_EXHAUSTED, listener.status.getCode());
@@ -500,7 +503,8 @@ public void readMessages() throws Exception {
assertNotNull(listener.headers);
for (int i = 0; i < numMessages; i++) {
Buffer buffer = createMessageFrame(message + i);
- frameHandler().data(false, 3, buffer, (int) buffer.size());
+ frameHandler().data(false, 3, buffer, (int) buffer.size(),
+ (int) buffer.size());
}
frameHandler().headers(true, true, 3, 0, grpcResponseTrailers(), HeadersMode.HTTP_20_HEADERS);
listener.waitUntilStreamClosed();
@@ -529,7 +533,8 @@ public void receivedHeadersForInvalidStreamShouldKillConnection() throws Excepti
@Test
public void receivedDataForInvalidStreamShouldKillConnection() throws Exception {
initTransport();
- frameHandler().data(false, 3, createMessageFrame(new String(new char[1000])), 1000);
+ frameHandler().data(false, 3, createMessageFrame(new String(new char[1000])),
+ 1000, 1000);
verify(frameWriter, timeout(TIME_OUT_MS))
.goAway(eq(0), eq(ErrorCode.PROTOCOL_ERROR), any(byte[].class));
verify(transportListener).transportShutdown(isA(Status.class));
@@ -551,7 +556,8 @@ public void invalidInboundHeadersCancelStream() throws Exception {
HeadersMode.HTTP_20_HEADERS);
// Now wait to receive 1000 bytes of data so we can have a better error message before
// cancelling the streaam.
- frameHandler().data(false, 3, createMessageFrame(new String(new char[1000])), 1000);
+ frameHandler().data(false, 3,
+ createMessageFrame(new String(new char[1000])), 1000, 1000);
verify(frameWriter, timeout(TIME_OUT_MS)).rstStream(eq(3), eq(ErrorCode.CANCEL));
assertNull(listener.headers);
assertEquals(Status.INTERNAL.getCode(), listener.status.getCode());
@@ -622,7 +628,8 @@ public void receiveResetNoError() throws Exception {
assertContainStream(3);
frameHandler().headers(false, false, 3, 0, grpcResponseHeaders(), HeadersMode.HTTP_20_HEADERS);
Buffer buffer = createMessageFrame("a message");
- frameHandler().data(false, 3, buffer, (int) buffer.size());
+ frameHandler().data(false, 3, buffer, (int) buffer.size(),
+ (int) buffer.size());
frameHandler().headers(true, true, 3, 0, grpcResponseTrailers(), HeadersMode.HTTP_20_HEADERS);
frameHandler().rstStream(3, ErrorCode.NO_ERROR);
stream.request(1);
@@ -762,15 +769,18 @@ public void windowUpdate() throws Exception {
int messageLength = INITIAL_WINDOW_SIZE / 4;
byte[] fakeMessage = new byte[messageLength];
+ int paddingLength = 2;
// Stream 1 receives a message
- Buffer buffer = createMessageFrame(fakeMessage);
+ Buffer buffer = createMessageFrame(fakeMessage, paddingLength);
int messageFrameLength = (int) buffer.size();
- frameHandler().data(false, 3, buffer, messageFrameLength);
+ frameHandler().data(false, 3, buffer, messageFrameLength - paddingLength,
+ messageFrameLength);
// Stream 2 receives a message
- buffer = createMessageFrame(fakeMessage);
- frameHandler().data(false, 5, buffer, messageFrameLength);
+ buffer = createMessageFrame(fakeMessage, paddingLength);
+ frameHandler().data(false, 5, buffer, messageFrameLength - paddingLength,
+ messageFrameLength);
verify(frameWriter, timeout(TIME_OUT_MS))
.windowUpdate(eq(0), eq((long) 2 * messageFrameLength));
@@ -778,17 +788,18 @@ public void windowUpdate() throws Exception {
// Stream 1 receives another message
buffer = createMessageFrame(fakeMessage);
- frameHandler().data(false, 3, buffer, messageFrameLength);
+ messageFrameLength = (int) buffer.size();
+ frameHandler().data(false, 3, buffer, messageFrameLength, messageFrameLength);
verify(frameWriter, timeout(TIME_OUT_MS))
- .windowUpdate(eq(3), eq((long) 2 * messageFrameLength));
+ .windowUpdate(eq(3), eq((long) 2 * messageFrameLength + paddingLength));
// Stream 2 receives another message
buffer = createMessageFrame(fakeMessage);
- frameHandler().data(false, 5, buffer, messageFrameLength);
+ frameHandler().data(false, 5, buffer, messageFrameLength, messageFrameLength);
verify(frameWriter, timeout(TIME_OUT_MS))
- .windowUpdate(eq(5), eq((long) 2 * messageFrameLength));
+ .windowUpdate(eq(5), eq((long) 2 * messageFrameLength + paddingLength));
verify(frameWriter, timeout(TIME_OUT_MS))
.windowUpdate(eq(0), eq((long) 2 * messageFrameLength));
@@ -819,7 +830,8 @@ public void windowUpdateWithInboundFlowControl() throws Exception {
frameHandler().headers(false, false, 3, 0, grpcResponseHeaders(), HeadersMode.HTTP_20_HEADERS);
Buffer buffer = createMessageFrame(fakeMessage);
long messageFrameLength = buffer.size();
- frameHandler().data(false, 3, buffer, (int) messageFrameLength);
+ frameHandler().data(false, 3, buffer, (int) messageFrameLength,
+ (int) messageFrameLength);
ArgumentCaptor idCaptor = ArgumentCaptor.forClass(Integer.class);
verify(frameWriter, timeout(TIME_OUT_MS)).windowUpdate(
idCaptor.capture(), eq(messageFrameLength));
@@ -1123,7 +1135,8 @@ public void receiveGoAway() throws Exception {
frameHandler().headers(false, false, 3, 0, grpcResponseHeaders(), HeadersMode.HTTP_20_HEADERS);
final String receivedMessage = "No, you are fine.";
Buffer buffer = createMessageFrame(receivedMessage);
- frameHandler().data(false, 3, buffer, (int) buffer.size());
+ frameHandler().data(false, 3, buffer, (int) buffer.size(),
+ (int) buffer.size());
frameHandler().headers(true, true, 3, 0, grpcResponseTrailers(), HeadersMode.HTTP_20_HEADERS);
listener1.waitUntilStreamClosed();
assertEquals(1, listener1.messages.size());
@@ -1154,12 +1167,12 @@ public void streamIdExhausted() throws Exception {
assertNotNull(listener.headers);
String message = "hello";
Buffer buffer = createMessageFrame(message);
- frameHandler().data(false, startId, buffer, (int) buffer.size());
+ frameHandler().data(false, startId, buffer, (int) buffer.size(), (int) buffer.size());
getStream(startId).cancel(Status.CANCELLED);
// Receives the second message after be cancelled.
buffer = createMessageFrame(message);
- frameHandler().data(false, startId, buffer, (int) buffer.size());
+ frameHandler().data(false, startId, buffer, (int) buffer.size(), (int) buffer.size());
listener.waitUntilStreamClosed();
// Should only have the first message delivered.
@@ -1329,7 +1342,7 @@ public void receivingWindowExceeded() throws Exception {
byte[] fakeMessage = new byte[messageLength];
Buffer buffer = createMessageFrame(fakeMessage);
int messageFrameLength = (int) buffer.size();
- frameHandler().data(false, 3, buffer, messageFrameLength);
+ frameHandler().data(false, 3, buffer, messageFrameLength, messageFrameLength);
listener.waitUntilStreamClosed();
assertEquals(Status.INTERNAL.getCode(), listener.status.getCode());
@@ -1392,7 +1405,8 @@ public void receiveDataWithoutHeader() throws Exception {
stream.start(listener);
stream.request(1);
Buffer buffer = createMessageFrame(new byte[1]);
- frameHandler().data(false, 3, buffer, (int) buffer.size());
+ frameHandler().data(false, 3, buffer, (int) buffer.size(),
+ (int) buffer.size());
// Trigger the failure by a trailer.
frameHandler().headers(
@@ -1414,11 +1428,13 @@ public void receiveDataWithoutHeaderAndTrailer() throws Exception {
stream.start(listener);
stream.request(1);
Buffer buffer = createMessageFrame(new byte[1]);
- frameHandler().data(false, 3, buffer, (int) buffer.size());
+ frameHandler().data(false, 3, buffer, (int) buffer.size(),
+ (int) buffer.size());
// Trigger the failure by a data frame.
buffer = createMessageFrame(new byte[1]);
- frameHandler().data(true, 3, buffer, (int) buffer.size());
+ frameHandler().data(true, 3, buffer, (int) buffer.size(),
+ (int) buffer.size());
listener.waitUntilStreamClosed();
assertEquals(Status.INTERNAL.getCode(), listener.status.getCode());
@@ -1436,7 +1452,8 @@ public void receiveLongEnoughDataWithoutHeaderAndTrailer() throws Exception {
stream.start(listener);
stream.request(1);
Buffer buffer = createMessageFrame(new byte[1000]);
- frameHandler().data(false, 3, buffer, (int) buffer.size());
+ frameHandler().data(false, 3, buffer, (int) buffer.size(),
+ (int) buffer.size());
// Once we receive enough detail, we cancel the stream. so we should have sent cancel.
verify(frameWriter, timeout(TIME_OUT_MS)).rstStream(eq(3), eq(ErrorCode.CANCEL));
@@ -1459,7 +1476,8 @@ public void receiveDataForUnknownStreamUpdateConnectionWindow() throws Exception
Buffer buffer = createMessageFrame(
new byte[INITIAL_WINDOW_SIZE / 2 + 1]);
- frameHandler().data(false, 3, buffer, (int) buffer.size());
+ frameHandler().data(false, 3, buffer, (int) buffer.size(),
+ (int) buffer.size());
// Should still update the connection window even stream 3 is gone.
verify(frameWriter, timeout(TIME_OUT_MS)).windowUpdate(0,
HEADER_LENGTH + INITIAL_WINDOW_SIZE / 2 + 1);
@@ -1467,7 +1485,8 @@ public void receiveDataForUnknownStreamUpdateConnectionWindow() throws Exception
new byte[INITIAL_WINDOW_SIZE / 2 + 1]);
// This should kill the connection, since we never created stream 5.
- frameHandler().data(false, 5, buffer, (int) buffer.size());
+ frameHandler().data(false, 5, buffer, (int) buffer.size(),
+ (int) buffer.size());
verify(frameWriter, timeout(TIME_OUT_MS))
.goAway(eq(0), eq(ErrorCode.PROTOCOL_ERROR), any(byte[].class));
verify(transportListener).transportShutdown(isA(Status.class));
@@ -2114,10 +2133,15 @@ private static Buffer createMessageFrame(String message) {
}
private static Buffer createMessageFrame(byte[] message) {
+ return createMessageFrame(message,0);
+ }
+
+ private static Buffer createMessageFrame(byte[] message, int paddingLength) {
Buffer buffer = new Buffer();
buffer.writeByte(0 /* UNCOMPRESSED */);
buffer.writeInt(message.length);
buffer.write(message);
+ buffer.write(new byte[paddingLength]);
return buffer;
}
diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerProviderTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerProviderTest.java
new file mode 100644
index 000000000000..93354f01e257
--- /dev/null
+++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerProviderTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2023 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 org.junit.Assert.assertThrows;
+
+import io.grpc.InsecureServerCredentials;
+import io.grpc.ServerCredentials;
+import io.grpc.ServerProvider;
+import io.grpc.ServerRegistryAccessor;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link OkHttpServerProvider}. */
+@RunWith(JUnit4.class)
+public class OkHttpServerProviderTest {
+ private OkHttpServerProvider provider = new OkHttpServerProvider();
+
+ @Test
+ public void provided() {
+ assertThat(ServerProvider.provider()).isInstanceOf(OkHttpServerProvider.class);
+ }
+
+ @Test
+ public void providedHardCoded() {
+ assertThat(ServerRegistryAccessor.getHardCodedClasses()).contains(OkHttpServerProvider.class);
+ }
+
+ @Test
+ public void basicMethods() {
+ assertThat(provider.isAvailable()).isTrue();
+ assertThat(provider.priority()).isEqualTo(4);
+ }
+
+ @Test
+ public void builderIsAOkHttpBuilder() {
+ assertThrows(UnsupportedOperationException.class, () -> provider.builderForPort(80));
+ }
+
+ @Test
+ public void newServerBuilderForPort_success() {
+ ServerProvider.NewServerBuilderResult result =
+ provider.newServerBuilderForPort(80, InsecureServerCredentials.create());
+ assertThat(result.getServerBuilder()).isInstanceOf(OkHttpServerBuilder.class);
+ }
+
+ @Test
+ public void newServerBuilderForPort_fail() {
+ ServerProvider.NewServerBuilderResult result = provider.newServerBuilderForPort(
+ 80, new FakeServerCredentials());
+ assertThat(result.getError()).contains("FakeServerCredentials");
+ }
+
+ private static final class FakeServerCredentials extends ServerCredentials {}
+}
diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java
index 455908816a8e..5ed8514b85bb 100644
--- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java
+++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java
@@ -60,6 +60,7 @@
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayDeque;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;
@@ -70,6 +71,7 @@
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import okio.Buffer;
+import okio.BufferedSink;
import okio.BufferedSource;
import okio.ByteString;
import okio.Okio;
@@ -90,15 +92,19 @@ public class OkHttpServerTransportTest {
private static final int TIME_OUT_MS = 2000;
private static final int INITIAL_WINDOW_SIZE = 65535;
private static final long MAX_CONNECTION_IDLE = TimeUnit.SECONDS.toNanos(1);
-
+ private static final byte FLAG_NONE = 0x0;
+ private static final byte FLAG_PADDED = 0x8;
+ private static final byte FLAG_END_STREAM = 0x1;
+ private static final byte TYPE_DATA = 0x0;
private MockServerTransportListener mockTransportListener = new MockServerTransportListener();
private ServerTransportListener transportListener
= mock(ServerTransportListener.class, delegatesTo(mockTransportListener));
private OkHttpServerTransport serverTransport;
private final ExecutorService threadPool = Executors.newCachedThreadPool();
private final SocketPair socketPair = SocketPair.create(threadPool);
- private final FrameWriter clientFrameWriter
- = new Http2().newWriter(Okio.buffer(Okio.sink(socketPair.getClientOutputStream())), true);
+ private final BufferedSink clientWriterSink = Okio.buffer(
+ Okio.sink(socketPair.getClientOutputStream()));
+ private final FrameWriter clientFrameWriter = new Http2().newWriter(clientWriterSink, true);
private final FrameReader clientFrameReader
= new Http2().newReader(Okio.buffer(Okio.source(socketPair.getClientInputStream())), true);
private final FrameReader.Handler clientFramesRead = mock(FrameReader.Handler.class);
@@ -135,7 +141,8 @@ public void setUp() throws Exception {
Buffer buf = new Buffer();
buf.write(in.getBuffer(), length);
clientDataFrames.data(outDone, streamId, buf);
- })).when(clientFramesRead).data(anyBoolean(), anyInt(), any(BufferedSource.class), anyInt());
+ })).when(clientFramesRead).data(anyBoolean(), anyInt(), any(BufferedSource.class), anyInt(),
+ anyInt());
}
@After
@@ -379,7 +386,8 @@ public void basicRpc_succeeds() throws Exception {
Buffer responseMessageFrame = createMessageFrame("Howdy client");
assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue();
verify(clientFramesRead)
- .data(eq(false), eq(1), any(BufferedSource.class), eq((int) responseMessageFrame.size()));
+ .data(eq(false), eq(1), any(BufferedSource.class), eq((int) responseMessageFrame.size()),
+ eq((int) responseMessageFrame.size()));
verify(clientDataFrames).data(false, 1, responseMessageFrame);
List responseTrailers = Arrays.asList(
@@ -440,7 +448,8 @@ public void activeRpc_delaysShutdownTermination() throws Exception {
Buffer responseMessageFrame = createMessageFrame("Howdy client");
assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue();
verify(clientFramesRead)
- .data(eq(false), eq(1), any(BufferedSource.class), eq((int) responseMessageFrame.size()));
+ .data(eq(false), eq(1), any(BufferedSource.class), eq((int) responseMessageFrame.size()),
+ eq((int) responseMessageFrame.size()));
verify(clientDataFrames).data(false, 1, responseMessageFrame);
pingPong();
assertThat(serverTransport.getActiveStreams().length).isEqualTo(1);
@@ -975,7 +984,8 @@ public void httpErrorsAdhereToFlowControl() throws Exception {
Buffer responseDataFrame = new Buffer().writeUtf8(errorDescription.substring(0, 1));
assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue();
verify(clientFramesRead).data(
- eq(false), eq(1), any(BufferedSource.class), eq((int) responseDataFrame.size()));
+ eq(false), eq(1), any(BufferedSource.class), eq((int) responseDataFrame.size()),
+ eq((int) responseDataFrame.size()));
verify(clientDataFrames).data(false, 1, responseDataFrame);
clientFrameWriter.windowUpdate(1, 1000);
@@ -984,7 +994,8 @@ public void httpErrorsAdhereToFlowControl() throws Exception {
responseDataFrame = new Buffer().writeUtf8(errorDescription.substring(1));
assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue();
verify(clientFramesRead).data(
- eq(true), eq(1), any(BufferedSource.class), eq((int) responseDataFrame.size()));
+ eq(true), eq(1), any(BufferedSource.class), eq((int) responseDataFrame.size()),
+ eq((int) responseDataFrame.size()));
verify(clientDataFrames).data(true, 1, responseDataFrame);
assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue();
@@ -993,6 +1004,71 @@ public void httpErrorsAdhereToFlowControl() throws Exception {
shutdownAndTerminate(/*lastStreamId=*/ 1);
}
+ @Test
+ public void windowUpdate() throws Exception {
+ serverBuilder.flowControlWindow(100);
+ initTransport();
+ handshake();
+
+ List headers = Arrays.asList(
+ HTTP_SCHEME_HEADER,
+ METHOD_HEADER,
+ new Header(Header.TARGET_AUTHORITY, "example.com:80"),
+ new Header(Header.TARGET_PATH, "/com.example/SimpleService.doit"),
+ CONTENT_TYPE_HEADER,
+ TE_HEADER,
+ new Header("some-metadata", "this could be anything"));
+ clientFrameWriter.headers(1, new ArrayList<>(headers));
+ clientFrameWriter.headers(3, new ArrayList<>(headers));
+ String message = "Hello Server Pad Me!"; // length = 20, add buffer length = 5
+ writeDataDirectly(clientWriterSink, FLAG_NONE, 1, message, 0);
+ pingPong();
+
+ MockStreamListener streamListener = mockTransportListener.newStreams.pop();
+ MockStreamListener streamListener2 = mockTransportListener.newStreams.pop();
+ assertThat(streamListener.stream.getAuthority()).isEqualTo("example.com:80");
+ assertThat(streamListener.method).isEqualTo("com.example/SimpleService.doit");
+ assertThat(streamListener.headers.get(
+ Metadata.Key.of("Some-Metadata", Metadata.ASCII_STRING_MARSHALLER)))
+ .isEqualTo("this could be anything");
+ streamListener.stream.request(1);
+ pingPong();
+ assertThat(streamListener.messages.pop()).isEqualTo("Hello Server Pad Me!");
+ streamListener.stream.writeHeaders(metadata("User-Data", "best data"));
+ streamListener.stream.writeMessage(new ByteArrayInputStream("Howdy client".getBytes(UTF_8)));
+ List responseHeaders = Arrays.asList(
+ new Header(":status", "200"),
+ CONTENT_TYPE_HEADER,
+ new Header("user-data", "best data"));
+ assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue();
+ verify(clientFramesRead)
+ .headers(false, false, 1, -1, responseHeaders, HeadersMode.HTTP_20_HEADERS);
+
+ writeDataDirectly(clientWriterSink, FLAG_PADDED, 1, message, 10);
+ writeDataDirectly(clientWriterSink, FLAG_PADDED | FLAG_END_STREAM, 3, message, 40);
+ clientFrameWriter.flush();
+
+ int expectedConsumed = message.length() + 5;
+ assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue();
+ verify(clientFramesRead).windowUpdate(0, expectedConsumed * 2 + 10);
+ assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue();
+ verify(clientFramesRead).windowUpdate(0, expectedConsumed + 40);
+ streamListener.stream.request(2);
+ streamListener2.stream.request(1);
+ assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue();
+ verify(clientFramesRead).windowUpdate(1, expectedConsumed * 2 + 10);
+ assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue();
+ verify(clientFramesRead).windowUpdate(3, expectedConsumed + 40);
+
+ writeDataDirectly(clientWriterSink, FLAG_PADDED | FLAG_END_STREAM, 1, message, 100);
+ clientFrameWriter.flush();
+ assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue();
+ verify(clientFramesRead).rstStream(eq(1), eq(ErrorCode.FLOW_CONTROL_ERROR));
+ clientFrameWriter.rstStream(3, ErrorCode.CANCEL);
+ pingPong();
+ shutdownAndTerminate(/*lastStreamId=*/ 3);
+ }
+
@Test
public void dataForStream0_failsWithGoAway() throws Exception {
initTransport();
@@ -1223,6 +1299,32 @@ private static Buffer createMessageFrame(String stringMessage) {
return buffer;
}
+ private void writeDataDirectly(BufferedSink sink, int flag, int streamId, String message,
+ int paddingLength) throws IOException {
+ Buffer buffer = createMessageFrame(message);
+ int bufferLengthWithPadding = (int) buffer.size();
+ if ((flag & FLAG_PADDED) != 0) {
+ bufferLengthWithPadding += paddingLength;
+ }
+ writeLength(sink, bufferLengthWithPadding);
+ sink.writeByte(TYPE_DATA);
+ sink.writeByte(flag & 0xff);
+ sink.writeInt(streamId & 0x7fffffff);
+ if ((flag & FLAG_PADDED) != 0) {
+ sink.writeByte((short)(paddingLength - 1));
+ char[] value = new char[paddingLength - 1];
+ Arrays.fill(value, '!');
+ buffer.write(new String(value).getBytes(UTF_8));
+ }
+ sink.write(buffer, buffer.size());
+ }
+
+ private void writeLength(BufferedSink sink, int length) throws IOException {
+ sink.writeByte((length >>> 16 ) & 0xff);
+ sink.writeByte((length >>> 8 ) & 0xff);
+ sink.writeByte(length & 0xff);
+ }
+
private Metadata metadata(String... keysAndValues) {
Metadata metadata = new Metadata();
assertThat(keysAndValues.length % 2).isEqualTo(0);
@@ -1279,7 +1381,8 @@ private void verifyHttpError(
Buffer responseDataFrame = new Buffer().writeUtf8(errorDescription);
assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue();
verify(clientFramesRead).data(
- eq(true), eq(streamId), any(BufferedSource.class), eq((int) responseDataFrame.size()));
+ eq(true), eq(streamId), any(BufferedSource.class),
+ eq((int) responseDataFrame.size()), eq((int) responseDataFrame.size()));
verify(clientDataFrames).data(true, streamId, responseDataFrame);
assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue();
diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/FrameReader.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/FrameReader.java
index 585ccb15355f..490673eff6a1 100644
--- a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/FrameReader.java
+++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/FrameReader.java
@@ -32,7 +32,7 @@ public interface FrameReader extends Closeable {
boolean nextFrame(Handler handler) throws IOException;
interface Handler {
- void data(boolean inFinished, int streamId, BufferedSource source, int length)
+ void data(boolean inFinished, int streamId, BufferedSource source, int length, int paddedLength)
throws IOException;
/**
diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Http2.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Http2.java
index 3a8c41c62856..0eb49b9f0762 100644
--- a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Http2.java
+++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Http2.java
@@ -220,7 +220,7 @@ private List readHeaderBlock(int length, short padding, byte flags, int
return hpackReader.getAndResetHeaderList();
}
- private void readData(Handler handler, int length, byte flags, int streamId)
+ private void readData(Handler handler, int paddedLength, byte flags, int streamId)
throws IOException {
// TODO: checkState open or half-closed (local) or raise STREAM_CLOSED
boolean inFinished = (flags & FLAG_END_STREAM) != 0;
@@ -230,10 +230,10 @@ private void readData(Handler handler, int length, byte flags, int streamId)
}
short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.readByte() & 0xff) : 0;
- length = lengthWithoutPadding(length, flags, padding);
+ int length = lengthWithoutPadding(paddedLength, flags, padding);
// FIXME: pass padding length to handler because it should be included for flow control
- handler.data(inFinished, streamId, source, length);
+ handler.data(inFinished, streamId, source, length, paddedLength);
source.skip(padding);
}
diff --git a/okhttp/third_party/okhttp/test/java/io/grpc/okhttp/internal/framed/Http2Test.java b/okhttp/third_party/okhttp/test/java/io/grpc/okhttp/internal/framed/Http2Test.java
new file mode 100644
index 000000000000..5631a18515dc
--- /dev/null
+++ b/okhttp/third_party/okhttp/test/java/io/grpc/okhttp/internal/framed/Http2Test.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2023 Square, Inc.
+ *
+ * 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.internal.framed;
+
+import static io.grpc.okhttp.internal.framed.Http2.FLAG_NONE;
+import static io.grpc.okhttp.internal.framed.Http2.FLAG_PADDED;
+import static io.grpc.okhttp.internal.framed.Http2.TYPE_DATA;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import okio.Buffer;
+import okio.BufferedSink;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@RunWith(JUnit4.class)
+public class Http2Test {
+ @Rule
+ public final MockitoRule mocks = MockitoJUnit.rule();
+ private FrameReader http2FrameReader;
+ @Mock
+ private FrameReader.Handler mockHandler;
+ private final int STREAM_ID = 6;
+
+ @Test
+ public void dataFrameNoPadding() throws IOException {
+ Buffer bufferIn = createData(FLAG_NONE, 3239, 0 );
+ http2FrameReader = new Http2.Reader(bufferIn, 100, true);
+ http2FrameReader.nextFrame(mockHandler);
+
+ verify(mockHandler).data(eq(false), eq(STREAM_ID), eq(bufferIn), eq(3239), eq(3239));
+ assertEquals(3239, bufferIn.size());
+ }
+
+ @Test
+ public void dataFrameOneLengthPadding() throws IOException {
+ Buffer bufferIn = createData(FLAG_PADDED, 1876, 0);
+ http2FrameReader = new Http2.Reader(bufferIn, 100, true);
+ http2FrameReader.nextFrame(mockHandler);
+
+ verify(mockHandler).data(eq(false), eq(STREAM_ID), eq(bufferIn), eq(1875), eq(1876));
+ assertEquals(1876, bufferIn.size());
+ }
+
+ @Test
+ public void dataFramePadding() throws IOException {
+ Buffer bufferIn = createData(FLAG_PADDED, 2037, 125);
+ http2FrameReader = new Http2.Reader(bufferIn, 100, true);
+ http2FrameReader.nextFrame(mockHandler);
+
+ verify(mockHandler).data(eq(false), eq(STREAM_ID), eq(bufferIn), eq(2037 - 126), eq(2037));
+ assertEquals(2037 - 125, bufferIn.size());
+ }
+
+ private Buffer createData(int flag, int length, int paddingLength) throws IOException {
+ Buffer sink = new Buffer();
+ writeLength(sink, length);
+ sink.writeByte(TYPE_DATA);
+ sink.writeByte(flag);
+ sink.writeInt(STREAM_ID);
+ if ((flag & FLAG_PADDED) != 0) {
+ sink.writeByte((short)paddingLength);
+ }
+ char[] value = new char[length];
+ Arrays.fill(value, '!');
+ sink.write(new String(value).getBytes(StandardCharsets.UTF_8));
+ return sink;
+ }
+
+ private void writeLength(BufferedSink sink, int length) throws IOException {
+ sink.writeByte((length >>> 16 ) & 0xff);
+ sink.writeByte((length >>> 8 ) & 0xff);
+ sink.writeByte(length & 0xff);
+ }
+}
diff --git a/protobuf-lite/build.gradle b/protobuf-lite/build.gradle
index 0ada691913c9..11a49d4816da 100644
--- a/protobuf-lite/build.gradle
+++ b/protobuf-lite/build.gradle
@@ -21,6 +21,12 @@ dependencies {
signature libraries.signature.android
}
+tasks.named("jar").configure {
+ manifest {
+ attributes('Automatic-Module-Name': 'io.grpc.protobuf.lite')
+ }
+}
+
tasks.named("compileTestJava").configure {
options.compilerArgs += [
"-Xlint:-cast"
diff --git a/protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoLiteUtils.java b/protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoLiteUtils.java
index 211ef5366e9e..a0f50cae8dbc 100644
--- a/protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoLiteUtils.java
+++ b/protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoLiteUtils.java
@@ -69,7 +69,6 @@ public final class ProtoLiteUtils {
*
* @since 1.0.0
*/
- @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1787")
public static void setExtensionRegistry(ExtensionRegistryLite newRegistry) {
globalRegistry = checkNotNull(newRegistry, "newRegistry");
}
diff --git a/protobuf/build.gradle b/protobuf/build.gradle
index 47d08e1931fd..8220b7da0df1 100644
--- a/protobuf/build.gradle
+++ b/protobuf/build.gradle
@@ -9,6 +9,12 @@ plugins {
description = 'gRPC: Protobuf'
+tasks.named("jar").configure {
+ manifest {
+ attributes('Automatic-Module-Name': 'io.grpc.protobuf')
+ }
+}
+
dependencies {
api project(':grpc-api'),
libraries.jsr305,
diff --git a/protobuf/src/main/java/io/grpc/protobuf/ProtoUtils.java b/protobuf/src/main/java/io/grpc/protobuf/ProtoUtils.java
index ebc708f522f4..eeef6e3b5830 100644
--- a/protobuf/src/main/java/io/grpc/protobuf/ProtoUtils.java
+++ b/protobuf/src/main/java/io/grpc/protobuf/ProtoUtils.java
@@ -43,7 +43,6 @@ public final class ProtoUtils {
*
* @since 1.16.0
*/
- @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1787")
public static void setExtensionRegistry(ExtensionRegistry registry) {
ProtoLiteUtils.setExtensionRegistry(registry);
}
diff --git a/repositories.bzl b/repositories.bzl
index 2b8a580c9159..d9a383a3e893 100644
--- a/repositories.bzl
+++ b/repositories.bzl
@@ -11,34 +11,34 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
# )
IO_GRPC_GRPC_JAVA_ARTIFACTS = [
"com.google.android:annotations:4.1.1.4",
- "com.google.api.grpc:proto-google-common-protos:2.17.0",
+ "com.google.api.grpc:proto-google-common-protos:2.22.0",
"com.google.auth:google-auth-library-credentials:1.4.0",
"com.google.auth:google-auth-library-oauth2-http:1.4.0",
- "com.google.auto.value:auto-value-annotations:1.10.1",
- "com.google.auto.value:auto-value:1.10.1",
+ "com.google.auto.value:auto-value-annotations:1.10.2",
+ "com.google.auto.value:auto-value:1.10.2",
"com.google.code.findbugs:jsr305:3.0.2",
"com.google.code.gson:gson:2.10.1",
- "com.google.errorprone:error_prone_annotations:2.18.0",
+ "com.google.errorprone:error_prone_annotations:2.20.0",
"com.google.guava:failureaccess:1.0.1",
"com.google.guava:guava:32.0.1-android",
"com.google.re2j:re2j:1.7",
- "com.google.truth:truth:1.0.1",
+ "com.google.truth:truth:1.1.5",
"com.squareup.okhttp:okhttp:2.7.5",
- "com.squareup.okio:okio:1.17.5",
- "io.netty:netty-buffer:4.1.87.Final",
- "io.netty:netty-codec-http2:4.1.87.Final",
- "io.netty:netty-codec-http:4.1.87.Final",
- "io.netty:netty-codec-socks:4.1.87.Final",
- "io.netty:netty-codec:4.1.87.Final",
- "io.netty:netty-common:4.1.87.Final",
- "io.netty:netty-handler-proxy:4.1.87.Final",
- "io.netty:netty-handler:4.1.87.Final",
- "io.netty:netty-resolver:4.1.87.Final",
+ "com.squareup.okio:okio:2.10.0",
+ "io.netty:netty-buffer:4.1.93.Final",
+ "io.netty:netty-codec-http2:4.1.93.Final",
+ "io.netty:netty-codec-http:4.1.93.Final",
+ "io.netty:netty-codec-socks:4.1.93.Final",
+ "io.netty:netty-codec:4.1.93.Final",
+ "io.netty:netty-common:4.1.93.Final",
+ "io.netty:netty-handler-proxy:4.1.93.Final",
+ "io.netty:netty-handler:4.1.93.Final",
+ "io.netty:netty-resolver:4.1.93.Final",
"io.netty:netty-tcnative-boringssl-static:2.0.61.Final",
"io.netty:netty-tcnative-classes:2.0.61.Final",
- "io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.87.Final",
- "io.netty:netty-transport-native-unix-common:4.1.87.Final",
- "io.netty:netty-transport:4.1.87.Final",
+ "io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.93.Final",
+ "io.netty:netty-transport-native-unix-common:4.1.93.Final",
+ "io.netty:netty-transport:4.1.93.Final",
"io.opencensus:opencensus-api:0.31.0",
"io.opencensus:opencensus-contrib-grpc-metrics:0.31.0",
"io.perfmark:perfmark-api:0.26.0",
@@ -69,6 +69,7 @@ IO_GRPC_GRPC_JAVA_OVERRIDE_TARGETS = {
"io.grpc:grpc-core": "@io_grpc_grpc_java//core:core_maven",
"io.grpc:grpc-googleapis": "@io_grpc_grpc_java//googleapis",
"io.grpc:grpc-grpclb": "@io_grpc_grpc_java//grpclb",
+ "io.grpc:grpc-inprocess": "@io_grpc_grpc_java//inprocess",
"io.grpc:grpc-netty": "@io_grpc_grpc_java//netty",
"io.grpc:grpc-netty-shaded": "@io_grpc_grpc_java//netty:shaded_maven",
"io.grpc:grpc-okhttp": "@io_grpc_grpc_java//okhttp",
@@ -79,6 +80,7 @@ IO_GRPC_GRPC_JAVA_OVERRIDE_TARGETS = {
"io.grpc:grpc-stub": "@io_grpc_grpc_java//stub",
"io.grpc:grpc-testing": "@io_grpc_grpc_java//testing",
"io.grpc:grpc-xds": "@io_grpc_grpc_java//xds:xds_maven",
+ "io.grpc:grpc-util": "@io_grpc_grpc_java//util",
}
def grpc_java_repositories():
@@ -141,18 +143,18 @@ def com_google_protobuf():
# This statement defines the @com_google_protobuf repo.
http_archive(
name = "com_google_protobuf",
- sha256 = "5d0f05587aa3ad56079b4c4481dcb462267e5f1075d905c321f8ed6339e74ab0",
- strip_prefix = "protobuf-22.3",
- urls = ["https://github.com/protocolbuffers/protobuf/releases/download/v22.3/protobuf-22.3.zip"],
+ sha256 = "5980276108f948e1ada091475549a8c75dc83c193129aab0e986ceaac3e97131",
+ strip_prefix = "protobuf-24.0",
+ urls = ["https://github.com/protocolbuffers/protobuf/releases/download/v24.0/protobuf-24.0.zip"],
)
def com_google_protobuf_javalite():
# java_lite_proto_library rules implicitly depend on @com_google_protobuf_javalite
http_archive(
name = "com_google_protobuf_javalite",
- sha256 = "5d0f05587aa3ad56079b4c4481dcb462267e5f1075d905c321f8ed6339e74ab0",
- strip_prefix = "protobuf-22.3",
- urls = ["https://github.com/protocolbuffers/protobuf/releases/download/v22.3/protobuf-22.3.zip"],
+ sha256 = "5980276108f948e1ada091475549a8c75dc83c193129aab0e986ceaac3e97131",
+ strip_prefix = "protobuf-24.0",
+ urls = ["https://github.com/protocolbuffers/protobuf/releases/download/v24.0/protobuf-24.0.zip"],
)
def io_grpc_grpc_proto():
diff --git a/rls/BUILD.bazel b/rls/BUILD.bazel
index d4af35692409..c67c7cd56bea 100644
--- a/rls/BUILD.bazel
+++ b/rls/BUILD.bazel
@@ -12,7 +12,7 @@ java_library(
"//api",
"//core",
"//core:internal",
- "//core:util",
+ "//util",
"//stub",
"@com_google_auto_value_auto_value_annotations//jar",
"@com_google_code_findbugs_jsr305//jar",
diff --git a/rls/build.gradle b/rls/build.gradle
index 453052addf39..b0abb08b4f3c 100644
--- a/rls/build.gradle
+++ b/rls/build.gradle
@@ -1,5 +1,5 @@
plugins {
- id "java"
+ id "java-library"
id "maven-publish"
id "com.google.protobuf"
id "jacoco"
@@ -8,8 +8,14 @@ plugins {
description = "gRPC: RouteLookupService Loadbalancing plugin"
+tasks.named("jar").configure {
+ manifest {
+ attributes('Automatic-Module-Name': 'io.grpc.rls')
+ }
+}
+
dependencies {
- implementation project(':grpc-core'),
+ implementation project(':grpc-util'),
project(':grpc-protobuf'),
project(':grpc-stub'),
libraries.auto.value.annotations,
@@ -39,6 +45,7 @@ tasks.named("javadoc").configure {
// Do not publish javadoc since currently there is no public API.
failOnError false // no public or protected classes found to document
exclude 'io/grpc/lookup/v1/**'
+ exclude 'io/grpc/rls/*Provider.java'
exclude 'io/grpc/rls/internal/**'
exclude 'io/grpc/rls/Internal*'
}
diff --git a/rls/src/main/java/io/grpc/rls/LinkedHashLruCache.java b/rls/src/main/java/io/grpc/rls/LinkedHashLruCache.java
index 8baba0bb8246..5a4a2dab4525 100644
--- a/rls/src/main/java/io/grpc/rls/LinkedHashLruCache.java
+++ b/rls/src/main/java/io/grpc/rls/LinkedHashLruCache.java
@@ -179,7 +179,6 @@ private SizedValue readInternal(K key) {
synchronized (lock) {
SizedValue existing = delegate.get(key);
if (existing != null && isExpired(key, existing.value, ticker.read())) {
- invalidate(key, EvictionType.EXPIRED);
return null;
}
return existing;
diff --git a/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java b/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java
index 120a486dec69..51b934e13b76 100644
--- a/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java
+++ b/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java
@@ -354,12 +354,7 @@ public void get_updatesLbState() throws Exception {
assertThat(stateCaptor.getAllValues())
.containsExactly(ConnectivityState.CONNECTING, ConnectivityState.READY);
Metadata headers = new Metadata();
- PickResult pickResult = pickerCaptor.getValue().pickSubchannel(
- new PickSubchannelArgsImpl(
- TestMethodDescriptors.voidMethod().toBuilder().setFullMethodName("service1/create")
- .build(),
- headers,
- CallOptions.DEFAULT));
+ PickResult pickResult = getPickResultForCreate(pickerCaptor, headers);
assertThat(pickResult.getStatus().isOk()).isTrue();
assertThat(pickResult.getSubchannel()).isNotNull();
assertThat(headers.get(RLS_DATA_KEY)).isEqualTo("header-rls-data-value");
@@ -395,6 +390,57 @@ public void get_updatesLbState() throws Exception {
assertThat(fakeThrottler.getNumUnthrottled()).isEqualTo(1);
}
+ @Test
+ public void timeout_not_changing_picked_subchannel() throws Exception {
+ setUpRlsLbClient();
+ RouteLookupRequest routeLookupRequest = RouteLookupRequest.create(ImmutableMap.of(
+ "server", "bigtable.googleapis.com", "service-key", "service1", "method-key", "create"));
+ rlsServerImpl.setLookupTable(
+ ImmutableMap.of(
+ routeLookupRequest,
+ RouteLookupResponse.create(
+ ImmutableList.of("primary.cloudbigtable.googleapis.com", "target2", "target3"),
+ "header-rls-data-value")));
+
+ // valid channel
+ CachedRouteLookupResponse resp = getInSyncContext(routeLookupRequest);
+ assertThat(resp.hasData()).isFalse();
+ fakeClock.forwardTime(SERVER_LATENCY_MILLIS, TimeUnit.MILLISECONDS);
+
+ resp = getInSyncContext(routeLookupRequest);
+ assertThat(resp.hasData()).isTrue();
+
+ ArgumentCaptor pickerCaptor = ArgumentCaptor.forClass(SubchannelPicker.class);
+ ArgumentCaptor stateCaptor =
+ ArgumentCaptor.forClass(ConnectivityState.class);
+ verify(helper, times(4)).updateBalancingState(stateCaptor.capture(), pickerCaptor.capture());
+
+ Metadata headers = new Metadata();
+ PickResult pickResult = getPickResultForCreate(pickerCaptor, headers);
+ assertThat(pickResult.getStatus().isOk()).isTrue();
+ assertThat(pickResult.getSubchannel().toString())
+ .isEqualTo("primary.cloudbigtable.googleapis.com");
+
+ fakeClock.forwardTime(5, TimeUnit.MINUTES);
+ PickResult pickResult2 = getPickResultForCreate(pickerCaptor, headers);
+ assertThat(pickResult2.getSubchannel()).isNull();
+ fakeClock.forwardTime(SERVER_LATENCY_MILLIS, TimeUnit.MILLISECONDS);
+ PickResult pickResult3 = getPickResultForCreate(pickerCaptor, headers);
+ assertThat(pickResult3.getSubchannel()).isNotNull();
+ assertThat(pickResult3.getSubchannel().toString())
+ .isEqualTo(pickResult.getSubchannel().toString());
+ }
+
+ private static PickResult getPickResultForCreate(ArgumentCaptor pickerCaptor,
+ Metadata headers) {
+ return pickerCaptor.getValue().pickSubchannel(
+ new PickSubchannelArgsImpl(
+ TestMethodDescriptors.voidMethod().toBuilder().setFullMethodName("service1/create")
+ .build(),
+ headers,
+ CallOptions.DEFAULT));
+ }
+
@Test
public void get_withAdaptiveThrottler() throws Exception {
AdaptiveThrottler adaptiveThrottler =
@@ -440,12 +486,7 @@ public void get_withAdaptiveThrottler() throws Exception {
.updateBalancingState(stateCaptor.capture(), pickerCaptor.capture());
Metadata headers = new Metadata();
- PickResult pickResult = pickerCaptor.getValue().pickSubchannel(
- new PickSubchannelArgsImpl(
- TestMethodDescriptors.voidMethod().toBuilder().setFullMethodName("service1/create")
- .build(),
- headers,
- CallOptions.DEFAULT));
+ PickResult pickResult = getPickResultForCreate(pickerCaptor, headers);
assertThat(pickResult.getSubchannel()).isNotNull();
assertThat(headers.get(RLS_DATA_KEY)).isEqualTo("header-rls-data-value");
@@ -680,7 +721,8 @@ public PickResult pickSubchannel(PickSubchannelArgs args) {
new SubchannelPicker() {
@Override
public PickResult pickSubchannel(PickSubchannelArgs args) {
- return PickResult.withSubchannel(mock(Subchannel.class));
+ return PickResult.withSubchannel(
+ mock(Subchannel.class, config.get("target").toString()));
}
});
}
diff --git a/rls/src/test/java/io/grpc/rls/LinkedHashLruCacheTest.java b/rls/src/test/java/io/grpc/rls/LinkedHashLruCacheTest.java
index 19b3a012151d..a31f58f5365b 100644
--- a/rls/src/test/java/io/grpc/rls/LinkedHashLruCacheTest.java
+++ b/rls/src/test/java/io/grpc/rls/LinkedHashLruCacheTest.java
@@ -150,13 +150,16 @@ public void eviction_size_shouldEvictAlreadyExpired() {
}
@Test
- public void eviction_get_shouldNotReturnAlreadyExpired() {
+ public void eviction_cleanupShouldRemoveAlreadyExpired() {
for (int i = 1; i <= MAX_SIZE; i++) {
// last entry is already expired when added
- cache.cache(i, new Entry("Entry" + i, ticker.read() + MAX_SIZE - i));
+ cache.cache(i, new Entry("Entry" + i,
+ ticker.read() + ((MAX_SIZE - i) * TimeUnit.MINUTES.toNanos(1)) + 1));
}
assertThat(cache.estimatedSize()).isEqualTo(MAX_SIZE);
+
+ fakeClock.forwardTime(1, TimeUnit.MINUTES);
assertThat(cache.read(MAX_SIZE)).isNull();
assertThat(cache.estimatedSize()).isEqualTo(MAX_SIZE - 1);
verify(evictionListener).onEviction(eq(MAX_SIZE), any(Entry.class), eq(EvictionType.EXPIRED));
diff --git a/services/BUILD.bazel b/services/BUILD.bazel
index bf12df6eeb1b..f4ec14cc255b 100644
--- a/services/BUILD.bazel
+++ b/services/BUILD.bazel
@@ -154,7 +154,7 @@ java_library(
":_health_java_grpc",
"//api",
"//core:internal",
- "//core:util",
+ "//util",
"@com_google_code_findbugs_jsr305//jar",
"@com_google_guava_guava//jar",
"@io_grpc_grpc_proto//:health_java_proto",
diff --git a/services/build.gradle b/services/build.gradle
index b834fcd2d795..3135d9095ea6 100644
--- a/services/build.gradle
+++ b/services/build.gradle
@@ -16,16 +16,22 @@ tasks.named("compileJava").configure {
]
}
+tasks.named("jar").configure {
+ manifest {
+ attributes('Automatic-Module-Name': 'io.grpc.services')
+ }
+}
+
dependencies {
api project(':grpc-protobuf'),
project(':grpc-stub'),
- project(':grpc-core')
+ project(':grpc-util')
implementation libraries.protobuf.java.util,
libraries.guava.jre // JRE required by protobuf-java-util
runtimeOnly libraries.errorprone.annotations,
- libraries.j2objc.annotations // Explicit dependency to keep in step with version used by guava
-
+ libraries.j2objc.annotations, // Explicit dependency to keep in step with version used by guava
+ libraries.gson // to fix checkUpperBoundDeps error here
compileOnly libraries.javax.annotation
testImplementation project(':grpc-testing'),
libraries.netty.transport.epoll, // for DomainSocketAddress
@@ -39,6 +45,7 @@ configureProtoCompilation()
tasks.named("javadoc").configure {
exclude 'io/grpc/services/Internal*.java'
exclude 'io/grpc/services/internal/*'
+ exclude 'io/grpc/protobuf/services/BinaryLogProvider.java'
exclude 'io/grpc/protobuf/services/internal/*'
}
diff --git a/services/src/main/java/io/grpc/services/CallMetricRecorder.java b/services/src/main/java/io/grpc/services/CallMetricRecorder.java
index f491fb7542e8..d480f0f4c3b4 100644
--- a/services/src/main/java/io/grpc/services/CallMetricRecorder.java
+++ b/services/src/main/java/io/grpc/services/CallMetricRecorder.java
@@ -41,6 +41,8 @@ public final class CallMetricRecorder {
new AtomicReference<>();
private final AtomicReference> requestCostMetrics =
new AtomicReference<>();
+ private final AtomicReference> namedMetrics =
+ new AtomicReference<>();
private double cpuUtilizationMetric = 0;
private double applicationUtilizationMetric = 0;
private double memoryUtilizationMetric = 0;
@@ -127,6 +129,27 @@ public CallMetricRecorder recordRequestCostMetric(String name, double value) {
return this;
}
+ /**
+ * Records an application-specific opaque custom metric measurement. If RPC has already finished,
+ * this method is no-op.
+ *
+ * A latter record will overwrite its former name-sakes.
+ *
+ * @return this recorder object
+ */
+ public CallMetricRecorder recordNamedMetric(String name, double value) {
+ if (disabled) {
+ return this;
+ }
+ if (namedMetrics.get() == null) {
+ // The chance of race of creation of the map should be very small, so it should be fine
+ // to create these maps that might be discarded.
+ namedMetrics.compareAndSet(null, new ConcurrentHashMap());
+ }
+ namedMetrics.get().put(name, value);
+ return this;
+ }
+
/**
* Records a call metric measurement for CPU utilization in the range [0, inf). Values outside the
* valid range are ignored. If RPC has already finished, this method is no-op.
@@ -235,12 +258,17 @@ Map finalizeAndDump() {
MetricReport finalizeAndDump2() {
Map savedRequestCostMetrics = finalizeAndDump();
Map savedUtilizationMetrics = utilizationMetrics.get();
+ Map savedNamedMetrics = namedMetrics.get();
if (savedUtilizationMetrics == null) {
savedUtilizationMetrics = Collections.emptyMap();
}
+ if (savedNamedMetrics == null) {
+ savedNamedMetrics = Collections.emptyMap();
+ }
return new MetricReport(cpuUtilizationMetric, applicationUtilizationMetric,
memoryUtilizationMetric, qps, eps, Collections.unmodifiableMap(savedRequestCostMetrics),
- Collections.unmodifiableMap(savedUtilizationMetrics)
+ Collections.unmodifiableMap(savedUtilizationMetrics),
+ Collections.unmodifiableMap(savedNamedMetrics)
);
}
diff --git a/services/src/main/java/io/grpc/services/InternalCallMetricRecorder.java b/services/src/main/java/io/grpc/services/InternalCallMetricRecorder.java
index 3b0cbbbda35c..a7ff1e5c32e8 100644
--- a/services/src/main/java/io/grpc/services/InternalCallMetricRecorder.java
+++ b/services/src/main/java/io/grpc/services/InternalCallMetricRecorder.java
@@ -47,8 +47,9 @@ public static MetricReport finalizeAndDump2(CallMetricRecorder recorder) {
public static MetricReport createMetricReport(double cpuUtilization,
double applicationUtilization, double memoryUtilization, double qps, double eps,
- Map requestCostMetrics, Map utilizationMetrics) {
+ Map requestCostMetrics, Map utilizationMetrics,
+ Map namedMetrics) {
return new MetricReport(cpuUtilization, applicationUtilization, memoryUtilization, qps, eps,
- requestCostMetrics, utilizationMetrics);
+ requestCostMetrics, utilizationMetrics, namedMetrics);
}
}
diff --git a/services/src/main/java/io/grpc/services/MetricRecorder.java b/services/src/main/java/io/grpc/services/MetricRecorder.java
index c585e7b54077..39b0e8df25f2 100644
--- a/services/src/main/java/io/grpc/services/MetricRecorder.java
+++ b/services/src/main/java/io/grpc/services/MetricRecorder.java
@@ -155,6 +155,6 @@ public void clearEpsMetric() {
MetricReport getMetricReport() {
return new MetricReport(cpuUtilization, applicationUtilization, memoryUtilization, qps, eps,
- Collections.emptyMap(), Collections.unmodifiableMap(metricsData));
+ Collections.emptyMap(), Collections.unmodifiableMap(metricsData), Collections.emptyMap());
}
}
diff --git a/services/src/main/java/io/grpc/services/MetricReport.java b/services/src/main/java/io/grpc/services/MetricReport.java
index 35cbfc056bf5..e559f0f00b51 100644
--- a/services/src/main/java/io/grpc/services/MetricReport.java
+++ b/services/src/main/java/io/grpc/services/MetricReport.java
@@ -35,10 +35,11 @@ public final class MetricReport {
private double eps;
private Map requestCostMetrics;
private Map utilizationMetrics;
+ private Map namedMetrics;
MetricReport(double cpuUtilization, double applicationUtilization, double memoryUtilization,
double qps, double eps, Map requestCostMetrics,
- Map utilizationMetrics) {
+ Map utilizationMetrics, Map namedMetrics) {
this.cpuUtilization = cpuUtilization;
this.applicationUtilization = applicationUtilization;
this.memoryUtilization = memoryUtilization;
@@ -46,6 +47,7 @@ public final class MetricReport {
this.eps = eps;
this.requestCostMetrics = checkNotNull(requestCostMetrics, "requestCostMetrics");
this.utilizationMetrics = checkNotNull(utilizationMetrics, "utilizationMetrics");
+ this.namedMetrics = checkNotNull(namedMetrics, "namedMetrics");
}
public double getCpuUtilization() {
@@ -68,6 +70,10 @@ public Map getUtilizationMetrics() {
return utilizationMetrics;
}
+ public Map getNamedMetrics() {
+ return namedMetrics;
+ }
+
public double getQps() {
return qps;
}
@@ -84,6 +90,7 @@ public String toString() {
.add("memoryUtilization", memoryUtilization)
.add("requestCost", requestCostMetrics)
.add("utilization", utilizationMetrics)
+ .add("named", namedMetrics)
.add("qps", qps)
.add("eps", eps)
.toString();
diff --git a/services/src/test/java/io/grpc/services/CallMetricRecorderTest.java b/services/src/test/java/io/grpc/services/CallMetricRecorderTest.java
index cb0bfc5d83fc..f60446b1a3a1 100644
--- a/services/src/test/java/io/grpc/services/CallMetricRecorderTest.java
+++ b/services/src/test/java/io/grpc/services/CallMetricRecorderTest.java
@@ -44,6 +44,9 @@ public void dumpDumpsAllSavedMetricValues() {
recorder.recordRequestCostMetric("cost1", 37465.12);
recorder.recordRequestCostMetric("cost2", 10293.0);
recorder.recordRequestCostMetric("cost3", 1.0);
+ recorder.recordNamedMetric("named1", 0.2233);
+ recorder.recordNamedMetric("named2", -1.618);
+ recorder.recordNamedMetric("named3", 3.1415926535);
recorder.recordCpuUtilizationMetric(0.1928);
recorder.recordApplicationUtilizationMetric(0.9987);
recorder.recordMemoryUtilizationMetric(0.474);
@@ -55,6 +58,8 @@ public void dumpDumpsAllSavedMetricValues() {
.containsExactly("util1", 0.154353423, "util2", 0.1367, "util3", 0.143734);
Truth.assertThat(dump.getRequestCostMetrics())
.containsExactly("cost1", 37465.12, "cost2", 10293.0, "cost3", 1.0);
+ Truth.assertThat(dump.getNamedMetrics())
+ .containsExactly("named1", 0.2233, "named2", -1.618, "named3", 3.1415926535);
Truth.assertThat(dump.getCpuUtilization()).isEqualTo(0.1928);
Truth.assertThat(dump.getApplicationUtilization()).isEqualTo(0.9987);
Truth.assertThat(dump.getMemoryUtilization()).isEqualTo(0.474);
@@ -62,6 +67,9 @@ public void dumpDumpsAllSavedMetricValues() {
Truth.assertThat(dump.getEps()).isEqualTo(1.618);
Truth.assertThat(dump.toString()).contains("eps=1.618");
Truth.assertThat(dump.toString()).contains("applicationUtilization=0.9987");
+ Truth.assertThat(dump.toString()).contains("named1=0.2233");
+ Truth.assertThat(dump.toString()).contains("named2=-1.618");
+ Truth.assertThat(dump.toString()).contains("named3=3.1415926535");
}
@Test
@@ -71,6 +79,7 @@ public void noMetricsRecordedAfterSnapshot() {
recorder.recordUtilizationMetric("cost", 0.154353423);
recorder.recordQpsMetric(3.14159);
recorder.recordEpsMetric(1.618);
+ recorder.recordNamedMetric("named1", 2.718);
assertThat(recorder.finalizeAndDump()).isEqualTo(initDump);
}
@@ -121,6 +130,9 @@ public void lastValueWinForMetricsWithSameName() {
recorder.recordUtilizationMetric("util1", 0.2837421);
recorder.recordMemoryUtilizationMetric(0.93840);
recorder.recordUtilizationMetric("util1", 0.843233);
+ recorder.recordNamedMetric("named1", 0.2233);
+ recorder.recordNamedMetric("named2", 2.718);
+ recorder.recordNamedMetric("named1", 3.1415926535);
recorder.recordQpsMetric(1928.3);
recorder.recordQpsMetric(100.8);
recorder.recordEpsMetric(3.14159);
@@ -133,6 +145,8 @@ public void lastValueWinForMetricsWithSameName() {
Truth.assertThat(dump.getMemoryUtilization()).isEqualTo(0.93840);
Truth.assertThat(dump.getUtilizationMetrics())
.containsExactly("util1", 0.843233);
+ Truth.assertThat(dump.getNamedMetrics())
+ .containsExactly("named1", 3.1415926535, "named2", 2.718);
Truth.assertThat(dump.getCpuUtilization()).isEqualTo(0);
Truth.assertThat(dump.getQps()).isEqualTo(100.8);
Truth.assertThat(dump.getEps()).isEqualTo(1.618);
diff --git a/servlet/build.gradle b/servlet/build.gradle
index f5ef32ae11e1..2b87134ca987 100644
--- a/servlet/build.gradle
+++ b/servlet/build.gradle
@@ -5,19 +5,8 @@ plugins {
description = "gRPC: Servlet"
-// javax.servlet-api 4.0 requires a minimum of Java 8, so we might as well use that source level
-sourceCompatibility = 1.8
-targetCompatibility = 1.8
-
def jettyVersion = '10.0.7'
-configurations {
- itImplementation.extendsFrom(implementation)
- undertowTestImplementation.extendsFrom(itImplementation)
- tomcatTestImplementation.extendsFrom(itImplementation)
- jettyTestImplementation.extendsFrom(itImplementation)
-}
-
sourceSets {
// Create a test sourceset for each classpath - could be simplified if we made new test directories
undertowTest {}
@@ -29,12 +18,25 @@ sourceSets {
}
}
+configurations {
+ itImplementation.extendsFrom(implementation)
+ undertowTestImplementation.extendsFrom(itImplementation)
+ tomcatTestImplementation.extendsFrom(itImplementation)
+ jettyTestImplementation.extendsFrom(itImplementation)
+}
+
+tasks.named("jar").configure {
+ manifest {
+ attributes('Automatic-Module-Name': 'io.grpc.servlet')
+ }
+}
+
dependencies {
api project(':grpc-api')
compileOnly 'javax.servlet:javax.servlet-api:4.0.1',
libraries.javax.annotation // java 9, 10 needs it
- implementation project(':grpc-core'),
+ implementation project(':grpc-util'),
libraries.guava
testImplementation 'javax.servlet:javax.servlet-api:4.0.1',
@@ -42,7 +44,7 @@ dependencies {
itImplementation project(':grpc-servlet'),
project(':grpc-netty'),
- project(':grpc-core').sourceSets.test.runtimeClasspath,
+ testFixtures(project(':grpc-core')),
libraries.junit
itImplementation(project(':grpc-interop-testing')) {
// Avoid grpc-netty-shaded dependency
@@ -56,11 +58,13 @@ dependencies {
jettyTestImplementation "org.eclipse.jetty:jetty-servlet:${jettyVersion}",
"org.eclipse.jetty.http2:http2-server:${jettyVersion}",
- "org.eclipse.jetty:jetty-client:${jettyVersion}"
- project(':grpc-testing')
+ "org.eclipse.jetty:jetty-client:${jettyVersion}",
+ project(':grpc-testing'),
+ libraries.truth,
+ libraries.protobuf.java
}
-test {
+tasks.named("test").configure {
if (JavaVersion.current().isJava9Compatible()) {
jvmArgs += [
// required for Lincheck
@@ -72,11 +76,11 @@ test {
// Set up individual classpaths for each test, to avoid any mismatch,
// and ensure they are only used when supported by the current jvm
-check.dependsOn(tasks.register('undertowTest', Test) {
+def undertowTest = tasks.register('undertowTest', Test) {
classpath = sourceSets.undertowTest.runtimeClasspath
testClassesDirs = sourceSets.undertowTest.output.classesDirs
-})
-check.dependsOn(tasks.register('tomcat9Test', Test) {
+}
+def tomcat9Test = tasks.register('tomcat9Test', Test) {
classpath = sourceSets.tomcatTest.runtimeClasspath
testClassesDirs = sourceSets.tomcatTest.output.classesDirs
@@ -93,19 +97,30 @@ check.dependsOn(tasks.register('tomcat9Test', Test) {
if (JavaVersion.current().isJava9Compatible()) {
jvmArgs += ['--add-opens=java.base/java.io=ALL-UNNAMED', '--add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED']
}
-})
+}
+
+tasks.named("check").configure {
+ dependsOn undertowTest, tomcat9Test
+}
+
+tasks.named("jacocoTestReport").configure {
+ // Must use executionData(Task...) override. The executionData(Object...) override doesn't find
+ // execution data correctly for tasks.
+ executionData undertowTest.get(), tomcat9Test.get()
+}
// Only run these tests if java 11+ is being used
if (JavaVersion.current().isJava11Compatible()) {
- check.dependsOn(tasks.register('jettyTest', Test) {
+ def jettyTest = tasks.register('jettyTest', Test) {
classpath = sourceSets.jettyTest.runtimeClasspath
testClassesDirs = sourceSets.jettyTest.output.classesDirs
- })
-}
-
-jacocoTestReport {
- executionData undertowTest, tomcat9Test
- if (JavaVersion.current().isJava11Compatible()) {
- executionData jettyTest
+ }
+ tasks.named("check").configure {
+ dependsOn jettyTest
+ }
+ tasks.named("jacocoTestReport").configure {
+ // Must use executionData(Task...) override. The executionData(Object...) override doesn't
+ // find execution data correctly for tasks.
+ executionData jettyTest.get()
}
}
diff --git a/servlet/jakarta/build.gradle b/servlet/jakarta/build.gradle
index 59f5ac78d804..82f11938f832 100644
--- a/servlet/jakarta/build.gradle
+++ b/servlet/jakarta/build.gradle
@@ -4,17 +4,8 @@ plugins {
}
description = "gRPC: Jakarta Servlet"
-sourceCompatibility = 1.8
-targetCompatibility = 1.8
// Set up classpaths and source directories for different servlet tests
-configurations {
- itImplementation.extendsFrom(implementation)
- jettyTestImplementation.extendsFrom(itImplementation)
- tomcatTestImplementation.extendsFrom(itImplementation)
- undertowTestImplementation.extendsFrom(itImplementation)
-}
-
sourceSets {
undertowTest {
java {
@@ -36,50 +27,65 @@ sourceSets {
}
}
+configurations {
+ itImplementation.extendsFrom(implementation)
+ jettyTestImplementation.extendsFrom(itImplementation)
+ tomcatTestImplementation.extendsFrom(itImplementation)
+ undertowTestImplementation.extendsFrom(itImplementation)
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+}
+
// Mechanically transform sources from grpc-servlet to use the corrected packages
def migrate(String name, String inputDir, SourceSet sourceSet) {
def outputDir = layout.buildDirectory.dir('generated/sources/jakarta-' + name)
- sourceSet.java.srcDir outputDir
- return tasks.register('migrateSources' + name.capitalize(), Sync) { task ->
+ sourceSet.java.srcDir tasks.register('migrateSources' + name.capitalize(), Sync) { task ->
into(outputDir)
from("$inputDir/io/grpc/servlet") {
into('io/grpc/servlet/jakarta')
filter { String line ->
- line.replaceAll('javax\\.servlet', 'jakarta.servlet')
- .replaceAll('io\\.grpc\\.servlet', 'io.grpc.servlet.jakarta')
+ line.replace('javax.servlet', 'jakarta.servlet')
+ .replace('io.grpc.servlet', 'io.grpc.servlet.jakarta')
}
}
}
}
-compileJava.dependsOn migrate('main', '../src/main/java', sourceSets.main)
-
-sourcesJar.dependsOn migrateSourcesMain
+migrate('main', '../src/main/java', sourceSets.main)
// Build the set of sourceSets and classpaths to modify, since Jetty 11 requires Java 11
// and must be skipped
-compileUndertowTestJava.dependsOn(migrate('undertowTest', '../src/undertowTest/java', sourceSets.undertowTest))
-compileTomcatTestJava.dependsOn(migrate('tomcatTest', '../src/tomcatTest/java', sourceSets.tomcatTest))
+migrate('undertowTest', '../src/undertowTest/java', sourceSets.undertowTest)
+migrate('tomcatTest', '../src/tomcatTest/java', sourceSets.tomcatTest)
if (JavaVersion.current().isJava11Compatible()) {
- compileJettyTestJava.dependsOn(migrate('jettyTest', '../src/jettyTest/java', sourceSets.jettyTest))
+ migrate('jettyTest', '../src/jettyTest/java', sourceSets.jettyTest)
}
// Disable checkstyle for this project, since it consists only of generated code
-tasks.withType(Checkstyle) {
+tasks.withType(Checkstyle).configureEach {
enabled = false
}
+tasks.named("jar").configure {
+ manifest {
+ attributes('Automatic-Module-Name': 'io.grpc.servlet.jakarta')
+ }
+}
+
dependencies {
api project(':grpc-api')
compileOnly 'jakarta.servlet:jakarta.servlet-api:5.0.0',
libraries.javax.annotation
- implementation project(':grpc-core'),
+ implementation project(':grpc-util'),
libraries.guava
itImplementation project(':grpc-servlet-jakarta'),
project(':grpc-netty'),
- project(':grpc-core').sourceSets.test.runtimeClasspath,
+ testFixtures(project(':grpc-core')),
libraries.junit
itImplementation(project(':grpc-interop-testing')) {
// Avoid grpc-netty-shaded dependency
@@ -97,11 +103,11 @@ dependencies {
// Set up individual classpaths for each test, to avoid any mismatch,
// and ensure they are only used when supported by the current jvm
-check.dependsOn(tasks.register('undertowTest', Test) {
+def undertowTest = tasks.register('undertowTest', Test) {
classpath = sourceSets.undertowTest.runtimeClasspath
testClassesDirs = sourceSets.undertowTest.output.classesDirs
-})
-check.dependsOn(tasks.register('tomcat10Test', Test) {
+}
+def tomcat10Test = tasks.register('tomcat10Test', Test) {
classpath = sourceSets.tomcatTest.runtimeClasspath
testClassesDirs = sourceSets.tomcatTest.output.classesDirs
@@ -118,11 +124,20 @@ check.dependsOn(tasks.register('tomcat10Test', Test) {
if (JavaVersion.current().isJava9Compatible()) {
jvmArgs += ['--add-opens=java.base/java.io=ALL-UNNAMED', '--add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED']
}
-})
+}
+
+tasks.named("check").configure {
+ dependsOn undertowTest, tomcat10Test
+}
+
// Only run these tests if java 11+ is being used
if (JavaVersion.current().isJava11Compatible()) {
- check.dependsOn(tasks.register('jetty11Test', Test) {
+ def jetty11Test = tasks.register('jetty11Test', Test) {
classpath = sourceSets.jettyTest.runtimeClasspath
testClassesDirs = sourceSets.jettyTest.output.classesDirs
- })
+ }
+
+ tasks.named("check").configure {
+ dependsOn jetty11Test
+ }
}
diff --git a/settings.gradle b/settings.gradle
index 2d2c65c89701..8e4e9a0a9287 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,17 +1,26 @@
pluginManagement {
plugins {
+ // https://developer.android.com/build/releases/gradle-plugin
id "com.android.application" version "7.4.0"
id "com.android.library" version "7.4.0"
- id "com.github.johnrengelman.shadow" version "7.1.2"
+ // https://github.com/johnrengelman/shadow/releases
+ id "com.github.johnrengelman.shadow" version "8.1.1"
id "com.github.kt3k.coveralls" version "2.12.2"
- id "com.google.cloud.tools.appengine" version "2.4.4"
- id "com.google.cloud.tools.jib" version "3.3.1"
+ // https://github.com/GoogleCloudPlatform/app-gradle-plugin/releases
+ id "com.google.cloud.tools.appengine" version "2.4.5"
+ // https://github.com/GoogleContainerTools/jib/blob/master/jib-gradle-plugin/CHANGELOG.md
+ id "com.google.cloud.tools.jib" version "3.3.2"
id "com.google.osdetector" version "1.7.3"
- id "com.google.protobuf" version "0.9.3"
+ // https://github.com/google/protobuf-gradle-plugin/releases
+ id "com.google.protobuf" version "0.9.4"
+ // https://github.com/melix/japicmp-gradle-plugin/blob/master/CHANGELOG.txt
id "me.champeau.gradle.japicmp" version "0.4.1"
+ // https://github.com/melix/jmh-gradle-plugin/releases
id "me.champeau.jmh" version "0.7.1"
+ // https://github.com/tbroyer/gradle-errorprone-plugin/releases
id "net.ltgt.errorprone" version "3.1.0"
- id "ru.vyarus.animalsniffer" version "1.7.0"
+ // https://github.com/xvik/gradle-animalsniffer-plugin/releases
+ id "ru.vyarus.animalsniffer" version "1.7.1"
}
resolutionStrategy {
eachPlugin {
@@ -60,6 +69,8 @@ include ":grpc-authz"
include ":grpc-gcp-observability"
include ":grpc-gcp-observability:interop"
include ":grpc-istio-interop-testing"
+include ":grpc-inprocess"
+include ":grpc-util"
project(':grpc-api').projectDir = "$rootDir/api" as File
project(':grpc-core').projectDir = "$rootDir/core" as File
@@ -91,6 +102,8 @@ project(':grpc-authz').projectDir = "$rootDir/authz" as File
project(':grpc-gcp-observability').projectDir = "$rootDir/gcp-observability" as File
project(':grpc-gcp-observability:interop').projectDir = "$rootDir/gcp-observability/interop" as File
project(':grpc-istio-interop-testing').projectDir = "$rootDir/istio-interop-testing" as File
+project(':grpc-inprocess').projectDir = "$rootDir/inprocess" as File
+project(':grpc-util').projectDir = "$rootDir/util" as File
if (settings.hasProperty('skipCodegen') && skipCodegen.toBoolean()) {
println '*** Skipping the build of codegen and compilation of proto files because skipCodegen=true'
diff --git a/stub/build.gradle b/stub/build.gradle
index 16a9ca2d995e..971e476fb33e 100644
--- a/stub/build.gradle
+++ b/stub/build.gradle
@@ -7,6 +7,13 @@ plugins {
}
description = "gRPC: Stub"
+
+tasks.named("jar").configure {
+ manifest {
+ attributes('Automatic-Module-Name': 'io.grpc.stub')
+ }
+}
+
dependencies {
api project(':grpc-api'),
libraries.guava
diff --git a/stub/src/main/java/io/grpc/stub/ClientCalls.java b/stub/src/main/java/io/grpc/stub/ClientCalls.java
index af1d43a47e7a..0eb599d784b5 100644
--- a/stub/src/main/java/io/grpc/stub/ClientCalls.java
+++ b/stub/src/main/java/io/grpc/stub/ClientCalls.java
@@ -136,9 +136,7 @@ public static StreamObserver asyncBidiStreamingCall(
public static RespT blockingUnaryCall(ClientCall call, ReqT req) {
try {
return getUnchecked(futureUnaryCall(call, req));
- } catch (RuntimeException e) {
- throw cancelThrow(call, e);
- } catch (Error e) {
+ } catch (RuntimeException | Error e) {
throw cancelThrow(call, e);
}
}
@@ -170,10 +168,7 @@ public static RespT blockingUnaryCall(
}
executor.shutdown();
return getUnchecked(responseFuture);
- } catch (RuntimeException e) {
- // Something very bad happened. All bets are off; it may be dangerous to wait for onClose().
- throw cancelThrow(call, e);
- } catch (Error e) {
+ } catch (RuntimeException | Error e) {
// Something very bad happened. All bets are off; it may be dangerous to wait for onClose().
throw cancelThrow(call, e);
} finally {
@@ -212,11 +207,10 @@ public static Iterator blockingServerStreamingCall(
*/
public static Iterator blockingServerStreamingCall(
Channel channel, MethodDescriptor method, CallOptions callOptions, ReqT req) {
- ThreadlessExecutor executor = new ThreadlessExecutor();
ClientCall call = channel.newCall(method,
- callOptions.withOption(ClientCalls.STUB_TYPE_OPTION, StubType.BLOCKING)
- .withExecutor(executor));
- BlockingResponseStream result = new BlockingResponseStream<>(call, executor);
+ callOptions.withOption(ClientCalls.STUB_TYPE_OPTION, StubType.BLOCKING));
+
+ BlockingResponseStream result = new BlockingResponseStream<>(call);
asyncUnaryRequestCall(call, req, result.listener());
return result;
}
@@ -361,8 +355,7 @@ private static StatusRuntimeException toStatusRuntimeException(Throwable t) {
private static RuntimeException cancelThrow(ClientCall, ?> call, Throwable t) {
try {
call.cancel(null, t);
- } catch (Throwable e) {
- assert e instanceof RuntimeException || e instanceof Error;
+ } catch (RuntimeException | Error e) {
logger.log(Level.SEVERE, "RuntimeException encountered while closing call", e);
}
if (t instanceof RuntimeException) {
@@ -393,9 +386,7 @@ private static void asyncUnaryRequestCall(
try {
call.sendMessage(req);
call.halfClose();
- } catch (RuntimeException e) {
- throw cancelThrow(call, e);
- } catch (Error e) {
+ } catch (RuntimeException | Error e) {
throw cancelThrow(call, e);
}
}
@@ -670,20 +661,12 @@ private static final class BlockingResponseStream implements Iterator {
private final BlockingQueue buffer = new ArrayBlockingQueue<>(3);
private final StartableListener listener = new QueuingListener();
private final ClientCall, T> call;
- /** May be null. */
- private final ThreadlessExecutor threadless;
// Only accessed when iterating.
private Object last;
// Non private to avoid synthetic class
BlockingResponseStream(ClientCall, T> call) {
- this(call, null);
- }
-
- // Non private to avoid synthetic class
- BlockingResponseStream(ClientCall, T> call, ThreadlessExecutor threadless) {
this.call = call;
- this.threadless = threadless;
}
StartableListener listener() {
@@ -693,31 +676,14 @@ StartableListener listener() {
private Object waitForNext() {
boolean interrupt = false;
try {
- if (threadless == null) {
- while (true) {
- try {
- return buffer.take();
- } catch (InterruptedException ie) {
- interrupt = true;
- call.cancel("Thread interrupted", ie);
- // Now wait for onClose() to be called, to guarantee BlockingQueue doesn't fill
- }
- }
- } else {
- Object next;
- while ((next = buffer.poll()) == null) {
- try {
- threadless.waitAndDrain();
- } catch (InterruptedException ie) {
- interrupt = true;
- call.cancel("Thread interrupted", ie);
- // Now wait for onClose() to be called, so interceptors can clean up
- }
- }
- if (next == this || next instanceof StatusRuntimeException) {
- threadless.shutdown();
+ while (true) {
+ try {
+ return buffer.take();
+ } catch (InterruptedException ie) {
+ interrupt = true;
+ call.cancel("Thread interrupted", ie);
+ // Now wait for onClose() to be called, to guarantee BlockingQueue doesn't fill
}
- return next;
}
} finally {
if (interrupt) {
diff --git a/stub/src/main/java/io/grpc/stub/MetadataUtils.java b/stub/src/main/java/io/grpc/stub/MetadataUtils.java
index 5395ba9b5e3e..addf54c0f813 100644
--- a/stub/src/main/java/io/grpc/stub/MetadataUtils.java
+++ b/stub/src/main/java/io/grpc/stub/MetadataUtils.java
@@ -18,12 +18,10 @@
import static com.google.common.base.Preconditions.checkNotNull;
-import com.google.errorprone.annotations.InlineMe;
import io.grpc.CallOptions;
import io.grpc.Channel;
import io.grpc.ClientCall;
import io.grpc.ClientInterceptor;
-import io.grpc.ExperimentalApi;
import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener;
import io.grpc.Metadata;
@@ -38,24 +36,6 @@ public final class MetadataUtils {
// Prevent instantiation
private MetadataUtils() {}
- /**
- * Attaches a set of request headers to a stub.
- *
- * @param stub to bind the headers to.
- * @param extraHeaders the headers to be passed by each call on the returned stub.
- * @return an implementation of the stub with {@code extraHeaders} bound to each call.
- * @deprecated Use {@code stub.withInterceptors(newAttachHeadersInterceptor(...))} instead.
- */
- @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1789")
- @Deprecated
- @InlineMe(
- replacement =
- "stub.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(extraHeaders))",
- imports = "io.grpc.stub.MetadataUtils")
- public static > T attachHeaders(T stub, Metadata extraHeaders) {
- return stub.withInterceptors(newAttachHeadersInterceptor(extraHeaders));
- }
-
/**
* Returns a client interceptor that attaches a set of headers to requests.
*
@@ -97,31 +77,6 @@ public void start(Listener responseListener, Metadata headers) {
}
}
- /**
- * Captures the last received metadata for a stub. Useful for testing
- *
- * @param stub to capture for
- * @param headersCapture to record the last received headers
- * @param trailersCapture to record the last received trailers
- * @return an implementation of the stub that allows to access the last received call's
- * headers and trailers via {@code headersCapture} and {@code trailersCapture}.
- * @deprecated Use {@code stub.withInterceptors(newCaptureMetadataInterceptor())} instead.
- */
- @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1789")
- @Deprecated
- @InlineMe(
- replacement =
- "stub.withInterceptors(MetadataUtils.newCaptureMetadataInterceptor(headersCapture,"
- + " trailersCapture))",
- imports = "io.grpc.stub.MetadataUtils")
- public static > T captureMetadata(
- T stub,
- AtomicReference headersCapture,
- AtomicReference trailersCapture) {
- return stub.withInterceptors(
- newCaptureMetadataInterceptor(headersCapture, trailersCapture));
- }
-
/**
* Captures the last received metadata on a channel. Useful for testing.
*
diff --git a/testing-proto/build.gradle b/testing-proto/build.gradle
index 168c059e66c7..e6afce468f06 100644
--- a/testing-proto/build.gradle
+++ b/testing-proto/build.gradle
@@ -8,6 +8,12 @@ plugins {
description = "gRPC: Testing Protos"
+tasks.named("jar").configure {
+ manifest {
+ attributes('Automatic-Module-Name': 'io.grpc.testing.protobuf')
+ }
+}
+
dependencies {
api project(':grpc-protobuf'),
project(':grpc-stub')
diff --git a/testing/BUILD.bazel b/testing/BUILD.bazel
index f1e9a5743ed9..071a9650b130 100644
--- a/testing/BUILD.bazel
+++ b/testing/BUILD.bazel
@@ -11,8 +11,8 @@ java_library(
deps = [
"//api",
"//context",
- "//core:inprocess",
- "//core:util",
+ "//inprocess",
+ "//util",
"//stub",
"@com_google_code_findbugs_jsr305//jar",
"@com_google_guava_guava//jar",
diff --git a/testing/build.gradle b/testing/build.gradle
index a2887eac3c8b..43332ba8a7c0 100644
--- a/testing/build.gradle
+++ b/testing/build.gradle
@@ -9,12 +9,13 @@ plugins {
description = "gRPC: Testing"
dependencies {
- api project(':grpc-core'),
+ api project(':grpc-inprocess'),
+ project(':grpc-util'),
project(':grpc-stub'),
libraries.junit
// Only io.grpc.internal.testing.StatsTestUtils depends on opencensus_api, for internal use.
compileOnly libraries.opencensus.api
- runtimeOnly project(":grpc-context") // Pull in newer version than census-api
+ runtimeOnly project(":grpc-api") // Pull in newer version than census-api
testImplementation libraries.mockito.core
diff --git a/testing/src/main/java/io/grpc/testing/GrpcCleanupRule.java b/testing/src/main/java/io/grpc/testing/GrpcCleanupRule.java
index f518dcb9e529..55188e571492 100644
--- a/testing/src/main/java/io/grpc/testing/GrpcCleanupRule.java
+++ b/testing/src/main/java/io/grpc/testing/GrpcCleanupRule.java
@@ -23,7 +23,6 @@
import com.google.common.base.Stopwatch;
import com.google.common.base.Ticker;
import com.google.common.collect.Lists;
-import io.grpc.ExperimentalApi;
import io.grpc.ManagedChannel;
import io.grpc.Server;
import java.util.ArrayList;
@@ -70,7 +69,6 @@
*
* @since 1.13.0
*/
-@ExperimentalApi("https://github.com/grpc/grpc-java/issues/2488")
@NotThreadSafe
public final class GrpcCleanupRule extends ExternalResource {
diff --git a/testing/src/main/java/io/grpc/testing/GrpcServerRule.java b/testing/src/main/java/io/grpc/testing/GrpcServerRule.java
index 71a4ab24c87f..641c9927d911 100644
--- a/testing/src/main/java/io/grpc/testing/GrpcServerRule.java
+++ b/testing/src/main/java/io/grpc/testing/GrpcServerRule.java
@@ -19,7 +19,6 @@
import static com.google.common.base.Preconditions.checkState;
import io.grpc.BindableService;
-import io.grpc.ExperimentalApi;
import io.grpc.ManagedChannel;
import io.grpc.Server;
import io.grpc.ServerServiceDefinition;
@@ -48,7 +47,6 @@
* An {@link AbstractStub} can be created against this service by using the
* {@link ManagedChannel} provided by {@link GrpcServerRule#getChannel()}.
*/
-@ExperimentalApi("https://github.com/grpc/grpc-java/issues/2488")
public final class GrpcServerRule extends ExternalResource {
private ManagedChannel channel;
diff --git a/testing/src/main/java/io/grpc/testing/TestMethodDescriptors.java b/testing/src/main/java/io/grpc/testing/TestMethodDescriptors.java
index b83c323cd5cb..5ffc60e369b4 100644
--- a/testing/src/main/java/io/grpc/testing/TestMethodDescriptors.java
+++ b/testing/src/main/java/io/grpc/testing/TestMethodDescriptors.java
@@ -16,7 +16,6 @@
package io.grpc.testing;
-import io.grpc.ExperimentalApi;
import io.grpc.MethodDescriptor;
import io.grpc.MethodDescriptor.MethodType;
import java.io.ByteArrayInputStream;
@@ -28,7 +27,6 @@
*
* @since 1.1.0
*/
-@ExperimentalApi("https://github.com/grpc/grpc-java/issues/2600")
public final class TestMethodDescriptors {
private TestMethodDescriptors() {}
@@ -38,7 +36,6 @@ private TestMethodDescriptors() {}
*
* @since 1.1.0
*/
- @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2600")
public static MethodDescriptor voidMethod() {
return MethodDescriptor.newBuilder()
.setType(MethodType.UNARY)
@@ -53,7 +50,6 @@ public static MethodDescriptor voidMethod() {
*
* @since 1.1.0
*/
- @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2600")
public static MethodDescriptor.Marshaller voidMarshaller() {
return new NoopMarshaller();
}
diff --git a/util/BUILD.bazel b/util/BUILD.bazel
new file mode 100644
index 000000000000..b95e428f435d
--- /dev/null
+++ b/util/BUILD.bazel
@@ -0,0 +1,18 @@
+java_library(
+ name = "util",
+ srcs = glob([
+ "src/main/java/io/grpc/util/*.java",
+ ]),
+ resources = glob([
+ "src/main/resources/**",
+ ]),
+ visibility = ["//visibility:public"],
+ deps = [
+ "//api",
+ "//core:internal",
+ "@com_google_code_findbugs_jsr305//jar",
+ "@com_google_guava_guava//jar",
+ "@com_google_j2objc_j2objc_annotations//jar",
+ "@org_codehaus_mojo_animal_sniffer_annotations//jar",
+ ],
+)
diff --git a/util/build.gradle b/util/build.gradle
new file mode 100644
index 000000000000..a05c55b27bb2
--- /dev/null
+++ b/util/build.gradle
@@ -0,0 +1,45 @@
+plugins {
+ id "java-library"
+ id "maven-publish"
+
+ id "me.champeau.jmh"
+ id "ru.vyarus.animalsniffer"
+}
+
+description = 'gRPC: Util'
+
+tasks.named("jar").configure {
+ manifest {
+ attributes('Automatic-Module-Name': 'io.grpc.util')
+ }
+}
+
+dependencies {
+ api project(':grpc-core')
+
+ implementation libraries.animalsniffer.annotations,
+ libraries.guava
+ testImplementation testFixtures(project(':grpc-api')),
+ testFixtures(project(':grpc-core')),
+ project(':grpc-testing')
+ testImplementation libraries.guava.testlib
+
+ jmh project(':grpc-testing')
+
+ signature libraries.signature.java
+ signature libraries.signature.android
+}
+
+animalsniffer {
+ // Don't check sourceSets.jmh
+ sourceSets = [
+ sourceSets.main,
+ sourceSets.test
+ ]
+}
+
+tasks.named("javadoc").configure {
+ exclude 'io/grpc/util/MultiChildLoadBalancer.java'
+ exclude 'io/grpc/util/OutlierDetectionLoadBalancer*'
+ exclude 'io/grpc/util/RoundRobinLoadBalancer*'
+}
diff --git a/core/src/jmh/java/io/grpc/util/HandlerRegistryBenchmark.java b/util/src/jmh/java/io/grpc/util/HandlerRegistryBenchmark.java
similarity index 100%
rename from core/src/jmh/java/io/grpc/util/HandlerRegistryBenchmark.java
rename to util/src/jmh/java/io/grpc/util/HandlerRegistryBenchmark.java
diff --git a/core/src/main/java/io/grpc/util/AdvancedTlsX509KeyManager.java b/util/src/main/java/io/grpc/util/AdvancedTlsX509KeyManager.java
similarity index 100%
rename from core/src/main/java/io/grpc/util/AdvancedTlsX509KeyManager.java
rename to util/src/main/java/io/grpc/util/AdvancedTlsX509KeyManager.java
diff --git a/core/src/main/java/io/grpc/util/AdvancedTlsX509TrustManager.java b/util/src/main/java/io/grpc/util/AdvancedTlsX509TrustManager.java
similarity index 100%
rename from core/src/main/java/io/grpc/util/AdvancedTlsX509TrustManager.java
rename to util/src/main/java/io/grpc/util/AdvancedTlsX509TrustManager.java
diff --git a/core/src/main/java/io/grpc/util/CertificateUtils.java b/util/src/main/java/io/grpc/util/CertificateUtils.java
similarity index 100%
rename from core/src/main/java/io/grpc/util/CertificateUtils.java
rename to util/src/main/java/io/grpc/util/CertificateUtils.java
diff --git a/core/src/main/java/io/grpc/util/ForwardingClientStreamTracer.java b/util/src/main/java/io/grpc/util/ForwardingClientStreamTracer.java
similarity index 100%
rename from core/src/main/java/io/grpc/util/ForwardingClientStreamTracer.java
rename to util/src/main/java/io/grpc/util/ForwardingClientStreamTracer.java
diff --git a/core/src/main/java/io/grpc/util/ForwardingLoadBalancer.java b/util/src/main/java/io/grpc/util/ForwardingLoadBalancer.java
similarity index 100%
rename from core/src/main/java/io/grpc/util/ForwardingLoadBalancer.java
rename to util/src/main/java/io/grpc/util/ForwardingLoadBalancer.java
diff --git a/core/src/main/java/io/grpc/util/ForwardingLoadBalancerHelper.java b/util/src/main/java/io/grpc/util/ForwardingLoadBalancerHelper.java
similarity index 100%
rename from core/src/main/java/io/grpc/util/ForwardingLoadBalancerHelper.java
rename to util/src/main/java/io/grpc/util/ForwardingLoadBalancerHelper.java
diff --git a/core/src/main/java/io/grpc/util/ForwardingSubchannel.java b/util/src/main/java/io/grpc/util/ForwardingSubchannel.java
similarity index 100%
rename from core/src/main/java/io/grpc/util/ForwardingSubchannel.java
rename to util/src/main/java/io/grpc/util/ForwardingSubchannel.java
diff --git a/core/src/main/java/io/grpc/util/GracefulSwitchLoadBalancer.java b/util/src/main/java/io/grpc/util/GracefulSwitchLoadBalancer.java
similarity index 100%
rename from core/src/main/java/io/grpc/util/GracefulSwitchLoadBalancer.java
rename to util/src/main/java/io/grpc/util/GracefulSwitchLoadBalancer.java
diff --git a/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java
new file mode 100644
index 000000000000..be0a23a16422
--- /dev/null
+++ b/util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2023 The gRPC Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.grpc.util;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static io.grpc.ConnectivityState.CONNECTING;
+import static io.grpc.ConnectivityState.IDLE;
+import static io.grpc.ConnectivityState.READY;
+import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.ConnectivityState;
+import io.grpc.Internal;
+import io.grpc.LoadBalancer;
+import io.grpc.LoadBalancerProvider;
+import io.grpc.Status;
+import io.grpc.SynchronizationContext;
+import io.grpc.SynchronizationContext.ScheduledHandle;
+import io.grpc.internal.ServiceConfigUtil.PolicySelection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+/**
+ * A base load balancing policy for those policies which has multiple children such as
+ * ClusterManager or the petiole policies. For internal use only.
+ */
+@Internal
+public abstract class MultiChildLoadBalancer extends LoadBalancer {
+
+ @VisibleForTesting
+ public static final int DELAYED_CHILD_DELETION_TIME_MINUTES = 15;
+ private static final Logger logger = Logger.getLogger(MultiChildLoadBalancer.class.getName());
+ private final Map childLbStates = new HashMap<>();
+ private final Helper helper;
+ protected final SynchronizationContext syncContext;
+ private final ScheduledExecutorService timeService;
+ // Set to true if currently in the process of handling resolved addresses.
+ private boolean resolvingAddresses;
+
+ protected MultiChildLoadBalancer(Helper helper) {
+ this.helper = checkNotNull(helper, "helper");
+ this.syncContext = checkNotNull(helper.getSynchronizationContext(), "syncContext");
+ this.timeService = checkNotNull(helper.getScheduledExecutorService(), "timeService");
+ logger.log(Level.FINE, "Created");
+ }
+
+ protected SubchannelPicker getInitialPicker() {
+ return EMPTY_PICKER;
+ }
+
+ protected SubchannelPicker getErrorPicker(Status error) {
+ return new ErrorPicker(error);
+ }
+
+ protected abstract Map getPolicySelectionMap(
+ ResolvedAddresses resolvedAddresses);
+
+ protected abstract SubchannelPicker getSubchannelPicker(
+ Map childPickers);
+
+ @Override
+ public boolean acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
+ try {
+ resolvingAddresses = true;
+ return acceptResolvedAddressesInternal(resolvedAddresses);
+ } finally {
+ resolvingAddresses = false;
+ }
+ }
+
+ private boolean acceptResolvedAddressesInternal(ResolvedAddresses resolvedAddresses) {
+ logger.log(Level.FINE, "Received resolution result: {0}", resolvedAddresses);
+ Map newChildPolicies = getPolicySelectionMap(resolvedAddresses);
+ for (Map.Entry entry : newChildPolicies.entrySet()) {
+ final Object key = entry.getKey();
+ LoadBalancerProvider childPolicyProvider = entry.getValue().getProvider();
+ Object childConfig = entry.getValue().getConfig();
+ if (!childLbStates.containsKey(key)) {
+ childLbStates.put(key, new ChildLbState(key, childPolicyProvider, getInitialPicker()));
+ } else {
+ childLbStates.get(key).reactivate(childPolicyProvider);
+ }
+ LoadBalancer childLb = childLbStates.get(key).lb;
+ ResolvedAddresses childAddresses =
+ resolvedAddresses.toBuilder().setLoadBalancingPolicyConfig(childConfig).build();
+ childLb.handleResolvedAddresses(childAddresses);
+ }
+ for (Object key : childLbStates.keySet()) {
+ if (!newChildPolicies.containsKey(key)) {
+ childLbStates.get(key).deactivate();
+ }
+ }
+ // Must update channel picker before return so that new RPCs will not be routed to deleted
+ // clusters and resolver can remove them in service config.
+ updateOverallBalancingState();
+ return true;
+ }
+
+ @Override
+ public void handleNameResolutionError(Status error) {
+ logger.log(Level.WARNING, "Received name resolution error: {0}", error);
+ boolean gotoTransientFailure = true;
+ for (ChildLbState state : childLbStates.values()) {
+ if (!state.deactivated) {
+ gotoTransientFailure = false;
+ state.lb.handleNameResolutionError(error);
+ }
+ }
+ if (gotoTransientFailure) {
+ helper.updateBalancingState(TRANSIENT_FAILURE, getErrorPicker(error));
+ }
+ }
+
+ @Override
+ public void shutdown() {
+ logger.log(Level.INFO, "Shutdown");
+ for (ChildLbState state : childLbStates.values()) {
+ state.shutdown();
+ }
+ childLbStates.clear();
+ }
+
+ private void updateOverallBalancingState() {
+ ConnectivityState overallState = null;
+ final Map childPickers = new HashMap<>();
+ for (ChildLbState childLbState : childLbStates.values()) {
+ if (childLbState.deactivated) {
+ continue;
+ }
+ childPickers.put(childLbState.key, childLbState.currentPicker);
+ overallState = aggregateState(overallState, childLbState.currentState);
+ }
+ if (overallState != null) {
+ helper.updateBalancingState(overallState, getSubchannelPicker(childPickers));
+ }
+ }
+
+ @Nullable
+ private static ConnectivityState aggregateState(
+ @Nullable ConnectivityState overallState, ConnectivityState childState) {
+ if (overallState == null) {
+ return childState;
+ }
+ if (overallState == READY || childState == READY) {
+ return READY;
+ }
+ if (overallState == CONNECTING || childState == CONNECTING) {
+ return CONNECTING;
+ }
+ if (overallState == IDLE || childState == IDLE) {
+ return IDLE;
+ }
+ return overallState;
+ }
+
+ private final class ChildLbState {
+ private final Object key;
+ private final GracefulSwitchLoadBalancer lb;
+ private LoadBalancerProvider policyProvider;
+ private ConnectivityState currentState = CONNECTING;
+ private SubchannelPicker currentPicker;
+ private boolean deactivated;
+ @Nullable
+ ScheduledHandle deletionTimer;
+
+ ChildLbState(Object key, LoadBalancerProvider policyProvider, SubchannelPicker initialPicker) {
+ this.key = key;
+ this.policyProvider = policyProvider;
+ lb = new GracefulSwitchLoadBalancer(new ChildLbStateHelper());
+ lb.switchTo(policyProvider);
+ currentPicker = initialPicker;
+ }
+
+ void deactivate() {
+ if (deactivated) {
+ return;
+ }
+
+ class DeletionTask implements Runnable {
+ @Override
+ public void run() {
+ shutdown();
+ childLbStates.remove(key);
+ }
+ }
+
+ deletionTimer =
+ syncContext.schedule(
+ new DeletionTask(),
+ DELAYED_CHILD_DELETION_TIME_MINUTES,
+ TimeUnit.MINUTES,
+ timeService);
+ deactivated = true;
+ logger.log(Level.FINE, "Child balancer {0} deactivated", key);
+ }
+
+ void reactivate(LoadBalancerProvider policyProvider) {
+ if (deletionTimer != null && deletionTimer.isPending()) {
+ deletionTimer.cancel();
+ deactivated = false;
+ logger.log(Level.FINE, "Child balancer {0} reactivated", key);
+ }
+ if (!this.policyProvider.getPolicyName().equals(policyProvider.getPolicyName())) {
+ Object[] objects = {
+ key, this.policyProvider.getPolicyName(),policyProvider.getPolicyName()};
+ logger.log(Level.FINE, "Child balancer {0} switching policy from {1} to {2}", objects);
+ lb.switchTo(policyProvider);
+ this.policyProvider = policyProvider;
+ }
+ }
+
+ void shutdown() {
+ if (deletionTimer != null && deletionTimer.isPending()) {
+ deletionTimer.cancel();
+ }
+ lb.shutdown();
+ logger.log(Level.FINE, "Child balancer {0} deleted", key);
+ }
+
+ private final class ChildLbStateHelper extends ForwardingLoadBalancerHelper {
+
+ @Override
+ public void updateBalancingState(final ConnectivityState newState,
+ final SubchannelPicker newPicker) {
+ // If we are already in the process of resolving addresses, the overall balancing state
+ // will be updated at the end of it, and we don't need to trigger that update here.
+ if (!childLbStates.containsKey(key)) {
+ return;
+ }
+ // Subchannel picker and state are saved, but will only be propagated to the channel
+ // when the child instance exits deactivated state.
+ currentState = newState;
+ currentPicker = newPicker;
+ if (!deactivated && !resolvingAddresses) {
+ updateOverallBalancingState();
+ }
+ }
+
+ @Override
+ protected Helper delegate() {
+ return helper;
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/io/grpc/util/MutableHandlerRegistry.java b/util/src/main/java/io/grpc/util/MutableHandlerRegistry.java
similarity index 96%
rename from core/src/main/java/io/grpc/util/MutableHandlerRegistry.java
rename to util/src/main/java/io/grpc/util/MutableHandlerRegistry.java
index c31102f4213d..307142b642c1 100644
--- a/core/src/main/java/io/grpc/util/MutableHandlerRegistry.java
+++ b/util/src/main/java/io/grpc/util/MutableHandlerRegistry.java
@@ -31,13 +31,12 @@
import javax.annotation.concurrent.ThreadSafe;
/**
- * Default implementation of {@link MutableHandlerRegistry}.
+ * Default implementation of {@link HandlerRegistry}.
*
* Uses {@link ConcurrentHashMap} to avoid service registration excessively
* blocking method lookup.
*/
@ThreadSafe
-@ExperimentalApi("https://github.com/grpc/grpc-java/issues/933")
public final class MutableHandlerRegistry extends HandlerRegistry {
private final ConcurrentMap services
= new ConcurrentHashMap<>();
diff --git a/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java b/util/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java
similarity index 96%
rename from core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java
rename to util/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java
index a1a4b4be2826..bd8825474fa6 100644
--- a/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java
+++ b/util/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java
@@ -394,47 +394,55 @@ public PickResult pickSubchannel(PickSubchannelArgs args) {
Subchannel subchannel = pickResult.getSubchannel();
if (subchannel != null) {
- return PickResult.withSubchannel(subchannel,
- new ResultCountingClientStreamTracerFactory(
- subchannel.getAttributes().get(ADDRESS_TRACKER_ATTR_KEY)));
+ return PickResult.withSubchannel(subchannel, new ResultCountingClientStreamTracerFactory(
+ subchannel.getAttributes().get(ADDRESS_TRACKER_ATTR_KEY),
+ pickResult.getStreamTracerFactory()));
}
return pickResult;
}
/**
- * Builds instances of {@link ResultCountingClientStreamTracer}.
+ * Builds instances of a {@link ClientStreamTracer} that increments the call count in the
+ * tracker for each closed stream.
*/
class ResultCountingClientStreamTracerFactory extends ClientStreamTracer.Factory {
private final AddressTracker tracker;
- ResultCountingClientStreamTracerFactory(AddressTracker tracker) {
- this.tracker = tracker;
- }
-
- @Override
- public ClientStreamTracer newClientStreamTracer(StreamInfo info, Metadata headers) {
- return new ResultCountingClientStreamTracer(tracker);
- }
- }
+ @Nullable
+ private final ClientStreamTracer.Factory delegateFactory;
- /**
- * Counts the results (successful/unsuccessful) of a particular {@link
- * OutlierDetectionSubchannel}s streams and increments the counter in the associated {@link
- * AddressTracker}.
- */
- class ResultCountingClientStreamTracer extends ClientStreamTracer {
-
- AddressTracker tracker;
-
- public ResultCountingClientStreamTracer(AddressTracker tracker) {
+ ResultCountingClientStreamTracerFactory(AddressTracker tracker,
+ @Nullable ClientStreamTracer.Factory delegateFactory) {
this.tracker = tracker;
+ this.delegateFactory = delegateFactory;
}
@Override
- public void streamClosed(Status status) {
- tracker.incrementCallCount(status.isOk());
+ public ClientStreamTracer newClientStreamTracer(StreamInfo info, Metadata headers) {
+ if (delegateFactory != null) {
+ ClientStreamTracer delegateTracer = delegateFactory.newClientStreamTracer(info, headers);
+ return new ForwardingClientStreamTracer() {
+ @Override
+ protected ClientStreamTracer delegate() {
+ return delegateTracer;
+ }
+
+ @Override
+ public void streamClosed(Status status) {
+ tracker.incrementCallCount(status.isOk());
+ delegate().streamClosed(status);
+ }
+ };
+ } else {
+ return new ClientStreamTracer() {
+ @Override
+ public void streamClosed(Status status) {
+ tracker.incrementCallCount(status.isOk());
+ }
+ };
+ }
}
}
}
diff --git a/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancerProvider.java b/util/src/main/java/io/grpc/util/OutlierDetectionLoadBalancerProvider.java
similarity index 100%
rename from core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancerProvider.java
rename to util/src/main/java/io/grpc/util/OutlierDetectionLoadBalancerProvider.java
diff --git a/core/src/main/java/io/grpc/util/RoundRobinLoadBalancer.java b/util/src/main/java/io/grpc/util/RoundRobinLoadBalancer.java
similarity index 100%
rename from core/src/main/java/io/grpc/util/RoundRobinLoadBalancer.java
rename to util/src/main/java/io/grpc/util/RoundRobinLoadBalancer.java
diff --git a/core/src/main/java/io/grpc/util/SecretRoundRobinLoadBalancerProvider.java b/util/src/main/java/io/grpc/util/SecretRoundRobinLoadBalancerProvider.java
similarity index 100%
rename from core/src/main/java/io/grpc/util/SecretRoundRobinLoadBalancerProvider.java
rename to util/src/main/java/io/grpc/util/SecretRoundRobinLoadBalancerProvider.java
diff --git a/core/src/main/java/io/grpc/util/TransmitStatusRuntimeExceptionInterceptor.java b/util/src/main/java/io/grpc/util/TransmitStatusRuntimeExceptionInterceptor.java
similarity index 100%
rename from core/src/main/java/io/grpc/util/TransmitStatusRuntimeExceptionInterceptor.java
rename to util/src/main/java/io/grpc/util/TransmitStatusRuntimeExceptionInterceptor.java
diff --git a/core/src/main/java/io/grpc/util/package-info.java b/util/src/main/java/io/grpc/util/package-info.java
similarity index 100%
rename from core/src/main/java/io/grpc/util/package-info.java
rename to util/src/main/java/io/grpc/util/package-info.java
diff --git a/core/src/bazel-util/resources/META-INF/services/io.grpc.LoadBalancerProvider b/util/src/main/resources/META-INF/services/io.grpc.LoadBalancerProvider
similarity index 54%
rename from core/src/bazel-util/resources/META-INF/services/io.grpc.LoadBalancerProvider
rename to util/src/main/resources/META-INF/services/io.grpc.LoadBalancerProvider
index 4c21dd2e1bfc..1fdd69cb00b6 100644
--- a/core/src/bazel-util/resources/META-INF/services/io.grpc.LoadBalancerProvider
+++ b/util/src/main/resources/META-INF/services/io.grpc.LoadBalancerProvider
@@ -1 +1,2 @@
io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider
+io.grpc.util.OutlierDetectionLoadBalancerProvider
diff --git a/core/src/test/java/io/grpc/util/CertificateUtilsTest.java b/util/src/test/java/io/grpc/util/CertificateUtilsTest.java
similarity index 100%
rename from core/src/test/java/io/grpc/util/CertificateUtilsTest.java
rename to util/src/test/java/io/grpc/util/CertificateUtilsTest.java
diff --git a/core/src/test/java/io/grpc/util/ForwardingClientStreamTracerTest.java b/util/src/test/java/io/grpc/util/ForwardingClientStreamTracerTest.java
similarity index 100%
rename from core/src/test/java/io/grpc/util/ForwardingClientStreamTracerTest.java
rename to util/src/test/java/io/grpc/util/ForwardingClientStreamTracerTest.java
diff --git a/core/src/test/java/io/grpc/util/ForwardingLoadBalancerHelperTest.java b/util/src/test/java/io/grpc/util/ForwardingLoadBalancerHelperTest.java
similarity index 100%
rename from core/src/test/java/io/grpc/util/ForwardingLoadBalancerHelperTest.java
rename to util/src/test/java/io/grpc/util/ForwardingLoadBalancerHelperTest.java
diff --git a/core/src/test/java/io/grpc/util/ForwardingLoadBalancerTest.java b/util/src/test/java/io/grpc/util/ForwardingLoadBalancerTest.java
similarity index 100%
rename from core/src/test/java/io/grpc/util/ForwardingLoadBalancerTest.java
rename to util/src/test/java/io/grpc/util/ForwardingLoadBalancerTest.java
diff --git a/core/src/test/java/io/grpc/util/ForwardingSubchannelTest.java b/util/src/test/java/io/grpc/util/ForwardingSubchannelTest.java
similarity index 100%
rename from core/src/test/java/io/grpc/util/ForwardingSubchannelTest.java
rename to util/src/test/java/io/grpc/util/ForwardingSubchannelTest.java
diff --git a/core/src/test/java/io/grpc/util/GracefulSwitchLoadBalancerTest.java b/util/src/test/java/io/grpc/util/GracefulSwitchLoadBalancerTest.java
similarity index 100%
rename from core/src/test/java/io/grpc/util/GracefulSwitchLoadBalancerTest.java
rename to util/src/test/java/io/grpc/util/GracefulSwitchLoadBalancerTest.java
diff --git a/core/src/test/java/io/grpc/util/MutableHandlerRegistryTest.java b/util/src/test/java/io/grpc/util/MutableHandlerRegistryTest.java
similarity index 100%
rename from core/src/test/java/io/grpc/util/MutableHandlerRegistryTest.java
rename to util/src/test/java/io/grpc/util/MutableHandlerRegistryTest.java
diff --git a/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerProviderTest.java b/util/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerProviderTest.java
similarity index 100%
rename from core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerProviderTest.java
rename to util/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerProviderTest.java
diff --git a/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java b/util/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java
similarity index 92%
rename from core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java
rename to util/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java
index 18f9bbf549f7..13f13421a1ea 100644
--- a/core/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java
+++ b/util/src/test/java/io/grpc/util/OutlierDetectionLoadBalancerTest.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
@@ -46,6 +47,7 @@
import io.grpc.LoadBalancer.SubchannelPicker;
import io.grpc.LoadBalancer.SubchannelStateListener;
import io.grpc.LoadBalancerProvider;
+import io.grpc.Metadata;
import io.grpc.Status;
import io.grpc.SynchronizationContext;
import io.grpc.internal.FakeClock;
@@ -96,6 +98,10 @@ public class OutlierDetectionLoadBalancerTest {
private Helper mockHelper;
@Mock
private SocketAddress mockSocketAddress;
+ @Mock
+ private ClientStreamTracer.Factory mockStreamTracerFactory;
+ @Mock
+ private ClientStreamTracer mockStreamTracer;
@Captor
private ArgumentCaptor connectivityStateCaptor;
@@ -193,6 +199,9 @@ public Void answer(InvocationOnMock invocation) throws Throwable {
}
});
+ when(mockStreamTracerFactory.newClientStreamTracer(any(),
+ any())).thenReturn(mockStreamTracer);
+
loadBalancer = new OutlierDetectionLoadBalancer(mockHelper, fakeClock.getTimeProvider());
}
@@ -355,6 +364,72 @@ public void delegatePick() throws Exception {
readySubchannel);
}
+ /**
+ * Any ClientStreamTracer.Factory set by the delegate picker should still get used.
+ */
+ @Test
+ public void delegatePickTracerFactoryPreserved() throws Exception {
+ OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder()
+ .setSuccessRateEjection(new SuccessRateEjection.Builder().build())
+ .setChildPolicy(new PolicySelection(fakeLbProvider, null)).build();
+
+ loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers.get(0)));
+
+ // Make one of the subchannels READY.
+ final Subchannel readySubchannel = subchannels.values().iterator().next();
+ deliverSubchannelState(readySubchannel, ConnectivityStateInfo.forNonError(READY));
+
+ verify(mockHelper, times(2)).updateBalancingState(stateCaptor.capture(),
+ pickerCaptor.capture());
+
+ // Make sure that we can pick the single READY subchannel.
+ SubchannelPicker picker = pickerCaptor.getAllValues().get(1);
+ PickResult pickResult = picker.pickSubchannel(mock(PickSubchannelArgs.class));
+
+ // Calls to a stream tracer created with the factory in the result should make it to a stream
+ // tracer the underlying LB/picker is using.
+ ClientStreamTracer clientStreamTracer = pickResult.getStreamTracerFactory()
+ .newClientStreamTracer(ClientStreamTracer.StreamInfo.newBuilder().build(), new Metadata());
+ clientStreamTracer.inboundHeaders();
+ // The underlying fake LB provider is configured with a factory that returns a mock stream
+ // tracer.
+ verify(mockStreamTracer).inboundHeaders();
+ }
+
+ /**
+ * Assure the tracer works even when the underlying LB does not have a tracer to delegate to.
+ */
+ @Test
+ public void delegatePickTracerFactoryNotSet() throws Exception {
+ // We set the mock factory to null to indicate that the delegate does not have its own tracer.
+ mockStreamTracerFactory = null;
+
+ OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder()
+ .setSuccessRateEjection(new SuccessRateEjection.Builder().build())
+ .setChildPolicy(new PolicySelection(fakeLbProvider, null)).build();
+
+ loadBalancer.acceptResolvedAddresses(buildResolvedAddress(config, servers.get(0)));
+
+ // Make one of the subchannels READY.
+ final Subchannel readySubchannel = subchannels.values().iterator().next();
+ deliverSubchannelState(readySubchannel, ConnectivityStateInfo.forNonError(READY));
+
+ verify(mockHelper, times(2)).updateBalancingState(stateCaptor.capture(),
+ pickerCaptor.capture());
+
+ // Make sure that we can pick the single READY subchannel.
+ SubchannelPicker picker = pickerCaptor.getAllValues().get(1);
+ PickResult pickResult = picker.pickSubchannel(mock(PickSubchannelArgs.class));
+
+ // With no delegate tracers factory a call to the OD tracer should still work
+ ClientStreamTracer clientStreamTracer = pickResult.getStreamTracerFactory()
+ .newClientStreamTracer(ClientStreamTracer.StreamInfo.newBuilder().build(), new Metadata());
+ clientStreamTracer.inboundHeaders();
+
+ // Sanity check to make sure the delegate tracer does not get called.
+ verifyNoInteractions(mockStreamTracer);
+ }
+
/**
* The success rate algorithm leaves a healthy set of addresses alone.
*/
@@ -1121,7 +1196,7 @@ void assertEjectedSubchannels(Set addresses) {
}
/** Round robin like fake load balancer. */
- private static final class FakeLoadBalancer extends LoadBalancer {
+ private final class FakeLoadBalancer extends LoadBalancer {
private final Helper helper;
List subchannelList;
@@ -1159,7 +1234,8 @@ public PickResult pickSubchannel(PickSubchannelArgs args) {
if (lastPickIndex < 0 || lastPickIndex > subchannelList.size() - 1) {
lastPickIndex = 0;
}
- return PickResult.withSubchannel(subchannelList.get(lastPickIndex++));
+ return PickResult.withSubchannel(subchannelList.get(lastPickIndex++),
+ mockStreamTracerFactory);
}
};
helper.updateBalancingState(state, picker);
diff --git a/core/src/test/java/io/grpc/util/RoundRobinLoadBalancerTest.java b/util/src/test/java/io/grpc/util/RoundRobinLoadBalancerTest.java
similarity index 100%
rename from core/src/test/java/io/grpc/util/RoundRobinLoadBalancerTest.java
rename to util/src/test/java/io/grpc/util/RoundRobinLoadBalancerTest.java
diff --git a/core/src/test/java/io/grpc/util/UtilServerInterceptorsTest.java b/util/src/test/java/io/grpc/util/UtilServerInterceptorsTest.java
similarity index 100%
rename from core/src/test/java/io/grpc/util/UtilServerInterceptorsTest.java
rename to util/src/test/java/io/grpc/util/UtilServerInterceptorsTest.java
diff --git a/xds/BUILD.bazel b/xds/BUILD.bazel
index d2c57fde0155..d3b746e39fad 100644
--- a/xds/BUILD.bazel
+++ b/xds/BUILD.bazel
@@ -38,7 +38,7 @@ java_library(
"//api",
"//context",
"//core:internal",
- "//core:util",
+ "//util",
"//netty",
"//stub",
"//services:metrics",
@@ -145,7 +145,7 @@ java_library(
"//api",
"//context",
"//core:internal",
- "//core:util",
+ "//util",
"//protobuf",
"//services:metrics",
"//services:metrics_internal",
diff --git a/xds/build.gradle b/xds/build.gradle
index 60e30c5102c5..3f3cf6a0f6e1 100644
--- a/xds/build.gradle
+++ b/xds/build.gradle
@@ -140,6 +140,9 @@ tasks.named("compileJava").configure {
tasks.named("jar").configure {
archiveClassifier = 'original'
+ manifest {
+ attributes('Automatic-Module-Name': 'io.grpc.xds')
+ }
}
tasks.named("sourcesJar").configure {
@@ -159,6 +162,7 @@ tasks.named("javadoc").configure {
exclude 'io/grpc/xds/FilterChainMatchingProtocolNegotiators.java'
exclude 'io/grpc/xds/TlsContextManager.java'
exclude 'io/grpc/xds/XdsAttributes.java'
+ exclude 'io/grpc/xds/XdsCredentialsProvider.java'
exclude 'io/grpc/xds/XdsInitializationException.java'
exclude 'io/grpc/xds/XdsNameResolverProvider.java'
exclude 'io/grpc/xds/internal/**'
diff --git a/xds/src/main/java/io/grpc/xds/BootstrapperImpl.java b/xds/src/main/java/io/grpc/xds/BootstrapperImpl.java
index e8a450e8df72..4aa008c112d4 100644
--- a/xds/src/main/java/io/grpc/xds/BootstrapperImpl.java
+++ b/xds/src/main/java/io/grpc/xds/BootstrapperImpl.java
@@ -169,7 +169,7 @@ BootstrapInfo bootstrap(Map rawData) throws XdsInitializationExceptio
if (rawLocality.containsKey("sub_zone")) {
subZone = JsonUtil.getString(rawLocality, "sub_zone");
}
- logger.log(XdsLogLevel.INFO, "Locality region: {0}, zone: {0}, subZone: {0}",
+ logger.log(XdsLogLevel.INFO, "Locality region: {0}, zone: {1}, subZone: {2}",
region, zone, subZone);
Locality locality = Locality.create(region, zone, subZone);
nodeBuilder.setLocality(locality);
diff --git a/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java b/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java
index a640bbd78b94..7257fdfd16b2 100644
--- a/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java
+++ b/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java
@@ -39,7 +39,6 @@
import io.grpc.xds.XdsClusterResource.CdsUpdate;
import io.grpc.xds.XdsClusterResource.CdsUpdate.ClusterType;
import io.grpc.xds.XdsLogger.XdsLogLevel;
-import io.grpc.xds.XdsSubchannelPickers.ErrorPicker;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
diff --git a/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java
index 074bb301b581..95ca1a33e153 100644
--- a/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java
+++ b/xds/src/main/java/io/grpc/xds/ClusterImplLoadBalancer.java
@@ -17,7 +17,6 @@
package io.grpc.xds;
import static com.google.common.base.Preconditions.checkNotNull;
-import static io.grpc.xds.XdsSubchannelPickers.BUFFER_PICKER;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
@@ -33,6 +32,7 @@
import io.grpc.Status;
import io.grpc.internal.ForwardingClientStreamTracer;
import io.grpc.internal.ObjectPool;
+import io.grpc.services.MetricReport;
import io.grpc.util.ForwardingLoadBalancerHelper;
import io.grpc.util.ForwardingSubchannel;
import io.grpc.util.GracefulSwitchLoadBalancer;
@@ -45,8 +45,9 @@
import io.grpc.xds.ThreadSafeRandom.ThreadSafeRandomImpl;
import io.grpc.xds.XdsLogger.XdsLogLevel;
import io.grpc.xds.XdsNameResolverProvider.CallCounterProvider;
-import io.grpc.xds.XdsSubchannelPickers.ErrorPicker;
import io.grpc.xds.internal.security.SslContextProviderSupplier;
+import io.grpc.xds.orca.OrcaPerRequestUtil;
+import io.grpc.xds.orca.OrcaPerRequestUtil.OrcaPerRequestReportListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -170,7 +171,7 @@ public void shutdown() {
private final class ClusterImplLbHelper extends ForwardingLoadBalancerHelper {
private final AtomicLong inFlights;
private ConnectivityState currentState = ConnectivityState.IDLE;
- private SubchannelPicker currentPicker = BUFFER_PICKER;
+ private SubchannelPicker currentPicker = LoadBalancer.EMPTY_PICKER;
private List dropPolicies = Collections.emptyList();
private long maxConcurrentRequests = DEFAULT_PER_CLUSTER_MAX_CONCURRENT_REQUESTS;
@Nullable
@@ -329,7 +330,9 @@ public PickResult pickSubchannel(PickSubchannelArgs args) {
if (stats != null) {
ClientStreamTracer.Factory tracerFactory = new CountingStreamTracerFactory(
stats, inFlights, result.getStreamTracerFactory());
- return PickResult.withSubchannel(result.getSubchannel(), tracerFactory);
+ ClientStreamTracer.Factory orcaTracerFactory = OrcaPerRequestUtil.getInstance()
+ .newOrcaClientStreamTracerFactory(tracerFactory, new OrcaPerRpcListener(stats));
+ return PickResult.withSubchannel(result.getSubchannel(), orcaTracerFactory);
}
}
return result;
@@ -386,4 +389,22 @@ public void streamClosed(Status status) {
};
}
}
+
+ private static final class OrcaPerRpcListener implements OrcaPerRequestReportListener {
+
+ private final ClusterLocalityStats stats;
+
+ private OrcaPerRpcListener(ClusterLocalityStats stats) {
+ this.stats = checkNotNull(stats, "stats");
+ }
+
+ /**
+ * Copies {@link MetricReport#getNamedMetrics()} to {@link ClusterLocalityStats} such that it is
+ * included in the snapshot for the LRS report sent to the LRS server.
+ */
+ @Override
+ public void onLoadReport(MetricReport report) {
+ stats.recordBackendLoadMetricStats(report.getNamedMetrics());
+ }
+ }
}
diff --git a/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java
index cce32c68246a..a4489204236e 100644
--- a/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java
+++ b/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java
@@ -16,266 +16,63 @@
package io.grpc.xds;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static io.grpc.ConnectivityState.CONNECTING;
-import static io.grpc.ConnectivityState.IDLE;
-import static io.grpc.ConnectivityState.READY;
-import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
-import static io.grpc.xds.XdsSubchannelPickers.BUFFER_PICKER;
-
-import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
-import io.grpc.ConnectivityState;
import io.grpc.InternalLogId;
-import io.grpc.LoadBalancer;
-import io.grpc.LoadBalancerProvider;
import io.grpc.Status;
-import io.grpc.SynchronizationContext;
-import io.grpc.SynchronizationContext.ScheduledHandle;
import io.grpc.internal.ServiceConfigUtil.PolicySelection;
-import io.grpc.util.ForwardingLoadBalancerHelper;
-import io.grpc.util.GracefulSwitchLoadBalancer;
+import io.grpc.util.MultiChildLoadBalancer;
import io.grpc.xds.ClusterManagerLoadBalancerProvider.ClusterManagerConfig;
import io.grpc.xds.XdsLogger.XdsLogLevel;
-import io.grpc.xds.XdsSubchannelPickers.ErrorPicker;
import java.util.HashMap;
import java.util.Map;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import javax.annotation.Nullable;
/**
* The top-level load balancing policy.
*/
-class ClusterManagerLoadBalancer extends LoadBalancer {
-
- @VisibleForTesting
- static final int DELAYED_CHILD_DELETION_TIME_MINUTES = 15;
+class ClusterManagerLoadBalancer extends MultiChildLoadBalancer {
- private final Map childLbStates = new HashMap<>();
- private final Helper helper;
- private final SynchronizationContext syncContext;
- private final ScheduledExecutorService timeService;
private final XdsLogger logger;
- // Set to true if currently in the process of handling resolved addresses.
- private boolean resolvingAddresses;
ClusterManagerLoadBalancer(Helper helper) {
- this.helper = checkNotNull(helper, "helper");
- this.syncContext = checkNotNull(helper.getSynchronizationContext(), "syncContext");
- this.timeService = checkNotNull(helper.getScheduledExecutorService(), "timeService");
+ super(helper);
logger = XdsLogger.withLogId(
InternalLogId.allocate("cluster_manager-lb", helper.getAuthority()));
logger.log(XdsLogLevel.INFO, "Created");
}
@Override
- public boolean acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
- try {
- resolvingAddresses = true;
- return acceptResolvedAddressesInternal(resolvedAddresses);
- } finally {
- resolvingAddresses = false;
- }
- }
-
- public boolean acceptResolvedAddressesInternal(ResolvedAddresses resolvedAddresses) {
- logger.log(XdsLogLevel.DEBUG, "Received resolution result: {0}", resolvedAddresses);
+ protected Map getPolicySelectionMap(
+ ResolvedAddresses resolvedAddresses) {
ClusterManagerConfig config = (ClusterManagerConfig)
resolvedAddresses.getLoadBalancingPolicyConfig();
- Map newChildPolicies = config.childPolicies;
+ Map newChildPolicies = new HashMap<>(config.childPolicies);
logger.log(
XdsLogLevel.INFO,
"Received cluster_manager lb config: child names={0}", newChildPolicies.keySet());
- for (Map.Entry entry : newChildPolicies.entrySet()) {
- final String name = entry.getKey();
- LoadBalancerProvider childPolicyProvider = entry.getValue().getProvider();
- Object childConfig = entry.getValue().getConfig();
- if (!childLbStates.containsKey(name)) {
- childLbStates.put(name, new ChildLbState(name, childPolicyProvider));
- } else {
- childLbStates.get(name).reactivate(childPolicyProvider);
- }
- LoadBalancer childLb = childLbStates.get(name).lb;
- ResolvedAddresses childAddresses =
- resolvedAddresses.toBuilder().setLoadBalancingPolicyConfig(childConfig).build();
- childLb.handleResolvedAddresses(childAddresses);
- }
- for (String name : childLbStates.keySet()) {
- if (!newChildPolicies.containsKey(name)) {
- childLbStates.get(name).deactivate();
- }
- }
- // Must update channel picker before return so that new RPCs will not be routed to deleted
- // clusters and resolver can remove them in service config.
- updateOverallBalancingState();
- return true;
- }
-
- @Override
- public void handleNameResolutionError(Status error) {
- logger.log(XdsLogLevel.WARNING, "Received name resolution error: {0}", error);
- boolean gotoTransientFailure = true;
- for (ChildLbState state : childLbStates.values()) {
- if (!state.deactivated) {
- gotoTransientFailure = false;
- state.lb.handleNameResolutionError(error);
- }
- }
- if (gotoTransientFailure) {
- helper.updateBalancingState(TRANSIENT_FAILURE, new ErrorPicker(error));
- }
+ return newChildPolicies;
}
@Override
- public void shutdown() {
- logger.log(XdsLogLevel.INFO, "Shutdown");
- for (ChildLbState state : childLbStates.values()) {
- state.shutdown();
- }
- childLbStates.clear();
- }
-
- private void updateOverallBalancingState() {
- ConnectivityState overallState = null;
- final Map childPickers = new HashMap<>();
- for (ChildLbState childLbState : childLbStates.values()) {
- if (childLbState.deactivated) {
- continue;
- }
- childPickers.put(childLbState.name, childLbState.currentPicker);
- overallState = aggregateState(overallState, childLbState.currentState);
- }
- if (overallState != null) {
- SubchannelPicker picker = new SubchannelPicker() {
- @Override
- public PickResult pickSubchannel(PickSubchannelArgs args) {
- String clusterName =
- args.getCallOptions().getOption(XdsNameResolver.CLUSTER_SELECTION_KEY);
- SubchannelPicker delegate = childPickers.get(clusterName);
- if (delegate == null) {
- return
- PickResult.withError(
- Status.UNAVAILABLE.withDescription("CDS encountered error: unable to find "
- + "available subchannel for cluster " + clusterName));
- }
- return delegate.pickSubchannel(args);
- }
-
- @Override
- public String toString() {
- return MoreObjects.toStringHelper(this).add("pickers", childPickers).toString();
- }
- };
- helper.updateBalancingState(overallState, picker);
- }
- }
-
- @Nullable
- private static ConnectivityState aggregateState(
- @Nullable ConnectivityState overallState, ConnectivityState childState) {
- if (overallState == null) {
- return childState;
- }
- if (overallState == READY || childState == READY) {
- return READY;
- }
- if (overallState == CONNECTING || childState == CONNECTING) {
- return CONNECTING;
- }
- if (overallState == IDLE || childState == IDLE) {
- return IDLE;
- }
- return overallState;
- }
-
- private final class ChildLbState {
- private final String name;
- private final GracefulSwitchLoadBalancer lb;
- private LoadBalancerProvider policyProvider;
- private ConnectivityState currentState = CONNECTING;
- private SubchannelPicker currentPicker = BUFFER_PICKER;
- private boolean deactivated;
- @Nullable
- ScheduledHandle deletionTimer;
-
- ChildLbState(String name, LoadBalancerProvider policyProvider) {
- this.name = name;
- this.policyProvider = policyProvider;
- lb = new GracefulSwitchLoadBalancer(new ChildLbStateHelper());
- lb.switchTo(policyProvider);
- }
-
- void deactivate() {
- if (deactivated) {
- return;
- }
-
- class DeletionTask implements Runnable {
- @Override
- public void run() {
- shutdown();
- childLbStates.remove(name);
- }
- }
-
- deletionTimer =
- syncContext.schedule(
- new DeletionTask(),
- DELAYED_CHILD_DELETION_TIME_MINUTES,
- TimeUnit.MINUTES,
- timeService);
- deactivated = true;
- logger.log(XdsLogLevel.DEBUG, "Child balancer {0} deactivated", name);
- }
-
- void reactivate(LoadBalancerProvider policyProvider) {
- if (deletionTimer != null && deletionTimer.isPending()) {
- deletionTimer.cancel();
- deactivated = false;
- logger.log(XdsLogLevel.DEBUG, "Child balancer {0} reactivated", name);
- }
- if (!this.policyProvider.getPolicyName().equals(policyProvider.getPolicyName())) {
- logger.log(
- XdsLogLevel.DEBUG,
- "Child balancer {0} switching policy from {1} to {2}",
- name, this.policyProvider.getPolicyName(), policyProvider.getPolicyName());
- lb.switchTo(policyProvider);
- this.policyProvider = policyProvider;
- }
- }
-
- void shutdown() {
- if (deletionTimer != null && deletionTimer.isPending()) {
- deletionTimer.cancel();
- }
- lb.shutdown();
- logger.log(XdsLogLevel.DEBUG, "Child balancer {0} deleted", name);
- }
-
- private final class ChildLbStateHelper extends ForwardingLoadBalancerHelper {
-
+ protected SubchannelPicker getSubchannelPicker(Map childPickers) {
+ return new SubchannelPicker() {
@Override
- public void updateBalancingState(final ConnectivityState newState,
- final SubchannelPicker newPicker) {
- // If we are already in the process of resolving addresses, the overall balancing state
- // will be updated at the end of it, and we don't need to trigger that update here.
- if (!childLbStates.containsKey(name)) {
- return;
- }
- // Subchannel picker and state are saved, but will only be propagated to the channel
- // when the child instance exits deactivated state.
- currentState = newState;
- currentPicker = newPicker;
- if (!deactivated && !resolvingAddresses) {
- updateOverallBalancingState();
+ public PickResult pickSubchannel(PickSubchannelArgs args) {
+ String clusterName =
+ args.getCallOptions().getOption(XdsNameResolver.CLUSTER_SELECTION_KEY);
+ SubchannelPicker childPicker = childPickers.get(clusterName);
+ if (childPicker == null) {
+ return
+ PickResult.withError(
+ Status.UNAVAILABLE.withDescription("CDS encountered error: unable to find "
+ + "available subchannel for cluster " + clusterName));
}
+ return childPicker.pickSubchannel(args);
}
@Override
- protected Helper delegate() {
- return helper;
+ public String toString() {
+ return MoreObjects.toStringHelper(this).add("pickers", childPickers).toString();
}
- }
+ };
}
}
diff --git a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java
index 3af58ef93cb5..a7564e89a8cd 100644
--- a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java
+++ b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java
@@ -55,7 +55,6 @@
import io.grpc.xds.XdsClient.ResourceWatcher;
import io.grpc.xds.XdsEndpointResource.EdsUpdate;
import io.grpc.xds.XdsLogger.XdsLogLevel;
-import io.grpc.xds.XdsSubchannelPickers.ErrorPicker;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
diff --git a/xds/src/main/java/io/grpc/xds/ControlPlaneClient.java b/xds/src/main/java/io/grpc/xds/ControlPlaneClient.java
index 5a3400c751a4..c919365d0935 100644
--- a/xds/src/main/java/io/grpc/xds/ControlPlaneClient.java
+++ b/xds/src/main/java/io/grpc/xds/ControlPlaneClient.java
@@ -383,8 +383,9 @@ public boolean isReady() {
void start() {
AggregatedDiscoveryServiceGrpc.AggregatedDiscoveryServiceStub stub =
AggregatedDiscoveryServiceGrpc.newStub(channel);
- StreamObserver responseReader =
- new ClientResponseObserver() {
+
+ final class AdsClientResponseObserver
+ implements ClientResponseObserver {
@Override
public void beforeStart(ClientCallStreamObserver requestStream) {
@@ -434,8 +435,9 @@ public void run() {
}
});
}
- };
- requestWriter = stub.streamAggregatedResources(responseReader);
+ }
+
+ requestWriter = stub.streamAggregatedResources(new AdsClientResponseObserver());
}
@Override
diff --git a/xds/src/main/java/io/grpc/xds/LoadReportClient.java b/xds/src/main/java/io/grpc/xds/LoadReportClient.java
index 9daa440a3dc7..b86c8110f636 100644
--- a/xds/src/main/java/io/grpc/xds/LoadReportClient.java
+++ b/xds/src/main/java/io/grpc/xds/LoadReportClient.java
@@ -45,6 +45,7 @@
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
import javax.annotation.Nullable;
/**
@@ -361,7 +362,16 @@ private io.envoyproxy.envoy.config.endpoint.v3.ClusterStats buildClusterStats(
.setTotalSuccessfulRequests(upstreamLocalityStats.totalSuccessfulRequests())
.setTotalErrorRequests(upstreamLocalityStats.totalErrorRequests())
.setTotalRequestsInProgress(upstreamLocalityStats.totalRequestsInProgress())
- .setTotalIssuedRequests(upstreamLocalityStats.totalIssuedRequests()));
+ .setTotalIssuedRequests(upstreamLocalityStats.totalIssuedRequests())
+ .addAllLoadMetricStats(
+ upstreamLocalityStats.loadMetricStatsMap().entrySet().stream().map(
+ e -> io.envoyproxy.envoy.config.endpoint.v3.EndpointLoadMetricStats.newBuilder()
+ .setMetricName(e.getKey())
+ .setNumRequestsFinishedWithMetric(
+ e.getValue().numRequestsFinishedWithMetric())
+ .setTotalMetricValue(e.getValue().totalMetricValue())
+ .build())
+ .collect(Collectors.toList())));
}
for (DroppedRequests droppedRequests : stats.droppedRequestsList()) {
builder.addDroppedRequests(
diff --git a/xds/src/main/java/io/grpc/xds/LoadStatsManager2.java b/xds/src/main/java/io/grpc/xds/LoadStatsManager2.java
index 4bd0ba437be1..e51c6eceeb57 100644
--- a/xds/src/main/java/io/grpc/xds/LoadStatsManager2.java
+++ b/xds/src/main/java/io/grpc/xds/LoadStatsManager2.java
@@ -23,6 +23,7 @@
import com.google.common.base.Supplier;
import com.google.common.collect.Sets;
import io.grpc.Status;
+import io.grpc.xds.Stats.BackendLoadMetricStats;
import io.grpc.xds.Stats.ClusterStats;
import io.grpc.xds.Stats.DroppedRequests;
import io.grpc.xds.Stats.UpstreamLocalityStats;
@@ -197,7 +198,7 @@ synchronized List getClusterStatsReports(String cluster) {
}
UpstreamLocalityStats upstreamLocalityStats = UpstreamLocalityStats.create(
locality, snapshot.callsIssued, snapshot.callsSucceeded, snapshot.callsFailed,
- snapshot.callsInProgress);
+ snapshot.callsInProgress, snapshot.loadMetricStatsMap);
builder.addUpstreamLocalityStats(upstreamLocalityStats);
// Use the max (drops/loads) recording interval as the overall interval for the
// cluster's stats. In general, they should be mostly identical.
@@ -322,6 +323,7 @@ final class ClusterLocalityStats {
private final AtomicLong callsSucceeded = new AtomicLong();
private final AtomicLong callsFailed = new AtomicLong();
private final AtomicLong callsIssued = new AtomicLong();
+ private Map loadMetricStatsMap = new HashMap<>();
private ClusterLocalityStats(
String clusterName, @Nullable String edsServiceName, Locality locality,
@@ -353,6 +355,23 @@ void recordCallFinished(Status status) {
}
}
+ /**
+ * Records all custom named backend load metric stats for per-call load reporting. For each
+ * metric key {@code name}, creates a new {@link BackendLoadMetricStats} with a finished
+ * requests counter of 1 and the {@code value} if the key is not present in the map. Otherwise,
+ * increments the finished requests counter and adds the {@code value} to the existing
+ * {@link BackendLoadMetricStats}.
+ */
+ synchronized void recordBackendLoadMetricStats(Map namedMetrics) {
+ namedMetrics.forEach((name, value) -> {
+ if (!loadMetricStatsMap.containsKey(name)) {
+ loadMetricStatsMap.put(name, new BackendLoadMetricStats(1, value));
+ } else {
+ loadMetricStatsMap.get(name).addMetricValueAndIncrementRequestsFinished(value);
+ }
+ });
+ }
+
/**
* Release the hard reference for this stats object (previously obtained via {@link
* LoadStatsManager2#getClusterLocalityStats}). The object may still be
@@ -367,8 +386,13 @@ void release() {
private ClusterLocalityStatsSnapshot snapshot() {
long duration = stopwatch.elapsed(TimeUnit.NANOSECONDS);
stopwatch.reset().start();
+ Map loadMetricStatsMapCopy;
+ synchronized (this) {
+ loadMetricStatsMapCopy = Collections.unmodifiableMap(loadMetricStatsMap);
+ loadMetricStatsMap = new HashMap<>();
+ }
return new ClusterLocalityStatsSnapshot(callsSucceeded.getAndSet(0), callsInProgress.get(),
- callsFailed.getAndSet(0), callsIssued.getAndSet(0), duration);
+ callsFailed.getAndSet(0), callsIssued.getAndSet(0), duration, loadMetricStatsMapCopy);
}
}
@@ -378,15 +402,18 @@ private static final class ClusterLocalityStatsSnapshot {
private final long callsFailed;
private final long callsIssued;
private final long durationNano;
+ private final Map loadMetricStatsMap;
private ClusterLocalityStatsSnapshot(
long callsSucceeded, long callsInProgress, long callsFailed, long callsIssued,
- long durationNano) {
+ long durationNano, Map loadMetricStatsMap) {
this.callsSucceeded = callsSucceeded;
this.callsInProgress = callsInProgress;
this.callsFailed = callsFailed;
this.callsIssued = callsIssued;
this.durationNano = durationNano;
+ this.loadMetricStatsMap = Collections.unmodifiableMap(
+ checkNotNull(loadMetricStatsMap, "loadMetricStatsMap"));
}
}
}
diff --git a/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java b/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java
index e833b3777b82..5cf543175656 100644
--- a/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java
+++ b/xds/src/main/java/io/grpc/xds/PriorityLoadBalancer.java
@@ -21,7 +21,6 @@
import static io.grpc.ConnectivityState.IDLE;
import static io.grpc.ConnectivityState.READY;
import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
-import static io.grpc.xds.XdsSubchannelPickers.BUFFER_PICKER;
import io.grpc.ConnectivityState;
import io.grpc.InternalLogId;
@@ -36,7 +35,6 @@
import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig;
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;
@@ -149,7 +147,7 @@ private void tryNextPriority() {
ChildLbState child =
new ChildLbState(priority, priorityConfigs.get(priority).ignoreReresolution);
children.put(priority, child);
- updateOverallState(priority, CONNECTING, BUFFER_PICKER);
+ updateOverallState(priority, CONNECTING, LoadBalancer.EMPTY_PICKER);
// Calling the child's updateResolvedAddresses() can result in tryNextPriority() being
// called recursively. We need to be sure to be done with processing here before it is
// called.
@@ -210,7 +208,7 @@ private final class ChildLbState {
@Nullable ScheduledHandle deletionTimer;
@Nullable String policy;
ConnectivityState connectivityState = CONNECTING;
- SubchannelPicker picker = BUFFER_PICKER;
+ SubchannelPicker picker = LoadBalancer.EMPTY_PICKER;
ChildLbState(final String priority, boolean ignoreReresolution) {
this.priority = priority;
diff --git a/xds/src/main/java/io/grpc/xds/RbacFilter.java b/xds/src/main/java/io/grpc/xds/RbacFilter.java
index 0f5ac1025bab..6a55f7f193ed 100644
--- a/xds/src/main/java/io/grpc/xds/RbacFilter.java
+++ b/xds/src/main/java/io/grpc/xds/RbacFilter.java
@@ -59,8 +59,10 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
+import java.util.stream.Collectors;
import javax.annotation.Nullable;
/** RBAC Http filter implementation. */
@@ -117,9 +119,12 @@ static ConfigOrError parseRbacConfig(RBAC rbac) {
default:
return ConfigOrError.fromError("Unknown rbacConfig action type: " + rbacConfig.getAction());
}
- Map policyMap = rbacConfig.getPoliciesMap();
List policyMatchers = new ArrayList<>();
- for (Map.Entry entry: policyMap.entrySet()) {
+ List> sortedPolicyEntries = rbacConfig.getPoliciesMap().entrySet()
+ .stream()
+ .sorted((a,b) -> a.getKey().compareTo(b.getKey()))
+ .collect(Collectors.toList());
+ for (Map.Entry entry: sortedPolicyEntries) {
try {
Policy policy = entry.getValue();
if (policy.hasCondition() || policy.hasCheckedCondition()) {
diff --git a/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java b/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java
index 436eca8ec5dc..20a70cb0322f 100644
--- a/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java
+++ b/xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java
@@ -39,7 +39,6 @@
import io.grpc.Status;
import io.grpc.SynchronizationContext;
import io.grpc.xds.XdsLogger.XdsLogLevel;
-import io.grpc.xds.XdsSubchannelPickers.ErrorPicker;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Collections;
diff --git a/xds/src/main/java/io/grpc/xds/Stats.java b/xds/src/main/java/io/grpc/xds/Stats.java
index 7e5fa8639d4d..5953f088448a 100644
--- a/xds/src/main/java/io/grpc/xds/Stats.java
+++ b/xds/src/main/java/io/grpc/xds/Stats.java
@@ -18,6 +18,8 @@
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
import javax.annotation.Nullable;
/** Represents client load stats. */
@@ -101,10 +103,45 @@ abstract static class UpstreamLocalityStats {
abstract long totalRequestsInProgress();
+ abstract ImmutableMap loadMetricStatsMap();
+
static UpstreamLocalityStats create(Locality locality, long totalIssuedRequests,
- long totalSuccessfulRequests, long totalErrorRequests, long totalRequestsInProgress) {
+ long totalSuccessfulRequests, long totalErrorRequests, long totalRequestsInProgress,
+ Map loadMetricStatsMap) {
return new AutoValue_Stats_UpstreamLocalityStats(locality, totalIssuedRequests,
- totalSuccessfulRequests, totalErrorRequests, totalRequestsInProgress);
+ totalSuccessfulRequests, totalErrorRequests, totalRequestsInProgress,
+ ImmutableMap.copyOf(loadMetricStatsMap));
+ }
+ }
+
+ /**
+ * Load metric stats for multi-dimensional load balancing.
+ */
+ static final class BackendLoadMetricStats {
+
+ private long numRequestsFinishedWithMetric;
+ private double totalMetricValue;
+
+ BackendLoadMetricStats(long numRequestsFinishedWithMetric, double totalMetricValue) {
+ this.numRequestsFinishedWithMetric = numRequestsFinishedWithMetric;
+ this.totalMetricValue = totalMetricValue;
+ }
+
+ public long numRequestsFinishedWithMetric() {
+ return numRequestsFinishedWithMetric;
+ }
+
+ public double totalMetricValue() {
+ return totalMetricValue;
+ }
+
+ /**
+ * Adds the given {@code metricValue} and increments the number of requests finished counter for
+ * the existing {@link BackendLoadMetricStats}.
+ */
+ public void addMetricValueAndIncrementRequestsFinished(double metricValue) {
+ numRequestsFinishedWithMetric += 1;
+ totalMetricValue += metricValue;
}
}
}
diff --git a/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java b/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java
index 48442a84b22d..833683729c22 100644
--- a/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java
+++ b/xds/src/main/java/io/grpc/xds/WeightedRoundRobinLoadBalancer.java
@@ -44,10 +44,10 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.PriorityQueue;
import java.util.Random;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -65,7 +65,7 @@ final class WeightedRoundRobinLoadBalancer extends RoundRobinLoadBalancer {
private final ScheduledExecutorService timeService;
private ScheduledHandle weightUpdateTimer;
private final Runnable updateWeightTask;
- private final Random random;
+ private final AtomicInteger sequence;
private final long infTime;
private final Ticker ticker;
@@ -81,7 +81,7 @@ public WeightedRoundRobinLoadBalancer(WrrHelper helper, Ticker ticker, Random ra
this.syncContext = checkNotNull(helper.getSynchronizationContext(), "syncContext");
this.timeService = checkNotNull(helper.getScheduledExecutorService(), "timeService");
this.updateWeightTask = new UpdateWeightTask();
- this.random = random;
+ this.sequence = new AtomicInteger(random.nextInt());
log.log(Level.FINE, "weighted_round_robin LB created");
}
@@ -120,7 +120,7 @@ private final class UpdateWeightTask implements Runnable {
@Override
public void run() {
if (currentPicker != null && currentPicker instanceof WeightedRoundRobinPicker) {
- ((WeightedRoundRobinPicker)currentPicker).updateWeight();
+ ((WeightedRoundRobinPicker) currentPicker).updateWeight();
}
weightUpdateTimer = syncContext.schedule(this, config.weightUpdatePeriodNanos,
TimeUnit.NANOSECONDS, timeService);
@@ -258,7 +258,7 @@ final class WeightedRoundRobinPicker extends RoundRobinPicker {
new HashMap<>();
private final boolean enableOobLoadReport;
private final float errorUtilizationPenalty;
- private volatile EdfScheduler scheduler;
+ private volatile StaticStrideScheduler scheduler;
WeightedRoundRobinPicker(List list, boolean enableOobLoadReport,
float errorUtilizationPenalty) {
@@ -279,7 +279,7 @@ public PickResult pickSubchannel(PickSubchannelArgs args) {
Subchannel subchannel = list.get(scheduler.pick());
if (!enableOobLoadReport) {
return PickResult.withSubchannel(subchannel,
- OrcaPerRequestUtil.getInstance().newOrcaClientStreamTracerFactory(
+ OrcaPerRequestUtil.getInstance().newOrcaClientStreamTracerFactory(
subchannelToReportListenerMap.getOrDefault(subchannel,
((WrrSubchannel) subchannel).new OrcaReportListener(errorUtilizationPenalty))));
} else {
@@ -288,27 +288,13 @@ public PickResult pickSubchannel(PickSubchannelArgs args) {
}
private void updateWeight() {
- int weightedChannelCount = 0;
- double avgWeight = 0;
- for (Subchannel value : list) {
- double newWeight = ((WrrSubchannel) value).getWeight();
- if (newWeight > 0) {
- avgWeight += newWeight;
- weightedChannelCount++;
- }
- }
- EdfScheduler scheduler = new EdfScheduler(list.size(), random);
- if (weightedChannelCount >= 1) {
- avgWeight /= 1.0 * weightedChannelCount;
- } else {
- avgWeight = 1;
- }
+ float[] newWeights = new float[list.size()];
for (int i = 0; i < list.size(); i++) {
WrrSubchannel subchannel = (WrrSubchannel) list.get(i);
double newWeight = subchannel.getWeight();
- scheduler.add(i, newWeight > 0 ? newWeight : avgWeight);
+ newWeights[i] = newWeight > 0 ? (float) newWeight : 0.0f;
}
- this.scheduler = scheduler;
+ this.scheduler = new StaticStrideScheduler(newWeights, sequence);
}
@Override
@@ -340,111 +326,146 @@ public boolean isEquivalentTo(RoundRobinPicker picker) {
}
}
- /**
- * The earliest deadline first implementation in which each object is
- * chosen deterministically and periodically with frequency proportional to its weight.
- *
- * Specifically, each object added to chooser is given a deadline equal to the multiplicative
- * inverse of its weight. The place of each object in its deadline is tracked, and each call to
- * choose returns the object with the least remaining time in its deadline.
- * (Ties are broken by the order in which the children were added to the chooser.) The deadline
- * advances by the multiplicative inverse of the object's weight.
- * For example, if items A and B are added with weights 0.5 and 0.2, successive chooses return:
+ /*
+ * The Static Stride Scheduler is an implementation of an earliest deadline first (EDF) scheduler
+ * in which each object's deadline is the multiplicative inverse of the object's weight.
+ *
+ * The way in which this is implemented is through a static stride scheduler.
+ * The Static Stride Scheduler works by iterating through the list of subchannel weights
+ * and using modular arithmetic to proportionally distribute picks, favoring entries
+ * with higher weights. It is based on the observation that the intended sequence generated
+ * from an EDF scheduler is a periodic one that can be achieved through modular arithmetic.
+ * The Static Stride Scheduler is more performant than other implementations of the EDF
+ * Scheduler, as it removes the need for a priority queue (and thus mutex locks).
+ *
+ * go/static-stride-scheduler
+ *
*
*
- * In the first call, the deadlines are A=2 (1/0.5) and B=5 (1/0.2), so A is returned.
- * The deadline of A is updated to 4.
- * Next, the remaining deadlines are A=4 and B=5, so A is returned. The deadline of A (2) is
- * updated to A=6.
- * Remaining deadlines are A=6 and B=5, so B is returned. The deadline of B is updated with
- * with B=10.
- * Remaining deadlines are A=6 and B=10, so A is returned. The deadline of A is updated with
- * A=8.
- * Remaining deadlines are A=8 and B=10, so A is returned. The deadline of A is updated with
- * A=10.
- * Remaining deadlines are A=10 and B=10, so A is returned. The deadline of A is updated
- * with A=12.
- * Remaining deadlines are A=12 and B=10, so B is returned. The deadline of B is updated
- * with B=15.
- * etc.
- *
- *
- * In short: the entry with the highest weight is preferred.
- *
- *
- * add() - O(lg n)
- * pick() - O(lg n)
- *
- *
+ * nextSequence() - O(1)
+ * pick() - O(n)
*/
@VisibleForTesting
- static final class EdfScheduler {
- private final PriorityQueue prioQueue;
-
- /**
- * Weights below this value will be upped to this minimum weight.
- */
- private static final double MINIMUM_WEIGHT = 0.0001;
-
- private final Object lock = new Object();
+ static final class StaticStrideScheduler {
+ private final short[] scaledWeights;
+ private final AtomicInteger sequence;
+ private static final int K_MAX_WEIGHT = 0xFFFF;
+
+ // Assuming the mean of all known weights is M, StaticStrideScheduler will clamp
+ // weights bigger than M*kMaxRatio and weights smaller than M*kMinRatio.
+ //
+ // This is done as a performance optimization by limiting the number of rounds for picks
+ // for edge cases where channels have large differences in subchannel weights.
+ // In this case, without these clips, it would potentially require the scheduler to
+ // frequently traverse through the entire subchannel list within the pick method.
+ //
+ // The current values of 10 and 0.1 were chosen without any experimenting. It should
+ // decrease the amount of sequences that the scheduler must traverse through in order
+ // to pick a high weight subchannel in such corner cases.
+ // But, it also makes WeightedRoundRobin to send slightly more requests to
+ // potentially very bad tasks (that would have near-zero weights) than zero.
+ // This is not necessarily a downside, though. Perhaps this is not a problem at
+ // all, and we can increase this value if needed to save CPU cycles.
+ private static final double K_MAX_RATIO = 10;
+ private static final double K_MIN_RATIO = 0.1;
+
+ StaticStrideScheduler(float[] weights, AtomicInteger sequence) {
+ checkArgument(weights.length >= 1, "Couldn't build scheduler: requires at least one weight");
+ int numChannels = weights.length;
+ int numWeightedChannels = 0;
+ double sumWeight = 0;
+ double unscaledMeanWeight;
+ float unscaledMaxWeight = 0;
+ for (float weight : weights) {
+ if (weight > 0) {
+ sumWeight += weight;
+ unscaledMaxWeight = Math.max(weight, unscaledMaxWeight);
+ numWeightedChannels++;
+ }
+ }
- private final Random random;
+ // Adjust max value s.t. ratio does not exceed K_MAX_RATIO. This should
+ // ensure that we on average do at most K_MAX_RATIO rounds for picks.
+ if (numWeightedChannels > 0) {
+ unscaledMeanWeight = sumWeight / numWeightedChannels;
+ unscaledMaxWeight = Math.min(unscaledMaxWeight, (float) (K_MAX_RATIO * unscaledMeanWeight));
+ } else {
+ // Fall back to round robin if all values are non-positives
+ unscaledMeanWeight = 1;
+ unscaledMaxWeight = 1;
+ }
- /**
- * Use the item's deadline as the order in the priority queue. If the deadlines are the same,
- * use the index. Index should be unique.
- */
- EdfScheduler(int initialCapacity, Random random) {
- this.prioQueue = new PriorityQueue(initialCapacity, (o1, o2) -> {
- if (o1.deadline == o2.deadline) {
- return Integer.compare(o1.index, o2.index);
+ // Scales weights s.t. max(weights) == K_MAX_WEIGHT, meanWeight is scaled accordingly.
+ // Note that, since we cap the weights to stay within K_MAX_RATIO, meanWeight might not
+ // match the actual mean of the values that end up in the scheduler.
+ double scalingFactor = K_MAX_WEIGHT / unscaledMaxWeight;
+ // We compute weightLowerBound and clamp it to 1 from below so that in the
+ // worst case, we represent tiny weights as 1.
+ int weightLowerBound = (int) Math.ceil(scalingFactor * unscaledMeanWeight * K_MIN_RATIO);
+ short[] scaledWeights = new short[numChannels];
+ for (int i = 0; i < numChannels; i++) {
+ if (weights[i] <= 0) {
+ scaledWeights[i] = (short) Math.round(scalingFactor * unscaledMeanWeight);
} else {
- return Double.compare(o1.deadline, o2.deadline);
+ int weight = (int) Math.round(scalingFactor * Math.min(weights[i], unscaledMaxWeight));
+ scaledWeights[i] = (short) Math.max(weight, weightLowerBound);
}
- });
- this.random = random;
+ }
+
+ this.scaledWeights = scaledWeights;
+ this.sequence = sequence;
}
- /**
- * Adds the item in the scheduler. This is not thread safe.
- *
- * @param index The field {@link ObjectState#index} to be added
- * @param weight positive weight for the added object
- */
- void add(int index, double weight) {
- checkArgument(weight > 0.0, "Weights need to be positive.");
- ObjectState state = new ObjectState(Math.max(weight, MINIMUM_WEIGHT), index);
- // Randomize the initial deadline.
- state.deadline = random.nextDouble() * (1 / state.weight);
- prioQueue.add(state);
+ /** Returns the next sequence number and atomically increases sequence with wraparound. */
+ private long nextSequence() {
+ return Integer.toUnsignedLong(sequence.getAndIncrement());
}
- /**
- * Picks the next WRR object.
+ /*
+ * Selects index of next backend server.
+ *
+ * A 2D array is compactly represented as a function of W(backend), where the row
+ * represents the generation and the column represents the backend index:
+ * X(backend,generation) | generation ∈ [0,kMaxWeight).
+ * Each element in the conceptual array is a boolean indicating whether the backend at
+ * this index should be picked now. If false, the counter is incremented again,
+ * and the new element is checked. An atomically incremented counter keeps track of our
+ * backend and generation through modular arithmetic within the pick() method.
+ *
+ * Modular arithmetic allows us to evenly distribute picks and skips between
+ * generations based on W(backend).
+ * X(backend,generation) = (W(backend) * generation) % kMaxWeight >= kMaxWeight - W(backend)
+ * If we have the same three backends with weights:
+ * W(backend) = {2,3,6} scaled to max(W(backend)) = 6, then X(backend,generation) is:
+ *
+ * B0 B1 B2
+ * T T T
+ * F F T
+ * F T T
+ * T F T
+ * F T T
+ * F F T
+ * The sequence of picked backend indices is given by
+ * walking across and down: {0,1,2,2,1,2,0,2,1,2,2}.
+ *
+ * To reduce the variance and spread the wasted work among different picks,
+ * an offset that varies per backend index is also included to the calculation.
*/
int pick() {
- synchronized (lock) {
- ObjectState minObject = prioQueue.remove();
- minObject.deadline += 1.0 / minObject.weight;
- prioQueue.add(minObject);
- return minObject.index;
+ while (true) {
+ long sequence = this.nextSequence();
+ int backendIndex = (int) (sequence % scaledWeights.length);
+ long generation = sequence / scaledWeights.length;
+ int weight = Short.toUnsignedInt(scaledWeights[backendIndex]);
+ long offset = (long) K_MAX_WEIGHT / 2 * backendIndex;
+ if ((weight * generation + offset) % K_MAX_WEIGHT < K_MAX_WEIGHT - weight) {
+ continue;
+ }
+ return backendIndex;
}
}
}
- /** Holds the state of the object. */
- @VisibleForTesting
- static class ObjectState {
- private final double weight;
- private final int index;
- private volatile double deadline;
-
- ObjectState(double weight, int index) {
- this.weight = weight;
- this.index = index;
- }
- }
-
static final class WeightedRoundRobinLoadBalancerConfig {
final long blackoutPeriodNanos;
final long weightExpirationPeriodNanos;
diff --git a/xds/src/main/java/io/grpc/xds/WeightedTargetLoadBalancer.java b/xds/src/main/java/io/grpc/xds/WeightedTargetLoadBalancer.java
index 825e4a8eca0c..596247b8234b 100644
--- a/xds/src/main/java/io/grpc/xds/WeightedTargetLoadBalancer.java
+++ b/xds/src/main/java/io/grpc/xds/WeightedTargetLoadBalancer.java
@@ -21,7 +21,6 @@
import static io.grpc.ConnectivityState.IDLE;
import static io.grpc.ConnectivityState.READY;
import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
-import static io.grpc.xds.XdsSubchannelPickers.BUFFER_PICKER;
import com.google.common.collect.ImmutableMap;
import io.grpc.ConnectivityState;
@@ -34,7 +33,6 @@
import io.grpc.xds.WeightedTargetLoadBalancerProvider.WeightedPolicySelection;
import io.grpc.xds.WeightedTargetLoadBalancerProvider.WeightedTargetConfig;
import io.grpc.xds.XdsLogger.XdsLogLevel;
-import io.grpc.xds.XdsSubchannelPickers.ErrorPicker;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -159,7 +157,7 @@ private void updateOverallBalancingState() {
if (overallState == TRANSIENT_FAILURE) {
picker = new WeightedRandomPicker(errorPickers);
} else {
- picker = XdsSubchannelPickers.BUFFER_PICKER;
+ picker = LoadBalancer.EMPTY_PICKER;
}
} else {
picker = new WeightedRandomPicker(childPickers);
@@ -191,7 +189,7 @@ private static ConnectivityState aggregateState(
private final class ChildHelper extends ForwardingLoadBalancerHelper {
String name;
ConnectivityState currentState = CONNECTING;
- SubchannelPicker currentPicker = BUFFER_PICKER;
+ SubchannelPicker currentPicker = LoadBalancer.EMPTY_PICKER;
private ChildHelper(String name) {
this.name = name;
diff --git a/xds/src/main/java/io/grpc/xds/WrrLocalityLoadBalancer.java b/xds/src/main/java/io/grpc/xds/WrrLocalityLoadBalancer.java
index b9196492624f..885844f1cbfc 100644
--- a/xds/src/main/java/io/grpc/xds/WrrLocalityLoadBalancer.java
+++ b/xds/src/main/java/io/grpc/xds/WrrLocalityLoadBalancer.java
@@ -32,7 +32,6 @@
import io.grpc.xds.WeightedTargetLoadBalancerProvider.WeightedPolicySelection;
import io.grpc.xds.WeightedTargetLoadBalancerProvider.WeightedTargetConfig;
import io.grpc.xds.XdsLogger.XdsLogLevel;
-import io.grpc.xds.XdsSubchannelPickers.ErrorPicker;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
diff --git a/xds/src/main/java/io/grpc/xds/XdsClusterResource.java b/xds/src/main/java/io/grpc/xds/XdsClusterResource.java
index 9571e11e21bc..73b0a975ff44 100644
--- a/xds/src/main/java/io/grpc/xds/XdsClusterResource.java
+++ b/xds/src/main/java/io/grpc/xds/XdsClusterResource.java
@@ -241,6 +241,11 @@ private static StructOrError parseNonAggregateCluster(
if (!edsClusterConfig.getServiceName().isEmpty()) {
edsServiceName = edsClusterConfig.getServiceName();
}
+ // edsServiceName is required if the CDS resource has an xdstp name.
+ if ((edsServiceName == null) && clusterName.toLowerCase().startsWith("xdstp:")) {
+ return StructOrError.fromError(
+ "EDS service_name must be set when Cluster resource has an xdstp name");
+ }
return StructOrError.fromStruct(CdsUpdate.forEds(
clusterName, edsServiceName, lrsServerInfo, maxConcurrentRequests, upstreamTlsContext,
outlierDetection));
diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java
index 7f853dcf1ee6..29043b177d97 100644
--- a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java
+++ b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java
@@ -147,7 +147,11 @@ final class XdsNameResolver extends NameResolver {
XdsClientPoolFactory xdsClientPoolFactory, ThreadSafeRandom random,
FilterRegistry filterRegistry, @Nullable Map bootstrapOverride) {
this.targetAuthority = targetAuthority;
- serviceAuthority = GrpcUtil.checkAuthority(checkNotNull(name, "name"));
+
+ // The name might have multiple slashes so encode it before verifying.
+ String authority = GrpcUtil.AuthorityEscaper.encodeAuthority(checkNotNull(name, "name"));
+ serviceAuthority = GrpcUtil.checkAuthority(authority);
+
this.overrideAuthority = overrideAuthority;
this.serviceConfigParser = checkNotNull(serviceConfigParser, "serviceConfigParser");
this.syncContext = checkNotNull(syncContext, "syncContext");
diff --git a/xds/src/main/java/io/grpc/xds/XdsResourceType.java b/xds/src/main/java/io/grpc/xds/XdsResourceType.java
index 86fbf3fd6b99..1bec72d492c7 100644
--- a/xds/src/main/java/io/grpc/xds/XdsResourceType.java
+++ b/xds/src/main/java/io/grpc/xds/XdsResourceType.java
@@ -58,7 +58,7 @@ abstract class XdsResourceType {
static boolean enableWrr = getFlag("GRPC_EXPERIMENTAL_XDS_WRR_LB", true);
@VisibleForTesting
- static boolean enablePickFirst = getFlag("GRPC_EXPERIMENTAL_PICKFIRST_LB_CONFIG", false);
+ static boolean enablePickFirst = getFlag("GRPC_EXPERIMENTAL_PICKFIRST_LB_CONFIG", true);
static final String TYPE_URL_CLUSTER_CONFIG =
"type.googleapis.com/envoy.extensions.clusters.aggregate.v3.ClusterConfig";
diff --git a/xds/src/main/java/io/grpc/xds/XdsSubchannelPickers.java b/xds/src/main/java/io/grpc/xds/XdsSubchannelPickers.java
deleted file mode 100644
index 5c2890c34eac..000000000000
--- a/xds/src/main/java/io/grpc/xds/XdsSubchannelPickers.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2019 The gRPC Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.grpc.xds;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import com.google.common.base.MoreObjects;
-import io.grpc.LoadBalancer.PickResult;
-import io.grpc.LoadBalancer.PickSubchannelArgs;
-import io.grpc.LoadBalancer.SubchannelPicker;
-import io.grpc.Status;
-
-final class XdsSubchannelPickers {
-
- private XdsSubchannelPickers() { /* DO NOT CALL ME */ }
-
- static final SubchannelPicker BUFFER_PICKER = new SubchannelPicker() {
- @Override
- public PickResult pickSubchannel(PickSubchannelArgs args) {
- return PickResult.withNoResult();
- }
-
- @Override
- public String toString() {
- return "BUFFER_PICKER";
- }
- };
-
- static final class ErrorPicker extends SubchannelPicker {
-
- private final Status error;
-
- ErrorPicker(Status error) {
- this.error = checkNotNull(error, "error");
- }
-
- @Override
- public PickResult pickSubchannel(PickSubchannelArgs args) {
- return PickResult.withError(error);
- }
-
- @Override
- public String toString() {
- return MoreObjects.toStringHelper(this)
- .add("error", error)
- .toString();
- }
- }
-}
diff --git a/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertificateProviderRegistry.java b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertificateProviderRegistry.java
index 2c320b79964c..f8ced7bb2cc4 100644
--- a/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertificateProviderRegistry.java
+++ b/xds/src/main/java/io/grpc/xds/internal/security/certprovider/CertificateProviderRegistry.java
@@ -59,7 +59,7 @@ public synchronized void register(CertificateProviderProvider certificateProvide
* Deregisters a provider. No-op if the provider is not in the registry.
*
* @param certificateProviderProvider the provider that was added to the registry via
- * {@link #register}.
+ * {@link #register}.
*/
public synchronized void deregister(CertificateProviderProvider certificateProviderProvider) {
checkNotNull(certificateProviderProvider, "certificateProviderProvider");
diff --git a/xds/src/main/java/io/grpc/xds/internal/security/trust/CertificateUtils.java b/xds/src/main/java/io/grpc/xds/internal/security/trust/CertificateUtils.java
index 6e244a438c07..d7696bbd3a63 100644
--- a/xds/src/main/java/io/grpc/xds/internal/security/trust/CertificateUtils.java
+++ b/xds/src/main/java/io/grpc/xds/internal/security/trust/CertificateUtils.java
@@ -49,9 +49,9 @@ public final class CertificateUtils {
private static CertificateFactory factory;
private static final Pattern KEY_PATTERN = Pattern.compile(
- "-+BEGIN\\s+.*PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+" + // Header
- "([a-z0-9+/=\\r\\n]+)" + // Base64 text
- "-+END\\s+.*PRIVATE\\s+KEY[^-]*-+", // Footer
+ "-+BEGIN\\s+.*PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+" // Header
+ + "([a-z0-9+/=\\r\\n]+)" // Base64 text
+ + "-+END\\s+.*PRIVATE\\s+KEY[^-]*-+", // Footer
Pattern.CASE_INSENSITIVE);
private static synchronized void initInstance() throws CertificateException {
diff --git a/xds/src/main/java/io/grpc/xds/orca/OrcaMetricReportingServerInterceptor.java b/xds/src/main/java/io/grpc/xds/orca/OrcaMetricReportingServerInterceptor.java
index 34922dfc8874..1b767e0303cd 100644
--- a/xds/src/main/java/io/grpc/xds/orca/OrcaMetricReportingServerInterceptor.java
+++ b/xds/src/main/java/io/grpc/xds/orca/OrcaMetricReportingServerInterceptor.java
@@ -120,7 +120,8 @@ private static OrcaLoadReport.Builder fromInternalReport(MetricReport internalRe
.setRpsFractional(internalReport.getQps())
.setEps(internalReport.getEps())
.putAllUtilization(internalReport.getUtilizationMetrics())
- .putAllRequestCost(internalReport.getRequestCostMetrics());
+ .putAllRequestCost(internalReport.getRequestCostMetrics())
+ .putAllNamedMetrics(internalReport.getNamedMetrics());
}
/**
@@ -133,7 +134,8 @@ private static void mergeMetrics(
MetricReport callMetricRecorderReport
) {
metricRecorderReportBuilder.putAllUtilization(callMetricRecorderReport.getUtilizationMetrics())
- .putAllRequestCost(callMetricRecorderReport.getRequestCostMetrics());
+ .putAllRequestCost(callMetricRecorderReport.getRequestCostMetrics())
+ .putAllNamedMetrics(callMetricRecorderReport.getNamedMetrics());
// Overwrite only if the values from the given MetricReport for CallMetricRecorder are set
double cpu = callMetricRecorderReport.getCpuUtilization();
if (isReportValueSet(cpu)) {
diff --git a/xds/src/main/java/io/grpc/xds/orca/OrcaPerRequestUtil.java b/xds/src/main/java/io/grpc/xds/orca/OrcaPerRequestUtil.java
index 97b98cd4a282..814015ba93ea 100644
--- a/xds/src/main/java/io/grpc/xds/orca/OrcaPerRequestUtil.java
+++ b/xds/src/main/java/io/grpc/xds/orca/OrcaPerRequestUtil.java
@@ -256,7 +256,7 @@ static MetricReport fromOrcaLoadReport(OrcaLoadReport loadReport) {
return InternalCallMetricRecorder.createMetricReport(loadReport.getCpuUtilization(),
loadReport.getApplicationUtilization(), loadReport.getMemUtilization(),
loadReport.getRpsFractional(), loadReport.getEps(), loadReport.getRequestCostMap(),
- loadReport.getUtilizationMap());
+ loadReport.getUtilizationMap(), loadReport.getNamedMetricsMap());
}
/**
diff --git a/xds/src/main/java/io/grpc/xds/orca/OrcaServiceImpl.java b/xds/src/main/java/io/grpc/xds/orca/OrcaServiceImpl.java
index 60cb3e1089f5..2fe53ab6d1cb 100644
--- a/xds/src/main/java/io/grpc/xds/orca/OrcaServiceImpl.java
+++ b/xds/src/main/java/io/grpc/xds/orca/OrcaServiceImpl.java
@@ -24,6 +24,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.protobuf.util.Durations;
import io.grpc.BindableService;
+import io.grpc.ExperimentalApi;
import io.grpc.ServerServiceDefinition;
import io.grpc.SynchronizationContext;
import io.grpc.services.InternalMetricRecorder;
@@ -41,6 +42,7 @@
* Implements a {@link BindableService} that generates Out-Of-Band server metrics.
* Register the returned service to the server, then a client can request for periodic load reports.
*/
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/9006")
public final class OrcaServiceImpl implements BindableService {
private static final Logger logger = Logger.getLogger(OrcaServiceImpl.class.getName());
diff --git a/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java
index 2ab101b730d1..7842967c0a58 100644
--- a/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java
+++ b/xds/src/test/java/io/grpc/xds/ClusterImplLoadBalancerTest.java
@@ -21,6 +21,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import com.github.xds.data.orca.v3.OrcaLoadReport;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import io.grpc.Attributes;
@@ -45,6 +46,7 @@
import io.grpc.internal.FakeClock;
import io.grpc.internal.ObjectPool;
import io.grpc.internal.ServiceConfigUtil.PolicySelection;
+import io.grpc.protobuf.ProtoUtils;
import io.grpc.xds.Bootstrapper.ServerInfo;
import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig;
import io.grpc.xds.Endpoints.DropOverload;
@@ -88,11 +90,16 @@
public class ClusterImplLoadBalancerTest {
@Rule public final MockitoRule mocks = MockitoJUnit.rule();
+ private static final double TOLERANCE = 1.0e-10;
private static final String AUTHORITY = "api.google.com";
private static final String CLUSTER = "cluster-foo.googleapis.com";
private static final String EDS_SERVICE_NAME = "service.googleapis.com";
private static final ServerInfo LRS_SERVER_INFO =
ServerInfo.create("api.google.com", InsecureChannelCredentials.create());
+ private static final Metadata.Key ORCA_ENDPOINT_LOAD_METRICS_KEY =
+ Metadata.Key.of(
+ "endpoint-load-metrics-bin",
+ ProtoUtils.metadataMarshaller(OrcaLoadReport.getDefaultInstance()));
private final SynchronizationContext syncContext = new SynchronizationContext(
new Thread.UncaughtExceptionHandler() {
@Override
@@ -255,7 +262,21 @@ public void recordLoadStats() {
ClientStreamTracer.StreamInfo.newBuilder().build(), new Metadata()); // second RPC call
ClientStreamTracer streamTracer3 = result.getStreamTracerFactory().newClientStreamTracer(
ClientStreamTracer.StreamInfo.newBuilder().build(), new Metadata()); // third RPC call
+ // When the trailer contains an ORCA report, the listener callback will be invoked.
+ Metadata trailersWithOrcaLoadReport1 = new Metadata();
+ trailersWithOrcaLoadReport1.put(ORCA_ENDPOINT_LOAD_METRICS_KEY,
+ OrcaLoadReport.newBuilder().setApplicationUtilization(1.414).setMemUtilization(0.034)
+ .setRpsFractional(1.414).putNamedMetrics("named1", 3.14159)
+ .putNamedMetrics("named2", -1.618).build());
+ streamTracer1.inboundTrailers(trailersWithOrcaLoadReport1);
streamTracer1.streamClosed(Status.OK);
+ Metadata trailersWithOrcaLoadReport2 = new Metadata();
+ trailersWithOrcaLoadReport2.put(ORCA_ENDPOINT_LOAD_METRICS_KEY,
+ OrcaLoadReport.newBuilder().setApplicationUtilization(0.99).setMemUtilization(0.123)
+ .setRpsFractional(0.905).putNamedMetrics("named1", 2.718)
+ .putNamedMetrics("named2", 1.414)
+ .putNamedMetrics("named3", 0.009).build());
+ streamTracer2.inboundTrailers(trailersWithOrcaLoadReport2);
streamTracer2.streamClosed(Status.UNAVAILABLE);
ClusterStats clusterStats =
Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
@@ -266,6 +287,24 @@ public void recordLoadStats() {
assertThat(localityStats.totalSuccessfulRequests()).isEqualTo(1L);
assertThat(localityStats.totalErrorRequests()).isEqualTo(1L);
assertThat(localityStats.totalRequestsInProgress()).isEqualTo(1L);
+ assertThat(localityStats.loadMetricStatsMap().containsKey("named1")).isTrue();
+ assertThat(
+ localityStats.loadMetricStatsMap().get("named1").numRequestsFinishedWithMetric()).isEqualTo(
+ 2L);
+ assertThat(localityStats.loadMetricStatsMap().get("named1").totalMetricValue()).isWithin(
+ TOLERANCE).of(3.14159 + 2.718);
+ assertThat(localityStats.loadMetricStatsMap().containsKey("named2")).isTrue();
+ assertThat(
+ localityStats.loadMetricStatsMap().get("named2").numRequestsFinishedWithMetric()).isEqualTo(
+ 2L);
+ assertThat(localityStats.loadMetricStatsMap().get("named2").totalMetricValue()).isWithin(
+ TOLERANCE).of(-1.618 + 1.414);
+ assertThat(localityStats.loadMetricStatsMap().containsKey("named3")).isTrue();
+ assertThat(
+ localityStats.loadMetricStatsMap().get("named3").numRequestsFinishedWithMetric()).isEqualTo(
+ 1L);
+ assertThat(localityStats.loadMetricStatsMap().get("named3").totalMetricValue()).isWithin(
+ TOLERANCE).of(0.009);
streamTracer3.streamClosed(Status.OK);
subchannel.shutdown(); // stats recorder released
@@ -278,6 +317,7 @@ public void recordLoadStats() {
assertThat(localityStats.totalSuccessfulRequests()).isEqualTo(1L);
assertThat(localityStats.totalErrorRequests()).isEqualTo(0L);
assertThat(localityStats.totalRequestsInProgress()).isEqualTo(0L);
+ assertThat(localityStats.loadMetricStatsMap().isEmpty()).isTrue();
clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
assertThat(clusterStats.upstreamLocalityStatsList()).isEmpty(); // no longer reported
diff --git a/xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerTest.java
index 6cb12550b602..6eab6151477d 100644
--- a/xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerTest.java
+++ b/xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerTest.java
@@ -54,7 +54,6 @@
import io.grpc.internal.ServiceConfigUtil.PolicySelection;
import io.grpc.testing.TestMethodDescriptors;
import io.grpc.xds.ClusterManagerLoadBalancerProvider.ClusterManagerConfig;
-import io.grpc.xds.XdsSubchannelPickers.ErrorPicker;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -126,10 +125,14 @@ public void handleResolvedAddressesUpdatesChannelPicker() {
assertThat(pickSubchannel(picker, "childA")).isEqualTo(PickResult.withNoResult());
assertThat(pickSubchannel(picker, "childB")).isEqualTo(PickResult.withNoResult());
assertThat(childBalancers).hasSize(2);
- FakeLoadBalancer childBalancer1 = childBalancers.get(0);
- FakeLoadBalancer childBalancer2 = childBalancers.get(1);
- assertThat(childBalancer1.name).isEqualTo("policy_a");
- assertThat(childBalancer2.name).isEqualTo("policy_b");
+ assertThat(childBalancers.stream()
+ .filter(b -> b.name.equals("policy_a"))
+ .count()).isEqualTo(1);
+ assertThat(childBalancers.stream()
+ .filter(b -> b.name.equals("policy_b"))
+ .count()).isEqualTo(1);
+ FakeLoadBalancer childBalancer1 = getChildBalancerByName("policy_a");
+ FakeLoadBalancer childBalancer2 = getChildBalancerByName("policy_b");
assertThat(childBalancer1.config).isEqualTo(lbConfigInventory.get("childA"));
assertThat(childBalancer2.config).isEqualTo(lbConfigInventory.get("childB"));
@@ -151,8 +154,7 @@ public void handleResolvedAddressesUpdatesChannelPicker() {
assertThat(childBalancer2.shutdown).isFalse();
assertThat(childBalancers).hasSize(3);
- FakeLoadBalancer childBalancer3 = childBalancers.get(2);
- assertThat(childBalancer3.name).isEqualTo("policy_c");
+ FakeLoadBalancer childBalancer3 = getChildBalancerByName("policy_c");
assertThat(childBalancer3.config).isEqualTo(lbConfigInventory.get("childC"));
// delayed policy_b deletion
@@ -166,8 +168,8 @@ public void updateBalancingStateFromChildBalancers() {
deliverResolvedAddresses(ImmutableMap.of("childA", "policy_a", "childB", "policy_b"));
assertThat(childBalancers).hasSize(2);
- FakeLoadBalancer childBalancer1 = childBalancers.get(0);
- FakeLoadBalancer childBalancer2 = childBalancers.get(1);
+ FakeLoadBalancer childBalancer1 = getChildBalancerByName("policy_a");
+ FakeLoadBalancer childBalancer2 = getChildBalancerByName("policy_b");
Subchannel subchannel1 = mock(Subchannel.class);
Subchannel subchannel2 = mock(Subchannel.class);
childBalancer1.deliverSubchannelState(subchannel1, ConnectivityState.READY);
@@ -184,11 +186,20 @@ public void updateBalancingStateFromChildBalancers() {
.isEqualTo(subchannel2);
}
+ private FakeLoadBalancer getChildBalancerByName(String name) {
+ for (FakeLoadBalancer childLb : childBalancers) {
+ if (childLb.name.equals(name)) {
+ return childLb;
+ }
+ }
+ return null;
+ }
+
@Test
public void ignoreBalancingStateUpdateForDeactivatedChildLbs() {
deliverResolvedAddresses(ImmutableMap.of("childA", "policy_a", "childB", "policy_b"));
deliverResolvedAddresses(ImmutableMap.of("childB", "policy_b"));
- FakeLoadBalancer childBalancer1 = childBalancers.get(0); // policy_a (deactivated)
+ FakeLoadBalancer childBalancer1 = getChildBalancerByName("policy_a"); // policy_a (deactivated)
Subchannel subchannel = mock(Subchannel.class);
childBalancer1.deliverSubchannelState(subchannel, ConnectivityState.READY);
verify(helper, never()).updateBalancingState(
@@ -231,8 +242,8 @@ public void handleNameResolutionError_beforeChildLbsInstantiated_returnErrorPick
public void handleNameResolutionError_afterChildLbsInstantiated_propagateToChildLbs() {
deliverResolvedAddresses(ImmutableMap.of("childA", "policy_a", "childB", "policy_b"));
assertThat(childBalancers).hasSize(2);
- FakeLoadBalancer childBalancer1 = childBalancers.get(0);
- FakeLoadBalancer childBalancer2 = childBalancers.get(1);
+ FakeLoadBalancer childBalancer1 = getChildBalancerByName("policy_a");
+ FakeLoadBalancer childBalancer2 = getChildBalancerByName("policy_b");
clusterManagerLoadBalancer.handleNameResolutionError(
Status.UNAVAILABLE.withDescription("resolver error"));
assertThat(childBalancer1.upstreamError.getCode()).isEqualTo(Code.UNAVAILABLE);
@@ -245,8 +256,8 @@ public void handleNameResolutionError_afterChildLbsInstantiated_propagateToChild
public void handleNameResolutionError_notPropagateToDeactivatedChildLbs() {
deliverResolvedAddresses(ImmutableMap.of("childA", "policy_a", "childB", "policy_b"));
deliverResolvedAddresses(ImmutableMap.of("childB", "policy_b"));
- FakeLoadBalancer childBalancer1 = childBalancers.get(0); // policy_a (deactivated)
- FakeLoadBalancer childBalancer2 = childBalancers.get(1); // policy_b
+ FakeLoadBalancer childBalancer1 = getChildBalancerByName("policy_a"); // policy_a (deactivated)
+ FakeLoadBalancer childBalancer2 = getChildBalancerByName("policy_b"); // policy_b
clusterManagerLoadBalancer.handleNameResolutionError(
Status.UNKNOWN.withDescription("unknown error"));
assertThat(childBalancer1.upstreamError).isNull();
diff --git a/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java
index 604dd57b5cb5..81a23d2b5fd7 100644
--- a/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java
+++ b/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java
@@ -153,13 +153,13 @@ public void uncaughtException(Thread t, Throwable e) {
private final NameResolverRegistry nsRegistry = new NameResolverRegistry();
private final PolicySelection roundRobin = new PolicySelection(
new FakeLoadBalancerProvider("wrr_locality_experimental"), new WrrLocalityConfig(
- new PolicySelection(new FakeLoadBalancerProvider("round_robin"), null)));
+ new PolicySelection(new FakeLoadBalancerProvider("round_robin"), null)));
private final PolicySelection ringHash = new PolicySelection(
new FakeLoadBalancerProvider("ring_hash_experimental"), new RingHashConfig(10L, 100L));
private final PolicySelection leastRequest = new PolicySelection(
new FakeLoadBalancerProvider("wrr_locality_experimental"), new WrrLocalityConfig(
- new PolicySelection(new FakeLoadBalancerProvider("least_request_experimental"),
- new LeastRequestConfig(3))));
+ new PolicySelection(new FakeLoadBalancerProvider("least_request_experimental"),
+ new LeastRequestConfig(3))));
private final List childBalancers = new ArrayList<>();
private final List resolvers = new ArrayList<>();
private final FakeXdsClient xdsClient = new FakeXdsClient();
diff --git a/xds/src/test/java/io/grpc/xds/LoadReportClientTest.java b/xds/src/test/java/io/grpc/xds/LoadReportClientTest.java
index 3c3a11b3cd07..910a9fc32856 100644
--- a/xds/src/test/java/io/grpc/xds/LoadReportClientTest.java
+++ b/xds/src/test/java/io/grpc/xds/LoadReportClientTest.java
@@ -35,6 +35,7 @@
import com.google.protobuf.util.Durations;
import io.envoyproxy.envoy.config.core.v3.Node;
import io.envoyproxy.envoy.config.endpoint.v3.ClusterStats;
+import io.envoyproxy.envoy.config.endpoint.v3.EndpointLoadMetricStats;
import io.envoyproxy.envoy.config.endpoint.v3.UpstreamLocalityStats;
import io.envoyproxy.envoy.service.load_stats.v3.LoadReportingServiceGrpc;
import io.envoyproxy.envoy.service.load_stats.v3.LoadStatsRequest;
@@ -198,11 +199,15 @@ private void addFakeStatsData() {
for (int i = 0; i < 31; i++) {
localityStats1.recordCallStarted();
}
+ localityStats1.recordBackendLoadMetricStats(ImmutableMap.of("named1", 3.14159));
+ localityStats1.recordBackendLoadMetricStats(ImmutableMap.of("named1", 1.618));
+ localityStats1.recordBackendLoadMetricStats(ImmutableMap.of("named1", -2.718));
ClusterLocalityStats localityStats2 =
loadStatsManager.getClusterLocalityStats(CLUSTER2, EDS_SERVICE_NAME2, LOCALITY2);
for (int i = 0; i < 45; i++) {
localityStats2.recordCallStarted();
}
+ localityStats2.recordBackendLoadMetricStats(ImmutableMap.of("named2", 1.414));
localityStats2.recordCallFinished(Status.OK);
}
@@ -245,6 +250,12 @@ public void periodicLoadReporting() {
assertThat(localityStats.getTotalSuccessfulRequests()).isEqualTo(0L);
assertThat(localityStats.getTotalErrorRequests()).isEqualTo(0L);
assertThat(localityStats.getTotalRequestsInProgress()).isEqualTo(31L);
+ assertThat(localityStats.getLoadMetricStatsCount()).isEqualTo(1);
+ EndpointLoadMetricStats loadMetricStats = Iterables.getOnlyElement(
+ localityStats.getLoadMetricStatsList());
+ assertThat(loadMetricStats.getMetricName()).isEqualTo("named1");
+ assertThat(loadMetricStats.getNumRequestsFinishedWithMetric()).isEqualTo(3L);
+ assertThat(loadMetricStats.getTotalMetricValue()).isEqualTo(3.14159 + 1.618 - 2.718);
fakeClock.forwardTime(10L, TimeUnit.SECONDS);
verify(requestObserver, times(3)).onNext(requestCaptor.capture());
@@ -263,6 +274,7 @@ public void periodicLoadReporting() {
assertThat(localityStats.getTotalSuccessfulRequests()).isEqualTo(0L);
assertThat(localityStats.getTotalErrorRequests()).isEqualTo(0L);
assertThat(localityStats.getTotalRequestsInProgress()).isEqualTo(31L);
+ assertThat(localityStats.getLoadMetricStatsList()).isEmpty();
// Management server updates the interval of sending load reports, while still asking for
// loads to cluster1 only.
@@ -287,6 +299,7 @@ public void periodicLoadReporting() {
assertThat(localityStats.getTotalSuccessfulRequests()).isEqualTo(0L);
assertThat(localityStats.getTotalErrorRequests()).isEqualTo(0L);
assertThat(localityStats.getTotalRequestsInProgress()).isEqualTo(31L);
+ assertThat(localityStats.getLoadMetricStatsList()).isEmpty();
// Management server asks to report loads for all clusters.
responseObserver.onNext(LoadStatsResponse.newBuilder().setSendAllClusters(true)
@@ -309,6 +322,7 @@ public void periodicLoadReporting() {
assertThat(localityStats1.getTotalSuccessfulRequests()).isEqualTo(0L);
assertThat(localityStats1.getTotalErrorRequests()).isEqualTo(0L);
assertThat(localityStats1.getTotalRequestsInProgress()).isEqualTo(31L);
+ assertThat(localityStats1.getLoadMetricStatsList()).isEmpty();
ClusterStats clusterStats2 = findClusterStats(request.getClusterStatsList(), CLUSTER2);
assertThat(Durations.toSeconds(clusterStats2.getLoadReportInterval()))
.isEqualTo(10L + 10L + 20L + 20L);
@@ -326,6 +340,12 @@ public void periodicLoadReporting() {
assertThat(localityStats2.getTotalSuccessfulRequests()).isEqualTo(1L);
assertThat(localityStats2.getTotalErrorRequests()).isEqualTo(0L);
assertThat(localityStats2.getTotalRequestsInProgress()).isEqualTo(45L - 1L);
+ assertThat(localityStats2.getLoadMetricStatsCount()).isEqualTo(1);
+ EndpointLoadMetricStats loadMetricStats2 = Iterables.getOnlyElement(
+ localityStats2.getLoadMetricStatsList());
+ assertThat(loadMetricStats2.getMetricName()).isEqualTo("named2");
+ assertThat(loadMetricStats2.getNumRequestsFinishedWithMetric()).isEqualTo(1L);
+ assertThat(loadMetricStats2.getTotalMetricValue()).isEqualTo(1.414);
// Load reports for cluster1 is no longer wanted.
responseObserver.onNext(LoadStatsResponse.newBuilder().addClusters(CLUSTER2)
@@ -348,6 +368,7 @@ public void periodicLoadReporting() {
assertThat(localityStats.getTotalSuccessfulRequests()).isEqualTo(0L);
assertThat(localityStats.getTotalErrorRequests()).isEqualTo(0L);
assertThat(localityStats.getTotalRequestsInProgress()).isEqualTo(44L);
+ assertThat(localityStats.getLoadMetricStatsList()).isEmpty();
fakeClock.forwardTime(10L, TimeUnit.SECONDS);
verify(requestObserver, times(7)).onNext(requestCaptor.capture());
@@ -366,6 +387,7 @@ public void periodicLoadReporting() {
assertThat(localityStats.getTotalSuccessfulRequests()).isEqualTo(0L);
assertThat(localityStats.getTotalErrorRequests()).isEqualTo(0L);
assertThat(localityStats.getTotalRequestsInProgress()).isEqualTo(44L);
+ assertThat(localityStats.getLoadMetricStatsList()).isEmpty();
// Management server asks loads for a cluster that client has no load data.
responseObserver.onNext(LoadStatsResponse.newBuilder().addClusters("unknown.googleapis.com")
@@ -495,6 +517,12 @@ public void lrsStreamClosedAndRetried() {
assertThat(localityStats.getTotalSuccessfulRequests()).isEqualTo(0L);
assertThat(localityStats.getTotalErrorRequests()).isEqualTo(0L);
assertThat(localityStats.getTotalRequestsInProgress()).isEqualTo(31L);
+ assertThat(localityStats.getLoadMetricStatsCount()).isEqualTo(1);
+ EndpointLoadMetricStats loadMetricStats = Iterables.getOnlyElement(
+ localityStats.getLoadMetricStatsList());
+ assertThat(loadMetricStats.getMetricName()).isEqualTo("named1");
+ assertThat(loadMetricStats.getNumRequestsFinishedWithMetric()).isEqualTo(3L);
+ assertThat(loadMetricStats.getTotalMetricValue()).isEqualTo(3.14159 + 1.618 - 2.718);
// Wrapping up
verify(backoffPolicyProvider, times(2)).get();
diff --git a/xds/src/test/java/io/grpc/xds/LoadStatsManager2Test.java b/xds/src/test/java/io/grpc/xds/LoadStatsManager2Test.java
index 0cfb7f46a22a..0389fa74acc6 100644
--- a/xds/src/test/java/io/grpc/xds/LoadStatsManager2Test.java
+++ b/xds/src/test/java/io/grpc/xds/LoadStatsManager2Test.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import io.grpc.Status;
import io.grpc.internal.FakeClock;
@@ -39,6 +40,7 @@
*/
@RunWith(JUnit4.class)
public class LoadStatsManager2Test {
+ private static final double TOLERANCE = 1.0e-10;
private static final String CLUSTER_NAME1 = "cluster-foo.googleapis.com";
private static final String CLUSTER_NAME2 = "cluster-bar.googleapis.com";
private static final String EDS_SERVICE_NAME1 = "backend-service-foo.googleapis.com";
@@ -71,12 +73,17 @@ public void recordAndGetReport() {
for (int i = 0; i < 19; i++) {
loadCounter1.recordCallStarted();
}
+ loadCounter1.recordBackendLoadMetricStats(ImmutableMap.of("named1", 3.14159));
+ loadCounter1.recordBackendLoadMetricStats(ImmutableMap.of("named1", 1.618));
+ loadCounter1.recordBackendLoadMetricStats(ImmutableMap.of("named1", 99.0));
+ loadCounter1.recordBackendLoadMetricStats(ImmutableMap.of("named1", -97.23, "named2", -2.718));
fakeClock.forwardTime(5L, TimeUnit.SECONDS);
dropCounter2.recordDroppedRequest();
loadCounter1.recordCallFinished(Status.OK);
for (int i = 0; i < 9; i++) {
loadCounter2.recordCallStarted();
}
+ loadCounter2.recordBackendLoadMetricStats(ImmutableMap.of("named3", 0.0009));
loadCounter2.recordCallFinished(Status.UNAVAILABLE);
fakeClock.forwardTime(10L, TimeUnit.SECONDS);
loadCounter3.recordCallStarted();
@@ -96,6 +103,18 @@ public void recordAndGetReport() {
assertThat(loadStats1.totalSuccessfulRequests()).isEqualTo(1L);
assertThat(loadStats1.totalErrorRequests()).isEqualTo(0L);
assertThat(loadStats1.totalRequestsInProgress()).isEqualTo(19L - 1L);
+ assertThat(loadStats1.loadMetricStatsMap().containsKey("named1")).isTrue();
+ assertThat(loadStats1.loadMetricStatsMap().containsKey("named2")).isTrue();
+ assertThat(
+ loadStats1.loadMetricStatsMap().get("named1").numRequestsFinishedWithMetric()).isEqualTo(
+ 4L);
+ assertThat(loadStats1.loadMetricStatsMap().get("named1").totalMetricValue()).isWithin(TOLERANCE)
+ .of(3.14159 + 1.618 + 99 - 97.23);
+ assertThat(
+ loadStats1.loadMetricStatsMap().get("named2").numRequestsFinishedWithMetric()).isEqualTo(
+ 1L);
+ assertThat(loadStats1.loadMetricStatsMap().get("named2").totalMetricValue()).isWithin(TOLERANCE)
+ .of(-2.718);
UpstreamLocalityStats loadStats2 =
findLocalityStats(stats1.upstreamLocalityStatsList(), LOCALITY2);
@@ -103,6 +122,12 @@ public void recordAndGetReport() {
assertThat(loadStats2.totalSuccessfulRequests()).isEqualTo(0L);
assertThat(loadStats2.totalErrorRequests()).isEqualTo(1L);
assertThat(loadStats2.totalRequestsInProgress()).isEqualTo(9L - 1L);
+ assertThat(loadStats2.loadMetricStatsMap().containsKey("named3")).isTrue();
+ assertThat(
+ loadStats2.loadMetricStatsMap().get("named3").numRequestsFinishedWithMetric()).isEqualTo(
+ 1L);
+ assertThat(loadStats2.loadMetricStatsMap().get("named3").totalMetricValue()).isWithin(TOLERANCE)
+ .of(0.0009);
ClusterStats stats2 = findClusterStats(allStats, CLUSTER_NAME1, EDS_SERVICE_NAME2);
assertThat(stats2.loadReportIntervalNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L + 10L));
@@ -121,6 +146,7 @@ public void recordAndGetReport() {
assertThat(loadStats3.totalSuccessfulRequests()).isEqualTo(0L);
assertThat(loadStats3.totalErrorRequests()).isEqualTo(0L);
assertThat(loadStats3.totalRequestsInProgress()).isEqualTo(1L);
+ assertThat(loadStats3.loadMetricStatsMap()).isEmpty();
fakeClock.forwardTime(3L, TimeUnit.SECONDS);
List clusterStatsList = loadStatsManager.getClusterStatsReports(CLUSTER_NAME1);
@@ -135,11 +161,13 @@ public void recordAndGetReport() {
assertThat(loadStats1.totalSuccessfulRequests()).isEqualTo(0L);
assertThat(loadStats1.totalErrorRequests()).isEqualTo(0L);
assertThat(loadStats1.totalRequestsInProgress()).isEqualTo(18L); // still in-progress
+ assertThat(loadStats1.loadMetricStatsMap()).isEmpty();
loadStats2 = findLocalityStats(stats1.upstreamLocalityStatsList(), LOCALITY2);
assertThat(loadStats2.totalIssuedRequests()).isEqualTo(0L);
assertThat(loadStats2.totalSuccessfulRequests()).isEqualTo(0L);
assertThat(loadStats2.totalErrorRequests()).isEqualTo(0L);
assertThat(loadStats2.totalRequestsInProgress()).isEqualTo(8L); // still in-progress
+ assertThat(loadStats2.loadMetricStatsMap()).isEmpty();
stats2 = findClusterStats(clusterStatsList, CLUSTER_NAME1, EDS_SERVICE_NAME2);
assertThat(stats2.loadReportIntervalNano()).isEqualTo(TimeUnit.SECONDS.toNanos(3L));
@@ -194,9 +222,12 @@ public void sharedLoadCounterStatsAggregation() {
ClusterLocalityStats ref2 = loadStatsManager.getClusterLocalityStats(
CLUSTER_NAME1, EDS_SERVICE_NAME1, LOCALITY1);
ref1.recordCallStarted();
+ ref1.recordBackendLoadMetricStats(ImmutableMap.of("named1", 1.618));
+ ref1.recordBackendLoadMetricStats(ImmutableMap.of("named1", 3.14159));
ref1.recordCallFinished(Status.OK);
ref2.recordCallStarted();
ref2.recordCallStarted();
+ ref2.recordBackendLoadMetricStats(ImmutableMap.of("named1", -1.0, "named2", 2.718));
ref2.recordCallFinished(Status.UNAVAILABLE);
ClusterStats stats = Iterables.getOnlyElement(
@@ -207,6 +238,18 @@ public void sharedLoadCounterStatsAggregation() {
assertThat(localityStats.totalSuccessfulRequests()).isEqualTo(1L);
assertThat(localityStats.totalErrorRequests()).isEqualTo(1L);
assertThat(localityStats.totalRequestsInProgress()).isEqualTo(1L + 2L - 1L - 1L);
+ assertThat(localityStats.loadMetricStatsMap().containsKey("named1")).isTrue();
+ assertThat(localityStats.loadMetricStatsMap().containsKey("named2")).isTrue();
+ assertThat(
+ localityStats.loadMetricStatsMap().get("named1").numRequestsFinishedWithMetric()).isEqualTo(
+ 3L);
+ assertThat(localityStats.loadMetricStatsMap().get("named1").totalMetricValue()).isWithin(
+ TOLERANCE).of(1.618 + 3.14159 - 1);
+ assertThat(
+ localityStats.loadMetricStatsMap().get("named2").numRequestsFinishedWithMetric()).isEqualTo(
+ 1L);
+ assertThat(localityStats.loadMetricStatsMap().get("named2").totalMetricValue()).isEqualTo(
+ 2.718);
}
@Test
@@ -215,6 +258,8 @@ public void loadCounterDelayedDeletionAfterAllInProgressRequestsReported() {
CLUSTER_NAME1, EDS_SERVICE_NAME1, LOCALITY1);
counter.recordCallStarted();
counter.recordCallStarted();
+ counter.recordBackendLoadMetricStats(ImmutableMap.of("named1", 2.718));
+ counter.recordBackendLoadMetricStats(ImmutableMap.of("named1", 1.414));
ClusterStats stats = Iterables.getOnlyElement(
loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
@@ -224,6 +269,12 @@ public void loadCounterDelayedDeletionAfterAllInProgressRequestsReported() {
assertThat(localityStats.totalSuccessfulRequests()).isEqualTo(0L);
assertThat(localityStats.totalErrorRequests()).isEqualTo(0L);
assertThat(localityStats.totalRequestsInProgress()).isEqualTo(2L);
+ assertThat(localityStats.loadMetricStatsMap().containsKey("named1")).isTrue();
+ assertThat(
+ localityStats.loadMetricStatsMap().get("named1").numRequestsFinishedWithMetric()).isEqualTo(
+ 2L);
+ assertThat(localityStats.loadMetricStatsMap().get("named1").totalMetricValue()).isEqualTo(
+ 2.718 + 1.414);
// release the counter, but requests still in-flight
counter.release();
@@ -234,6 +285,7 @@ public void loadCounterDelayedDeletionAfterAllInProgressRequestsReported() {
assertThat(localityStats.totalErrorRequests()).isEqualTo(0L);
assertThat(localityStats.totalRequestsInProgress())
.isEqualTo(2L); // retained by in-flight calls
+ assertThat(localityStats.loadMetricStatsMap().isEmpty()).isTrue();
counter.recordCallFinished(Status.OK);
counter.recordCallFinished(Status.UNAVAILABLE);
@@ -243,6 +295,7 @@ public void loadCounterDelayedDeletionAfterAllInProgressRequestsReported() {
assertThat(localityStats.totalSuccessfulRequests()).isEqualTo(1L);
assertThat(localityStats.totalErrorRequests()).isEqualTo(1L);
assertThat(localityStats.totalRequestsInProgress()).isEqualTo(0L);
+ assertThat(localityStats.loadMetricStatsMap().isEmpty()).isTrue();
assertThat(loadStatsManager.getClusterStatsReports(CLUSTER_NAME1)).isEmpty();
}
diff --git a/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java
index a005f40fad7b..ebcd68dc950c 100644
--- a/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java
+++ b/xds/src/test/java/io/grpc/xds/PriorityLoadBalancerTest.java
@@ -21,7 +21,7 @@
import static io.grpc.ConnectivityState.IDLE;
import static io.grpc.ConnectivityState.READY;
import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
-import static io.grpc.xds.XdsSubchannelPickers.BUFFER_PICKER;
+import static io.grpc.LoadBalancer.EMPTY_PICKER;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
@@ -41,6 +41,7 @@
import io.grpc.ConnectivityState;
import io.grpc.EquivalentAddressGroup;
import io.grpc.LoadBalancer;
+import io.grpc.LoadBalancer.ErrorPicker;
import io.grpc.LoadBalancer.Helper;
import io.grpc.LoadBalancer.PickResult;
import io.grpc.LoadBalancer.PickSubchannelArgs;
@@ -55,7 +56,6 @@
import io.grpc.internal.TestUtils.StandardLoadBalancerProvider;
import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig;
import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig.PriorityChildConfig;
-import io.grpc.xds.XdsSubchannelPickers.ErrorPicker;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
@@ -423,13 +423,13 @@ public void idleToConnectingDoesNotTriggerFailOver() {
// p0 gets IDLE.
helper0.updateBalancingState(
IDLE,
- BUFFER_PICKER);
+ EMPTY_PICKER);
assertCurrentPickerIsBufferPicker();
// p0 goes to CONNECTING
helper0.updateBalancingState(
IDLE,
- BUFFER_PICKER);
+ EMPTY_PICKER);
assertCurrentPickerIsBufferPicker();
// no failover happened
@@ -459,15 +459,15 @@ public void connectingResetFailOverIfSeenReadyOrIdleSinceTransientFailure() {
// p0 gets IDLE.
helper0.updateBalancingState(
IDLE,
- BUFFER_PICKER);
+ EMPTY_PICKER);
assertCurrentPickerIsBufferPicker();
// p0 goes to CONNECTING, reset failover timer
fakeClock.forwardTime(5, TimeUnit.SECONDS);
helper0.updateBalancingState(
CONNECTING,
- BUFFER_PICKER);
- verify(helper, times(2)).updateBalancingState(eq(CONNECTING), eq(BUFFER_PICKER));
+ EMPTY_PICKER);
+ verify(helper, times(2)).updateBalancingState(eq(CONNECTING), eq(EMPTY_PICKER));
// failover happens
fakeClock.forwardTime(10, TimeUnit.SECONDS);
@@ -509,7 +509,7 @@ public PickResult pickSubchannel(PickSubchannelArgs args) {
// p0 goes to CONNECTING
helper0.updateBalancingState(
IDLE,
- BUFFER_PICKER);
+ EMPTY_PICKER);
assertCurrentPickerIsBufferPicker();
// no failover happened
@@ -560,7 +560,7 @@ public void typicalPriorityFailOverFlowWithIdleUpdate() {
// p0 gets IDLE.
helper0.updateBalancingState(
IDLE,
- BUFFER_PICKER);
+ EMPTY_PICKER);
assertCurrentPickerIsBufferPicker();
// p0 fails over to p1 immediately.
@@ -581,13 +581,13 @@ public void typicalPriorityFailOverFlowWithIdleUpdate() {
// p2 gets IDLE
helper2.updateBalancingState(
IDLE,
- BUFFER_PICKER);
+ EMPTY_PICKER);
assertCurrentPickerIsBufferPicker();
// p0 gets back to IDLE
helper0.updateBalancingState(
IDLE,
- BUFFER_PICKER);
+ EMPTY_PICKER);
assertCurrentPickerIsBufferPicker();
// p2 fails but does not affect overall picker
@@ -614,13 +614,13 @@ public void typicalPriorityFailOverFlowWithIdleUpdate() {
// p2 gets back to IDLE
helper2.updateBalancingState(
IDLE,
- BUFFER_PICKER);
+ EMPTY_PICKER);
assertCurrentPickerIsBufferPicker();
// p0 gets back to IDLE
helper0.updateBalancingState(
IDLE,
- BUFFER_PICKER);
+ EMPTY_PICKER);
assertCurrentPickerIsBufferPicker();
// p0 fails over to p2 and picker is updated to p2's existing picker.
diff --git a/xds/src/test/java/io/grpc/xds/RbacFilterTest.java b/xds/src/test/java/io/grpc/xds/RbacFilterTest.java
index f86b36df5e8b..29af01b222f2 100644
--- a/xds/src/test/java/io/grpc/xds/RbacFilterTest.java
+++ b/xds/src/test/java/io/grpc/xds/RbacFilterTest.java
@@ -26,6 +26,7 @@
import static org.mockito.Mockito.when;
import com.google.api.expr.v1alpha1.Expr;
+import com.google.common.collect.ImmutableList;
import com.google.protobuf.Any;
import com.google.protobuf.Message;
import com.google.protobuf.UInt32Value;
@@ -339,6 +340,22 @@ public void ignoredConfig() {
assertThat(result.config).isEqualTo(RbacConfig.create(null));
}
+ @Test
+ public void testOrderIndependenceOfPolicies() {
+ Message rawProto = buildComplexRbac(ImmutableList.of(1, 2, 3, 4, 5, 6), true);
+ ConfigOrError ascFirst = new RbacFilter().parseFilterConfig(Any.pack(rawProto));
+
+ rawProto = buildComplexRbac(ImmutableList.of(1, 2, 3, 4, 5, 6), false);
+ ConfigOrError ascLast = new RbacFilter().parseFilterConfig(Any.pack(rawProto));
+
+ assertThat(ascFirst.config).isEqualTo(ascLast.config);
+
+ rawProto = buildComplexRbac(ImmutableList.of(6, 5, 4, 3, 2, 1), true);
+ ConfigOrError decFirst = new RbacFilter().parseFilterConfig(Any.pack(rawProto));
+
+ assertThat(ascFirst.config).isEqualTo(decFirst.config);
+ }
+
private static Metadata metadata(String key, String value) {
Metadata metadata = new Metadata();
metadata.put(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER), value);
@@ -369,11 +386,61 @@ private ConfigOrError parseRaw(List permissionList,
private io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBAC buildRbac(
List permissionList, List principalList) {
return io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBAC.newBuilder()
- .setRules(RBAC.newBuilder().setAction(Action.DENY)
- .putPolicies("policy-name", Policy.newBuilder()
- .addAllPermissions(permissionList)
- .addAllPrincipals(principalList).build()).build()).build();
+ .setRules(buildRbacRule("policy-name", Action.DENY,
+ permissionList, principalList)).build();
+ }
+
+ private static RBAC buildRbacRule(String policyName, Action action,
+ List permissionList, List principalList) {
+ return RBAC.newBuilder().setAction(action)
+ .putPolicies(policyName, Policy.newBuilder()
+ .addAllPermissions(permissionList)
+ .addAllPrincipals(principalList).build())
+ .build();
+ }
+
+ private io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBAC buildComplexRbac(
+ List ids, boolean listsFirst) {
+ Policy policy1 = createSimplePolicyUsingLists(0);
+
+ RBAC.Builder ruleBuilder = RBAC.newBuilder().setAction(Action.DENY);
+
+ if (listsFirst) {
+ ruleBuilder.putPolicies("list-policy", policy1);
+ }
+
+ String base = "filterConfig\\u003dRbacConfig{authConfig\\u003dAuthConfig{policies\\u003d[Poli"
+ + "cyMatcher{name\\u003dpsm-interop-authz-policy-20230514-0917-er2uh_td_rbac_rule_";
+
+ for (Integer id : ids) {
+ ruleBuilder.putPolicies(base + id, createSimplePolicyUsingLists(id));
+ }
+
+ if (!listsFirst) {
+ ruleBuilder.putPolicies("list-policy", policy1);
+ }
+
+ return io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBAC.newBuilder()
+ .setRules(ruleBuilder.build()).build();
+ }
+
+ private static Policy createSimplePolicyUsingLists(int id) {
+ CidrRange cidrRange = CidrRange.newBuilder().setAddressPrefix("10.10." + id + ".0")
+ .setPrefixLen(UInt32Value.of(24)).build();
+ List permissionList = Arrays.asList(
+ Permission.newBuilder().setAndRules(Permission.Set.newBuilder()
+ .addRules(Permission.newBuilder().setDestinationIp(cidrRange).build())
+ .addRules(Permission.newBuilder().setDestinationPort(9090).build()).build()
+ ).build());
+ List principalList = Arrays.asList(
+ Principal.newBuilder().setAndIds(Principal.Set.newBuilder()
+ .addIds(Principal.newBuilder().setDirectRemoteIp(cidrRange).build())
+ .addIds(Principal.newBuilder().setRemoteIp(cidrRange).build())
+ .build()).build());
+ return Policy.newBuilder()
+ .addAllPermissions(permissionList)
+ .addAllPrincipals(principalList).build();
}
private ConfigOrError parseOverride(List permissionList,
diff --git a/xds/src/test/java/io/grpc/xds/WeightedRoundRobinLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/WeightedRoundRobinLoadBalancerTest.java
index daf58a174d99..ac08f69f88cf 100644
--- a/xds/src/test/java/io/grpc/xds/WeightedRoundRobinLoadBalancerTest.java
+++ b/xds/src/test/java/io/grpc/xds/WeightedRoundRobinLoadBalancerTest.java
@@ -52,7 +52,7 @@
import io.grpc.internal.FakeClock;
import io.grpc.services.InternalCallMetricRecorder;
import io.grpc.services.MetricReport;
-import io.grpc.xds.WeightedRoundRobinLoadBalancer.EdfScheduler;
+import io.grpc.xds.WeightedRoundRobinLoadBalancer.StaticStrideScheduler;
import io.grpc.xds.WeightedRoundRobinLoadBalancer.WeightedRoundRobinLoadBalancerConfig;
import io.grpc.xds.WeightedRoundRobinLoadBalancer.WeightedRoundRobinPicker;
import io.grpc.xds.WeightedRoundRobinLoadBalancer.WrrSubchannel;
@@ -175,7 +175,7 @@ public Void answer(InvocationOnMock invocation) throws Throwable {
}
});
wrr = new WeightedRoundRobinLoadBalancer(helper, fakeClock.getDeadlineTicker(),
- new FakeRandom());
+ new FakeRandom(0));
}
@Test
@@ -214,13 +214,13 @@ public void wrrLifeCycle() {
WrrSubchannel weightedSubchannel2 = (WrrSubchannel) weightedPicker.getList().get(1);
weightedSubchannel1.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport(
InternalCallMetricRecorder.createMetricReport(
- 0.1, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>()));
+ 0.1, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>(), new HashMap<>()));
weightedSubchannel2.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport(
InternalCallMetricRecorder.createMetricReport(
- 0.2, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>()));
+ 0.2, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>(), new HashMap<>()));
assertThat(fakeClock.forwardTime(11, TimeUnit.SECONDS)).isEqualTo(1);
assertThat(weightedPicker.pickSubchannel(mockArgs)
- .getSubchannel()).isEqualTo(weightedSubchannel1);
+ .getSubchannel()).isEqualTo(weightedSubchannel1);
assertThat(fakeClock.getPendingTasks().size()).isEqualTo(1);
weightedConfig = WeightedRoundRobinLoadBalancerConfig.newBuilder()
.setWeightUpdatePeriodNanos(500_000_000L) //.5s
@@ -260,10 +260,10 @@ public void enableOobLoadReportConfig() {
WrrSubchannel weightedSubchannel2 = (WrrSubchannel) weightedPicker.getList().get(1);
weightedSubchannel1.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport(
InternalCallMetricRecorder.createMetricReport(
- 0.1, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>()));
+ 0.1, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>(), new HashMap<>()));
weightedSubchannel2.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport(
InternalCallMetricRecorder.createMetricReport(
- 0.9, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>()));
+ 0.9, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>(), new HashMap<>()));
assertThat(fakeClock.forwardTime(11, TimeUnit.SECONDS)).isEqualTo(1);
PickResult pickResult = weightedPicker.pickSubchannel(mockArgs);
assertThat(pickResult.getSubchannel()).isEqualTo(weightedSubchannel1);
@@ -330,66 +330,69 @@ weightedSubchannel3.new OrcaReportListener(weightedConfig.errorUtilizationPenalt
}
assertThat(pickCount.size()).isEqualTo(3);
assertThat(Math.abs(pickCount.get(weightedSubchannel1) / 10000.0 - subchannel1PickRatio))
- .isAtMost(0.001);
+ .isLessThan(0.0002);
assertThat(Math.abs(pickCount.get(weightedSubchannel2) / 10000.0 - subchannel2PickRatio ))
- .isAtMost(0.001);
+ .isLessThan(0.0002);
assertThat(Math.abs(pickCount.get(weightedSubchannel3) / 10000.0 - subchannel3PickRatio ))
- .isAtMost(0.001);
+ .isLessThan(0.0002);
}
@Test
- public void pickByWeight_LargeWeight() {
+ public void pickByWeight_largeWeight() {
MetricReport report1 = InternalCallMetricRecorder.createMetricReport(
- 0.1, 0, 0.1, 999, 0, new HashMap<>(), new HashMap<>());
+ 0.1, 0, 0.1, 999, 0, new HashMap<>(), new HashMap<>(), new HashMap<>());
MetricReport report2 = InternalCallMetricRecorder.createMetricReport(
- 0.9, 0, 0.1, 2, 0, new HashMap<>(), new HashMap<>());
+ 0.9, 0, 0.1, 2, 0, new HashMap<>(), new HashMap<>(), new HashMap<>());
MetricReport report3 = InternalCallMetricRecorder.createMetricReport(
- 0.86, 0, 0.1, 100, 0, new HashMap<>(), new HashMap<>());
- double totalWeight = 999 / 0.1 + 2 / 0.9 + 100 / 0.86;
-
- pickByWeight(report1, report2, report3, 999 / 0.1 / totalWeight, 2 / 0.9 / totalWeight,
- 100 / 0.86 / totalWeight);
+ 0.86, 0, 0.1, 100, 0, new HashMap<>(), new HashMap<>(), new HashMap<>());
+ double meanWeight = (999 / 0.1 + 2 / 0.9 + 100 / 0.86) / 3;
+ double cappedMin = meanWeight * 0.1; // min capped at minRatio * meanWeight
+ double totalWeight = 999 / 0.1 + cappedMin + cappedMin;
+ pickByWeight(report1, report2, report3, 999 / 0.1 / totalWeight, cappedMin / totalWeight,
+ cappedMin / totalWeight);
}
@Test
public void pickByWeight_largeWeight_useApplicationUtilization() {
MetricReport report1 = InternalCallMetricRecorder.createMetricReport(
- 0.44, 0.1, 0.1, 999, 0, new HashMap<>(), new HashMap<>());
+ 0.44, 0.1, 0.1, 999, 0, new HashMap<>(), new HashMap<>(), new HashMap<>());
MetricReport report2 = InternalCallMetricRecorder.createMetricReport(
- 0.12, 0.9, 0.1, 2, 0, new HashMap<>(), new HashMap<>());
+ 0.12, 0.9, 0.1, 2, 0, new HashMap<>(), new HashMap<>(), new HashMap<>());
MetricReport report3 = InternalCallMetricRecorder.createMetricReport(
- 0.33, 0.86, 0.1, 100, 0, new HashMap<>(), new HashMap<>());
- double totalWeight = 999 / 0.1 + 2 / 0.9 + 100 / 0.86;
-
- pickByWeight(report1, report2, report3, 999 / 0.1 / totalWeight, 2 / 0.9 / totalWeight,
- 100 / 0.86 / totalWeight);
+ 0.33, 0.86, 0.1, 100, 0, new HashMap<>(), new HashMap<>(), new HashMap<>());
+ double meanWeight = (999 / 0.1 + 2 / 0.9 + 100 / 0.86) / 3;
+ double cappedMin = meanWeight * 0.1;
+ double totalWeight = 999 / 0.1 + cappedMin + cappedMin; // min capped at minRatio * meanWeight
+ pickByWeight(report1, report2, report3, 999 / 0.1 / totalWeight, cappedMin / totalWeight,
+ cappedMin / totalWeight);
}
@Test
public void pickByWeight_largeWeight_withEps_defaultErrorUtilizationPenalty() {
MetricReport report1 = InternalCallMetricRecorder.createMetricReport(
- 0.1, 0, 0.1, 999, 13, new HashMap<>(), new HashMap<>());
+ 0.1, 0, 0.1, 999, 13, new HashMap<>(), new HashMap<>(), new HashMap<>());
MetricReport report2 = InternalCallMetricRecorder.createMetricReport(
- 0.9, 0, 0.1, 2, 1.8, new HashMap<>(), new HashMap<>());
+ 0.9, 0, 0.1, 2, 1.8, new HashMap<>(), new HashMap<>(), new HashMap<>());
MetricReport report3 = InternalCallMetricRecorder.createMetricReport(
- 0.86, 0, 0.1, 100, 3, new HashMap<>(), new HashMap<>());
- double weight1 = 999 / (0.1 + 13 / 999F * weightedConfig.errorUtilizationPenalty);
- double weight2 = 2 / (0.9 + 1.8 / 2F * weightedConfig.errorUtilizationPenalty);
- double weight3 = 100 / (0.86 + 3 / 100F * weightedConfig.errorUtilizationPenalty);
- double totalWeight = weight1 + weight2 + weight3;
-
- pickByWeight(report1, report2, report3, weight1 / totalWeight, weight2 / totalWeight,
- weight3 / totalWeight);
+ 0.86, 0, 0.1, 100, 3, new HashMap<>(), new HashMap<>(), new HashMap<>());
+ double weight1 = 999 / (0.1 + 13 / 999F * weightedConfig.errorUtilizationPenalty); // ~5609.899
+ double weight2 = 2 / (0.9 + 1.8 / 2F * weightedConfig.errorUtilizationPenalty); // ~0.317
+ double weight3 = 100 / (0.86 + 3 / 100F * weightedConfig.errorUtilizationPenalty); // ~96.154
+ double meanWeight = (weight1 + weight2 + weight3) / 3;
+ double cappedMin = meanWeight * 0.1; // min capped at minRatio * meanWeight
+ double totalWeight = weight1 + cappedMin + cappedMin;
+ pickByWeight(report1, report2, report3, weight1 / totalWeight, cappedMin / totalWeight,
+ cappedMin / totalWeight);
}
@Test
public void pickByWeight_normalWeight() {
MetricReport report1 = InternalCallMetricRecorder.createMetricReport(
- 0.12, 0, 0.1, 22, 0, new HashMap<>(), new HashMap<>());
+ 0.12, 0, 0.1, 22, 0, new HashMap<>(), new HashMap<>(), new HashMap<>());
MetricReport report2 = InternalCallMetricRecorder.createMetricReport(
- 0.28, 0, 0.1, 40, 0, new HashMap<>(), new HashMap<>());
+ 0.28, 0, 0.1, 40, 0, new HashMap<>(), new HashMap<>(), new HashMap<>());
MetricReport report3 = InternalCallMetricRecorder.createMetricReport(
- 0.86, 0, 0.1, 100, 0, new HashMap<>(), new HashMap<>());
+ 0.86, 0, 0.1, 100, 0, new HashMap<>(), new HashMap<>(), new HashMap<>());
double totalWeight = 22 / 0.12 + 40 / 0.28 + 100 / 0.86;
pickByWeight(report1, report2, report3, 22 / 0.12 / totalWeight,
40 / 0.28 / totalWeight, 100 / 0.86 / totalWeight
@@ -399,11 +402,11 @@ public void pickByWeight_normalWeight() {
@Test
public void pickByWeight_normalWeight_useApplicationUtilization() {
MetricReport report1 = InternalCallMetricRecorder.createMetricReport(
- 0.72, 0.12, 0.1, 22, 0, new HashMap<>(), new HashMap<>());
+ 0.72, 0.12, 0.1, 22, 0, new HashMap<>(), new HashMap<>(), new HashMap<>());
MetricReport report2 = InternalCallMetricRecorder.createMetricReport(
- 0.98, 0.28, 0.1, 40, 0, new HashMap<>(), new HashMap<>());
+ 0.98, 0.28, 0.1, 40, 0, new HashMap<>(), new HashMap<>(), new HashMap<>());
MetricReport report3 = InternalCallMetricRecorder.createMetricReport(
- 0.99, 0.86, 0.1, 100, 0, new HashMap<>(), new HashMap<>());
+ 0.99, 0.86, 0.1, 100, 0, new HashMap<>(), new HashMap<>(), new HashMap<>());
double totalWeight = 22 / 0.12 + 40 / 0.28 + 100 / 0.86;
pickByWeight(report1, report2, report3, 22 / 0.12 / totalWeight,
40 / 0.28 / totalWeight, 100 / 0.86 / totalWeight
@@ -413,11 +416,11 @@ public void pickByWeight_normalWeight_useApplicationUtilization() {
@Test
public void pickByWeight_normalWeight_withEps_defaultErrorUtilizationPenalty() {
MetricReport report1 = InternalCallMetricRecorder.createMetricReport(
- 0.12, 0, 0.1, 22, 19.7, new HashMap<>(), new HashMap<>());
+ 0.12, 0, 0.1, 22, 19.7, new HashMap<>(), new HashMap<>(), new HashMap<>());
MetricReport report2 = InternalCallMetricRecorder.createMetricReport(
- 0.28, 0, 0.1, 40, 0.998, new HashMap<>(), new HashMap<>());
+ 0.28, 0, 0.1, 40, 0.998, new HashMap<>(), new HashMap<>(), new HashMap<>());
MetricReport report3 = InternalCallMetricRecorder.createMetricReport(
- 0.86, 0, 0.1, 100, 3.14159, new HashMap<>(), new HashMap<>());
+ 0.86, 0, 0.1, 100, 3.14159, new HashMap<>(), new HashMap<>(), new HashMap<>());
double weight1 = 22 / (0.12 + 19.7 / 22F * weightedConfig.errorUtilizationPenalty);
double weight2 = 40 / (0.28 + 0.998 / 40F * weightedConfig.errorUtilizationPenalty);
double weight3 = 100 / (0.86 + 3.14159 / 100F * weightedConfig.errorUtilizationPenalty);
@@ -433,11 +436,11 @@ public void pickByWeight_normalWeight_withEps_customErrorUtilizationPenalty() {
.setErrorUtilizationPenalty(1.75F).build();
MetricReport report1 = InternalCallMetricRecorder.createMetricReport(
- 0.12, 0, 0.1, 22, 19.7, new HashMap<>(), new HashMap<>());
+ 0.12, 0, 0.1, 22, 19.7, new HashMap<>(), new HashMap<>(), new HashMap<>());
MetricReport report2 = InternalCallMetricRecorder.createMetricReport(
- 0.28, 0, 0.1, 40, 0.998, new HashMap<>(), new HashMap<>());
+ 0.28, 0, 0.1, 40, 0.998, new HashMap<>(), new HashMap<>(), new HashMap<>());
MetricReport report3 = InternalCallMetricRecorder.createMetricReport(
- 0.86, 0, 0.1, 100, 3.14159, new HashMap<>(), new HashMap<>());
+ 0.86, 0, 0.1, 100, 3.14159, new HashMap<>(), new HashMap<>(), new HashMap<>());
double weight1 = 22 / (0.12 + 19.7 / 22F * weightedConfig.errorUtilizationPenalty);
double weight2 = 40 / (0.28 + 0.998 / 40F * weightedConfig.errorUtilizationPenalty);
double weight3 = 100 / (0.86 + 3.14159 / 100F * weightedConfig.errorUtilizationPenalty);
@@ -453,11 +456,11 @@ public void pickByWeight_avgWeight_zeroCpuUtilization_withEps_customErrorUtiliza
.setErrorUtilizationPenalty(1.75F).build();
MetricReport report1 = InternalCallMetricRecorder.createMetricReport(
- 0, 0, 0.1, 22, 19.7, new HashMap<>(), new HashMap<>());
+ 0, 0, 0.1, 22, 19.7, new HashMap<>(), new HashMap<>(), new HashMap<>());
MetricReport report2 = InternalCallMetricRecorder.createMetricReport(
- 0, 0, 0.1, 40, 0.998, new HashMap<>(), new HashMap<>());
+ 0, 0, 0.1, 40, 0.998, new HashMap<>(), new HashMap<>(), new HashMap<>());
MetricReport report3 = InternalCallMetricRecorder.createMetricReport(
- 0, 0, 0.1, 100, 3.14159, new HashMap<>(), new HashMap<>());
+ 0, 0, 0.1, 100, 3.14159, new HashMap<>(), new HashMap<>(), new HashMap<>());
double avgSubchannelPickRatio = 1.0 / 3;
pickByWeight(report1, report2, report3, avgSubchannelPickRatio, avgSubchannelPickRatio,
@@ -508,10 +511,10 @@ public void blackoutPeriod() {
WrrSubchannel weightedSubchannel2 = (WrrSubchannel) weightedPicker.getList().get(1);
weightedSubchannel1.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport(
InternalCallMetricRecorder.createMetricReport(
- 0.1, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>()));
+ 0.1, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>(), new HashMap<>()));
weightedSubchannel2.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport(
InternalCallMetricRecorder.createMetricReport(
- 0.2, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>()));
+ 0.2, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>(), new HashMap<>()));
assertThat(fakeClock.forwardTime(5, TimeUnit.SECONDS)).isEqualTo(1);
Map pickCount = new HashMap<>();
for (int i = 0; i < 1000; i++) {
@@ -520,8 +523,8 @@ weightedSubchannel2.new OrcaReportListener(weightedConfig.errorUtilizationPenalt
}
assertThat(pickCount.size()).isEqualTo(2);
// within blackout period, fallback to simple round robin
- assertThat(Math.abs(pickCount.get(weightedSubchannel1) / 1000.0 - 0.5)).isAtMost(0.001);
- assertThat(Math.abs(pickCount.get(weightedSubchannel2) / 1000.0 - 0.5)).isAtMost(0.001);
+ assertThat(Math.abs(pickCount.get(weightedSubchannel1) / 1000.0 - 0.5)).isLessThan(0.002);
+ assertThat(Math.abs(pickCount.get(weightedSubchannel2) / 1000.0 - 0.5)).isLessThan(0.002);
assertThat(fakeClock.forwardTime(5, TimeUnit.SECONDS)).isEqualTo(1);
pickCount = new HashMap<>();
@@ -532,9 +535,9 @@ weightedSubchannel2.new OrcaReportListener(weightedConfig.errorUtilizationPenalt
assertThat(pickCount.size()).isEqualTo(2);
// after blackout period
assertThat(Math.abs(pickCount.get(weightedSubchannel1) / 1000.0 - 2.0 / 3))
- .isAtMost(0.001);
+ .isLessThan(0.002);
assertThat(Math.abs(pickCount.get(weightedSubchannel2) / 1000.0 - 1.0 / 3))
- .isAtMost(0.001);
+ .isLessThan(0.002);
}
@Test
@@ -568,10 +571,10 @@ public void updateWeightTimer() {
WrrSubchannel weightedSubchannel2 = (WrrSubchannel) weightedPicker.getList().get(1);
weightedSubchannel1.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport(
InternalCallMetricRecorder.createMetricReport(
- 0.1, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>()));
+ 0.1, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>(), new HashMap<>()));
weightedSubchannel2.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport(
InternalCallMetricRecorder.createMetricReport(
- 0.2, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>()));
+ 0.2, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>(), new HashMap<>()));
assertThat(fakeClock.forwardTime(11, TimeUnit.SECONDS)).isEqualTo(1);
assertThat(weightedPicker.pickSubchannel(mockArgs)
.getSubchannel()).isEqualTo(weightedSubchannel1);
@@ -585,14 +588,15 @@ weightedSubchannel2.new OrcaReportListener(weightedConfig.errorUtilizationPenalt
assertThat(fakeClock.getPendingTasks().size()).isEqualTo(1);
weightedSubchannel1.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport(
InternalCallMetricRecorder.createMetricReport(
- 0.2, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>()));
+ 0.2, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>(), new HashMap<>()));
weightedSubchannel2.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport(
InternalCallMetricRecorder.createMetricReport(
- 0.1, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>()));
+ 0.1, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>(), new HashMap<>()));
//timer fires, new weight updated
assertThat(fakeClock.forwardTime(500, TimeUnit.MILLISECONDS)).isEqualTo(1);
assertThat(weightedPicker.pickSubchannel(mockArgs)
.getSubchannel()).isEqualTo(weightedSubchannel2);
+
}
@Test
@@ -619,10 +623,10 @@ public void weightExpired() {
WrrSubchannel weightedSubchannel2 = (WrrSubchannel) weightedPicker.getList().get(1);
weightedSubchannel1.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport(
InternalCallMetricRecorder.createMetricReport(
- 0.1, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>()));
+ 0.1, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>(), new HashMap<>()));
weightedSubchannel2.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport(
InternalCallMetricRecorder.createMetricReport(
- 0.2, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>()));
+ 0.2, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>(), new HashMap<>()));
assertThat(fakeClock.forwardTime(10, TimeUnit.SECONDS)).isEqualTo(1);
Map pickCount = new HashMap<>();
for (int i = 0; i < 1000; i++) {
@@ -631,9 +635,9 @@ weightedSubchannel2.new OrcaReportListener(weightedConfig.errorUtilizationPenalt
}
assertThat(pickCount.size()).isEqualTo(2);
assertThat(Math.abs(pickCount.get(weightedSubchannel1) / 1000.0 - 2.0 / 3))
- .isAtMost(0.001);
+ .isLessThan(0.002);
assertThat(Math.abs(pickCount.get(weightedSubchannel2) / 1000.0 - 1.0 / 3))
- .isAtMost(0.001);
+ .isLessThan(0.002);
// weight expired, fallback to simple round robin
assertThat(fakeClock.forwardTime(300, TimeUnit.SECONDS)).isEqualTo(1);
@@ -644,9 +648,9 @@ weightedSubchannel2.new OrcaReportListener(weightedConfig.errorUtilizationPenalt
}
assertThat(pickCount.size()).isEqualTo(2);
assertThat(Math.abs(pickCount.get(weightedSubchannel1) / 1000.0 - 0.5))
- .isAtMost(0.001);
+ .isLessThan(0.002);
assertThat(Math.abs(pickCount.get(weightedSubchannel2) / 1000.0 - 0.5))
- .isAtMost(0.001);
+ .isLessThan(0.002);
}
@Test
@@ -684,7 +688,7 @@ public void rrFallback() {
subchannel.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport(
InternalCallMetricRecorder.createMetricReport(
0.1, 0, 0.1, qpsByChannel.get(subchannel), 0,
- new HashMap<>(), new HashMap<>()));
+ new HashMap<>(), new HashMap<>(), new HashMap<>()));
}
assertThat(Math.abs(pickCount.get(weightedSubchannel1) / 1000.0 - 1.0 / 2))
.isAtMost(0.1);
@@ -700,7 +704,7 @@ subchannel.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoad
subchannel.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport(
InternalCallMetricRecorder.createMetricReport(
0.1, 0, 0.1, qpsByChannel.get(subchannel), 0,
- new HashMap<>(), new HashMap<>()));
+ new HashMap<>(), new HashMap<>(), new HashMap<>()));
fakeClock.forwardTime(50, TimeUnit.MILLISECONDS);
}
assertThat(pickCount.size()).isEqualTo(2);
@@ -738,10 +742,10 @@ public void unknownWeightIsAvgWeight() {
WrrSubchannel weightedSubchannel3 = (WrrSubchannel) weightedPicker.getList().get(2);
weightedSubchannel1.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport(
InternalCallMetricRecorder.createMetricReport(
- 0.1, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>()));
+ 0.1, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>(), new HashMap<>()));
weightedSubchannel2.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport(
InternalCallMetricRecorder.createMetricReport(
- 0.2, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>()));
+ 0.2, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>(), new HashMap<>()));
assertThat(fakeClock.forwardTime(10, TimeUnit.SECONDS)).isEqualTo(1);
Map pickCount = new HashMap<>();
for (int i = 0; i < 1000; i++) {
@@ -750,12 +754,12 @@ weightedSubchannel2.new OrcaReportListener(weightedConfig.errorUtilizationPenalt
}
assertThat(pickCount.size()).isEqualTo(3);
assertThat(Math.abs(pickCount.get(weightedSubchannel1) / 1000.0 - 4.0 / 9))
- .isAtMost(0.001);
+ .isLessThan(0.002);
assertThat(Math.abs(pickCount.get(weightedSubchannel2) / 1000.0 - 2.0 / 9))
- .isAtMost(0.001);
+ .isLessThan(0.002);
// subchannel3's weight is average of subchannel1 and subchannel2
assertThat(Math.abs(pickCount.get(weightedSubchannel3) / 1000.0 - 3.0 / 9))
- .isAtMost(0.001);
+ .isLessThan(0.002);
}
@Test
@@ -782,10 +786,10 @@ public void pickFromOtherThread() throws Exception {
WrrSubchannel weightedSubchannel2 = (WrrSubchannel) weightedPicker.getList().get(1);
weightedSubchannel1.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport(
InternalCallMetricRecorder.createMetricReport(
- 0.1, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>()));
+ 0.1, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>(), new HashMap<>()));
weightedSubchannel2.new OrcaReportListener(weightedConfig.errorUtilizationPenalty).onLoadReport(
InternalCallMetricRecorder.createMetricReport(
- 0.2, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>()));
+ 0.2, 0, 0.1, 1, 0, new HashMap<>(), new HashMap<>(), new HashMap<>()));
CyclicBarrier barrier = new CyclicBarrier(2);
Map pickCount = new ConcurrentHashMap<>();
pickCount.put(weightedSubchannel1, new AtomicInteger(0));
@@ -816,50 +820,263 @@ public void run() {
assertThat(pickCount.size()).isEqualTo(2);
// after blackout period
assertThat(Math.abs(pickCount.get(weightedSubchannel1).get() / 2000.0 - 2.0 / 3))
- .isAtMost(0.001);
+ .isLessThan(0.002);
assertThat(Math.abs(pickCount.get(weightedSubchannel2).get() / 2000.0 - 1.0 / 3))
- .isAtMost(0.001);
+ .isLessThan(0.002);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void wrrConfig_TimeValueNonNull() {
+ WeightedRoundRobinLoadBalancerConfig.newBuilder().setBlackoutPeriodNanos((Long) null);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void wrrConfig_BooleanValueNonNull() {
+ WeightedRoundRobinLoadBalancerConfig.newBuilder().setEnableOobLoadReport((Boolean) null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void emptyWeights() {
+ float[] weights = {};
+ Random random = new Random(0);
+ VerifyingScheduler sss = new VerifyingScheduler(weights, new AtomicInteger(random.nextInt()));
+ sss.pick();
+ }
+
+ @Test
+ public void testPicksEqualsWeights() {
+ float[] weights = {1.0f, 2.0f, 3.0f};
+ Random random = new Random(0);
+ VerifyingScheduler sss = new VerifyingScheduler(weights, new AtomicInteger(random.nextInt()));
+ int[] expectedPicks = new int[] {1, 2, 3};
+ int[] picks = new int[3];
+ for (int i = 0; i < 6; i++) {
+ picks[sss.pick()] += 1;
+ }
+ assertThat(picks).isEqualTo(expectedPicks);
+ }
+
+ @Test
+ public void testContainsZeroWeightUseMean() {
+ float[] weights = {3.0f, 0.0f, 1.0f};
+ Random random = new Random(0);
+ VerifyingScheduler sss = new VerifyingScheduler(weights, new AtomicInteger(random.nextInt()));
+ int[] expectedPicks = new int[] {3, 2, 1};
+ int[] picks = new int[3];
+ for (int i = 0; i < 6; i++) {
+ picks[sss.pick()] += 1;
+ }
+ assertThat(picks).isEqualTo(expectedPicks);
+ }
+
+ @Test
+ public void testContainsNegativeWeightUseMean() {
+ float[] weights = {3.0f, -1.0f, 1.0f};
+ Random random = new Random(0);
+ VerifyingScheduler sss = new VerifyingScheduler(weights, new AtomicInteger(random.nextInt()));
+ int[] expectedPicks = new int[] {3, 2, 1};
+ int[] picks = new int[3];
+ for (int i = 0; i < 6; i++) {
+ picks[sss.pick()] += 1;
+ }
+ assertThat(picks).isEqualTo(expectedPicks);
+ }
+
+ @Test
+ public void testAllSameWeights() {
+ float[] weights = {1.0f, 1.0f, 1.0f};
+ Random random = new Random(0);
+ VerifyingScheduler sss = new VerifyingScheduler(weights, new AtomicInteger(random.nextInt()));
+ int[] expectedPicks = new int[] {2, 2, 2};
+ int[] picks = new int[3];
+ for (int i = 0; i < 6; i++) {
+ picks[sss.pick()] += 1;
+ }
+ assertThat(picks).isEqualTo(expectedPicks);
+ }
+
+ @Test
+ public void testAllZeroWeightsIsRoundRobin() {
+ float[] weights = {0.0f, 0.0f, 0.0f};
+ Random random = new Random(0);
+ VerifyingScheduler sss = new VerifyingScheduler(weights, new AtomicInteger(random.nextInt()));
+ int[] expectedPicks = new int[] {2, 2, 2};
+ int[] picks = new int[3];
+ for (int i = 0; i < 6; i++) {
+ picks[sss.pick()] += 1;
+ }
+ assertThat(picks).isEqualTo(expectedPicks);
+ }
+
+ @Test
+ public void testAllInvalidWeightsIsRoundRobin() {
+ float[] weights = {-3.1f, -0.0f, 0.0f};
+ Random random = new Random(0);
+ VerifyingScheduler sss = new VerifyingScheduler(weights, new AtomicInteger(random.nextInt()));
+ int[] expectedPicks = new int[] {2, 2, 2};
+ int[] picks = new int[3];
+ for (int i = 0; i < 6; i++) {
+ picks[sss.pick()] += 1;
+ }
+ assertThat(picks).isEqualTo(expectedPicks);
}
@Test
- public void edfScheduler() {
- Random random = new Random();
- double totalWeight = 0;
- int capacity = random.nextInt(10) + 1;
- double[] weights = new double[capacity];
- EdfScheduler scheduler = new EdfScheduler(capacity, random);
- for (int i = 0; i < capacity; i++) {
- weights[i] = random.nextDouble();
- scheduler.add(i, weights[i]);
- totalWeight += weights[i];
+ public void testTwoWeights() {
+ float[] weights = {1.43f, 2.119f};
+ Random random = new Random(0);
+ VerifyingScheduler sss = new VerifyingScheduler(weights, new AtomicInteger(random.nextInt()));
+ double totalWeight = 1.43 + 2.119;
+ Map pickCount = new HashMap<>();
+ for (int i = 0; i < 1000; i++) {
+ int result = sss.pick();
+ pickCount.put(result, pickCount.getOrDefault(result, 0) + 1);
}
+ for (int i = 0; i < 2; i++) {
+ assertThat(Math.abs(pickCount.getOrDefault(i, 0) / 1000.0 - weights[i] / totalWeight))
+ .isLessThan(0.002);
+ }
+ }
+
+ @Test
+ public void testManyWeights() {
+ float[] weights = {1.3f, 2.5f, 3.23f, 4.11f, 7.001f};
+ Random random = new Random(0);
+ VerifyingScheduler sss = new VerifyingScheduler(weights, new AtomicInteger(random.nextInt()));
+ double totalWeight = 1.3 + 2.5 + 3.23 + 4.11 + 7.001;
Map pickCount = new HashMap<>();
for (int i = 0; i < 1000; i++) {
- int result = scheduler.pick();
+ int result = sss.pick();
pickCount.put(result, pickCount.getOrDefault(result, 0) + 1);
}
- for (int i = 0; i < capacity; i++) {
- assertThat(Math.abs(pickCount.getOrDefault(i, 0) / 1000.0 - weights[i] / totalWeight) )
- .isAtMost(0.01);
+ for (int i = 0; i < 5; i++) {
+ assertThat(Math.abs(pickCount.getOrDefault(i, 0) / 1000.0 - weights[i] / totalWeight))
+ .isLessThan(0.002);
}
}
@Test
- public void edsScheduler_sameWeight() {
- EdfScheduler scheduler = new EdfScheduler(2, new FakeRandom());
- scheduler.add(0, 0.5);
- scheduler.add(1, 0.5);
- assertThat(scheduler.pick()).isEqualTo(0);
+ public void testMaxClamped() {
+ float[] weights = {81f, 1f, 1f, 1f, 1f, 1f, 1f, 1f,
+ 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f};
+ VerifyingScheduler sss = new VerifyingScheduler(weights, new AtomicInteger(0));
+ int[] picks = new int[weights.length];
+
+ // max gets clamped to mean*maxRatio = 50 for this set of weights. So if we
+ // pick 50 + 19 times we should get all possible picks.
+ for (int i = 1; i < 70; i++) {
+ picks[sss.pick()] += 1;
+ }
+ int[] expectedPicks = new int[] {50, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
+ assertThat(picks).isEqualTo(expectedPicks);
}
- @Test(expected = NullPointerException.class)
- public void wrrConfig_TimeValueNonNull() {
- WeightedRoundRobinLoadBalancerConfig.newBuilder().setBlackoutPeriodNanos((Long) null);
+ @Test
+ public void testMinClamped() {
+ float[] weights = {100f, 1e-10f};
+ VerifyingScheduler sss = new VerifyingScheduler(weights, new AtomicInteger(0));
+ int[] picks = new int[weights.length];
+
+ // We pick 201 elements and ensure that the second channel (with epsilon
+ // weight) also gets picked. The math is: mean value of elements is ~50, so
+ // the first channel keeps its weight of 100, but the second element's weight
+ // gets capped from below to 50*0.1 = 5.
+ for (int i = 0; i < 105; i++) {
+ picks[sss.pick()] += 1;
+ }
+ int[] expectedPicks = new int[] {100, 5};
+ assertThat(picks).isEqualTo(expectedPicks);
}
- @Test(expected = NullPointerException.class)
- public void wrrConfig_BooleanValueNonNull() {
- WeightedRoundRobinLoadBalancerConfig.newBuilder().setEnableOobLoadReport((Boolean) null);
+ @Test
+ public void testDeterministicPicks() {
+ float[] weights = {2.0f, 3.0f, 6.0f};
+ AtomicInteger sequence = new AtomicInteger(0);
+ VerifyingScheduler sss = new VerifyingScheduler(weights, sequence);
+ assertThat(sequence.get()).isEqualTo(0);
+ assertThat(sss.pick()).isEqualTo(1);
+ assertThat(sequence.get()).isEqualTo(2);
+ assertThat(sss.pick()).isEqualTo(2);
+ assertThat(sequence.get()).isEqualTo(3);
+ assertThat(sss.pick()).isEqualTo(2);
+ assertThat(sequence.get()).isEqualTo(6);
+ assertThat(sss.pick()).isEqualTo(0);
+ assertThat(sequence.get()).isEqualTo(7);
+ assertThat(sss.pick()).isEqualTo(1);
+ assertThat(sequence.get()).isEqualTo(8);
+ assertThat(sss.pick()).isEqualTo(2);
+ assertThat(sequence.get()).isEqualTo(9);
+ }
+
+ @Test
+ public void testImmediateWraparound() {
+ float[] weights = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f};
+ VerifyingScheduler sss = new VerifyingScheduler(weights, new AtomicInteger(-1));
+ double totalWeight = 15;
+ Map pickCount = new HashMap<>();
+ for (int i = 0; i < 1000; i++) {
+ int result = sss.pick();
+ pickCount.put(result, pickCount.getOrDefault(result, 0) + 1);
+ }
+ for (int i = 0; i < 5; i++) {
+ assertThat(Math.abs(pickCount.getOrDefault(i, 0) / 1000.0 - weights[i] / totalWeight))
+ .isLessThan(0.002);
+ }
+ }
+
+ @Test
+ public void testWraparound() {
+ float[] weights = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f};
+ VerifyingScheduler sss = new VerifyingScheduler(weights, new AtomicInteger(-500));
+ double totalWeight = 15;
+ Map pickCount = new HashMap<>();
+ for (int i = 0; i < 1000; i++) {
+ int result = sss.pick();
+ pickCount.put(result, pickCount.getOrDefault(result, 0) + 1);
+ }
+ for (int i = 0; i < 5; i++) {
+ assertThat(Math.abs(pickCount.getOrDefault(i, 0) / 1000.0 - weights[i] / totalWeight))
+ .isLessThan(0.002);
+ }
+ }
+
+ @Test
+ public void testDeterministicWraparound() {
+ float[] weights = {2.0f, 3.0f, 6.0f};
+ AtomicInteger sequence = new AtomicInteger(-1);
+ VerifyingScheduler sss = new VerifyingScheduler(weights, sequence);
+ assertThat(sequence.get()).isEqualTo(-1);
+ assertThat(sss.pick()).isEqualTo(1);
+ assertThat(sequence.get()).isEqualTo(2);
+ assertThat(sss.pick()).isEqualTo(2);
+ assertThat(sequence.get()).isEqualTo(3);
+ assertThat(sss.pick()).isEqualTo(2);
+ assertThat(sequence.get()).isEqualTo(6);
+ assertThat(sss.pick()).isEqualTo(0);
+ assertThat(sequence.get()).isEqualTo(7);
+ assertThat(sss.pick()).isEqualTo(1);
+ assertThat(sequence.get()).isEqualTo(8);
+ assertThat(sss.pick()).isEqualTo(2);
+ assertThat(sequence.get()).isEqualTo(9);
+ }
+
+ private static final class VerifyingScheduler {
+ private final StaticStrideScheduler delegate;
+ private final int max;
+ private final AtomicInteger sequence;
+
+ public VerifyingScheduler(float[] weights, AtomicInteger sequence) {
+ this.delegate = new StaticStrideScheduler(weights, sequence);
+ this.max = weights.length;
+ this.sequence = sequence;
+ }
+
+ public int pick() {
+ int start = sequence.get();
+ int i = delegate.pick();
+ assertThat(sequence.get() - start).isAtMost(max);
+ return i;
+ }
}
private static class FakeSocketAddress extends SocketAddress {
@@ -875,10 +1092,16 @@ private static class FakeSocketAddress extends SocketAddress {
}
private static class FakeRandom extends Random {
+ private int nextInt;
+
+ public FakeRandom(int nextInt) {
+ this.nextInt = nextInt;
+ }
+
@Override
- public double nextDouble() {
+ public int nextInt() {
// return constant value to disable init deadline randomization in the scheduler
- return 0.322023;
+ return nextInt;
}
}
}
diff --git a/xds/src/test/java/io/grpc/xds/WeightedTargetLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/WeightedTargetLoadBalancerTest.java
index 91ab1e8fac4c..6cec8b0fb6ff 100644
--- a/xds/src/test/java/io/grpc/xds/WeightedTargetLoadBalancerTest.java
+++ b/xds/src/test/java/io/grpc/xds/WeightedTargetLoadBalancerTest.java
@@ -20,7 +20,7 @@
import static io.grpc.ConnectivityState.CONNECTING;
import static io.grpc.ConnectivityState.READY;
import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
-import static io.grpc.xds.XdsSubchannelPickers.BUFFER_PICKER;
+import static io.grpc.LoadBalancer.EMPTY_PICKER;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeast;
@@ -39,6 +39,7 @@
import io.grpc.Attributes;
import io.grpc.EquivalentAddressGroup;
import io.grpc.LoadBalancer;
+import io.grpc.LoadBalancer.ErrorPicker;
import io.grpc.LoadBalancer.Helper;
import io.grpc.LoadBalancer.PickResult;
import io.grpc.LoadBalancer.PickSubchannelArgs;
@@ -52,7 +53,6 @@
import io.grpc.xds.WeightedRandomPicker.WeightedChildPicker;
import io.grpc.xds.WeightedTargetLoadBalancerProvider.WeightedPolicySelection;
import io.grpc.xds.WeightedTargetLoadBalancerProvider.WeightedTargetConfig;
-import io.grpc.xds.XdsSubchannelPickers.ErrorPicker;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
@@ -209,7 +209,7 @@ public void handleResolvedAddresses() {
.setAttributes(Attributes.newBuilder().set(fakeKey, fakeValue).build())
.setLoadBalancingPolicyConfig(new WeightedTargetConfig(targets))
.build());
- verify(helper).updateBalancingState(eq(CONNECTING), eq(BUFFER_PICKER));
+ verify(helper).updateBalancingState(eq(CONNECTING), eq(EMPTY_PICKER));
assertThat(childBalancers).hasSize(4);
assertThat(childHelpers).hasSize(4);
assertThat(fooLbCreated).isEqualTo(2);
@@ -246,7 +246,7 @@ public void handleResolvedAddresses() {
.setAddresses(ImmutableList.of())
.setLoadBalancingPolicyConfig(new WeightedTargetConfig(newTargets))
.build());
- verify(helper, atLeast(2)).updateBalancingState(eq(CONNECTING), eq(BUFFER_PICKER));
+ verify(helper, atLeast(2)).updateBalancingState(eq(CONNECTING), eq(EMPTY_PICKER));
assertThat(childBalancers).hasSize(5);
assertThat(childHelpers).hasSize(5);
assertThat(fooLbCreated).isEqualTo(3); // One more foo LB created for target4
@@ -288,7 +288,7 @@ public void handleNameResolutionError() {
.setAddresses(ImmutableList.of())
.setLoadBalancingPolicyConfig(new WeightedTargetConfig(targets))
.build());
- verify(helper).updateBalancingState(eq(CONNECTING), eq(BUFFER_PICKER));
+ verify(helper).updateBalancingState(eq(CONNECTING), eq(EMPTY_PICKER));
// Error after child balancers created.
weightedTargetLb.handleNameResolutionError(Status.ABORTED);
@@ -315,7 +315,7 @@ public void balancingStateUpdatedFromChildBalancers() {
.setAddresses(ImmutableList.of())
.setLoadBalancingPolicyConfig(new WeightedTargetConfig(targets))
.build());
- verify(helper).updateBalancingState(eq(CONNECTING), eq(BUFFER_PICKER));
+ verify(helper).updateBalancingState(eq(CONNECTING), eq(EMPTY_PICKER));
// Subchannels to be created for each child balancer.
final SubchannelPicker[] subchannelPickers = new SubchannelPicker[]{
@@ -335,7 +335,7 @@ public void balancingStateUpdatedFromChildBalancers() {
childHelpers.get(1).updateBalancingState(TRANSIENT_FAILURE, failurePickers[1]);
verify(helper, never()).updateBalancingState(
eq(TRANSIENT_FAILURE), any(SubchannelPicker.class));
- verify(helper, times(2)).updateBalancingState(eq(CONNECTING), eq(BUFFER_PICKER));
+ verify(helper, times(2)).updateBalancingState(eq(CONNECTING), eq(EMPTY_PICKER));
// Another child balancer goes to READY.
childHelpers.get(2).updateBalancingState(READY, subchannelPickers[2]);
@@ -396,7 +396,7 @@ public void raceBetweenShutdownAndChildLbBalancingStateUpdate() {
.setAddresses(ImmutableList.of())
.setLoadBalancingPolicyConfig(new WeightedTargetConfig(targets))
.build());
- verify(helper).updateBalancingState(eq(CONNECTING), eq(BUFFER_PICKER));
+ verify(helper).updateBalancingState(eq(CONNECTING), eq(EMPTY_PICKER));
// LB shutdown and subchannel state change can happen simultaneously. If shutdown runs first,
// any further balancing state update should be ignored.
diff --git a/xds/src/test/java/io/grpc/xds/WrrLocalityLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/WrrLocalityLoadBalancerTest.java
index 344876aa348a..bb80635f1bdd 100644
--- a/xds/src/test/java/io/grpc/xds/WrrLocalityLoadBalancerTest.java
+++ b/xds/src/test/java/io/grpc/xds/WrrLocalityLoadBalancerTest.java
@@ -30,6 +30,7 @@
import io.grpc.ConnectivityState;
import io.grpc.EquivalentAddressGroup;
import io.grpc.LoadBalancer;
+import io.grpc.LoadBalancer.ErrorPicker;
import io.grpc.LoadBalancer.Helper;
import io.grpc.LoadBalancer.ResolvedAddresses;
import io.grpc.LoadBalancer.SubchannelPicker;
@@ -41,7 +42,6 @@
import io.grpc.xds.WeightedTargetLoadBalancerProvider.WeightedPolicySelection;
import io.grpc.xds.WeightedTargetLoadBalancerProvider.WeightedTargetConfig;
import io.grpc.xds.WrrLocalityLoadBalancer.WrrLocalityConfig;
-import io.grpc.xds.XdsSubchannelPickers.ErrorPicker;
import java.net.SocketAddress;
import java.util.Collections;
import java.util.List;
diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTestBase.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTestBase.java
index 56e37e7192f5..c18b324b14c6 100644
--- a/xds/src/test/java/io/grpc/xds/XdsClientImplTestBase.java
+++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTestBase.java
@@ -2444,6 +2444,34 @@ public void cdsResponseErrorHandling_badTransportSocketName() {
verifyStatusWithNodeId(errorCaptor.getValue(), Code.UNAVAILABLE, errorMsg);
}
+ @Test
+ public void cdsResponseErrorHandling_xdstpWithoutEdsConfig() {
+ String cdsResourceName = "xdstp://authority.xds.com/envoy.config.cluster.v3.Cluster/cluster1";
+
+ final Any testClusterRoundRobin =
+ Any.pack(mf.buildEdsCluster(cdsResourceName, null, "round_robin", null,
+ null, false, null, "envoy.transport_sockets.tls", null, null
+ ));
+ final Any okClusterRoundRobin =
+ Any.pack(mf.buildEdsCluster(cdsResourceName, "eds-service-bar.googleapis.com",
+ "round_robin", null,null, false, null, "envoy.transport_sockets.tls", null, null));
+
+
+ DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(),
+ cdsResourceName, cdsResourceWatcher);
+ call.sendResponse(CDS, testClusterRoundRobin, VERSION_1, "0000");
+
+ List errors = ImmutableList.of("CDS response Cluster "
+ + "\'xdstp://authority.xds.com/envoy.config.cluster.v3.Cluster/cluster1\' "
+ + "validation error: EDS service_name must be set when Cluster resource has an xdstp name");
+ call.verifyRequest(CDS, cdsResourceName, "", "", NODE); // get this out of the way
+ call.verifyRequestNack(CDS, cdsResourceName, "", "0000", NODE, errors);
+ verifySubscribedResourcesMetadataSizes(0, 1, 0, 0);
+
+ call.sendResponse(CDS, okClusterRoundRobin, VERSION_1, "0001");
+ call.verifyRequest(CDS, cdsResourceName, VERSION_1, "0001", NODE);
+ }
+
@Test
@SuppressWarnings("unchecked")
public void cachedCdsResource_data() {
diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverProviderTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverProviderTest.java
index 95e3f2f997fb..a216c3de0281 100644
--- a/xds/src/test/java/io/grpc/xds/XdsNameResolverProviderTest.java
+++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverProviderTest.java
@@ -110,14 +110,19 @@ public void validName_noAuthority() {
}
@Test
- public void invalidName_hostnameContainsUnderscore() {
- URI uri = URI.create("xds:///foo_bar.googleapis.com");
- try {
- provider.newNameResolver(uri, args);
- fail("Expected IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // Expected
- }
+ public void validName_urlExtractedAuthorityInvalidWithoutEncoding() {
+ XdsNameResolver resolver =
+ provider.newNameResolver(URI.create("xds:///1234/path/foo.googleapis.com:8080"), args);
+ assertThat(resolver).isNotNull();
+ assertThat(resolver.getServiceAuthority()).isEqualTo("1234%2Fpath%2Ffoo.googleapis.com:8080");
+ }
+
+ @Test
+ public void validName_urlwithTargetAuthorityAndExtractedAuthorityInvalidWithoutEncoding() {
+ XdsNameResolver resolver = provider.newNameResolver(URI.create(
+ "xds://trafficdirector.google.com/1234/path/foo.googleapis.com:8080"), args);
+ assertThat(resolver).isNotNull();
+ assertThat(resolver.getServiceAuthority()).isEqualTo("1234%2Fpath%2Ffoo.googleapis.com:8080");
}
@Test
diff --git a/xds/src/test/java/io/grpc/xds/XdsTestControlPlaneService.java b/xds/src/test/java/io/grpc/xds/XdsTestControlPlaneService.java
index d7ac5bdd3cb9..c51327dc84d9 100644
--- a/xds/src/test/java/io/grpc/xds/XdsTestControlPlaneService.java
+++ b/xds/src/test/java/io/grpc/xds/XdsTestControlPlaneService.java
@@ -122,8 +122,8 @@ public void run() {
@Override
public StreamObserver streamAggregatedResources(
final StreamObserver responseObserver) {
- final StreamObserver requestObserver =
- new StreamObserver() {
+
+ final class AdsStreamObserver implements StreamObserver {
@Override
public void onNext(final DiscoveryRequest value) {
syncContext.execute(new Runnable() {
@@ -176,8 +176,9 @@ public void onCompleted() {
xdsNonces.get(type).remove(responseObserver);
}
}
- };
- return requestObserver;
+ }
+
+ return new AdsStreamObserver();
}
//must run in syncContext
diff --git a/xds/src/test/java/io/grpc/xds/internal/security/SecurityProtocolNegotiatorsTest.java b/xds/src/test/java/io/grpc/xds/internal/security/SecurityProtocolNegotiatorsTest.java
index 8a4123d54a3d..753bc967089c 100644
--- a/xds/src/test/java/io/grpc/xds/internal/security/SecurityProtocolNegotiatorsTest.java
+++ b/xds/src/test/java/io/grpc/xds/internal/security/SecurityProtocolNegotiatorsTest.java
@@ -233,7 +233,7 @@ public SocketAddress remoteAddress() {
ProtocolNegotiationEvent event = InternalProtocolNegotiationEvent.getDefault();
Attributes attr = InternalProtocolNegotiationEvent.getAttributes(event)
.toBuilder().set(ATTR_SERVER_SSL_CONTEXT_PROVIDER_SUPPLIER,
- new SslContextProviderSupplier(downstreamTlsContext, tlsContextManager)).build();
+ new SslContextProviderSupplier(downstreamTlsContext, tlsContextManager)).build();
pipeline.fireUserEventTriggered(InternalProtocolNegotiationEvent.withAttributes(event, attr));
channelHandlerCtx = pipeline.context(handlerPickerHandler);
assertThat(channelHandlerCtx).isNull();
diff --git a/xds/src/test/java/io/grpc/xds/orca/OrcaMetricReportingServerInterceptorTest.java b/xds/src/test/java/io/grpc/xds/orca/OrcaMetricReportingServerInterceptorTest.java
index 00562be3dc52..ec56467e5a5f 100644
--- a/xds/src/test/java/io/grpc/xds/orca/OrcaMetricReportingServerInterceptorTest.java
+++ b/xds/src/test/java/io/grpc/xds/orca/OrcaMetricReportingServerInterceptorTest.java
@@ -72,6 +72,7 @@ public class OrcaMetricReportingServerInterceptorTest {
private final Map applicationUtilizationMetricsMap = new HashMap<>();
private final Map applicationCostMetrics = new HashMap<>();
+ private final Map applicationNamedMetrics = new HashMap<>();
private double cpuUtilizationMetrics = 0;
private double applicationUtilizationMetrics = 0;
private double memoryUtilizationMetrics = 0;
@@ -98,6 +99,9 @@ public void unaryRpc(
CallMetricRecorder.getCurrent().recordRequestCostMetric(entry.getKey(),
entry.getValue());
}
+ for (Map.Entry entry : applicationNamedMetrics.entrySet()) {
+ CallMetricRecorder.getCurrent().recordNamedMetric(entry.getKey(), entry.getValue());
+ }
CallMetricRecorder.getCurrent().recordCpuUtilizationMetric(cpuUtilizationMetrics);
CallMetricRecorder.getCurrent()
.recordApplicationUtilizationMetric(applicationUtilizationMetrics);
@@ -133,8 +137,8 @@ public void unaryRpc(
@Test
public void shareCallMetricRecorderInContext() throws IOException {
final CallMetricRecorder callMetricRecorder = new CallMetricRecorder();
- ServerStreamTracer.Factory callMetricRecorderSharingStreamTracerFactory =
- new ServerStreamTracer.Factory() {
+ ServerStreamTracer.Factory callMetricRecorderSharingStreamTracerFactory;
+ callMetricRecorderSharingStreamTracerFactory = new ServerStreamTracer.Factory() {
@Override
public ServerStreamTracer newServerStreamTracer(String fullMethodName, Metadata headers) {
return new ServerStreamTracer() {
@@ -196,6 +200,9 @@ public void responseTrailersContainAllReportedMetricsFromCallMetricRecorder() {
applicationUtilizationMetricsMap.put("util1", 0.1082);
applicationUtilizationMetricsMap.put("util2", 0.4936);
applicationUtilizationMetricsMap.put("util3", 0.5342);
+ applicationNamedMetrics.put("named1", 0.777);
+ applicationNamedMetrics.put("named2", 737.747);
+ applicationNamedMetrics.put("named3", -0.380);
cpuUtilizationMetrics = 0.3465;
applicationUtilizationMetrics = 0.99887;
memoryUtilizationMetrics = 0.764;
@@ -209,6 +216,8 @@ public void responseTrailersContainAllReportedMetricsFromCallMetricRecorder() {
.containsExactly("util1", 0.1082, "util2", 0.4936, "util3", 0.5342);
assertThat(report.getRequestCostMap())
.containsExactly("cost1", 1231.4543, "cost2", 0.1367, "cost3", 7614.145);
+ assertThat(report.getNamedMetricsMap())
+ .containsExactly("named1", 0.777, "named2", 737.747, "named3", -0.380);
assertThat(report.getCpuUtilization()).isEqualTo(0.3465);
assertThat(report.getApplicationUtilization()).isEqualTo(0.99887);
assertThat(report.getMemUtilization()).isEqualTo(0.764);
@@ -221,6 +230,9 @@ public void responseTrailersContainMergedMetricsFromCallMetricRecorderAndMetricR
applicationUtilizationMetricsMap.put("util1", 0.1482);
applicationUtilizationMetricsMap.put("util2", 0.4036);
applicationUtilizationMetricsMap.put("util3", 0.5742);
+ applicationNamedMetrics.put("named1", 0.777);
+ applicationNamedMetrics.put("named2", 737.747);
+ applicationNamedMetrics.put("named3", -0.380);
cpuUtilizationMetrics = 0.3465;
memoryUtilizationMetrics = 0.967;
metricRecorder.setApplicationUtilizationMetric(2.718);
@@ -240,6 +252,8 @@ public void responseTrailersContainMergedMetricsFromCallMetricRecorderAndMetricR
assertThat(report.getUtilizationMap())
.containsExactly("util1", 0.1482, "util2", 0.4036, "util3", 0.5742, "serverUtil1", 0.7467,
"serverUtil2", 0.2233);
+ assertThat(report.getNamedMetricsMap())
+ .containsExactly("named1", 0.777, "named2", 737.747, "named3", -0.380);
assertThat(report.getRequestCostMap()).isEmpty();
assertThat(report.getCpuUtilization()).isEqualTo(0.3465);
assertThat(report.getApplicationUtilization()).isEqualTo(2.718);
diff --git a/xds/src/test/java/io/grpc/xds/orca/OrcaPerRequestUtilTest.java b/xds/src/test/java/io/grpc/xds/orca/OrcaPerRequestUtilTest.java
index ef06e6fccfec..4d0805029151 100644
--- a/xds/src/test/java/io/grpc/xds/orca/OrcaPerRequestUtilTest.java
+++ b/xds/src/test/java/io/grpc/xds/orca/OrcaPerRequestUtilTest.java
@@ -124,7 +124,8 @@ static boolean reportEqual(MetricReport a,
&& a.getQps() == b.getQps()
&& a.getEps() == b.getEps()
&& Objects.equal(a.getRequestCostMetrics(), b.getRequestCostMetrics())
- && Objects.equal(a.getUtilizationMetrics(), b.getUtilizationMetrics());
+ && Objects.equal(a.getUtilizationMetrics(), b.getUtilizationMetrics())
+ && Objects.equal(a.getNamedMetrics(), b.getNamedMetrics());
}
/**
diff --git a/xds/third_party/envoy/import.sh b/xds/third_party/envoy/import.sh
index c4b5a8516f3b..b85b06678001 100755
--- a/xds/third_party/envoy/import.sh
+++ b/xds/third_party/envoy/import.sh
@@ -13,15 +13,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# Update VERSION then in this directory run ./import.sh
+# Update VERSION then execute this script
set -e
-BRANCH=main
# import VERSION from the google internal copybara_version.txt for Envoy
VERSION=0478eba2a495027bf6ac8e787c42e2f5b9eb553b
-GIT_REPO="https://github.com/envoyproxy/envoy.git"
-GIT_BASE_DIR=envoy
-SOURCE_PROTO_BASE_DIR=envoy/api
+DOWNLOAD_URL="https://github.com/envoyproxy/envoy/archive/${VERSION}.tar.gz"
+DOWNLOAD_BASE_DIR="envoy-${VERSION}"
+SOURCE_PROTO_BASE_DIR="${DOWNLOAD_BASE_DIR}/api"
TARGET_PROTO_BASE_DIR=src/main/proto
# Sorted alphabetically.
FILES=(
@@ -181,19 +180,13 @@ envoy/type/v3/semantic_version.proto
pushd `git rev-parse --show-toplevel`/xds/third_party/envoy
-# clone the envoy github repo in a tmp directory
+# put the repo in a tmp directory
tmpdir="$(mktemp -d)"
trap "rm -rf ${tmpdir}" EXIT
+curl -Ls "${DOWNLOAD_URL}" | tar xz -C "${tmpdir}"
-pushd "${tmpdir}"
-git clone -b $BRANCH $GIT_REPO
-trap "rm -rf $GIT_BASE_DIR" EXIT
-cd "$GIT_BASE_DIR"
-git checkout $VERSION
-popd
-
-cp -p "${tmpdir}/${GIT_BASE_DIR}/LICENSE" LICENSE
-cp -p "${tmpdir}/${GIT_BASE_DIR}/NOTICE" NOTICE
+cp -p "${tmpdir}/${DOWNLOAD_BASE_DIR}/LICENSE" LICENSE
+cp -p "${tmpdir}/${DOWNLOAD_BASE_DIR}/NOTICE" NOTICE
rm -rf "${TARGET_PROTO_BASE_DIR}"
mkdir -p "${TARGET_PROTO_BASE_DIR}"
diff --git a/xds/third_party/googleapis/import.sh b/xds/third_party/googleapis/import.sh
index e83536564e16..d51893e23d42 100755
--- a/xds/third_party/googleapis/import.sh
+++ b/xds/third_party/googleapis/import.sh
@@ -13,14 +13,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# Update VERSION then in this directory run ./import.sh
+# Update VERSION then execute this script
set -e
-BRANCH=master
VERSION=ca1372c6d7bcb199638ebfdb40d2b2660bab7b88
-GIT_REPO="https://github.com/googleapis/googleapis.git"
-GIT_BASE_DIR=googleapis
-SOURCE_PROTO_BASE_DIR=googleapis
+DOWNLOAD_URL="https://github.com/googleapis/googleapis/archive/${VERSION}.tar.gz"
+DOWNLOAD_BASE_DIR="googleapis-${VERSION}"
+SOURCE_PROTO_BASE_DIR="${DOWNLOAD_BASE_DIR}"
TARGET_PROTO_BASE_DIR=src/main/proto
# Sorted alphabetically.
FILES=(
@@ -30,18 +29,12 @@ google/api/expr/v1alpha1/syntax.proto
pushd `git rev-parse --show-toplevel`/xds/third_party/googleapis
-# clone the googleapis github repo in a tmp directory
+# put the repo in a tmp directory
tmpdir="$(mktemp -d)"
trap "rm -rf ${tmpdir}" EXIT
+curl -Ls "${DOWNLOAD_URL}" | tar xz -C "${tmpdir}"
-pushd "${tmpdir}"
-git clone -b $BRANCH $GIT_REPO
-trap "rm -rf $GIT_BASE_DIR" EXIT
-cd "$GIT_BASE_DIR"
-git checkout $VERSION
-popd
-
-cp -p "${tmpdir}/${GIT_BASE_DIR}/LICENSE" LICENSE
+cp -p "${tmpdir}/${DOWNLOAD_BASE_DIR}/LICENSE" LICENSE
rm -rf "${TARGET_PROTO_BASE_DIR}"
mkdir -p "${TARGET_PROTO_BASE_DIR}"
diff --git a/xds/third_party/protoc-gen-validate/import.sh b/xds/third_party/protoc-gen-validate/import.sh
index 4e30b0e1180a..64c92b16c200 100755
--- a/xds/third_party/protoc-gen-validate/import.sh
+++ b/xds/third_party/protoc-gen-validate/import.sh
@@ -13,33 +13,31 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# Update GIT_ORIGIN_REV_ID then in this directory run ./import.sh
+# Update VERSION then execute this script
set -e
-BRANCH=main
-# import GIT_ORIGIN_REV_ID from one of the google internal CLs
-GIT_ORIGIN_REV_ID=dfcdc5ea103dda467963fb7079e4df28debcfd28
-GIT_REPO="https://github.com/envoyproxy/protoc-gen-validate.git"
-GIT_BASE_DIR=protoc-gen-validate
-SOURCE_PROTO_BASE_DIR=protoc-gen-validate
+# import VERSION from one of the google internal CLs
+VERSION=dfcdc5ea103dda467963fb7079e4df28debcfd28
+DOWNLOAD_URL="https://github.com/envoyproxy/protoc-gen-validate/archive/${VERSION}.tar.gz"
+DOWNLOAD_BASE_DIR="protoc-gen-validate-${VERSION}"
+SOURCE_PROTO_BASE_DIR="${DOWNLOAD_BASE_DIR}"
TARGET_PROTO_BASE_DIR=src/main/proto
# Sorted alphabetically.
FILES=(
validate/validate.proto
)
-# clone the protoc-gen-validate github repo in a tmp directory
+pushd `git rev-parse --show-toplevel`/xds/third_party/protoc-gen-validate
+
+# put the repo in a tmp directory
tmpdir="$(mktemp -d)"
-pushd "${tmpdir}"
-rm -rf "$GIT_BASE_DIR"
-git clone -b $BRANCH $GIT_REPO
-cd "$GIT_BASE_DIR"
-git checkout $GIT_ORIGIN_REV_ID
-popd
+trap "rm -rf ${tmpdir}" EXIT
+curl -Ls "${DOWNLOAD_URL}" | tar xz -C "${tmpdir}"
-cp -p "${tmpdir}/${GIT_BASE_DIR}/LICENSE" LICENSE
-cp -p "${tmpdir}/${GIT_BASE_DIR}/NOTICE" NOTICE
+cp -p "${tmpdir}/${DOWNLOAD_BASE_DIR}/LICENSE" LICENSE
+cp -p "${tmpdir}/${DOWNLOAD_BASE_DIR}/NOTICE" NOTICE
+rm -rf "${TARGET_PROTO_BASE_DIR}"
mkdir -p "${TARGET_PROTO_BASE_DIR}"
pushd "${TARGET_PROTO_BASE_DIR}"
@@ -51,4 +49,4 @@ do
done
popd
-rm -rf "$tmpdir"
+popd
diff --git a/xds/third_party/xds/import.sh b/xds/third_party/xds/import.sh
index f759cb0d35f7..cda86d0368f0 100755
--- a/xds/third_party/xds/import.sh
+++ b/xds/third_party/xds/import.sh
@@ -13,15 +13,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# Update VERSION then in this directory run ./import.sh
+# Update VERSION then execute this script
set -e
-BRANCH=main
# import VERSION from one of the google internal CLs
VERSION=e9ce68804cb4e64cab5a52e3c8baf840d4ff87b7
-GIT_REPO="https://github.com/cncf/xds.git"
-GIT_BASE_DIR=xds
-SOURCE_PROTO_BASE_DIR=xds
+DOWNLOAD_URL="https://github.com/cncf/xds/archive/${VERSION}.tar.gz"
+DOWNLOAD_BASE_DIR="xds-${VERSION}"
+SOURCE_PROTO_BASE_DIR="${DOWNLOAD_BASE_DIR}"
TARGET_PROTO_BASE_DIR=src/main/proto
# Sorted alphabetically.
FILES=(
@@ -54,18 +53,12 @@ xds/type/v3/typed_struct.proto
pushd `git rev-parse --show-toplevel`/xds/third_party/xds
-# clone the xds github repo in a tmp directory
+# put the repo in a tmp directory
tmpdir="$(mktemp -d)"
-trap "rm -rf $tmpdir" EXIT
+trap "rm -rf ${tmpdir}" EXIT
+curl -Ls "${DOWNLOAD_URL}" | tar xz -C "${tmpdir}"
-pushd "${tmpdir}"
-git clone -b $BRANCH $GIT_REPO
-trap "rm -rf $GIT_BASE_DIR" EXIT
-cd "$GIT_BASE_DIR"
-git checkout $VERSION
-popd
-
-cp -p "${tmpdir}/${GIT_BASE_DIR}/LICENSE" LICENSE
+cp -p "${tmpdir}/${DOWNLOAD_BASE_DIR}/LICENSE" LICENSE
rm -rf "${TARGET_PROTO_BASE_DIR}"
mkdir -p "${TARGET_PROTO_BASE_DIR}"