diff --git a/COMPILING.md b/COMPILING.md
index 1423ec88d78..026aec138ed 100644
--- a/COMPILING.md
+++ b/COMPILING.md
@@ -44,11 +44,11 @@ This section is only necessary if you are making changes to the code
generation. Most users only need to use `skipCodegen=true` as discussed above.
### Build Protobuf
-The codegen plugin is C++ code and requires protobuf 3.21.1 or later.
+The codegen plugin is C++ code and requires protobuf 3.21.7 or later.
For Linux, Mac and MinGW:
```
-$ PROTOBUF_VERSION=3.21.1
+$ PROTOBUF_VERSION=3.21.7
$ curl -LO https://github.com/protocolbuffers/protobuf/releases/download/v$PROTOBUF_VERSION/protobuf-all-$PROTOBUF_VERSION.tar.gz
$ tar xzf protobuf-all-$PROTOBUF_VERSION.tar.gz
$ cd protobuf-$PROTOBUF_VERSION
diff --git a/README.md b/README.md
index f906b351ec7..7775e726ba0 100644
--- a/README.md
+++ b/README.md
@@ -31,8 +31,8 @@ For a guided tour, take a look at the [quick start
guide](https://grpc.io/docs/languages/java/quickstart) or the more explanatory [gRPC
basics](https://grpc.io/docs/languages/java/basics).
-The [examples](https://github.com/grpc/grpc-java/tree/v1.48.1/examples) and the
-[Android example](https://github.com/grpc/grpc-java/tree/v1.48.1/examples/android)
+The [examples](https://github.com/grpc/grpc-java/tree/v1.50.0/examples) and the
+[Android example](https://github.com/grpc/grpc-java/tree/v1.50.0/examples/android)
are standalone projects that showcase the usage of gRPC.
Download
@@ -43,18 +43,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`:
io.grpcgrpc-netty-shaded
- 1.48.1
+ 1.50.0runtimeio.grpcgrpc-protobuf
- 1.48.1
+ 1.50.0io.grpcgrpc-stub
- 1.48.1
+ 1.50.0org.apache.tomcat
@@ -66,23 +66,23 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`:
Or for Gradle with non-Android, add to your dependencies:
```gradle
-runtimeOnly 'io.grpc:grpc-netty-shaded:1.48.1'
-implementation 'io.grpc:grpc-protobuf:1.48.1'
-implementation 'io.grpc:grpc-stub:1.48.1'
+runtimeOnly 'io.grpc:grpc-netty-shaded:1.50.0'
+implementation 'io.grpc:grpc-protobuf:1.50.0'
+implementation 'io.grpc:grpc-stub:1.50.0'
compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+
```
For Android client, use `grpc-okhttp` instead of `grpc-netty-shaded` and
`grpc-protobuf-lite` instead of `grpc-protobuf`:
```gradle
-implementation 'io.grpc:grpc-okhttp:1.48.1'
-implementation 'io.grpc:grpc-protobuf-lite:1.48.1'
-implementation 'io.grpc:grpc-stub:1.48.1'
+implementation 'io.grpc:grpc-okhttp:1.50.0'
+implementation 'io.grpc:grpc-protobuf-lite:1.50.0'
+implementation 'io.grpc:grpc-stub:1.50.0'
compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+
```
[the JARs]:
-https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.48.1
+https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.50.0
Development snapshots are available in [Sonatypes's snapshot
repository](https://oss.sonatype.org/content/repositories/snapshots/).
@@ -112,9 +112,9 @@ For protobuf-based codegen integrated with the Maven build system, you can use
protobuf-maven-plugin0.6.1
- com.google.protobuf:protoc:3.21.1:exe:${os.detected.classifier}
+ com.google.protobuf:protoc:3.21.7:exe:${os.detected.classifier}grpc-java
- io.grpc:protoc-gen-grpc-java:1.48.1:exe:${os.detected.classifier}
+ io.grpc:protoc-gen-grpc-java:1.50.0:exe:${os.detected.classifier}
@@ -140,11 +140,11 @@ plugins {
protobuf {
protoc {
- artifact = "com.google.protobuf:protoc:3.21.1"
+ artifact = "com.google.protobuf:protoc:3.21.7"
}
plugins {
grpc {
- artifact = 'io.grpc:protoc-gen-grpc-java:1.48.1'
+ artifact = 'io.grpc:protoc-gen-grpc-java:1.50.0'
}
}
generateProtoTasks {
@@ -173,11 +173,11 @@ plugins {
protobuf {
protoc {
- artifact = "com.google.protobuf:protoc:3.21.1"
+ artifact = "com.google.protobuf:protoc:3.21.7"
}
plugins {
grpc {
- artifact = 'io.grpc:protoc-gen-grpc-java:1.48.1'
+ artifact = 'io.grpc:protoc-gen-grpc-java:1.50.0'
}
}
generateProtoTasks {
diff --git a/SECURITY.md b/SECURITY.md
index b78850f0804..1d36eb90103 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -411,7 +411,8 @@ grpc-netty version | netty-handler version | netty-tcnative-boringssl-static ver
1.35.x-1.41.x | 4.1.52.Final | 2.0.34.Final
1.42.x-1.43.x | 4.1.63.Final | 2.0.38.Final
1.44.x-1.47.x | 4.1.72.Final | 2.0.46.Final
-1.48.x- | 4.1.77.Final | 2.0.53.Final
+1.48.x-1.49.x | 4.1.77.Final | 2.0.53.Final
+1.50.x- | 4.1.79.Final | 2.0.54.Final
_(grpc-netty-shaded avoids issues with keeping these versions in sync.)_
diff --git a/alts/build.gradle b/alts/build.gradle
index 90d2f0516de..926a3d4993b 100644
--- a/alts/build.gradle
+++ b/alts/build.gradle
@@ -45,7 +45,7 @@ dependencies {
classifier = "linux-x86_64"
}
}
- signature 'org.codehaus.mojo.signature:java17:1.0@signature'
+ signature libraries.signature.java
}
configureProtoCompilation()
diff --git a/alts/src/main/java/io/grpc/alts/AltsContext.java b/alts/src/main/java/io/grpc/alts/AltsContext.java
index f264ad112d7..7680de4160e 100644
--- a/alts/src/main/java/io/grpc/alts/AltsContext.java
+++ b/alts/src/main/java/io/grpc/alts/AltsContext.java
@@ -20,7 +20,6 @@
import io.grpc.alts.internal.AltsInternalContext;
import io.grpc.alts.internal.HandshakerResult;
import io.grpc.alts.internal.Identity;
-import io.grpc.alts.internal.SecurityLevel;
/** {@code AltsContext} contains security-related information on the ALTS channel. */
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/7864")
diff --git a/alts/src/main/java/io/grpc/alts/internal/AltsHandshakerStub.java b/alts/src/main/java/io/grpc/alts/internal/AltsHandshakerStub.java
index 61d9fd2f894..bb2ff9dbc4d 100644
--- a/alts/src/main/java/io/grpc/alts/internal/AltsHandshakerStub.java
+++ b/alts/src/main/java/io/grpc/alts/internal/AltsHandshakerStub.java
@@ -64,12 +64,18 @@ public HandshakerResp send(HandshakerReq req) throws InterruptedException, IOExc
if (!responseQueue.isEmpty()) {
throw new IOException("Received an unexpected response.");
}
+
writer.onNext(req);
Optional result = responseQueue.take();
- if (!result.isPresent()) {
- maybeThrowIoException();
+ if (result.isPresent()) {
+ return result.get();
+ }
+
+ if (exceptionMessage.get() != null) {
+ throw new IOException(exceptionMessage.get());
+ } else {
+ throw new IOException("No handshaker response received");
}
- return result.get();
}
/** Create a new writer if the writer is null. */
diff --git a/alts/src/main/java/io/grpc/alts/internal/TsiHandshaker.java b/alts/src/main/java/io/grpc/alts/internal/TsiHandshaker.java
index 35b945770d2..c6da647168f 100644
--- a/alts/src/main/java/io/grpc/alts/internal/TsiHandshaker.java
+++ b/alts/src/main/java/io/grpc/alts/internal/TsiHandshaker.java
@@ -86,7 +86,7 @@ public interface TsiHandshaker {
*
* @return the extracted peer.
*/
- public Object extractPeerObject() throws GeneralSecurityException;
+ Object extractPeerObject() throws GeneralSecurityException;
/**
* Creates a frame protector from a completed handshake. No other methods may be called after the
diff --git a/api/build.gradle b/api/build.gradle
index f38bbd38d6c..05cd80674c3 100644
--- a/api/build.gradle
+++ b/api/build.gradle
@@ -24,8 +24,8 @@ dependencies {
}
jmh project(':grpc-core')
- signature "org.codehaus.mojo.signature:java17:1.0@signature"
- signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature"
+ signature libraries.signature.java
+ signature libraries.signature.android
}
tasks.named("javadoc").configure {
diff --git a/api/src/main/java/io/grpc/CallCredentials.java b/api/src/main/java/io/grpc/CallCredentials.java
index 3e588e24027..1d353fb9f6d 100644
--- a/api/src/main/java/io/grpc/CallCredentials.java
+++ b/api/src/main/java/io/grpc/CallCredentials.java
@@ -91,6 +91,13 @@ public abstract static class RequestInfo {
*/
public abstract MethodDescriptor, ?> getMethodDescriptor();
+ /**
+ * The call options used to call this RPC.
+ */
+ public CallOptions getCallOptions() {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
/**
* The security level on the transport.
*/
diff --git a/api/src/main/java/io/grpc/InternalMayRequireSpecificExecutor.java b/api/src/main/java/io/grpc/InternalMayRequireSpecificExecutor.java
new file mode 100644
index 00000000000..0be807ce9dc
--- /dev/null
+++ b/api/src/main/java/io/grpc/InternalMayRequireSpecificExecutor.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2022 The gRPC Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.grpc;
+
+@Internal
+public interface InternalMayRequireSpecificExecutor {
+ boolean isSpecificExecutorRequired();
+}
diff --git a/api/src/main/java/io/grpc/InternalMetadata.java b/api/src/main/java/io/grpc/InternalMetadata.java
index 2823882952f..cbf6b72aaf0 100644
--- a/api/src/main/java/io/grpc/InternalMetadata.java
+++ b/api/src/main/java/io/grpc/InternalMetadata.java
@@ -19,6 +19,7 @@
import com.google.common.io.BaseEncoding;
import io.grpc.Metadata.AsciiMarshaller;
import io.grpc.Metadata.BinaryStreamMarshaller;
+import java.io.InputStream;
import java.nio.charset.Charset;
/**
@@ -100,7 +101,7 @@ public static Object[] serializePartial(Metadata md) {
/**
* Creates a holder for a pre-parsed value read by the transport.
*
- * @param marshaller The {@link Metadata#BinaryStreamMarshaller} associated with this value.
+ * @param marshaller The {@link Metadata.BinaryStreamMarshaller} associated with this value.
* @param value The value to store.
* @return an object holding the pre-parsed value for this key.
*/
diff --git a/api/src/main/java/io/grpc/InternalStatus.java b/api/src/main/java/io/grpc/InternalStatus.java
index 9f6854a2de2..b6549bb435f 100644
--- a/api/src/main/java/io/grpc/InternalStatus.java
+++ b/api/src/main/java/io/grpc/InternalStatus.java
@@ -16,6 +16,8 @@
package io.grpc;
+import javax.annotation.Nullable;
+
/**
* Accesses internal data. Do not use this.
*/
@@ -34,4 +36,14 @@ private InternalStatus() {}
*/
@Internal
public static final Metadata.Key CODE_KEY = Status.CODE_KEY;
+
+ /**
+ * Create a new {@link StatusRuntimeException} with the internal option of skipping the filling
+ * of the stack trace.
+ */
+ @Internal
+ public static final StatusRuntimeException asRuntimeException(Status status,
+ @Nullable Metadata trailers, boolean fillInStackTrace) {
+ return new StatusRuntimeException(status, trailers, fillInStackTrace);
+ }
}
diff --git a/api/src/main/java/io/grpc/LoadBalancer.java b/api/src/main/java/io/grpc/LoadBalancer.java
index 8d74216b36f..6469e33b907 100644
--- a/api/src/main/java/io/grpc/LoadBalancer.java
+++ b/api/src/main/java/io/grpc/LoadBalancer.java
@@ -124,39 +124,46 @@ public abstract class LoadBalancer {
*
*
Implementations should not modify the given {@code servers}.
*
- * @param servers the resolved server addresses, never empty.
- * @param attributes extra information from naming system.
- * @deprecated override {@link #handleResolvedAddresses(ResolvedAddresses) instead}
- * @since 1.2.0
+ * @param resolvedAddresses the resolved server addresses, attributes, and config.
+ * @since 1.21.0
*/
- @Deprecated
- public void handleResolvedAddressGroups(
- List servers,
- @NameResolver.ResolutionResultAttr Attributes attributes) {
+ public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) {
if (recursionCount++ == 0) {
- handleResolvedAddresses(
- ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(attributes).build());
+ // Note that the information about the addresses actually being accepted will be lost
+ // if you rely on this method for backward compatibility.
+ acceptResolvedAddresses(resolvedAddresses);
}
recursionCount = 0;
}
/**
- * Handles newly resolved server groups and metadata attributes from name resolution system.
- * {@code servers} contained in {@link EquivalentAddressGroup} should be considered equivalent
- * but may be flattened into a single list if needed.
+ * Accepts newly resolved addresses from the name resolution system. The {@link
+ * EquivalentAddressGroup} addresses should be considered equivalent but may be flattened into a
+ * single list if needed.
*
- *
Implementations should not modify the given {@code servers}.
+ *
Implementations can choose to reject the given addresses by returning {@code false}.
+ *
+ *
Implementations should not modify the given {@code addresses}.
*
* @param resolvedAddresses the resolved server addresses, attributes, and config.
- * @since 1.21.0
+ * @return {@code true} if the resolved addresses were accepted. {@code false} if rejected.
+ * @since 1.49.0
*/
- @SuppressWarnings("deprecation")
- public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) {
- if (recursionCount++ == 0) {
- handleResolvedAddressGroups(
- resolvedAddresses.getAddresses(), resolvedAddresses.getAttributes());
+ public boolean acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
+ if (resolvedAddresses.getAddresses().isEmpty()
+ && !canHandleEmptyAddressListFromNameResolution()) {
+ handleNameResolutionError(Status.UNAVAILABLE.withDescription(
+ "NameResolver returned no usable address. addrs=" + resolvedAddresses.getAddresses()
+ + ", attrs=" + resolvedAddresses.getAttributes()));
+ return false;
+ } else {
+ if (recursionCount++ == 0) {
+ handleResolvedAddresses(resolvedAddresses);
+ }
+ recursionCount = 0;
+
+ return true;
}
- recursionCount = 0;
}
/**
@@ -1073,8 +1080,10 @@ public void refreshNameResolution() {
* that need to be updated for the new expected behavior.
*
* @since 1.38.0
+ * @deprecated Warning has been removed
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/8088")
+ @Deprecated
public void ignoreRefreshNameResolutionCheck() {
// no-op
}
@@ -1356,13 +1365,19 @@ public interface SubchannelStateListener {
* unnecessary delays of RPCs. Please refer to {@link PickResult#withSubchannel
* PickResult.withSubchannel()}'s javadoc for more information.
*
+ *
When a subchannel's state is IDLE or TRANSIENT_FAILURE and the address for the subchannel
+ * was received in {@link LoadBalancer#handleResolvedAddresses}, load balancers should call
+ * {@link Helper#refreshNameResolution} to inform polling name resolvers that it is an
+ * appropriate time to refresh the addresses. Without the refresh, changes to the addresses may
+ * never be detected.
+ *
*
SHUTDOWN can only happen in two cases. One is that LoadBalancer called {@link
* Subchannel#shutdown} earlier, thus it should have already discarded this Subchannel. The
* other is that Channel is doing a {@link ManagedChannel#shutdownNow forced shutdown} or has
* already terminated, thus there won't be further requests to LoadBalancer. Therefore, the
* LoadBalancer usually don't need to react to a SHUTDOWN state.
- * @param newState the new state
*
+ * @param newState the new state
* @since 1.22.0
*/
void onSubchannelState(ConnectivityStateInfo newState);
diff --git a/api/src/main/java/io/grpc/Metadata.java b/api/src/main/java/io/grpc/Metadata.java
index 9c2a2227f8c..6311762601c 100644
--- a/api/src/main/java/io/grpc/Metadata.java
+++ b/api/src/main/java/io/grpc/Metadata.java
@@ -211,8 +211,8 @@ private int len() {
return size * 2;
}
+ /** checks when {@link #namesAndValues} is null or has no elements. */
private boolean isEmpty() {
- /** checks when {@link #namesAndValues} is null or has no elements */
return size == 0;
}
diff --git a/api/src/main/java/io/grpc/Status.java b/api/src/main/java/io/grpc/Status.java
index 1ad5abc0539..f6e88eab8dd 100644
--- a/api/src/main/java/io/grpc/Status.java
+++ b/api/src/main/java/io/grpc/Status.java
@@ -50,6 +50,10 @@
*
*
Utility functions are provided to convert a status to an exception and to extract them
* back out.
+ *
+ *
Extended descriptions, including a list of codes that should not be generated by the library,
+ * can be found at
+ * doc/statuscodes.md
*/
@Immutable
@CheckReturnValue
diff --git a/api/src/main/java/io/grpc/SynchronizationContext.java b/api/src/main/java/io/grpc/SynchronizationContext.java
index 03d26b55f0a..fe4243ec227 100644
--- a/api/src/main/java/io/grpc/SynchronizationContext.java
+++ b/api/src/main/java/io/grpc/SynchronizationContext.java
@@ -163,6 +163,38 @@ public String toString() {
return new ScheduledHandle(runnable, future);
}
+ /**
+ * Schedules a task to be added and run via {@link #execute} after an inital delay and then
+ * repeated after the delay until cancelled.
+ *
+ * @param task the task being scheduled
+ * @param initialDelay the delay before the first run
+ * @param delay the delay after the first run.
+ * @param unit the time unit for the delay
+ * @param timerService the {@code ScheduledExecutorService} that provides delayed execution
+ *
+ * @return an object for checking the status and/or cancel the scheduled task
+ */
+ public final ScheduledHandle scheduleWithFixedDelay(
+ final Runnable task, long initialDelay, long delay, TimeUnit unit,
+ ScheduledExecutorService timerService) {
+ final ManagedRunnable runnable = new ManagedRunnable(task);
+ ScheduledFuture> future = timerService.scheduleWithFixedDelay(new Runnable() {
+ @Override
+ public void run() {
+ execute(runnable);
+ }
+
+ @Override
+ public String toString() {
+ return task.toString() + "(scheduled in SynchronizationContext with delay of " + delay
+ + ")";
+ }
+ }, initialDelay, delay, unit);
+ return new ScheduledHandle(runnable, future);
+ }
+
+
private static class ManagedRunnable implements Runnable {
final Runnable task;
boolean isCancelled;
diff --git a/api/src/test/java/io/grpc/LoadBalancerRegistryTest.java b/api/src/test/java/io/grpc/LoadBalancerRegistryTest.java
index e8588e5e8d8..3debc871121 100644
--- a/api/src/test/java/io/grpc/LoadBalancerRegistryTest.java
+++ b/api/src/test/java/io/grpc/LoadBalancerRegistryTest.java
@@ -41,7 +41,7 @@ public void getClassesViaHardcoded_classesPresent() throws Exception {
@Test
public void stockProviders() {
LoadBalancerRegistry defaultRegistry = LoadBalancerRegistry.getDefaultRegistry();
- assertThat(defaultRegistry.providers()).hasSize(3);
+ assertThat(defaultRegistry.providers()).hasSize(4);
LoadBalancerProvider pickFirst = defaultRegistry.getProvider("pick_first");
assertThat(pickFirst).isInstanceOf(PickFirstLoadBalancerProvider.class);
@@ -52,6 +52,12 @@ public void stockProviders() {
"io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider");
assertThat(roundRobin.getPriority()).isEqualTo(5);
+ LoadBalancerProvider outlierDetection = defaultRegistry.getProvider(
+ "outlier_detection_experimental");
+ assertThat(outlierDetection.getClass().getName()).isEqualTo(
+ "io.grpc.util.OutlierDetectionLoadBalancerProvider");
+ assertThat(roundRobin.getPriority()).isEqualTo(5);
+
LoadBalancerProvider grpclb = defaultRegistry.getProvider("grpclb");
assertThat(grpclb).isInstanceOf(GrpclbLoadBalancerProvider.class);
assertThat(grpclb.getPriority()).isEqualTo(5);
diff --git a/api/src/test/java/io/grpc/LoadBalancerTest.java b/api/src/test/java/io/grpc/LoadBalancerTest.java
index be3d10ba2ae..beaf3335e2c 100644
--- a/api/src/test/java/io/grpc/LoadBalancerTest.java
+++ b/api/src/test/java/io/grpc/LoadBalancerTest.java
@@ -235,13 +235,14 @@ public void createSubchannelArgs_toString() {
@Deprecated
@Test
- public void handleResolvedAddressGroups_delegatesToHandleResolvedAddresses() {
+ public void handleResolvedAddresses_delegatesToAcceptResolvedAddresses() {
final AtomicReference resultCapture = new AtomicReference<>();
LoadBalancer balancer = new LoadBalancer() {
@Override
- public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) {
+ public boolean acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
resultCapture.set(resolvedAddresses);
+ return true;
}
@Override
@@ -260,23 +261,22 @@ public void shutdown() {
List servers = Arrays.asList(
new EquivalentAddressGroup(new SocketAddress(){}),
new EquivalentAddressGroup(new SocketAddress(){}));
- balancer.handleResolvedAddressGroups(servers, attrs);
+ ResolvedAddresses addresses = ResolvedAddresses.newBuilder().setAddresses(servers)
+ .setAttributes(attrs).build();
+ balancer.handleResolvedAddresses(addresses);
assertThat(resultCapture.get()).isEqualTo(
ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(attrs).build());
}
@Deprecated
@Test
- public void handleResolvedAddresses_delegatesToHandleResolvedAddressGroups() {
- final AtomicReference> serversCapture = new AtomicReference<>();
- final AtomicReference attrsCapture = new AtomicReference<>();
+ public void acceptResolvedAddresses_delegatesToHandleResolvedAddressGroups() {
+ final AtomicReference addressesCapture = new AtomicReference<>();
LoadBalancer balancer = new LoadBalancer() {
@Override
- public void handleResolvedAddressGroups(
- List servers, Attributes attrs) {
- serversCapture.set(servers);
- attrsCapture.set(attrs);
+ public void handleResolvedAddresses(ResolvedAddresses addresses) {
+ addressesCapture.set(addresses);
}
@Override
@@ -295,25 +295,23 @@ public void shutdown() {
List servers = Arrays.asList(
new EquivalentAddressGroup(new SocketAddress(){}),
new EquivalentAddressGroup(new SocketAddress(){}));
- balancer.handleResolvedAddresses(
- ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(attrs).build());
- assertThat(serversCapture.get()).isEqualTo(servers);
- assertThat(attrsCapture.get()).isEqualTo(attrs);
+ ResolvedAddresses addresses = ResolvedAddresses.newBuilder().setAddresses(servers)
+ .setAttributes(attrs).build();
+ balancer.handleResolvedAddresses(addresses);
+ assertThat(addressesCapture.get().getAddresses()).isEqualTo(servers);
+ assertThat(addressesCapture.get().getAttributes()).isEqualTo(attrs);
}
@Deprecated
@Test
- public void handleResolvedAddresses_noInfiniteLoop() {
- final List> serversCapture = new ArrayList<>();
- final List attrsCapture = new ArrayList<>();
+ public void acceptResolvedAddresses_noInfiniteLoop() {
+ final List addressesCapture = new ArrayList<>();
LoadBalancer balancer = new LoadBalancer() {
@Override
- public void handleResolvedAddressGroups(
- List servers, Attributes attrs) {
- serversCapture.add(servers);
- attrsCapture.add(attrs);
- super.handleResolvedAddressGroups(servers, attrs);
+ public void handleResolvedAddresses(ResolvedAddresses addresses) {
+ addressesCapture.add(addresses);
+ super.handleResolvedAddresses(addresses);
}
@Override
@@ -328,12 +326,12 @@ public void shutdown() {
List servers = Arrays.asList(
new EquivalentAddressGroup(new SocketAddress(){}),
new EquivalentAddressGroup(new SocketAddress(){}));
- balancer.handleResolvedAddresses(
- ResolvedAddresses.newBuilder().setAddresses(servers).setAttributes(attrs).build());
- assertThat(serversCapture).hasSize(1);
- assertThat(attrsCapture).hasSize(1);
- assertThat(serversCapture.get(0)).isEqualTo(servers);
- assertThat(attrsCapture.get(0)).isEqualTo(attrs);
+ ResolvedAddresses addresses = ResolvedAddresses.newBuilder().setAddresses(servers)
+ .setAttributes(attrs).build();
+ balancer.handleResolvedAddresses(addresses);
+ assertThat(addressesCapture).hasSize(1);
+ assertThat(addressesCapture.get(0).getAddresses()).isEqualTo(servers);
+ assertThat(addressesCapture.get(0).getAttributes()).isEqualTo(attrs);
}
private static class NoopHelper extends LoadBalancer.Helper {
diff --git a/auth/build.gradle b/auth/build.gradle
index e9c9d0b4aad..73c108d5203 100644
--- a/auth/build.gradle
+++ b/auth/build.gradle
@@ -13,6 +13,6 @@ dependencies {
implementation libraries.guava
testImplementation project(':grpc-testing'),
libraries.google.auth.oauth2Http
- signature "org.codehaus.mojo.signature:java17:1.0@signature"
- signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature"
+ signature libraries.signature.java
+ signature libraries.signature.android
}
diff --git a/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java b/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java
index 4b95a6c7f4d..2a414e792d9 100644
--- a/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java
+++ b/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java
@@ -22,6 +22,7 @@
import com.google.auth.RequestMetadataCallback;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.io.BaseEncoding;
+import io.grpc.InternalMayRequireSpecificExecutor;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import io.grpc.SecurityLevel;
@@ -44,13 +45,16 @@
/**
* Wraps {@link Credentials} as a {@link io.grpc.CallCredentials}.
*/
-final class GoogleAuthLibraryCallCredentials extends io.grpc.CallCredentials {
+final class GoogleAuthLibraryCallCredentials extends io.grpc.CallCredentials
+ implements InternalMayRequireSpecificExecutor {
private static final Logger log
= Logger.getLogger(GoogleAuthLibraryCallCredentials.class.getName());
private static final JwtHelper jwtHelper
= createJwtHelperOrNull(GoogleAuthLibraryCallCredentials.class.getClassLoader());
- private static final Class extends Credentials> googleCredentialsClass
+ private static final Class extends Credentials> GOOGLE_CREDENTIALS_CLASS
= loadGoogleCredentialsClass();
+ private static final Class> APP_ENGINE_CREDENTIALS_CLASS
+ = loadAppEngineCredentials();
private final boolean requirePrivacy;
@VisibleForTesting
@@ -59,6 +63,8 @@ final class GoogleAuthLibraryCallCredentials extends io.grpc.CallCredentials {
private Metadata lastHeaders;
private Map> lastMetadata;
+ private Boolean requiresSpecificExecutor;
+
public GoogleAuthLibraryCallCredentials(Credentials creds) {
this(creds, jwtHelper);
}
@@ -67,12 +73,12 @@ public GoogleAuthLibraryCallCredentials(Credentials creds) {
GoogleAuthLibraryCallCredentials(Credentials creds, JwtHelper jwtHelper) {
checkNotNull(creds, "creds");
boolean requirePrivacy = false;
- if (googleCredentialsClass != null) {
+ if (GOOGLE_CREDENTIALS_CLASS != null) {
// All GoogleCredentials instances are bearer tokens and should only be used on private
// channels. This catches all return values from GoogleCredentials.getApplicationDefault().
// This should be checked before upgrading the Service Account to JWT, as JWT is also a bearer
// token.
- requirePrivacy = googleCredentialsClass.isInstance(creds);
+ requirePrivacy = GOOGLE_CREDENTIALS_CLASS.isInstance(creds);
}
if (jwtHelper != null) {
creds = jwtHelper.tryServiceAccountToJwt(creds);
@@ -242,6 +248,16 @@ private static Class extends Credentials> loadGoogleCredentialsClass() {
return rawGoogleCredentialsClass.asSubclass(Credentials.class);
}
+ @Nullable
+ private static Class> loadAppEngineCredentials() {
+ try {
+ return Class.forName("com.google.auth.appengine.AppEngineCredentials");
+ } catch (ClassNotFoundException ex) {
+ log.log(Level.FINE, "AppEngineCredentials not available in classloader", ex);
+ return null;
+ }
+ }
+
private static class MethodPair {
private final Method getter;
private final Method builderSetter;
@@ -298,6 +314,11 @@ public JwtHelper(Class> rawServiceAccountClass, ClassLoader loader)
Method setter = builderClass.getMethod("setPrivateKeyId", getter.getReturnType());
methodPairs.add(new MethodPair(getter, setter));
}
+ {
+ Method getter = serviceAccountClass.getMethod("getQuotaProjectId");
+ Method setter = builderClass.getMethod("setQuotaProjectId", getter.getReturnType());
+ methodPairs.add(new MethodPair(getter, setter));
+ }
}
/**
@@ -348,4 +369,24 @@ public Credentials tryServiceAccountToJwt(Credentials creds) {
return creds;
}
}
+
+ /**
+ * This method is to support the hack for AppEngineCredentials which need to run on a
+ * specific thread.
+ * @return Whether a specific executor is needed or if any executor can be used
+ */
+ @Override
+ public boolean isSpecificExecutorRequired() {
+ // Cache the value so we only need to try to load the class once
+ if (requiresSpecificExecutor == null) {
+ if (APP_ENGINE_CREDENTIALS_CLASS == null) {
+ requiresSpecificExecutor = Boolean.FALSE;
+ } else {
+ requiresSpecificExecutor = APP_ENGINE_CREDENTIALS_CLASS.isInstance(creds);
+ }
+ }
+
+ return requiresSpecificExecutor;
+ }
+
}
diff --git a/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java b/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java
index ee5713bfd27..cbb2afdc3d0 100644
--- a/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java
+++ b/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java
@@ -379,6 +379,7 @@ public void jwtAccessCredentialsInRequestMetadata() throws Exception {
.setClientEmail("test-email@example.com")
.setPrivateKey(pair.getPrivate())
.setPrivateKeyId("test-private-key-id")
+ .setQuotaProjectId("test-quota-project-id")
.build();
GoogleAuthLibraryCallCredentials callCredentials =
new GoogleAuthLibraryCallCredentials(credentials);
@@ -401,6 +402,10 @@ public void jwtAccessCredentialsInRequestMetadata() throws Exception {
|| "https://example.com:123/a.service".equals(payload.get("aud")));
assertEquals("test-email@example.com", payload.get("iss"));
assertEquals("test-email@example.com", payload.get("sub"));
+
+ Metadata.Key quotaProject = Metadata.Key
+ .of("X-Goog-User-Project", Metadata.ASCII_STRING_MARSHALLER);
+ assertEquals("test-quota-project-id", Iterables.getOnlyElement(headers.getAll(quotaProject)));
}
private int runPendingRunnables() {
diff --git a/authz/build.gradle b/authz/build.gradle
index d0126922aca..ce327f14e14 100644
--- a/authz/build.gradle
+++ b/authz/build.gradle
@@ -26,7 +26,7 @@ dependencies {
shadow configurations.implementation.getDependencies().minus([xdsDependency])
shadow project(path: ':grpc-xds', configuration: 'shadow')
- signature "org.codehaus.mojo.signature:java17:1.0@signature"
+ signature libraries.signature.java
}
tasks.named("jar").configure {
diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle
index 17977692ecb..90251e6692f 100644
--- a/benchmarks/build.gradle
+++ b/benchmarks/build.gradle
@@ -5,6 +5,7 @@ plugins {
id "com.google.protobuf"
id "me.champeau.jmh"
+ id "ru.vyarus.animalsniffer"
}
description = "grpc Benchmarks"
@@ -39,6 +40,8 @@ dependencies {
testImplementation libraries.junit,
libraries.mockito.core
+
+ signature libraries.signature.java
}
import net.ltgt.gradle.errorprone.CheckSeverity
diff --git a/binder/build.gradle b/binder/build.gradle
index 24dd0d9015d..41c4bd4cc22 100644
--- a/binder/build.gradle
+++ b/binder/build.gradle
@@ -23,14 +23,14 @@ android {
}
}
}
- compileSdkVersion 29
+ compileSdkVersion 30
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
defaultConfig {
minSdkVersion 19
- targetSdkVersion 29
+ targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -96,6 +96,8 @@ tasks.withType(JavaCompile).configureEach {
tasks.register("javadocs", Javadoc) {
source = android.sourceSets.main.java.srcDirs
+ exclude 'io/grpc/binder/internal/**'
+ exclude 'io/grpc/binder/Internal*'
classpath += files(android.getBootClasspath())
classpath += files({
android.libraryVariants.collect { variant ->
diff --git a/binder/src/main/java/io/grpc/binder/SecurityPolicies.java b/binder/src/main/java/io/grpc/binder/SecurityPolicies.java
index 25f215be696..653ae90bd77 100644
--- a/binder/src/main/java/io/grpc/binder/SecurityPolicies.java
+++ b/binder/src/main/java/io/grpc/binder/SecurityPolicies.java
@@ -17,11 +17,14 @@
package io.grpc.binder;
import android.annotation.SuppressLint;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
import android.os.Build;
+import android.os.Build.VERSION;
import android.os.Process;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
@@ -173,6 +176,50 @@ public Status checkAuthorization(int uid) {
};
}
+ /**
+ * Creates {@link SecurityPolicy} which checks if the app is a device owner app. See
+ * {@link DevicePolicyManager}.
+ */
+ public static SecurityPolicy isDeviceOwner(Context applicationContext) {
+ DevicePolicyManager devicePolicyManager =
+ (DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ return anyPackageWithUidSatisfies(
+ applicationContext,
+ pkg -> VERSION.SDK_INT >= 18 && devicePolicyManager.isDeviceOwnerApp(pkg),
+ "Rejected by device owner policy. No packages found for UID.",
+ "Rejected by device owner policy");
+ }
+
+ /**
+ * Creates {@link SecurityPolicy} which checks if the app is a profile owner app. See
+ * {@link DevicePolicyManager}.
+ */
+ public static SecurityPolicy isProfileOwner(Context applicationContext) {
+ DevicePolicyManager devicePolicyManager =
+ (DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ return anyPackageWithUidSatisfies(
+ applicationContext,
+ pkg -> VERSION.SDK_INT >= 21 && devicePolicyManager.isProfileOwnerApp(pkg),
+ "Rejected by profile owner policy. No packages found for UID.",
+ "Rejected by profile owner policy");
+ }
+
+ /**
+ * Creates {@link SecurityPolicy} which checks if the app is a profile owner app on an
+ * organization-owned device. See {@link DevicePolicyManager}.
+ */
+ public static SecurityPolicy isProfileOwnerOnOrganizationOwnedDevice(Context applicationContext) {
+ DevicePolicyManager devicePolicyManager =
+ (DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ return anyPackageWithUidSatisfies(
+ applicationContext,
+ pkg -> VERSION.SDK_INT >= 30
+ && devicePolicyManager.isProfileOwnerApp(pkg)
+ && devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile(),
+ "Rejected by profile owner on organization-owned device policy. No packages found for UID.",
+ "Rejected by profile owner on organization-owned device policy");
+ }
+
private static Status checkUidSignature(
PackageManager packageManager,
int uid,
@@ -406,6 +453,29 @@ private static Status checkPermissions(
return Status.OK;
}
+ private static SecurityPolicy anyPackageWithUidSatisfies(
+ Context applicationContext,
+ Predicate condition,
+ String errorMessageForNoPackages,
+ String errorMessageForDenied) {
+ return new SecurityPolicy() {
+ @Override
+ public Status checkAuthorization(int uid) {
+ String[] packages = applicationContext.getPackageManager().getPackagesForUid(uid);
+ if (packages == null || packages.length == 0) {
+ return Status.UNAUTHENTICATED.withDescription(errorMessageForNoPackages);
+ }
+
+ for (String pkg : packages) {
+ if (condition.apply(pkg)) {
+ return Status.OK;
+ }
+ }
+ return Status.PERMISSION_DENIED.withDescription(errorMessageForDenied);
+ }
+ };
+ }
+
/**
* Checks if the SHA-256 hash of the {@code signature} matches one of the {@code
* expectedSignatureSha256Hashes}.
diff --git a/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java b/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java
index a26d4dd58af..6a692090549 100644
--- a/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java
+++ b/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java
@@ -24,10 +24,15 @@
import static com.google.common.truth.Truth.assertThat;
import static org.robolectric.Shadows.shadowOf;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
+import android.os.Build;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
import android.os.Process;
import androidx.test.core.app.ApplicationProvider;
import com.google.common.collect.ImmutableList;
@@ -40,6 +45,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
public final class SecurityPoliciesTest {
@@ -59,6 +65,7 @@ public final class SecurityPoliciesTest {
private Context appContext;
private PackageManager packageManager;
+ private DevicePolicyManager devicePolicyManager;
private SecurityPolicy policy;
@@ -66,6 +73,8 @@ public final class SecurityPoliciesTest {
public void setUp() {
appContext = ApplicationProvider.getApplicationContext();
packageManager = appContext.getPackageManager();
+ devicePolicyManager =
+ (DevicePolicyManager) appContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
}
@SuppressWarnings("deprecation")
@@ -323,6 +332,171 @@ public void testHasPermissions_failsIfPackageDoesNotHavePermissions() throws Exc
.contains(OTHER_UID_PACKAGE_NAME);
}
+ @Test
+ @Config(sdk = 18)
+ public void testIsDeviceOwner_succeedsForDeviceOwner() throws Exception {
+ PackageInfo info =
+ newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+ installPackages(OTHER_UID, info);
+ shadowOf(devicePolicyManager)
+ .setDeviceOwner(new ComponentName(OTHER_UID_PACKAGE_NAME, "foo"));
+
+ policy = SecurityPolicies.isDeviceOwner(appContext);
+
+ assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode());
+ }
+
+ @Test
+ @Config(sdk = 18)
+ public void testIsDeviceOwner_failsForNotDeviceOwner() throws Exception {
+ PackageInfo info =
+ newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+ installPackages(OTHER_UID, info);
+
+ policy = SecurityPolicies.isDeviceOwner(appContext);
+
+ assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
+ }
+
+ @Test
+ @Config(sdk = 18)
+ public void testIsDeviceOwner_failsWhenNoPackagesForUid() throws Exception {
+ policy = SecurityPolicies.isDeviceOwner(appContext);
+
+ assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.UNAUTHENTICATED.getCode());
+ }
+
+ @Test
+ @Config(sdk = 17)
+ public void testIsDeviceOwner_failsForSdkLevelTooLow() throws Exception {
+ PackageInfo info =
+ newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+ installPackages(OTHER_UID, info);
+
+ policy = SecurityPolicies.isDeviceOwner(appContext);
+
+ assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
+ }
+
+ @Test
+ @Config(sdk = 21)
+ public void testIsProfileOwner_succeedsForProfileOwner() throws Exception {
+ PackageInfo info =
+ newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+ installPackages(OTHER_UID, info);
+ shadowOf(devicePolicyManager)
+ .setProfileOwner(new ComponentName(OTHER_UID_PACKAGE_NAME, "foo"));
+
+ policy = SecurityPolicies.isProfileOwner(appContext);
+
+ assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode());
+ }
+
+ @Test
+ @Config(sdk = 21)
+ public void testIsProfileOwner_failsForNotProfileOwner() throws Exception {
+ PackageInfo info =
+ newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+ installPackages(OTHER_UID, info);
+
+ policy = SecurityPolicies.isProfileOwner(appContext);
+
+ assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
+ }
+
+ @Test
+ @Config(sdk = 21)
+ public void testIsProfileOwner_failsWhenNoPackagesForUid() throws Exception {
+ policy = SecurityPolicies.isProfileOwner(appContext);
+
+ assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.UNAUTHENTICATED.getCode());
+ }
+
+ @Test
+ @Config(sdk = 19)
+ public void testIsProfileOwner_failsForSdkLevelTooLow() throws Exception {
+ PackageInfo info =
+ newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+ installPackages(OTHER_UID, info);
+
+ policy = SecurityPolicies.isProfileOwner(appContext);
+
+ assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
+ }
+
+ @Test
+ @Config(sdk = 30)
+ public void testIsProfileOwnerOnOrgOwned_succeedsForProfileOwnerOnOrgOwned() throws Exception {
+ PackageInfo info =
+ newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+ installPackages(OTHER_UID, info);
+ shadowOf(devicePolicyManager)
+ .setProfileOwner(new ComponentName(OTHER_UID_PACKAGE_NAME, "foo"));
+ shadowOf(devicePolicyManager).setOrganizationOwnedDeviceWithManagedProfile(true);
+
+ policy = SecurityPolicies.isProfileOwnerOnOrganizationOwnedDevice(appContext);
+
+ assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode());
+
+ }
+
+ @Test
+ @Config(sdk = 30)
+ public void testIsProfileOwnerOnOrgOwned_failsForProfileOwnerOnNonOrgOwned() throws Exception {
+ PackageInfo info =
+ newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+ installPackages(OTHER_UID, info);
+ shadowOf(devicePolicyManager)
+ .setProfileOwner(new ComponentName(OTHER_UID_PACKAGE_NAME, "foo"));
+ shadowOf(devicePolicyManager).setOrganizationOwnedDeviceWithManagedProfile(false);
+
+ policy = SecurityPolicies.isProfileOwnerOnOrganizationOwnedDevice(appContext);
+
+ assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
+ }
+
+ @Test
+ @Config(sdk = 21)
+ public void testIsProfileOwnerOnOrgOwned_failsForNotProfileOwner() throws Exception {
+ PackageInfo info =
+ newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+ installPackages(OTHER_UID, info);
+
+ policy = SecurityPolicies.isProfileOwnerOnOrganizationOwnedDevice(appContext);
+
+ assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
+ }
+
+ @Test
+ @Config(sdk = 21)
+ public void testIsProfileOwnerOnOrgOwned_failsWhenNoPackagesForUid() throws Exception {
+ policy = SecurityPolicies.isProfileOwnerOnOrganizationOwnedDevice(appContext);
+
+ assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.UNAUTHENTICATED.getCode());
+ }
+
+ @Test
+ @Config(sdk = 29)
+ public void testIsProfileOwnerOnOrgOwned_failsForSdkLevelTooLow() throws Exception {
+ PackageInfo info =
+ newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+ installPackages(OTHER_UID, info);
+
+ policy = SecurityPolicies.isProfileOwner(appContext);
+
+ assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
+ }
+
private static PackageInfoBuilder newBuilder() {
return new PackageInfoBuilder();
}
diff --git a/build.gradle b/build.gradle
index a63b9cd5a6a..056cb75994d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -20,7 +20,7 @@ subprojects {
apply plugin: "net.ltgt.errorprone"
group = "io.grpc"
- version = "1.49.0-SNAPSHOT" // CURRENT_GRPC_VERSION
+ version = "1.50.0" // CURRENT_GRPC_VERSION
repositories {
maven { // The google mirror is less flaky than mavenCentral()
diff --git a/buildscripts/kokoro/psm-security.cfg b/buildscripts/kokoro/psm-security.cfg
index f2cfd7babff..9df0bbb7867 100644
--- a/buildscripts/kokoro/psm-security.cfg
+++ b/buildscripts/kokoro/psm-security.cfg
@@ -7,7 +7,7 @@ timeout_mins: 180
action {
define_artifacts {
regex: "artifacts/**/*sponge_log.xml"
- regex: "artifacts/**/*sponge_log.log"
+ regex: "artifacts/**/*.log"
strip_prefix: "artifacts"
}
}
diff --git a/buildscripts/kokoro/psm-security.sh b/buildscripts/kokoro/psm-security.sh
index 201772723dd..defe427e5d7 100755
--- a/buildscripts/kokoro/psm-security.sh
+++ b/buildscripts/kokoro/psm-security.sh
@@ -119,6 +119,8 @@ run_test() {
# Test driver usage:
# https://github.com/grpc/grpc/tree/master/tools/run_tests/xds_k8s_test_driver#basic-usage
local test_name="${1:?Usage: run_test test_name}"
+ local out_dir="${TEST_XML_OUTPUT_DIR}/${test_name}"
+ mkdir -pv "${out_dir}"
set -x
python -m "tests.${test_name}" \
--flagfile="${TEST_DRIVER_FLAGFILE}" \
@@ -126,9 +128,11 @@ run_test() {
--server_image="${SERVER_IMAGE_NAME}:${GIT_COMMIT}" \
--client_image="${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \
--testing_version="${TESTING_VERSION}" \
- --xml_output_file="${TEST_XML_OUTPUT_DIR}/${test_name}/sponge_log.xml" \
- --force_cleanup
- set +x
+ --force_cleanup \
+ --collect_app_logs \
+ --log_dir="${out_dir}" \
+ --xml_output_file="${out_dir}/sponge_log.xml" \
+ |& tee "${out_dir}/sponge_log.log"
}
#######################################
@@ -170,9 +174,15 @@ main() {
build_docker_images_if_needed
# Run tests
cd "${TEST_DRIVER_FULL_DIR}"
- run_test baseline_test
- run_test security_test
- run_test authz_test
+ local failed_tests=0
+ test_suites=("baseline_test" "security_test" "authz_test")
+ for test in "${test_suites[@]}"; do
+ run_test $test || (( failed_tests++ ))
+ done
+ echo "Failed test suites: ${failed_tests}"
+ if (( failed_tests > 0 )); then
+ exit 1
+ fi
}
main "$@"
diff --git a/buildscripts/kokoro/xds_k8s_lb.cfg b/buildscripts/kokoro/xds_k8s_lb.cfg
index 43971896cd4..10ea2d43b5d 100644
--- a/buildscripts/kokoro/xds_k8s_lb.cfg
+++ b/buildscripts/kokoro/xds_k8s_lb.cfg
@@ -7,7 +7,7 @@ timeout_mins: 180
action {
define_artifacts {
regex: "artifacts/**/*sponge_log.xml"
- regex: "artifacts/**/*sponge_log.log"
+ regex: "artifacts/**/*.log"
strip_prefix: "artifacts"
}
}
diff --git a/buildscripts/kokoro/xds_k8s_lb.sh b/buildscripts/kokoro/xds_k8s_lb.sh
index d9e575bb652..6e0277ecb81 100755
--- a/buildscripts/kokoro/xds_k8s_lb.sh
+++ b/buildscripts/kokoro/xds_k8s_lb.sh
@@ -118,6 +118,8 @@ run_test() {
# Test driver usage:
# https://github.com/grpc/grpc/tree/master/tools/run_tests/xds_k8s_test_driver#basic-usage
local test_name="${1:?Usage: run_test test_name}"
+ local out_dir="${TEST_XML_OUTPUT_DIR}/${test_name}"
+ mkdir -pv "${out_dir}"
set -x
python -m "tests.${test_name}" \
--flagfile="${TEST_DRIVER_FLAGFILE}" \
@@ -126,9 +128,11 @@ run_test() {
--server_image="${SERVER_IMAGE_NAME}:${GIT_COMMIT}" \
--client_image="${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \
--testing_version="${TESTING_VERSION}" \
- --xml_output_file="${TEST_XML_OUTPUT_DIR}/${test_name}/sponge_log.xml" \
- --force_cleanup
- set +x
+ --force_cleanup \
+ --collect_app_logs \
+ --log_dir="${out_dir}" \
+ --xml_output_file="${out_dir}/sponge_log.xml" \
+ |& tee "${out_dir}/sponge_log.log"
}
#######################################
@@ -172,7 +176,7 @@ main() {
# Run tests
cd "${TEST_DRIVER_FULL_DIR}"
local failed_tests=0
- test_suites=("api_listener_test" "change_backend_service_test" "failover_test" "remove_neg_test" "round_robin_test" "affinity_test")
+ test_suites=("api_listener_test" "change_backend_service_test" "failover_test" "remove_neg_test" "round_robin_test" "affinity_test" "outlier_detection_test" "custom_lb_test")
for test in "${test_suites[@]}"; do
run_test $test || (( failed_tests++ ))
done
diff --git a/buildscripts/kokoro/xds_url_map.cfg b/buildscripts/kokoro/xds_url_map.cfg
index 4b5be84f880..1fa6c0141cb 100644
--- a/buildscripts/kokoro/xds_url_map.cfg
+++ b/buildscripts/kokoro/xds_url_map.cfg
@@ -7,7 +7,7 @@ timeout_mins: 90
action {
define_artifacts {
regex: "artifacts/**/*sponge_log.xml"
- regex: "artifacts/**/*sponge_log.log"
+ regex: "artifacts/**/*.log"
strip_prefix: "artifacts"
}
}
diff --git a/buildscripts/kokoro/xds_url_map.sh b/buildscripts/kokoro/xds_url_map.sh
index 657f695475d..50737e1569d 100755
--- a/buildscripts/kokoro/xds_url_map.sh
+++ b/buildscripts/kokoro/xds_url_map.sh
@@ -118,15 +118,19 @@ run_test() {
# Test driver usage:
# https://github.com/grpc/grpc/tree/master/tools/run_tests/xds_k8s_test_driver#basic-usage
local test_name="${1:?Usage: run_test test_name}"
+ local out_dir="${TEST_XML_OUTPUT_DIR}/${test_name}"
+ mkdir -pv "${out_dir}"
set -x
python -m "tests.${test_name}" \
--flagfile="${TEST_DRIVER_FLAGFILE}" \
+ --flagfile="config/url-map.cfg" \
--kube_context="${KUBE_CONTEXT}" \
--client_image="${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \
--testing_version="${TESTING_VERSION}" \
- --xml_output_file="${TEST_XML_OUTPUT_DIR}/${test_name}/sponge_log.xml" \
- --flagfile="config/url-map.cfg"
- set +x
+ --collect_app_logs \
+ --log_dir="${out_dir}" \
+ --xml_output_file="${out_dir}/sponge_log.xml" \
+ |& tee "${out_dir}/sponge_log.log"
}
#######################################
diff --git a/buildscripts/make_dependencies.bat b/buildscripts/make_dependencies.bat
index 30e8dd548a1..2bbfd394d46 100644
--- a/buildscripts/make_dependencies.bat
+++ b/buildscripts/make_dependencies.bat
@@ -1,6 +1,4 @@
-set PROTOBUF_VER=21.1
-@rem Workaround https://github.com/protocolbuffers/protobuf/issues/10172
-set PROTOBUF_VER_ISSUE_10172=3.%PROTOBUF_VER%
+set PROTOBUF_VER=21.7
set CMAKE_NAME=cmake-3.3.2-win32-x86
if not exist "protobuf-%PROTOBUF_VER%\build\Release\" (
@@ -25,7 +23,6 @@ set PATH=%PATH%;%cd%\%CMAKE_NAME%\bin
powershell -command "$ErrorActionPreference = 'stop'; & { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; iwr https://github.com/google/protobuf/archive/v%PROTOBUF_VER%.zip -OutFile protobuf.zip }" || exit /b 1
powershell -command "$ErrorActionPreference = 'stop'; & { Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory('protobuf.zip', '.') }" || exit /b 1
del protobuf.zip
-rename protobuf-%PROTOBUF_VER_ISSUE_10172% protobuf-%PROTOBUF_VER%
mkdir protobuf-%PROTOBUF_VER%\build
pushd protobuf-%PROTOBUF_VER%\build
diff --git a/buildscripts/make_dependencies.sh b/buildscripts/make_dependencies.sh
index 8f0033e8a75..29eed3d5050 100755
--- a/buildscripts/make_dependencies.sh
+++ b/buildscripts/make_dependencies.sh
@@ -3,9 +3,7 @@
# Build protoc
set -evux -o pipefail
-PROTOBUF_VERSION=21.1
-# https://github.com/protocolbuffers/protobuf/issues/10172
-PROTOBUF_VERSION_ISSUE_10172=3.$PROTOBUF_VERSION
+PROTOBUF_VERSION=21.7
# ARCH is x86_64 bit unless otherwise specified.
ARCH="${ARCH:-x86_64}"
@@ -30,7 +28,6 @@ if [ -f ${INSTALL_DIR}/bin/protoc ]; then
else
if [[ ! -d "$DOWNLOAD_DIR"/protobuf-"${PROTOBUF_VERSION}" ]]; then
curl -Ls https://github.com/google/protobuf/releases/download/v${PROTOBUF_VERSION}/protobuf-all-${PROTOBUF_VERSION}.tar.gz | tar xz -C $DOWNLOAD_DIR
- mv "$DOWNLOAD_DIR/protobuf-${PROTOBUF_VERSION_ISSUE_10172}" "$DOWNLOAD_DIR/protobuf-${PROTOBUF_VERSION}"
fi
pushd $DOWNLOAD_DIR/protobuf-${PROTOBUF_VERSION}
# install here so we don't need sudo
diff --git a/census/build.gradle b/census/build.gradle
index d09d0b18996..02b7e6395b1 100644
--- a/census/build.gradle
+++ b/census/build.gradle
@@ -1,6 +1,8 @@
plugins {
id "java-library"
id "maven-publish"
+
+ id "ru.vyarus.animalsniffer"
}
description = 'gRPC: Census'
@@ -18,6 +20,9 @@ dependencies {
project(':grpc-core').sourceSets.test.output,
project(':grpc-testing'),
libraries.opencensus.impl
+
+ signature libraries.signature.java
+ signature libraries.signature.android
}
tasks.named("javadoc").configure {
diff --git a/census/src/main/java/io/grpc/census/CensusStatsModule.java b/census/src/main/java/io/grpc/census/CensusStatsModule.java
index 366be55de68..d05368f3ee5 100644
--- a/census/src/main/java/io/grpc/census/CensusStatsModule.java
+++ b/census/src/main/java/io/grpc/census/CensusStatsModule.java
@@ -188,7 +188,7 @@ private static final class ClientTracer extends ClientStreamTracer {
@Nullable
private static final AtomicLongFieldUpdater inboundUncompressedSizeUpdater;
- /**
+ /*
* When using Atomic*FieldUpdater, some Samsung Android 5.0.x devices encounter a bug in their
* JDK reflection API that triggers a NoSuchFieldException. When this occurs, we fallback to
* (potentially racy) direct updates of the volatile variables.
@@ -268,7 +268,7 @@ public void streamCreated(Attributes transportAttrs, Metadata headers) {
}
@Override
- @SuppressWarnings("NonAtomicVolatileUpdate")
+ @SuppressWarnings({"NonAtomicVolatileUpdate", "NonAtomicOperationOnVolatileField"})
public void outboundWireSize(long bytes) {
if (outboundWireSizeUpdater != null) {
outboundWireSizeUpdater.getAndAdd(this, bytes);
@@ -369,13 +369,11 @@ void recordFinishedAttempt() {
// TODO(songya): remove the deprecated measure constants once they are completed removed.
.put(DeprecatedCensusConstants.RPC_CLIENT_FINISHED_COUNT, 1)
// The latency is double value
- .put(
- DeprecatedCensusConstants.RPC_CLIENT_ROUNDTRIP_LATENCY,
- roundtripNanos / NANOS_PER_MILLI)
- .put(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_COUNT, outboundMessageCount)
- .put(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_COUNT, inboundMessageCount)
- .put(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_BYTES, outboundWireSize)
- .put(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_BYTES, inboundWireSize)
+ .put(RpcMeasureConstants.GRPC_CLIENT_ROUNDTRIP_LATENCY, roundtripNanos / NANOS_PER_MILLI)
+ .put(RpcMeasureConstants.GRPC_CLIENT_SENT_MESSAGES_PER_RPC, outboundMessageCount)
+ .put(RpcMeasureConstants.GRPC_CLIENT_RECEIVED_MESSAGES_PER_RPC, inboundMessageCount)
+ .put(RpcMeasureConstants.GRPC_CLIENT_SENT_BYTES_PER_RPC, outboundWireSize)
+ .put(RpcMeasureConstants.GRPC_CLIENT_RECEIVED_BYTES_PER_RPC, inboundWireSize)
.put(
DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES,
outboundUncompressedSize)
@@ -443,7 +441,7 @@ static final class CallAttemptsTracerFactory extends
if (module.recordStartedRpcs) {
// Record here in case newClientStreamTracer() would never be called.
module.statsRecorder.newMeasureMap()
- .put(DeprecatedCensusConstants.RPC_CLIENT_STARTED_COUNT, 1)
+ .put(RpcMeasureConstants.GRPC_CLIENT_STARTED_RPCS, 1)
.record(startCtx);
}
}
@@ -462,7 +460,7 @@ public ClientStreamTracer newClientStreamTracer(StreamInfo info, Metadata metada
}
if (module.recordStartedRpcs && attemptsPerCall.get() > 0) {
module.statsRecorder.newMeasureMap()
- .put(DeprecatedCensusConstants.RPC_CLIENT_STARTED_COUNT, 1)
+ .put(RpcMeasureConstants.GRPC_CLIENT_STARTED_RPCS, 1)
.record(startCtx);
}
if (info.isTransparentRetry()) {
@@ -562,7 +560,7 @@ private static final class ServerTracer extends ServerStreamTracer {
@Nullable
private static final AtomicLongFieldUpdater inboundUncompressedSizeUpdater;
- /**
+ /*
* When using Atomic*FieldUpdater, some Samsung Android 5.0.x devices encounter a bug in their
* JDK reflection API that triggers a NoSuchFieldException. When this occurs, we fallback to
* (potentially racy) direct updates of the volatile variables.
@@ -628,7 +626,7 @@ private static final class ServerTracer extends ServerStreamTracer {
this.stopwatch = module.stopwatchSupplier.get().start();
if (module.recordStartedRpcs) {
module.statsRecorder.newMeasureMap()
- .put(DeprecatedCensusConstants.RPC_SERVER_STARTED_COUNT, 1)
+ .put(RpcMeasureConstants.GRPC_SERVER_STARTED_RPCS, 1)
.record(parentCtx);
}
}
@@ -728,13 +726,11 @@ public void streamClosed(Status status) {
// TODO(songya): remove the deprecated measure constants once they are completed removed.
.put(DeprecatedCensusConstants.RPC_SERVER_FINISHED_COUNT, 1)
// The latency is double value
- .put(
- DeprecatedCensusConstants.RPC_SERVER_SERVER_LATENCY,
- elapsedTimeNanos / NANOS_PER_MILLI)
- .put(DeprecatedCensusConstants.RPC_SERVER_RESPONSE_COUNT, outboundMessageCount)
- .put(DeprecatedCensusConstants.RPC_SERVER_REQUEST_COUNT, inboundMessageCount)
- .put(DeprecatedCensusConstants.RPC_SERVER_RESPONSE_BYTES, outboundWireSize)
- .put(DeprecatedCensusConstants.RPC_SERVER_REQUEST_BYTES, inboundWireSize)
+ .put(RpcMeasureConstants.GRPC_SERVER_SERVER_LATENCY, elapsedTimeNanos / NANOS_PER_MILLI)
+ .put(RpcMeasureConstants.GRPC_SERVER_SENT_MESSAGES_PER_RPC, outboundMessageCount)
+ .put(RpcMeasureConstants.GRPC_SERVER_RECEIVED_MESSAGES_PER_RPC, inboundMessageCount)
+ .put(RpcMeasureConstants.GRPC_SERVER_SENT_BYTES_PER_RPC, outboundWireSize)
+ .put(RpcMeasureConstants.GRPC_SERVER_RECEIVED_BYTES_PER_RPC, inboundWireSize)
.put(
DeprecatedCensusConstants.RPC_SERVER_UNCOMPRESSED_RESPONSE_BYTES,
outboundUncompressedSize)
diff --git a/census/src/main/java/io/grpc/census/CensusTracingModule.java b/census/src/main/java/io/grpc/census/CensusTracingModule.java
index b507db10c64..f413da94388 100644
--- a/census/src/main/java/io/grpc/census/CensusTracingModule.java
+++ b/census/src/main/java/io/grpc/census/CensusTracingModule.java
@@ -65,7 +65,7 @@ final class CensusTracingModule {
@Nullable private static final AtomicIntegerFieldUpdater streamClosedUpdater;
- /**
+ /*
* When using Atomic*FieldUpdater, some Samsung Android 5.0.x devices encounter a bug in their JDK
* reflection API that triggers a NoSuchFieldException. When this occurs, we fallback to
* (potentially racy) direct updates of the volatile variables.
@@ -92,9 +92,12 @@ final class CensusTracingModule {
final Metadata.Key tracingHeader;
private final TracingClientInterceptor clientInterceptor = new TracingClientInterceptor();
private final ServerTracerFactory serverTracerFactory = new ServerTracerFactory();
+ private final boolean addMessageEvents;
CensusTracingModule(
- Tracer censusTracer, final BinaryFormat censusPropagationBinaryFormat) {
+ Tracer censusTracer,
+ final BinaryFormat censusPropagationBinaryFormat,
+ boolean addMessageEvents) {
this.censusTracer = checkNotNull(censusTracer, "censusTracer");
checkNotNull(censusPropagationBinaryFormat, "censusPropagationBinaryFormat");
this.tracingHeader =
@@ -114,6 +117,7 @@ public SpanContext parseBytes(byte[] serialized) {
}
}
});
+ this.addMessageEvents = addMessageEvents;
}
/**
@@ -211,9 +215,12 @@ private static EndSpanOptions createEndSpanOptions(
.build();
}
- private static void recordMessageEvent(
+ private void recordMessageEvent(
Span span, MessageEvent.Type type,
int seqNo, long optionalWireSize, long optionalUncompressedSize) {
+ if (!addMessageEvents) {
+ return;
+ }
MessageEvent.Builder eventBuilder = MessageEvent.builder(type, seqNo);
if (optionalUncompressedSize != -1) {
eventBuilder.setUncompressedMessageSize(optionalUncompressedSize);
@@ -282,7 +289,7 @@ void callEnded(io.grpc.Status status) {
}
}
- private static final class ClientTracer extends ClientStreamTracer {
+ private final class ClientTracer extends ClientStreamTracer {
private final Span span;
final Metadata.Key tracingHeader;
final boolean isSampledToLocalTracing;
diff --git a/census/src/main/java/io/grpc/census/InternalCensusTracingAccessor.java b/census/src/main/java/io/grpc/census/InternalCensusTracingAccessor.java
index 2df6c5fb4bd..5709d3a6e49 100644
--- a/census/src/main/java/io/grpc/census/InternalCensusTracingAccessor.java
+++ b/census/src/main/java/io/grpc/census/InternalCensusTracingAccessor.java
@@ -36,10 +36,21 @@ private InternalCensusTracingAccessor() {
* Returns a {@link ClientInterceptor} with default tracing implementation.
*/
public static ClientInterceptor getClientInterceptor() {
+ return getClientInterceptor(true);
+ }
+
+ /**
+ * @param addMessageEvents add message events to Spans
+ *
+ * @return a {@link ClientInterceptor} with default tracing implementation.
+ */
+ public static ClientInterceptor getClientInterceptor(
+ boolean addMessageEvents) {
CensusTracingModule censusTracing =
new CensusTracingModule(
Tracing.getTracer(),
- Tracing.getPropagationComponent().getBinaryFormat());
+ Tracing.getPropagationComponent().getBinaryFormat(),
+ addMessageEvents);
return censusTracing.getClientInterceptor();
}
@@ -47,10 +58,19 @@ public static ClientInterceptor getClientInterceptor() {
* Returns a {@link ServerStreamTracer.Factory} with default stats implementation.
*/
public static ServerStreamTracer.Factory getServerStreamTracerFactory() {
+ return getServerStreamTracerFactory(true);
+ }
+
+ /**
+ * Returns a {@link ServerStreamTracer.Factory} with default stats implementation.
+ */
+ public static ServerStreamTracer.Factory getServerStreamTracerFactory(
+ boolean addMessageEvents) {
CensusTracingModule censusTracing =
new CensusTracingModule(
Tracing.getTracer(),
- Tracing.getPropagationComponent().getBinaryFormat());
+ Tracing.getPropagationComponent().getBinaryFormat(),
+ addMessageEvents);
return censusTracing.getServerTracerFactory();
}
}
diff --git a/census/src/main/java/io/grpc/census/internal/DeprecatedCensusConstants.java b/census/src/main/java/io/grpc/census/internal/DeprecatedCensusConstants.java
index 2b0a4763a28..7470bc81b15 100644
--- a/census/src/main/java/io/grpc/census/internal/DeprecatedCensusConstants.java
+++ b/census/src/main/java/io/grpc/census/internal/DeprecatedCensusConstants.java
@@ -27,49 +27,23 @@
public final class DeprecatedCensusConstants {
public static final MeasureLong RPC_CLIENT_ERROR_COUNT =
RpcMeasureConstants.RPC_CLIENT_ERROR_COUNT;
- public static final MeasureDouble RPC_CLIENT_REQUEST_BYTES =
- RpcMeasureConstants.RPC_CLIENT_REQUEST_BYTES;
- public static final MeasureDouble RPC_CLIENT_RESPONSE_BYTES =
- RpcMeasureConstants.RPC_CLIENT_RESPONSE_BYTES;
- public static final MeasureDouble RPC_CLIENT_ROUNDTRIP_LATENCY =
- RpcMeasureConstants.RPC_CLIENT_ROUNDTRIP_LATENCY;
- public static final MeasureDouble RPC_CLIENT_SERVER_ELAPSED_TIME =
- RpcMeasureConstants.RPC_CLIENT_SERVER_ELAPSED_TIME;
public static final MeasureDouble RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES =
RpcMeasureConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES;
public static final MeasureDouble RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES =
RpcMeasureConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES;
- public static final MeasureLong RPC_CLIENT_STARTED_COUNT =
- RpcMeasureConstants.RPC_CLIENT_STARTED_COUNT;
public static final MeasureLong RPC_CLIENT_FINISHED_COUNT =
RpcMeasureConstants.RPC_CLIENT_FINISHED_COUNT;
- public static final MeasureLong RPC_CLIENT_REQUEST_COUNT =
- RpcMeasureConstants.RPC_CLIENT_REQUEST_COUNT;
- public static final MeasureLong RPC_CLIENT_RESPONSE_COUNT =
- RpcMeasureConstants.RPC_CLIENT_RESPONSE_COUNT;
public static final MeasureLong RPC_SERVER_ERROR_COUNT =
RpcMeasureConstants.RPC_SERVER_ERROR_COUNT;
- public static final MeasureDouble RPC_SERVER_REQUEST_BYTES =
- RpcMeasureConstants.RPC_SERVER_REQUEST_BYTES;
- public static final MeasureDouble RPC_SERVER_RESPONSE_BYTES =
- RpcMeasureConstants.RPC_SERVER_RESPONSE_BYTES;
public static final MeasureDouble RPC_SERVER_SERVER_ELAPSED_TIME =
RpcMeasureConstants.RPC_SERVER_SERVER_ELAPSED_TIME;
- public static final MeasureDouble RPC_SERVER_SERVER_LATENCY =
- RpcMeasureConstants.RPC_SERVER_SERVER_LATENCY;
public static final MeasureDouble RPC_SERVER_UNCOMPRESSED_REQUEST_BYTES =
RpcMeasureConstants.RPC_SERVER_UNCOMPRESSED_REQUEST_BYTES;
public static final MeasureDouble RPC_SERVER_UNCOMPRESSED_RESPONSE_BYTES =
RpcMeasureConstants.RPC_SERVER_UNCOMPRESSED_RESPONSE_BYTES;
- public static final MeasureLong RPC_SERVER_STARTED_COUNT =
- RpcMeasureConstants.RPC_SERVER_STARTED_COUNT;
public static final MeasureLong RPC_SERVER_FINISHED_COUNT =
RpcMeasureConstants.RPC_SERVER_FINISHED_COUNT;
- public static final MeasureLong RPC_SERVER_REQUEST_COUNT =
- RpcMeasureConstants.RPC_SERVER_REQUEST_COUNT;
- public static final MeasureLong RPC_SERVER_RESPONSE_COUNT =
- RpcMeasureConstants.RPC_SERVER_RESPONSE_COUNT;
private DeprecatedCensusConstants() {}
}
diff --git a/census/src/test/java/io/grpc/census/CensusModulesTest.java b/census/src/test/java/io/grpc/census/CensusModulesTest.java
index 9382ab84c12..9768797d579 100644
--- a/census/src/test/java/io/grpc/census/CensusModulesTest.java
+++ b/census/src/test/java/io/grpc/census/CensusModulesTest.java
@@ -225,7 +225,7 @@ public void setUp() throws Exception {
new CensusStatsModule(
tagger, tagCtxSerializer, statsRecorder, fakeClock.getStopwatchSupplier(),
true, true, true, false /* real-time */, true);
- censusTracing = new CensusTracingModule(tracer, mockTracingPropagationHandler);
+ censusTracing = new CensusTracingModule(tracer, mockTracingPropagationHandler, true);
}
@After
@@ -416,8 +416,7 @@ private void subtestClientBasicStatsDefaultContext(
assertEquals(1, record.tags.size());
TagValue methodTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_METHOD);
assertEquals(method.getFullMethodName(), methodTag.asString());
- assertEquals(
- 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_STARTED_COUNT));
+ assertEquals(1, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_STARTED_RPCS));
} else {
assertNull(statsRecorder.pollRecord());
}
@@ -484,25 +483,26 @@ private void subtestClientBasicStatsDefaultContext(
1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_FINISHED_COUNT));
assertNull(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_ERROR_COUNT));
assertEquals(
- 2, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_COUNT));
+ 2, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_SENT_MESSAGES_PER_RPC));
assertEquals(
1028 + 99,
- record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_BYTES));
+ record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_SENT_BYTES_PER_RPC));
assertEquals(
1128 + 865,
record.getMetricAsLongOrFail(
DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES));
assertEquals(
- 2, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_COUNT));
+ 2,
+ record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_RECEIVED_MESSAGES_PER_RPC));
assertEquals(
33 + 154,
- record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_BYTES));
+ record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_RECEIVED_BYTES_PER_RPC));
assertEquals(
67 + 552,
record.getMetricAsLongOrFail(
DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES));
assertEquals(30 + 100 + 16 + 24,
- record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ROUNDTRIP_LATENCY));
+ record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_ROUNDTRIP_LATENCY));
assertZeroRetryRecorded();
} else {
assertNull(statsRecorder.pollRecord());
@@ -526,8 +526,7 @@ public void recordRetryStats() {
assertEquals(1, record.tags.size());
TagValue methodTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_METHOD);
assertEquals(method.getFullMethodName(), methodTag.asString());
- assertEquals(
- 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_STARTED_COUNT));
+ assertEquals(1, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_STARTED_RPCS));
fakeClock.forwardTime(30, MILLISECONDS);
tracer.outboundHeaders();
@@ -553,16 +552,16 @@ record = statsRecorder.pollRecord();
1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_FINISHED_COUNT));
assertEquals(1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ERROR_COUNT));
assertEquals(
- 2, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_COUNT));
+ 2, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_SENT_MESSAGES_PER_RPC));
assertEquals(
- 1028, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_BYTES));
+ 1028, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_SENT_BYTES_PER_RPC));
assertEquals(
1128,
record.getMetricAsLongOrFail(
DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES));
assertEquals(
30 + 100 + 24,
- record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ROUNDTRIP_LATENCY));
+ record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_ROUNDTRIP_LATENCY));
// faking retry
fakeClock.forwardTime(1000, MILLISECONDS);
@@ -571,8 +570,7 @@ record = statsRecorder.pollRecord();
assertEquals(1, record.tags.size());
methodTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_METHOD);
assertEquals(method.getFullMethodName(), methodTag.asString());
- assertEquals(
- 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_STARTED_COUNT));
+ assertEquals(1, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_STARTED_RPCS));
tracer.outboundHeaders();
tracer.outboundMessage(0);
assertRealTimeMetric(
@@ -595,16 +593,16 @@ record = statsRecorder.pollRecord();
1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_FINISHED_COUNT));
assertEquals(1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ERROR_COUNT));
assertEquals(
- 2, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_COUNT));
+ 2, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_SENT_MESSAGES_PER_RPC));
assertEquals(
- 1028, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_BYTES));
+ 1028, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_SENT_BYTES_PER_RPC));
assertEquals(
1128,
record.getMetricAsLongOrFail(
DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES));
assertEquals(
100 ,
- record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ROUNDTRIP_LATENCY));
+ record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_ROUNDTRIP_LATENCY));
// fake transparent retry
fakeClock.forwardTime(10, MILLISECONDS);
@@ -614,8 +612,7 @@ record = statsRecorder.pollRecord();
assertEquals(1, record.tags.size());
methodTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_METHOD);
assertEquals(method.getFullMethodName(), methodTag.asString());
- assertEquals(
- 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_STARTED_COUNT));
+ assertEquals(1, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_STARTED_RPCS));
tracer.streamClosed(Status.UNAVAILABLE);
record = statsRecorder.pollRecord();
statusTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_STATUS);
@@ -624,9 +621,9 @@ record = statsRecorder.pollRecord();
1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_FINISHED_COUNT));
assertEquals(1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ERROR_COUNT));
assertEquals(
- 0, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_COUNT));
+ 0, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_SENT_MESSAGES_PER_RPC));
assertEquals(
- 0, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_BYTES));
+ 0, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_SENT_BYTES_PER_RPC));
// fake another transparent retry
fakeClock.forwardTime(10, MILLISECONDS);
@@ -634,8 +631,7 @@ record = statsRecorder.pollRecord();
STREAM_INFO.toBuilder().setIsTransparentRetry(true).build(), new Metadata());
record = statsRecorder.pollRecord();
assertEquals(1, record.tags.size());
- assertEquals(
- 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_STARTED_COUNT));
+ assertEquals(1, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_STARTED_RPCS));
tracer.outboundHeaders();
tracer.outboundMessage(0);
assertRealTimeMetric(
@@ -667,25 +663,25 @@ record = statsRecorder.pollRecord();
1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_FINISHED_COUNT));
assertThat(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_ERROR_COUNT)).isNull();
assertEquals(
- 2, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_COUNT));
+ 2, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_SENT_MESSAGES_PER_RPC));
assertEquals(
- 1028, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_BYTES));
+ 1028, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_SENT_BYTES_PER_RPC));
assertEquals(
1128,
record.getMetricAsLongOrFail(
DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES));
assertEquals(
- 1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_COUNT));
+ 1, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_RECEIVED_MESSAGES_PER_RPC));
assertEquals(
33,
- record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_BYTES));
+ record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_RECEIVED_BYTES_PER_RPC));
assertEquals(
67,
record.getMetricAsLongOrFail(
DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES));
assertEquals(
16 + 24 ,
- record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ROUNDTRIP_LATENCY));
+ record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_ROUNDTRIP_LATENCY));
record = statsRecorder.pollRecord();
methodTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_METHOD);
@@ -818,9 +814,7 @@ public void clientStreamNeverCreatedStillRecordStats() {
assertEquals(1, record.tags.size());
TagValue methodTag = record.tags.get(RpcMeasureConstants.GRPC_CLIENT_METHOD);
assertEquals(method.getFullMethodName(), methodTag.asString());
- assertEquals(
- 1,
- record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_STARTED_COUNT));
+ assertEquals(1, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_STARTED_RPCS));
// Completion record
record = statsRecorder.pollRecord();
@@ -837,24 +831,24 @@ record = statsRecorder.pollRecord();
1,
record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ERROR_COUNT));
assertEquals(
- 0, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_COUNT));
+ 0, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_SENT_MESSAGES_PER_RPC));
assertEquals(
- 0, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_BYTES));
+ 0, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_SENT_BYTES_PER_RPC));
assertEquals(
0,
record.getMetricAsLongOrFail(
DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES));
assertEquals(
- 0, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_COUNT));
+ 0, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_RECEIVED_MESSAGES_PER_RPC));
assertEquals(
- 0, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_BYTES));
+ 0, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_RECEIVED_BYTES_PER_RPC));
assertEquals(0,
record.getMetricAsLongOrFail(
DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES));
assertEquals(
3000,
- record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_CLIENT_ROUNDTRIP_LATENCY));
- assertNull(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_SERVER_ELAPSED_TIME));
+ record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_CLIENT_ROUNDTRIP_LATENCY));
+ assertNull(record.getMetric(RpcMeasureConstants.GRPC_CLIENT_SERVER_LATENCY));
assertZeroRetryRecorded();
}
@@ -1182,9 +1176,7 @@ private void subtestServerBasicStatsNoHeaders(
assertEquals(1, record.tags.size());
TagValue methodTag = record.tags.get(RpcMeasureConstants.GRPC_SERVER_METHOD);
assertEquals(method.getFullMethodName(), methodTag.asString());
- assertEquals(
- 1,
- record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_SERVER_STARTED_COUNT));
+ assertEquals(1, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_SERVER_STARTED_RPCS));
} else {
assertNull(statsRecorder.pollRecord());
}
@@ -1258,24 +1250,25 @@ private void subtestServerBasicStatsNoHeaders(
assertEquals(
1, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_SERVER_ERROR_COUNT));
assertEquals(
- 2, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_SERVER_RESPONSE_COUNT));
+ 2, record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_SERVER_SENT_MESSAGES_PER_RPC));
assertEquals(
1028 + 99,
- record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_SERVER_RESPONSE_BYTES));
+ record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_SERVER_SENT_BYTES_PER_RPC));
assertEquals(
1128 + 865,
record.getMetricAsLongOrFail(
DeprecatedCensusConstants.RPC_SERVER_UNCOMPRESSED_RESPONSE_BYTES));
assertEquals(
- 2, record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_SERVER_REQUEST_COUNT));
+ 2,
+ record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_SERVER_RECEIVED_MESSAGES_PER_RPC));
assertEquals(
34 + 154,
- record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_SERVER_REQUEST_BYTES));
+ record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_SERVER_RECEIVED_BYTES_PER_RPC));
assertEquals(67 + 552,
record.getMetricAsLongOrFail(
DeprecatedCensusConstants.RPC_SERVER_UNCOMPRESSED_REQUEST_BYTES));
assertEquals(100 + 16 + 24,
- record.getMetricAsLongOrFail(DeprecatedCensusConstants.RPC_SERVER_SERVER_LATENCY));
+ record.getMetricAsLongOrFail(RpcMeasureConstants.GRPC_SERVER_SERVER_LATENCY));
} else {
assertNull(statsRecorder.pollRecord());
}
@@ -1399,24 +1392,24 @@ public void generateTraceSpanName() {
private static void assertNoServerContent(StatsTestUtils.MetricsRecord record) {
assertNull(record.getMetric(DeprecatedCensusConstants.RPC_SERVER_ERROR_COUNT));
- assertNull(record.getMetric(DeprecatedCensusConstants.RPC_SERVER_REQUEST_COUNT));
- assertNull(record.getMetric(DeprecatedCensusConstants.RPC_SERVER_RESPONSE_COUNT));
- assertNull(record.getMetric(DeprecatedCensusConstants.RPC_SERVER_REQUEST_BYTES));
- assertNull(record.getMetric(DeprecatedCensusConstants.RPC_SERVER_RESPONSE_BYTES));
+ assertNull(record.getMetric(RpcMeasureConstants.GRPC_SERVER_RECEIVED_MESSAGES_PER_RPC));
+ assertNull(record.getMetric(RpcMeasureConstants.GRPC_SERVER_SENT_MESSAGES_PER_RPC));
+ assertNull(record.getMetric(RpcMeasureConstants.GRPC_SERVER_RECEIVED_BYTES_PER_RPC));
+ assertNull(record.getMetric(RpcMeasureConstants.GRPC_SERVER_SENT_BYTES_PER_RPC));
assertNull(record.getMetric(DeprecatedCensusConstants.RPC_SERVER_SERVER_ELAPSED_TIME));
- assertNull(record.getMetric(DeprecatedCensusConstants.RPC_SERVER_SERVER_LATENCY));
+ assertNull(record.getMetric(RpcMeasureConstants.GRPC_SERVER_SERVER_LATENCY));
assertNull(record.getMetric(DeprecatedCensusConstants.RPC_SERVER_UNCOMPRESSED_REQUEST_BYTES));
assertNull(record.getMetric(DeprecatedCensusConstants.RPC_SERVER_UNCOMPRESSED_RESPONSE_BYTES));
}
private static void assertNoClientContent(StatsTestUtils.MetricsRecord record) {
assertNull(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_ERROR_COUNT));
- assertNull(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_COUNT));
- assertNull(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_COUNT));
- assertNull(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_REQUEST_BYTES));
- assertNull(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_RESPONSE_BYTES));
- assertNull(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_ROUNDTRIP_LATENCY));
- assertNull(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_SERVER_ELAPSED_TIME));
+ assertNull(record.getMetric(RpcMeasureConstants.GRPC_CLIENT_SENT_MESSAGES_PER_RPC));
+ assertNull(record.getMetric(RpcMeasureConstants.GRPC_CLIENT_RECEIVED_MESSAGES_PER_RPC));
+ assertNull(record.getMetric(RpcMeasureConstants.GRPC_CLIENT_SENT_BYTES_PER_RPC));
+ assertNull(record.getMetric(RpcMeasureConstants.GRPC_CLIENT_RECEIVED_BYTES_PER_RPC));
+ assertNull(record.getMetric(RpcMeasureConstants.GRPC_CLIENT_ROUNDTRIP_LATENCY));
+ assertNull(record.getMetric(RpcMeasureConstants.GRPC_CLIENT_SERVER_LATENCY));
assertNull(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES));
assertNull(record.getMetric(DeprecatedCensusConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES));
}
@@ -1514,7 +1507,7 @@ public Long apply(AggregationData arg) {
});
}
- private static class CallInfo extends ServerCallInfo {
+ static class CallInfo extends ServerCallInfo {
private final MethodDescriptor methodDescriptor;
private final Attributes attributes;
private final String authority;
diff --git a/census/src/test/java/io/grpc/census/CensusTracingNoMessageEventTest.java b/census/src/test/java/io/grpc/census/CensusTracingNoMessageEventTest.java
new file mode 100644
index 00000000000..1bdefe4e749
--- /dev/null
+++ b/census/src/test/java/io/grpc/census/CensusTracingNoMessageEventTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2022 The gRPC Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.grpc.census;
+
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+
+import io.grpc.Attributes;
+import io.grpc.ClientStreamTracer;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.ServerStreamTracer;
+import io.grpc.Status;
+import io.grpc.census.CensusTracingModule.CallAttemptsTracerFactory;
+import io.grpc.internal.testing.StatsTestUtils.FakeStatsRecorder;
+import io.grpc.internal.testing.StatsTestUtils.MockableSpan;
+import io.grpc.testing.GrpcServerRule;
+import io.opencensus.trace.MessageEvent;
+import io.opencensus.trace.Span;
+import io.opencensus.trace.SpanBuilder;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.Tracer;
+import io.opencensus.trace.propagation.BinaryFormat;
+import java.io.InputStream;
+import java.util.Random;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/**
+ * Test for {@link CensusTracingModule}.
+ */
+@RunWith(JUnit4.class)
+public class CensusTracingNoMessageEventTest {
+ private static final ClientStreamTracer.StreamInfo STREAM_INFO =
+ ClientStreamTracer.StreamInfo.newBuilder().build();
+
+ private static class StringInputStream extends InputStream {
+ final String string;
+
+ StringInputStream(String string) {
+ this.string = string;
+ }
+
+ @Override
+ public int read() {
+ // InProcessTransport doesn't actually read bytes from the InputStream. The InputStream is
+ // passed to the InProcess server and consumed by MARSHALLER.parse().
+ throw new UnsupportedOperationException("Should not be called");
+ }
+ }
+
+ private static final MethodDescriptor.Marshaller MARSHALLER =
+ new MethodDescriptor.Marshaller() {
+ @Override
+ public InputStream stream(String value) {
+ return new StringInputStream(value);
+ }
+
+ @Override
+ public String parse(InputStream stream) {
+ return ((StringInputStream) stream).string;
+ }
+ };
+
+ @Rule
+ public final MockitoRule mocks = MockitoJUnit.rule();
+
+ private final MethodDescriptor method =
+ MethodDescriptor.newBuilder()
+ .setType(MethodDescriptor.MethodType.UNKNOWN)
+ .setRequestMarshaller(MARSHALLER)
+ .setResponseMarshaller(MARSHALLER)
+ .setFullMethodName("package1.service2/method3")
+ .build();
+
+ private final FakeStatsRecorder statsRecorder = new FakeStatsRecorder();
+ private final Random random = new Random(1234);
+ private final Span spyClientSpan = spy(MockableSpan.generateRandomSpan(random));
+ private final Span spyAttemptSpan = spy(MockableSpan.generateRandomSpan(random));
+ private final SpanContext fakeAttemptSpanContext = spyAttemptSpan.getContext();
+ private final Span spyServerSpan = spy(MockableSpan.generateRandomSpan(random));
+ private final byte[] binarySpanContext = new byte[]{3, 1, 5};
+ private final SpanBuilder spyClientSpanBuilder = spy(new MockableSpan.Builder());
+ private final SpanBuilder spyAttemptSpanBuilder = spy(new MockableSpan.Builder());
+ private final SpanBuilder spyServerSpanBuilder = spy(new MockableSpan.Builder());
+
+ @Rule
+ public final GrpcServerRule grpcServerRule = new GrpcServerRule().directExecutor();
+
+ @Mock
+ private Tracer tracer;
+ @Mock
+ private BinaryFormat mockTracingPropagationHandler;
+
+ @Captor
+ private ArgumentCaptor messageEventCaptor;
+
+ private CensusTracingModule censusTracing;
+
+ @Before
+ public void setUp() throws Exception {
+ when(spyClientSpanBuilder.startSpan()).thenReturn(spyClientSpan);
+ when(spyAttemptSpanBuilder.startSpan()).thenReturn(spyAttemptSpan);
+ when(tracer.spanBuilderWithExplicitParent(
+ eq("Sent.package1.service2.method3"), ArgumentMatchers.any()))
+ .thenReturn(spyClientSpanBuilder);
+ when(tracer.spanBuilderWithExplicitParent(
+ eq("Attempt.package1.service2.method3"), ArgumentMatchers.any()))
+ .thenReturn(spyAttemptSpanBuilder);
+ when(spyServerSpanBuilder.startSpan()).thenReturn(spyServerSpan);
+ when(tracer.spanBuilderWithRemoteParent(anyString(), ArgumentMatchers.any()))
+ .thenReturn(spyServerSpanBuilder);
+ when(mockTracingPropagationHandler.toByteArray(any(SpanContext.class)))
+ .thenReturn(binarySpanContext);
+ when(mockTracingPropagationHandler.fromByteArray(any(byte[].class)))
+ .thenReturn(fakeAttemptSpanContext);
+
+ censusTracing = new CensusTracingModule(tracer, mockTracingPropagationHandler, false);
+ }
+
+ @After
+ public void wrapUp() {
+ assertNull(statsRecorder.pollRecord());
+ }
+
+ @Test
+ public void clientBasicTracingNoMessageEvents() {
+ CallAttemptsTracerFactory callTracer =
+ censusTracing.newClientCallTracer(null, method);
+ Metadata headers = new Metadata();
+ ClientStreamTracer clientStreamTracer = callTracer.newClientStreamTracer(STREAM_INFO, headers);
+ clientStreamTracer.streamCreated(Attributes.EMPTY, headers);
+
+ clientStreamTracer.outboundMessage(0);
+ clientStreamTracer.outboundMessageSent(0, 882, -1);
+ clientStreamTracer.inboundMessage(0);
+ clientStreamTracer.outboundMessage(1);
+ clientStreamTracer.outboundMessageSent(1, -1, 27);
+ clientStreamTracer.inboundMessageRead(0, 255, 90);
+
+ clientStreamTracer.streamClosed(Status.OK);
+ callTracer.callEnded(Status.OK);
+
+ InOrder inOrder = inOrder(spyClientSpan, spyAttemptSpan);
+ inOrder.verify(spyAttemptSpan, times(0)).addMessageEvent(messageEventCaptor.capture());
+ }
+
+ @Test
+ public void serverBasicTracingNoMessageEvents() {
+ ServerStreamTracer.Factory tracerFactory = censusTracing.getServerTracerFactory();
+ ServerStreamTracer serverStreamTracer =
+ tracerFactory.newServerStreamTracer(method.getFullMethodName(), new Metadata());
+
+ serverStreamTracer.serverCallStarted(
+ new CensusModulesTest.CallInfo<>(method, Attributes.EMPTY, null));
+
+ serverStreamTracer.outboundMessage(0);
+ serverStreamTracer.outboundMessageSent(0, 882, -1);
+ serverStreamTracer.inboundMessage(0);
+ serverStreamTracer.outboundMessage(1);
+ serverStreamTracer.outboundMessageSent(1, -1, 27);
+ serverStreamTracer.inboundMessageRead(0, 255, 90);
+
+ serverStreamTracer.streamClosed(Status.CANCELLED);
+
+ InOrder inOrder = inOrder(spyServerSpan);
+ inOrder.verify(spyServerSpan, times(0)).addMessageEvent(messageEventCaptor.capture());
+ }
+}
diff --git a/compiler/src/test/golden/TestDeprecatedService.java.txt b/compiler/src/test/golden/TestDeprecatedService.java.txt
index 4f21c75f7d0..b98b10ce1f2 100644
--- a/compiler/src/test/golden/TestDeprecatedService.java.txt
+++ b/compiler/src/test/golden/TestDeprecatedService.java.txt
@@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
*
*/
@javax.annotation.Generated(
- value = "by gRPC proto compiler (version 1.49.0-SNAPSHOT)",
+ value = "by gRPC proto compiler (version 1.50.0)",
comments = "Source: grpc/testing/compiler/test.proto")
@io.grpc.stub.annotations.GrpcGenerated
@java.lang.Deprecated
diff --git a/compiler/src/test/golden/TestService.java.txt b/compiler/src/test/golden/TestService.java.txt
index 4fc8b282a0a..50e151870fb 100644
--- a/compiler/src/test/golden/TestService.java.txt
+++ b/compiler/src/test/golden/TestService.java.txt
@@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
*
*/
@javax.annotation.Generated(
- value = "by gRPC proto compiler (version 1.49.0-SNAPSHOT)",
+ value = "by gRPC proto compiler (version 1.50.0)",
comments = "Source: grpc/testing/compiler/test.proto")
@io.grpc.stub.annotations.GrpcGenerated
public final class TestServiceGrpc {
diff --git a/compiler/src/testLite/golden/TestDeprecatedService.java.txt b/compiler/src/testLite/golden/TestDeprecatedService.java.txt
index 5ae3fdd746b..6795af8e330 100644
--- a/compiler/src/testLite/golden/TestDeprecatedService.java.txt
+++ b/compiler/src/testLite/golden/TestDeprecatedService.java.txt
@@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
*
*/
@javax.annotation.Generated(
- value = "by gRPC proto compiler (version 1.49.0-SNAPSHOT)",
+ value = "by gRPC proto compiler (version 1.50.0)",
comments = "Source: grpc/testing/compiler/test.proto")
@io.grpc.stub.annotations.GrpcGenerated
@java.lang.Deprecated
diff --git a/compiler/src/testLite/golden/TestService.java.txt b/compiler/src/testLite/golden/TestService.java.txt
index ed26c986f45..2e2b2c6cf57 100644
--- a/compiler/src/testLite/golden/TestService.java.txt
+++ b/compiler/src/testLite/golden/TestService.java.txt
@@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
*
*/
@javax.annotation.Generated(
- value = "by gRPC proto compiler (version 1.49.0-SNAPSHOT)",
+ value = "by gRPC proto compiler (version 1.50.0)",
comments = "Source: grpc/testing/compiler/test.proto")
@io.grpc.stub.annotations.GrpcGenerated
public final class TestServiceGrpc {
diff --git a/context/src/test/java/io/grpc/ContextTest.java b/context/src/test/java/io/grpc/ContextTest.java
index 5171d4839a9..c2f36f41d71 100644
--- a/context/src/test/java/io/grpc/ContextTest.java
+++ b/context/src/test/java/io/grpc/ContextTest.java
@@ -16,6 +16,7 @@
package io.grpc;
+import static com.google.common.truth.TruthJUnit.assume;
import static io.grpc.Context.cancellableAncestor;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
@@ -872,6 +873,20 @@ public String call() {
@Test
public void storageReturnsNullTest() throws Exception {
+ // TODO(sergiitk): JDK-8210522 changes the behaviour of Java reflection to filter out
+ // security-sensitive fields in the java.lang.reflect.Field. This prohibits
+ // Field.class.getDeclaredFields("modifiers") call we rely on in this test.
+ // Until we have a good solution for setting a custom storage for testing purposes,
+ // we'll have to skip this test for JDK >= 11. Ref https://bugs.openjdk.org/browse/JDK-8210522
+ double javaVersion;
+ // Graceful version check. Run the test if the version undetermined.
+ try {
+ javaVersion = Double.parseDouble(System.getProperty("java.specification.version", "0"));
+ } catch (NumberFormatException e) {
+ javaVersion = 0;
+ }
+ assume().that(javaVersion).isLessThan(11);
+
Class> lazyStorageClass = Class.forName("io.grpc.Context$LazyStorage");
Field storage = lazyStorageClass.getDeclaredField("storage");
assertTrue(Modifier.isFinal(storage.getModifiers()));
diff --git a/core/build.gradle b/core/build.gradle
index d242802dfcf..9778c035f5c 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -42,8 +42,8 @@ dependencies {
jmh project(':grpc-testing')
- signature "org.codehaus.mojo.signature:java17:1.0@signature"
- signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature"
+ signature libraries.signature.java
+ signature libraries.signature.android
}
tasks.named("javadoc").configure {
diff --git a/core/src/main/java/io/grpc/internal/AutoConfiguredLoadBalancerFactory.java b/core/src/main/java/io/grpc/internal/AutoConfiguredLoadBalancerFactory.java
index 01c48b9efcf..b8c9cab7459 100644
--- a/core/src/main/java/io/grpc/internal/AutoConfiguredLoadBalancerFactory.java
+++ b/core/src/main/java/io/grpc/internal/AutoConfiguredLoadBalancerFactory.java
@@ -20,11 +20,9 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
-import io.grpc.Attributes;
import io.grpc.ChannelLogger.ChannelLogLevel;
import io.grpc.ConnectivityState;
import io.grpc.ConnectivityStateInfo;
-import io.grpc.EquivalentAddressGroup;
import io.grpc.LoadBalancer;
import io.grpc.LoadBalancer.Helper;
import io.grpc.LoadBalancer.PickResult;
@@ -67,10 +65,14 @@ private static final class NoopLoadBalancer extends LoadBalancer {
@Override
@Deprecated
- public void handleResolvedAddressGroups(List s, Attributes a) {}
+ @SuppressWarnings("InlineMeSuggester")
+ public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) {
+ }
@Override
- public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) {}
+ public boolean acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
+ return true;
+ }
@Override
public void handleNameResolutionError(Status error) {}
@@ -97,14 +99,10 @@ public final class AutoConfiguredLoadBalancer {
}
/**
- * Returns non-OK status if resolvedAddresses is empty and delegate lb requires address ({@link
- * LoadBalancer#canHandleEmptyAddressListFromNameResolution()} returns {@code false}). {@code
- * AutoConfiguredLoadBalancer} doesn't expose {@code
- * canHandleEmptyAddressListFromNameResolution} because it depends on the delegated LB.
+ * Returns non-OK status if the delegate rejects the resolvedAddresses (e.g. if it does not
+ * support an empty list).
*/
- Status tryHandleResolvedAddresses(ResolvedAddresses resolvedAddresses) {
- List servers = resolvedAddresses.getAddresses();
- Attributes attributes = resolvedAddresses.getAttributes();
+ boolean tryAcceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
PolicySelection policySelection =
(PolicySelection) resolvedAddresses.getLoadBalancingPolicyConfig();
@@ -118,7 +116,7 @@ Status tryHandleResolvedAddresses(ResolvedAddresses resolvedAddresses) {
delegate.shutdown();
delegateProvider = null;
delegate = new NoopLoadBalancer();
- return Status.OK;
+ return true;
}
policySelection =
new PolicySelection(defaultProvider, /* config= */ null);
@@ -141,20 +139,12 @@ Status tryHandleResolvedAddresses(ResolvedAddresses resolvedAddresses) {
ChannelLogLevel.DEBUG, "Load-balancing config: {0}", policySelection.config);
}
- LoadBalancer delegate = getDelegate();
- if (resolvedAddresses.getAddresses().isEmpty()
- && !delegate.canHandleEmptyAddressListFromNameResolution()) {
- return Status.UNAVAILABLE.withDescription(
- "NameResolver returned no usable address. addrs=" + servers + ", attrs=" + attributes);
- } else {
- delegate.handleResolvedAddresses(
- ResolvedAddresses.newBuilder()
- .setAddresses(resolvedAddresses.getAddresses())
- .setAttributes(attributes)
- .setLoadBalancingPolicyConfig(lbConfig)
- .build());
- return Status.OK;
- }
+ return getDelegate().acceptResolvedAddresses(
+ ResolvedAddresses.newBuilder()
+ .setAddresses(resolvedAddresses.getAddresses())
+ .setAttributes(resolvedAddresses.getAttributes())
+ .setLoadBalancingPolicyConfig(lbConfig)
+ .build());
}
void handleNameResolutionError(Status error) {
diff --git a/core/src/main/java/io/grpc/internal/BackoffPolicy.java b/core/src/main/java/io/grpc/internal/BackoffPolicy.java
index cdca4a22606..c80ef9e1f9d 100644
--- a/core/src/main/java/io/grpc/internal/BackoffPolicy.java
+++ b/core/src/main/java/io/grpc/internal/BackoffPolicy.java
@@ -20,7 +20,7 @@
* Determines how long to wait before doing some action (typically a retry, or a reconnect).
*/
public interface BackoffPolicy {
- public interface Provider {
+ interface Provider {
BackoffPolicy get();
}
diff --git a/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java b/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java
index 04409558bea..1537d1c664f 100644
--- a/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java
+++ b/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 The gRPC Authors
+ * Copyright 2016,2022 The gRPC Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@
import io.grpc.ChannelLogger;
import io.grpc.ClientStreamTracer;
import io.grpc.CompositeCallCredentials;
+import io.grpc.InternalMayRequireSpecificExecutor;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import io.grpc.SecurityLevel;
@@ -126,6 +127,11 @@ public ClientStream newStream(
return method;
}
+ @Override
+ public CallOptions getCallOptions() {
+ return callOptions;
+ }
+
@Override
public SecurityLevel getSecurityLevel() {
return firstNonNull(
@@ -144,7 +150,21 @@ public Attributes getTransportAttrs() {
}
};
try {
- creds.applyRequestMetadata(requestInfo, appExecutor, applier);
+ // Hack to allow appengine to work when using AppEngineCredentials (b/244209681)
+ // since processing must happen on a specific thread.
+ //
+ // Ideally would always use appExecutor and we could eliminate the interface
+ // InternalMayRequireSpecificExecutor
+ Executor executor;
+ if (creds instanceof InternalMayRequireSpecificExecutor
+ && ((InternalMayRequireSpecificExecutor)creds).isSpecificExecutorRequired()
+ && callOptions.getExecutor() != null) {
+ executor = callOptions.getExecutor();
+ } else {
+ executor = appExecutor;
+ }
+
+ creds.applyRequestMetadata(requestInfo, executor, applier);
} catch (Throwable t) {
applier.fail(Status.UNAUTHENTICATED
.withDescription("Credentials should use fail() instead of throwing exceptions")
diff --git a/core/src/main/java/io/grpc/internal/DelayedClientCall.java b/core/src/main/java/io/grpc/internal/DelayedClientCall.java
index df56fc0c5f7..42cedddd0e7 100644
--- a/core/src/main/java/io/grpc/internal/DelayedClientCall.java
+++ b/core/src/main/java/io/grpc/internal/DelayedClientCall.java
@@ -121,6 +121,7 @@ private ScheduledFuture> scheduleDeadlineIfNeeded(
buf.append(seconds);
buf.append(String.format(Locale.US, ".%09d", nanos));
buf.append("s. ");
+
/** Cancels the call if deadline exceeded prior to the real call being set. */
class DeadlineExceededRunnable implements Runnable {
@Override
@@ -151,9 +152,9 @@ public final Runnable setCall(ClientCall call) {
}
setRealCall(checkNotNull(call, "call"));
}
- return new Runnable() {
+ return new ContextRunnable(context) {
@Override
- public void run() {
+ public void runInContext() {
drainPendingCalls();
}
};
diff --git a/core/src/main/java/io/grpc/internal/GrpcUtil.java b/core/src/main/java/io/grpc/internal/GrpcUtil.java
index 605fabc0cc2..dcf552b339b 100644
--- a/core/src/main/java/io/grpc/internal/GrpcUtil.java
+++ b/core/src/main/java/io/grpc/internal/GrpcUtil.java
@@ -56,7 +56,10 @@
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -75,6 +78,17 @@ public final class GrpcUtil {
private static final Logger log = Logger.getLogger(GrpcUtil.class.getName());
+ private static final Set INAPPROPRIATE_CONTROL_PLANE_STATUS
+ = Collections.unmodifiableSet(EnumSet.of(
+ Status.Code.OK,
+ Status.Code.INVALID_ARGUMENT,
+ Status.Code.NOT_FOUND,
+ Status.Code.ALREADY_EXISTS,
+ Status.Code.FAILED_PRECONDITION,
+ Status.Code.ABORTED,
+ Status.Code.OUT_OF_RANGE,
+ Status.Code.DATA_LOSS));
+
public static final Charset US_ASCII = Charset.forName("US-ASCII");
/**
@@ -203,7 +217,7 @@ public byte[] parseAsciiString(byte[] serialized) {
public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults();
- private static final String IMPLEMENTATION_VERSION = "1.49.0-SNAPSHOT"; // CURRENT_GRPC_VERSION
+ private static final String IMPLEMENTATION_VERSION = "1.50.0"; // CURRENT_GRPC_VERSION
/**
* The default timeout in nanos for a keepalive ping request.
@@ -747,10 +761,12 @@ public ListenableFuture getStats() {
}
if (!result.getStatus().isOk()) {
if (result.isDrop()) {
- return new FailingClientTransport(result.getStatus(), RpcProgress.DROPPED);
+ return new FailingClientTransport(
+ replaceInappropriateControlPlaneStatus(result.getStatus()), RpcProgress.DROPPED);
}
if (!isWaitForReady) {
- return new FailingClientTransport(result.getStatus(), RpcProgress.PROCESSED);
+ return new FailingClientTransport(
+ replaceInappropriateControlPlaneStatus(result.getStatus()), RpcProgress.PROCESSED);
}
}
return null;
@@ -805,6 +821,19 @@ public static void exhaust(InputStream in) throws IOException {
while (in.read(buf) != -1) {}
}
+ /**
+ * Some status codes from the control plane are not appropritate to use in the data plane. If one
+ * is given it will be replaced with INTERNAL, indicating a bug in the control plane
+ * implementation.
+ */
+ public static Status replaceInappropriateControlPlaneStatus(Status status) {
+ checkArgument(status != null);
+ return INAPPROPRIATE_CONTROL_PLANE_STATUS.contains(status.getCode())
+ ? Status.INTERNAL.withDescription(
+ "Inappropriate status code from control plane: " + status.getCode() + " "
+ + status.getDescription()).withCause(status.getCause()) : status;
+ }
+
/**
* Checks whether the given item exists in the iterable. This is copied from Guava Collect's
* {@code Iterables.contains()} because Guava Collect is not Android-friendly thus core can't
diff --git a/core/src/main/java/io/grpc/internal/InternalServer.java b/core/src/main/java/io/grpc/internal/InternalServer.java
index 0445ae3dfab..a6079081233 100644
--- a/core/src/main/java/io/grpc/internal/InternalServer.java
+++ b/core/src/main/java/io/grpc/internal/InternalServer.java
@@ -50,7 +50,7 @@ public interface InternalServer {
void shutdown();
/**
- * Returns the first listening socket address. May change after {@link start(ServerListener)} is
+ * Returns the first listening socket address. May change after {@link #start(ServerListener)} is
* called.
*/
SocketAddress getListenSocketAddress();
@@ -61,7 +61,7 @@ public interface InternalServer {
@Nullable InternalInstrumented getListenSocketStats();
/**
- * Returns a list of listening socket addresses. May change after {@link start(ServerListener)}
+ * Returns a list of listening socket addresses. May change after {@link #start(ServerListener)}
* is called.
*/
List extends SocketAddress> getListenSocketAddresses();
diff --git a/netty/src/main/java/io/grpc/netty/KeepAliveEnforcer.java b/core/src/main/java/io/grpc/internal/KeepAliveEnforcer.java
similarity index 94%
rename from netty/src/main/java/io/grpc/netty/KeepAliveEnforcer.java
rename to core/src/main/java/io/grpc/internal/KeepAliveEnforcer.java
index 6470e440327..dd539e75a18 100644
--- a/netty/src/main/java/io/grpc/netty/KeepAliveEnforcer.java
+++ b/core/src/main/java/io/grpc/internal/KeepAliveEnforcer.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package io.grpc.netty;
+package io.grpc.internal;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
@@ -22,11 +22,11 @@
import javax.annotation.CheckReturnValue;
/** Monitors the client's PING usage to make sure the rate is permitted. */
-class KeepAliveEnforcer {
+public final class KeepAliveEnforcer {
@VisibleForTesting
- static final int MAX_PING_STRIKES = 2;
+ public static final int MAX_PING_STRIKES = 2;
@VisibleForTesting
- static final long IMPLICIT_PERMIT_TIME_NANOS = TimeUnit.HOURS.toNanos(2);
+ public static final long IMPLICIT_PERMIT_TIME_NANOS = TimeUnit.HOURS.toNanos(2);
private final boolean permitWithoutCalls;
private final long minTimeNanos;
diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java
index 3e2e3ec17fc..9cc7d70d1e7 100644
--- a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java
+++ b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java
@@ -1199,7 +1199,8 @@ public void start(Listener observer, Metadata headers) {
InternalConfigSelector.Result result = configSelector.selectConfig(args);
Status status = result.getStatus();
if (!status.isOk()) {
- executeCloseObserverInContext(observer, status);
+ executeCloseObserverInContext(observer,
+ GrpcUtil.replaceInappropriateControlPlaneStatus(status));
delegate = (ClientCall) NOOP_CALL;
return;
}
@@ -1453,8 +1454,6 @@ void remove(RetriableStream> retriableStream) {
private final class LbHelperImpl extends LoadBalancer.Helper {
AutoConfiguredLoadBalancer lb;
- boolean nsRefreshedByLb;
- boolean ignoreRefreshNsCheck;
@Override
public AbstractSubchannel createSubchannel(CreateSubchannelArgs args) {
@@ -1493,7 +1492,6 @@ public void run() {
@Override
public void refreshNameResolution() {
syncContext.throwIfNotInThisSynchronizationContext();
- nsRefreshedByLb = true;
final class LoadBalancerRefreshNameResolution implements Runnable {
@Override
public void run() {
@@ -1504,11 +1502,6 @@ public void run() {
syncContext.execute(new LoadBalancerRefreshNameResolution());
}
- @Override
- public void ignoreRefreshNameResolutionCheck() {
- ignoreRefreshNsCheck = true;
- }
-
@Override
public ManagedChannel createOobChannel(EquivalentAddressGroup addressGroup, String authority) {
return createOobChannel(Collections.singletonList(addressGroup), authority);
@@ -1818,6 +1811,10 @@ public void run() {
channelLogger.log(
ChannelLogLevel.INFO,
"Fallback to error due to invalid first service config without default config");
+ // This error could be an "inappropriate" control plane error that should not bleed
+ // through to client code using gRPC. We let them flow through here to the LB as
+ // we later check for these error codes when investigating pick results in
+ // GrpcUtil.getTransportFromPickResult().
onError(configOrError.getError());
return;
} else {
@@ -1860,16 +1857,17 @@ public void run() {
.set(LoadBalancer.ATTR_HEALTH_CHECKING_CONFIG, healthCheckingConfig)
.build();
}
+ Attributes attributes = attrBuilder.build();
- Status handleResult = helper.lb.tryHandleResolvedAddresses(
+ boolean addressesAccepted = helper.lb.tryAcceptResolvedAddresses(
ResolvedAddresses.newBuilder()
.setAddresses(servers)
- .setAttributes(attrBuilder.build())
+ .setAttributes(attributes)
.setLoadBalancingPolicyConfig(effectiveServiceConfig.getLoadBalancingConfig())
.build());
- if (!handleResult.isOk()) {
- handleErrorInSyncContext(handleResult.augmentDescription(resolver + " was used"));
+ if (!addressesAccepted) {
+ scheduleExponentialBackOffInSyncContext();
}
}
}
@@ -1979,16 +1977,6 @@ void onTerminated(InternalSubchannel is) {
void onStateChange(InternalSubchannel is, ConnectivityStateInfo newState) {
checkState(listener != null, "listener is null");
listener.onSubchannelState(newState);
- if (newState.getState() == TRANSIENT_FAILURE || newState.getState() == IDLE) {
- if (!helper.ignoreRefreshNsCheck && !helper.nsRefreshedByLb) {
- logger.log(Level.WARNING,
- "LoadBalancer should call Helper.refreshNameResolution() to refresh name "
- + "resolution if subchannel state becomes TRANSIENT_FAILURE or IDLE. "
- + "This will no longer happen automatically in the future releases");
- refreshAndResetNameResolution();
- helper.nsRefreshedByLb = true;
- }
- }
}
@Override
diff --git a/netty/src/main/java/io/grpc/netty/MaxConnectionIdleManager.java b/core/src/main/java/io/grpc/internal/MaxConnectionIdleManager.java
similarity index 77%
rename from netty/src/main/java/io/grpc/netty/MaxConnectionIdleManager.java
rename to core/src/main/java/io/grpc/internal/MaxConnectionIdleManager.java
index 964ae44b178..4d4a36dda01 100644
--- a/netty/src/main/java/io/grpc/netty/MaxConnectionIdleManager.java
+++ b/core/src/main/java/io/grpc/internal/MaxConnectionIdleManager.java
@@ -14,11 +14,9 @@
* limitations under the License.
*/
-package io.grpc.netty;
+package io.grpc.internal;
import com.google.common.annotations.VisibleForTesting;
-import io.grpc.internal.LogExceptionRunnable;
-import io.netty.channel.ChannelHandlerContext;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@@ -27,7 +25,7 @@
/**
* Monitors connection idle time; shutdowns the connection if the max connection idle is reached.
*/
-abstract class MaxConnectionIdleManager {
+public final class MaxConnectionIdleManager {
private static final Ticker systemTicker = new Ticker() {
@Override
public long nanoTime() {
@@ -46,23 +44,23 @@ public long nanoTime() {
private boolean shutdownDelayed;
private boolean isActive;
- MaxConnectionIdleManager(long maxConnectionIdleInNanos) {
+ public MaxConnectionIdleManager(long maxConnectionIdleInNanos) {
this(maxConnectionIdleInNanos, systemTicker);
}
@VisibleForTesting
- MaxConnectionIdleManager(long maxConnectionIdleInNanos, Ticker ticker) {
+ public MaxConnectionIdleManager(long maxConnectionIdleInNanos, Ticker ticker) {
this.maxConnectionIdleInNanos = maxConnectionIdleInNanos;
this.ticker = ticker;
}
- /** A {@link NettyServerHandler} was added to the transport. */
- void start(ChannelHandlerContext ctx) {
- start(ctx, ctx.executor());
- }
-
- @VisibleForTesting
- void start(final ChannelHandlerContext ctx, final ScheduledExecutorService scheduler) {
+ /**
+ * Start the initial scheduled shutdown given the transport status reaches max connection idle.
+ *
+ * @param closeJob Closes the connection by sending GO_AWAY with status code NO_ERROR and ASCII
+ * debug data max_idle and then doing the graceful connection termination.
+ */
+ public void start(final Runnable closeJob, final ScheduledExecutorService scheduler) {
this.scheduler = scheduler;
nextIdleMonitorTime = ticker.nanoTime() + maxConnectionIdleInNanos;
@@ -78,7 +76,7 @@ public void run() {
}
// if isActive, exit. Will schedule a new shutdownFuture once onTransportIdle
} else {
- close(ctx);
+ closeJob.run();
shutdownFuture = null;
}
}
@@ -88,20 +86,15 @@ public void run() {
scheduler.schedule(shutdownTask, maxConnectionIdleInNanos, TimeUnit.NANOSECONDS);
}
- /**
- * Closes the connection by sending GO_AWAY with status code NO_ERROR and ASCII debug data
- * max_idle and then doing the graceful connection termination.
- */
- abstract void close(ChannelHandlerContext ctx);
/** There are outstanding RPCs on the transport. */
- void onTransportActive() {
+ public void onTransportActive() {
isActive = true;
shutdownDelayed = true;
}
/** There are no outstanding RPCs on the transport. */
- void onTransportIdle() {
+ public void onTransportIdle() {
isActive = false;
if (shutdownFuture == null) {
return;
@@ -116,7 +109,7 @@ void onTransportIdle() {
}
/** Transport is being terminated. */
- void onTransportTermination() {
+ public void onTransportTermination() {
if (shutdownFuture != null) {
shutdownFuture.cancel(false);
shutdownFuture = null;
@@ -124,7 +117,7 @@ void onTransportTermination() {
}
@VisibleForTesting
- interface Ticker {
+ public interface Ticker {
long nanoTime();
}
}
diff --git a/core/src/main/java/io/grpc/internal/MetadataApplierImpl.java b/core/src/main/java/io/grpc/internal/MetadataApplierImpl.java
index 6893713c1d2..12cab15053f 100644
--- a/core/src/main/java/io/grpc/internal/MetadataApplierImpl.java
+++ b/core/src/main/java/io/grpc/internal/MetadataApplierImpl.java
@@ -82,7 +82,8 @@ public void apply(Metadata headers) {
public void fail(Status status) {
checkArgument(!status.isOk(), "Cannot fail with OK status");
checkState(!finalized, "apply() or fail() already called");
- finalizeWith(new FailingClientStream(status, tracers));
+ finalizeWith(
+ new FailingClientStream(GrpcUtil.replaceInappropriateControlPlaneStatus(status), tracers));
}
private void finalizeWith(ClientStream stream) {
diff --git a/core/src/main/java/io/grpc/internal/PickFirstLoadBalancer.java b/core/src/main/java/io/grpc/internal/PickFirstLoadBalancer.java
index d5f74db54a7..e9c4d79150a 100644
--- a/core/src/main/java/io/grpc/internal/PickFirstLoadBalancer.java
+++ b/core/src/main/java/io/grpc/internal/PickFirstLoadBalancer.java
@@ -45,8 +45,15 @@ final class PickFirstLoadBalancer extends LoadBalancer {
}
@Override
- public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) {
+ public boolean acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
List servers = resolvedAddresses.getAddresses();
+ if (servers.isEmpty()) {
+ handleNameResolutionError(Status.UNAVAILABLE.withDescription(
+ "NameResolver returned no usable address. addrs=" + resolvedAddresses.getAddresses()
+ + ", attrs=" + resolvedAddresses.getAttributes()));
+ return false;
+ }
+
if (subchannel == null) {
final Subchannel subchannel = helper.createSubchannel(
CreateSubchannelArgs.newBuilder()
@@ -67,6 +74,8 @@ public void onSubchannelState(ConnectivityStateInfo stateInfo) {
} else {
subchannel.updateAddresses(servers);
}
+
+ return true;
}
@Override
diff --git a/core/src/main/java/io/grpc/internal/PickFirstLoadBalancerProvider.java b/core/src/main/java/io/grpc/internal/PickFirstLoadBalancerProvider.java
index 0b11af94e2d..7f7b366564e 100644
--- a/core/src/main/java/io/grpc/internal/PickFirstLoadBalancerProvider.java
+++ b/core/src/main/java/io/grpc/internal/PickFirstLoadBalancerProvider.java
@@ -18,6 +18,7 @@
import io.grpc.LoadBalancer;
import io.grpc.LoadBalancerProvider;
+import io.grpc.NameResolver;
import io.grpc.NameResolver.ConfigOrError;
import java.util.Map;
diff --git a/core/src/main/java/io/grpc/internal/ServerCallImpl.java b/core/src/main/java/io/grpc/internal/ServerCallImpl.java
index 0b680715294..47ffdf9caaf 100644
--- a/core/src/main/java/io/grpc/internal/ServerCallImpl.java
+++ b/core/src/main/java/io/grpc/internal/ServerCallImpl.java
@@ -35,6 +35,7 @@
import io.grpc.Context;
import io.grpc.DecompressorRegistry;
import io.grpc.InternalDecompressorRegistry;
+import io.grpc.InternalStatus;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import io.grpc.SecurityLevel;
@@ -368,19 +369,22 @@ public void closed(Status status) {
}
private void closedInternal(Status status) {
+ Throwable cancelCause = null;
try {
if (status.isOk()) {
listener.onComplete();
} else {
call.cancelled = true;
listener.onCancel();
+ // The status will not have a cause in all failure scenarios but we want to make sure
+ // we always cancel the context with one to keep the context cancelled state consistent.
+ cancelCause = InternalStatus.asRuntimeException(
+ Status.CANCELLED.withDescription("RPC cancelled"), null, false);
}
} finally {
// Cancel context after delivering RPC closure notification to allow the application to
// clean up and update any state based on whether onComplete or onCancel was called.
- // Note that in failure situations JumpToApplicationThreadServerStreamListener has already
- // closed the context. In these situations this cancel() call will be a no-op.
- context.cancel(null);
+ context.cancel(cancelCause);
}
}
diff --git a/core/src/main/java/io/grpc/internal/ServerImpl.java b/core/src/main/java/io/grpc/internal/ServerImpl.java
index 6bfe2d38ab3..bbd52c14bba 100644
--- a/core/src/main/java/io/grpc/internal/ServerImpl.java
+++ b/core/src/main/java/io/grpc/internal/ServerImpl.java
@@ -45,6 +45,7 @@
import io.grpc.InternalInstrumented;
import io.grpc.InternalLogId;
import io.grpc.InternalServerInterceptors;
+import io.grpc.InternalStatus;
import io.grpc.Metadata;
import io.grpc.ServerCall;
import io.grpc.ServerCallExecutorSupplier;
@@ -894,9 +895,18 @@ private void closedInternal(final Status status) {
// For cancellations, promptly inform any users of the context that their work should be
// aborted. Otherwise, we can wait until pending work is done.
if (!status.isOk()) {
+ // Since status was not OK we know that the call did not complete and got cancelled. To
+ // reflect this on the context we need to close it with a cause exception. Since not every
+ // failed status has an exception we will create one here if needed.
+ Throwable cancelCause = status.getCause();
+ if (cancelCause == null) {
+ cancelCause = InternalStatus.asRuntimeException(
+ Status.CANCELLED.withDescription("RPC cancelled"), null, false);
+ }
+
// The callExecutor might be busy doing user work. To avoid waiting, use an executor that
// is not serializing.
- cancelExecutor.execute(new ContextCloser(context, status.getCause()));
+ cancelExecutor.execute(new ContextCloser(context, cancelCause));
}
final Link link = PerfMark.linkOut();
diff --git a/core/src/main/java/io/grpc/internal/StreamListener.java b/core/src/main/java/io/grpc/internal/StreamListener.java
index 090c0555b0a..a893a9c84b6 100644
--- a/core/src/main/java/io/grpc/internal/StreamListener.java
+++ b/core/src/main/java/io/grpc/internal/StreamListener.java
@@ -59,6 +59,6 @@ interface MessageProducer {
* messages until the producer returns null, at which point the producer may be discarded.
*/
@Nullable
- public InputStream next();
+ InputStream next();
}
}
diff --git a/core/src/main/java/io/grpc/util/AdvancedTlsX509KeyManager.java b/core/src/main/java/io/grpc/util/AdvancedTlsX509KeyManager.java
index 9c9102b12cb..1530834d609 100644
--- a/core/src/main/java/io/grpc/util/AdvancedTlsX509KeyManager.java
+++ b/core/src/main/java/io/grpc/util/AdvancedTlsX509KeyManager.java
@@ -246,7 +246,8 @@ private UpdateResult readAndUpdate(File keyFile, File certFile, long oldKeyTime,
* Mainly used to avoid throwing IO Exceptions in java.io.Closeable.
*/
public interface Closeable extends java.io.Closeable {
- @Override public void close();
+ @Override
+ void close();
}
}
diff --git a/core/src/main/java/io/grpc/util/AdvancedTlsX509TrustManager.java b/core/src/main/java/io/grpc/util/AdvancedTlsX509TrustManager.java
index 51bf57aeb34..7465e632104 100644
--- a/core/src/main/java/io/grpc/util/AdvancedTlsX509TrustManager.java
+++ b/core/src/main/java/io/grpc/util/AdvancedTlsX509TrustManager.java
@@ -295,7 +295,8 @@ private long readAndUpdate(File trustCertFile, long oldTime)
// Mainly used to avoid throwing IO Exceptions in java.io.Closeable.
public interface Closeable extends java.io.Closeable {
- @Override public void close();
+ @Override
+ void close();
}
public static Builder newBuilder() {
diff --git a/core/src/main/java/io/grpc/util/ForwardingLoadBalancer.java b/core/src/main/java/io/grpc/util/ForwardingLoadBalancer.java
index 628eda3b71b..cefcbf344ea 100644
--- a/core/src/main/java/io/grpc/util/ForwardingLoadBalancer.java
+++ b/core/src/main/java/io/grpc/util/ForwardingLoadBalancer.java
@@ -17,14 +17,10 @@
package io.grpc.util;
import com.google.common.base.MoreObjects;
-import io.grpc.Attributes;
import io.grpc.ConnectivityStateInfo;
-import io.grpc.EquivalentAddressGroup;
import io.grpc.ExperimentalApi;
import io.grpc.LoadBalancer;
-import io.grpc.NameResolver;
import io.grpc.Status;
-import java.util.List;
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771")
public abstract class ForwardingLoadBalancer extends LoadBalancer {
@@ -33,14 +29,6 @@ public abstract class ForwardingLoadBalancer extends LoadBalancer {
*/
protected abstract LoadBalancer delegate();
- @Override
- @Deprecated
- public void handleResolvedAddressGroups(
- List servers,
- @NameResolver.ResolutionResultAttr Attributes attributes) {
- delegate().handleResolvedAddressGroups(servers, attributes);
- }
-
@Override
public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) {
delegate().handleResolvedAddresses(resolvedAddresses);
diff --git a/core/src/main/java/io/grpc/util/ForwardingLoadBalancerHelper.java b/core/src/main/java/io/grpc/util/ForwardingLoadBalancerHelper.java
index 05db207806d..f8e901205ee 100644
--- a/core/src/main/java/io/grpc/util/ForwardingLoadBalancerHelper.java
+++ b/core/src/main/java/io/grpc/util/ForwardingLoadBalancerHelper.java
@@ -95,6 +95,7 @@ public void refreshNameResolution() {
}
@Override
+ @Deprecated
public void ignoreRefreshNameResolutionCheck() {
delegate().ignoreRefreshNameResolutionCheck();
}
diff --git a/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java
new file mode 100644
index 00000000000..e7d93d9d52f
--- /dev/null
+++ b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java
@@ -0,0 +1,1082 @@
+/*
+ * Copyright 2022 The gRPC Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.grpc.util;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ForwardingMap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import io.grpc.Attributes;
+import io.grpc.ClientStreamTracer;
+import io.grpc.ClientStreamTracer.StreamInfo;
+import io.grpc.ConnectivityState;
+import io.grpc.ConnectivityStateInfo;
+import io.grpc.EquivalentAddressGroup;
+import io.grpc.Internal;
+import io.grpc.LoadBalancer;
+import io.grpc.Metadata;
+import io.grpc.Status;
+import io.grpc.SynchronizationContext;
+import io.grpc.SynchronizationContext.ScheduledHandle;
+import io.grpc.internal.ServiceConfigUtil.PolicySelection;
+import io.grpc.internal.TimeProvider;
+import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.atomic.AtomicLong;
+import javax.annotation.Nullable;
+
+/**
+ * Wraps a child {@code LoadBalancer} while monitoring for outlier backends and removing them from
+ * the use of the child LB.
+ *
+ *
This implements the outlier detection gRFC:
+ * https://github.com/grpc/proposal/blob/master/A50-xds-outlier-detection.md
+ */
+@Internal
+public final class OutlierDetectionLoadBalancer extends LoadBalancer {
+
+ @VisibleForTesting
+ final AddressTrackerMap trackerMap;
+
+ private final SynchronizationContext syncContext;
+ private final Helper childHelper;
+ private final GracefulSwitchLoadBalancer switchLb;
+ private TimeProvider timeProvider;
+ private final ScheduledExecutorService timeService;
+ private ScheduledHandle detectionTimerHandle;
+ private Long detectionTimerStartNanos;
+
+ private static final Attributes.Key ADDRESS_TRACKER_ATTR_KEY
+ = Attributes.Key.create("addressTrackerKey");
+
+ /**
+ * Creates a new instance of {@link OutlierDetectionLoadBalancer}.
+ */
+ public OutlierDetectionLoadBalancer(Helper helper, TimeProvider timeProvider) {
+ childHelper = new ChildHelper(checkNotNull(helper, "helper"));
+ switchLb = new GracefulSwitchLoadBalancer(childHelper);
+ trackerMap = new AddressTrackerMap();
+ this.syncContext = checkNotNull(helper.getSynchronizationContext(), "syncContext");
+ this.timeService = checkNotNull(helper.getScheduledExecutorService(), "timeService");
+ this.timeProvider = timeProvider;
+ }
+
+ @Override
+ public boolean acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
+ OutlierDetectionLoadBalancerConfig config
+ = (OutlierDetectionLoadBalancerConfig) resolvedAddresses.getLoadBalancingPolicyConfig();
+
+ // The map should only retain entries for addresses in this latest update.
+ ArrayList addresses = new ArrayList<>();
+ for (EquivalentAddressGroup addressGroup : resolvedAddresses.getAddresses()) {
+ addresses.addAll(addressGroup.getAddresses());
+ }
+ trackerMap.keySet().retainAll(addresses);
+
+ trackerMap.updateTrackerConfigs(config);
+
+ // Add any new ones.
+ trackerMap.putNewTrackers(config, addresses);
+
+ switchLb.switchTo(config.childPolicy.getProvider());
+
+ // If outlier detection is actually configured, start a timer that will periodically try to
+ // detect outliers.
+ if (config.outlierDetectionEnabled()) {
+ Long initialDelayNanos;
+
+ if (detectionTimerStartNanos == null) {
+ // On the first go we use the configured interval.
+ initialDelayNanos = config.intervalNanos;
+ } else {
+ // If a timer has started earlier we cancel it and use the difference between the start
+ // time and now as the interval.
+ initialDelayNanos = Math.max(0L,
+ config.intervalNanos - (timeProvider.currentTimeNanos() - detectionTimerStartNanos));
+ }
+
+ // If a timer has been previously created we need to cancel it and reset all the call counters
+ // for a fresh start.
+ if (detectionTimerHandle != null) {
+ detectionTimerHandle.cancel();
+ trackerMap.resetCallCounters();
+ }
+
+ detectionTimerHandle = syncContext.scheduleWithFixedDelay(new DetectionTimer(config),
+ initialDelayNanos, config.intervalNanos, NANOSECONDS, timeService);
+ } else if (detectionTimerHandle != null) {
+ // Outlier detection is not configured, but we have a lingering timer. Let's cancel it and
+ // uneject any addresses we may have ejected.
+ detectionTimerHandle.cancel();
+ detectionTimerStartNanos = null;
+ trackerMap.cancelTracking();
+ }
+
+ switchLb.handleResolvedAddresses(
+ resolvedAddresses.toBuilder().setLoadBalancingPolicyConfig(config.childPolicy.getConfig())
+ .build());
+ return true;
+ }
+
+ @Override
+ public void handleNameResolutionError(Status error) {
+ switchLb.handleNameResolutionError(error);
+ }
+
+ @Override
+ public void shutdown() {
+ switchLb.shutdown();
+ }
+
+ /**
+ * This timer will be invoked periodically, according to configuration, and it will look for any
+ * outlier subchannels.
+ */
+ class DetectionTimer implements Runnable {
+
+ OutlierDetectionLoadBalancerConfig config;
+
+ DetectionTimer(OutlierDetectionLoadBalancerConfig config) {
+ this.config = config;
+ }
+
+ @Override
+ public void run() {
+ detectionTimerStartNanos = timeProvider.currentTimeNanos();
+
+ trackerMap.swapCounters();
+
+ for (OutlierEjectionAlgorithm algo : OutlierEjectionAlgorithm.forConfig(config)) {
+ algo.ejectOutliers(trackerMap, detectionTimerStartNanos);
+ }
+
+ trackerMap.maybeUnejectOutliers(detectionTimerStartNanos);
+ }
+ }
+
+ /**
+ * This child helper wraps the provided helper so that it can hand out wrapped {@link
+ * OutlierDetectionSubchannel}s and manage the address info map.
+ */
+ class ChildHelper extends ForwardingLoadBalancerHelper {
+
+ private Helper delegate;
+
+ ChildHelper(Helper delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ protected Helper delegate() {
+ return delegate;
+ }
+
+ @Override
+ public Subchannel createSubchannel(CreateSubchannelArgs args) {
+ // Subchannels are wrapped so that we can monitor call results and to trigger failures when
+ // we decide to eject the subchannel.
+ OutlierDetectionSubchannel subchannel = new OutlierDetectionSubchannel(
+ delegate.createSubchannel(args));
+
+ // If the subchannel is associated with a single address that is also already in the map
+ // the subchannel will be added to the map and be included in outlier detection.
+ List addressGroups = args.getAddresses();
+ if (hasSingleAddress(addressGroups)
+ && trackerMap.containsKey(addressGroups.get(0).getAddresses().get(0))) {
+ AddressTracker tracker = trackerMap.get(addressGroups.get(0).getAddresses().get(0));
+ tracker.addSubchannel(subchannel);
+
+ // If this address has already been ejected, we need to immediately eject this Subchannel.
+ if (tracker.ejectionTimeNanos != null) {
+ subchannel.eject();
+ }
+ }
+
+ return subchannel;
+ }
+
+ @Override
+ public void updateBalancingState(ConnectivityState newState, SubchannelPicker newPicker) {
+ delegate.updateBalancingState(newState, new OutlierDetectionPicker(newPicker));
+ }
+ }
+
+ class OutlierDetectionSubchannel extends ForwardingSubchannel {
+
+ private final Subchannel delegate;
+ private AddressTracker addressTracker;
+ private boolean ejected;
+ private ConnectivityStateInfo lastSubchannelState;
+ private SubchannelStateListener subchannelStateListener;
+
+ OutlierDetectionSubchannel(Subchannel delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void start(SubchannelStateListener listener) {
+ subchannelStateListener = listener;
+ super.start(new OutlierDetectionSubchannelStateListener(listener));
+ }
+
+ @Override
+ public Attributes getAttributes() {
+ if (addressTracker != null) {
+ return delegate.getAttributes().toBuilder().set(ADDRESS_TRACKER_ATTR_KEY, addressTracker)
+ .build();
+ } else {
+ return delegate.getAttributes();
+ }
+ }
+
+ @Override
+ public void updateAddresses(List addressGroups) {
+ // Outlier detection only supports subchannels with a single address, but the list of
+ // addressGroups associated with a subchannel can change at any time, so we need to react to
+ // changes in the address list plurality.
+
+ // No change in address plurality, we replace the single one with a new one.
+ if (hasSingleAddress(getAllAddresses()) && hasSingleAddress(addressGroups)) {
+ // Remove the current subchannel from the old address it is associated with in the map.
+ if (trackerMap.containsValue(addressTracker)) {
+ addressTracker.removeSubchannel(this);
+ }
+
+ // If the map has an entry for the new address, we associate this subchannel with it.
+ SocketAddress address = addressGroups.get(0).getAddresses().get(0);
+ if (trackerMap.containsKey(address)) {
+ trackerMap.get(address).addSubchannel(this);
+ }
+ } else if (hasSingleAddress(getAllAddresses()) && !hasSingleAddress(addressGroups)) {
+ // We go from a single address to having multiple, making this subchannel uneligible for
+ // outlier detection. Remove it from all trackers and reset the call counters of all the
+ // associated trackers.
+ // Remove the current subchannel from the old address it is associated with in the map.
+ if (trackerMap.containsKey(getAddresses().getAddresses().get(0))) {
+ AddressTracker tracker = trackerMap.get(getAddresses().getAddresses().get(0));
+ tracker.removeSubchannel(this);
+ tracker.resetCallCounters();
+ }
+ } else if (!hasSingleAddress(getAllAddresses()) && hasSingleAddress(addressGroups)) {
+ // We go from, previously uneligble, multiple address mode to a single address. If the map
+ // has an entry for the new address, we associate this subchannel with it.
+ SocketAddress address = addressGroups.get(0).getAddresses().get(0);
+ if (trackerMap.containsKey(address)) {
+ AddressTracker tracker = trackerMap.get(address);
+ tracker.addSubchannel(this);
+ }
+ }
+
+ // We could also have multiple addressGroups and get an update for multiple new ones. This is
+ // a no-op as we will just continue to ignore multiple address subchannels.
+
+ delegate.updateAddresses(addressGroups);
+ }
+
+ /**
+ * If the {@link Subchannel} is considered for outlier detection the associated {@link
+ * AddressTracker} should be set.
+ */
+ void setAddressTracker(AddressTracker addressTracker) {
+ this.addressTracker = addressTracker;
+ }
+
+ void clearAddressTracker() {
+ this.addressTracker = null;
+ }
+
+ void eject() {
+ ejected = true;
+ subchannelStateListener.onSubchannelState(
+ ConnectivityStateInfo.forTransientFailure(Status.UNAVAILABLE));
+ }
+
+ void uneject() {
+ ejected = false;
+ if (lastSubchannelState != null) {
+ subchannelStateListener.onSubchannelState(lastSubchannelState);
+ }
+ }
+
+ boolean isEjected() {
+ return ejected;
+ }
+
+ @Override
+ protected Subchannel delegate() {
+ return delegate;
+ }
+
+ /**
+ * Wraps the actual listener so that state changes from the actual one can be intercepted.
+ */
+ class OutlierDetectionSubchannelStateListener implements SubchannelStateListener {
+
+ private final SubchannelStateListener delegate;
+
+ OutlierDetectionSubchannelStateListener(SubchannelStateListener delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void onSubchannelState(ConnectivityStateInfo newState) {
+ lastSubchannelState = newState;
+ if (!ejected) {
+ delegate.onSubchannelState(newState);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * This picker delegates the actual picking logic to a wrapped delegate, but associates a {@link
+ * ClientStreamTracer} with each pick to track the results of each subchannel stream.
+ */
+ class OutlierDetectionPicker extends SubchannelPicker {
+
+ private final SubchannelPicker delegate;
+
+ OutlierDetectionPicker(SubchannelPicker delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public PickResult pickSubchannel(PickSubchannelArgs args) {
+ PickResult pickResult = delegate.pickSubchannel(args);
+
+ Subchannel subchannel = pickResult.getSubchannel();
+ if (subchannel != null) {
+ return PickResult.withSubchannel(subchannel,
+ new ResultCountingClientStreamTracerFactory(
+ subchannel.getAttributes().get(ADDRESS_TRACKER_ATTR_KEY)));
+ }
+
+ return pickResult;
+ }
+
+ /**
+ * Builds instances of {@link ResultCountingClientStreamTracer}.
+ */
+ class ResultCountingClientStreamTracerFactory extends ClientStreamTracer.Factory {
+
+ private final AddressTracker tracker;
+
+ ResultCountingClientStreamTracerFactory(AddressTracker tracker) {
+ this.tracker = tracker;
+ }
+
+ @Override
+ public ClientStreamTracer newClientStreamTracer(StreamInfo info, Metadata headers) {
+ return new ResultCountingClientStreamTracer(tracker);
+ }
+ }
+
+ /**
+ * Counts the results (successful/unsuccessful) of a particular {@link
+ * OutlierDetectionSubchannel}s streams and increments the counter in the associated {@link
+ * AddressTracker}.
+ */
+ class ResultCountingClientStreamTracer extends ClientStreamTracer {
+
+ AddressTracker tracker;
+
+ public ResultCountingClientStreamTracer(AddressTracker tracker) {
+ this.tracker = tracker;
+ }
+
+ @Override
+ public void streamClosed(Status status) {
+ tracker.incrementCallCount(status.isOk());
+ }
+ }
+ }
+
+ /**
+ * Tracks additional information about a set of equivalent addresses needed for outlier
+ * detection.
+ */
+ static class AddressTracker {
+
+ private OutlierDetectionLoadBalancerConfig config;
+ // Marked as volatile to assure that when the inactive counter is swapped in as the new active
+ // one, all threads see the change and don't hold on to a reference to the now inactive counter.
+ private volatile CallCounter activeCallCounter = new CallCounter();
+ private CallCounter inactiveCallCounter = new CallCounter();
+ private Long ejectionTimeNanos;
+ private int ejectionTimeMultiplier;
+ private final Set subchannels = new HashSet<>();
+
+ AddressTracker(OutlierDetectionLoadBalancerConfig config) {
+ this.config = config;
+ }
+
+ void setConfig(OutlierDetectionLoadBalancerConfig config) {
+ this.config = config;
+ }
+
+ /**
+ * Adds a subchannel to the tracker, while assuring that the subchannel ejection status is
+ * updated to match the tracker's if needed.
+ */
+ boolean addSubchannel(OutlierDetectionSubchannel subchannel) {
+ // Make sure that the subchannel is in the same ejection state as the new tracker it is
+ // associated with.
+ if (subchannelsEjected() && !subchannel.isEjected()) {
+ subchannel.eject();
+ } else if (!subchannelsEjected() && subchannel.isEjected()) {
+ subchannel.uneject();
+ }
+ subchannel.setAddressTracker(this);
+ return subchannels.add(subchannel);
+ }
+
+ boolean removeSubchannel(OutlierDetectionSubchannel subchannel) {
+ subchannel.clearAddressTracker();
+ return subchannels.remove(subchannel);
+ }
+
+ boolean containsSubchannel(OutlierDetectionSubchannel subchannel) {
+ return subchannels.contains(subchannel);
+ }
+
+ @VisibleForTesting
+ Set getSubchannels() {
+ return ImmutableSet.copyOf(subchannels);
+ }
+
+ void incrementCallCount(boolean success) {
+ // If neither algorithm is configured, no point in incrementing counters.
+ if (config.successRateEjection == null && config.failurePercentageEjection == null) {
+ return;
+ }
+
+ if (success) {
+ activeCallCounter.successCount.getAndIncrement();
+ } else {
+ activeCallCounter.failureCount.getAndIncrement();
+ }
+ }
+
+ @VisibleForTesting
+ long activeVolume() {
+ return activeCallCounter.successCount.get() + activeCallCounter.failureCount.get();
+ }
+
+ long inactiveVolume() {
+ return inactiveCallCounter.successCount.get() + inactiveCallCounter.failureCount.get();
+ }
+
+ double successRate() {
+ return ((double) inactiveCallCounter.successCount.get()) / inactiveVolume();
+ }
+
+ double failureRate() {
+ return ((double)inactiveCallCounter.failureCount.get()) / inactiveVolume();
+ }
+
+ void resetCallCounters() {
+ activeCallCounter.reset();
+ inactiveCallCounter.reset();
+ }
+
+ void decrementEjectionTimeMultiplier() {
+ // The multiplier should not go negative.
+ ejectionTimeMultiplier = ejectionTimeMultiplier == 0 ? 0 : ejectionTimeMultiplier - 1;
+ }
+
+ void resetEjectionTimeMultiplier() {
+ ejectionTimeMultiplier = 0;
+ }
+
+ /**
+ * Swaps the active and inactive counters.
+ *
+ *
Note that this method is not thread safe as the swap is not done atomically. This is
+ * expected to only be called from the timer that is scheduled at a fixed delay, assuring that
+ * only one timer is active at a time.
+ */
+ void swapCounters() {
+ inactiveCallCounter.reset();
+ CallCounter tempCounter = activeCallCounter;
+ activeCallCounter = inactiveCallCounter;
+ inactiveCallCounter = tempCounter;
+ }
+
+ void ejectSubchannels(long ejectionTimeNanos) {
+ this.ejectionTimeNanos = ejectionTimeNanos;
+ ejectionTimeMultiplier++;
+ for (OutlierDetectionSubchannel subchannel : subchannels) {
+ subchannel.eject();
+ }
+ }
+
+ /**
+ * Uneject a currently ejected address.
+ */
+ void unejectSubchannels() {
+ checkState(ejectionTimeNanos != null, "not currently ejected");
+ ejectionTimeNanos = null;
+ for (OutlierDetectionSubchannel subchannel : subchannels) {
+ subchannel.uneject();
+ }
+ }
+
+ boolean subchannelsEjected() {
+ return ejectionTimeNanos != null;
+ }
+
+ public boolean maxEjectionTimeElapsed(long currentTimeNanos) {
+ // The instant in time beyond which the address should no longer be ejected. Also making sure
+ // we honor any maximum ejection time setting.
+ long maxEjectionDurationSecs
+ = Math.max(config.baseEjectionTimeNanos, config.maxEjectionTimeNanos);
+ long maxEjectionTimeNanos =
+ ejectionTimeNanos + Math.min(
+ config.baseEjectionTimeNanos * ejectionTimeMultiplier,
+ maxEjectionDurationSecs);
+
+ return currentTimeNanos > maxEjectionTimeNanos;
+ }
+
+ /** Tracks both successful and failed call counts. */
+ private static class CallCounter {
+ AtomicLong successCount = new AtomicLong();
+ AtomicLong failureCount = new AtomicLong();
+
+ void reset() {
+ successCount.set(0);
+ failureCount.set(0);
+ }
+ }
+ }
+
+ /**
+ * Maintains a mapping from addresses to their trackers.
+ */
+ static class AddressTrackerMap extends ForwardingMap {
+ private final Map trackerMap;
+
+ AddressTrackerMap() {
+ trackerMap = new HashMap<>();
+ }
+
+ @Override
+ protected Map delegate() {
+ return trackerMap;
+ }
+
+ void updateTrackerConfigs(OutlierDetectionLoadBalancerConfig config) {
+ for (AddressTracker tracker: trackerMap.values()) {
+ tracker.setConfig(config);
+ }
+ }
+
+ /** Adds a new tracker for every given address. */
+ void putNewTrackers(OutlierDetectionLoadBalancerConfig config,
+ Collection addresses) {
+ for (SocketAddress address : addresses) {
+ if (!trackerMap.containsKey(address)) {
+ trackerMap.put(address, new AddressTracker(config));
+ }
+ }
+ }
+
+ /** Resets the call counters for all the trackers in the map. */
+ void resetCallCounters() {
+ for (AddressTracker tracker : trackerMap.values()) {
+ tracker.resetCallCounters();
+ }
+ }
+
+ /**
+ * When OD gets disabled we need to uneject any subchannels that may have been ejected and
+ * to reset the ejection time multiplier.
+ */
+ void cancelTracking() {
+ for (AddressTracker tracker : trackerMap.values()) {
+ if (tracker.subchannelsEjected()) {
+ tracker.unejectSubchannels();
+ }
+ tracker.resetEjectionTimeMultiplier();
+ }
+ }
+
+ /** Swaps the active and inactive counters for each tracker. */
+ void swapCounters() {
+ for (AddressTracker tracker : trackerMap.values()) {
+ tracker.swapCounters();
+ }
+ }
+
+ /**
+ * At the end of a timer run we need to decrement the ejection time multiplier for trackers
+ * that don't have ejected subchannels and uneject ones that have spent the maximum ejection
+ * time allowed.
+ */
+ void maybeUnejectOutliers(Long detectionTimerStartNanos) {
+ for (AddressTracker tracker : trackerMap.values()) {
+ if (!tracker.subchannelsEjected()) {
+ tracker.decrementEjectionTimeMultiplier();
+ }
+
+ if (tracker.subchannelsEjected() && tracker.maxEjectionTimeElapsed(
+ detectionTimerStartNanos)) {
+ tracker.unejectSubchannels();
+ }
+ }
+ }
+
+ /**
+ * How many percent of the addresses have been ejected.
+ */
+ double ejectionPercentage() {
+ if (trackerMap.isEmpty()) {
+ return 0;
+ }
+ int totalAddresses = 0;
+ int ejectedAddresses = 0;
+ for (AddressTracker tracker : trackerMap.values()) {
+ totalAddresses++;
+ if (tracker.subchannelsEjected()) {
+ ejectedAddresses++;
+ }
+ }
+ return ((double)ejectedAddresses / totalAddresses) * 100;
+ }
+ }
+
+
+ /**
+ * Implementations provide different ways of ejecting outlier addresses..
+ */
+ interface OutlierEjectionAlgorithm {
+
+ /** Eject any outlier addresses. */
+ void ejectOutliers(AddressTrackerMap trackerMap, long ejectionTimeNanos);
+
+ /** Builds a list of algorithms that are enabled in the given config. */
+ @Nullable
+ static List forConfig(OutlierDetectionLoadBalancerConfig config) {
+ ImmutableList.Builder algoListBuilder = ImmutableList.builder();
+ if (config.successRateEjection != null) {
+ algoListBuilder.add(new SuccessRateOutlierEjectionAlgorithm(config));
+ }
+ if (config.failurePercentageEjection != null) {
+ algoListBuilder.add(new FailurePercentageOutlierEjectionAlgorithm(config));
+ }
+ return algoListBuilder.build();
+ }
+ }
+
+ /**
+ * This algorithm ejects addresses that don't maintain a required rate of successful calls. The
+ * required rate is not fixed, but is based on the mean and standard deviation of the success
+ * rates of all of the addresses.
+ */
+ static class SuccessRateOutlierEjectionAlgorithm implements OutlierEjectionAlgorithm {
+
+ private final OutlierDetectionLoadBalancerConfig config;
+
+ SuccessRateOutlierEjectionAlgorithm(OutlierDetectionLoadBalancerConfig config) {
+ checkArgument(config.successRateEjection != null, "success rate ejection config is null");
+ this.config = config;
+ }
+
+ @Override
+ public void ejectOutliers(AddressTrackerMap trackerMap, long ejectionTimeNanos) {
+
+ // Only consider addresses that have the minimum request volume specified in the config.
+ List trackersWithVolume = trackersWithVolume(trackerMap,
+ config.successRateEjection.requestVolume);
+ // If we don't have enough addresses with significant volume then there's nothing to do.
+ if (trackersWithVolume.size() < config.successRateEjection.minimumHosts
+ || trackersWithVolume.size() == 0) {
+ return;
+ }
+
+ // Calculate mean and standard deviation of the fractions of successful calls.
+ List successRates = new ArrayList<>();
+ for (AddressTracker tracker : trackersWithVolume) {
+ successRates.add(tracker.successRate());
+ }
+ double mean = mean(successRates);
+ double stdev = standardDeviation(successRates, mean);
+
+ double requiredSuccessRate =
+ mean - stdev * (config.successRateEjection.stdevFactor / 1000f);
+
+ for (AddressTracker tracker : trackersWithVolume) {
+ // If we are above or equal to the max ejection percentage, don't eject any more. This will
+ // allow the total ejections to go one above the max, but at the same time it assures at
+ // least one ejection, which the spec calls for. This behavior matches what Envoy proxy
+ // does.
+ if (trackerMap.ejectionPercentage() >= config.maxEjectionPercent) {
+ return;
+ }
+
+ // If success rate is below the threshold, eject the address.
+ if (tracker.successRate() < requiredSuccessRate) {
+ // Only eject some addresses based on the enforcement percentage.
+ if (new Random().nextInt(100) < config.successRateEjection.enforcementPercentage) {
+ tracker.ejectSubchannels(ejectionTimeNanos);
+ }
+ }
+ }
+ }
+
+ /** Calculates the mean of the given values. */
+ @VisibleForTesting
+ static double mean(Collection values) {
+ double totalValue = 0;
+ for (double value : values) {
+ totalValue += value;
+ }
+
+ return totalValue / values.size();
+ }
+
+ /** Calculates the standard deviation for the given values and their mean. */
+ @VisibleForTesting
+ static double standardDeviation(Collection values, double mean) {
+ double squaredDifferenceSum = 0;
+ for (double value : values) {
+ double difference = value - mean;
+ squaredDifferenceSum += difference * difference;
+ }
+ double variance = squaredDifferenceSum / values.size();
+
+ return Math.sqrt(variance);
+ }
+ }
+
+ static class FailurePercentageOutlierEjectionAlgorithm implements OutlierEjectionAlgorithm {
+
+ private final OutlierDetectionLoadBalancerConfig config;
+
+ FailurePercentageOutlierEjectionAlgorithm(OutlierDetectionLoadBalancerConfig config) {
+ this.config = config;
+ }
+
+ @Override
+ public void ejectOutliers(AddressTrackerMap trackerMap, long ejectionTimeNanos) {
+
+ // Only consider addresses that have the minimum request volume specified in the config.
+ List trackersWithVolume = trackersWithVolume(trackerMap,
+ config.failurePercentageEjection.requestVolume);
+ // If we don't have enough addresses with significant volume then there's nothing to do.
+ if (trackersWithVolume.size() < config.failurePercentageEjection.minimumHosts
+ || trackersWithVolume.size() == 0) {
+ return;
+ }
+
+ // If this address does not have enough volume to be considered, skip to the next one.
+ for (AddressTracker tracker : trackersWithVolume) {
+ // If we are above or equal to the max ejection percentage, don't eject any more. This will
+ // allow the total ejections to go one above the max, but at the same time it assures at
+ // least one ejection, which the spec calls for. This behavior matches what Envoy proxy
+ // does.
+ if (trackerMap.ejectionPercentage() >= config.maxEjectionPercent) {
+ return;
+ }
+
+ if (tracker.inactiveVolume() < config.failurePercentageEjection.requestVolume) {
+ continue;
+ }
+
+ // If the failure rate is above the threshold, we should eject...
+ double maxFailureRate = ((double)config.failurePercentageEjection.threshold) / 100;
+ if (tracker.failureRate() > maxFailureRate) {
+ // ...but only enforce this based on the enforcement percentage.
+ if (new Random().nextInt(100) < config.failurePercentageEjection.enforcementPercentage) {
+ tracker.ejectSubchannels(ejectionTimeNanos);
+ }
+ }
+ }
+ }
+ }
+
+ /** Returns only the trackers that have the minimum configured volume to be considered. */
+ private static List trackersWithVolume(AddressTrackerMap trackerMap,
+ int volume) {
+ List trackersWithVolume = new ArrayList<>();
+ for (AddressTracker tracker : trackerMap.values()) {
+ if (tracker.inactiveVolume() >= volume) {
+ trackersWithVolume.add(tracker);
+ }
+ }
+ return trackersWithVolume;
+ }
+
+ /** Counts how many addresses are in a given address group. */
+ private static boolean hasSingleAddress(List addressGroups) {
+ int addressCount = 0;
+ for (EquivalentAddressGroup addressGroup : addressGroups) {
+ addressCount += addressGroup.getAddresses().size();
+ if (addressCount > 1) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * The configuration for {@link OutlierDetectionLoadBalancer}.
+ */
+ public static final class OutlierDetectionLoadBalancerConfig {
+
+ public final Long intervalNanos;
+ public final Long baseEjectionTimeNanos;
+ public final Long maxEjectionTimeNanos;
+ public final Integer maxEjectionPercent;
+ public final SuccessRateEjection successRateEjection;
+ public final FailurePercentageEjection failurePercentageEjection;
+ public final PolicySelection childPolicy;
+
+ private OutlierDetectionLoadBalancerConfig(Long intervalNanos,
+ Long baseEjectionTimeNanos,
+ Long maxEjectionTimeNanos,
+ Integer maxEjectionPercent,
+ SuccessRateEjection successRateEjection,
+ FailurePercentageEjection failurePercentageEjection,
+ PolicySelection childPolicy) {
+ this.intervalNanos = intervalNanos;
+ this.baseEjectionTimeNanos = baseEjectionTimeNanos;
+ this.maxEjectionTimeNanos = maxEjectionTimeNanos;
+ this.maxEjectionPercent = maxEjectionPercent;
+ this.successRateEjection = successRateEjection;
+ this.failurePercentageEjection = failurePercentageEjection;
+ this.childPolicy = childPolicy;
+ }
+
+ /** Builds a new {@link OutlierDetectionLoadBalancerConfig}. */
+ public static class Builder {
+ Long intervalNanos = 10_000_000_000L; // 10s
+ Long baseEjectionTimeNanos = 30_000_000_000L; // 30s
+ Long maxEjectionTimeNanos = 30_000_000_000L; // 30s
+ Integer maxEjectionPercent = 10;
+ SuccessRateEjection successRateEjection;
+ FailurePercentageEjection failurePercentageEjection;
+ PolicySelection childPolicy;
+
+ /** The interval between outlier detection sweeps. */
+ public Builder setIntervalNanos(Long intervalNanos) {
+ checkArgument(intervalNanos != null);
+ this.intervalNanos = intervalNanos;
+ return this;
+ }
+
+ /** The base time an address is ejected for. */
+ public Builder setBaseEjectionTimeNanos(Long baseEjectionTimeNanos) {
+ checkArgument(baseEjectionTimeNanos != null);
+ this.baseEjectionTimeNanos = baseEjectionTimeNanos;
+ return this;
+ }
+
+ /** The longest time an address can be ejected. */
+ public Builder setMaxEjectionTimeNanos(Long maxEjectionTimeNanos) {
+ checkArgument(maxEjectionTimeNanos != null);
+ this.maxEjectionTimeNanos = maxEjectionTimeNanos;
+ return this;
+ }
+
+ /** The algorithm agnostic maximum percentage of addresses that can be ejected. */
+ public Builder setMaxEjectionPercent(Integer maxEjectionPercent) {
+ checkArgument(maxEjectionPercent != null);
+ this.maxEjectionPercent = maxEjectionPercent;
+ return this;
+ }
+
+ /** Set to enable success rate ejection. */
+ public Builder setSuccessRateEjection(
+ SuccessRateEjection successRateEjection) {
+ this.successRateEjection = successRateEjection;
+ return this;
+ }
+
+ /** Set to enable failure percentage ejection. */
+ public Builder setFailurePercentageEjection(
+ FailurePercentageEjection failurePercentageEjection) {
+ this.failurePercentageEjection = failurePercentageEjection;
+ return this;
+ }
+
+ /** Sets the child policy the {@link OutlierDetectionLoadBalancer} delegates to. */
+ public Builder setChildPolicy(PolicySelection childPolicy) {
+ checkState(childPolicy != null);
+ this.childPolicy = childPolicy;
+ return this;
+ }
+
+ /** Builds a new instance of {@link OutlierDetectionLoadBalancerConfig}. */
+ public OutlierDetectionLoadBalancerConfig build() {
+ checkState(childPolicy != null);
+ return new OutlierDetectionLoadBalancerConfig(intervalNanos, baseEjectionTimeNanos,
+ maxEjectionTimeNanos, maxEjectionPercent, successRateEjection,
+ failurePercentageEjection, childPolicy);
+ }
+ }
+
+ /** The configuration for success rate ejection. */
+ public static class SuccessRateEjection {
+
+ public final Integer stdevFactor;
+ public final Integer enforcementPercentage;
+ public final Integer minimumHosts;
+ public final Integer requestVolume;
+
+ SuccessRateEjection(Integer stdevFactor, Integer enforcementPercentage, Integer minimumHosts,
+ Integer requestVolume) {
+ this.stdevFactor = stdevFactor;
+ this.enforcementPercentage = enforcementPercentage;
+ this.minimumHosts = minimumHosts;
+ this.requestVolume = requestVolume;
+ }
+
+ /** Builds new instances of {@link SuccessRateEjection}. */
+ public static final class Builder {
+
+ Integer stdevFactor = 1900;
+ Integer enforcementPercentage = 100;
+ Integer minimumHosts = 5;
+ Integer requestVolume = 100;
+
+ /** The product of this and the standard deviation of success rates determine the ejection
+ * threshold.
+ */
+ public Builder setStdevFactor(Integer stdevFactor) {
+ checkArgument(stdevFactor != null);
+ this.stdevFactor = stdevFactor;
+ return this;
+ }
+
+ /** Only eject this percentage of outliers. */
+ public Builder setEnforcementPercentage(Integer enforcementPercentage) {
+ checkArgument(enforcementPercentage != null);
+ checkArgument(enforcementPercentage >= 0 && enforcementPercentage <= 100);
+ this.enforcementPercentage = enforcementPercentage;
+ return this;
+ }
+
+ /** The minimum amount of hosts needed for success rate ejection. */
+ public Builder setMinimumHosts(Integer minimumHosts) {
+ checkArgument(minimumHosts != null);
+ checkArgument(minimumHosts >= 0);
+ this.minimumHosts = minimumHosts;
+ return this;
+ }
+
+ /** The minimum address request volume to be considered for success rate ejection. */
+ public Builder setRequestVolume(Integer requestVolume) {
+ checkArgument(requestVolume != null);
+ checkArgument(requestVolume >= 0);
+ this.requestVolume = requestVolume;
+ return this;
+ }
+
+ /** Builds a new instance of {@link SuccessRateEjection}. */
+ public SuccessRateEjection build() {
+ return new SuccessRateEjection(stdevFactor, enforcementPercentage, minimumHosts,
+ requestVolume);
+ }
+ }
+ }
+
+ /** The configuration for failure percentage ejection. */
+ public static class FailurePercentageEjection {
+ public final Integer threshold;
+ public final Integer enforcementPercentage;
+ public final Integer minimumHosts;
+ public final Integer requestVolume;
+
+ FailurePercentageEjection(Integer threshold, Integer enforcementPercentage,
+ Integer minimumHosts, Integer requestVolume) {
+ this.threshold = threshold;
+ this.enforcementPercentage = enforcementPercentage;
+ this.minimumHosts = minimumHosts;
+ this.requestVolume = requestVolume;
+ }
+
+ /** For building new {@link FailurePercentageEjection} instances. */
+ public static class Builder {
+ Integer threshold = 85;
+ Integer enforcementPercentage = 100;
+ Integer minimumHosts = 5;
+ Integer requestVolume = 50;
+
+ /** The failure percentage that will result in an address being considered an outlier. */
+ public Builder setThreshold(Integer threshold) {
+ checkArgument(threshold != null);
+ checkArgument(threshold >= 0 && threshold <= 100);
+ this.threshold = threshold;
+ return this;
+ }
+
+ /** Only eject this percentage of outliers. */
+ public Builder setEnforcementPercentage(Integer enforcementPercentage) {
+ checkArgument(enforcementPercentage != null);
+ checkArgument(enforcementPercentage >= 0 && enforcementPercentage <= 100);
+ this.enforcementPercentage = enforcementPercentage;
+ return this;
+ }
+
+ /** The minimum amount of host for failure percentage ejection to be enabled. */
+ public Builder setMinimumHosts(Integer minimumHosts) {
+ checkArgument(minimumHosts != null);
+ checkArgument(minimumHosts >= 0);
+ this.minimumHosts = minimumHosts;
+ return this;
+ }
+
+ /**
+ * The request volume required for an address to be considered for failure percentage
+ * ejection.
+ */
+ public Builder setRequestVolume(Integer requestVolume) {
+ checkArgument(requestVolume != null);
+ checkArgument(requestVolume >= 0);
+ this.requestVolume = requestVolume;
+ return this;
+ }
+
+ /** Builds a new instance of {@link FailurePercentageEjection}. */
+ public FailurePercentageEjection build() {
+ return new FailurePercentageEjection(threshold, enforcementPercentage, minimumHosts,
+ requestVolume);
+ }
+ }
+ }
+
+ /** Determine if any outlier detection algorithms are enabled in the config. */
+ boolean outlierDetectionEnabled() {
+ return successRateEjection != null || failurePercentageEjection != null;
+ }
+ }
+}
diff --git a/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancerProvider.java b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancerProvider.java
new file mode 100644
index 00000000000..e52c7414653
--- /dev/null
+++ b/core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancerProvider.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2022 The gRPC Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.grpc.util;
+
+import io.grpc.Internal;
+import io.grpc.LoadBalancer;
+import io.grpc.LoadBalancer.Helper;
+import io.grpc.LoadBalancerProvider;
+import io.grpc.LoadBalancerRegistry;
+import io.grpc.NameResolver.ConfigOrError;
+import io.grpc.Status;
+import io.grpc.internal.JsonUtil;
+import io.grpc.internal.ServiceConfigUtil;
+import io.grpc.internal.ServiceConfigUtil.LbConfig;
+import io.grpc.internal.ServiceConfigUtil.PolicySelection;
+import io.grpc.internal.TimeProvider;
+import io.grpc.util.OutlierDetectionLoadBalancer.OutlierDetectionLoadBalancerConfig;
+import io.grpc.util.OutlierDetectionLoadBalancer.OutlierDetectionLoadBalancerConfig.FailurePercentageEjection;
+import io.grpc.util.OutlierDetectionLoadBalancer.OutlierDetectionLoadBalancerConfig.SuccessRateEjection;
+import java.util.List;
+import java.util.Map;
+
+@Internal
+public final class OutlierDetectionLoadBalancerProvider extends LoadBalancerProvider {
+
+ @Override
+ public LoadBalancer newLoadBalancer(Helper helper) {
+ return new OutlierDetectionLoadBalancer(helper, TimeProvider.SYSTEM_TIME_PROVIDER);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ public int getPriority() {
+ return 5;
+ }
+
+ @Override
+ public String getPolicyName() {
+ return "outlier_detection_experimental";
+ }
+
+ @Override
+ public ConfigOrError parseLoadBalancingPolicyConfig(Map rawConfig) {
+ // Common configuration.
+ Long intervalNanos = JsonUtil.getStringAsDuration(rawConfig, "interval");
+ Long baseEjectionTimeNanos = JsonUtil.getStringAsDuration(rawConfig, "baseEjectionTime");
+ Long maxEjectionTimeNanos = JsonUtil.getStringAsDuration(rawConfig, "maxEjectionTime");
+ Integer maxEjectionPercentage = JsonUtil.getNumberAsInteger(rawConfig,
+ "maxEjectionPercentage");
+
+ OutlierDetectionLoadBalancerConfig.Builder configBuilder
+ = new OutlierDetectionLoadBalancerConfig.Builder();
+ if (intervalNanos != null) {
+ configBuilder.setIntervalNanos(intervalNanos);
+ }
+ if (baseEjectionTimeNanos != null) {
+ configBuilder.setBaseEjectionTimeNanos(baseEjectionTimeNanos);
+ }
+ if (maxEjectionTimeNanos != null) {
+ configBuilder.setMaxEjectionTimeNanos(maxEjectionTimeNanos);
+ }
+ if (maxEjectionPercentage != null) {
+ configBuilder.setMaxEjectionPercent(maxEjectionPercentage);
+ }
+
+ // Success rate ejection specific configuration.
+ Map rawSuccessRateEjection = JsonUtil.getObject(rawConfig, "successRateEjection");
+ if (rawSuccessRateEjection != null) {
+ SuccessRateEjection.Builder successRateEjectionBuilder = new SuccessRateEjection.Builder();
+
+ Integer stdevFactor = JsonUtil.getNumberAsInteger(rawSuccessRateEjection, "stdevFactor");
+ Integer enforcementPercentage = JsonUtil.getNumberAsInteger(rawSuccessRateEjection,
+ "enforcementPercentage");
+ Integer minimumHosts = JsonUtil.getNumberAsInteger(rawSuccessRateEjection, "minimumHosts");
+ Integer requestVolume = JsonUtil.getNumberAsInteger(rawSuccessRateEjection, "requestVolume");
+
+ if (stdevFactor != null) {
+ successRateEjectionBuilder.setStdevFactor(stdevFactor);
+ }
+ if (enforcementPercentage != null) {
+ successRateEjectionBuilder.setEnforcementPercentage(enforcementPercentage);
+ }
+ if (minimumHosts != null) {
+ successRateEjectionBuilder.setMinimumHosts(minimumHosts);
+ }
+ if (requestVolume != null) {
+ successRateEjectionBuilder.setRequestVolume(requestVolume);
+ }
+
+ configBuilder.setSuccessRateEjection(successRateEjectionBuilder.build());
+ }
+
+ // Failure percentage ejection specific configuration.
+ Map rawFailurePercentageEjection = JsonUtil.getObject(rawConfig,
+ "failurePercentageEjection");
+ if (rawFailurePercentageEjection != null) {
+ FailurePercentageEjection.Builder failurePercentageEjectionBuilder
+ = new FailurePercentageEjection.Builder();
+
+ Integer threshold = JsonUtil.getNumberAsInteger(rawFailurePercentageEjection, "threshold");
+ Integer enforcementPercentage = JsonUtil.getNumberAsInteger(rawFailurePercentageEjection,
+ "enforcementPercentage");
+ Integer minimumHosts = JsonUtil.getNumberAsInteger(rawFailurePercentageEjection,
+ "minimumHosts");
+ Integer requestVolume = JsonUtil.getNumberAsInteger(rawFailurePercentageEjection,
+ "requestVolume");
+
+ if (threshold != null) {
+ failurePercentageEjectionBuilder.setThreshold(threshold);
+ }
+ if (enforcementPercentage != null) {
+ failurePercentageEjectionBuilder.setEnforcementPercentage(enforcementPercentage);
+ }
+ if (minimumHosts != null) {
+ failurePercentageEjectionBuilder.setMinimumHosts(minimumHosts);
+ }
+ if (requestVolume != null) {
+ failurePercentageEjectionBuilder.setRequestVolume(requestVolume);
+ }
+
+ configBuilder.setFailurePercentageEjection(failurePercentageEjectionBuilder.build());
+ }
+
+ // Child load balancer configuration.
+ List childConfigCandidates = ServiceConfigUtil.unwrapLoadBalancingConfigList(
+ JsonUtil.getListOfObjects(rawConfig, "childPolicy"));
+ if (childConfigCandidates == null || childConfigCandidates.isEmpty()) {
+ return ConfigOrError.fromError(Status.INTERNAL.withDescription(
+ "No child policy in outlier_detection_experimental LB policy: "
+ + rawConfig));
+ }
+ ConfigOrError selectedConfig =
+ ServiceConfigUtil.selectLbPolicyFromList(childConfigCandidates,
+ LoadBalancerRegistry.getDefaultRegistry());
+ if (selectedConfig.getError() != null) {
+ return selectedConfig;
+ }
+ configBuilder.setChildPolicy((PolicySelection) selectedConfig.getConfig());
+
+ return ConfigOrError.fromConfig(configBuilder.build());
+ }
+}
diff --git a/core/src/main/resources/META-INF/services/io.grpc.LoadBalancerProvider b/core/src/main/resources/META-INF/services/io.grpc.LoadBalancerProvider
index cb200d5f044..d68a57c4eb3 100644
--- a/core/src/main/resources/META-INF/services/io.grpc.LoadBalancerProvider
+++ b/core/src/main/resources/META-INF/services/io.grpc.LoadBalancerProvider
@@ -1,2 +1,3 @@
io.grpc.internal.PickFirstLoadBalancerProvider
io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider
+io.grpc.util.OutlierDetectionLoadBalancerProvider
diff --git a/core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest.java b/core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest.java
index bf7c63818cf..ad886c31142 100644
--- a/core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest.java
+++ b/core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest.java
@@ -21,8 +21,8 @@
import static org.mockito.AdditionalAnswers.delegatesTo;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.ArgumentMatchers.same;
-import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -97,9 +97,8 @@ public class AutoConfiguredLoadBalancerFactoryTest {
@Before
public void setUp() {
- when(testLbBalancer.canHandleEmptyAddressListFromNameResolution()).thenCallRealMethod();
- assertThat(testLbBalancer.canHandleEmptyAddressListFromNameResolution()).isFalse();
- when(testLbBalancer2.canHandleEmptyAddressListFromNameResolution()).thenReturn(true);
+ when(testLbBalancer.acceptResolvedAddresses(isA(ResolvedAddresses.class))).thenReturn(true);
+ when(testLbBalancer2.acceptResolvedAddresses(isA(ResolvedAddresses.class))).thenReturn(true);
defaultRegistry.register(testLbBalancerProvider);
defaultRegistry.register(testLbBalancerProvider2);
}
@@ -171,7 +170,7 @@ public void shutdown() {
}
@Test
- public void handleResolvedAddressGroups_keepOldBalancer() {
+ public void acceptResolvedAddresses_keepOldBalancer() {
final List servers =
Collections.singletonList(new EquivalentAddressGroup(new SocketAddress(){}));
Helper helper = new TestHelper() {
@@ -184,19 +183,19 @@ public Subchannel createSubchannel(CreateSubchannelArgs args) {
AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(helper);
LoadBalancer oldDelegate = lb.getDelegate();
- Status handleResult = lb.tryHandleResolvedAddresses(
+ boolean addressesAccepted = lb.tryAcceptResolvedAddresses(
ResolvedAddresses.newBuilder()
.setAddresses(servers)
.setAttributes(Attributes.EMPTY)
.setLoadBalancingPolicyConfig(null)
.build());
- assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK);
+ assertThat(addressesAccepted).isTrue();
assertThat(lb.getDelegate()).isSameInstanceAs(oldDelegate);
}
@Test
- public void handleResolvedAddressGroups_shutsDownOldBalancer() throws Exception {
+ public void acceptResolvedAddresses_shutsDownOldBalancer() throws Exception {
Map serviceConfig =
parseConfig("{\"loadBalancingConfig\": [ {\"round_robin\": { } } ] }");
ConfigOrError lbConfigs = lbf.parseLoadBalancerPolicy(serviceConfig);
@@ -226,13 +225,13 @@ public void shutdown() {
};
lb.setDelegate(testlb);
- Status handleResult = lb.tryHandleResolvedAddresses(
+ boolean addressesAccepted = lb.tryAcceptResolvedAddresses(
ResolvedAddresses.newBuilder()
.setAddresses(servers)
.setLoadBalancingPolicyConfig(lbConfigs.getConfig())
.build());
- assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK);
+ assertThat(addressesAccepted).isTrue();
assertThat(lb.getDelegateProvider().getClass().getName()).isEqualTo(
"io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider");
assertTrue(shutdown.get());
@@ -240,7 +239,7 @@ public void shutdown() {
@Test
@SuppressWarnings("unchecked")
- public void handleResolvedAddressGroups_propagateLbConfigToDelegate() throws Exception {
+ public void acceptResolvedAddresses_propagateLbConfigToDelegate() throws Exception {
Map rawServiceConfig =
parseConfig("{\"loadBalancingConfig\": [ {\"test_lb\": { \"setting1\": \"high\" } } ] }");
ConfigOrError lbConfigs = lbf.parseLoadBalancerPolicy(rawServiceConfig);
@@ -251,20 +250,19 @@ public void handleResolvedAddressGroups_propagateLbConfigToDelegate() throws Exc
Helper helper = new TestHelper();
AutoConfiguredLoadBalancer lb = lbf.newLoadBalancer(helper);
- Status handleResult = lb.tryHandleResolvedAddresses(
+ boolean addressesAccepted = lb.tryAcceptResolvedAddresses(
ResolvedAddresses.newBuilder()
.setAddresses(servers)
.setLoadBalancingPolicyConfig(lbConfigs.getConfig())
.build());
verify(testLbBalancerProvider).newLoadBalancer(same(helper));
- assertThat(handleResult.getCode()).isEqualTo(Status.Code.OK);
+ assertThat(addressesAccepted).isTrue();
assertThat(lb.getDelegate()).isSameInstanceAs(testLbBalancer);
ArgumentCaptor resultCaptor =
ArgumentCaptor.forClass(ResolvedAddresses.class);
- verify(testLbBalancer).handleResolvedAddresses(resultCaptor.capture());
+ verify(testLbBalancer).acceptResolvedAddresses(resultCaptor.capture());
assertThat(resultCaptor.getValue().getAddresses()).containsExactlyElementsIn(servers).inOrder();
- verify(testLbBalancer, atLeast(0)).canHandleEmptyAddressListFromNameResolution();
ArgumentCaptor