From 7d97443cd76b121e256ff4ea55eff2a99e0fac47 Mon Sep 17 00:00:00 2001 From: Eric Gribkoff Date: Wed, 15 Jul 2020 13:51:06 -0700 Subject: [PATCH 01/88] Start 1.32.0 development cycle (#7214) --- build.gradle | 2 +- .../src/test/golden/TestDeprecatedService.java.txt | 2 +- compiler/src/test/golden/TestService.java.txt | 2 +- .../src/testLite/golden/TestDeprecatedService.java.txt | 2 +- compiler/src/testLite/golden/TestService.java.txt | 2 +- core/src/main/java/io/grpc/internal/GrpcUtil.java | 2 +- examples/android/clientcache/app/build.gradle | 10 +++++----- examples/android/helloworld/app/build.gradle | 8 ++++---- examples/android/routeguide/app/build.gradle | 8 ++++---- examples/android/strictmode/app/build.gradle | 8 ++++---- examples/build.gradle | 2 +- examples/example-alts/build.gradle | 2 +- examples/example-gauth/build.gradle | 2 +- examples/example-gauth/pom.xml | 4 ++-- examples/example-hostname/build.gradle | 2 +- examples/example-hostname/pom.xml | 4 ++-- examples/example-jwt-auth/build.gradle | 2 +- examples/example-jwt-auth/pom.xml | 4 ++-- examples/example-tls/build.gradle | 2 +- examples/example-tls/pom.xml | 4 ++-- examples/example-xds/build.gradle | 2 +- examples/pom.xml | 4 ++-- 22 files changed, 40 insertions(+), 40 deletions(-) diff --git a/build.gradle b/build.gradle index 49350d47233..e7e6b5383af 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ subprojects { apply plugin: "net.ltgt.errorprone" group = "io.grpc" - version = "1.31.0-SNAPSHOT" // CURRENT_GRPC_VERSION + version = "1.32.0-SNAPSHOT" // CURRENT_GRPC_VERSION repositories { maven { // The google mirror is less flaky than mavenCentral() diff --git a/compiler/src/test/golden/TestDeprecatedService.java.txt b/compiler/src/test/golden/TestDeprecatedService.java.txt index 878e975d8c4..1374659889b 100644 --- a/compiler/src/test/golden/TestDeprecatedService.java.txt +++ b/compiler/src/test/golden/TestDeprecatedService.java.txt @@ -21,7 +21,7 @@ import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.31.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.32.0-SNAPSHOT)", comments = "Source: grpc/testing/compiler/test.proto") @java.lang.Deprecated public final class TestDeprecatedServiceGrpc { diff --git a/compiler/src/test/golden/TestService.java.txt b/compiler/src/test/golden/TestService.java.txt index 72379370a09..f6fd217cbdd 100644 --- a/compiler/src/test/golden/TestService.java.txt +++ b/compiler/src/test/golden/TestService.java.txt @@ -21,7 +21,7 @@ import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.31.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.32.0-SNAPSHOT)", comments = "Source: grpc/testing/compiler/test.proto") public final class TestServiceGrpc { diff --git a/compiler/src/testLite/golden/TestDeprecatedService.java.txt b/compiler/src/testLite/golden/TestDeprecatedService.java.txt index 1f3752b70b5..694bab3b3d8 100644 --- a/compiler/src/testLite/golden/TestDeprecatedService.java.txt +++ b/compiler/src/testLite/golden/TestDeprecatedService.java.txt @@ -21,7 +21,7 @@ import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.31.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.32.0-SNAPSHOT)", comments = "Source: grpc/testing/compiler/test.proto") @java.lang.Deprecated public final class TestDeprecatedServiceGrpc { diff --git a/compiler/src/testLite/golden/TestService.java.txt b/compiler/src/testLite/golden/TestService.java.txt index 26621d19c2f..50fc255cd4a 100644 --- a/compiler/src/testLite/golden/TestService.java.txt +++ b/compiler/src/testLite/golden/TestService.java.txt @@ -21,7 +21,7 @@ import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.31.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.32.0-SNAPSHOT)", comments = "Source: grpc/testing/compiler/test.proto") public final class TestServiceGrpc { diff --git a/core/src/main/java/io/grpc/internal/GrpcUtil.java b/core/src/main/java/io/grpc/internal/GrpcUtil.java index cd87100e10c..3e210961541 100644 --- a/core/src/main/java/io/grpc/internal/GrpcUtil.java +++ b/core/src/main/java/io/grpc/internal/GrpcUtil.java @@ -197,7 +197,7 @@ public byte[] parseAsciiString(byte[] serialized) { public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults(); - private static final String IMPLEMENTATION_VERSION = "1.31.0-SNAPSHOT"; // CURRENT_GRPC_VERSION + private static final String IMPLEMENTATION_VERSION = "1.32.0-SNAPSHOT"; // CURRENT_GRPC_VERSION /** * The default timeout in nanos for a keepalive ping request. diff --git a/examples/android/clientcache/app/build.gradle b/examples/android/clientcache/app/build.gradle index 9ca462d909c..96e05eba65f 100644 --- a/examples/android/clientcache/app/build.gradle +++ b/examples/android/clientcache/app/build.gradle @@ -30,7 +30,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.12.0' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.31.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -50,12 +50,12 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.0.2' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.31.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.31.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.31.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' testImplementation 'junit:junit:4.12' testImplementation 'com.google.truth:truth:1.0.1' - testImplementation 'io.grpc:grpc-testing:1.31.0-SNAPSHOT' // CURRENT_GRPC_VERSION + testImplementation 'io.grpc:grpc-testing:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION } diff --git a/examples/android/helloworld/app/build.gradle b/examples/android/helloworld/app/build.gradle index e8209b9dca4..e2229f395bc 100644 --- a/examples/android/helloworld/app/build.gradle +++ b/examples/android/helloworld/app/build.gradle @@ -29,7 +29,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.12.0' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.31.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -49,8 +49,8 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.0.2' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.31.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.31.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.31.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/android/routeguide/app/build.gradle b/examples/android/routeguide/app/build.gradle index 76982b8c949..37a18ff858a 100644 --- a/examples/android/routeguide/app/build.gradle +++ b/examples/android/routeguide/app/build.gradle @@ -28,7 +28,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.12.0' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.31.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -48,8 +48,8 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.0.2' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.31.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.31.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.31.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/android/strictmode/app/build.gradle b/examples/android/strictmode/app/build.gradle index 5b1084d534d..03d923d5a80 100644 --- a/examples/android/strictmode/app/build.gradle +++ b/examples/android/strictmode/app/build.gradle @@ -29,7 +29,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.12.0' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.31.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -49,8 +49,8 @@ dependencies { implementation 'com.android.support:appcompat-v7:28.0.0' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.31.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.31.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.31.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/build.gradle b/examples/build.gradle index c4ee9175de5..770e9e28d9e 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -22,7 +22,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.31.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.12.0' def protocVersion = protobufVersion diff --git a/examples/example-alts/build.gradle b/examples/example-alts/build.gradle index e468e06afd6..7f3d202f01f 100644 --- a/examples/example-alts/build.gradle +++ b/examples/example-alts/build.gradle @@ -23,7 +23,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.31.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.12.0' dependencies { diff --git a/examples/example-gauth/build.gradle b/examples/example-gauth/build.gradle index 9505afba55b..76a1563b167 100644 --- a/examples/example-gauth/build.gradle +++ b/examples/example-gauth/build.gradle @@ -23,7 +23,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.31.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.12.0' def protocVersion = protobufVersion diff --git a/examples/example-gauth/pom.xml b/examples/example-gauth/pom.xml index 4072a98d714..779f91a0250 100644 --- a/examples/example-gauth/pom.xml +++ b/examples/example-gauth/pom.xml @@ -6,13 +6,13 @@ jar - 1.31.0-SNAPSHOT + 1.32.0-SNAPSHOT example-gauth https://github.com/grpc/grpc-java UTF-8 - 1.31.0-SNAPSHOT + 1.32.0-SNAPSHOT 3.12.0 1.7 diff --git a/examples/example-hostname/build.gradle b/examples/example-hostname/build.gradle index 7f3c1f53713..b7624483aac 100644 --- a/examples/example-hostname/build.gradle +++ b/examples/example-hostname/build.gradle @@ -21,7 +21,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.31.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.12.0' dependencies { diff --git a/examples/example-hostname/pom.xml b/examples/example-hostname/pom.xml index 9c27bfc500d..fea6ee83fd0 100644 --- a/examples/example-hostname/pom.xml +++ b/examples/example-hostname/pom.xml @@ -6,13 +6,13 @@ jar - 1.31.0-SNAPSHOT + 1.32.0-SNAPSHOT example-hostname https://github.com/grpc/grpc-java UTF-8 - 1.31.0-SNAPSHOT + 1.32.0-SNAPSHOT 3.12.0 1.7 diff --git a/examples/example-jwt-auth/build.gradle b/examples/example-jwt-auth/build.gradle index 351b8576a7a..a597a7e4beb 100644 --- a/examples/example-jwt-auth/build.gradle +++ b/examples/example-jwt-auth/build.gradle @@ -22,7 +22,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.31.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.12.0' def protocVersion = protobufVersion diff --git a/examples/example-jwt-auth/pom.xml b/examples/example-jwt-auth/pom.xml index 00127d3a6c4..cc813513bec 100644 --- a/examples/example-jwt-auth/pom.xml +++ b/examples/example-jwt-auth/pom.xml @@ -7,13 +7,13 @@ jar - 1.31.0-SNAPSHOT + 1.32.0-SNAPSHOT example-jwt-auth https://github.com/grpc/grpc-java UTF-8 - 1.31.0-SNAPSHOT + 1.32.0-SNAPSHOT 3.12.0 3.12.0 diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index fe21a94c4a5..0ae0df47c92 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -23,7 +23,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.31.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION def nettyTcNativeVersion = '2.0.30.Final' def protocVersion = '3.12.0' diff --git a/examples/example-tls/pom.xml b/examples/example-tls/pom.xml index cca6c711827..7efb6aa3e94 100644 --- a/examples/example-tls/pom.xml +++ b/examples/example-tls/pom.xml @@ -6,13 +6,13 @@ jar - 1.31.0-SNAPSHOT + 1.32.0-SNAPSHOT example-tls https://github.com/grpc/grpc-java UTF-8 - 1.31.0-SNAPSHOT + 1.32.0-SNAPSHOT 3.12.0 2.0.30.Final diff --git a/examples/example-xds/build.gradle b/examples/example-xds/build.gradle index 40fa0f47803..4142790c764 100644 --- a/examples/example-xds/build.gradle +++ b/examples/example-xds/build.gradle @@ -18,7 +18,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.31.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION dependencies { // This example's client is the same as the helloworld client. We depend on the helloworld diff --git a/examples/pom.xml b/examples/pom.xml index f55466332bd..d6aa9b6aabc 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -6,13 +6,13 @@ jar - 1.31.0-SNAPSHOT + 1.32.0-SNAPSHOT examples https://github.com/grpc/grpc-java UTF-8 - 1.31.0-SNAPSHOT + 1.32.0-SNAPSHOT 3.12.0 3.12.0 From 92f4842f0f1b2838ffe4d68af8805eb1426df6ef Mon Sep 17 00:00:00 2001 From: Chengyuan Zhang Date: Wed, 15 Jul 2020 21:06:02 +0000 Subject: [PATCH 02/88] xds: perform header matching on concatenated values (#7215) Combine values of header fields with the same key to a comma-separated string before performing header matching. --- xds/src/main/java/io/grpc/xds/RouteMatch.java | 42 +++++++++---------- .../io/grpc/xds/XdsRoutingLoadBalancer.java | 9 +--- .../test/java/io/grpc/xds/RouteMatchTest.java | 23 ++++++---- 3 files changed, 36 insertions(+), 38 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/RouteMatch.java b/xds/src/main/java/io/grpc/xds/RouteMatch.java index 75cbaa9e564..5b52e71c298 100644 --- a/xds/src/main/java/io/grpc/xds/RouteMatch.java +++ b/xds/src/main/java/io/grpc/xds/RouteMatch.java @@ -17,6 +17,7 @@ package io.grpc.xds; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Joiner; import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects.ToStringHelper; import com.google.re2j.Pattern; @@ -25,7 +26,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; import javax.annotation.Nullable; /** @@ -60,7 +60,7 @@ final class RouteMatch { * *

Match is not deterministic if a runtime fraction match rule presents in this RouteMatch. */ - boolean matches(String path, Map> headers) { + boolean matches(String path, Map> headers) { if (!pathMatch.matches(path)) { return false; } @@ -229,35 +229,31 @@ static final class HeaderMatcher { this.isInvertedMatch = isInvertedMatch; } - private boolean matchesValue(@Nullable Set values) { + private boolean matchesValue(@Nullable Iterable values) { if (presentMatch != null) { return (values == null) == presentMatch.equals(isInvertedMatch); } if (values == null) { return false; } - boolean baseMatch = false; - for (String value : values) { - if (exactMatch != null) { - baseMatch = exactMatch.equals(value); - } else if (safeRegExMatch != null) { - baseMatch = safeRegExMatch.matches(value); - } else if (rangeMatch != null) { - long numValue; - try { - numValue = Long.parseLong(value); - } catch (NumberFormatException ignored) { - continue; - } + String valueStr = Joiner.on(",").join(values); + boolean baseMatch; + if (exactMatch != null) { + baseMatch = exactMatch.equals(valueStr); + } else if (safeRegExMatch != null) { + baseMatch = safeRegExMatch.matches(valueStr); + } else if (rangeMatch != null) { + long numValue; + try { + numValue = Long.parseLong(valueStr); baseMatch = rangeMatch.contains(numValue); - } else if (prefixMatch != null) { - baseMatch = value.startsWith(prefixMatch); - } else { - baseMatch = value.endsWith(suffixMatch); - } - if (baseMatch) { - break; + } catch (NumberFormatException ignored) { + baseMatch = false; } + } else if (prefixMatch != null) { + baseMatch = valueStr.startsWith(prefixMatch); + } else { + baseMatch = valueStr.endsWith(suffixMatch); } return baseMatch != isInvertedMatch; } diff --git a/xds/src/main/java/io/grpc/xds/XdsRoutingLoadBalancer.java b/xds/src/main/java/io/grpc/xds/XdsRoutingLoadBalancer.java index 428164ec41a..1cd5045052a 100644 --- a/xds/src/main/java/io/grpc/xds/XdsRoutingLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/XdsRoutingLoadBalancer.java @@ -42,7 +42,6 @@ import io.grpc.xds.XdsRoutingLoadBalancerProvider.XdsRoutingConfig; import io.grpc.xds.XdsSubchannelPickers.ErrorPicker; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -264,18 +263,14 @@ static final class RouteMatchingSubchannelPicker extends SubchannelPicker { @Override public PickResult pickSubchannel(PickSubchannelArgs args) { // Index ASCII headers by keys. - Map> asciiHeaders = new HashMap<>(); + Map> asciiHeaders = new HashMap<>(); Metadata headers = args.getHeaders(); for (String headerName : headers.keys()) { if (headerName.endsWith(Metadata.BINARY_HEADER_SUFFIX)) { continue; } - Set headerValues = new HashSet<>(); Metadata.Key key = Metadata.Key.of(headerName, Metadata.ASCII_STRING_MARSHALLER); - for (String value : headers.getAll(key)) { - headerValues.add(value); - } - asciiHeaders.put(headerName, headerValues); + asciiHeaders.put(headerName, headers.getAll(key)); } for (Map.Entry entry : routePickers.entrySet()) { RouteMatch routeMatch = entry.getKey(); diff --git a/xds/src/test/java/io/grpc/xds/RouteMatchTest.java b/xds/src/test/java/io/grpc/xds/RouteMatchTest.java index 222a4a1de3b..712758e1168 100644 --- a/xds/src/test/java/io/grpc/xds/RouteMatchTest.java +++ b/xds/src/test/java/io/grpc/xds/RouteMatchTest.java @@ -26,9 +26,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -38,15 +36,15 @@ @RunWith(JUnit4.class) public class RouteMatchTest { - private final Map> headers = new HashMap<>(); + private final Map> headers = new HashMap<>(); @Before public void setUp() { - headers.put("content-type", Collections.singleton("application/grpc")); - headers.put("grpc-encoding", Collections.singleton("gzip")); - headers.put("user-agent", Collections.singleton("gRPC-Java")); - headers.put("content-length", Collections.singleton("1000")); - headers.put("custom-key", new HashSet<>(Arrays.asList("custom-value1", "custom-value2"))); + headers.put("content-type", Collections.singletonList("application/grpc")); + headers.put("grpc-encoding", Collections.singletonList("gzip")); + headers.put("user-agent", Collections.singletonList("gRPC-Java")); + headers.put("content-length", Collections.singletonList("1000")); + headers.put("custom-key", Arrays.asList("custom-value1", "custom-value2")); } @Test @@ -135,6 +133,15 @@ public void routeMatching_withHeaders() { null, true)), null); assertThat(routeMatch6.matches("/FooService/barMethod", headers)).isFalse(); + + RouteMatch routeMatch7 = new RouteMatch( + new PathMatcher("/FooService/barMethod", null, null), + Collections.singletonList( + new HeaderMatcher( + "custom-key", "custom-value1,custom-value2", null, null, null, null, + null, false)), + null); + assertThat(routeMatch7.matches("/FooService/barMethod", headers)).isTrue(); } @Test From 44db31d147141ad9e066eb8ed28077dc2ea71999 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 16 Jul 2020 09:39:37 -0700 Subject: [PATCH 03/88] benchmarks: Use correct classpath for scripts 'runtime' is the old classpath, pre-java-library plugin. We could swap to 'runtimeClasspath', but it is cleaner to just use startScript's defaults. We already use that approach in interop-testing. Fixes #7218 --- benchmarks/build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index c59ef8e7039..b3137ca272c 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -61,7 +61,7 @@ task qps_client(type: CreateStartScripts) { "-javaagent:" + configurations.alpnagent.asPath ].plus(vmArgs) outputDir = new File(project.buildDir, 'tmp') - classpath = jar.outputs.files + project.configurations.runtime + classpath = startScripts.classpath } task openloop_client(type: CreateStartScripts) { @@ -71,14 +71,14 @@ task openloop_client(type: CreateStartScripts) { "-javaagent:" + configurations.alpnagent.asPath ].plus(vmArgs) outputDir = new File(project.buildDir, 'tmp') - classpath = jar.outputs.files + project.configurations.runtime + classpath = startScripts.classpath } task qps_server(type: CreateStartScripts) { mainClassName = "io.grpc.benchmarks.qps.AsyncServer" applicationName = "qps_server" outputDir = new File(project.buildDir, 'tmp') - classpath = jar.outputs.files + project.configurations.runtime + classpath = startScripts.classpath } task benchmark_worker(type: CreateStartScripts) { @@ -88,7 +88,7 @@ task benchmark_worker(type: CreateStartScripts) { "-javaagent:" + configurations.alpnagent.asPath ].plus(vmArgs) outputDir = new File(project.buildDir, 'tmp') - classpath = jar.outputs.files + project.configurations.runtime + classpath = startScripts.classpath } applicationDistribution.into("bin") { From d342b111f92c0242715a2edccb9184854eb16682 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Thu, 16 Jul 2020 13:52:03 -0700 Subject: [PATCH 04/88] api: add InternalConfigSelector abstract class --- .../java/io/grpc/InternalConfigSelector.java | 101 ++++++++++++++++++ .../io/grpc/InternalConfigSelectorTest.java | 43 ++++++++ 2 files changed, 144 insertions(+) create mode 100644 api/src/main/java/io/grpc/InternalConfigSelector.java create mode 100644 api/src/test/java/io/grpc/InternalConfigSelectorTest.java diff --git a/api/src/main/java/io/grpc/InternalConfigSelector.java b/api/src/main/java/io/grpc/InternalConfigSelector.java new file mode 100644 index 00000000000..1ab021d06f6 --- /dev/null +++ b/api/src/main/java/io/grpc/InternalConfigSelector.java @@ -0,0 +1,101 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc; + +import static com.google.common.base.Preconditions.checkNotNull; + +import javax.annotation.Nullable; + +// The class can not be located in io.grpc.internal since it is used as a cross-module API. +// Otherwise, shading would break it. +/** + * Per method config selector that the channel or load balancers will use to choose the appropriate + * config or take config related actions for an RPC. + */ +@Internal +public abstract class InternalConfigSelector { + @NameResolver.ResolutionResultAttr + public static final Attributes.Key KEY + = Attributes.Key.create("io.grpc.config-selector"); + + // Use PickSubchannelArgs for SelectConfigArgs for now. May change over time. + /** Selects the config for an PRC. */ + public abstract Result selectConfig(LoadBalancer.PickSubchannelArgs args); + + public static final class Result { + private final Object config; + @Nullable + private final ClientInterceptor interceptor; + + private Result(Object config, @Nullable ClientInterceptor interceptor) { + this.config = checkNotNull(config, "config"); + this.interceptor = interceptor; + } + + /** + * Returns a parsed config. Must have been returned via + * ServiceConfigParser.parseServiceConfig().getConfig() + */ + public Object getConfig() { + return config; + } + + /** + * Returns an interceptor that would be used to modify CallOptions, in addition to monitoring + * call lifecycle. + */ + @Nullable + public ClientInterceptor getInterceptor() { + return interceptor; + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static final class Builder { + private Object config; + private ClientInterceptor interceptor; + + private Builder() {} + + /** + * Sets the parsed config. + * + * @return this + */ + public Builder setConfig(Object config) { + this.config = checkNotNull(config, "config"); + return this; + } + + /** + * Sets the interceptor. + * + * @return this + */ + public Builder setInterceptor(@Nullable ClientInterceptor interceptor) { + this.interceptor = interceptor; + return this; + } + + public Result build() { + return new Result(config, interceptor); + } + } + } +} diff --git a/api/src/test/java/io/grpc/InternalConfigSelectorTest.java b/api/src/test/java/io/grpc/InternalConfigSelectorTest.java new file mode 100644 index 00000000000..a7dac8cbbc4 --- /dev/null +++ b/api/src/test/java/io/grpc/InternalConfigSelectorTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link InternalConfigSelector}. */ +@RunWith(JUnit4.class) +public class InternalConfigSelectorTest { + @Test + public void resultBuilder() { + Object config = "fake_config"; + ClientInterceptor interceptor = mock(ClientInterceptor.class); + InternalConfigSelector.Result.Builder builder = InternalConfigSelector.Result.newBuilder(); + + InternalConfigSelector.Result result = builder.setConfig(config).build(); + assertThat(result.getConfig()).isEqualTo(config); + assertThat(result.getInterceptor()).isNull(); + + result = builder.setConfig(config).setInterceptor(interceptor).build(); + assertThat(result.getConfig()).isEqualTo(config); + assertThat(result.getInterceptor()).isSameInstanceAs(interceptor); + } +} From 1d443e03896979b4ad2bbaa9ef8e6834b4a16c52 Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Sat, 18 Jul 2020 02:40:15 +0900 Subject: [PATCH 05/88] interop-testing: fix flakiness of deadlineExceeded test (#7202) Increase the deadline amount to avoid the false positive of deadline exceeded due to the call is scheduled to start after the deadline. --- .../java/io/grpc/testing/integration/AbstractInteropTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java b/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java index 35cd30583a5..14b4b923d13 100644 --- a/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java +++ b/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java @@ -1114,7 +1114,7 @@ public void deadlineExceeded() throws Exception { // warm up the channel and JVM blockingStub.emptyCall(Empty.getDefaultInstance()); TestServiceGrpc.TestServiceBlockingStub stub = - blockingStub.withDeadlineAfter(100, TimeUnit.MILLISECONDS); + blockingStub.withDeadlineAfter(1, TimeUnit.SECONDS); StreamingOutputCallRequest request = StreamingOutputCallRequest.newBuilder() .addResponseParameters(ResponseParameters.newBuilder() .setIntervalUs((int) TimeUnit.SECONDS.toMicros(20))) From 8ab2c751500160ea37779025d345eb79577e6e14 Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Fri, 17 Jul 2020 11:58:30 -0700 Subject: [PATCH 06/88] xds: fix DistributorWatcher to send last updates to newly added watchers (#7220) --- .../certprovider/CertificateProvider.java | 36 +++++++++++++++--- .../CertificateProviderStoreTest.java | 38 +++++++++++++++++++ 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProvider.java index 0bea184fb94..89d6954898c 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProvider.java @@ -16,6 +16,8 @@ package io.grpc.xds.internal.certprovider; +import static com.google.common.base.Preconditions.checkNotNull; + import com.google.common.annotations.VisibleForTesting; import io.grpc.Status; import io.grpc.xds.internal.sds.Closeable; @@ -47,33 +49,57 @@ public interface Watcher { @VisibleForTesting static final class DistributorWatcher implements Watcher { + private PrivateKey lastKey; + private List lastCertChain; + private List lastTrustedRoots; + @VisibleForTesting final Set downsstreamWatchers = new HashSet<>(); synchronized void addWatcher(Watcher watcher) { downsstreamWatchers.add(watcher); + if (lastKey != null && lastCertChain != null) { + sendLastCertificateUpdate(watcher); + } + if (lastTrustedRoots != null) { + sendLastTrustedRootsUpdate(watcher); + } } synchronized void removeWatcher(Watcher watcher) { downsstreamWatchers.remove(watcher); } + private void sendLastCertificateUpdate(Watcher watcher) { + watcher.updateCertificate(lastKey, lastCertChain); + } + + private void sendLastTrustedRootsUpdate(Watcher watcher) { + watcher.updateTrustedRoots(lastTrustedRoots); + } + @Override - public void updateCertificate(PrivateKey key, List certChain) { + public synchronized void updateCertificate(PrivateKey key, List certChain) { + checkNotNull(key, "key"); + checkNotNull(certChain, "certChain"); + lastKey = key; + lastCertChain = certChain; for (Watcher watcher : downsstreamWatchers) { - watcher.updateCertificate(key, certChain); + sendLastCertificateUpdate(watcher); } } @Override - public void updateTrustedRoots(List trustedRoots) { + public synchronized void updateTrustedRoots(List trustedRoots) { + checkNotNull(trustedRoots, "trustedRoots"); + lastTrustedRoots = trustedRoots; for (Watcher watcher : downsstreamWatchers) { - watcher.updateTrustedRoots(trustedRoots); + sendLastTrustedRootsUpdate(watcher); } } @Override - public void onError(Status errorStatus) { + public synchronized void onError(Status errorStatus) { for (Watcher watcher : downsstreamWatchers) { watcher.onError(errorStatus); } diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/CertificateProviderStoreTest.java b/xds/src/test/java/io/grpc/xds/internal/certprovider/CertificateProviderStoreTest.java index a01fd9c0101..521de29d09a 100644 --- a/xds/src/test/java/io/grpc/xds/internal/certprovider/CertificateProviderStoreTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/certprovider/CertificateProviderStoreTest.java @@ -189,6 +189,44 @@ public void onePluginSameConfig_sameInstance() { assertThat(testCertificateProvider.closeCalled).isEqualTo(1); } + @Test + @SuppressWarnings("deprecation") + public void onePluginSameConfig_secondWatcherAfterFirstNotify() { + registerPlugin("plugin1"); + CertificateProvider.Watcher mockWatcher1 = mock(CertificateProvider.Watcher.class); + CertificateProviderStore.Handle handle1 = certificateProviderStore.createOrGetProvider( + "cert-name1", "plugin1", "config", mockWatcher1, true); + TestCertificateProvider testCertificateProvider = + (TestCertificateProvider) handle1.certProvider; + CertificateProvider.DistributorWatcher distWatcher = testCertificateProvider.getWatcher(); + PrivateKey testKey = mock(PrivateKey.class); + X509Certificate cert = mock(X509Certificate.class); + List testList = ImmutableList.of(cert); + testCertificateProvider.getWatcher().updateCertificate(testKey, testList); + verify(mockWatcher1, times(1)).updateCertificate(eq(testKey), eq(testList)); + testCertificateProvider.getWatcher().updateTrustedRoots(testList); + verify(mockWatcher1, times(1)).updateTrustedRoots(eq(testList)); + testCertificateProvider.getWatcher().onError(Status.CANCELLED); + verify(mockWatcher1, times(1)).onError(eq(Status.CANCELLED)); + reset(mockWatcher1); + + // now add the second watcher + CertificateProvider.Watcher mockWatcher2 = mock(CertificateProvider.Watcher.class); + CertificateProviderStore.Handle unused = certificateProviderStore.createOrGetProvider( + "cert-name1", "plugin1", "config", mockWatcher2, true); + assertThat(distWatcher.downsstreamWatchers).hasSize(2); + // updates sent to the second watcher + verify(mockWatcher2, times(1)).updateCertificate(eq(testKey), eq(testList)); + verify(mockWatcher2, times(1)).updateTrustedRoots(eq(testList)); + // but not errors! + verify(mockWatcher2, never()).onError(eq(Status.CANCELLED)); + // and none to first one + verify(mockWatcher1, never()) + .updateCertificate(any(PrivateKey.class), anyListOf(X509Certificate.class)); + verify(mockWatcher1, never()).updateTrustedRoots(anyListOf(X509Certificate.class)); + verify(mockWatcher1, never()).onError(any(Status.class)); + } + @Test public void onePluginTwoInstances_notifyError() { registerPlugin("plugin1"); From 9f56b8cea2fc82f19b2ae8133f3b109dfb4a26c1 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Fri, 17 Jul 2020 13:33:44 -0700 Subject: [PATCH 07/88] api: change ConfigSelector.Result to use callback instead of interceptor We found that the interceptor approach for `ConfigSelector` would be adding a layer of indirection for no gain: The API Result selectConfig(LoadBalancer.PickSubchannelArgs args) consumes headers among other inputs, because route matching might need to match the headers; and the API produces ClientInterceptor among other outputs. But the headers is not available until clientCall.start(listner, headers), whereas the interceptor need be applied to the call before clientCall.start(). So the input is not available until the output is applied. That means we will need to delay calling the downstream newCall() (which will either be RealChannel or the interceptor) until start() is called. So we want to change to the other approach similar to what c-core is taking: Have `Result(Object config, CallOptions, Runnable committedCallback)`, where CallOption is the selector modified CallOption, and committedCallback is used to monitor the call lifecycle. --- .../java/io/grpc/InternalConfigSelector.java | 41 ++++++++++++++----- .../io/grpc/InternalConfigSelectorTest.java | 22 +++++++--- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/api/src/main/java/io/grpc/InternalConfigSelector.java b/api/src/main/java/io/grpc/InternalConfigSelector.java index 1ab021d06f6..751ff017578 100644 --- a/api/src/main/java/io/grpc/InternalConfigSelector.java +++ b/api/src/main/java/io/grpc/InternalConfigSelector.java @@ -38,12 +38,14 @@ public abstract class InternalConfigSelector { public static final class Result { private final Object config; + private final CallOptions callOptions; @Nullable - private final ClientInterceptor interceptor; + private final Runnable committedCallback; - private Result(Object config, @Nullable ClientInterceptor interceptor) { + private Result(Object config, CallOptions callOptions, @Nullable Runnable committedCallback) { this.config = checkNotNull(config, "config"); - this.interceptor = interceptor; + this.callOptions = checkNotNull(callOptions, "callOptions"); + this.committedCallback = committedCallback; } /** @@ -55,12 +57,18 @@ public Object getConfig() { } /** - * Returns an interceptor that would be used to modify CallOptions, in addition to monitoring - * call lifecycle. + * Returns a config-selector-modified CallOptions for the RPC. + */ + public CallOptions getCallOptions() { + return callOptions; + } + + /** + * Returns a callback to be invoked when the RPC no longer needs a picker. */ @Nullable - public ClientInterceptor getInterceptor() { - return interceptor; + public Runnable getCommittedCallback() { + return committedCallback; } public static Builder newBuilder() { @@ -69,7 +77,8 @@ public static Builder newBuilder() { public static final class Builder { private Object config; - private ClientInterceptor interceptor; + private CallOptions callOptions; + private Runnable committedCallback; private Builder() {} @@ -83,18 +92,28 @@ public Builder setConfig(Object config) { return this; } + /** + * Sets the CallOptions. + * + * @return this + */ + public Builder setCallOptions(CallOptions callOptions) { + this.callOptions = checkNotNull(callOptions, "callOptions"); + return this; + } + /** * Sets the interceptor. * * @return this */ - public Builder setInterceptor(@Nullable ClientInterceptor interceptor) { - this.interceptor = interceptor; + public Builder setCommittedCallback(@Nullable Runnable committedCallback) { + this.committedCallback = committedCallback; return this; } public Result build() { - return new Result(config, interceptor); + return new Result(config, callOptions, committedCallback); } } } diff --git a/api/src/test/java/io/grpc/InternalConfigSelectorTest.java b/api/src/test/java/io/grpc/InternalConfigSelectorTest.java index a7dac8cbbc4..37ab2e54cda 100644 --- a/api/src/test/java/io/grpc/InternalConfigSelectorTest.java +++ b/api/src/test/java/io/grpc/InternalConfigSelectorTest.java @@ -17,7 +17,6 @@ package io.grpc; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.mock; import org.junit.Test; import org.junit.runner.RunWith; @@ -29,15 +28,26 @@ public class InternalConfigSelectorTest { @Test public void resultBuilder() { Object config = "fake_config"; - ClientInterceptor interceptor = mock(ClientInterceptor.class); + CallOptions callOptions = CallOptions.DEFAULT.withAuthority("fake authority"); + Runnable committedCallback = new Runnable() { + @Override + public void run() {} + }; InternalConfigSelector.Result.Builder builder = InternalConfigSelector.Result.newBuilder(); - InternalConfigSelector.Result result = builder.setConfig(config).build(); + InternalConfigSelector.Result result = + builder.setConfig(config).setCallOptions(callOptions).build(); assertThat(result.getConfig()).isEqualTo(config); - assertThat(result.getInterceptor()).isNull(); + assertThat(result.getCallOptions()).isEqualTo(callOptions); + assertThat(result.getCommittedCallback()).isNull(); - result = builder.setConfig(config).setInterceptor(interceptor).build(); + result = builder + .setConfig(config) + .setCallOptions(callOptions) + .setCommittedCallback(committedCallback) + .build(); assertThat(result.getConfig()).isEqualTo(config); - assertThat(result.getInterceptor()).isSameInstanceAs(interceptor); + assertThat(result.getCallOptions()).isEqualTo(callOptions); + assertThat(result.getCommittedCallback()).isSameInstanceAs(committedCallback); } } From e7cd2299c4fb13459cd319a2add2b3c2ebae025c Mon Sep 17 00:00:00 2001 From: ZhenLian Date: Fri, 17 Jul 2020 19:46:51 -0700 Subject: [PATCH 08/88] xds: change package name for CEL lib (#7235) --- .../java/io/grpc/xds/internal/rbac/engine/cel/Activation.java | 2 +- .../io/grpc/xds/internal/rbac/engine/cel/DefaultDispatcher.java | 2 +- .../grpc/xds/internal/rbac/engine/cel/DefaultInterpreter.java | 2 +- .../xds/internal/rbac/engine/cel/DescriptorMessageProvider.java | 2 +- .../java/io/grpc/xds/internal/rbac/engine/cel/Dispatcher.java | 2 +- .../io/grpc/xds/internal/rbac/engine/cel/Interpretable.java | 2 +- .../java/io/grpc/xds/internal/rbac/engine/cel/Interpreter.java | 2 +- .../grpc/xds/internal/rbac/engine/cel/InterpreterException.java | 2 +- .../java/io/grpc/xds/internal/rbac/engine/cel/Metadata.java | 2 +- .../grpc/xds/internal/rbac/engine/cel/RuntimeTypeProvider.java | 2 +- .../io/grpc/xds/internal/rbac/engine/cel/CelInterfaceTest.java | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/Activation.java b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/Activation.java index 28b99a411c7..6e306f8f864 100644 --- a/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/Activation.java +++ b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/Activation.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.cel; +package io.grpc.xds.internal.rbac.engine.cel; import com.google.common.collect.ImmutableMap; import java.util.Map; diff --git a/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/DefaultDispatcher.java b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/DefaultDispatcher.java index 452dd3ab867..6b90784e1ba 100644 --- a/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/DefaultDispatcher.java +++ b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/DefaultDispatcher.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.cel; +package io.grpc.xds.internal.rbac.engine.cel; import java.lang.String; import java.util.List; diff --git a/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/DefaultInterpreter.java b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/DefaultInterpreter.java index e9c9625efce..4d33c28b7f1 100644 --- a/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/DefaultInterpreter.java +++ b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/DefaultInterpreter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.cel; +package io.grpc.xds.internal.rbac.engine.cel; import com.google.api.expr.v1alpha1.Expr; diff --git a/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/DescriptorMessageProvider.java b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/DescriptorMessageProvider.java index a060b041437..66025a84f8b 100644 --- a/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/DescriptorMessageProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/DescriptorMessageProvider.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.cel; +package io.grpc.xds.internal.rbac.engine.cel; import com.google.protobuf.Descriptors.Descriptor; diff --git a/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/Dispatcher.java b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/Dispatcher.java index 30227d948aa..9e1536b79a8 100644 --- a/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/Dispatcher.java +++ b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/Dispatcher.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.cel; +package io.grpc.xds.internal.rbac.engine.cel; import java.util.List; diff --git a/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/Interpretable.java b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/Interpretable.java index 3a823d3a185..49c102ff496 100644 --- a/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/Interpretable.java +++ b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/Interpretable.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.cel; +package io.grpc.xds.internal.rbac.engine.cel; /** * Represent an expression which can be interpreted repeatedly using a given activation. diff --git a/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/Interpreter.java b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/Interpreter.java index 6e85d63d508..919bbe2f450 100644 --- a/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/Interpreter.java +++ b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/Interpreter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.cel; +package io.grpc.xds.internal.rbac.engine.cel; import com.google.api.expr.v1alpha1.Expr; diff --git a/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/InterpreterException.java b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/InterpreterException.java index f2d9c219c3b..e1768a984d4 100644 --- a/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/InterpreterException.java +++ b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/InterpreterException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.cel; +package io.grpc.xds.internal.rbac.engine.cel; import java.lang.Exception; import javax.annotation.Nullable; diff --git a/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/Metadata.java b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/Metadata.java index 48b32ac1bdf..e4e961b065c 100644 --- a/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/Metadata.java +++ b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/Metadata.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.cel; +package io.grpc.xds.internal.rbac.engine.cel; import java.lang.String; diff --git a/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/RuntimeTypeProvider.java b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/RuntimeTypeProvider.java index 7683756f15d..fbddb29ef48 100644 --- a/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/RuntimeTypeProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/RuntimeTypeProvider.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.cel; +package io.grpc.xds.internal.rbac.engine.cel; /** * The {@code RuntimeTypeProvider} is a combination of the MessageProvider diff --git a/xds/src/test/java/io/grpc/xds/internal/rbac/engine/cel/CelInterfaceTest.java b/xds/src/test/java/io/grpc/xds/internal/rbac/engine/cel/CelInterfaceTest.java index 7bfb2e9b73c..194955d7140 100644 --- a/xds/src/test/java/io/grpc/xds/internal/rbac/engine/cel/CelInterfaceTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/rbac/engine/cel/CelInterfaceTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.internal.cel; +package io.grpc.xds.internal.rbac.engine.cel; import static com.google.common.truth.Truth.assertThat; From b9d067677cdeacfb93d8654f8e267b8837c976cc Mon Sep 17 00:00:00 2001 From: Chengyuan Zhang Date: Mon, 20 Jul 2020 19:12:59 +0000 Subject: [PATCH 09/88] xds: implement the top-level LB policy (#7203) The top-level LB policy, which is an aggregator for CDS policies. It maintains the lifecycle of CDS LB policy instances. The pick argument taken from the Channel contains the information to determine which child CDS policy instance should the picking operation be delegated to. The implementation is similar to the action part of what we currently have in the routing policy. The existing routing policy will be refactored to two parts, with the route match part moved into ConfigSelector and action part being this top-level LB policy. --- .../grpc/xds/ClusterManagerLoadBalancer.java | 265 +++++++++++++ .../ClusterManagerLoadBalancerProvider.java | 155 ++++++++ .../main/java/io/grpc/xds/XdsLbPolicies.java | 1 + .../services/io.grpc.LoadBalancerProvider | 1 + ...lusterManagerLoadBalancerProviderTest.java | 145 ++++++++ .../xds/ClusterManagerLoadBalancerTest.java | 351 ++++++++++++++++++ 6 files changed, 918 insertions(+) create mode 100644 xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java create mode 100644 xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancerProvider.java create mode 100644 xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerProviderTest.java create mode 100644 xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerTest.java diff --git a/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java new file mode 100644 index 00000000000..589a42e04c9 --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java @@ -0,0 +1,265 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.grpc.ConnectivityState.CONNECTING; +import static io.grpc.ConnectivityState.IDLE; +import static io.grpc.ConnectivityState.READY; +import static io.grpc.ConnectivityState.TRANSIENT_FAILURE; +import static io.grpc.xds.XdsSubchannelPickers.BUFFER_PICKER; + +import com.google.common.annotations.VisibleForTesting; +import io.grpc.CallOptions; +import io.grpc.ConnectivityState; +import io.grpc.InternalLogId; +import io.grpc.LoadBalancer; +import io.grpc.LoadBalancerProvider; +import io.grpc.Status; +import io.grpc.SynchronizationContext; +import io.grpc.SynchronizationContext.ScheduledHandle; +import io.grpc.internal.ServiceConfigUtil.PolicySelection; +import io.grpc.util.ForwardingLoadBalancerHelper; +import io.grpc.util.GracefulSwitchLoadBalancer; +import io.grpc.xds.ClusterManagerLoadBalancerProvider.ClusterManagerConfig; +import io.grpc.xds.XdsLogger.XdsLogLevel; +import io.grpc.xds.XdsSubchannelPickers.ErrorPicker; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; + +/** + * The top-level load balancing policy. + */ +class ClusterManagerLoadBalancer extends LoadBalancer { + + @VisibleForTesting + static final int DELAYED_CHILD_DELETION_TIME_MINUTES = 15; + @VisibleForTesting + static final CallOptions.Key ROUTING_CLUSTER_NAME_KEY = + CallOptions.Key.create("io.grpc.xds.ROUTING_CLUSTER_NAME_KEY"); + + private final Map childLbStates = new HashMap<>(); + private final Helper helper; + private final SynchronizationContext syncContext; + private final ScheduledExecutorService timeService; + private final XdsLogger logger; + + ClusterManagerLoadBalancer(Helper helper) { + this.helper = checkNotNull(helper, "helper"); + this.syncContext = checkNotNull(helper.getSynchronizationContext(), "syncContext"); + this.timeService = checkNotNull(helper.getScheduledExecutorService(), "timeService"); + logger = XdsLogger.withLogId( + InternalLogId.allocate("cluster_manager-lb", helper.getAuthority())); + logger.log(XdsLogLevel.INFO, "Created"); + } + + @Override + public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { + logger.log(XdsLogLevel.DEBUG, "Received resolution result: {0}", resolvedAddresses); + ClusterManagerConfig config = (ClusterManagerConfig) + resolvedAddresses.getLoadBalancingPolicyConfig(); + Map newChildPolicies = config.childPolicies; + logger.log( + XdsLogLevel.INFO, + "Received cluster_manager lb config: child names={0}", newChildPolicies.keySet()); + for (Map.Entry entry : newChildPolicies.entrySet()) { + final String name = entry.getKey(); + LoadBalancerProvider childPolicyProvider = entry.getValue().getProvider(); + Object childConfig = entry.getValue().getConfig(); + if (!childLbStates.containsKey(name)) { + childLbStates.put(name, new ChildLbState(name, childPolicyProvider)); + } else { + childLbStates.get(name).reactivate(childPolicyProvider); + } + final LoadBalancer childLb = childLbStates.get(name).lb; + final ResolvedAddresses childAddresses = + resolvedAddresses.toBuilder().setLoadBalancingPolicyConfig(childConfig).build(); + syncContext.execute(new Runnable() { + @Override + public void run() { + childLb.handleResolvedAddresses(childAddresses); + } + }); + } + for (String name : childLbStates.keySet()) { + if (!newChildPolicies.containsKey(name)) { + childLbStates.get(name).deactivate(); + } + } + updateOverallBalancingState(); + } + + @Override + public void handleNameResolutionError(Status error) { + logger.log(XdsLogLevel.WARNING, "Received name resolution error: {0}", error); + boolean gotoTransientFailure = true; + for (ChildLbState state : childLbStates.values()) { + if (!state.deactivated) { + gotoTransientFailure = false; + state.lb.handleNameResolutionError(error); + } + } + if (gotoTransientFailure) { + helper.updateBalancingState(TRANSIENT_FAILURE, new ErrorPicker(error)); + } + } + + @Override + public boolean canHandleEmptyAddressListFromNameResolution() { + return true; + } + + @Override + public void shutdown() { + logger.log(XdsLogLevel.INFO, "Shutdown"); + for (ChildLbState state : childLbStates.values()) { + state.shutdown(); + } + } + + private void updateOverallBalancingState() { + ConnectivityState overallState = null; + final Map childPickers = new HashMap<>(); + for (ChildLbState childLbState : childLbStates.values()) { + if (childLbState.deactivated) { + continue; + } + childPickers.put(childLbState.name, childLbState.currentPicker); + overallState = aggregateState(overallState, childLbState.currentState); + } + if (overallState != null) { + SubchannelPicker picker = new SubchannelPicker() { + @Override + public PickResult pickSubchannel(PickSubchannelArgs args) { + String clusterName = args.getCallOptions().getOption(ROUTING_CLUSTER_NAME_KEY); + SubchannelPicker delegate = childPickers.get(clusterName); + if (delegate == null) { + return + PickResult.withError( + Status.UNAVAILABLE.withDescription("Unable to find cluster " + clusterName)); + } + return delegate.pickSubchannel(args); + } + }; + helper.updateBalancingState(overallState, picker); + } + } + + @Nullable + private static ConnectivityState aggregateState( + @Nullable ConnectivityState overallState, ConnectivityState childState) { + if (overallState == null) { + return childState; + } + if (overallState == READY || childState == READY) { + return READY; + } + if (overallState == CONNECTING || childState == CONNECTING) { + return CONNECTING; + } + if (overallState == IDLE || childState == IDLE) { + return IDLE; + } + return overallState; + } + + private final class ChildLbState { + private final String name; + private final GracefulSwitchLoadBalancer lb; + private LoadBalancerProvider policyProvider; + private ConnectivityState currentState = CONNECTING; + private SubchannelPicker currentPicker = BUFFER_PICKER; + private boolean deactivated; + @Nullable + ScheduledHandle deletionTimer; + + ChildLbState(String name, LoadBalancerProvider policyProvider) { + this.name = name; + this.policyProvider = policyProvider; + lb = new GracefulSwitchLoadBalancer(new ChildLbStateHelper()); + lb.switchTo(policyProvider); + } + + void deactivate() { + if (deactivated) { + return; + } + + class DeletionTask implements Runnable { + @Override + public void run() { + shutdown(); + childLbStates.remove(name); + } + } + + deletionTimer = + syncContext.schedule( + new DeletionTask(), + DELAYED_CHILD_DELETION_TIME_MINUTES, + TimeUnit.MINUTES, + timeService); + deactivated = true; + logger.log(XdsLogLevel.DEBUG, "Child balancer {0} deactivated", name); + } + + void reactivate(LoadBalancerProvider policyProvider) { + if (deletionTimer != null && deletionTimer.isPending()) { + deletionTimer.cancel(); + deactivated = false; + logger.log(XdsLogLevel.DEBUG, "Child balancer {0} reactivated", name); + } + if (!this.policyProvider.getPolicyName().equals(policyProvider.getPolicyName())) { + logger.log( + XdsLogLevel.DEBUG, + "Child balancer {0} switching policy from {1} to {2}", + name, this.policyProvider.getPolicyName(), policyProvider.getPolicyName()); + lb.switchTo(policyProvider); + this.policyProvider = policyProvider; + } + } + + void shutdown() { + deactivated = true; + if (deletionTimer != null && deletionTimer.isPending()) { + deletionTimer.cancel(); + } + lb.shutdown(); + logger.log(XdsLogLevel.DEBUG, "Child balancer {0} deleted", name); + } + + private final class ChildLbStateHelper extends ForwardingLoadBalancerHelper { + + @Override + public void updateBalancingState(ConnectivityState newState, SubchannelPicker newPicker) { + currentState = newState; + currentPicker = newPicker; + if (!deactivated) { + updateOverallBalancingState(); + } + } + + @Override + protected Helper delegate() { + return helper; + } + } + } +} diff --git a/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancerProvider.java b/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancerProvider.java new file mode 100644 index 00000000000..e219c0467ac --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancerProvider.java @@ -0,0 +1,155 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.MoreObjects; +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 java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import javax.annotation.Nullable; + +/** + * The provider for the cluster_manager load balancing policy. This class should not be directly + * referenced in code. The policy should be accessed through + * {@link LoadBalancerRegistry#getProvider} with the name "cluster_manager_experimental". + */ +@Internal +public class ClusterManagerLoadBalancerProvider extends LoadBalancerProvider { + + @Nullable + private final LoadBalancerRegistry lbRegistry; + + public ClusterManagerLoadBalancerProvider() { + this(null); + } + + @VisibleForTesting + ClusterManagerLoadBalancerProvider(@Nullable LoadBalancerRegistry lbRegistry) { + this.lbRegistry = lbRegistry; + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public int getPriority() { + return 5; + } + + @Override + public String getPolicyName() { + return XdsLbPolicies.CLUSTER_MANAGER_POLICY_NAME; + } + + @Override + public ConfigOrError parseLoadBalancingPolicyConfig(Map rawConfig) { + Map parsedChildPolicies = new LinkedHashMap<>(); + try { + Map childPolicies = JsonUtil.getObject(rawConfig, "childPolicy"); + if (childPolicies == null || childPolicies.isEmpty()) { + return ConfigOrError.fromError(Status.INTERNAL.withDescription( + "No child policy provided for cluster_manager LB policy: " + rawConfig)); + } + for (String name : childPolicies.keySet()) { + Map childPolicy = JsonUtil.getObject(childPolicies, name); + if (childPolicy == null) { + return ConfigOrError.fromError(Status.INTERNAL.withDescription( + "No config for child " + name + " in cluster_manager LB policy: " + rawConfig)); + } + List childConfigCandidates = + ServiceConfigUtil.unwrapLoadBalancingConfigList( + JsonUtil.getListOfObjects(childPolicy, "lbPolicy")); + if (childConfigCandidates == null || childConfigCandidates.isEmpty()) { + return ConfigOrError.fromError(Status.INTERNAL.withDescription( + "No config specified for child " + name + " in cluster_manager Lb policy: " + + rawConfig)); + } + LoadBalancerRegistry registry = + lbRegistry != null ? lbRegistry : LoadBalancerRegistry.getDefaultRegistry(); + ConfigOrError selectedConfig = + ServiceConfigUtil.selectLbPolicyFromList(childConfigCandidates, registry); + if (selectedConfig.getError() != null) { + Status error = selectedConfig.getError(); + return ConfigOrError.fromError( + Status.INTERNAL + .withCause(error.getCause()) + .withDescription(error.getDescription()) + .augmentDescription("Failed to select config for child " + name)); + } + parsedChildPolicies.put(name, (PolicySelection) selectedConfig.getConfig()); + } + } catch (RuntimeException e) { + return ConfigOrError.fromError( + Status.fromThrowable(e).withDescription( + "Failed to parse cluster_manager LB config: " + rawConfig)); + } + return ConfigOrError.fromConfig(new ClusterManagerConfig(parsedChildPolicies)); + } + + @Override + public LoadBalancer newLoadBalancer(Helper helper) { + return new ClusterManagerLoadBalancer(helper); + } + + static class ClusterManagerConfig { + final Map childPolicies; + + ClusterManagerConfig(Map childPolicies) { + this.childPolicies = Collections.unmodifiableMap(childPolicies); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ClusterManagerConfig)) { + return false; + } + ClusterManagerConfig config = (ClusterManagerConfig) o; + return Objects.equals(childPolicies, config.childPolicies); + } + + @Override + public int hashCode() { + return Objects.hash(childPolicies); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("childPolicies", childPolicies) + .toString(); + } + } +} diff --git a/xds/src/main/java/io/grpc/xds/XdsLbPolicies.java b/xds/src/main/java/io/grpc/xds/XdsLbPolicies.java index 95fa120b0b7..06ec45dc915 100644 --- a/xds/src/main/java/io/grpc/xds/XdsLbPolicies.java +++ b/xds/src/main/java/io/grpc/xds/XdsLbPolicies.java @@ -17,6 +17,7 @@ package io.grpc.xds; final class XdsLbPolicies { + static final String CLUSTER_MANAGER_POLICY_NAME = "cluster_manager_experimental"; static final String CDS_POLICY_NAME = "cds_experimental"; static final String EDS_POLICY_NAME = "eds_experimental"; static final String WEIGHTED_TARGET_POLICY_NAME = "weighted_target_experimental"; diff --git a/xds/src/main/resources/META-INF/services/io.grpc.LoadBalancerProvider b/xds/src/main/resources/META-INF/services/io.grpc.LoadBalancerProvider index 9f83a305988..a3baf7399e2 100644 --- a/xds/src/main/resources/META-INF/services/io.grpc.LoadBalancerProvider +++ b/xds/src/main/resources/META-INF/services/io.grpc.LoadBalancerProvider @@ -2,3 +2,4 @@ io.grpc.xds.CdsLoadBalancerProvider io.grpc.xds.EdsLoadBalancerProvider io.grpc.xds.WeightedTargetLoadBalancerProvider io.grpc.xds.XdsRoutingLoadBalancerProvider +io.grpc.xds.ClusterManagerLoadBalancerProvider diff --git a/xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerProviderTest.java b/xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerProviderTest.java new file mode 100644 index 00000000000..3621761b718 --- /dev/null +++ b/xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerProviderTest.java @@ -0,0 +1,145 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds; + +import static com.google.common.truth.Truth.assertThat; + +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.internal.JsonParser; +import io.grpc.internal.ServiceConfigUtil.PolicySelection; +import io.grpc.xds.ClusterManagerLoadBalancerProvider.ClusterManagerConfig; +import java.io.IOException; +import java.util.Collections; +import java.util.Map; +import org.junit.Test; + +/** Tests for {@link ClusterManagerLoadBalancerProvider}. */ +public class ClusterManagerLoadBalancerProviderTest { + + @Test + public void parseClusterManagerLoadBalancingPolicyConfig() throws IOException { + LoadBalancerRegistry lbRegistry = new LoadBalancerRegistry(); + ClusterManagerLoadBalancerProvider provider = + new ClusterManagerLoadBalancerProvider(lbRegistry); + final Object fooConfig = new Object(); + LoadBalancerProvider lbProviderFoo = new LoadBalancerProvider() { + @Override + public boolean isAvailable() { + return true; + } + + @Override + public int getPriority() { + return 5; + } + + @Override + public String getPolicyName() { + return "foo_policy"; + } + + @Override + public LoadBalancer newLoadBalancer(Helper helper) { + throw new UnsupportedOperationException("Should not be called"); + } + + @Override + public ConfigOrError parseLoadBalancingPolicyConfig( + Map rawLoadBalancingPolicyConfig) { + return ConfigOrError.fromConfig(fooConfig); + } + }; + final Object barConfig = new Object(); + LoadBalancerProvider lbProviderBar = new LoadBalancerProvider() { + @Override + public boolean isAvailable() { + return true; + } + + @Override + public int getPriority() { + return 5; + } + + @Override + public String getPolicyName() { + return "bar_policy"; + } + + @Override + public LoadBalancer newLoadBalancer(Helper helper) { + throw new UnsupportedOperationException("Should not be called"); + } + + @Override + public ConfigOrError parseLoadBalancingPolicyConfig( + Map rawLoadBalancingPolicyConfig) { + return ConfigOrError.fromConfig(barConfig); + } + }; + lbRegistry.register(lbProviderFoo); + lbRegistry.register(lbProviderBar); + + String clusterManagerConfigJson = "{\n" + + " \"childPolicy\": {\n" + + " \"child1\": {\n" + + " \"lbPolicy\": [\n" + + " {\n" + + " \"foo_policy\": {" + + " \"config_name\": \"config_value\"\n" + + " }\n" + + " }\n" + + " ]\n" + + " },\n" + + " \"child2\": {\n" + + " \"lbPolicy\": [\n" + + " {\n" + + " \"bar_policy\": {}\n" + + " }, {\n" + + " \"unsupported\": {}\n" + + " }\n" + + " ]\n" + + " }\n" + + " }\n" + + "}"; + @SuppressWarnings("unchecked") + Map rawLbConfigMap = (Map) JsonParser.parse(clusterManagerConfigJson); + ConfigOrError configOrError = provider.parseLoadBalancingPolicyConfig(rawLbConfigMap); + assertThat(configOrError.getConfig()).isNotNull(); + ClusterManagerConfig config = (ClusterManagerConfig) configOrError.getConfig(); + assertThat(config.childPolicies) + .containsExactly( + "child1", + new PolicySelection( + lbProviderFoo, Collections.singletonMap("config_name", "config_value"), fooConfig), + "child2", + new PolicySelection(lbProviderBar, Collections.emptyMap(), barConfig)); + } + + @Test + public void registered() { + LoadBalancerProvider provider = + LoadBalancerRegistry + .getDefaultRegistry() + .getProvider("cluster_manager_experimental"); + assertThat(provider).isInstanceOf(ClusterManagerLoadBalancerProvider.class); + } +} diff --git a/xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerTest.java new file mode 100644 index 00000000000..257ee3e1810 --- /dev/null +++ b/xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerTest.java @@ -0,0 +1,351 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import io.grpc.CallOptions; +import io.grpc.ConnectivityState; +import io.grpc.EquivalentAddressGroup; +import io.grpc.LoadBalancer; +import io.grpc.LoadBalancer.Helper; +import io.grpc.LoadBalancer.PickResult; +import io.grpc.LoadBalancer.PickSubchannelArgs; +import io.grpc.LoadBalancer.ResolvedAddresses; +import io.grpc.LoadBalancer.Subchannel; +import io.grpc.LoadBalancer.SubchannelPicker; +import io.grpc.LoadBalancerProvider; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.MethodDescriptor.MethodType; +import io.grpc.Status; +import io.grpc.Status.Code; +import io.grpc.SynchronizationContext; +import io.grpc.internal.FakeClock; +import io.grpc.internal.PickSubchannelArgsImpl; +import io.grpc.internal.ServiceConfigUtil.PolicySelection; +import io.grpc.testing.TestMethodDescriptors; +import io.grpc.xds.ClusterManagerLoadBalancerProvider.ClusterManagerConfig; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Tests for {@link ClusterManagerLoadBalancer}. */ +@RunWith(JUnit4.class) +public class ClusterManagerLoadBalancerTest { + + private final SynchronizationContext syncContext = new SynchronizationContext( + new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + throw new AssertionError(e); + } + }); + private final FakeClock fakeClock = new FakeClock(); + + @Captor + ArgumentCaptor pickerCaptor; + @Mock + private LoadBalancer.Helper helper; + + private final Map lbConfigInventory = new HashMap<>(); + private final List childBalancers = new ArrayList<>(); + private LoadBalancer clusterManagerLoadBalancer; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(helper.getSynchronizationContext()).thenReturn(syncContext); + when(helper.getScheduledExecutorService()).thenReturn(fakeClock.getScheduledExecutorService()); + lbConfigInventory.put("childA", new Object()); + lbConfigInventory.put("childB", new Object()); + lbConfigInventory.put("childC", null); + clusterManagerLoadBalancer = new ClusterManagerLoadBalancer(helper); + } + + @After + public void tearDown() { + clusterManagerLoadBalancer.shutdown(); + for (FakeLoadBalancer childLb : childBalancers) { + assertThat(childLb.shutdown).isTrue(); + } + } + + @Test + public void handleResolvedAddressesUpdatesChannelPicker() { + deliverResolvedAddresses(ImmutableMap.of("childA", "policy_a", "childB", "policy_b")); + + verify(helper, atLeastOnce()).updateBalancingState( + eq(ConnectivityState.CONNECTING), pickerCaptor.capture()); + SubchannelPicker picker = pickerCaptor.getValue(); + assertThat(pickSubchannel(picker, "childA")).isEqualTo(PickResult.withNoResult()); + assertThat(pickSubchannel(picker, "childB")).isEqualTo(PickResult.withNoResult()); + assertThat(childBalancers).hasSize(2); + FakeLoadBalancer childBalancer1 = childBalancers.get(0); + FakeLoadBalancer childBalancer2 = childBalancers.get(1); + assertThat(childBalancer1.name).isEqualTo("policy_a"); + assertThat(childBalancer2.name).isEqualTo("policy_b"); + assertThat(childBalancer1.config).isEqualTo(lbConfigInventory.get("childA")); + assertThat(childBalancer2.config).isEqualTo(lbConfigInventory.get("childB")); + + // Receive an updated config. + deliverResolvedAddresses(ImmutableMap.of("childA", "policy_a", "childC", "policy_c")); + + verify(helper, atLeast(2)) + .updateBalancingState(eq(ConnectivityState.CONNECTING), pickerCaptor.capture()); + picker = pickerCaptor.getValue(); + assertThat(pickSubchannel(picker, "childA")).isEqualTo(PickResult.withNoResult()); + assertThat(pickSubchannel(picker, "childC")).isEqualTo(PickResult.withNoResult()); + Status status = pickSubchannel(picker, "childB").getStatus(); + assertThat(status.getCode()).isEqualTo(Code.UNAVAILABLE); + assertThat(status.getDescription()).isEqualTo("Unable to find cluster childB"); + assertThat(fakeClock.numPendingTasks()) + .isEqualTo(1); // (delayed) shutdown because "childB" is removed + assertThat(childBalancer1.shutdown).isFalse(); + assertThat(childBalancer2.shutdown).isFalse(); + + assertThat(childBalancers).hasSize(3); + FakeLoadBalancer childBalancer3 = childBalancers.get(2); + assertThat(childBalancer3.name).isEqualTo("policy_c"); + assertThat(childBalancer3.config).isEqualTo(lbConfigInventory.get("childC")); + + fakeClock.forwardTime( + ClusterManagerLoadBalancer.DELAYED_CHILD_DELETION_TIME_MINUTES, TimeUnit.MINUTES); + assertThat(childBalancer2.shutdown).isTrue(); + } + + @Test + public void updateBalancingStateFromChildBalancers() { + deliverResolvedAddresses(ImmutableMap.of("childA", "policy_a", "childB", "policy_b")); + + assertThat(childBalancers).hasSize(2); + FakeLoadBalancer childBalancer1 = childBalancers.get(0); + FakeLoadBalancer childBalancer2 = childBalancers.get(1); + Subchannel subchannel1 = mock(Subchannel.class); + Subchannel subchannel2 = mock(Subchannel.class); + childBalancer1.deliverSubchannelState(subchannel1, ConnectivityState.READY); + + verify(helper).updateBalancingState(eq(ConnectivityState.READY), pickerCaptor.capture()); + SubchannelPicker picker = pickerCaptor.getValue(); + assertThat(pickSubchannel(picker, "childA").getSubchannel()).isEqualTo(subchannel1); + assertThat(pickSubchannel(picker, "childB")).isEqualTo(PickResult.withNoResult()); + + childBalancer2.deliverSubchannelState(subchannel2, ConnectivityState.READY); + verify(helper, times(2)) + .updateBalancingState(eq(ConnectivityState.READY), pickerCaptor.capture()); + assertThat(pickSubchannel(pickerCaptor.getValue(), "childB").getSubchannel()) + .isEqualTo(subchannel2); + } + + @Test + public void updateBalancingStateFromDeactivatedChildBalancer() { + FakeLoadBalancer balancer = deliverAddressesAndUpdateToRemoveChildPolicy("childA", "policy_a"); + Subchannel subchannel = mock(Subchannel.class); + balancer.deliverSubchannelState(subchannel, ConnectivityState.READY); + verify(helper, never()).updateBalancingState( + eq(ConnectivityState.READY), any(SubchannelPicker.class)); + + deliverResolvedAddresses(ImmutableMap.of("childA", "policy_a")); + verify(helper).updateBalancingState(eq(ConnectivityState.READY), pickerCaptor.capture()); + assertThat(pickSubchannel(pickerCaptor.getValue(), "childA").getSubchannel()) + .isEqualTo(subchannel); + } + + @Test + public void errorPropagation() { + Status error = Status.UNAVAILABLE.withDescription("resolver error"); + clusterManagerLoadBalancer.handleNameResolutionError(error); + verify(helper).updateBalancingState( + eq(ConnectivityState.TRANSIENT_FAILURE), pickerCaptor.capture()); + PickResult result = pickerCaptor.getValue().pickSubchannel(mock(PickSubchannelArgs.class)); + assertThat(result.getStatus().getCode()).isEqualTo(Code.UNAVAILABLE); + assertThat(result.getStatus().getDescription()).isEqualTo("resolver error"); + + deliverResolvedAddresses(ImmutableMap.of("childA", "policy_a", "childB", "policy_b")); + + assertThat(childBalancers).hasSize(2); + FakeLoadBalancer childBalancer1 = childBalancers.get(0); + FakeLoadBalancer childBalancer2 = childBalancers.get(1); + + clusterManagerLoadBalancer.handleNameResolutionError(error); + assertThat(childBalancer1.upstreamError.getCode()).isEqualTo(Code.UNAVAILABLE); + assertThat(childBalancer1.upstreamError.getDescription()).isEqualTo("resolver error"); + assertThat(childBalancer2.upstreamError.getCode()).isEqualTo(Code.UNAVAILABLE); + assertThat(childBalancer2.upstreamError.getDescription()).isEqualTo("resolver error"); + } + + @Test + public void errorPropagationToDeactivatedChildBalancer() { + FakeLoadBalancer balancer = deliverAddressesAndUpdateToRemoveChildPolicy("childA", "policy_a"); + clusterManagerLoadBalancer.handleNameResolutionError( + Status.UNKNOWN.withDescription("unknown error")); + assertThat(balancer.upstreamError).isNull(); + } + + private FakeLoadBalancer deliverAddressesAndUpdateToRemoveChildPolicy( + String childName, String childPolicyName) { + lbConfigInventory.put("childFoo", null); + deliverResolvedAddresses( + ImmutableMap.of(childName, childPolicyName, "childFoo", "policy_foo")); + + verify(helper, atLeastOnce()).updateBalancingState( + eq(ConnectivityState.CONNECTING), any(SubchannelPicker.class)); + assertThat(childBalancers).hasSize(2); + FakeLoadBalancer balancer = childBalancers.get(0); + + deliverResolvedAddresses(ImmutableMap.of("childFoo", "policy_foo")); + verify(helper, atLeast(2)).updateBalancingState( + eq(ConnectivityState.CONNECTING), any(SubchannelPicker.class)); + assertThat(Iterables.getOnlyElement(fakeClock.getPendingTasks()).getDelay(TimeUnit.MINUTES)) + .isEqualTo(ClusterManagerLoadBalancer.DELAYED_CHILD_DELETION_TIME_MINUTES); + return balancer; + } + + private void deliverResolvedAddresses(final Map childPolicies) { + syncContext.execute(new Runnable() { + @Override + public void run() { + clusterManagerLoadBalancer + .handleResolvedAddresses( + ResolvedAddresses.newBuilder() + .setAddresses(Collections.emptyList()) + .setLoadBalancingPolicyConfig(buildConfig(childPolicies)) + .build()); + } + }); + } + + private ClusterManagerConfig buildConfig(Map childPolicies) { + Map childPolicySelections = new LinkedHashMap<>(); + for (String name : childPolicies.keySet()) { + String childPolicyName = childPolicies.get(name); + Object childConfig = lbConfigInventory.get(name); + PolicySelection policy = + new PolicySelection(new FakeLoadBalancerProvider(childPolicyName), null, childConfig); + childPolicySelections.put(name, policy); + } + return new ClusterManagerConfig(childPolicySelections); + } + + private static PickResult pickSubchannel(SubchannelPicker picker, String name) { + PickSubchannelArgs args = + new PickSubchannelArgsImpl( + MethodDescriptor.newBuilder() + .setType(MethodType.UNARY) + .setFullMethodName("/service/method") + .setRequestMarshaller(TestMethodDescriptors.voidMarshaller()) + .setResponseMarshaller(TestMethodDescriptors.voidMarshaller()) + .build(), + new Metadata(), + CallOptions.DEFAULT.withOption( + ClusterManagerLoadBalancer.ROUTING_CLUSTER_NAME_KEY, name)); + return picker.pickSubchannel(args); + } + + private final class FakeLoadBalancerProvider extends LoadBalancerProvider { + private final String policyName; + + FakeLoadBalancerProvider(String policyName) { + this.policyName = policyName; + } + + @Override + public LoadBalancer newLoadBalancer(Helper helper) { + FakeLoadBalancer balancer = new FakeLoadBalancer(policyName, helper); + childBalancers.add(balancer); + return balancer; + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public int getPriority() { + return 0; // doesn't matter + } + + @Override + public String getPolicyName() { + return policyName; + } + } + + private final class FakeLoadBalancer extends LoadBalancer { + private final String name; + private final Helper helper; + private Object config; + private Status upstreamError; + private boolean shutdown; + + FakeLoadBalancer(String name, Helper helper) { + this.name = name; + this.helper = helper; + } + + @Override + public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { + config = resolvedAddresses.getLoadBalancingPolicyConfig(); + } + + @Override + public void handleNameResolutionError(Status error) { + upstreamError = error; + } + + @Override + public void shutdown() { + shutdown = true; + childBalancers.remove(this); + } + + void deliverSubchannelState(final Subchannel subchannel, ConnectivityState state) { + SubchannelPicker picker = new SubchannelPicker() { + @Override + public PickResult pickSubchannel(PickSubchannelArgs args) { + return PickResult.withSubchannel(subchannel); + } + }; + helper.updateBalancingState(state, picker); + } + } +} From 4edbe245f1407f9e12d0332831988d7d0dc2dbce Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Mon, 20 Jul 2020 13:42:57 -0700 Subject: [PATCH 10/88] xds: import v3 proto for ADS service --- .../v3/AggregatedDiscoveryServiceGrpc.java | 376 ++++++++++++++++++ xds/third_party/envoy/import.sh | 2 + .../envoy/service/discovery/v3/ads.proto | 44 ++ .../service/discovery/v3/discovery.proto | 244 ++++++++++++ 4 files changed, 666 insertions(+) create mode 100644 xds/src/generated/main/grpc/io/envoyproxy/envoy/service/discovery/v3/AggregatedDiscoveryServiceGrpc.java create mode 100644 xds/third_party/envoy/src/main/proto/envoy/service/discovery/v3/ads.proto create mode 100644 xds/third_party/envoy/src/main/proto/envoy/service/discovery/v3/discovery.proto diff --git a/xds/src/generated/main/grpc/io/envoyproxy/envoy/service/discovery/v3/AggregatedDiscoveryServiceGrpc.java b/xds/src/generated/main/grpc/io/envoyproxy/envoy/service/discovery/v3/AggregatedDiscoveryServiceGrpc.java new file mode 100644 index 00000000000..a2698b43853 --- /dev/null +++ b/xds/src/generated/main/grpc/io/envoyproxy/envoy/service/discovery/v3/AggregatedDiscoveryServiceGrpc.java @@ -0,0 +1,376 @@ +package io.envoyproxy.envoy.service.discovery.v3; + +import static io.grpc.MethodDescriptor.generateFullMethodName; +import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall; +import static io.grpc.stub.ClientCalls.asyncClientStreamingCall; +import static io.grpc.stub.ClientCalls.asyncServerStreamingCall; +import static io.grpc.stub.ClientCalls.asyncUnaryCall; +import static io.grpc.stub.ClientCalls.blockingServerStreamingCall; +import static io.grpc.stub.ClientCalls.blockingUnaryCall; +import static io.grpc.stub.ClientCalls.futureUnaryCall; +import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall; +import static io.grpc.stub.ServerCalls.asyncClientStreamingCall; +import static io.grpc.stub.ServerCalls.asyncServerStreamingCall; +import static io.grpc.stub.ServerCalls.asyncUnaryCall; +import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall; +import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall; + +/** + *

+ * See https://github.com/lyft/envoy-api#apis for a description of the role of
+ * ADS and how it is intended to be used by a management server. ADS requests
+ * have the same structure as their singleton xDS counterparts, but can
+ * multiplex many resource types on a single stream. The type_url in the
+ * DiscoveryRequest/DiscoveryResponse provides sufficient information to recover
+ * the multiplexed singleton APIs at the Envoy instance and management server.
+ * 
+ */ +@javax.annotation.Generated( + value = "by gRPC proto compiler", + comments = "Source: envoy/service/discovery/v3/ads.proto") +public final class AggregatedDiscoveryServiceGrpc { + + private AggregatedDiscoveryServiceGrpc() {} + + public static final String SERVICE_NAME = "envoy.service.discovery.v3.AggregatedDiscoveryService"; + + // Static method descriptors that strictly reflect the proto. + private static volatile io.grpc.MethodDescriptor getStreamAggregatedResourcesMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "StreamAggregatedResources", + requestType = io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest.class, + responseType = io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse.class, + methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING) + public static io.grpc.MethodDescriptor getStreamAggregatedResourcesMethod() { + io.grpc.MethodDescriptor getStreamAggregatedResourcesMethod; + if ((getStreamAggregatedResourcesMethod = AggregatedDiscoveryServiceGrpc.getStreamAggregatedResourcesMethod) == null) { + synchronized (AggregatedDiscoveryServiceGrpc.class) { + if ((getStreamAggregatedResourcesMethod = AggregatedDiscoveryServiceGrpc.getStreamAggregatedResourcesMethod) == null) { + AggregatedDiscoveryServiceGrpc.getStreamAggregatedResourcesMethod = getStreamAggregatedResourcesMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "StreamAggregatedResources")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse.getDefaultInstance())) + .setSchemaDescriptor(new AggregatedDiscoveryServiceMethodDescriptorSupplier("StreamAggregatedResources")) + .build(); + } + } + } + return getStreamAggregatedResourcesMethod; + } + + private static volatile io.grpc.MethodDescriptor getDeltaAggregatedResourcesMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "DeltaAggregatedResources", + requestType = io.envoyproxy.envoy.service.discovery.v3.DeltaDiscoveryRequest.class, + responseType = io.envoyproxy.envoy.service.discovery.v3.DeltaDiscoveryResponse.class, + methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING) + public static io.grpc.MethodDescriptor getDeltaAggregatedResourcesMethod() { + io.grpc.MethodDescriptor getDeltaAggregatedResourcesMethod; + if ((getDeltaAggregatedResourcesMethod = AggregatedDiscoveryServiceGrpc.getDeltaAggregatedResourcesMethod) == null) { + synchronized (AggregatedDiscoveryServiceGrpc.class) { + if ((getDeltaAggregatedResourcesMethod = AggregatedDiscoveryServiceGrpc.getDeltaAggregatedResourcesMethod) == null) { + AggregatedDiscoveryServiceGrpc.getDeltaAggregatedResourcesMethod = getDeltaAggregatedResourcesMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "DeltaAggregatedResources")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + io.envoyproxy.envoy.service.discovery.v3.DeltaDiscoveryRequest.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + io.envoyproxy.envoy.service.discovery.v3.DeltaDiscoveryResponse.getDefaultInstance())) + .setSchemaDescriptor(new AggregatedDiscoveryServiceMethodDescriptorSupplier("DeltaAggregatedResources")) + .build(); + } + } + } + return getDeltaAggregatedResourcesMethod; + } + + /** + * Creates a new async stub that supports all call types for the service + */ + public static AggregatedDiscoveryServiceStub newStub(io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @java.lang.Override + public AggregatedDiscoveryServiceStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new AggregatedDiscoveryServiceStub(channel, callOptions); + } + }; + return AggregatedDiscoveryServiceStub.newStub(factory, channel); + } + + /** + * Creates a new blocking-style stub that supports unary and streaming output calls on the service + */ + public static AggregatedDiscoveryServiceBlockingStub newBlockingStub( + io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @java.lang.Override + public AggregatedDiscoveryServiceBlockingStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new AggregatedDiscoveryServiceBlockingStub(channel, callOptions); + } + }; + return AggregatedDiscoveryServiceBlockingStub.newStub(factory, channel); + } + + /** + * Creates a new ListenableFuture-style stub that supports unary calls on the service + */ + public static AggregatedDiscoveryServiceFutureStub newFutureStub( + io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @java.lang.Override + public AggregatedDiscoveryServiceFutureStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new AggregatedDiscoveryServiceFutureStub(channel, callOptions); + } + }; + return AggregatedDiscoveryServiceFutureStub.newStub(factory, channel); + } + + /** + *
+   * See https://github.com/lyft/envoy-api#apis for a description of the role of
+   * ADS and how it is intended to be used by a management server. ADS requests
+   * have the same structure as their singleton xDS counterparts, but can
+   * multiplex many resource types on a single stream. The type_url in the
+   * DiscoveryRequest/DiscoveryResponse provides sufficient information to recover
+   * the multiplexed singleton APIs at the Envoy instance and management server.
+   * 
+ */ + public static abstract class AggregatedDiscoveryServiceImplBase implements io.grpc.BindableService { + + /** + *
+     * This is a gRPC-only API.
+     * 
+ */ + public io.grpc.stub.StreamObserver streamAggregatedResources( + io.grpc.stub.StreamObserver responseObserver) { + return asyncUnimplementedStreamingCall(getStreamAggregatedResourcesMethod(), responseObserver); + } + + /** + */ + public io.grpc.stub.StreamObserver deltaAggregatedResources( + io.grpc.stub.StreamObserver responseObserver) { + return asyncUnimplementedStreamingCall(getDeltaAggregatedResourcesMethod(), responseObserver); + } + + @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() { + return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor()) + .addMethod( + getStreamAggregatedResourcesMethod(), + asyncBidiStreamingCall( + new MethodHandlers< + io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest, + io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse>( + this, METHODID_STREAM_AGGREGATED_RESOURCES))) + .addMethod( + getDeltaAggregatedResourcesMethod(), + asyncBidiStreamingCall( + new MethodHandlers< + io.envoyproxy.envoy.service.discovery.v3.DeltaDiscoveryRequest, + io.envoyproxy.envoy.service.discovery.v3.DeltaDiscoveryResponse>( + this, METHODID_DELTA_AGGREGATED_RESOURCES))) + .build(); + } + } + + /** + *
+   * See https://github.com/lyft/envoy-api#apis for a description of the role of
+   * ADS and how it is intended to be used by a management server. ADS requests
+   * have the same structure as their singleton xDS counterparts, but can
+   * multiplex many resource types on a single stream. The type_url in the
+   * DiscoveryRequest/DiscoveryResponse provides sufficient information to recover
+   * the multiplexed singleton APIs at the Envoy instance and management server.
+   * 
+ */ + public static final class AggregatedDiscoveryServiceStub extends io.grpc.stub.AbstractAsyncStub { + private AggregatedDiscoveryServiceStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @java.lang.Override + protected AggregatedDiscoveryServiceStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new AggregatedDiscoveryServiceStub(channel, callOptions); + } + + /** + *
+     * This is a gRPC-only API.
+     * 
+ */ + public io.grpc.stub.StreamObserver streamAggregatedResources( + io.grpc.stub.StreamObserver responseObserver) { + return asyncBidiStreamingCall( + getChannel().newCall(getStreamAggregatedResourcesMethod(), getCallOptions()), responseObserver); + } + + /** + */ + public io.grpc.stub.StreamObserver deltaAggregatedResources( + io.grpc.stub.StreamObserver responseObserver) { + return asyncBidiStreamingCall( + getChannel().newCall(getDeltaAggregatedResourcesMethod(), getCallOptions()), responseObserver); + } + } + + /** + *
+   * See https://github.com/lyft/envoy-api#apis for a description of the role of
+   * ADS and how it is intended to be used by a management server. ADS requests
+   * have the same structure as their singleton xDS counterparts, but can
+   * multiplex many resource types on a single stream. The type_url in the
+   * DiscoveryRequest/DiscoveryResponse provides sufficient information to recover
+   * the multiplexed singleton APIs at the Envoy instance and management server.
+   * 
+ */ + public static final class AggregatedDiscoveryServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub { + private AggregatedDiscoveryServiceBlockingStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @java.lang.Override + protected AggregatedDiscoveryServiceBlockingStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new AggregatedDiscoveryServiceBlockingStub(channel, callOptions); + } + } + + /** + *
+   * See https://github.com/lyft/envoy-api#apis for a description of the role of
+   * ADS and how it is intended to be used by a management server. ADS requests
+   * have the same structure as their singleton xDS counterparts, but can
+   * multiplex many resource types on a single stream. The type_url in the
+   * DiscoveryRequest/DiscoveryResponse provides sufficient information to recover
+   * the multiplexed singleton APIs at the Envoy instance and management server.
+   * 
+ */ + public static final class AggregatedDiscoveryServiceFutureStub extends io.grpc.stub.AbstractFutureStub { + private AggregatedDiscoveryServiceFutureStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @java.lang.Override + protected AggregatedDiscoveryServiceFutureStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new AggregatedDiscoveryServiceFutureStub(channel, callOptions); + } + } + + private static final int METHODID_STREAM_AGGREGATED_RESOURCES = 0; + private static final int METHODID_DELTA_AGGREGATED_RESOURCES = 1; + + private static final class MethodHandlers implements + io.grpc.stub.ServerCalls.UnaryMethod, + io.grpc.stub.ServerCalls.ServerStreamingMethod, + io.grpc.stub.ServerCalls.ClientStreamingMethod, + io.grpc.stub.ServerCalls.BidiStreamingMethod { + private final AggregatedDiscoveryServiceImplBase serviceImpl; + private final int methodId; + + MethodHandlers(AggregatedDiscoveryServiceImplBase serviceImpl, int methodId) { + this.serviceImpl = serviceImpl; + this.methodId = methodId; + } + + @java.lang.Override + @java.lang.SuppressWarnings("unchecked") + public void invoke(Req request, io.grpc.stub.StreamObserver responseObserver) { + switch (methodId) { + default: + throw new AssertionError(); + } + } + + @java.lang.Override + @java.lang.SuppressWarnings("unchecked") + public io.grpc.stub.StreamObserver invoke( + io.grpc.stub.StreamObserver responseObserver) { + switch (methodId) { + case METHODID_STREAM_AGGREGATED_RESOURCES: + return (io.grpc.stub.StreamObserver) serviceImpl.streamAggregatedResources( + (io.grpc.stub.StreamObserver) responseObserver); + case METHODID_DELTA_AGGREGATED_RESOURCES: + return (io.grpc.stub.StreamObserver) serviceImpl.deltaAggregatedResources( + (io.grpc.stub.StreamObserver) responseObserver); + default: + throw new AssertionError(); + } + } + } + + private static abstract class AggregatedDiscoveryServiceBaseDescriptorSupplier + implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier { + AggregatedDiscoveryServiceBaseDescriptorSupplier() {} + + @java.lang.Override + public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() { + return io.envoyproxy.envoy.service.discovery.v3.AdsProto.getDescriptor(); + } + + @java.lang.Override + public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() { + return getFileDescriptor().findServiceByName("AggregatedDiscoveryService"); + } + } + + private static final class AggregatedDiscoveryServiceFileDescriptorSupplier + extends AggregatedDiscoveryServiceBaseDescriptorSupplier { + AggregatedDiscoveryServiceFileDescriptorSupplier() {} + } + + private static final class AggregatedDiscoveryServiceMethodDescriptorSupplier + extends AggregatedDiscoveryServiceBaseDescriptorSupplier + implements io.grpc.protobuf.ProtoMethodDescriptorSupplier { + private final String methodName; + + AggregatedDiscoveryServiceMethodDescriptorSupplier(String methodName) { + this.methodName = methodName; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() { + return getServiceDescriptor().findMethodByName(methodName); + } + } + + private static volatile io.grpc.ServiceDescriptor serviceDescriptor; + + public static io.grpc.ServiceDescriptor getServiceDescriptor() { + io.grpc.ServiceDescriptor result = serviceDescriptor; + if (result == null) { + synchronized (AggregatedDiscoveryServiceGrpc.class) { + result = serviceDescriptor; + if (result == null) { + serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME) + .setSchemaDescriptor(new AggregatedDiscoveryServiceFileDescriptorSupplier()) + .addMethod(getStreamAggregatedResourcesMethod()) + .addMethod(getDeltaAggregatedResourcesMethod()) + .build(); + } + } + } + return result; + } +} diff --git a/xds/third_party/envoy/import.sh b/xds/third_party/envoy/import.sh index e528611d171..cd16468c50b 100755 --- a/xds/third_party/envoy/import.sh +++ b/xds/third_party/envoy/import.sh @@ -117,6 +117,8 @@ envoy/extensions/transport_sockets/tls/v3/secret.proto envoy/extensions/transport_sockets/tls/v3/tls.proto envoy/service/discovery/v2/ads.proto envoy/service/discovery/v2/sds.proto +envoy/service/discovery/v3/ads.proto +envoy/service/discovery/v3/discovery.proto envoy/service/load_stats/v2/lrs.proto envoy/type/http.proto envoy/type/matcher/regex.proto diff --git a/xds/third_party/envoy/src/main/proto/envoy/service/discovery/v3/ads.proto b/xds/third_party/envoy/src/main/proto/envoy/service/discovery/v3/ads.proto new file mode 100644 index 00000000000..03021559ab6 --- /dev/null +++ b/xds/third_party/envoy/src/main/proto/envoy/service/discovery/v3/ads.proto @@ -0,0 +1,44 @@ +syntax = "proto3"; + +package envoy.service.discovery.v3; + +import "envoy/service/discovery/v3/discovery.proto"; + +import "udpa/annotations/status.proto"; +import "udpa/annotations/versioning.proto"; + +option java_package = "io.envoyproxy.envoy.service.discovery.v3"; +option java_outer_classname = "AdsProto"; +option java_multiple_files = true; +option java_generic_services = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Aggregated Discovery Service (ADS)] + +// [#not-implemented-hide:] Discovery services for endpoints, clusters, routes, +// and listeners are retained in the package `envoy.api.v2` for backwards +// compatibility with existing management servers. New development in discovery +// services should proceed in the package `envoy.service.discovery.v2`. + +// See https://github.com/lyft/envoy-api#apis for a description of the role of +// ADS and how it is intended to be used by a management server. ADS requests +// have the same structure as their singleton xDS counterparts, but can +// multiplex many resource types on a single stream. The type_url in the +// DiscoveryRequest/DiscoveryResponse provides sufficient information to recover +// the multiplexed singleton APIs at the Envoy instance and management server. +service AggregatedDiscoveryService { + // This is a gRPC-only API. + rpc StreamAggregatedResources(stream DiscoveryRequest) returns (stream DiscoveryResponse) { + } + + rpc DeltaAggregatedResources(stream DeltaDiscoveryRequest) + returns (stream DeltaDiscoveryResponse) { + } +} + +// [#not-implemented-hide:] Not configuration. Workaround c++ protobuf issue with importing +// services: https://github.com/google/protobuf/issues/4221 +message AdsDummy { + option (udpa.annotations.versioning).previous_message_type = + "envoy.service.discovery.v2.AdsDummy"; +} diff --git a/xds/third_party/envoy/src/main/proto/envoy/service/discovery/v3/discovery.proto b/xds/third_party/envoy/src/main/proto/envoy/service/discovery/v3/discovery.proto new file mode 100644 index 00000000000..b8e31160a88 --- /dev/null +++ b/xds/third_party/envoy/src/main/proto/envoy/service/discovery/v3/discovery.proto @@ -0,0 +1,244 @@ +syntax = "proto3"; + +package envoy.service.discovery.v3; + +import "envoy/config/core/v3/base.proto"; + +import "google/protobuf/any.proto"; +import "google/rpc/status.proto"; + +import "udpa/annotations/status.proto"; +import "udpa/annotations/versioning.proto"; + +option java_package = "io.envoyproxy.envoy.service.discovery.v3"; +option java_outer_classname = "DiscoveryProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Common discovery API components] + +// A DiscoveryRequest requests a set of versioned resources of the same type for +// a given Envoy node on some API. +// [#next-free-field: 7] +message DiscoveryRequest { + option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.DiscoveryRequest"; + + // The version_info provided in the request messages will be the version_info + // received with the most recent successfully processed response or empty on + // the first request. It is expected that no new request is sent after a + // response is received until the Envoy instance is ready to ACK/NACK the new + // configuration. ACK/NACK takes place by returning the new API config version + // as applied or the previous API config version respectively. Each type_url + // (see below) has an independent version associated with it. + string version_info = 1; + + // The node making the request. + config.core.v3.Node node = 2; + + // List of resources to subscribe to, e.g. list of cluster names or a route + // configuration name. If this is empty, all resources for the API are + // returned. LDS/CDS may have empty resource_names, which will cause all + // resources for the Envoy instance to be returned. The LDS and CDS responses + // will then imply a number of resources that need to be fetched via EDS/RDS, + // which will be explicitly enumerated in resource_names. + repeated string resource_names = 3; + + // Type of the resource that is being requested, e.g. + // "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment". This is implicit + // in requests made via singleton xDS APIs such as CDS, LDS, etc. but is + // required for ADS. + string type_url = 4; + + // nonce corresponding to DiscoveryResponse being ACK/NACKed. See above + // discussion on version_info and the DiscoveryResponse nonce comment. This + // may be empty only if 1) this is a non-persistent-stream xDS such as HTTP, + // or 2) the client has not yet accepted an update in this xDS stream (unlike + // delta, where it is populated only for new explicit ACKs). + string response_nonce = 5; + + // This is populated when the previous :ref:`DiscoveryResponse ` + // failed to update configuration. The *message* field in *error_details* provides the Envoy + // internal exception related to the failure. It is only intended for consumption during manual + // debugging, the string provided is not guaranteed to be stable across Envoy versions. + google.rpc.Status error_detail = 6; +} + +// [#next-free-field: 7] +message DiscoveryResponse { + option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.DiscoveryResponse"; + + // The version of the response data. + string version_info = 1; + + // The response resources. These resources are typed and depend on the API being called. + repeated google.protobuf.Any resources = 2; + + // [#not-implemented-hide:] + // Canary is used to support two Envoy command line flags: + // + // * --terminate-on-canary-transition-failure. When set, Envoy is able to + // terminate if it detects that configuration is stuck at canary. Consider + // this example sequence of updates: + // - Management server applies a canary config successfully. + // - Management server rolls back to a production config. + // - Envoy rejects the new production config. + // Since there is no sensible way to continue receiving configuration + // updates, Envoy will then terminate and apply production config from a + // clean slate. + // * --dry-run-canary. When set, a canary response will never be applied, only + // validated via a dry run. + bool canary = 3; + + // Type URL for resources. Identifies the xDS API when muxing over ADS. + // Must be consistent with the type_url in the 'resources' repeated Any (if non-empty). + string type_url = 4; + + // For gRPC based subscriptions, the nonce provides a way to explicitly ack a + // specific DiscoveryResponse in a following DiscoveryRequest. Additional + // messages may have been sent by Envoy to the management server for the + // previous version on the stream prior to this DiscoveryResponse, that were + // unprocessed at response send time. The nonce allows the management server + // to ignore any further DiscoveryRequests for the previous version until a + // DiscoveryRequest bearing the nonce. The nonce is optional and is not + // required for non-stream based xDS implementations. + string nonce = 5; + + // [#not-implemented-hide:] + // The control plane instance that sent the response. + config.core.v3.ControlPlane control_plane = 6; +} + +// DeltaDiscoveryRequest and DeltaDiscoveryResponse are used in a new gRPC +// endpoint for Delta xDS. +// +// With Delta xDS, the DeltaDiscoveryResponses do not need to include a full +// snapshot of the tracked resources. Instead, DeltaDiscoveryResponses are a +// diff to the state of a xDS client. +// In Delta XDS there are per-resource versions, which allow tracking state at +// the resource granularity. +// An xDS Delta session is always in the context of a gRPC bidirectional +// stream. This allows the xDS server to keep track of the state of xDS clients +// connected to it. +// +// In Delta xDS the nonce field is required and used to pair +// DeltaDiscoveryResponse to a DeltaDiscoveryRequest ACK or NACK. +// Optionally, a response message level system_version_info is present for +// debugging purposes only. +// +// DeltaDiscoveryRequest plays two independent roles. Any DeltaDiscoveryRequest +// can be either or both of: [1] informing the server of what resources the +// client has gained/lost interest in (using resource_names_subscribe and +// resource_names_unsubscribe), or [2] (N)ACKing an earlier resource update from +// the server (using response_nonce, with presence of error_detail making it a NACK). +// Additionally, the first message (for a given type_url) of a reconnected gRPC stream +// has a third role: informing the server of the resources (and their versions) +// that the client already possesses, using the initial_resource_versions field. +// +// As with state-of-the-world, when multiple resource types are multiplexed (ADS), +// all requests/acknowledgments/updates are logically walled off by type_url: +// a Cluster ACK exists in a completely separate world from a prior Route NACK. +// In particular, initial_resource_versions being sent at the "start" of every +// gRPC stream actually entails a message for each type_url, each with its own +// initial_resource_versions. +// [#next-free-field: 8] +message DeltaDiscoveryRequest { + option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.DeltaDiscoveryRequest"; + + // The node making the request. + config.core.v3.Node node = 1; + + // Type of the resource that is being requested, e.g. + // "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment". + string type_url = 2; + + // DeltaDiscoveryRequests allow the client to add or remove individual + // resources to the set of tracked resources in the context of a stream. + // All resource names in the resource_names_subscribe list are added to the + // set of tracked resources and all resource names in the resource_names_unsubscribe + // list are removed from the set of tracked resources. + // + // *Unlike* state-of-the-world xDS, an empty resource_names_subscribe or + // resource_names_unsubscribe list simply means that no resources are to be + // added or removed to the resource list. + // *Like* state-of-the-world xDS, the server must send updates for all tracked + // resources, but can also send updates for resources the client has not subscribed to. + // + // NOTE: the server must respond with all resources listed in resource_names_subscribe, + // even if it believes the client has the most recent version of them. The reason: + // the client may have dropped them, but then regained interest before it had a chance + // to send the unsubscribe message. See DeltaSubscriptionStateTest.RemoveThenAdd. + // + // These two fields can be set in any DeltaDiscoveryRequest, including ACKs + // and initial_resource_versions. + // + // A list of Resource names to add to the list of tracked resources. + repeated string resource_names_subscribe = 3; + + // A list of Resource names to remove from the list of tracked resources. + repeated string resource_names_unsubscribe = 4; + + // Informs the server of the versions of the resources the xDS client knows of, to enable the + // client to continue the same logical xDS session even in the face of gRPC stream reconnection. + // It will not be populated: [1] in the very first stream of a session, since the client will + // not yet have any resources, [2] in any message after the first in a stream (for a given + // type_url), since the server will already be correctly tracking the client's state. + // (In ADS, the first message *of each type_url* of a reconnected stream populates this map.) + // The map's keys are names of xDS resources known to the xDS client. + // The map's values are opaque resource versions. + map initial_resource_versions = 5; + + // When the DeltaDiscoveryRequest is a ACK or NACK message in response + // to a previous DeltaDiscoveryResponse, the response_nonce must be the + // nonce in the DeltaDiscoveryResponse. + // Otherwise (unlike in DiscoveryRequest) response_nonce must be omitted. + string response_nonce = 6; + + // This is populated when the previous :ref:`DiscoveryResponse ` + // failed to update configuration. The *message* field in *error_details* + // provides the Envoy internal exception related to the failure. + google.rpc.Status error_detail = 7; +} + +// [#next-free-field: 7] +message DeltaDiscoveryResponse { + option (udpa.annotations.versioning).previous_message_type = + "envoy.api.v2.DeltaDiscoveryResponse"; + + // The version of the response data (used for debugging). + string system_version_info = 1; + + // The response resources. These are typed resources, whose types must match + // the type_url field. + repeated Resource resources = 2; + + // field id 3 IS available! + + // Type URL for resources. Identifies the xDS API when muxing over ADS. + // Must be consistent with the type_url in the Any within 'resources' if 'resources' is non-empty. + string type_url = 4; + + // Resources names of resources that have be deleted and to be removed from the xDS Client. + // Removed resources for missing resources can be ignored. + repeated string removed_resources = 6; + + // The nonce provides a way for DeltaDiscoveryRequests to uniquely + // reference a DeltaDiscoveryResponse when (N)ACKing. The nonce is required. + string nonce = 5; +} + +message Resource { + option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.Resource"; + + // The resource's name, to distinguish it from others of the same type of resource. + string name = 3; + + // The aliases are a list of other names that this resource can go by. + repeated string aliases = 4; + + // The resource level version. It allows xDS to track the state of individual + // resources. + string version = 1; + + // The resource being tracked. + google.protobuf.Any resource = 2; +} From 24731102c616474e5dffe8490f10ff6dc865f79c Mon Sep 17 00:00:00 2001 From: susinmotion Date: Tue, 21 Jul 2020 15:45:47 -0400 Subject: [PATCH 11/88] grpclb: Make ATTR_LB_ADDRS public (#7230) --- grpclb/src/main/java/io/grpc/grpclb/GrpclbConstants.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grpclb/src/main/java/io/grpc/grpclb/GrpclbConstants.java b/grpclb/src/main/java/io/grpc/grpclb/GrpclbConstants.java index efc945a2fc2..bda6473b0cc 100644 --- a/grpclb/src/main/java/io/grpc/grpclb/GrpclbConstants.java +++ b/grpclb/src/main/java/io/grpc/grpclb/GrpclbConstants.java @@ -45,7 +45,7 @@ public final class GrpclbConstants { /** * Attribute key for gRPC LB server addresses. */ - static final Attributes.Key> ATTR_LB_ADDRS = + public static final Attributes.Key> ATTR_LB_ADDRS = Attributes.Key.create("io.grpc.grpclb.lbAddrs"); /** From 1e238ee2a0484cfc28ea2bdab38b25f72ac59fe7 Mon Sep 17 00:00:00 2001 From: Chengyuan Zhang Date: Wed, 22 Jul 2020 01:02:29 +0000 Subject: [PATCH 12/88] xds: add header matching special cases for hiding/exposing some gRPC headers (#7224) Expose "content-type" header (hard-coded) at header matching, pretend it's already there. --- xds/src/main/java/io/grpc/xds/RouteMatch.java | 11 ++++++- .../test/java/io/grpc/xds/RouteMatchTest.java | 30 +++++++++++++++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/RouteMatch.java b/xds/src/main/java/io/grpc/xds/RouteMatch.java index 5b52e71c298..4fdbe7c29a7 100644 --- a/xds/src/main/java/io/grpc/xds/RouteMatch.java +++ b/xds/src/main/java/io/grpc/xds/RouteMatch.java @@ -65,7 +65,16 @@ boolean matches(String path, Map> headers) { return false; } for (HeaderMatcher headerMatcher : headerMatchers) { - if (!headerMatcher.matchesValue(headers.get(headerMatcher.getName()))) { + Iterable headerValues = headers.get(headerMatcher.getName()); + // Special cases for hiding headers: "grpc-previous-rpc-attempts". + if (headerMatcher.getName().equals("grpc-previous-rpc-attempts")) { + headerValues = null; + } + // Special case for exposing headers: "content-type". + if (headerMatcher.getName().equals("content-type")) { + headerValues = Collections.singletonList("application/grpc"); + } + if (!headerMatcher.matchesValue(headerValues)) { return false; } } diff --git a/xds/src/test/java/io/grpc/xds/RouteMatchTest.java b/xds/src/test/java/io/grpc/xds/RouteMatchTest.java index 712758e1168..463473d7fc6 100644 --- a/xds/src/test/java/io/grpc/xds/RouteMatchTest.java +++ b/xds/src/test/java/io/grpc/xds/RouteMatchTest.java @@ -40,7 +40,7 @@ public class RouteMatchTest { @Before public void setUp() { - headers.put("content-type", Collections.singletonList("application/grpc")); + headers.put("authority", Collections.singletonList("foo.googleapis.com")); headers.put("grpc-encoding", Collections.singletonList("gzip")); headers.put("user-agent", Collections.singletonList("gRPC-Java")); headers.put("content-length", Collections.singletonList("1000")); @@ -79,7 +79,7 @@ public void routeMatching_withHeaders() { new HeaderMatcher( "grpc-encoding", "gzip", null, null, null, null, null, false), new HeaderMatcher( - "content-type", null, Pattern.compile(".*grpc.*"), null, null, null, + "authority", null, Pattern.compile(".*googleapis.*"), null, null, null, null, false), new HeaderMatcher( "content-length", null, null, new Range(100, 10000), null, null, null, false), @@ -93,7 +93,7 @@ public void routeMatching_withHeaders() { new PathMatcher("/FooService/barMethod", null, null), Collections.singletonList( new HeaderMatcher( - "content-type", null, Pattern.compile(".*grpc.*"), null, null, null, + "authority", null, Pattern.compile(".*googleapis.*"), null, null, null, null, true)), null); assertThat(routeMatch2.matches("/FooService/barMethod", headers)).isFalse(); @@ -161,6 +161,30 @@ public void routeMatching_withRuntimeFraction() { assertThat(routeMatch2.matches("/FooService/barMethod", headers)).isFalse(); } + @Test + public void headerMatching_specialCaseGrpcHeaders() { + Map> headers = new HashMap<>(); + headers.put("grpc-previous-rpc-attempts", Collections.singletonList("0")); + + RouteMatch routeMatch1 = + new RouteMatch(new PathMatcher("/FooService/barMethod", null, null), + Arrays.asList( + new HeaderMatcher( + "grpc-previous-rpc-attempts", "0", null, null, null, null, + null, false)), + null); + assertThat(routeMatch1.matches("/FooService/barMethod", headers)).isFalse(); + + RouteMatch routeMatch2 = + new RouteMatch(new PathMatcher("/FooService/barMethod", null, null), + Arrays.asList( + new HeaderMatcher( + "content-type", "application/grpc", null, null, null, null, + null, false)), + null); + assertThat(routeMatch2.matches("/FooService/barMethod", headers)).isTrue(); + } + private static final class FakeRandom implements ThreadSafeRandom { private final int value; From 9f49e48237193c2ec3c17735f5c40dcb6b94a978 Mon Sep 17 00:00:00 2001 From: Chengyuan Zhang Date: Wed, 22 Jul 2020 01:03:36 +0000 Subject: [PATCH 13/88] xds: support load reporting all clusters option and fix actual report interval measurement (#7209) - Add support for send_all_clusters field in LRS response. When it is set to true, just send load reports for clusters that the client is currently tracking (aka, is sending load to). - The actual load report interval (in each ClusterStats message, which contains the stats for each cluster:eds_service) should be tracked individually. --- .../java/io/grpc/xds/LoadReportClient.java | 88 ++++++++++--------- .../io/grpc/xds/LoadReportClientTest.java | 67 ++++++++------ 2 files changed, 88 insertions(+), 67 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/LoadReportClient.java b/xds/src/main/java/io/grpc/xds/LoadReportClient.java index 525b645d610..1314583cfa7 100644 --- a/xds/src/main/java/io/grpc/xds/LoadReportClient.java +++ b/xds/src/main/java/io/grpc/xds/LoadReportClient.java @@ -67,7 +67,7 @@ final class LoadReportClient { private final BackoffPolicy.Provider backoffPolicyProvider; // Sources of load stats data for each cluster:cluster_service. - private final Map> loadStatsStoreMap = new HashMap<>(); + private final Map> loadStatsEntities = new HashMap<>(); private boolean started; @Nullable @@ -148,18 +148,18 @@ void stopLoadReporting() { void addLoadStatsStore( String clusterName, @Nullable String clusterServiceName, LoadStatsStore loadStatsStore) { checkState( - !loadStatsStoreMap.containsKey(clusterName) - || !loadStatsStoreMap.get(clusterName).containsKey(clusterServiceName), + !loadStatsEntities.containsKey(clusterName) + || !loadStatsEntities.get(clusterName).containsKey(clusterServiceName), "load stats for cluster: %s, cluster service: %s already exists", clusterName, clusterServiceName); logger.log( XdsLogLevel.INFO, "Add load stats for cluster: {0}, cluster_service: {1}", clusterName, clusterServiceName); - if (!loadStatsStoreMap.containsKey(clusterName)) { - loadStatsStoreMap.put(clusterName, new HashMap()); + if (!loadStatsEntities.containsKey(clusterName)) { + loadStatsEntities.put(clusterName, new HashMap()); } - Map clusterLoadStatsStores = loadStatsStoreMap.get(clusterName); - clusterLoadStatsStores.put(clusterServiceName, loadStatsStore); + Map clusterLoadStatsEntities = loadStatsEntities.get(clusterName); + clusterLoadStatsEntities.put(clusterServiceName, new LoadStatsEntity(loadStatsStore)); } /** @@ -167,8 +167,8 @@ void addLoadStatsStore( */ void removeLoadStatsStore(String clusterName, @Nullable String clusterServiceName) { checkState( - loadStatsStoreMap.containsKey(clusterName) - && loadStatsStoreMap.get(clusterName).containsKey(clusterServiceName), + loadStatsEntities.containsKey(clusterName) + && loadStatsEntities.get(clusterName).containsKey(clusterServiceName), "load stats for cluster: %s, cluster service: %s does not exist", clusterName, clusterServiceName); logger.log( @@ -176,10 +176,10 @@ void removeLoadStatsStore(String clusterName, @Nullable String clusterServiceNam "Remove load stats for cluster: {0}, cluster_service: {1}", clusterName, clusterServiceName); - Map clusterLoadStatsStores = loadStatsStoreMap.get(clusterName); - clusterLoadStatsStores.remove(clusterServiceName); - if (clusterLoadStatsStores.isEmpty()) { - loadStatsStoreMap.remove(clusterName); + Map clusterLoadStatsEntities = loadStatsEntities.get(clusterName); + clusterLoadStatsEntities.remove(clusterServiceName); + if (clusterLoadStatsEntities.isEmpty()) { + loadStatsEntities.remove(clusterName); } } @@ -217,10 +217,8 @@ private void startLrsRpc() { private class LrsStream implements StreamObserver { - // Cluster to report loads for asked by management server. final Set clusterNames = new HashSet<>(); final LoadReportingServiceGrpc.LoadReportingServiceStub stub; - final Stopwatch reportStopwatch; StreamObserver lrsRequestWriter; boolean initialResponseReceived; boolean closed; @@ -229,15 +227,10 @@ private class LrsStream implements StreamObserver { LrsStream(LoadReportingServiceGrpc.LoadReportingServiceStub stub, Stopwatch stopwatch) { this.stub = checkNotNull(stub, "stub"); - reportStopwatch = checkNotNull(stopwatch, "stopwatch"); } void start() { lrsRequestWriter = stub.withWaitForReady().streamLoadStats(this); - reportStopwatch.reset().start(); - - // Send an initial LRS request with empty cluster stats. Management server is able to - // infer clusters the gRPC client sending loads to. LoadStatsRequest initRequest = LoadStatsRequest.newBuilder() .setNode(node) @@ -278,19 +271,12 @@ public void run() { } private void sendLoadReport() { - long interval = reportStopwatch.elapsed(TimeUnit.NANOSECONDS); - reportStopwatch.reset().start(); LoadStatsRequest.Builder requestBuilder = LoadStatsRequest.newBuilder().setNode(node); for (String name : clusterNames) { - if (loadStatsStoreMap.containsKey(name)) { - Map clusterLoadStatsStores = loadStatsStoreMap.get(name); - for (LoadStatsStore statsStore : clusterLoadStatsStores.values()) { - ClusterStats report = - statsStore.generateLoadReport() - .toBuilder() - .setLoadReportInterval(Durations.fromNanos(interval)) - .build(); - requestBuilder.addClusterStats(report); + if (loadStatsEntities.containsKey(name)) { + Map clusterLoadStatsEntities = loadStatsEntities.get(name); + for (LoadStatsEntity entity : clusterLoadStatsEntities.values()) { + requestBuilder.addClusterStats(entity.getLoadStats()); } } } @@ -317,28 +303,27 @@ private void handleResponse(LoadStatsResponse response) { if (closed) { return; } - if (!initialResponseReceived) { logger.log(XdsLogLevel.DEBUG, "Received LRS initial response:\n{0}", response); initialResponseReceived = true; } else { logger.log(XdsLogLevel.DEBUG, "Received LRS response:\n{0}", response); } - long interval = Durations.toNanos(response.getLoadReportingInterval()); - if (interval != loadReportIntervalNano) { - logger.log(XdsLogLevel.INFO, "Update load reporting interval to {0} ns", interval); - loadReportIntervalNano = interval; - callback.onReportResponse(loadReportIntervalNano); - } - if (clusterNames.size() != response.getClustersCount() - || !clusterNames.containsAll(response.getClustersList())) { + clusterNames.clear(); + if (response.getSendAllClusters()) { + clusterNames.addAll(loadStatsEntities.keySet()); + logger.log(XdsLogLevel.INFO, "Update to report loads for all clusters"); + } else { logger.log( XdsLogLevel.INFO, "Update load reporting clusters to {0}", response.getClustersList()); - clusterNames.clear(); clusterNames.addAll(response.getClustersList()); } + long interval = Durations.toNanos(response.getLoadReportingInterval()); + logger.log(XdsLogLevel.INFO, "Update load reporting interval to {0} ns", interval); + loadReportIntervalNano = interval; scheduleNextLoadReport(); + callback.onReportResponse(loadReportIntervalNano); } private void handleStreamClosed(Status status) { @@ -401,6 +386,27 @@ private void cleanUp() { } } + private final class LoadStatsEntity { + private final LoadStatsStore loadStatsStore; + private final Stopwatch stopwatch; + + private LoadStatsEntity(LoadStatsStore loadStatsStore) { + this.loadStatsStore = loadStatsStore; + this.stopwatch = stopwatchSupplier.get().reset().start(); + } + + private ClusterStats getLoadStats() { + ClusterStats stats = + loadStatsStore.generateLoadReport() + .toBuilder() + .setLoadReportInterval( + Durations.fromNanos(stopwatch.elapsed(TimeUnit.NANOSECONDS))) + .build(); + stopwatch.reset().start(); + return stats; + } + } + /** * Callbacks for passing information received from client load reporting responses to xDS load * balancer, such as the load reporting interval requested by the traffic director. diff --git a/xds/src/test/java/io/grpc/xds/LoadReportClientTest.java b/xds/src/test/java/io/grpc/xds/LoadReportClientTest.java index 8c2e6f5a9fc..4b8645a7e93 100644 --- a/xds/src/test/java/io/grpc/xds/LoadReportClientTest.java +++ b/xds/src/test/java/io/grpc/xds/LoadReportClientTest.java @@ -56,7 +56,9 @@ import io.grpc.testing.GrpcCleanupRule; import io.grpc.xds.LoadReportClient.LoadReportCallback; import java.util.ArrayDeque; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -178,7 +180,7 @@ public void cancelled(Context context) { when(backoffPolicy1.nextBackoffNanos()) .thenReturn(TimeUnit.SECONDS.toNanos(1L), TimeUnit.SECONDS.toNanos(10L)); when(backoffPolicy2.nextBackoffNanos()) - .thenReturn(TimeUnit.SECONDS.toNanos(1L), TimeUnit.SECONDS.toNanos(10L)); + .thenReturn(TimeUnit.SECONDS.toNanos(2L), TimeUnit.SECONDS.toNanos(20L)); lrsClient = new LoadReportClient( logId, @@ -216,15 +218,17 @@ public void typicalWorkflow() { responseObserver.onNext(buildLrsResponse(ImmutableList.of(cluster1), 1000)); inOrder.verify(callback).onReportResponse(1000); - ArgumentMatcher expectedLoadReportMatcher = - new LoadStatsRequestMatcher(ImmutableList.of(rawStats1), 1000); + ClusterStats expectedStats1 = + rawStats1.toBuilder().setLoadReportInterval(Durations.fromNanos(1000)).build(); fakeClock.forwardNanos(999); inOrder.verifyNoMoreInteractions(); fakeClock.forwardNanos(1); - inOrder.verify(requestObserver).onNext(argThat(expectedLoadReportMatcher)); + inOrder.verify(requestObserver) + .onNext(argThat(new LoadStatsRequestMatcher(Collections.singletonList(expectedStats1)))); fakeClock.forwardNanos(1000); - inOrder.verify(requestObserver).onNext(argThat(expectedLoadReportMatcher)); + inOrder.verify(requestObserver) + .onNext(argThat(new LoadStatsRequestMatcher(Collections.singletonList(expectedStats1)))); String cluster2 = "cluster-bar.googleapis.com"; ClusterStats rawStats2 = generateClusterLoadStats(cluster2, null); @@ -236,28 +240,38 @@ public void typicalWorkflow() { responseObserver.onNext(buildLrsResponse(ImmutableList.of(cluster1), 2000)); inOrder.verify(callback).onReportResponse(2000); + expectedStats1 = + rawStats1.toBuilder().setLoadReportInterval(Durations.fromNanos(2000)).build(); fakeClock.forwardNanos(1000); inOrder.verifyNoMoreInteractions(); - fakeClock.forwardNanos(1000); inOrder.verify(requestObserver) - .onNext(argThat(new LoadStatsRequestMatcher(ImmutableList.of(rawStats1), 2000))); - - // Management server asks to report loads for cluster1 and cluster2. - responseObserver.onNext(buildLrsResponse(ImmutableList.of(cluster1, cluster2), 2000)); + .onNext(argThat(new LoadStatsRequestMatcher(Collections.singletonList(expectedStats1)))); + + // Management server asks to report loads for all clusters. + responseObserver.onNext( + LoadStatsResponse.newBuilder() + .setSendAllClusters(true) + .setLoadReportingInterval(Durations.fromNanos(2000)) + .build()); + inOrder.verify(callback).onReportResponse(2000); + ClusterStats expectedStats2 = + rawStats2.toBuilder().setLoadReportInterval(Durations.fromNanos(2000 + 2000)).build(); fakeClock.forwardNanos(2000); inOrder.verify(requestObserver) .onNext( argThat( - new LoadStatsRequestMatcher(ImmutableList.of(rawStats1, rawStats2), 2000))); + new LoadStatsRequestMatcher(Arrays.asList(expectedStats1, expectedStats2)))); // Load reports for cluster1 is no longer wanted. - responseObserver.onNext(buildLrsResponse(ImmutableList.of(cluster2), 2000)); + responseObserver.onNext(buildLrsResponse(Collections.singletonList(cluster2), 2000)); + expectedStats2 = + rawStats2.toBuilder().setLoadReportInterval(Durations.fromNanos(2000)).build(); fakeClock.forwardNanos(2000); inOrder.verify(requestObserver) - .onNext(argThat(new LoadStatsRequestMatcher(ImmutableList.of(rawStats2), 2000))); + .onNext(argThat(new LoadStatsRequestMatcher(Collections.singletonList(expectedStats2)))); // Management server asks loads for a cluster that client has no load data. responseObserver @@ -331,7 +345,7 @@ public void lrsStreamClosedAndRetried() { // Balancer sends a response asking for loads of the cluster. responseObserver - .onNext(buildLrsResponse(ImmutableList.of(clusterName), 0)); + .onNext(buildLrsResponse(ImmutableList.of(clusterName), 5)); // Then breaks the RPC responseObserver.onError(Status.UNAVAILABLE.asException()); @@ -348,12 +362,12 @@ public void lrsStreamClosedAndRetried() { fakeClock.forwardNanos(4); responseObserver.onError(Status.UNAVAILABLE.asException()); - // Will be on the first retry (1s) of backoff sequence 2. + // Will be on the first retry (2s) of backoff sequence 2. inOrder.verify(backoffPolicy2).nextBackoffNanos(); assertEquals(1, fakeClock.numPendingTasks(LRS_RPC_RETRY_TASK_FILTER)); // Fast-forward to a moment before the retry, the time spent in the last try is deducted. - fakeClock.forwardNanos(TimeUnit.SECONDS.toNanos(1) - 4 - 1); + fakeClock.forwardNanos(TimeUnit.SECONDS.toNanos(2) - 4 - 1); verifyNoMoreInteractions(mockLoadReportingService); // Then time for retry fakeClock.forwardNanos(1); @@ -368,8 +382,13 @@ public void lrsStreamClosedAndRetried() { responseObserver .onNext(buildLrsResponse(ImmutableList.of(clusterName), 10)); fakeClock.forwardNanos(10); + ClusterStats expectedStats = + stats.toBuilder() + .setLoadReportInterval( + Durations.add(Durations.fromSeconds(1 + 10 + 2), Durations.fromNanos(10))) + .build(); verify(requestObserver) - .onNext(argThat(new LoadStatsRequestMatcher(ImmutableList.of(stats), 10))); + .onNext(argThat(new LoadStatsRequestMatcher(Collections.singletonList(expectedStats)))); // Wrapping up verify(backoffPolicyProvider, times(2)).get(); @@ -466,9 +485,9 @@ private static ClusterStats generateClusterLoadStats( UpstreamLocalityStats.newBuilder() .setLocality( Locality.newBuilder() - .setRegion("region-foo") - .setZone("zone-bar") - .setSubZone("subzone-baz")) + .setRegion(clusterName + "-region-foo") + .setZone(clusterName + "-zone-bar") + .setSubZone(clusterName + "-subzone-baz")) .setTotalRequestsInProgress(callsInProgress) .setTotalSuccessfulRequests(callsSucceeded) .setTotalErrorRequests(callsFailed) @@ -491,13 +510,9 @@ private static ClusterStats generateClusterLoadStats( private static class LoadStatsRequestMatcher implements ArgumentMatcher { private final Map expectedStats = new HashMap<>(); - LoadStatsRequestMatcher(Collection clusterStats, long expectedIntervalNano) { + LoadStatsRequestMatcher(Collection clusterStats) { for (ClusterStats stats : clusterStats) { - ClusterStats statsWithInterval = - stats.toBuilder() - .setLoadReportInterval(Durations.fromNanos(expectedIntervalNano)) - .build(); - expectedStats.put(statsWithInterval.getClusterName(), statsWithInterval); + expectedStats.put(stats.getClusterName(), stats); } } From e4215b422d142ea0288c6236fb703141b380f60d Mon Sep 17 00:00:00 2001 From: Chengyuan Zhang Date: Wed, 22 Jul 2020 08:16:51 +0000 Subject: [PATCH 14/88] xds: routing policy should immediately update a picker that selects base on updated config (#7233) The routing LB policy should immediately the Channel's picker that delegates picking to the updated routes. Otherwise, new RPCs will keep being sent through old routes even if they are removed. This change also includes the fix for syncing state change for child balancers in deactivated state. --- .../io/grpc/xds/XdsRoutingLoadBalancer.java | 18 +- .../grpc/xds/XdsRoutingLoadBalancerTest.java | 279 +++++++++++++----- 2 files changed, 216 insertions(+), 81 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/XdsRoutingLoadBalancer.java b/xds/src/main/java/io/grpc/xds/XdsRoutingLoadBalancer.java index 1cd5045052a..9a18b238862 100644 --- a/xds/src/main/java/io/grpc/xds/XdsRoutingLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/XdsRoutingLoadBalancer.java @@ -86,14 +86,14 @@ public void handleResolvedAddresses(final ResolvedAddresses resolvedAddresses) { } else { childLbStates.get(actionName).reactivate(action.getProvider()); } + final LoadBalancer childLb = childLbStates.get(actionName).lb; syncContext.execute(new Runnable() { @Override public void run() { - childLbStates.get(actionName).lb - .handleResolvedAddresses( - resolvedAddresses.toBuilder() - .setLoadBalancingPolicyConfig(action.getConfig()) - .build()); + childLb.handleResolvedAddresses( + resolvedAddresses.toBuilder() + .setLoadBalancingPolicyConfig(action.getConfig()) + .build()); } }); } @@ -102,6 +102,7 @@ public void run() { for (String actionName : diff) { childLbStates.get(actionName).deactivate(); } + updateOverallBalancingState(); } @Override @@ -235,12 +236,11 @@ private final class RouteHelper extends ForwardingLoadBalancerHelper { @Override public void updateBalancingState(ConnectivityState newState, SubchannelPicker newPicker) { - if (deactivated) { - return; - } currentState = newState; currentPicker = newPicker; - updateOverallBalancingState(); + if (!deactivated) { + updateOverallBalancingState(); + } } @Override diff --git a/xds/src/test/java/io/grpc/xds/XdsRoutingLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/XdsRoutingLoadBalancerTest.java index 54646c37ea3..f9bc47fc55e 100644 --- a/xds/src/test/java/io/grpc/xds/XdsRoutingLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsRoutingLoadBalancerTest.java @@ -17,12 +17,18 @@ package io.grpc.xds; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; import io.grpc.CallOptions; import io.grpc.ConnectivityState; import io.grpc.EquivalentAddressGroup; @@ -38,6 +44,7 @@ import io.grpc.MethodDescriptor; import io.grpc.MethodDescriptor.MethodType; import io.grpc.Status; +import io.grpc.Status.Code; import io.grpc.SynchronizationContext; import io.grpc.internal.FakeClock; import io.grpc.internal.PickSubchannelArgsImpl; @@ -51,14 +58,18 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -77,6 +88,8 @@ public void uncaughtException(Thread t, Throwable e) { @Mock private LoadBalancer.Helper helper; + @Captor + ArgumentCaptor pickerCaptor; private RouteMatch routeMatch1 = new RouteMatch( @@ -95,7 +108,8 @@ public void uncaughtException(Thread t, Throwable e) { new PathMatcher(null, "/", null), Collections.emptyList(), null); - private List childBalancers = new ArrayList<>(); + private final Map lbConfigInventory = new HashMap<>(); + private final List childBalancers = new ArrayList<>(); private LoadBalancer xdsRoutingLoadBalancer; @Before @@ -103,107 +117,228 @@ public void setUp() { MockitoAnnotations.initMocks(this); when(helper.getSynchronizationContext()).thenReturn(syncContext); when(helper.getScheduledExecutorService()).thenReturn(fakeClock.getScheduledExecutorService()); + lbConfigInventory.put("actionA", new Object()); + lbConfigInventory.put("actionB", new Object()); + lbConfigInventory.put("actionC", null); xdsRoutingLoadBalancer = new XdsRoutingLoadBalancer(helper); } - @Test - public void typicalWorkflow() { - Object childConfig1 = new Object(); - Object childConfig2 = new Object(); - PolicySelection policyA = - new PolicySelection(new FakeLoadBalancerProvider("policy_a"), null, childConfig1); - PolicySelection policyB = - new PolicySelection(new FakeLoadBalancerProvider("policy_b"), null, childConfig2); - PolicySelection policyC = - new PolicySelection(new FakeLoadBalancerProvider("policy_c"), null , null); - - XdsRoutingConfig config = - new XdsRoutingConfig( - Arrays.asList( - new Route(routeMatch1, "action_a"), - new Route(routeMatch2, "action_b"), - new Route(routeMatch3, "action_a")), - ImmutableMap.of("action_a", policyA, "action_b", policyB)); - xdsRoutingLoadBalancer - .handleResolvedAddresses( - ResolvedAddresses.newBuilder() - .setAddresses(Collections.emptyList()) - .setLoadBalancingPolicyConfig(config) - .build()); + @After + public void tearDown() { + xdsRoutingLoadBalancer.shutdown(); + for (FakeLoadBalancer childLb : childBalancers) { + assertThat(childLb.shutdown).isTrue(); + } + } + @Test + public void handleResolvedAddressesUpdatesChannelPicker() { + deliverResolvedAddresses( + ImmutableMap.of( + new Route(routeMatch1, "actionA"), "policy_a", + new Route(routeMatch2, "actionB"), "policy_b")); + + verify(helper, atLeastOnce()).updateBalancingState( + eq(ConnectivityState.CONNECTING), pickerCaptor.capture()); + RouteMatchingSubchannelPicker picker = (RouteMatchingSubchannelPicker) pickerCaptor.getValue(); + assertThat(picker.routePickers).hasSize(2); + assertThat(picker.routePickers.get(routeMatch1).pickSubchannel(mock(PickSubchannelArgs.class))) + .isEqualTo(PickResult.withNoResult()); + assertThat(picker.routePickers.get(routeMatch2).pickSubchannel(mock(PickSubchannelArgs.class))) + .isEqualTo(PickResult.withNoResult()); assertThat(childBalancers).hasSize(2); FakeLoadBalancer childBalancer1 = childBalancers.get(0); FakeLoadBalancer childBalancer2 = childBalancers.get(1); assertThat(childBalancer1.name).isEqualTo("policy_a"); assertThat(childBalancer2.name).isEqualTo("policy_b"); - assertThat(childBalancer1.config).isEqualTo(childConfig1); - assertThat(childBalancer2.config).isEqualTo(childConfig2); - - // Receive an updated routing config. - config = - new XdsRoutingConfig( - Arrays.asList( - new Route(routeMatch1, "action_b"), - new Route(routeMatch2, "action_c"), - new Route(routeMatch3, "action_c")), - ImmutableMap.of("action_b", policyA, "action_c", policyC)); - xdsRoutingLoadBalancer - .handleResolvedAddresses( - ResolvedAddresses.newBuilder() - .setAddresses(Collections.emptyList()) - .setLoadBalancingPolicyConfig(config) - .build()); - - assertThat(childBalancer2.shutdown) - .isTrue(); // (immediate) shutdown because "action_b" changes policy (before ready) + assertThat(childBalancer1.config).isEqualTo(lbConfigInventory.get("actionA")); + assertThat(childBalancer2.config).isEqualTo(lbConfigInventory.get("actionB")); + + // Receive an updated config. + deliverResolvedAddresses( + ImmutableMap.of( + new Route(routeMatch1, "actionA"), "policy_a", + new Route(routeMatch3, "actionC"), "policy_c")); + + verify(helper, atLeast(2)) + .updateBalancingState(eq(ConnectivityState.CONNECTING), pickerCaptor.capture()); + picker = (RouteMatchingSubchannelPicker) pickerCaptor.getValue(); + assertThat(picker.routePickers).hasSize(2); + assertThat(picker.routePickers).doesNotContainKey(routeMatch2); + assertThat(picker.routePickers.get(routeMatch3).pickSubchannel(mock(PickSubchannelArgs.class))) + .isEqualTo(PickResult.withNoResult()); assertThat(fakeClock.numPendingTasks()) - .isEqualTo(1); // (delayed) shutdown because "action_a" is removed + .isEqualTo(1); // (delayed) shutdown because "actionB" is removed assertThat(childBalancer1.shutdown).isFalse(); + assertThat(childBalancer2.shutdown).isFalse(); + assertThat(childBalancers).hasSize(3); - FakeLoadBalancer childBalancer3 = childBalancers.get(1); - FakeLoadBalancer childBalancer4 = childBalancers.get(2); - assertThat(childBalancer3.name).isEqualTo("policy_a"); - assertThat(childBalancer3).isNotSameInstanceAs(childBalancer1); - assertThat(childBalancer4.name).isEqualTo("policy_c"); + FakeLoadBalancer childBalancer3 = childBalancers.get(2); + assertThat(childBalancer3.name).isEqualTo("policy_c"); + assertThat(childBalancer3.config).isEqualTo(lbConfigInventory.get("actionC")); - // Simulate subchannel state update from the leaf policy. + fakeClock.forwardTime( + XdsRoutingLoadBalancer.DELAYED_ACTION_DELETION_TIME_MINUTES, TimeUnit.MINUTES); + assertThat(childBalancer2.shutdown).isTrue(); + } + + @Test + public void updateWithActionPolicyChange() { + deliverResolvedAddresses(ImmutableMap.of(new Route(routeMatch1, "actionA"), "policy_a")); + FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers); + assertThat(childBalancer.name).isEqualTo("policy_a"); + assertThat(childBalancer.config).isEqualTo(lbConfigInventory.get("actionA")); + + deliverResolvedAddresses(ImmutableMap.of(new Route(routeMatch1, "actionA"), "policy_b")); + assertThat(childBalancer.shutdown).isTrue(); // immediate shutdown as the it was not ready + assertThat(Iterables.getOnlyElement(childBalancers).name).isEqualTo("policy_b"); + assertThat(Iterables.getOnlyElement(childBalancers).config) + .isEqualTo(lbConfigInventory.get("actionA")); + } + + @Test + public void updateBalancingStateFromChildBalancers() { + deliverResolvedAddresses( + ImmutableMap.of( + new Route(routeMatch1, "actionA"), "policy_a", + new Route(routeMatch2, "actionB"), "policy_b")); + + assertThat(childBalancers).hasSize(2); + FakeLoadBalancer childBalancer1 = childBalancers.get(0); + FakeLoadBalancer childBalancer2 = childBalancers.get(1); Subchannel subchannel1 = mock(Subchannel.class); Subchannel subchannel2 = mock(Subchannel.class); - Subchannel subchannel3 = mock(Subchannel.class); childBalancer1.deliverSubchannelState(subchannel1, ConnectivityState.READY); - childBalancer3.deliverSubchannelState(subchannel2, ConnectivityState.CONNECTING); - childBalancer4.deliverSubchannelState(subchannel3, ConnectivityState.READY); - ArgumentCaptor pickerCaptor = ArgumentCaptor.forClass(null); verify(helper).updateBalancingState(eq(ConnectivityState.READY), pickerCaptor.capture()); RouteMatchingSubchannelPicker picker = (RouteMatchingSubchannelPicker) pickerCaptor.getValue(); - assertThat(picker.routePickers).hasSize(3); + assertThat(picker.routePickers).hasSize(2); assertThat( picker.routePickers.get(routeMatch1) .pickSubchannel(mock(PickSubchannelArgs.class)).getSubchannel()) - .isSameInstanceAs(subchannel2); // routeMatch1 -> action_b -> policy_a -> subchannel2 + .isEqualTo(subchannel1); + assertThat(picker.routePickers.get(routeMatch2).pickSubchannel(mock(PickSubchannelArgs.class))) + .isEqualTo(PickResult.withNoResult()); + + childBalancer2.deliverSubchannelState(subchannel2, ConnectivityState.READY); + verify(helper, times(2)) + .updateBalancingState(eq(ConnectivityState.READY), pickerCaptor.capture()); + picker = (RouteMatchingSubchannelPicker) pickerCaptor.getValue(); assertThat( picker.routePickers.get(routeMatch2) .pickSubchannel(mock(PickSubchannelArgs.class)).getSubchannel()) - .isSameInstanceAs(subchannel3); // routeMatch2 -> action_c -> policy_c -> subchannel3 + .isEqualTo(subchannel2); + } + + @Test + public void updateBalancingStateFromDeactivatedChildBalancer() { + FakeLoadBalancer balancer = + deliverAddressesAndUpdateToRemoveChildPolicy( + new Route(routeMatch1, "actionA"), "policy_a"); + Subchannel subchannel = mock(Subchannel.class); + balancer.deliverSubchannelState(subchannel, ConnectivityState.READY); + verify(helper, never()).updateBalancingState( + eq(ConnectivityState.READY), any(SubchannelPicker.class)); + + deliverResolvedAddresses(ImmutableMap.of(new Route(routeMatch1, "actionA"), "policy_a")); + verify(helper).updateBalancingState(eq(ConnectivityState.READY), pickerCaptor.capture()); + RouteMatchingSubchannelPicker picker = (RouteMatchingSubchannelPicker) pickerCaptor.getValue(); assertThat( - picker.routePickers.get(routeMatch3) + picker.routePickers.get(routeMatch1) .pickSubchannel(mock(PickSubchannelArgs.class)).getSubchannel()) - .isSameInstanceAs(subchannel3); // routeMatch3 -> action_c -> policy_c -> subchannel3 + .isEqualTo(subchannel); + } - // Error propagation from upstream policies. - Status error = Status.UNAVAILABLE.withDescription("network error"); + @Test + public void errorPropagation() { + Status error = Status.UNAVAILABLE.withDescription("resolver error"); xdsRoutingLoadBalancer.handleNameResolutionError(error); - assertThat(childBalancer1.upstreamError).isNull(); - assertThat(childBalancer3.upstreamError).isEqualTo(error); - assertThat(childBalancer4.upstreamError).isEqualTo(error); - fakeClock.forwardTime( - XdsRoutingLoadBalancer.DELAYED_ACTION_DELETION_TIME_MINUTES, TimeUnit.MINUTES); - assertThat(childBalancer1.shutdown).isTrue(); + verify(helper).updateBalancingState( + eq(ConnectivityState.TRANSIENT_FAILURE), pickerCaptor.capture()); + PickResult result = pickerCaptor.getValue().pickSubchannel(mock(PickSubchannelArgs.class)); + assertThat(result.getStatus().getCode()).isEqualTo(Code.UNAVAILABLE); + assertThat(result.getStatus().getDescription()).isEqualTo("resolver error"); - xdsRoutingLoadBalancer.shutdown(); - assertThat(childBalancer3.shutdown).isTrue(); - assertThat(childBalancer4.shutdown).isTrue(); + deliverResolvedAddresses( + ImmutableMap.of( + new Route(routeMatch1, "actionA"), "policy_a", + new Route(routeMatch2, "actionB"), "policy_b")); + + assertThat(childBalancers).hasSize(2); + FakeLoadBalancer childBalancer1 = childBalancers.get(0); + FakeLoadBalancer childBalancer2 = childBalancers.get(1); + + xdsRoutingLoadBalancer.handleNameResolutionError(error); + assertThat(childBalancer1.upstreamError.getCode()).isEqualTo(Code.UNAVAILABLE); + assertThat(childBalancer1.upstreamError.getDescription()).isEqualTo("resolver error"); + assertThat(childBalancer2.upstreamError.getCode()).isEqualTo(Code.UNAVAILABLE); + assertThat(childBalancer2.upstreamError.getDescription()).isEqualTo("resolver error"); + } + + @Test + public void errorPropagationToDeactivatedChildBalancer() { + FakeLoadBalancer balancer = + deliverAddressesAndUpdateToRemoveChildPolicy( + new Route(routeMatch1, "actionA"), "policy_a"); + xdsRoutingLoadBalancer.handleNameResolutionError( + Status.UNKNOWN.withDescription("unknown error")); + assertThat(balancer.upstreamError).isNull(); + } + + private FakeLoadBalancer deliverAddressesAndUpdateToRemoveChildPolicy( + Route route, String childPolicyName) { + lbConfigInventory.put("actionX", null); + Route routeX = + new Route( + new RouteMatch( + new PathMatcher( + "/XService/xMethod", null, null), + Collections.emptyList(), + null), + "actionX"); + deliverResolvedAddresses( + ImmutableMap.of(route, childPolicyName, routeX, "policy_x")); + + verify(helper, atLeastOnce()).updateBalancingState( + eq(ConnectivityState.CONNECTING), any(SubchannelPicker.class)); + assertThat(childBalancers).hasSize(2); + FakeLoadBalancer balancer = childBalancers.get(0); + + deliverResolvedAddresses(ImmutableMap.of(routeX, "policy_x")); + verify(helper, atLeast(2)).updateBalancingState( + eq(ConnectivityState.CONNECTING), any(SubchannelPicker.class)); + assertThat(Iterables.getOnlyElement(fakeClock.getPendingTasks()).getDelay(TimeUnit.MINUTES)) + .isEqualTo(XdsRoutingLoadBalancer.DELAYED_ACTION_DELETION_TIME_MINUTES); + return balancer; + } + + private void deliverResolvedAddresses(final Map childPolicies) { + syncContext.execute(new Runnable() { + @Override + public void run() { + xdsRoutingLoadBalancer + .handleResolvedAddresses( + ResolvedAddresses.newBuilder() + .setAddresses(Collections.emptyList()) + .setLoadBalancingPolicyConfig(buildConfig(childPolicies)) + .build()); + } + }); + } + + private XdsRoutingConfig buildConfig(Map childPolicies) { + Map childPolicySelections = new LinkedHashMap<>(); + List routeList = new ArrayList<>(); + for (Route route : childPolicies.keySet()) { + String childActionName = route.getActionName(); + String childPolicyName = childPolicies.get(route); + Object childConfig = lbConfigInventory.get(childActionName); + PolicySelection policy = + new PolicySelection(new FakeLoadBalancerProvider(childPolicyName), null, childConfig); + childPolicySelections.put(childActionName, policy); + routeList.add(route); + } + return new XdsRoutingConfig(routeList, childPolicySelections); } @Test From c60f5ff95bcd836672a0557981f0b8972e1aa406 Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Wed, 22 Jul 2020 16:36:38 -0700 Subject: [PATCH 15/88] xds: implement STS based OAuth 2.0 credentials exchange (#7232) --- xds/build.gradle | 1 + .../grpc/xds/internal/sts/StsCredentials.java | 169 ++++++++++++++ .../xds/internal/sts/StsCredentialsTest.java | 207 ++++++++++++++++++ 3 files changed, 377 insertions(+) create mode 100644 xds/src/main/java/io/grpc/xds/internal/sts/StsCredentials.java create mode 100644 xds/src/test/java/io/grpc/xds/internal/sts/StsCredentialsTest.java diff --git a/xds/build.gradle b/xds/build.gradle index c6fb61ff823..5371d085d38 100644 --- a/xds/build.gradle +++ b/xds/build.gradle @@ -23,6 +23,7 @@ dependencies { project(':grpc-stub'), project(':grpc-core'), project(':grpc-services'), + project(':grpc-auth'), project(path: ':grpc-alts', configuration: 'shadow'), libraries.gson, libraries.re2j diff --git a/xds/src/main/java/io/grpc/xds/internal/sts/StsCredentials.java b/xds/src/main/java/io/grpc/xds/internal/sts/StsCredentials.java new file mode 100644 index 00000000000..65816502494 --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/internal/sts/StsCredentials.java @@ -0,0 +1,169 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds.internal.sts; + +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpContent; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpRequestFactory; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpStatusCodes; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.http.json.JsonHttpContent; +import com.google.api.client.json.JsonObjectParser; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.client.util.GenericData; +import com.google.auth.http.HttpTransportFactory; +import com.google.auth.oauth2.AccessToken; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.io.Files; + +import java.io.File; +import java.io.IOException; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +// TODO(sanjaypujare): replace with the official implementation from google-auth once ready +/** Implementation of OAuth2 Token Exchange as per https://tools.ietf.org/html/rfc8693. */ +public final class StsCredentials extends GoogleCredentials { + private static final long serialVersionUID = 6647041424685484932L; + + private static final HttpTransportFactory defaultHttpTransportFactory = + new DefaultHttpTransportFactory(); + private static final String CLOUD_PLATFORM_SCOPE = + "https://www.googleapis.com/auth/cloud-platform"; + private final String sourceCredentialsFileLocation; + private final String identityTokenEndpoint; + private final String audience; + private transient HttpTransportFactory transportFactory; + + private StsCredentials( + String identityTokenEndpoint, + String audience, + String sourceCredentialsFileLocation, + HttpTransportFactory transportFactory) { + this.identityTokenEndpoint = identityTokenEndpoint; + this.audience = audience; + this.sourceCredentialsFileLocation = sourceCredentialsFileLocation; + this.transportFactory = transportFactory; + } + + /** + * Creates an StsCredentials. + * + * @param identityTokenEndpoint URL of the token exchange service to use. + * @param audience Audience to use in the STS request. + * @param sourceCredentialsFileLocation file-system location that contains the + * source creds e.g. JWT contents. + */ + public static StsCredentials create( + String identityTokenEndpoint, String audience, String sourceCredentialsFileLocation) { + return create( + identityTokenEndpoint, + audience, + sourceCredentialsFileLocation, + getFromServiceLoader(HttpTransportFactory.class, defaultHttpTransportFactory)); + } + + @VisibleForTesting + static StsCredentials create( + String identityTokenEndpoint, + String audience, + String sourceCredentialsFileLocation, + HttpTransportFactory transportFactory) { + return new StsCredentials( + identityTokenEndpoint, audience, sourceCredentialsFileLocation, transportFactory); + } + + @Override + public AccessToken refreshAccessToken() throws IOException { + AccessToken tok = getSourceAccessTokenFromFileLocation(); + + HttpTransport httpTransport = this.transportFactory.create(); + JsonObjectParser parser = new JsonObjectParser(JacksonFactory.getDefaultInstance()); + + HttpRequestFactory requestFactory = httpTransport.createRequestFactory(); + GenericUrl url = new GenericUrl(identityTokenEndpoint); + + Map params = new HashMap<>(); + params.put("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange"); + params.put("subject_token_type", "urn:ietf:params:oauth:token-type:jwt"); + params.put("requested_token_type", "urn:ietf:params:oauth:token-type:access_token"); + params.put("subject_token", tok.getTokenValue()); + params.put("scope", CLOUD_PLATFORM_SCOPE); + params.put("audience", audience); + HttpContent content = new JsonHttpContent(parser.getJsonFactory(), params); + HttpRequest request = requestFactory.buildPostRequest(url, content); + request.setParser(parser); + + HttpResponse response = null; + try { + response = request.execute(); + } catch (IOException e) { + throw new IOException("Error requesting access token", e); + } + + if (response.getStatusCode() != HttpStatusCodes.STATUS_CODE_OK) { + throw new IOException("Error getting access token: " + getStatusString(response)); + } + + GenericData responseData = null; + try { + responseData = response.parseAs(GenericData.class); + } finally { + response.disconnect(); + } + + String access_token = (String) responseData.get("access_token"); + Date expiryTime = null; // just in case expired_in value is not present + if (responseData.containsKey("expires_in")) { + expiryTime = new Date(System.currentTimeMillis() + + ((BigDecimal) responseData.get("expires_in")).longValue() * 1000L); + } + return new AccessToken(access_token, expiryTime); + } + + private AccessToken getSourceAccessTokenFromFileLocation() throws IOException { + return new AccessToken( + Files.asCharSource(new File(sourceCredentialsFileLocation), StandardCharsets.UTF_8).read(), + null); + } + + private static String getStatusString(HttpResponse response) { + return response.getStatusCode() + " : " + response.getStatusMessage(); + } + + @Override + public Builder toBuilder() { + throw new UnsupportedOperationException("toBuilder not supported"); + } + + private static class DefaultHttpTransportFactory implements HttpTransportFactory { + + private static final HttpTransport netHttpTransport = new NetHttpTransport(); + + @Override + public HttpTransport create() { + return netHttpTransport; + } + } +} diff --git a/xds/src/test/java/io/grpc/xds/internal/sts/StsCredentialsTest.java b/xds/src/test/java/io/grpc/xds/internal/sts/StsCredentialsTest.java new file mode 100644 index 00000000000..4fa13fde14d --- /dev/null +++ b/xds/src/test/java/io/grpc/xds/internal/sts/StsCredentialsTest.java @@ -0,0 +1,207 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds.internal.sts; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.api.client.http.HttpStatusCodes; +import com.google.api.client.http.json.JsonHttpContent; +import com.google.api.client.testing.http.MockHttpTransport; +import com.google.api.client.testing.http.MockLowLevelHttpRequest; +import com.google.api.client.testing.http.MockLowLevelHttpResponse; +import com.google.auth.http.HttpTransportFactory; +import com.google.auth.oauth2.AccessToken; +import com.google.common.collect.Range; +import com.google.common.io.Files; +import com.google.common.util.concurrent.MoreExecutors; +import io.grpc.CallCredentials; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.SecurityLevel; +import io.grpc.auth.MoreCallCredentials; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; + +/** Tests for {@link StsCredentials}. */ +@RunWith(JUnit4.class) +public class StsCredentialsTest { + public static final String AUDIENCE_VALUE = + "identitynamespace:my-trust-domain:my-identity-provider"; + public static final String STS_URL = "https://securetoken.googleapis.com/v1/identitybindingtoken"; + private static final String TOKEN_FILE_NAME = "istio-token.txt"; + static final Metadata.Key KEY_FOR_AUTHORIZATION = + Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER); + + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + private String currentFileContents; + private File tempTokenFile; + + @Before + public void setUp() throws IOException { + tempTokenFile = tempFolder.newFile(TOKEN_FILE_NAME); + currentFileContents = "test-token-content-time-" + System.currentTimeMillis(); + Files.write(currentFileContents.getBytes(StandardCharsets.UTF_8), tempTokenFile); + } + + @SuppressWarnings("unchecked") + @Test + public void testStsRequestResponse() throws IOException { + MockHttpTransport.Builder builder = new MockHttpTransport.Builder(); + MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); + response.setContent(MOCK_RESPONSE); + builder.setLowLevelHttpResponse(response); + MockHttpTransport httpTransport = builder.build(); + HttpTransportFactory httpTransportFactory = mock(HttpTransportFactory.class); + when(httpTransportFactory.create()).thenReturn(httpTransport); + StsCredentials stsCredentials = + StsCredentials.create( + STS_URL, AUDIENCE_VALUE, tempTokenFile.getAbsolutePath(), httpTransportFactory); + AccessToken token = stsCredentials.refreshAccessToken(); + assertThat(token).isNotNull(); + assertThat(token.getTokenValue()).isEqualTo(ACCESS_TOKEN); + long diff = token.getExpirationTime().getTime() - System.currentTimeMillis(); + assertThat(diff).isIn(Range.closed(3550000L, 3650000L)); + MockLowLevelHttpRequest request = httpTransport.getLowLevelHttpRequest(); + assertThat(request.getUrl()).isEqualTo(STS_URL); + assertThat(request.getContentType()).isEqualTo("application/json; charset=UTF-8"); + assertThat(request.getStreamingContent()).isInstanceOf(JsonHttpContent.class); + Map map = + (Map) ((JsonHttpContent) request.getStreamingContent()).getData(); + assertThat(map.get("subject_token_type")).isEqualTo("urn:ietf:params:oauth:token-type:jwt"); + assertThat(map.get("grant_type")).isEqualTo("urn:ietf:params:oauth:grant-type:token-exchange"); + assertThat(map.get("subject_token")).isEqualTo(currentFileContents); + assertThat(map.get("requested_token_type")) + .isEqualTo("urn:ietf:params:oauth:token-type:access_token"); + assertThat(map.get("scope")).isEqualTo("https://www.googleapis.com/auth/cloud-platform"); + assertThat(map.get("audience")).isEqualTo(AUDIENCE_VALUE); + } + + @Test + public void stsCredentialsInCallCreds() throws IOException { + MockHttpTransport.Builder builder = new MockHttpTransport.Builder(); + MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); + response.setContent(MOCK_RESPONSE); + builder.setLowLevelHttpResponse(response); + MockHttpTransport httpTransport = builder.build(); + HttpTransportFactory httpTransportFactory = mock(HttpTransportFactory.class); + when(httpTransportFactory.create()).thenReturn(httpTransport); + StsCredentials stsCredentials = + StsCredentials.create( + STS_URL, AUDIENCE_VALUE, tempTokenFile.getAbsolutePath(), httpTransportFactory); + CallCredentials callCreds = MoreCallCredentials.from(stsCredentials); + CallCredentials.RequestInfo requestInfo = mock(CallCredentials.RequestInfo.class); + when(requestInfo.getSecurityLevel()).thenReturn(SecurityLevel.PRIVACY_AND_INTEGRITY); + when(requestInfo.getAuthority()).thenReturn("auth"); + MethodDescriptor.Marshaller requestMarshaller = mock(MethodDescriptor.Marshaller.class); + MethodDescriptor.Marshaller responseMarshaller = mock(MethodDescriptor.Marshaller.class); + MethodDescriptor.Builder mBuilder = + MethodDescriptor.newBuilder(requestMarshaller, responseMarshaller); + mBuilder.setType(MethodDescriptor.MethodType.UNARY); + mBuilder.setFullMethodName("service/method"); + MethodDescriptor methodDescriptor = mBuilder.build(); + doReturn(methodDescriptor).when(requestInfo).getMethodDescriptor(); + CallCredentials.MetadataApplier applier = mock(CallCredentials.MetadataApplier.class); + callCreds.applyRequestMetadata(requestInfo, MoreExecutors.directExecutor(), applier); + ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(null); + verify(applier).apply(metadataCaptor.capture()); + Metadata metadata = metadataCaptor.getValue(); + assertThat(metadata).isNotNull(); + String authValue = metadata.get(KEY_FOR_AUTHORIZATION); + assertThat(authValue).isEqualTo("Bearer " + ACCESS_TOKEN); + } + + @Test + public void testStsRequest_exception() throws IOException { + MockHttpTransport.Builder builder = new MockHttpTransport.Builder(); + MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); + response.setStatusCode(HttpStatusCodes.STATUS_CODE_BAD_REQUEST); + response.setContent(MOCK_RESPONSE); + builder.setLowLevelHttpResponse(response); + MockHttpTransport httpTransport = builder.build(); + HttpTransportFactory httpTransportFactory = mock(HttpTransportFactory.class); + when(httpTransportFactory.create()).thenReturn(httpTransport); + StsCredentials stsCredentials = + StsCredentials.create( + STS_URL, AUDIENCE_VALUE, tempTokenFile.getAbsolutePath(), httpTransportFactory); + try { + stsCredentials.refreshAccessToken(); + fail("exception expected"); + } catch (IOException ioe) { + assertThat(ioe.getMessage()).isEqualTo("Error requesting access token"); + } + } + + @Test + public void testStsRequest_nonSuccessCode() throws IOException { + MockHttpTransport.Builder builder = new MockHttpTransport.Builder(); + MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); + response.setStatusCode(HttpStatusCodes.STATUS_CODE_NO_CONTENT); + response.setContent(MOCK_RESPONSE); + builder.setLowLevelHttpResponse(response); + MockHttpTransport httpTransport = builder.build(); + HttpTransportFactory httpTransportFactory = mock(HttpTransportFactory.class); + when(httpTransportFactory.create()).thenReturn(httpTransport); + StsCredentials stsCredentials = + StsCredentials.create( + STS_URL, AUDIENCE_VALUE, tempTokenFile.getAbsolutePath(), httpTransportFactory); + try { + stsCredentials.refreshAccessToken(); + fail("exception expected"); + } catch (IOException ioe) { + assertThat(ioe.getMessage()).isEqualTo("Error getting access token: 204 : null"); + } + } + + @Test + public void toBuilder_unsupportedException() { + HttpTransportFactory httpTransportFactory = mock(HttpTransportFactory.class); + StsCredentials stsCredentials = + StsCredentials.create( + STS_URL, AUDIENCE_VALUE, tempTokenFile.getAbsolutePath(), httpTransportFactory); + try { + stsCredentials.toBuilder(); + fail("exception expected"); + } catch (UnsupportedOperationException uoe) { + assertThat(uoe.getMessage()).isEqualTo("toBuilder not supported"); + } + } + + private static final String ACCESS_TOKEN = "eyJhbGciOiJSU"; + private static final String MOCK_RESPONSE = + "{\"access_token\": \"" + + ACCESS_TOKEN + + "\",\n" + + " \"issued_token_type\": \"urn:ietf:params:oauth:token-type:access_token\",\n" + + " \"token_type\": \"Bearer\",\n" + + " \"expires_in\": 3600\n" + + "}"; +} From 61fd6d3ecdf04cc957808a109828529992e4244f Mon Sep 17 00:00:00 2001 From: Ran Date: Thu, 23 Jul 2020 12:58:24 -0700 Subject: [PATCH 16/88] xds: fix missing @RunWith annotation. (#7245) --- .../io/grpc/xds/ClusterManagerLoadBalancerProviderTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerProviderTest.java b/xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerProviderTest.java index 3621761b718..52404155184 100644 --- a/xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerProviderTest.java @@ -30,8 +30,11 @@ import java.util.Collections; import java.util.Map; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; /** Tests for {@link ClusterManagerLoadBalancerProvider}. */ +@RunWith(JUnit4.class) public class ClusterManagerLoadBalancerProviderTest { @Test From 8a792c33674b4c18930e5d0c874c864cc702bc21 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 23 Jul 2020 12:41:45 -0700 Subject: [PATCH 17/88] buildscripts: Fix check for java-example-hostname:latest tagging The previous 'git ls-remote' was returning the tag-referenced commits via vF.O.O^{} which was confusing the version check (as v1.30.0 would not match v1.30.0^{}). Passing --refs filters those ^{} tags. There was also a missing 'v' in the version check. It was added explicitly to generate the list but not to check the result. --- buildscripts/kokoro/upload_artifacts.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildscripts/kokoro/upload_artifacts.sh b/buildscripts/kokoro/upload_artifacts.sh index c56cddf8f8c..45e49d1e368 100644 --- a/buildscripts/kokoro/upload_artifacts.sh +++ b/buildscripts/kokoro/upload_artifacts.sh @@ -73,7 +73,7 @@ fi VERSION="$(cat $LOCAL_OTHER_ARTIFACTS/version)" EXAMPLE_HOSTNAME_ID="$(cat "$LOCAL_OTHER_ARTIFACTS/example-hostname.id")" docker load --input "$LOCAL_OTHER_ARTIFACTS/example-hostname.tar" -LATEST_VERSION="$((echo "v$VERSION"; git ls-remote -t https://github.com/grpc/grpc-java.git | cut -f 2 | sed s#refs/tags/##) | sort -V | tail -n 1)" +LATEST_VERSION="$((echo "v$VERSION"; git ls-remote -t --refs https://github.com/grpc/grpc-java.git | cut -f 2 | sed s#refs/tags/##) | sort -V | tail -n 1)" STAGING_REPO=a93898609ef848 @@ -81,7 +81,7 @@ STAGING_REPO=a93898609ef848 docker tag "$EXAMPLE_HOSTNAME_ID" "grpc/java-example-hostname:${VERSION}" docker push "grpc/java-example-hostname:${VERSION}" -if [[ "$VERSION" = "$LATEST_VERSION" ]]; then +if [[ "v$VERSION" = "$LATEST_VERSION" ]]; then docker tag "$EXAMPLE_HOSTNAME_ID" grpc/java-example-hostname:latest docker push grpc/java-example-hostname:latest fi From 6ec9387cbcf3b23d7954d312e844b6859b68b305 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Fri, 24 Jul 2020 09:40:53 -0700 Subject: [PATCH 18/88] core: refactor ClientTransportProvider to ClientStreamProvider Replace the two APIs of ClientCallImpl.ClientTransportProvider with a single API: newStream(). --- .../java/io/grpc/internal/ClientCallImpl.java | 42 +---- .../io/grpc/internal/ManagedChannelImpl.java | 97 ++++++----- .../java/io/grpc/internal/OobChannel.java | 23 ++- .../io/grpc/internal/SubchannelChannel.java | 27 ++- .../io/grpc/internal/ClientCallImplTest.java | 159 ++++++++---------- 5 files changed, 154 insertions(+), 194 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/ClientCallImpl.java b/core/src/main/java/io/grpc/internal/ClientCallImpl.java index 0460f0c5e3c..2c0321b27a5 100644 --- a/core/src/main/java/io/grpc/internal/ClientCallImpl.java +++ b/core/src/main/java/io/grpc/internal/ClientCallImpl.java @@ -41,7 +41,6 @@ import io.grpc.Deadline; import io.grpc.DecompressorRegistry; import io.grpc.InternalDecompressorRegistry; -import io.grpc.LoadBalancer.PickSubchannelArgs; import io.grpc.Metadata; import io.grpc.MethodDescriptor; import io.grpc.MethodDescriptor.MethodType; @@ -84,12 +83,11 @@ final class ClientCallImpl extends ClientCall { private final Context context; private final boolean unaryRequest; private final CallOptions callOptions; - private final boolean retryEnabled; private ClientStream stream; private volatile boolean cancelListenersShouldBeRemoved; private boolean cancelCalled; private boolean halfCloseCalled; - private final ClientTransportProvider clientTransportProvider; + private final ClientStreamProvider clientStreamProvider; private ContextCancellationListener cancellationListener; private final ScheduledExecutorService deadlineCancellationExecutor; private boolean fullStreamDecompression; @@ -101,10 +99,9 @@ final class ClientCallImpl extends ClientCall { ClientCallImpl( MethodDescriptor method, Executor executor, CallOptions callOptions, - ClientTransportProvider clientTransportProvider, + ClientStreamProvider clientStreamProvider, ScheduledExecutorService deadlineCancellationExecutor, - CallTracer channelCallsTracer, - boolean retryEnabled) { + CallTracer channelCallsTracer) { this.method = method; // TODO(carl-mastrangelo): consider moving this construction to ManagedChannelImpl. this.tag = PerfMark.createTag(method.getFullMethodName(), System.identityHashCode(this)); @@ -124,9 +121,8 @@ final class ClientCallImpl extends ClientCall { this.unaryRequest = method.getType() == MethodType.UNARY || method.getType() == MethodType.SERVER_STREAMING; this.callOptions = callOptions; - this.clientTransportProvider = clientTransportProvider; + this.clientStreamProvider = clientStreamProvider; this.deadlineCancellationExecutor = deadlineCancellationExecutor; - this.retryEnabled = retryEnabled; PerfMark.event("ClientCall.", tag); } @@ -149,23 +145,14 @@ public void cancelled(Context context) { } /** - * Provider of {@link ClientTransport}s. + * Provider of {@link ClientStream}s. */ - // TODO(zdapeng): replace the two APIs with a single API: newStream() - interface ClientTransportProvider { - /** - * Returns a transport for a new call. - * - * @param args object containing call arguments. - */ - ClientTransport get(PickSubchannelArgs args); - - ClientStream newRetriableStream( - MethodDescriptor method, + interface ClientStreamProvider { + ClientStream newStream( + MethodDescriptor method, CallOptions callOptions, Metadata headers, Context context); - } ClientCallImpl setFullStreamDecompression(boolean fullStreamDecompression) { @@ -252,18 +239,7 @@ private void startInternal(final Listener observer, Metadata headers) { if (!deadlineExceeded) { logIfContextNarrowedTimeout( effectiveDeadline, context.getDeadline(), callOptions.getDeadline()); - if (retryEnabled) { - stream = clientTransportProvider.newRetriableStream(method, callOptions, headers, context); - } else { - ClientTransport transport = clientTransportProvider.get( - new PickSubchannelArgsImpl(method, headers, callOptions)); - Context origContext = context.attach(); - try { - stream = transport.newStream(method, headers, callOptions); - } finally { - context.detach(origContext); - } - } + stream = clientStreamProvider.newStream(method, callOptions, headers, context); } else { stream = new FailingClientStream( DEADLINE_EXCEEDED.withDescription( diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java index 66a14977112..015b82917df 100644 --- a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java +++ b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java @@ -72,7 +72,7 @@ import io.grpc.SynchronizationContext; import io.grpc.SynchronizationContext.ScheduledHandle; import io.grpc.internal.AutoConfiguredLoadBalancerFactory.AutoConfiguredLoadBalancer; -import io.grpc.internal.ClientCallImpl.ClientTransportProvider; +import io.grpc.internal.ClientCallImpl.ClientStreamProvider; import io.grpc.internal.RetriableStream.ChannelBufferMeter; import io.grpc.internal.RetriableStream.Throttle; import java.net.URI; @@ -454,9 +454,8 @@ private void refreshNameResolution() { } } - private final class ChannelTransportProvider implements ClientTransportProvider { - @Override - public ClientTransport get(PickSubchannelArgs args) { + private final class ChannelStreamProvider implements ClientStreamProvider { + private ClientTransport getTransport(PickSubchannelArgs args) { SubchannelPicker pickerCopy = subchannelPicker; if (shutdown.get()) { // If channel is shut down, delayedTransport is also shut down which will fail the stream @@ -494,57 +493,68 @@ public void run() { } @Override - public ClientStream newRetriableStream( - final MethodDescriptor method, + public ClientStream newStream( + final MethodDescriptor method, final CallOptions callOptions, final Metadata headers, final Context context) { - checkState(retryEnabled, "retry should be enabled"); - final Throttle throttle = lastServiceConfig.getRetryThrottling(); - final class RetryStream extends RetriableStream { - RetryStream() { - super( - method, - headers, - channelBufferUsed, - perRpcBufferLimit, - channelBufferLimit, - getCallExecutor(callOptions), - transportFactory.getScheduledExecutorService(), - callOptions.getOption(RETRY_POLICY_KEY), - callOptions.getOption(HEDGING_POLICY_KEY), - throttle); + if (!retryEnabled) { + ClientTransport transport = + getTransport(new PickSubchannelArgsImpl(method, headers, callOptions)); + Context origContext = context.attach(); + try { + return transport.newStream(method, headers, callOptions); + } finally { + context.detach(origContext); } + } else { + final Throttle throttle = lastServiceConfig.getRetryThrottling(); + final class RetryStream extends RetriableStream { + @SuppressWarnings("unchecked") + RetryStream() { + super( + (MethodDescriptor) method, + headers, + channelBufferUsed, + perRpcBufferLimit, + channelBufferLimit, + getCallExecutor(callOptions), + transportFactory.getScheduledExecutorService(), + callOptions.getOption(RETRY_POLICY_KEY), + callOptions.getOption(HEDGING_POLICY_KEY), + throttle); + } - @Override - Status prestart() { - return uncommittedRetriableStreamsRegistry.add(this); - } + @Override + Status prestart() { + return uncommittedRetriableStreamsRegistry.add(this); + } - @Override - void postCommit() { - uncommittedRetriableStreamsRegistry.remove(this); - } + @Override + void postCommit() { + uncommittedRetriableStreamsRegistry.remove(this); + } - @Override - ClientStream newSubstream(ClientStreamTracer.Factory tracerFactory, Metadata newHeaders) { - CallOptions newOptions = callOptions.withStreamTracerFactory(tracerFactory); - ClientTransport transport = - get(new PickSubchannelArgsImpl(method, newHeaders, newOptions)); - Context origContext = context.attach(); - try { - return transport.newStream(method, newHeaders, newOptions); - } finally { - context.detach(origContext); + @Override + ClientStream newSubstream(ClientStreamTracer.Factory tracerFactory, Metadata newHeaders) { + CallOptions newOptions = callOptions.withStreamTracerFactory(tracerFactory); + ClientTransport transport = + getTransport(new PickSubchannelArgsImpl(method, newHeaders, newOptions)); + Context origContext = context.attach(); + try { + return transport.newStream(method, newHeaders, newOptions); + } finally { + context.detach(origContext); + } } } - } - return new RetryStream(); + return new RetryStream<>(); + } } } - private final ClientTransportProvider transportProvider = new ChannelTransportProvider(); + private final ClientStreamProvider transportProvider = new ChannelStreamProvider(); private final Rescheduler idleTimer; @@ -887,8 +897,7 @@ public ClientCall newCall(MethodDescriptor method, + CallOptions callOptions, Metadata headers, Context context) { + Context origContext = context.attach(); // delayed transport's newStream() always acquires a lock, but concurrent performance doesn't // matter here because OOB communication should be sparse, and it's not on application RPC's // critical path. - return delayedTransport; - } - - @Override - public ClientStream newRetriableStream(MethodDescriptor method, - CallOptions callOptions, Metadata headers, Context context) { - throw new UnsupportedOperationException("OobChannel should not create retriable streams"); + try { + return delayedTransport.newStream(method, headers, callOptions); + } finally { + context.detach(origContext); + } } }; @@ -202,8 +202,7 @@ public ClientCall newCall( MethodDescriptor methodDescriptor, CallOptions callOptions) { return new ClientCallImpl<>(methodDescriptor, callOptions.getExecutor() == null ? executor : callOptions.getExecutor(), - callOptions, transportProvider, deadlineCancellationExecutor, channelCallsTracer, - false /* retryEnabled */); + callOptions, transportProvider, deadlineCancellationExecutor, channelCallsTracer); } @Override diff --git a/core/src/main/java/io/grpc/internal/SubchannelChannel.java b/core/src/main/java/io/grpc/internal/SubchannelChannel.java index 9d5bfcec7d7..d55c403ef03 100644 --- a/core/src/main/java/io/grpc/internal/SubchannelChannel.java +++ b/core/src/main/java/io/grpc/internal/SubchannelChannel.java @@ -23,13 +23,11 @@ import io.grpc.Channel; import io.grpc.ClientCall; import io.grpc.Context; -import io.grpc.LoadBalancer.PickSubchannelArgs; import io.grpc.Metadata; import io.grpc.MethodDescriptor; import io.grpc.Status; -import io.grpc.internal.ClientCallImpl.ClientTransportProvider; +import io.grpc.internal.ClientCallImpl.ClientStreamProvider; import io.grpc.internal.ClientStreamListener.RpcProgress; -import io.grpc.internal.GrpcUtil; import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; @@ -48,21 +46,20 @@ final class SubchannelChannel extends Channel { private final ScheduledExecutorService deadlineCancellationExecutor; private final CallTracer callsTracer; - private final ClientTransportProvider transportProvider = new ClientTransportProvider() { + private final ClientStreamProvider transportProvider = new ClientStreamProvider() { @Override - public ClientTransport get(PickSubchannelArgs args) { + public ClientStream newStream(MethodDescriptor method, + CallOptions callOptions, Metadata headers, Context context) { ClientTransport transport = subchannel.getTransport(); if (transport == null) { - return notReadyTransport; - } else { - return transport; + transport = notReadyTransport; + } + Context origContext = context.attach(); + try { + return transport.newStream(method, headers, callOptions); + } finally { + context.detach(origContext); } - } - - @Override - public ClientStream newRetriableStream(MethodDescriptor method, - CallOptions callOptions, Metadata headers, Context context) { - throw new UnsupportedOperationException("OobChannel should not create retriable streams"); } }; @@ -109,7 +106,7 @@ public void sendMessage(RequestT message) {} return new ClientCallImpl<>(methodDescriptor, effectiveExecutor, callOptions.withOption(GrpcUtil.CALL_OPTIONS_RPC_OWNED_BY_BALANCER, Boolean.TRUE), - transportProvider, deadlineCancellationExecutor, callsTracer, false /* retryEnabled */); + transportProvider, deadlineCancellationExecutor, callsTracer); } @Override diff --git a/core/src/test/java/io/grpc/internal/ClientCallImplTest.java b/core/src/test/java/io/grpc/internal/ClientCallImplTest.java index cf74c2be46e..268264f848d 100644 --- a/core/src/test/java/io/grpc/internal/ClientCallImplTest.java +++ b/core/src/test/java/io/grpc/internal/ClientCallImplTest.java @@ -56,7 +56,7 @@ import io.grpc.MethodDescriptor.MethodType; import io.grpc.Status; import io.grpc.Status.Code; -import io.grpc.internal.ClientCallImpl.ClientTransportProvider; +import io.grpc.internal.ClientCallImpl.ClientStreamProvider; import io.grpc.internal.testing.SingleMessageProducer; import io.grpc.testing.TestMethodDescriptors; import java.io.ByteArrayInputStream; @@ -108,18 +108,13 @@ public class ClientCallImplTest { @Rule public final MockitoRule mocks = MockitoJUnit.rule(); - @Mock private ClientStreamListener streamListener; - @Mock private ClientTransport clientTransport; @Captor private ArgumentCaptor statusCaptor; @Mock private ClientStreamTracer.Factory streamTracerFactory; @Mock - private ClientTransport transport; - - @Mock - private ClientTransportProvider provider; + private ClientStreamProvider clientStreamProvider; @Mock private ClientStream stream; @@ -140,9 +135,11 @@ public class ClientCallImplTest { @Before public void setUp() { - when(provider.get(any(PickSubchannelArgsImpl.class))).thenReturn(transport); - when(transport.newStream( - any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class))) + when(clientStreamProvider.newStream( + (MethodDescriptor) any(MethodDescriptor.class), + any(CallOptions.class), + any(Metadata.class), + any(Context.class))) .thenReturn(stream); doAnswer(new Answer() { @Override @@ -167,10 +164,9 @@ public void statusPropagatedFromStreamToCallListener() { method, executor, baseCallOptions, - provider, + clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer, - /* retryEnabled= */ false); + channelCallTracer); call.start(callListener, new Metadata()); verify(stream).start(listenerArgumentCaptor.capture()); final ClientStreamListener streamListener = listenerArgumentCaptor.getValue(); @@ -189,10 +185,9 @@ public void exceptionInOnMessageTakesPrecedenceOverServer() { method, executor, baseCallOptions, - provider, + clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer, - /* retryEnabled= */ false); + channelCallTracer); call.start(callListener, new Metadata()); verify(stream).start(listenerArgumentCaptor.capture()); final ClientStreamListener streamListener = listenerArgumentCaptor.getValue(); @@ -227,10 +222,9 @@ public void exceptionInOnHeadersTakesPrecedenceOverServer() { method, executor, baseCallOptions, - provider, + clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer, - /* retryEnabled= */ false); + channelCallTracer); call.start(callListener, new Metadata()); verify(stream).start(listenerArgumentCaptor.capture()); final ClientStreamListener streamListener = listenerArgumentCaptor.getValue(); @@ -272,10 +266,9 @@ class PointOfNoReturnExecutor implements Executor { method, executor, baseCallOptions, - provider, + clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer, - /* retryEnabled= */ false); + channelCallTracer); callListener = new NoopClientCall.NoopClientCallListener() { private final RuntimeException failure = new RuntimeException("bad"); @@ -308,10 +301,9 @@ public void exceptionInOnReadyTakesPrecedenceOverServer() { method.toBuilder().setType(MethodType.UNKNOWN).build(), executor, baseCallOptions, - provider, + clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer, - /* retryEnabled= */ false); + channelCallTracer); call.start(callListener, new Metadata()); verify(stream).start(listenerArgumentCaptor.capture()); final ClientStreamListener streamListener = listenerArgumentCaptor.getValue(); @@ -343,16 +335,16 @@ public void advertisedEncodingsAreSent() { method, MoreExecutors.directExecutor(), baseCallOptions, - provider, + clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer, - /* retryEnabled= */ false) + channelCallTracer) .setDecompressorRegistry(decompressorRegistry); call.start(callListener, new Metadata()); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(Metadata.class); - verify(transport).newStream(eq(method), metadataCaptor.capture(), same(baseCallOptions)); + verify(clientStreamProvider) + .newStream(eq(method), same(baseCallOptions), metadataCaptor.capture(), any(Context.class)); Metadata actual = metadataCaptor.getValue(); // there should only be one. @@ -367,10 +359,9 @@ public void authorityPropagatedToStream() { method, MoreExecutors.directExecutor(), baseCallOptions.withAuthority("overridden-authority"), - provider, + clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer, - /* retryEnabled= */ false) + channelCallTracer) .setDecompressorRegistry(decompressorRegistry); call.start(callListener, new Metadata()); @@ -384,16 +375,16 @@ public void callOptionsPropagatedToTransport() { method, MoreExecutors.directExecutor(), callOptions, - provider, + clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer, - /* retryEnabled= */ false) + channelCallTracer) .setDecompressorRegistry(decompressorRegistry); final Metadata metadata = new Metadata(); call.start(callListener, metadata); - verify(transport).newStream(same(method), same(metadata), same(callOptions)); + verify(clientStreamProvider) + .newStream(same(method), same(callOptions), same(metadata), any(Context.class)); } @Test @@ -403,10 +394,9 @@ public void authorityNotPropagatedToStream() { MoreExecutors.directExecutor(), // Don't provide an authority baseCallOptions, - provider, + clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer, - /* retryEnabled= */ false) + channelCallTracer) .setDecompressorRegistry(decompressorRegistry); call.start(callListener, new Metadata()); @@ -574,10 +564,9 @@ public void callerContextPropagatedToListener() throws Exception { method.toBuilder().setType(MethodType.UNKNOWN).build(), new SerializingExecutor(Executors.newSingleThreadExecutor()), baseCallOptions, - provider, + clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer, - /* retryEnabled= */ false) + channelCallTracer) .setDecompressorRegistry(decompressorRegistry); context.detach(previous); @@ -652,10 +641,9 @@ public void contextCancellationCancelsStream() throws Exception { method, new SerializingExecutor(Executors.newSingleThreadExecutor()), baseCallOptions, - provider, + clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer, - /* retryEnabled= */ false) + channelCallTracer) .setDecompressorRegistry(decompressorRegistry); cancellableContext.detach(previous); @@ -682,10 +670,9 @@ public void contextAlreadyCancelledNotifiesImmediately() throws Exception { method, new SerializingExecutor(Executors.newSingleThreadExecutor()), baseCallOptions, - provider, + clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer, - /* retryEnabled= */ false) + channelCallTracer) .setDecompressorRegistry(decompressorRegistry); cancellableContext.detach(previous); @@ -709,7 +696,7 @@ public void onClose(Status status, Metadata trailers) { call.halfClose(); // Stream should never be created. - verifyZeroInteractions(transport); + verifyZeroInteractions(clientStreamProvider); try { call.sendMessage(null); @@ -727,19 +714,22 @@ public void deadlineExceededBeforeCallStarted() { method, new SerializingExecutor(Executors.newSingleThreadExecutor()), callOptions, - provider, + clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer, - /* retryEnabled= */ false) + channelCallTracer) .setDecompressorRegistry(decompressorRegistry); call.start(callListener, new Metadata()); - verify(transport, times(0)) - .newStream(any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class)); + verify(clientStreamProvider, never()) + .newStream( + (MethodDescriptor) any(MethodDescriptor.class), + any(CallOptions.class), + any(Metadata.class), + any(Context.class)); verify(callListener, timeout(1000)).onClose(statusCaptor.capture(), any(Metadata.class)); assertEquals(Status.Code.DEADLINE_EXCEEDED, statusCaptor.getValue().getCode()); assertThat(statusCaptor.getValue().getDescription()) .startsWith("ClientCall started after deadline exceeded"); - verifyZeroInteractions(provider); + verifyZeroInteractions(clientStreamProvider); } @Test @@ -752,10 +742,9 @@ public void contextDeadlineShouldBePropagatedToStream() { method, MoreExecutors.directExecutor(), baseCallOptions, - provider, + clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer, - /* retryEnabled= */ false); + channelCallTracer); call.start(callListener, new Metadata()); context.detach(origContext); @@ -777,10 +766,9 @@ public void contextDeadlineShouldOverrideLargerCallOptionsDeadline() { method, MoreExecutors.directExecutor(), callOpts, - provider, + clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer, - /* retryEnabled= */ false); + channelCallTracer); call.start(callListener, new Metadata()); context.detach(origContext); @@ -802,10 +790,9 @@ public void contextDeadlineShouldNotOverrideSmallerCallOptionsDeadline() { method, MoreExecutors.directExecutor(), callOpts, - provider, + clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer, - /* retryEnabled= */ false); + channelCallTracer); call.start(callListener, new Metadata()); context.detach(origContext); @@ -823,10 +810,9 @@ public void callOptionsDeadlineShouldBePropagatedToStream() { method, MoreExecutors.directExecutor(), callOpts, - provider, + clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer, - /* retryEnabled= */ false); + channelCallTracer); call.start(callListener, new Metadata()); ArgumentCaptor deadlineCaptor = ArgumentCaptor.forClass(Deadline.class); @@ -841,10 +827,9 @@ public void noDeadlineShouldBePropagatedToStream() { method, MoreExecutors.directExecutor(), baseCallOptions, - provider, + clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer, - /* retryEnabled= */ false); + channelCallTracer); call.start(callListener, new Metadata()); verify(stream, never()).setDeadline(any(Deadline.class)); @@ -860,10 +845,9 @@ public void expiredDeadlineCancelsStream_CallOptions() { MoreExecutors.directExecutor(), baseCallOptions.withDeadline( Deadline.after(1, TimeUnit.SECONDS, fakeClock.getDeadlineTicker())), - provider, + clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer, - /* retryEnabled= */ false); + channelCallTracer); call.start(callListener, new Metadata()); @@ -899,10 +883,9 @@ public void expiredDeadlineCancelsStream_Context() { method, MoreExecutors.directExecutor(), baseCallOptions, - provider, + clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer, - /* retryEnabled= */ false); + channelCallTracer); context.detach(origContext); @@ -930,10 +913,9 @@ public void streamCancelAbortsDeadlineTimer() { method, MoreExecutors.directExecutor(), baseCallOptions.withDeadline(Deadline.after(1, TimeUnit.SECONDS)), - provider, + clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer, - /* retryEnabled= */ false); + channelCallTracer); call.start(callListener, new Metadata()); call.cancel("canceled", null); @@ -955,10 +937,9 @@ public void timeoutShouldNotBeSet() { method, MoreExecutors.directExecutor(), baseCallOptions, - provider, + clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer, - /* retryEnabled= */ false); + channelCallTracer); Metadata headers = new Metadata(); @@ -973,10 +954,9 @@ public void cancelInOnMessageShouldInvokeStreamCancel() throws Exception { method, MoreExecutors.directExecutor(), baseCallOptions, - provider, + clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer, - /* retryEnabled= */ false); + channelCallTracer); final Exception cause = new Exception(); ClientCall.Listener callListener = new ClientCall.Listener() { @@ -1011,10 +991,9 @@ public void startAddsMaxSize() { method, new SerializingExecutor(Executors.newSingleThreadExecutor()), callOptions, - provider, + clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer, - /* retryEnabled= */ false) + channelCallTracer) .setDecompressorRegistry(decompressorRegistry); call.start(callListener, new Metadata()); @@ -1026,8 +1005,8 @@ public void startAddsMaxSize() { @Test public void getAttributes() { ClientCallImpl call = new ClientCallImpl<>( - method, MoreExecutors.directExecutor(), baseCallOptions, provider, - deadlineCancellationExecutor, channelCallTracer, /* retryEnabled= */ false); + method, MoreExecutors.directExecutor(), baseCallOptions, clientStreamProvider, + deadlineCancellationExecutor, channelCallTracer); Attributes attrs = Attributes.newBuilder().set(Key.create("fake key"), "fake value").build(); when(stream.getAttributes()).thenReturn(attrs); From 7949f65e0ffa5d21f754f7814cebb9a441f38eb6 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Tue, 28 Jul 2020 08:03:46 -0700 Subject: [PATCH 19/88] xds: remove GRPC_XDS_EXPERIMENTAL_ROUTING flag --- .../main/java/io/grpc/xds/XdsClientImpl.java | 15 -------- .../java/io/grpc/xds/XdsClientImplTest.java | 35 ------------------- .../java/io/grpc/xds/XdsClientTestHelper.java | 5 --- .../xds/XdsNameResolverIntegrationTest.java | 3 -- 4 files changed, 58 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/XdsClientImpl.java b/xds/src/main/java/io/grpc/xds/XdsClientImpl.java index f559595bce3..e8bd49906b3 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClientImpl.java +++ b/xds/src/main/java/io/grpc/xds/XdsClientImpl.java @@ -24,7 +24,6 @@ import com.google.common.base.Stopwatch; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; import com.google.protobuf.Any; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.MessageOrBuilder; @@ -111,10 +110,6 @@ final class XdsClientImpl extends XdsClient { private static final String ADS_TYPE_URL_EDS = "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment"; - // Mutable for testing. - static boolean enableExperimentalRouting = Boolean.parseBoolean( - System.getenv("GRPC_XDS_EXPERIMENTAL_ROUTING")); - private final MessagePrinter respPrinter = new MessagePrinter(); private final InternalLogId logId; @@ -889,16 +884,6 @@ static List populateRoutesInVirtualHost(VirtualHost virtua throw new InvalidProtoDataException( "Virtual host [" + virtualHost.getName() + "] contains no usable route"); } - - if (!enableExperimentalRouting) { - EnvoyProtoData.Route defaultRoute = Iterables.getLast(routes); - if (!defaultRoute.isDefaultRoute()) { - throw new InvalidProtoDataException( - "Virtual host [" + virtualHost.getName() - + "] contains non-default route as the last route"); - } - return Collections.singletonList(defaultRoute); - } return Collections.unmodifiableList(routes); } diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java index 5f747a24b0c..8e8002ecc0b 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java @@ -307,7 +307,6 @@ ManagedChannel createChannel(List servers) { @After public void tearDown() { - XdsClientImpl.enableExperimentalRouting = false; xdsClient.shutdown(); assertThat(adsEnded.get()).isTrue(); assertThat(lrsEnded.get()).isTrue(); @@ -647,7 +646,6 @@ public void resolveVirtualHostInRdsResponse() { */ @Test public void resolveVirtualHostWithPathMatchingInRdsResponse() { - XdsClientImpl.enableExperimentalRouting = true; xdsClient.watchConfigData(TARGET_AUTHORITY, configWatcher); StreamObserver responseObserver = responseObservers.poll(); StreamObserver requestObserver = requestObservers.poll(); @@ -3430,25 +3428,6 @@ public void populateRoutesInVirtualHost_routeWithCaseInsensitiveMatch() { XdsClientImpl.populateRoutesInVirtualHost(virtualHost); } - @Test - public void populateRoutesInVirtualHost_lastRouteIsNotDefaultRoute() { - VirtualHost virtualHost = - VirtualHost.newBuilder() - .setName("virtualhost00.googleapis.com") // don't care - .addDomains(TARGET_AUTHORITY) - .addRoutes( - Route.newBuilder() - .setRoute(RouteAction.newBuilder().setCluster("cluster.googleapis.com")) - .setMatch( - RouteMatch.newBuilder() - .setPrefix("/service/method") - .setCaseSensitive(BoolValue.newBuilder().setValue(true)))) - .build(); - - thrown.expect(XdsClientImpl.InvalidProtoDataException.class); - XdsClientImpl.populateRoutesInVirtualHost(virtualHost); - } - @Test public void populateRoutesInVirtualHost_NoUsableRoute() { VirtualHost virtualHost = @@ -3514,13 +3493,6 @@ public void messagePrinter_printLdsResponse() { + " \"prefix\": \"\"\n" + " },\n" + " \"route\": {\n" - + " \"cluster\": \"whatever cluster\"\n" - + " }\n" - + " }, {\n" - + " \"match\": {\n" - + " \"prefix\": \"\"\n" - + " },\n" - + " \"route\": {\n" + " \"cluster\": \"cluster.googleapis.com\"\n" + " }\n" + " }]\n" @@ -3564,13 +3536,6 @@ public void messagePrinter_printRdsResponse() { + " \"prefix\": \"\"\n" + " },\n" + " \"route\": {\n" - + " \"cluster\": \"whatever cluster\"\n" - + " }\n" - + " }, {\n" - + " \"match\": {\n" - + " \"prefix\": \"\"\n" - + " },\n" - + " \"route\": {\n" + " \"cluster\": \"cluster.googleapis.com\"\n" + " }\n" + " }]\n" diff --git a/xds/src/test/java/io/grpc/xds/XdsClientTestHelper.java b/xds/src/test/java/io/grpc/xds/XdsClientTestHelper.java index fff1eabcec4..868fb426379 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientTestHelper.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientTestHelper.java @@ -110,11 +110,6 @@ static VirtualHost buildVirtualHost(List domains, String clusterName) { .setName("virtualhost00.googleapis.com") // don't care .addAllDomains(domains) .addRoutes( - Route.newBuilder() - .setRoute(RouteAction.newBuilder().setCluster("whatever cluster")) - .setMatch(RouteMatch.newBuilder().setPrefix(""))) - .addRoutes( - // Only the last (default) route matters. Route.newBuilder() .setRoute(RouteAction.newBuilder().setCluster(clusterName)) .setMatch(RouteMatch.newBuilder().setPrefix(""))) diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverIntegrationTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverIntegrationTest.java index 26083a0522d..fdacff15e98 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverIntegrationTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverIntegrationTest.java @@ -188,7 +188,6 @@ public BootstrapInfo readBootstrap() { @After public void tearDown() { xdsNameResolver.shutdown(); - XdsClientImpl.enableExperimentalRouting = false; } @Test @@ -352,7 +351,6 @@ public void resolve_cdsLoadBalancing() { @Test @SuppressWarnings("unchecked") public void resolve_xdsRoutingLoadBalancing() { - XdsClientImpl.enableExperimentalRouting = true; xdsNameResolver.start(mockListener); assertThat(responseObservers).hasSize(1); StreamObserver responseObserver = responseObservers.poll(); @@ -453,7 +451,6 @@ public void resolve_xdsRoutingLoadBalancing() { @SuppressWarnings("unchecked") @Test public void resolve_weightedTargetLoadBalancing() { - XdsClientImpl.enableExperimentalRouting = true; xdsNameResolver.start(mockListener); assertThat(responseObservers).hasSize(1); StreamObserver responseObserver = responseObservers.poll(); From 784e804259938cd6fca0494cb730e524f2e99416 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Tue, 28 Jul 2020 08:56:41 -0700 Subject: [PATCH 20/88] xds: refactor usage of Node in Bootstrap to EnvoyProtoData.Node In preparation for xds-v3 support. --- .../main/java/io/grpc/xds/Bootstrapper.java | 69 +--- .../java/io/grpc/xds/EdsLoadBalancer.java | 2 +- .../main/java/io/grpc/xds/EnvoyProtoData.java | 353 +++++++++++++++++- .../java/io/grpc/xds/LoadStatsStoreImpl.java | 2 +- .../main/java/io/grpc/xds/XdsClientImpl.java | 32 +- .../xds/XdsClientWrapperForServerSds.java | 2 +- .../java/io/grpc/xds/XdsNameResolver.java | 2 +- .../sds/ClientSslContextProviderFactory.java | 2 +- .../sds/ServerSslContextProviderFactory.java | 2 +- .../java/io/grpc/xds/BootstrapperTest.java | 53 ++- .../java/io/grpc/xds/EdsLoadBalancerTest.java | 7 +- .../java/io/grpc/xds/EnvoyProtoDataTest.java | 97 ++++- .../io/grpc/xds/LoadStatsStoreImplTest.java | 2 +- .../java/io/grpc/xds/XdsClientImplTest.java | 8 +- .../xds/XdsClientImplTestForListener.java | 36 +- .../java/io/grpc/xds/XdsClientTestHelper.java | 4 +- .../xds/XdsNameResolverIntegrationTest.java | 2 +- 17 files changed, 520 insertions(+), 155 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/Bootstrapper.java b/xds/src/main/java/io/grpc/xds/Bootstrapper.java index cceb900c027..f967d43d8fd 100644 --- a/xds/src/main/java/io/grpc/xds/Bootstrapper.java +++ b/xds/src/main/java/io/grpc/xds/Bootstrapper.java @@ -17,17 +17,13 @@ package io.grpc.xds; import com.google.common.annotations.VisibleForTesting; -import com.google.protobuf.ListValue; -import com.google.protobuf.NullValue; -import com.google.protobuf.Struct; -import com.google.protobuf.Value; -import io.envoyproxy.envoy.api.v2.core.Locality; -import io.envoyproxy.envoy.api.v2.core.Node; import io.grpc.Internal; import io.grpc.internal.GrpcUtil; import io.grpc.internal.GrpcUtil.GrpcBuildVersion; import io.grpc.internal.JsonParser; import io.grpc.internal.JsonUtil; +import io.grpc.xds.EnvoyProtoData.Locality; +import io.grpc.xds.EnvoyProtoData.Node; import io.grpc.xds.XdsLogger.XdsLogLevel; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -133,34 +129,24 @@ static BootstrapInfo parseConfig(String rawData) throws IOException { } Map metadata = JsonUtil.getObject(rawNode, "metadata"); if (metadata != null) { - Struct.Builder structBuilder = Struct.newBuilder(); - for (Map.Entry entry : metadata.entrySet()) { - logger.log( - XdsLogLevel.INFO, - "Node metadata field {0}: {1}", entry.getKey(), entry.getValue()); - structBuilder.putFields(entry.getKey(), convertToValue(entry.getValue())); - } - nodeBuilder.setMetadata(structBuilder); + nodeBuilder.setMetadata(metadata); } Map rawLocality = JsonUtil.getObject(rawNode, "locality"); if (rawLocality != null) { - Locality.Builder localityBuilder = Locality.newBuilder(); - if (rawLocality.containsKey("region")) { - String region = JsonUtil.getString(rawLocality, "region"); + String region = JsonUtil.getString(rawLocality, "region"); + String zone = JsonUtil.getString(rawLocality, "zone"); + String subZone = JsonUtil.getString(rawLocality, "sub_zone"); + if (region != null) { logger.log(XdsLogLevel.INFO, "Locality region: {0}", region); - localityBuilder.setRegion(region); } if (rawLocality.containsKey("zone")) { - String zone = JsonUtil.getString(rawLocality, "zone"); logger.log(XdsLogLevel.INFO, "Locality zone: {0}", zone); - localityBuilder.setZone(zone); } if (rawLocality.containsKey("sub_zone")) { - String subZone = JsonUtil.getString(rawLocality, "sub_zone"); logger.log(XdsLogLevel.INFO, "Locality sub_zone: {0}", subZone); - localityBuilder.setSubZone(subZone); } - nodeBuilder.setLocality(localityBuilder); + Locality locality = new Locality(region, zone, subZone); + nodeBuilder.setLocality(locality); } } GrpcBuildVersion buildVersion = GrpcUtil.getGrpcBuildVersion(); @@ -173,43 +159,6 @@ static BootstrapInfo parseConfig(String rawData) throws IOException { return new BootstrapInfo(servers, nodeBuilder.build()); } - /** - * Converts Java representation of the given JSON value to protobuf's {@link - * com.google.protobuf.Value} representation. - * - *

The given {@code rawObject} must be a valid JSON value in Java representation, which is - * either a {@code Map}, {@code List}, {@code String}, {@code Double}, - * {@code Boolean}, or {@code null}. - */ - private static Value convertToValue(Object rawObject) { - Value.Builder valueBuilder = Value.newBuilder(); - if (rawObject == null) { - valueBuilder.setNullValue(NullValue.NULL_VALUE); - } else if (rawObject instanceof Double) { - valueBuilder.setNumberValue((Double) rawObject); - } else if (rawObject instanceof String) { - valueBuilder.setStringValue((String) rawObject); - } else if (rawObject instanceof Boolean) { - valueBuilder.setBoolValue((Boolean) rawObject); - } else if (rawObject instanceof Map) { - Struct.Builder structBuilder = Struct.newBuilder(); - @SuppressWarnings("unchecked") - Map map = (Map) rawObject; - for (Map.Entry entry : map.entrySet()) { - structBuilder.putFields(entry.getKey(), convertToValue(entry.getValue())); - } - valueBuilder.setStructValue(structBuilder); - } else if (rawObject instanceof List) { - ListValue.Builder listBuilder = ListValue.newBuilder(); - List list = (List) rawObject; - for (Object obj : list) { - listBuilder.addValues(convertToValue(obj)); - } - valueBuilder.setListValue(listBuilder); - } - return valueBuilder.build(); - } - /** * Data class containing channel credentials configurations for xDS protocol communication. */ diff --git a/xds/src/main/java/io/grpc/xds/EdsLoadBalancer.java b/xds/src/main/java/io/grpc/xds/EdsLoadBalancer.java index 34e90c7c7a1..5c5c1889fca 100644 --- a/xds/src/main/java/io/grpc/xds/EdsLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/EdsLoadBalancer.java @@ -22,7 +22,6 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import io.envoyproxy.envoy.api.v2.core.Node; import io.grpc.Attributes; import io.grpc.InternalLogId; import io.grpc.LoadBalancer; @@ -38,6 +37,7 @@ import io.grpc.xds.EnvoyProtoData.DropOverload; import io.grpc.xds.EnvoyProtoData.Locality; import io.grpc.xds.EnvoyProtoData.LocalityLbEndpoints; +import io.grpc.xds.EnvoyProtoData.Node; import io.grpc.xds.LocalityStore.LocalityStoreFactory; import io.grpc.xds.XdsClient.EndpointUpdate; import io.grpc.xds.XdsClient.EndpointWatcher; diff --git a/xds/src/main/java/io/grpc/xds/EnvoyProtoData.java b/xds/src/main/java/io/grpc/xds/EnvoyProtoData.java index be691337f22..eeae2f96174 100644 --- a/xds/src/main/java/io/grpc/xds/EnvoyProtoData.java +++ b/xds/src/main/java/io/grpc/xds/EnvoyProtoData.java @@ -22,6 +22,10 @@ import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects.ToStringHelper; import com.google.common.collect.ImmutableList; +import com.google.protobuf.ListValue; +import com.google.protobuf.NullValue; +import com.google.protobuf.Struct; +import com.google.protobuf.Value; import com.google.re2j.Pattern; import com.google.re2j.PatternSyntaxException; import io.envoyproxy.envoy.type.v3.FractionalPercent; @@ -34,6 +38,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Objects; import javax.annotation.Nullable; @@ -135,17 +140,345 @@ public String toString() { } /** - * See corresponding Envoy proto message {@link io.envoyproxy.envoy.api.v2.core.Locality}. + * See corresponding Envoy proto message {@link io.envoyproxy.envoy.config.core.v3.Node}. + */ + public static final class Node { + + private final String id; + private final String cluster; + @Nullable + private final Map metadata; + @Nullable + private final Locality locality; + private final List

listeningAddresses; + private final String buildVersion; + private final String userAgentName; + @Nullable + private final String userAgentVersion; + private final List clientFeatures; + + private Node( + String id, String cluster, @Nullable Map metadata, @Nullable Locality locality, + List
listeningAddresses, String buildVersion, String userAgentName, + @Nullable String userAgentVersion, List clientFeatures) { + this.id = checkNotNull(id, "id"); + this.cluster = checkNotNull(cluster, "cluster"); + this.metadata = metadata; + this.locality = locality; + this.listeningAddresses = Collections.unmodifiableList( + checkNotNull(listeningAddresses, "listeningAddresses")); + this.buildVersion = checkNotNull(buildVersion, "buildVersion"); + this.userAgentName = checkNotNull(userAgentName, "userAgentName"); + this.userAgentVersion = userAgentVersion; + this.clientFeatures = Collections.unmodifiableList( + checkNotNull(clientFeatures, "clientFeatures")); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("id", id) + .add("cluster", cluster) + .add("metadata", metadata) + .add("locality", locality) + .add("listeningAddresses", listeningAddresses) + .add("buildVersion", buildVersion) + .add("userAgentName", userAgentName) + .add("userAgentVersion", userAgentVersion) + .add("clientFeatures", clientFeatures) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Node node = (Node) o; + return Objects.equals(id, node.id) + && Objects.equals(cluster, node.cluster) + && Objects.equals(metadata, node.metadata) + && Objects.equals(locality, node.locality) + && Objects.equals(listeningAddresses, node.listeningAddresses) + && Objects.equals(buildVersion, node.buildVersion) + && Objects.equals(userAgentName, node.userAgentName) + && Objects.equals(userAgentVersion, node.userAgentVersion) + && Objects.equals(clientFeatures, node.clientFeatures); + } + + @Override + public int hashCode() { + return Objects + .hash(id, cluster, metadata, locality, listeningAddresses, buildVersion, userAgentName, + userAgentVersion, clientFeatures); + } + + static final class Builder { + private String id = ""; + private String cluster = ""; + @Nullable + private Map metadata; + @Nullable + private Locality locality; + private final List
listeningAddresses = new ArrayList<>(); + private String buildVersion = ""; + private String userAgentName = ""; + @Nullable + private String userAgentVersion; + private final List clientFeatures = new ArrayList<>(); + + private Builder() { + } + + Builder setId(String id) { + this.id = checkNotNull(id, "id"); + return this; + } + + Builder setCluster(String cluster) { + this.cluster = checkNotNull(cluster, "cluster"); + return this; + } + + Builder setMetadata(Map metadata) { + this.metadata = checkNotNull(metadata, "metadata"); + return this; + } + + Builder setLocality(Locality locality) { + this.locality = checkNotNull(locality, "locality"); + return this; + } + + Builder addListeningAddresses(Address address) { + listeningAddresses.add(checkNotNull(address, "address")); + return this; + } + + Builder setBuildVersion(String buildVersion) { + this.buildVersion = checkNotNull(buildVersion, "buildVersion"); + return this; + } + + Builder setUserAgentName(String userAgentName) { + this.userAgentName = checkNotNull(userAgentName, "userAgentName"); + return this; + } + + Builder setUserAgentVersion(String userAgentVersion) { + this.userAgentVersion = checkNotNull(userAgentVersion, "userAgentVersion"); + return this; + } + + Builder addClientFeatures(String clientFeature) { + this.clientFeatures.add(checkNotNull(clientFeature, "clientFeature")); + return this; + } + + Node build() { + return new Node( + id, cluster, metadata, locality, listeningAddresses, buildVersion, userAgentName, + userAgentVersion, clientFeatures); + } + } + + static Builder newBuilder() { + return new Builder(); + } + + Builder toBuilder() { + Builder builder = new Builder().setId(id).setCluster(cluster); + if (metadata != null) { + builder.setMetadata(metadata); + } + if (locality != null) { + builder.setLocality(locality); + } + builder.listeningAddresses.addAll(listeningAddresses); + return builder; + } + + String getId() { + return id; + } + + String getCluster() { + return cluster; + } + + @Nullable + Map getMetadata() { + return metadata; + } + + @Nullable + Locality getLocality() { + return locality; + } + + List
getListeningAddresses() { + return listeningAddresses; + } + + io.envoyproxy.envoy.config.core.v3.Node toEnvoyProtoNode() { + io.envoyproxy.envoy.config.core.v3.Node.Builder builder = + io.envoyproxy.envoy.config.core.v3.Node.newBuilder(); + builder.setId(id); + builder.setCluster(cluster); + if (metadata != null) { + Struct.Builder structBuilder = Struct.newBuilder(); + for (Map.Entry entry : metadata.entrySet()) { + structBuilder.putFields(entry.getKey(), convertToValue(entry.getValue())); + } + builder.setMetadata(structBuilder); + } + if (locality != null) { + builder.setLocality(locality.toEnvoyProtoLocality()); + } + for (Address address : listeningAddresses) { + builder.addListeningAddresses(address.toEnvoyProtoAddress()); + } + builder.setUserAgentName(userAgentName); + if (userAgentVersion != null) { + builder.setUserAgentVersion(userAgentVersion); + } + builder.addAllClientFeatures(clientFeatures); + return builder.build(); + } + + @SuppressWarnings("deprecation") // Deprecated v2 API setBuildVersion(). + public io.envoyproxy.envoy.api.v2.core.Node toEnvoyProtoNodeV2() { + io.envoyproxy.envoy.api.v2.core.Node.Builder builder = + io.envoyproxy.envoy.api.v2.core.Node.newBuilder(); + builder.setId(id); + builder.setCluster(cluster); + if (metadata != null) { + Struct.Builder structBuilder = Struct.newBuilder(); + for (Map.Entry entry : metadata.entrySet()) { + structBuilder.putFields(entry.getKey(), convertToValue(entry.getValue())); + } + builder.setMetadata(structBuilder); + } + if (locality != null) { + builder.setLocality(locality.toEnvoyProtoLocalityV2()); + } + for (Address address : listeningAddresses) { + builder.addListeningAddresses(address.toEnvoyProtoAddressV2()); + } + builder.setBuildVersion(buildVersion); + builder.setUserAgentName(userAgentName); + if (userAgentVersion != null) { + builder.setUserAgentVersion(userAgentVersion); + } + builder.addAllClientFeatures(clientFeatures); + return builder.build(); + } + } + + /** + * Converts Java representation of the given JSON value to protobuf's {@link + * com.google.protobuf.Value} representation. + * + *

The given {@code rawObject} must be a valid JSON value in Java representation, which is + * either a {@code Map}, {@code List}, {@code String}, {@code Double}, {@code + * Boolean}, or {@code null}. + */ + private static Value convertToValue(Object rawObject) { + Value.Builder valueBuilder = Value.newBuilder(); + if (rawObject == null) { + valueBuilder.setNullValue(NullValue.NULL_VALUE); + } else if (rawObject instanceof Double) { + valueBuilder.setNumberValue((Double) rawObject); + } else if (rawObject instanceof String) { + valueBuilder.setStringValue((String) rawObject); + } else if (rawObject instanceof Boolean) { + valueBuilder.setBoolValue((Boolean) rawObject); + } else if (rawObject instanceof Map) { + Struct.Builder structBuilder = Struct.newBuilder(); + @SuppressWarnings("unchecked") + Map map = (Map) rawObject; + for (Map.Entry entry : map.entrySet()) { + structBuilder.putFields(entry.getKey(), convertToValue(entry.getValue())); + } + valueBuilder.setStructValue(structBuilder); + } else if (rawObject instanceof List) { + ListValue.Builder listBuilder = ListValue.newBuilder(); + List list = (List) rawObject; + for (Object obj : list) { + listBuilder.addValues(convertToValue(obj)); + } + valueBuilder.setListValue(listBuilder); + } + return valueBuilder.build(); + } + + /** + * See corresponding Envoy proto message {@link io.envoyproxy.envoy.config.core.v3.Address}. + */ + static final class Address { + private final String address; + private final int port; + + Address(String address, int port) { + this.address = checkNotNull(address, "address"); + this.port = port; + } + + io.envoyproxy.envoy.config.core.v3.Address toEnvoyProtoAddress() { + return + io.envoyproxy.envoy.config.core.v3.Address.newBuilder().setSocketAddress( + io.envoyproxy.envoy.config.core.v3.SocketAddress.newBuilder().setAddress(address) + .setPortValue(port)).build(); + } + + io.envoyproxy.envoy.api.v2.core.Address toEnvoyProtoAddressV2() { + return + io.envoyproxy.envoy.api.v2.core.Address.newBuilder().setSocketAddress( + io.envoyproxy.envoy.api.v2.core.SocketAddress.newBuilder().setAddress(address) + .setPortValue(port)).build(); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("address", address) + .add("port", port) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Address address1 = (Address) o; + return port == address1.port && Objects.equals(address, address1.address); + } + + @Override + public int hashCode() { + return Objects.hash(address, port); + } + } + + /** + * See corresponding Envoy proto message {@link io.envoyproxy.envoy.config.core.v3.Locality}. */ static final class Locality { private final String region; private final String zone; private final String subZone; - Locality(String region, String zone, String subZone) { - this.region = region; - this.zone = zone; - this.subZone = subZone; + Locality(@Nullable String region, @Nullable String zone, @Nullable String subZone) { + this.region = region == null ? "" : region; + this.zone = zone == null ? "" : zone; + this.subZone = subZone == null ? "" : subZone; } static Locality fromEnvoyProtoLocality(io.envoyproxy.envoy.config.core.v3.Locality locality) { @@ -163,7 +496,15 @@ static Locality fromEnvoyProtoLocalityV2(io.envoyproxy.envoy.api.v2.core.Localit /* subZone = */ locality.getSubZone()); } - io.envoyproxy.envoy.api.v2.core.Locality toEnvoyProtoLocality() { + io.envoyproxy.envoy.config.core.v3.Locality toEnvoyProtoLocality() { + return io.envoyproxy.envoy.config.core.v3.Locality.newBuilder() + .setRegion(region) + .setZone(zone) + .setSubZone(subZone) + .build(); + } + + io.envoyproxy.envoy.api.v2.core.Locality toEnvoyProtoLocalityV2() { return io.envoyproxy.envoy.api.v2.core.Locality.newBuilder() .setRegion(region) .setZone(zone) diff --git a/xds/src/main/java/io/grpc/xds/LoadStatsStoreImpl.java b/xds/src/main/java/io/grpc/xds/LoadStatsStoreImpl.java index 37ff09d91be..42ba84e8508 100644 --- a/xds/src/main/java/io/grpc/xds/LoadStatsStoreImpl.java +++ b/xds/src/main/java/io/grpc/xds/LoadStatsStoreImpl.java @@ -74,7 +74,7 @@ public ClusterStats generateLoadReport() { for (Map.Entry entry : localityLoadCounters.entrySet()) { ClientLoadSnapshot snapshot = entry.getValue().snapshot(); UpstreamLocalityStats.Builder localityStatsBuilder = - UpstreamLocalityStats.newBuilder().setLocality(entry.getKey().toEnvoyProtoLocality()); + UpstreamLocalityStats.newBuilder().setLocality(entry.getKey().toEnvoyProtoLocalityV2()); localityStatsBuilder .setTotalSuccessfulRequests(snapshot.getCallsSucceeded()) .setTotalErrorRequests(snapshot.getCallsFailed()) diff --git a/xds/src/main/java/io/grpc/xds/XdsClientImpl.java b/xds/src/main/java/io/grpc/xds/XdsClientImpl.java index e8bd49906b3..ddaec29abee 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClientImpl.java +++ b/xds/src/main/java/io/grpc/xds/XdsClientImpl.java @@ -27,14 +27,10 @@ import com.google.protobuf.Any; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.MessageOrBuilder; -import com.google.protobuf.Struct; -import com.google.protobuf.Value; import com.google.protobuf.util.JsonFormat; import com.google.rpc.Code; import io.envoyproxy.envoy.api.v2.DiscoveryRequest; import io.envoyproxy.envoy.api.v2.DiscoveryResponse; -import io.envoyproxy.envoy.api.v2.core.Node; -import io.envoyproxy.envoy.api.v2.core.SocketAddress; import io.envoyproxy.envoy.config.cluster.v3.Cluster; import io.envoyproxy.envoy.config.cluster.v3.Cluster.DiscoveryType; import io.envoyproxy.envoy.config.cluster.v3.Cluster.EdsClusterConfig; @@ -62,6 +58,7 @@ import io.grpc.xds.EnvoyProtoData.DropOverload; import io.grpc.xds.EnvoyProtoData.Locality; import io.grpc.xds.EnvoyProtoData.LocalityLbEndpoints; +import io.grpc.xds.EnvoyProtoData.Node; import io.grpc.xds.EnvoyProtoData.StructOrError; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.LoadReportClient.LoadReportCallback; @@ -458,18 +455,13 @@ void watchListenerData(int port, ListenerWatcher watcher) { /** In case of Listener watcher metadata to be updated to include port. */ private void updateNodeMetadataForListenerRequest(int port) { - Struct newMetadata = node.getMetadata().toBuilder() - .putFields("TRAFFICDIRECTOR_PROXYLESS", - Value.newBuilder().setStringValue("1").build()) - .build(); - io.envoyproxy.envoy.api.v2.core.Address listeningAddress = - io.envoyproxy.envoy.api.v2.core.Address.newBuilder() - .setSocketAddress( - SocketAddress.newBuilder() - .setAddress("0.0.0.0") - .setPortValue(port) - .build()) - .build(); + Map newMetadata = new HashMap<>(); + if (node.getMetadata() != null) { + newMetadata.putAll(node.getMetadata()); + } + newMetadata.put("TRAFFICDIRECTOR_PROXYLESS", "1"); + EnvoyProtoData.Address listeningAddress = + new EnvoyProtoData.Address("0.0.0.0", port); node = node.toBuilder().setMetadata(newMetadata).addListeningAddresses(listeningAddress).build(); } @@ -483,7 +475,7 @@ void reportClientStats( logId, targetName, channel, - node, + node.toEnvoyProtoNodeV2(), syncContext, timeService, backoffPolicyProvider, @@ -1480,7 +1472,7 @@ private void sendXdsRequest(String typeUrl, Collection resourceNames) { DiscoveryRequest .newBuilder() .setVersionInfo(version) - .setNode(node) + .setNode(node.toEnvoyProtoNodeV2()) .addAllResourceNames(resourceNames) .setTypeUrl(typeUrl) .setResponseNonce(nonce) @@ -1514,7 +1506,7 @@ private void sendAckRequest(String typeUrl, Collection resourceNames, DiscoveryRequest .newBuilder() .setVersionInfo(versionInfo) - .setNode(node) + .setNode(node.toEnvoyProtoNodeV2()) .addAllResourceNames(resourceNames) .setTypeUrl(typeUrl) .setResponseNonce(nonce) @@ -1561,7 +1553,7 @@ private void sendNackRequest(String typeUrl, Collection resourceNames, DiscoveryRequest .newBuilder() .setVersionInfo(versionInfo) - .setNode(node) + .setNode(node.toEnvoyProtoNodeV2()) .addAllResourceNames(resourceNames) .setTypeUrl(typeUrl) .setResponseNonce(nonce) diff --git a/xds/src/main/java/io/grpc/xds/XdsClientWrapperForServerSds.java b/xds/src/main/java/io/grpc/xds/XdsClientWrapperForServerSds.java index 79873e9dfa0..95e7cb9e06f 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClientWrapperForServerSds.java +++ b/xds/src/main/java/io/grpc/xds/XdsClientWrapperForServerSds.java @@ -20,7 +20,6 @@ import static com.google.common.base.Preconditions.checkState; import com.google.common.annotations.VisibleForTesting; -import io.envoyproxy.envoy.api.v2.core.Node; import io.grpc.Internal; import io.grpc.InternalLogId; import io.grpc.Status; @@ -28,6 +27,7 @@ import io.grpc.internal.ExponentialBackoffPolicy; import io.grpc.internal.GrpcUtil; import io.grpc.internal.SharedResourceHolder; +import io.grpc.xds.EnvoyProtoData.Node; import io.grpc.xds.EnvoyServerProtoData.CidrRange; import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; import io.grpc.xds.EnvoyServerProtoData.FilterChain; diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java index 47946949b49..e0c03e5f907 100644 --- a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java +++ b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java @@ -26,7 +26,6 @@ import com.google.common.collect.Iterables; import com.google.gson.Gson; import com.google.re2j.Pattern; -import io.envoyproxy.envoy.api.v2.core.Node; import io.grpc.Attributes; import io.grpc.EquivalentAddressGroup; import io.grpc.InternalLogId; @@ -39,6 +38,7 @@ import io.grpc.xds.Bootstrapper.BootstrapInfo; import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.EnvoyProtoData.ClusterWeight; +import io.grpc.xds.EnvoyProtoData.Node; import io.grpc.xds.EnvoyProtoData.Route; import io.grpc.xds.EnvoyProtoData.RouteAction; import io.grpc.xds.RouteMatch.FractionMatcher; diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/ClientSslContextProviderFactory.java b/xds/src/main/java/io/grpc/xds/internal/sds/ClientSslContextProviderFactory.java index bb6a636e969..84b2f8284aa 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/ClientSslContextProviderFactory.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/ClientSslContextProviderFactory.java @@ -43,7 +43,7 @@ public SslContextProvider create(UpstreamTlsContext upstreamTlsContext) { try { return SdsClientSslContextProvider.getProvider( upstreamTlsContext, - Bootstrapper.getInstance().readBootstrap().getNode(), + Bootstrapper.getInstance().readBootstrap().getNode().toEnvoyProtoNodeV2(), Executors.newSingleThreadExecutor(new ThreadFactoryBuilder() .setNameFormat("client-sds-sslcontext-provider-%d") .setDaemon(true) diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/ServerSslContextProviderFactory.java b/xds/src/main/java/io/grpc/xds/internal/sds/ServerSslContextProviderFactory.java index fb271a32dc7..1cd2cfa8e9b 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/ServerSslContextProviderFactory.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/ServerSslContextProviderFactory.java @@ -45,7 +45,7 @@ public SslContextProvider create( try { return SdsServerSslContextProvider.getProvider( downstreamTlsContext, - Bootstrapper.getInstance().readBootstrap().getNode(), + Bootstrapper.getInstance().readBootstrap().getNode().toEnvoyProtoNodeV2(), Executors.newSingleThreadExecutor(new ThreadFactoryBuilder() .setNameFormat("server-sds-sslcontext-provider-%d") .setDaemon(true) diff --git a/xds/src/test/java/io/grpc/xds/BootstrapperTest.java b/xds/src/test/java/io/grpc/xds/BootstrapperTest.java index 37ef4161259..10d2c109167 100644 --- a/xds/src/test/java/io/grpc/xds/BootstrapperTest.java +++ b/xds/src/test/java/io/grpc/xds/BootstrapperTest.java @@ -18,15 +18,14 @@ import static com.google.common.truth.Truth.assertThat; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; -import com.google.protobuf.Struct; -import com.google.protobuf.Value; -import io.envoyproxy.envoy.api.v2.core.Locality; -import io.envoyproxy.envoy.api.v2.core.Node; import io.grpc.internal.GrpcUtil; import io.grpc.internal.GrpcUtil.GrpcBuildVersion; import io.grpc.xds.Bootstrapper.BootstrapInfo; import io.grpc.xds.Bootstrapper.ServerInfo; +import io.grpc.xds.EnvoyProtoData.Locality; +import io.grpc.xds.EnvoyProtoData.Node; import java.io.IOException; import java.util.List; import org.junit.Rule; @@ -82,16 +81,13 @@ public void parseBootstrap_validData_singleXdsServer() throws IOException { getNodeBuilder() .setId("ENVOY_NODE_ID") .setCluster("ENVOY_CLUSTER") - .setLocality( - Locality.newBuilder() - .setRegion("ENVOY_REGION").setZone("ENVOY_ZONE").setSubZone("ENVOY_SUBZONE")) + .setLocality(new Locality("ENVOY_REGION", "ENVOY_ZONE", "ENVOY_SUBZONE")) .setMetadata( - Struct.newBuilder() - .putFields("TRAFFICDIRECTOR_INTERCEPTION_PORT", - Value.newBuilder().setStringValue("ENVOY_PORT").build()) - .putFields("TRAFFICDIRECTOR_NETWORK_NAME", - Value.newBuilder().setStringValue("VPC_NETWORK_NAME").build()) - .build()) + ImmutableMap.of( + "TRAFFICDIRECTOR_INTERCEPTION_PORT", + "ENVOY_PORT", + "TRAFFICDIRECTOR_NETWORK_NAME", + "VPC_NETWORK_NAME")) .build()); } @@ -145,16 +141,13 @@ public void parseBootstrap_validData_multipleXdsServers() throws IOException { getNodeBuilder() .setId("ENVOY_NODE_ID") .setCluster("ENVOY_CLUSTER") - .setLocality( - Locality.newBuilder() - .setRegion("ENVOY_REGION").setZone("ENVOY_ZONE").setSubZone("ENVOY_SUBZONE")) + .setLocality(new Locality("ENVOY_REGION", "ENVOY_ZONE", "ENVOY_SUBZONE")) .setMetadata( - Struct.newBuilder() - .putFields("TRAFFICDIRECTOR_INTERCEPTION_PORT", - Value.newBuilder().setStringValue("ENVOY_PORT").build()) - .putFields("TRAFFICDIRECTOR_NETWORK_NAME", - Value.newBuilder().setStringValue("VPC_NETWORK_NAME").build()) - .build()) + ImmutableMap.of( + "TRAFFICDIRECTOR_INTERCEPTION_PORT", + "ENVOY_PORT", + "TRAFFICDIRECTOR_NETWORK_NAME", + "VPC_NETWORK_NAME")) .build()); } @@ -201,16 +194,13 @@ public void parseBootstrap_IgnoreIrrelevantFields() throws IOException { getNodeBuilder() .setId("ENVOY_NODE_ID") .setCluster("ENVOY_CLUSTER") - .setLocality( - Locality.newBuilder() - .setRegion("ENVOY_REGION").setZone("ENVOY_ZONE").setSubZone("ENVOY_SUBZONE")) + .setLocality(new Locality("ENVOY_REGION", "ENVOY_ZONE", "ENVOY_SUBZONE")) .setMetadata( - Struct.newBuilder() - .putFields("TRAFFICDIRECTOR_INTERCEPTION_PORT", - Value.newBuilder().setStringValue("ENVOY_PORT").build()) - .putFields("TRAFFICDIRECTOR_NETWORK_NAME", - Value.newBuilder().setStringValue("VPC_NETWORK_NAME").build()) - .build()) + ImmutableMap.of( + "TRAFFICDIRECTOR_INTERCEPTION_PORT", + "ENVOY_PORT", + "TRAFFICDIRECTOR_NETWORK_NAME", + "VPC_NETWORK_NAME")) .build()); } @@ -304,7 +294,6 @@ public void parseBootstrap_serverWithoutServerUri() throws IOException { Bootstrapper.parseConfig(rawData); } - @SuppressWarnings("deprecation") private static Node.Builder getNodeBuilder() { GrpcBuildVersion buildVersion = GrpcUtil.getGrpcBuildVersion(); return diff --git a/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java index ff9101b5a60..66ce45c3ff4 100644 --- a/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java @@ -43,7 +43,6 @@ import io.envoyproxy.envoy.api.v2.ClusterLoadAssignment.Policy.DropOverload; import io.envoyproxy.envoy.api.v2.DiscoveryRequest; import io.envoyproxy.envoy.api.v2.DiscoveryResponse; -import io.envoyproxy.envoy.api.v2.core.Node; import io.envoyproxy.envoy.api.v2.endpoint.LbEndpoint; import io.envoyproxy.envoy.api.v2.endpoint.LocalityLbEndpoints; import io.envoyproxy.envoy.service.discovery.v2.AggregatedDiscoveryServiceGrpc.AggregatedDiscoveryServiceImplBase; @@ -76,6 +75,7 @@ import io.grpc.xds.Bootstrapper.ChannelCreds; import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.EdsLoadBalancerProvider.EdsConfig; +import io.grpc.xds.EnvoyProtoData.Node; import io.grpc.xds.LocalityStore.LocalityStoreFactory; import io.grpc.xds.XdsClient.EndpointUpdate; import io.grpc.xds.XdsClient.XdsChannelFactory; @@ -231,7 +231,8 @@ public StreamObserver streamAggregatedResources( final List serverList = ImmutableList.of( new ServerInfo("trafficdirector.googleapis.com", ImmutableList.of())); - BootstrapInfo bootstrapInfo = new BootstrapInfo(serverList, Node.getDefaultInstance()); + Node node = Node.newBuilder().build(); + BootstrapInfo bootstrapInfo = new BootstrapInfo(serverList, node); doReturn(bootstrapInfo).when(bootstrapper).readBootstrap(); if (isFullFlow) { @@ -240,7 +241,7 @@ public StreamObserver streamAggregatedResources( SERVICE_AUTHORITY, serverList, channelFactory, - Node.getDefaultInstance(), + node, syncContext, fakeClock.getScheduledExecutorService(), mock(BackoffPolicy.Provider.class), diff --git a/xds/src/test/java/io/grpc/xds/EnvoyProtoDataTest.java b/xds/src/test/java/io/grpc/xds/EnvoyProtoDataTest.java index 7e633d55bd5..fa278fb8147 100644 --- a/xds/src/test/java/io/grpc/xds/EnvoyProtoDataTest.java +++ b/xds/src/test/java/io/grpc/xds/EnvoyProtoDataTest.java @@ -18,9 +18,12 @@ import static com.google.common.truth.Truth.assertThat; +import com.google.common.collect.ImmutableMap; import com.google.common.testing.EqualsTester; import com.google.protobuf.BoolValue; +import com.google.protobuf.Struct; import com.google.protobuf.UInt32Value; +import com.google.protobuf.Value; import com.google.re2j.Pattern; import io.envoyproxy.envoy.config.core.v3.RuntimeFractionalPercent; import io.envoyproxy.envoy.config.route.v3.QueryParameterMatcher; @@ -29,8 +32,10 @@ import io.envoyproxy.envoy.type.matcher.v3.RegexMatcher; import io.envoyproxy.envoy.type.v3.FractionalPercent; import io.envoyproxy.envoy.type.v3.Int64Range; +import io.grpc.xds.EnvoyProtoData.Address; import io.grpc.xds.EnvoyProtoData.ClusterWeight; import io.grpc.xds.EnvoyProtoData.Locality; +import io.grpc.xds.EnvoyProtoData.Node; import io.grpc.xds.EnvoyProtoData.Route; import io.grpc.xds.EnvoyProtoData.RouteAction; import io.grpc.xds.EnvoyProtoData.StructOrError; @@ -63,7 +68,8 @@ public void locality_convertToAndFromLocalityProto() { assertThat(xdsLocality.getZone()).isEqualTo("test_zone"); assertThat(xdsLocality.getSubZone()).isEqualTo("test_subzone"); - io.envoyproxy.envoy.api.v2.core.Locality convertedLocality = xdsLocality.toEnvoyProtoLocality(); + io.envoyproxy.envoy.api.v2.core.Locality convertedLocality = + xdsLocality.toEnvoyProtoLocalityV2(); assertThat(convertedLocality.getRegion()).isEqualTo("test_region"); assertThat(convertedLocality.getZone()).isEqualTo("test_zone"); assertThat(convertedLocality.getSubZone()).isEqualTo("test_subzone"); @@ -84,6 +90,95 @@ public void locality_equal() { .testEquals(); } + @Test + public void convertNode() { + Node node = Node.newBuilder() + .setId("node-id") + .setCluster("cluster") + .setMetadata( + ImmutableMap.of( + "TRAFFICDIRECTOR_INTERCEPTION_PORT", + "ENVOY_PORT", + "TRAFFICDIRECTOR_NETWORK_NAME", + "VPC_NETWORK_NAME")) + .setLocality(new Locality("region", "zone", "subzone")) + .addListeningAddresses(new Address("www.foo.com", 8080)) + .addListeningAddresses(new Address("www.bar.com", 8088)) + .setBuildVersion("v1") + .setUserAgentName("agent") + .setUserAgentVersion("1.1") + .addClientFeatures("feature-1") + .addClientFeatures("feature-2") + .build(); + io.envoyproxy.envoy.config.core.v3.Node nodeProto = + io.envoyproxy.envoy.config.core.v3.Node.newBuilder() + .setId("node-id") + .setCluster("cluster") + .setMetadata(Struct.newBuilder() + .putFields("TRAFFICDIRECTOR_INTERCEPTION_PORT", + Value.newBuilder().setStringValue("ENVOY_PORT").build()) + .putFields("TRAFFICDIRECTOR_NETWORK_NAME", + Value.newBuilder().setStringValue("VPC_NETWORK_NAME").build())) + .setLocality( + io.envoyproxy.envoy.config.core.v3.Locality.newBuilder() + .setRegion("region") + .setZone("zone") + .setSubZone("subzone")) + .addListeningAddresses( + io.envoyproxy.envoy.config.core.v3.Address.newBuilder() + .setSocketAddress( + io.envoyproxy.envoy.config.core.v3.SocketAddress.newBuilder() + .setAddress("www.foo.com") + .setPortValue(8080))) + .addListeningAddresses( + io.envoyproxy.envoy.config.core.v3.Address.newBuilder() + .setSocketAddress( + io.envoyproxy.envoy.config.core.v3.SocketAddress.newBuilder() + .setAddress("www.bar.com") + .setPortValue(8088))) + .setUserAgentName("agent") + .setUserAgentVersion("1.1") + .addClientFeatures("feature-1") + .addClientFeatures("feature-2") + .build(); + assertThat(node.toEnvoyProtoNode()).isEqualTo(nodeProto); + + @SuppressWarnings("deprecation") // Deprecated v2 API setBuildVersion(). + io.envoyproxy.envoy.api.v2.core.Node nodeProtoV2 = + io.envoyproxy.envoy.api.v2.core.Node.newBuilder() + .setId("node-id") + .setCluster("cluster") + .setMetadata(Struct.newBuilder() + .putFields("TRAFFICDIRECTOR_INTERCEPTION_PORT", + Value.newBuilder().setStringValue("ENVOY_PORT").build()) + .putFields("TRAFFICDIRECTOR_NETWORK_NAME", + Value.newBuilder().setStringValue("VPC_NETWORK_NAME").build())) + .setLocality( + io.envoyproxy.envoy.api.v2.core.Locality.newBuilder() + .setRegion("region") + .setZone("zone") + .setSubZone("subzone")) + .addListeningAddresses( + io.envoyproxy.envoy.api.v2.core.Address.newBuilder() + .setSocketAddress( + io.envoyproxy.envoy.api.v2.core.SocketAddress.newBuilder() + .setAddress("www.foo.com") + .setPortValue(8080))) + .addListeningAddresses( + io.envoyproxy.envoy.api.v2.core.Address.newBuilder() + .setSocketAddress( + io.envoyproxy.envoy.api.v2.core.SocketAddress.newBuilder() + .setAddress("www.bar.com") + .setPortValue(8088))) + .setBuildVersion("v1") + .setUserAgentName("agent") + .setUserAgentVersion("1.1") + .addClientFeatures("feature-1") + .addClientFeatures("feature-2") + .build(); + assertThat(node.toEnvoyProtoNodeV2()).isEqualTo(nodeProtoV2); + } + @Test public void locality_hash() { assertThat(new Locality("region", "zone", "subzone").hashCode()) diff --git a/xds/src/test/java/io/grpc/xds/LoadStatsStoreImplTest.java b/xds/src/test/java/io/grpc/xds/LoadStatsStoreImplTest.java index d58fd4bb92e..96b96b67684 100644 --- a/xds/src/test/java/io/grpc/xds/LoadStatsStoreImplTest.java +++ b/xds/src/test/java/io/grpc/xds/LoadStatsStoreImplTest.java @@ -83,7 +83,7 @@ private static UpstreamLocalityStats buildUpstreamLocalityStats( @Nullable List metrics) { UpstreamLocalityStats.Builder builder = UpstreamLocalityStats.newBuilder() - .setLocality(locality.toEnvoyProtoLocality()) + .setLocality(locality.toEnvoyProtoLocalityV2()) .setTotalSuccessfulRequests(callsSucceed) .setTotalErrorRequests(callsFailed) .setTotalRequestsInProgress(callsInProgress) diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java index 8e8002ecc0b..a63b5401c03 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java @@ -57,7 +57,6 @@ import io.envoyproxy.envoy.api.v2.core.AggregatedConfigSource; import io.envoyproxy.envoy.api.v2.core.ConfigSource; import io.envoyproxy.envoy.api.v2.core.HealthStatus; -import io.envoyproxy.envoy.api.v2.core.Node; import io.envoyproxy.envoy.api.v2.endpoint.ClusterStats; import io.envoyproxy.envoy.api.v2.route.RedirectAction; import io.envoyproxy.envoy.api.v2.route.WeightedCluster; @@ -94,6 +93,7 @@ import io.grpc.xds.EnvoyProtoData.LbEndpoint; import io.grpc.xds.EnvoyProtoData.Locality; import io.grpc.xds.EnvoyProtoData.LocalityLbEndpoints; +import io.grpc.xds.EnvoyProtoData.Node; import io.grpc.xds.XdsClient.ClusterUpdate; import io.grpc.xds.XdsClient.ClusterWatcher; import io.grpc.xds.XdsClient.ConfigUpdate; @@ -132,7 +132,7 @@ public class XdsClientImplTest { private static final String TARGET_AUTHORITY = "foo.googleapis.com:8080"; - private static final Node NODE = Node.getDefaultInstance(); + private static final Node NODE = Node.newBuilder().build(); private static final FakeClock.TaskFilter RPC_RETRY_TASK_FILTER = new FakeClock.TaskFilter() { @Override @@ -294,7 +294,7 @@ ManagedChannel createChannel(List servers) { TARGET_AUTHORITY, servers, channelFactory, - NODE, + EnvoyProtoData.Node.newBuilder().build(), syncContext, fakeClock.getScheduledExecutorService(), backoffPolicyProvider, @@ -3732,7 +3732,7 @@ public boolean matches(DiscoveryRequest argument) { if (!resourceNames.equals(new HashSet<>(argument.getResourceNamesList()))) { return false; } - return NODE.equals(argument.getNode()); + return argument.getNode().equals(NODE.toEnvoyProtoNodeV2()); } } diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTestForListener.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTestForListener.java index fed260b7b11..91d22c69ee0 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTestForListener.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTestForListener.java @@ -37,16 +37,12 @@ import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.Any; import com.google.protobuf.InvalidProtocolBufferException; -import com.google.protobuf.Struct; import com.google.protobuf.UInt32Value; -import com.google.protobuf.Value; import io.envoyproxy.envoy.api.v2.DiscoveryRequest; import io.envoyproxy.envoy.api.v2.DiscoveryResponse; import io.envoyproxy.envoy.api.v2.Listener; import io.envoyproxy.envoy.api.v2.auth.DownstreamTlsContext; -import io.envoyproxy.envoy.api.v2.core.Address; import io.envoyproxy.envoy.api.v2.core.CidrRange; -import io.envoyproxy.envoy.api.v2.core.Node; import io.envoyproxy.envoy.api.v2.core.SocketAddress; import io.envoyproxy.envoy.api.v2.core.TransportSocket; import io.envoyproxy.envoy.api.v2.listener.Filter; @@ -70,6 +66,8 @@ import io.grpc.testing.GrpcCleanupRule; import io.grpc.xds.Bootstrapper.ChannelCreds; import io.grpc.xds.Bootstrapper.ServerInfo; +import io.grpc.xds.EnvoyProtoData.Address; +import io.grpc.xds.EnvoyProtoData.Node; import io.grpc.xds.XdsClient.ConfigWatcher; import io.grpc.xds.XdsClient.ListenerUpdate; import io.grpc.xds.XdsClient.ListenerWatcher; @@ -78,7 +76,9 @@ import java.io.IOException; import java.util.ArrayDeque; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Queue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -109,7 +109,7 @@ public class XdsClientImplTestForListener { "type.googleapis.com/" + "envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager"; - private static final Node NODE = Node.getDefaultInstance(); + private static final Node NODE = Node.newBuilder().build(); private static final FakeClock.TaskFilter RPC_RETRY_TASK_FILTER = new FakeClock.TaskFilter() { @Override @@ -230,15 +230,12 @@ public void tearDown() { } private static Node getNodeToVerify() { - Struct newMetadata = NODE.getMetadata().toBuilder() - .putFields("TRAFFICDIRECTOR_PROXYLESS", - Value.newBuilder().setStringValue("1").build()) - .build(); - Address listeningAddress = - Address.newBuilder() - .setSocketAddress( - SocketAddress.newBuilder().setAddress("0.0.0.0").setPortValue(PORT).build()) - .build(); + Map newMetadata = new HashMap<>(); + if (NODE.getMetadata() != null) { + newMetadata.putAll(NODE.getMetadata()); + } + newMetadata.put("TRAFFICDIRECTOR_PROXYLESS", "1"); + Address listeningAddress = new Address("0.0.0.0", PORT); return NODE.toBuilder() .setMetadata(newMetadata) .addListeningAddresses(listeningAddress) @@ -249,7 +246,7 @@ private static DiscoveryRequest buildDiscoveryRequest( Node node, String versionInfo, String typeUrl, String nonce) { return DiscoveryRequest.newBuilder() .setVersionInfo(versionInfo) - .setNode(node) + .setNode(node.toEnvoyProtoNodeV2()) .setTypeUrl(typeUrl) .setResponseNonce(nonce) .build(); @@ -815,10 +812,11 @@ public void streamClosedAndRetry() { static Listener buildListenerWithFilterChain(String name, int portValue, String address, FilterChain... filterChains) { - Address listenerAddress = Address.newBuilder() - .setSocketAddress(SocketAddress.newBuilder() - .setPortValue(portValue).setAddress(address)) - .build(); + io.envoyproxy.envoy.api.v2.core.Address listenerAddress = + io.envoyproxy.envoy.api.v2.core.Address.newBuilder() + .setSocketAddress( + SocketAddress.newBuilder().setPortValue(portValue).setAddress(address)) + .build(); return Listener.newBuilder() .setName(name) diff --git a/xds/src/test/java/io/grpc/xds/XdsClientTestHelper.java b/xds/src/test/java/io/grpc/xds/XdsClientTestHelper.java index 868fb426379..9918a9073e1 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientTestHelper.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientTestHelper.java @@ -39,7 +39,6 @@ import io.envoyproxy.envoy.api.v2.core.GrpcService; import io.envoyproxy.envoy.api.v2.core.GrpcService.GoogleGrpc; import io.envoyproxy.envoy.api.v2.core.HealthStatus; -import io.envoyproxy.envoy.api.v2.core.Node; import io.envoyproxy.envoy.api.v2.core.SelfConfigSource; import io.envoyproxy.envoy.api.v2.core.SocketAddress; import io.envoyproxy.envoy.api.v2.core.TransportSocket; @@ -51,6 +50,7 @@ import io.envoyproxy.envoy.config.listener.v2.ApiListener; import io.envoyproxy.envoy.type.FractionalPercent; import io.envoyproxy.envoy.type.FractionalPercent.DenominatorType; +import io.grpc.xds.EnvoyProtoData.Node; import java.util.List; import javax.annotation.Nullable; @@ -79,7 +79,7 @@ static DiscoveryRequest buildDiscoveryRequest(Node node, String versionInfo, return DiscoveryRequest.newBuilder() .setVersionInfo(versionInfo) - .setNode(node) + .setNode(node.toEnvoyProtoNodeV2()) .setTypeUrl(typeUrl) .addAllResourceNames(resourceNames) .setResponseNonce(nonce) diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverIntegrationTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverIntegrationTest.java index fdacff15e98..4584bcc6ea7 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverIntegrationTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverIntegrationTest.java @@ -37,7 +37,6 @@ import io.envoyproxy.envoy.api.v2.DiscoveryResponse; import io.envoyproxy.envoy.api.v2.core.AggregatedConfigSource; import io.envoyproxy.envoy.api.v2.core.ConfigSource; -import io.envoyproxy.envoy.api.v2.core.Node; import io.envoyproxy.envoy.api.v2.route.Route; import io.envoyproxy.envoy.api.v2.route.RouteAction; import io.envoyproxy.envoy.api.v2.route.RouteMatch; @@ -65,6 +64,7 @@ import io.grpc.stub.StreamObserver; import io.grpc.testing.GrpcCleanupRule; import io.grpc.xds.Bootstrapper.ServerInfo; +import io.grpc.xds.EnvoyProtoData.Node; import io.grpc.xds.XdsClient.XdsChannelFactory; import java.io.IOException; import java.util.ArrayDeque; From f444b7bcc01969e27d7cdb78dafdbc750220a4ce Mon Sep 17 00:00:00 2001 From: Chengyuan Zhang Date: Tue, 28 Jul 2020 19:40:55 +0000 Subject: [PATCH 21/88] android, cronet, android-interop-testing, example/drop support for android SDK versions older than 16 (#7253) --- android-interop-testing/build.gradle | 3 +-- android/build.gradle | 7 +++++-- cronet/build.gradle | 5 ++++- examples/android/helloworld/app/build.gradle | 3 +-- examples/android/routeguide/app/build.gradle | 2 +- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/android-interop-testing/build.gradle b/android-interop-testing/build.gradle index ec144bda19a..0bfc37c4480 100644 --- a/android-interop-testing/build.gradle +++ b/android-interop-testing/build.gradle @@ -29,8 +29,7 @@ android { defaultConfig { applicationId "io.grpc.android.integrationtest" - // API level 14+ is required for TLS since Google Play Services v10.2 - minSdkVersion 14 + minSdkVersion 16 targetSdkVersion 26 versionCode 1 versionName "1.0" diff --git a/android/build.gradle b/android/build.gradle index 97f24cd92c1..252b362aca0 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -11,7 +11,7 @@ android { compileSdkVersion 28 defaultConfig { consumerProguardFiles "proguard-rules.txt" - minSdkVersion 14 + minSdkVersion 16 targetSdkVersion 28 versionCode 1 versionName "1.0" @@ -31,7 +31,10 @@ dependencies { testImplementation project('::grpc-okhttp') testImplementation libraries.androidx_test testImplementation libraries.junit - testImplementation libraries.robolectric + testImplementation (libraries.robolectric) { + // Unreleased change: https://github.com/robolectric/robolectric/pull/5432 + exclude group: 'com.google.auto.service', module: 'auto-service' + } testImplementation libraries.truth } diff --git a/cronet/build.gradle b/cronet/build.gradle index b1b65685bb2..8a57797b638 100644 --- a/cronet/build.gradle +++ b/cronet/build.gradle @@ -42,7 +42,10 @@ dependencies { testImplementation libraries.junit testImplementation libraries.mockito - testImplementation libraries.robolectric + testImplementation (libraries.robolectric) { + // Unreleased change: https://github.com/robolectric/robolectric/pull/5432 + exclude group: 'com.google.auto.service', module: 'auto-service' + } } task javadocs(type: Javadoc) { diff --git a/examples/android/helloworld/app/build.gradle b/examples/android/helloworld/app/build.gradle index e2229f395bc..6f0b4da88a9 100644 --- a/examples/android/helloworld/app/build.gradle +++ b/examples/android/helloworld/app/build.gradle @@ -6,8 +6,7 @@ android { defaultConfig { applicationId "io.grpc.helloworldexample" - // API level 14+ is required for TLS since Google Play Services v10.2 - minSdkVersion 14 + minSdkVersion 16 targetSdkVersion 27 versionCode 1 versionName "1.0" diff --git a/examples/android/routeguide/app/build.gradle b/examples/android/routeguide/app/build.gradle index 37a18ff858a..3c32ad0f46f 100644 --- a/examples/android/routeguide/app/build.gradle +++ b/examples/android/routeguide/app/build.gradle @@ -6,7 +6,7 @@ android { defaultConfig { applicationId "io.grpc.routeguideexample" - minSdkVersion 14 + minSdkVersion 16 targetSdkVersion 27 versionCode 1 versionName "1.0" From 4fbe6bef7d17a354cd94ac68c23a753e6a60cdbb Mon Sep 17 00:00:00 2001 From: Eric Gribkoff Date: Tue, 28 Jul 2020 12:43:00 -0700 Subject: [PATCH 22/88] interop-testing: add flags to xds test client --- buildscripts/kokoro/xds.sh | 8 +- .../testing/integration/XdsTestClient.java | 261 ++++++++++++++---- .../testing/integration/XdsTestServer.java | 60 +++- .../main/proto/grpc/testing/messages.proto | 6 + 4 files changed, 276 insertions(+), 59 deletions(-) diff --git a/buildscripts/kokoro/xds.sh b/buildscripts/kokoro/xds.sh index 9d662451a2a..f911f25a1e2 100755 --- a/buildscripts/kokoro/xds.sh +++ b/buildscripts/kokoro/xds.sh @@ -20,9 +20,15 @@ popd git clone -b "${branch}" --single-branch --depth=1 https://github.com/grpc/grpc.git grpc/tools/run_tests/helper_scripts/prep_xds.sh + +# Test cases "path_matching" and "header_matching" are not included in "all", +# because not all interop clients in all languages support these new tests. +# +# TODO(ericgribkoff): remove "path_matching" and "header_matching" from +# --test_case after they are added into "all". JAVA_OPTS=-Djava.util.logging.config.file=grpc-java/buildscripts/xds_logging.properties \ python3 grpc/tools/run_tests/run_xds_tests.py \ - --test_case=all \ + --test_case="all,path_matching,header_matching" \ --project_id=grpc-testing \ --source_image=projects/grpc-testing/global/images/xds-test-server \ --path_to_server_binary=/java_server/grpc-java/interop-testing/build/install/grpc-interop-testing/bin/xds-test-server \ diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/XdsTestClient.java b/interop-testing/src/main/java/io/grpc/testing/integration/XdsTestClient.java index 0bd2fbeb7e9..73af15981ec 100644 --- a/interop-testing/src/main/java/io/grpc/testing/integration/XdsTestClient.java +++ b/interop-testing/src/main/java/io/grpc/testing/integration/XdsTestClient.java @@ -16,6 +16,9 @@ package io.grpc.testing.integration; +import com.google.common.base.CaseFormat; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; import com.google.common.primitives.Ints; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; @@ -24,12 +27,16 @@ import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.SettableFuture; import io.grpc.CallOptions; +import io.grpc.Channel; import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ForwardingClientCall.SimpleForwardingClientCall; +import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener; import io.grpc.Grpc; import io.grpc.ManagedChannel; import io.grpc.Metadata; +import io.grpc.MethodDescriptor; import io.grpc.Server; -import io.grpc.Status; import io.grpc.netty.NettyChannelBuilder; import io.grpc.netty.NettyServerBuilder; import io.grpc.stub.StreamObserver; @@ -38,6 +45,7 @@ import io.grpc.testing.integration.Messages.SimpleRequest; import io.grpc.testing.integration.Messages.SimpleResponse; import java.util.ArrayList; +import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -47,6 +55,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -62,13 +71,24 @@ public final class XdsTestClient { private int numChannels = 1; private boolean printResponse = false; private int qps = 1; - private int rpcTimeoutSec = 2; + private List rpcTypes = ImmutableList.of(RpcType.UNARY_CALL); + private EnumMap metadata = new EnumMap<>(RpcType.class); + private int rpcTimeoutSec = 20; private String server = "localhost:8080"; private int statsPort = 8081; private Server statsServer; private long currentRequestId; private ListeningScheduledExecutorService exec; + private enum RpcType { + EMPTY_CALL, + UNARY_CALL; + + public String toCamelCase() { + return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, toString()); + } + } + /** * The main application allowing this client to be launched from the command line. */ @@ -111,12 +131,16 @@ private void parseArgs(String[] args) { break; } String value = parts[1]; - if ("num_channels".equals(key)) { + if ("metadata".equals(key)) { + metadata = parseMetadata(value); + } else if ("num_channels".equals(key)) { numChannels = Integer.valueOf(value); } else if ("print_response".equals(key)) { printResponse = Boolean.valueOf(value); } else if ("qps".equals(key)) { qps = Integer.valueOf(value); + } else if ("rpc".equals(key)) { + rpcTypes = parseRpcs(value); } else if ("rpc_timeout_sec".equals(key)) { rpcTimeoutSec = Integer.valueOf(value); } else if ("server".equals(key)) { @@ -139,8 +163,12 @@ private void parseArgs(String[] args) { + c.numChannels + "\n --print_response=BOOL Write RPC response to stdout. Default: " + c.printResponse - + "\n --qps=INT Qps per channel. Default: " + + "\n --qps=INT Qps per channel, for each type of RPC. Default: " + c.qps + + "\n --rpc=STR Types of RPCs to make, ',' separated string. RPCs can " + + "be EmptyCall or UnaryCall. Default: UnaryCall" + + "\n --metadata=STR The metadata to send with each RPC, in the format " + + "EmptyCall:key1:value1,UnaryCall:key2:value2." + "\n --rpc_timeout_sec=INT Per RPC timeout seconds. Default: " + c.rpcTimeoutSec + "\n --server=host:port Address of server. Default: " @@ -152,6 +180,45 @@ private void parseArgs(String[] args) { } } + private static List parseRpcs(String rpcArg) { + List rpcs = new ArrayList<>(); + for (String rpc : Splitter.on(',').split(rpcArg)) { + rpcs.add(parseRpc(rpc)); + } + return rpcs; + } + + private static EnumMap parseMetadata(String metadataArg) { + EnumMap rpcMetadata = new EnumMap<>(RpcType.class); + for (String metadata : Splitter.on(',').omitEmptyStrings().split(metadataArg)) { + List parts = Splitter.on(':').splitToList(metadata); + if (parts.size() != 3) { + throw new IllegalArgumentException("Invalid metadata: '" + metadata + "'"); + } + RpcType rpc = parseRpc(parts.get(0)); + String key = parts.get(1); + String value = parts.get(2); + Metadata md = new Metadata(); + md.put(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER), value); + if (rpcMetadata.containsKey(rpc)) { + rpcMetadata.get(rpc).merge(md); + } else { + rpcMetadata.put(rpc, md); + } + } + return rpcMetadata; + } + + private static RpcType parseRpc(String rpc) { + if ("EmptyCall".equals(rpc)) { + return RpcType.EMPTY_CALL; + } else if ("UnaryCall".equals(rpc)) { + return RpcType.UNARY_CALL; + } else { + throw new IllegalArgumentException("Unknown RPC: '" + rpc + "'"); + } + } + private void run() { statsServer = NettyServerBuilder.forPort(statsPort).addService(new XdsStatsImpl()).build(); try { @@ -186,6 +253,11 @@ private void stop() throws InterruptedException { private void runQps() throws InterruptedException, ExecutionException { final SettableFuture failure = SettableFuture.create(); final class PeriodicRpc implements Runnable { + private final RpcType rpcType; + + private PeriodicRpc(RpcType rpcType) { + this.rpcType = rpcType; + } @Override public void run() { @@ -197,69 +269,137 @@ public void run() { savedWatchers.addAll(watchers); } - SimpleRequest request = SimpleRequest.newBuilder().setFillServerId(true).build(); + final Metadata headersToSend; + if (metadata.containsKey(rpcType)) { + headersToSend = metadata.get(rpcType); + } else { + headersToSend = new Metadata(); + } ManagedChannel channel = channels.get((int) (requestId % channels.size())); - final ClientCall call = - channel.newCall( - TestServiceGrpc.getUnaryCallMethod(), - CallOptions.DEFAULT.withDeadlineAfter(rpcTimeoutSec, TimeUnit.SECONDS)); - call.start( - new ClientCall.Listener() { - private String hostname; + TestServiceGrpc.TestServiceStub stub = TestServiceGrpc.newStub(channel); + final AtomicReference> clientCallRef = new AtomicReference<>(); + final AtomicReference hostnameRef = new AtomicReference<>(); + stub = + stub.withDeadlineAfter(rpcTimeoutSec, TimeUnit.SECONDS) + .withInterceptors( + new ClientInterceptor() { + @Override + public ClientCall interceptCall( + MethodDescriptor method, + CallOptions callOptions, + Channel next) { + ClientCall call = next.newCall(method, callOptions); + clientCallRef.set(call); + return new SimpleForwardingClientCall(call) { + @Override + public void start(Listener responseListener, Metadata headers) { + headers.merge(headersToSend); + super.start( + new SimpleForwardingClientCallListener(responseListener) { + @Override + public void onHeaders(Metadata headers) { + hostnameRef.set(headers.get(XdsTestServer.HOSTNAME_KEY)); + super.onHeaders(headers); + } + }, + headers); + } + }; + } + }); - @Override - public void onMessage(SimpleResponse response) { - hostname = response.getHostname(); - // TODO(ericgribkoff) Currently some test environments cannot access the stats RPC - // service and rely on parsing stdout. - if (printResponse) { - System.out.println( - "Greeting: Hello world, this is " - + hostname - + ", from " - + call.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR)); + if (rpcType == RpcType.EMPTY_CALL) { + stub.emptyCall( + EmptyProtos.Empty.getDefaultInstance(), + new StreamObserver() { + @Override + public void onCompleted() { + notifyWatchers(savedWatchers, rpcType, requestId, hostnameRef.get()); } - } - @Override - public void onClose(Status status, Metadata trailers) { - if (printResponse && !status.isOk()) { - logger.log(Level.WARNING, "Greeting RPC failed with status {0}", status); + @Override + public void onError(Throwable t) { + notifyWatchers(savedWatchers, rpcType, requestId, hostnameRef.get()); } - for (XdsStatsWatcher watcher : savedWatchers) { - watcher.rpcCompleted(requestId, hostname); + + @Override + public void onNext(EmptyProtos.Empty response) {} + }); + } else if (rpcType == RpcType.UNARY_CALL) { + SimpleRequest request = SimpleRequest.newBuilder().setFillServerId(true).build(); + stub.unaryCall( + request, + new StreamObserver() { + @Override + public void onCompleted() { + notifyWatchers(savedWatchers, rpcType, requestId, hostnameRef.get()); + } + + @Override + public void onError(Throwable t) { + if (printResponse) { + logger.log(Level.WARNING, "Rpc failed: {0}", t); + } + notifyWatchers(savedWatchers, rpcType, requestId, hostnameRef.get()); } - } - }, - new Metadata()); - call.sendMessage(request); - call.request(1); - call.halfClose(); + @Override + public void onNext(SimpleResponse response) { + // TODO(ericgribkoff) Currently some test environments cannot access the stats RPC + // service and rely on parsing stdout. + if (printResponse) { + System.out.println( + "Greeting: Hello world, this is " + + response.getHostname() + + ", from " + + clientCallRef + .get() + .getAttributes() + .get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR)); + } + // Use the hostname from the response if not present in the metadata. + // TODO(ericgribkoff) Delete when server is deployed that sets metadata value. + if (hostnameRef.get() == null) { + hostnameRef.set(response.getHostname()); + } + } + }); + } } } long nanosPerQuery = TimeUnit.SECONDS.toNanos(1) / qps; - ListenableScheduledFuture future = - exec.scheduleAtFixedRate(new PeriodicRpc(), 0, nanosPerQuery, TimeUnit.NANOSECONDS); - Futures.addCallback( - future, - new FutureCallback() { + for (RpcType rpcType : rpcTypes) { + ListenableScheduledFuture future = + exec.scheduleAtFixedRate( + new PeriodicRpc(rpcType), 0, nanosPerQuery, TimeUnit.NANOSECONDS); - @Override - public void onFailure(Throwable t) { - failure.setException(t); - } + Futures.addCallback( + future, + new FutureCallback() { - @Override - public void onSuccess(Object o) {} - }, - MoreExecutors.directExecutor()); + @Override + public void onFailure(Throwable t) { + failure.setException(t); + } + + @Override + public void onSuccess(Object o) {} + }, + MoreExecutors.directExecutor()); + } failure.get(); } + private void notifyWatchers( + Set watchers, RpcType rpcType, long requestId, String hostname) { + for (XdsStatsWatcher watcher : watchers) { + watcher.rpcCompleted(rpcType, requestId, hostname); + } + } + private class XdsStatsImpl extends LoadBalancerStatsServiceGrpc.LoadBalancerStatsServiceImplBase { @Override public void getClientStats( @@ -286,6 +426,8 @@ private static class XdsStatsWatcher { private final long startId; private final long endId; private final Map rpcsByPeer = new HashMap<>(); + private final EnumMap> rpcsByTypeAndPeer = + new EnumMap<>(RpcType.class); private final Object lock = new Object(); private int noRemotePeer; @@ -295,7 +437,7 @@ private XdsStatsWatcher(long startId, long endId) { this.endId = endId; } - void rpcCompleted(long requestId, @Nullable String hostname) { + void rpcCompleted(RpcType rpcType, long requestId, @Nullable String hostname) { synchronized (lock) { if (startId <= requestId && requestId < endId) { if (hostname != null) { @@ -304,6 +446,19 @@ void rpcCompleted(long requestId, @Nullable String hostname) { } else { rpcsByPeer.put(hostname, 1); } + if (rpcsByTypeAndPeer.containsKey(rpcType)) { + if (rpcsByTypeAndPeer.get(rpcType).containsKey(hostname)) { + rpcsByTypeAndPeer + .get(rpcType) + .put(hostname, rpcsByTypeAndPeer.get(rpcType).get(hostname) + 1); + } else { + rpcsByTypeAndPeer.get(rpcType).put(hostname, 1); + } + } else { + Map rpcMap = new HashMap<>(); + rpcMap.put(hostname, 1); + rpcsByTypeAndPeer.put(rpcType, rpcMap); + } } else { noRemotePeer += 1; } @@ -325,6 +480,12 @@ LoadBalancerStatsResponse waitForRpcStats(long timeoutSeconds) { LoadBalancerStatsResponse.Builder builder = LoadBalancerStatsResponse.newBuilder(); synchronized (lock) { builder.putAllRpcsByPeer(rpcsByPeer); + for (Map.Entry> entry : rpcsByTypeAndPeer.entrySet()) { + LoadBalancerStatsResponse.RpcsByPeer.Builder rpcs = + LoadBalancerStatsResponse.RpcsByPeer.newBuilder(); + rpcs.putAllRpcsByPeer(entry.getValue()); + builder.putRpcsByMethod(entry.getKey().toCamelCase(), rpcs.build()); + } builder.setNumFailures(noRemotePeer + (int) latch.getCount()); } return builder.build(); diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/XdsTestServer.java b/interop-testing/src/main/java/io/grpc/testing/integration/XdsTestServer.java index a7fcbf7968f..b9c8e3adbca 100644 --- a/interop-testing/src/main/java/io/grpc/testing/integration/XdsTestServer.java +++ b/interop-testing/src/main/java/io/grpc/testing/integration/XdsTestServer.java @@ -16,7 +16,13 @@ package io.grpc.testing.integration; +import io.grpc.ForwardingServerCall.SimpleForwardingServerCall; +import io.grpc.Metadata; import io.grpc.Server; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; +import io.grpc.ServerInterceptors; import io.grpc.health.v1.HealthCheckResponse.ServingStatus; import io.grpc.netty.NettyServerBuilder; import io.grpc.protobuf.services.ProtoReflectionService; @@ -32,12 +38,16 @@ /** Interop test server that implements the xDS testing service. */ public final class XdsTestServer { + static final Metadata.Key HOSTNAME_KEY = + Metadata.Key.of("hostname", Metadata.ASCII_STRING_MARSHALLER); + private static Logger logger = Logger.getLogger(XdsTestServer.class.getName()); private int port = 8080; private String serverId = "java_server"; private HealthStatusManager health; private Server server; + private String host; /** * The main application allowing this client to be launched from the command line. @@ -111,10 +121,18 @@ private void parseArgs(String[] args) { } private void start() throws Exception { + try { + host = InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + logger.log(Level.SEVERE, "Failed to get host", e); + throw new RuntimeException(e); + } health = new HealthStatusManager(); server = NettyServerBuilder.forPort(port) - .addService(new TestServiceImpl(serverId)) + .addService( + ServerInterceptors.intercept( + new TestServiceImpl(serverId, host), new HostnameInterceptor(host))) .addService(new XdsUpdateHealthServiceImpl(health)) .addService(health.getHealthService()) .addService(ProtoReflectionService.newInstance()) @@ -140,14 +158,16 @@ private static class TestServiceImpl extends TestServiceGrpc.TestServiceImplBase private final String serverId; private final String host; - private TestServiceImpl(String serverId) { + private TestServiceImpl(String serverId, String host) { this.serverId = serverId; - try { - host = InetAddress.getLocalHost().getHostName(); - } catch (UnknownHostException e) { - logger.log(Level.SEVERE, "Failed to get host", e); - throw new RuntimeException(e); - } + this.host = host; + } + + @Override + public void emptyCall( + EmptyProtos.Empty req, StreamObserver responseObserver) { + responseObserver.onNext(EmptyProtos.Empty.getDefaultInstance()); + responseObserver.onCompleted(); } @Override @@ -182,4 +202,28 @@ public void setNotServing( responseObserver.onCompleted(); } } + + private static class HostnameInterceptor implements ServerInterceptor { + private final String host; + + private HostnameInterceptor(String host) { + this.host = host; + } + + @Override + public ServerCall.Listener interceptCall( + ServerCall call, + final Metadata requestHeaders, + ServerCallHandler next) { + return next.startCall( + new SimpleForwardingServerCall(call) { + @Override + public void sendHeaders(Metadata responseHeaders) { + responseHeaders.put(HOSTNAME_KEY, host); + super.sendHeaders(responseHeaders); + } + }, + requestHeaders); + } + } } diff --git a/interop-testing/src/main/proto/grpc/testing/messages.proto b/interop-testing/src/main/proto/grpc/testing/messages.proto index 5665de8504b..a84f708ee3d 100644 --- a/interop-testing/src/main/proto/grpc/testing/messages.proto +++ b/interop-testing/src/main/proto/grpc/testing/messages.proto @@ -195,4 +195,10 @@ message LoadBalancerStatsResponse { map rpcs_by_peer = 1; // The number of RPCs that failed to record a remote peer. int32 num_failures = 2; + message RpcsByPeer { + // The number of completed RPCs for each peer. + map rpcs_by_peer = 1; + } + // The number of completed RPCs for each type (UnaryCall or EmptyCall). + map rpcs_by_method = 3; } From d2182fe19731bafe58e90d571b29d8ad1f677a9c Mon Sep 17 00:00:00 2001 From: Menghan Li Date: Tue, 28 Jul 2020 14:07:43 -0700 Subject: [PATCH 23/88] interop-testing: add path_matching and header_matching (#7254) --- buildscripts/kokoro/xds.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/buildscripts/kokoro/xds.sh b/buildscripts/kokoro/xds.sh index f911f25a1e2..a85bdf9a084 100755 --- a/buildscripts/kokoro/xds.sh +++ b/buildscripts/kokoro/xds.sh @@ -30,11 +30,14 @@ JAVA_OPTS=-Djava.util.logging.config.file=grpc-java/buildscripts/xds_logging.pro python3 grpc/tools/run_tests/run_xds_tests.py \ --test_case="all,path_matching,header_matching" \ --project_id=grpc-testing \ - --source_image=projects/grpc-testing/global/images/xds-test-server \ + --source_image=projects/grpc-testing/global/images/xds-test-server-2 \ --path_to_server_binary=/java_server/grpc-java/interop-testing/build/install/grpc-interop-testing/bin/xds-test-server \ --gcp_suffix=$(date '+%s') \ --verbose \ --client_cmd="grpc-java/interop-testing/build/install/grpc-interop-testing/bin/xds-test-client \ --server=xds:///{server_uri} \ --stats_port={stats_port} \ - --qps={qps}" + --qps={qps} \ + {rpcs_to_send} \ + {metadata_to_send}" + From 06ca927a649c84fb3fc4b6c82aa7695bf3edf728 Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Wed, 29 Jul 2020 09:10:02 -0700 Subject: [PATCH 24/88] xds: first part of MeshCaCertificateProvider (#7247) --- .../certprovider/CertificateProvider.java | 3 + .../CertificateProviderStore.java | 6 +- .../MeshCaCertificateProvider.java | 134 +++++++++++ .../MeshCaCertificateProviderProvider.java | 197 ++++++++++++++++ .../grpc/xds/internal/sts/StsCredentials.java | 79 ++++--- .../CertificateProviderStoreTest.java | 9 + ...MeshCaCertificateProviderProviderTest.java | 212 ++++++++++++++++++ .../xds/internal/sts/StsCredentialsTest.java | 17 +- 8 files changed, 618 insertions(+), 39 deletions(-) create mode 100644 xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProvider.java create mode 100644 xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderProvider.java create mode 100644 xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderProviderTest.java diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProvider.java index 89d6954898c..9b96f9957fb 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProvider.java @@ -124,6 +124,9 @@ protected CertificateProvider(DistributorWatcher watcher, boolean notifyCertUpda @Override public abstract void close(); + /** Starts the cert refresh and watcher update cycle. */ + public abstract void start(); + private final DistributorWatcher watcher; private final boolean notifyCertUpdates; diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderStore.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderStore.java index 2a1452fbe9f..5b09a581278 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderStore.java +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderStore.java @@ -131,8 +131,10 @@ public CertificateProvider create(CertProviderKey key) { if (certProviderProvider == null) { throw new IllegalArgumentException("Provider not found."); } - return certProviderProvider.createCertificateProvider( - key.config, new CertificateProvider.DistributorWatcher(), key.notifyCertUpdates); + CertificateProvider certProvider = certProviderProvider.createCertificateProvider( + key.config, new CertificateProvider.DistributorWatcher(), key.notifyCertUpdates); + certProvider.start(); + return certProvider; } } diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProvider.java new file mode 100644 index 00000000000..a4d22faa6a9 --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProvider.java @@ -0,0 +1,134 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds.internal.certprovider; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.auth.oauth2.GoogleCredentials; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import io.grpc.internal.BackoffPolicy; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** Implementation of {@link CertificateProvider} for the Google Mesh CA. */ +final class MeshCaCertificateProvider extends CertificateProvider { + private static final Logger logger = Logger.getLogger(MeshCaCertificateProvider.class.getName()); + + protected MeshCaCertificateProvider(DistributorWatcher watcher, boolean notifyCertUpdates, + String meshCaUrl, String zone, long validitySeconds, + int keySize, String alg, String signatureAlg, MeshCaChannelFactory meshCaChannelFactory, + BackoffPolicy.Provider backoffPolicyProvider, long renewalGracePeriodSeconds, + int maxRetryAttempts, GoogleCredentials oauth2Creds) { + super(watcher, notifyCertUpdates); + } + + @Override + public void start() { + // TODO implement + } + + @Override + public void close() { + // TODO implement + } + + /** Factory for creating channels to MeshCA sever. */ + abstract static class MeshCaChannelFactory { + + private static final MeshCaChannelFactory DEFAULT_INSTANCE = + new MeshCaChannelFactory() { + + /** Creates a channel to the URL in the given list. */ + @Override + ManagedChannel createChannel(String serverUri) { + checkArgument(serverUri != null && !serverUri.isEmpty(), "serverUri is null/empty!"); + logger.log(Level.INFO, "Creating channel to {0}", serverUri); + + ManagedChannelBuilder channelBuilder = ManagedChannelBuilder.forTarget(serverUri); + return channelBuilder.keepAliveTime(1, TimeUnit.MINUTES).build(); + } + }; + + static MeshCaChannelFactory getInstance() { + return DEFAULT_INSTANCE; + } + + /** + * Creates a channel to the server. + */ + abstract ManagedChannel createChannel(String serverUri); + } + + /** Factory for creating channels to MeshCA sever. */ + abstract static class Factory { + private static final Factory DEFAULT_INSTANCE = + new Factory() { + + @Override + MeshCaCertificateProvider create( + DistributorWatcher watcher, + boolean notifyCertUpdates, + String meshCaUrl, + String zone, + long validitySeconds, + int keySize, + String alg, + String signatureAlg, + MeshCaChannelFactory meshCaChannelFactory, + BackoffPolicy.Provider backoffPolicyProvider, + long renewalGracePeriodSeconds, + int maxRetryAttempts, + GoogleCredentials oauth2Creds) { + return new MeshCaCertificateProvider( + watcher, + notifyCertUpdates, + meshCaUrl, + zone, + validitySeconds, + keySize, + alg, + signatureAlg, + meshCaChannelFactory, + backoffPolicyProvider, + renewalGracePeriodSeconds, + maxRetryAttempts, + oauth2Creds); + } + }; + + static Factory getInstance() { + return DEFAULT_INSTANCE; + } + + abstract MeshCaCertificateProvider create( + DistributorWatcher watcher, + boolean notifyCertUpdates, + String meshCaUrl, + String zone, + long validitySeconds, + int keySize, + String alg, + String signatureAlg, + MeshCaChannelFactory meshCaChannelFactory, + BackoffPolicy.Provider backoffPolicyProvider, + long renewalGracePeriodSeconds, + int maxRetryAttempts, + GoogleCredentials oauth2Creds); + } +} diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderProvider.java new file mode 100644 index 00000000000..a9c1b01ba0c --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderProvider.java @@ -0,0 +1,197 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds.internal.certprovider; + +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 com.google.common.annotations.VisibleForTesting; +import io.grpc.internal.BackoffPolicy; +import io.grpc.internal.ExponentialBackoffPolicy; +import io.grpc.xds.internal.sts.StsCredentials; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Provider of {@link CertificateProvider}s. Implemented by the implementer of the plugin. We may + * move this out of the internal package and make this an official API in the future. + */ +final class MeshCaCertificateProviderProvider implements CertificateProviderProvider { + + private static final String MESHCA_URL_KEY = "meshCaUrl"; + private static final String RPC_TIMEOUT_SECONDS_KEY = "rpcTimeoutSeconds"; + private static final String GKECLUSTER_URL_KEY = "gkeClusterUrl"; + private static final String CERT_VALIDITY_SECONDS_KEY = "certValiditySeconds"; + private static final String RENEWAL_GRACE_PERIOD_SECONDS_KEY = "renewalGracePeriodSeconds"; + private static final String KEY_ALGO_KEY = "keyAlgo"; // aka keyType + private static final String KEY_SIZE_KEY = "keySize"; + private static final String SIGNATURE_ALGO_KEY = "signatureAlgo"; + private static final String MAX_RETRY_ATTEMPTS_KEY = "maxRetryAttempts"; + private static final String STS_URL_KEY = "stsUrl"; + private static final String GKE_SA_JWT_LOCATION_KEY = "gkeSaJwtLocation"; + + static final String MESHCA_URL_DEFAULT = "meshca.googleapis.com"; + static final long RPC_TIMEOUT_SECONDS_DEFAULT = 5L; + static final long CERT_VALIDITY_SECONDS_DEFAULT = 9L * 3600L; // 9 hours + static final long RENEWAL_GRACE_PERIOD_SECONDS_DEFAULT = 1L * 3600L; // 1 hour + static final String KEY_ALGO_DEFAULT = "RSA"; // aka keyType + static final int KEY_SIZE_DEFAULT = 2048; + static final String SIGNATURE_ALGO_DEFAULT = "SHA256withRSA"; + static final int MAX_RETRY_ATTEMPTS_DEFAULT = 3; + static final String STS_URL_DEFAULT = "https://securetoken.googleapis.com/v1/identitybindingtoken"; + + private static final Pattern CLUSTER_URL_PATTERN = Pattern + .compile(".*/projects/(.*)/locations/(.*)/clusters/.*"); + + private static final String TRUST_DOMAIN_SUFFIX = ".svc.id.goog"; + private static final String AUDIENCE_PREFIX = "identitynamespace:"; + static final String MESH_CA_NAME = "meshCA"; + + static { + CertificateProviderRegistry.getInstance() + .register( + new MeshCaCertificateProviderProvider( + StsCredentials.Factory.getInstance(), + MeshCaCertificateProvider.MeshCaChannelFactory.getInstance(), + new ExponentialBackoffPolicy.Provider(), + MeshCaCertificateProvider.Factory.getInstance())); + } + + final StsCredentials.Factory stsCredentialsFactory; + final MeshCaCertificateProvider.MeshCaChannelFactory meshCaChannelFactory; + final BackoffPolicy.Provider backoffPolicyProvider; + final MeshCaCertificateProvider.Factory meshCaCertificateProviderFactory; + + @VisibleForTesting + MeshCaCertificateProviderProvider(StsCredentials.Factory stsCredentialsFactory, + MeshCaCertificateProvider.MeshCaChannelFactory meshCaChannelFactory, + BackoffPolicy.Provider backoffPolicyProvider, + MeshCaCertificateProvider.Factory meshCaCertificateProviderFactory) { + this.stsCredentialsFactory = stsCredentialsFactory; + this.meshCaChannelFactory = meshCaChannelFactory; + this.backoffPolicyProvider = backoffPolicyProvider; + this.meshCaCertificateProviderFactory = meshCaCertificateProviderFactory; + } + + @Override + public String getName() { + return MESH_CA_NAME; + } + + @Override + public CertificateProvider createCertificateProvider( + Object config, CertificateProvider.DistributorWatcher watcher, boolean notifyCertUpdates) { + + Config configObj = validateAndTranslateConfig(config); + + // Construct audience from project and gkeClusterUrl + String audience = + AUDIENCE_PREFIX + configObj.project + TRUST_DOMAIN_SUFFIX + ":" + configObj.gkeClusterUrl; + StsCredentials stsCredentials = stsCredentialsFactory + .create(configObj.stsUrl, audience, configObj.gkeSaJwtLocation); + + return meshCaCertificateProviderFactory.create(watcher, notifyCertUpdates, configObj.meshCaUrl, + configObj.zone, + configObj.certValiditySeconds, configObj.keySize, configObj.keyAlgo, + configObj.signatureAlgo, + meshCaChannelFactory, backoffPolicyProvider, + configObj.renewalGracePeriodSeconds, configObj.maxRetryAttempts, stsCredentials); + } + + private static Config validateAndTranslateConfig(Object config) { + // TODO(sanjaypujare): add support for string, struct proto etc + checkArgument(config instanceof Map, "Only Map supported for config"); + @SuppressWarnings("unchecked") Map map = (Map)config; + + Config configObj = new Config(); + configObj.meshCaUrl = mapGetOrDefault(map, MESHCA_URL_KEY, MESHCA_URL_DEFAULT); + configObj.rpcTimeoutSeconds = + mapGetOrDefault(map, RPC_TIMEOUT_SECONDS_KEY, RPC_TIMEOUT_SECONDS_DEFAULT); + configObj.gkeClusterUrl = + checkNotNull( + map.get(GKECLUSTER_URL_KEY), GKECLUSTER_URL_KEY + " is required in the config"); + configObj.certValiditySeconds = + mapGetOrDefault(map, CERT_VALIDITY_SECONDS_KEY, CERT_VALIDITY_SECONDS_DEFAULT); + configObj.renewalGracePeriodSeconds = + mapGetOrDefault( + map, RENEWAL_GRACE_PERIOD_SECONDS_KEY, RENEWAL_GRACE_PERIOD_SECONDS_DEFAULT); + configObj.keyAlgo = mapGetOrDefault(map, KEY_ALGO_KEY, KEY_ALGO_DEFAULT); + configObj.keySize = mapGetOrDefault(map, KEY_SIZE_KEY, KEY_SIZE_DEFAULT); + configObj.signatureAlgo = mapGetOrDefault(map, SIGNATURE_ALGO_KEY, SIGNATURE_ALGO_DEFAULT); + configObj.maxRetryAttempts = + mapGetOrDefault(map, MAX_RETRY_ATTEMPTS_KEY, MAX_RETRY_ATTEMPTS_DEFAULT); + configObj.stsUrl = mapGetOrDefault(map, STS_URL_KEY, STS_URL_DEFAULT); + configObj.gkeSaJwtLocation = + checkNotNull( + map.get(GKE_SA_JWT_LOCATION_KEY), + GKE_SA_JWT_LOCATION_KEY + " is required in the config"); + parseProjectAndZone(configObj.gkeClusterUrl, configObj); + return configObj; + } + + private static String mapGetOrDefault(Map map, String key, String defaultVal) { + String value = map.get(key); + if (value == null) { + return defaultVal; + } + return value; + } + + private static Long mapGetOrDefault(Map map, String key, long defaultVal) { + String value = map.get(key); + if (value == null) { + return defaultVal; + } + return Long.parseLong(value); + } + + private static Integer mapGetOrDefault(Map map, String key, int defaultVal) { + String value = map.get(key); + if (value == null) { + return defaultVal; + } + return Integer.parseInt(value); + } + + private static void parseProjectAndZone(String gkeClusterUrl, Config configObj) { + Matcher matcher = CLUSTER_URL_PATTERN.matcher(gkeClusterUrl); + checkState(matcher.find(), "gkeClusterUrl does not have correct format"); + checkState(matcher.groupCount() == 2, "gkeClusterUrl does not have project and location parts"); + configObj.project = matcher.group(1); + configObj.zone = matcher.group(2); + } + + /** POJO class for storing various config values. */ + @VisibleForTesting + static class Config { + String meshCaUrl; + Long rpcTimeoutSeconds; + String gkeClusterUrl; + Long certValiditySeconds; + Long renewalGracePeriodSeconds; + String keyAlgo; // aka keyType + Integer keySize; + String signatureAlgo; + Integer maxRetryAttempts; + String stsUrl; + String gkeSaJwtLocation; + String zone; + String project; + } +} diff --git a/xds/src/main/java/io/grpc/xds/internal/sts/StsCredentials.java b/xds/src/main/java/io/grpc/xds/internal/sts/StsCredentials.java index 65816502494..fde5f1f0a96 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sts/StsCredentials.java +++ b/xds/src/main/java/io/grpc/xds/internal/sts/StsCredentials.java @@ -47,14 +47,14 @@ public final class StsCredentials extends GoogleCredentials { private static final long serialVersionUID = 6647041424685484932L; - private static final HttpTransportFactory defaultHttpTransportFactory = + @VisibleForTesting static final HttpTransportFactory defaultHttpTransportFactory = new DefaultHttpTransportFactory(); private static final String CLOUD_PLATFORM_SCOPE = "https://www.googleapis.com/auth/cloud-platform"; - private final String sourceCredentialsFileLocation; - private final String identityTokenEndpoint; - private final String audience; - private transient HttpTransportFactory transportFactory; + @VisibleForTesting final String sourceCredentialsFileLocation; + @VisibleForTesting final String identityTokenEndpoint; + @VisibleForTesting final String audience; + @VisibleForTesting transient HttpTransportFactory transportFactory; private StsCredentials( String identityTokenEndpoint, @@ -67,33 +67,6 @@ private StsCredentials( this.transportFactory = transportFactory; } - /** - * Creates an StsCredentials. - * - * @param identityTokenEndpoint URL of the token exchange service to use. - * @param audience Audience to use in the STS request. - * @param sourceCredentialsFileLocation file-system location that contains the - * source creds e.g. JWT contents. - */ - public static StsCredentials create( - String identityTokenEndpoint, String audience, String sourceCredentialsFileLocation) { - return create( - identityTokenEndpoint, - audience, - sourceCredentialsFileLocation, - getFromServiceLoader(HttpTransportFactory.class, defaultHttpTransportFactory)); - } - - @VisibleForTesting - static StsCredentials create( - String identityTokenEndpoint, - String audience, - String sourceCredentialsFileLocation, - HttpTransportFactory transportFactory) { - return new StsCredentials( - identityTokenEndpoint, audience, sourceCredentialsFileLocation, transportFactory); - } - @Override public AccessToken refreshAccessToken() throws IOException { AccessToken tok = getSourceAccessTokenFromFileLocation(); @@ -157,6 +130,48 @@ public Builder toBuilder() { throw new UnsupportedOperationException("toBuilder not supported"); } + /** Factory for creating StsCredentials. */ + public abstract static class Factory { + private static final Factory DEFAULT_INSTANCE = + new Factory() { + + @Override + public StsCredentials create( + String identityTokenEndpoint, String audience, String sourceCredentialsFileLocation) { + return create( + identityTokenEndpoint, + audience, + sourceCredentialsFileLocation, + getFromServiceLoader(HttpTransportFactory.class, defaultHttpTransportFactory)); + } + }; + + public static Factory getInstance() { + return DEFAULT_INSTANCE; + } + + /** + * Creates an StsCredentials. + * + * @param identityTokenEndpoint URL of the token exchange service to use. + * @param audience Audience to use in the STS request. + * @param sourceCredentialsFileLocation file-system location that contains the + * source creds e.g. JWT contents. + */ + public abstract StsCredentials create( + String identityTokenEndpoint, String audience, String sourceCredentialsFileLocation); + + @VisibleForTesting + static StsCredentials create( + String identityTokenEndpoint, + String audience, + String sourceCredentialsFileLocation, + HttpTransportFactory transportFactory) { + return new StsCredentials( + identityTokenEndpoint, audience, sourceCredentialsFileLocation, transportFactory); + } + } + private static class DefaultHttpTransportFactory implements HttpTransportFactory { private static final HttpTransport netHttpTransport = new NetHttpTransport(); diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/CertificateProviderStoreTest.java b/xds/src/test/java/io/grpc/xds/internal/certprovider/CertificateProviderStoreTest.java index 521de29d09a..8a0dfeb1d7e 100644 --- a/xds/src/test/java/io/grpc/xds/internal/certprovider/CertificateProviderStoreTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/certprovider/CertificateProviderStoreTest.java @@ -53,6 +53,7 @@ private class TestCertificateProvider extends CertificateProvider { Object config; CertificateProviderProvider certProviderProvider; int closeCalled = 0; + int startCalled = 0; protected TestCertificateProvider( CertificateProvider.DistributorWatcher watcher, @@ -71,6 +72,11 @@ protected TestCertificateProvider( public void close() { closeCalled++; } + + @Override + public void start() { + startCalled++; + } } @Before @@ -161,6 +167,7 @@ public void onePluginSameConfig_sameInstance() { assertThat(handle1.certProvider).isInstanceOf(TestCertificateProvider.class); TestCertificateProvider testCertificateProvider = (TestCertificateProvider) handle1.certProvider; + assertThat(testCertificateProvider.startCalled).isEqualTo(1); CertificateProvider.DistributorWatcher distWatcher = testCertificateProvider.getWatcher(); assertThat(distWatcher.downsstreamWatchers).hasSize(2); PrivateKey testKey = mock(PrivateKey.class); @@ -335,6 +342,8 @@ private static void checkDifferentInstances( verify(mockWatcher2, times(1)).updateCertificate(eq(testKey2), eq(testList2)); verify(mockWatcher1, never()) .updateCertificate(any(PrivateKey.class), anyListOf(X509Certificate.class)); + assertThat(testCertificateProvider1.startCalled).isEqualTo(1); + assertThat(testCertificateProvider2.startCalled).isEqualTo(1); handle2.close(); assertThat(testCertificateProvider2.closeCalled).isEqualTo(1); handle1.close(); diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderProviderTest.java new file mode 100644 index 00000000000..d9d4da9350b --- /dev/null +++ b/xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderProviderTest.java @@ -0,0 +1,212 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds.internal.certprovider; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import com.google.auth.oauth2.GoogleCredentials; +import io.grpc.internal.BackoffPolicy; +import io.grpc.internal.ExponentialBackoffPolicy; +import io.grpc.xds.internal.sts.StsCredentials; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Unit tests for {@link MeshCaCertificateProviderProvider}. */ +@RunWith(JUnit4.class) +public class MeshCaCertificateProviderProviderTest { + + public static final String EXPECTED_AUDIENCE = + "identitynamespace:test-project1.svc.id.goog:https://container.googleapis.com/v1/projects/test-project1/locations/test-zone2/clusters/test-cluster3"; + public static final String TMP_PATH_4 = "/tmp/path4"; + public static final String NON_DEFAULT_MESH_CA_URL = "nonDefaultMeshCaUrl"; + public static final String GKE_CLUSTER_URL = + "https://container.googleapis.com/v1/projects/test-project1/locations/test-zone2/clusters/test-cluster3"; + + @Mock + StsCredentials.Factory stsCredentialsFactory; + + @Mock + MeshCaCertificateProvider.MeshCaChannelFactory meshCaChannelFactory; + + @Mock + BackoffPolicy.Provider backoffPolicyProvider; + + @Mock + MeshCaCertificateProvider.Factory meshCaCertificateProviderFactory; + private MeshCaCertificateProviderProvider provider; + + @Before + public void setUp() throws IOException { + MockitoAnnotations.initMocks(this); + provider = + new MeshCaCertificateProviderProvider( + stsCredentialsFactory, + meshCaChannelFactory, + backoffPolicyProvider, + meshCaCertificateProviderFactory); + } + + @Test + public void providerRegisteredName() { + CertificateProviderProvider certProviderProvider = CertificateProviderRegistry.getInstance() + .getProvider(MeshCaCertificateProviderProvider.MESH_CA_NAME); + assertThat(certProviderProvider).isInstanceOf(MeshCaCertificateProviderProvider.class); + MeshCaCertificateProviderProvider meshCaCertificateProviderProvider = + (MeshCaCertificateProviderProvider) certProviderProvider; + assertThat(meshCaCertificateProviderProvider.stsCredentialsFactory) + .isSameInstanceAs(StsCredentials.Factory.getInstance()); + assertThat(meshCaCertificateProviderProvider.meshCaChannelFactory) + .isSameInstanceAs(MeshCaCertificateProvider.MeshCaChannelFactory.getInstance()); + assertThat(meshCaCertificateProviderProvider.backoffPolicyProvider) + .isInstanceOf(ExponentialBackoffPolicy.Provider.class); + assertThat(meshCaCertificateProviderProvider.meshCaCertificateProviderFactory) + .isSameInstanceAs(MeshCaCertificateProvider.Factory.getInstance()); + } + + @Test + public void createProvider_minimalConfig() { + CertificateProvider.DistributorWatcher distWatcher = + new CertificateProvider.DistributorWatcher(); + Map map = buildMinimalMap(); + provider.createCertificateProvider(map, distWatcher, true); + verify(stsCredentialsFactory, times(1)) + .create( + eq(MeshCaCertificateProviderProvider.STS_URL_DEFAULT), + eq(EXPECTED_AUDIENCE), + eq(TMP_PATH_4)); + verify(meshCaCertificateProviderFactory, times(1)) + .create( + eq(distWatcher), + eq(true), + eq(MeshCaCertificateProviderProvider.MESHCA_URL_DEFAULT), + eq("test-zone2"), + eq(MeshCaCertificateProviderProvider.CERT_VALIDITY_SECONDS_DEFAULT), + eq(MeshCaCertificateProviderProvider.KEY_SIZE_DEFAULT), + eq(MeshCaCertificateProviderProvider.KEY_ALGO_DEFAULT), + eq(MeshCaCertificateProviderProvider.SIGNATURE_ALGO_DEFAULT), + eq(meshCaChannelFactory), + eq(backoffPolicyProvider), + eq(MeshCaCertificateProviderProvider.RENEWAL_GRACE_PERIOD_SECONDS_DEFAULT), + eq(MeshCaCertificateProviderProvider.MAX_RETRY_ATTEMPTS_DEFAULT), + (GoogleCredentials) isNull()); + } + + @Test + public void createProvider_missingGkeUrl_expectException() { + CertificateProvider.DistributorWatcher distWatcher = + new CertificateProvider.DistributorWatcher(); + Map map = buildMinimalMap(); + map.remove("gkeClusterUrl"); + try { + provider.createCertificateProvider(map, distWatcher, true); + fail("exception expected"); + } catch (NullPointerException npe) { + assertThat(npe).hasMessageThat().isEqualTo("gkeClusterUrl is required in the config"); + } + } + + @Test + public void createProvider_missingGkeSaJwtLocation_expectException() { + CertificateProvider.DistributorWatcher distWatcher = + new CertificateProvider.DistributorWatcher(); + Map map = buildMinimalMap(); + map.remove("gkeSaJwtLocation"); + try { + provider.createCertificateProvider(map, distWatcher, true); + fail("exception expected"); + } catch (NullPointerException npe) { + assertThat(npe).hasMessageThat().isEqualTo("gkeSaJwtLocation is required in the config"); + } + } + + @Test + public void createProvider_missingProject_expectException() { + CertificateProvider.DistributorWatcher distWatcher = + new CertificateProvider.DistributorWatcher(); + Map map = buildMinimalMap(); + map.put("gkeClusterUrl", "https://container.googleapis.com/v1/project/test-project1/locations/test-zone2/clusters/test-cluster3"); + try { + provider.createCertificateProvider(map, distWatcher, true); + fail("exception expected"); + } catch (IllegalStateException ex) { + assertThat(ex).hasMessageThat().isEqualTo("gkeClusterUrl does not have correct format"); + } + } + + @Test + public void createProvider_nonDefaultFullConfig() { + CertificateProvider.DistributorWatcher distWatcher = + new CertificateProvider.DistributorWatcher(); + Map map = buildFullMap(); + provider.createCertificateProvider(map, distWatcher, true); + verify(stsCredentialsFactory, times(1)) + .create( + eq("nonDefaultStsUrl"), + eq(EXPECTED_AUDIENCE), + eq(TMP_PATH_4)); + verify(meshCaCertificateProviderFactory, times(1)) + .create( + eq(distWatcher), + eq(true), + eq(NON_DEFAULT_MESH_CA_URL), + eq("test-zone2"), + eq(234567L), + eq(4096), + eq("KEY-ALGO1"), + eq("SIG-ALGO2"), + eq(meshCaChannelFactory), + eq(backoffPolicyProvider), + eq(4321L), + eq(9), + (GoogleCredentials) isNull()); + } + + private Map buildFullMap() { + Map map = new HashMap<>(); + map.put("gkeClusterUrl", GKE_CLUSTER_URL); + map.put("gkeSaJwtLocation", TMP_PATH_4); + map.put("meshCaUrl", NON_DEFAULT_MESH_CA_URL); + map.put("rpcTimeoutSeconds", "123"); + map.put("certValiditySeconds", "234567"); + map.put("renewalGracePeriodSeconds", "4321"); + map.put("keyAlgo", "KEY-ALGO1"); + map.put("keySize", "4096"); + map.put("signatureAlgo", "SIG-ALGO2"); + map.put("maxRetryAttempts", "9"); + map.put("stsUrl", "nonDefaultStsUrl"); + return map; + } + + private Map buildMinimalMap() { + Map map = new HashMap<>(); + map.put("gkeClusterUrl", GKE_CLUSTER_URL); + map.put("gkeSaJwtLocation", TMP_PATH_4); + return map; + } +} diff --git a/xds/src/test/java/io/grpc/xds/internal/sts/StsCredentialsTest.java b/xds/src/test/java/io/grpc/xds/internal/sts/StsCredentialsTest.java index 4fa13fde14d..d6adedeca57 100644 --- a/xds/src/test/java/io/grpc/xds/internal/sts/StsCredentialsTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/sts/StsCredentialsTest.java @@ -83,7 +83,7 @@ public void testStsRequestResponse() throws IOException { HttpTransportFactory httpTransportFactory = mock(HttpTransportFactory.class); when(httpTransportFactory.create()).thenReturn(httpTransport); StsCredentials stsCredentials = - StsCredentials.create( + StsCredentials.Factory.create( STS_URL, AUDIENCE_VALUE, tempTokenFile.getAbsolutePath(), httpTransportFactory); AccessToken token = stsCredentials.refreshAccessToken(); assertThat(token).isNotNull(); @@ -115,7 +115,7 @@ public void stsCredentialsInCallCreds() throws IOException { HttpTransportFactory httpTransportFactory = mock(HttpTransportFactory.class); when(httpTransportFactory.create()).thenReturn(httpTransport); StsCredentials stsCredentials = - StsCredentials.create( + StsCredentials.Factory.create( STS_URL, AUDIENCE_VALUE, tempTokenFile.getAbsolutePath(), httpTransportFactory); CallCredentials callCreds = MoreCallCredentials.from(stsCredentials); CallCredentials.RequestInfo requestInfo = mock(CallCredentials.RequestInfo.class); @@ -150,7 +150,7 @@ public void testStsRequest_exception() throws IOException { HttpTransportFactory httpTransportFactory = mock(HttpTransportFactory.class); when(httpTransportFactory.create()).thenReturn(httpTransport); StsCredentials stsCredentials = - StsCredentials.create( + StsCredentials.Factory.create( STS_URL, AUDIENCE_VALUE, tempTokenFile.getAbsolutePath(), httpTransportFactory); try { stsCredentials.refreshAccessToken(); @@ -171,7 +171,7 @@ public void testStsRequest_nonSuccessCode() throws IOException { HttpTransportFactory httpTransportFactory = mock(HttpTransportFactory.class); when(httpTransportFactory.create()).thenReturn(httpTransport); StsCredentials stsCredentials = - StsCredentials.create( + StsCredentials.Factory.create( STS_URL, AUDIENCE_VALUE, tempTokenFile.getAbsolutePath(), httpTransportFactory); try { stsCredentials.refreshAccessToken(); @@ -185,7 +185,7 @@ public void testStsRequest_nonSuccessCode() throws IOException { public void toBuilder_unsupportedException() { HttpTransportFactory httpTransportFactory = mock(HttpTransportFactory.class); StsCredentials stsCredentials = - StsCredentials.create( + StsCredentials.Factory.create( STS_URL, AUDIENCE_VALUE, tempTokenFile.getAbsolutePath(), httpTransportFactory); try { stsCredentials.toBuilder(); @@ -195,6 +195,13 @@ public void toBuilder_unsupportedException() { } } + @Test + public void defaultFactory() { + StsCredentials stsCreds = StsCredentials.Factory.getInstance() + .create(STS_URL, AUDIENCE_VALUE, tempTokenFile.getAbsolutePath()); + assertThat(stsCreds.transportFactory).isEqualTo(StsCredentials.defaultHttpTransportFactory); + } + private static final String ACCESS_TOKEN = "eyJhbGciOiJSU"; private static final String MOCK_RESPONSE = "{\"access_token\": \"" From 9eb33838adc8f621fb53b30233b3e97195672b2e Mon Sep 17 00:00:00 2001 From: Eric Gribkoff Date: Wed, 29 Jul 2020 14:55:24 -0700 Subject: [PATCH 25/88] Update minimum Android API in README (#7265) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0a655313be2..304de5b914a 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ gRPC-Java - An RPC library and framework ======================================== gRPC-Java works with JDK 7. gRPC-Java clients are supported on Android API -levels 14 and up (Ice Cream Sandwich and later). Deploying gRPC servers on an -Android device is not supported. +levels 16 and up (Jelly Bean and later). Deploying gRPC servers on an Android +device is not supported. TLS usage typically requires using Java 8, or Play Services Dynamic Security Provider on Android. Please see the [Security Readme](SECURITY.md). From de36e4245816e41db2cb3bb78f05afb3bdb90db2 Mon Sep 17 00:00:00 2001 From: Eric Gribkoff Date: Thu, 30 Jul 2020 08:50:21 -0700 Subject: [PATCH 26/88] Update README etc to reference 1.31.0 (#7267) --- README.md | 24 ++++++++++++------------ cronet/README.md | 2 +- documentation/android-channel-builder.md | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 304de5b914a..f9c504d03f8 100644 --- a/README.md +++ b/README.md @@ -42,17 +42,17 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`: io.grpc grpc-netty-shaded - 1.30.2 + 1.31.0 io.grpc grpc-protobuf - 1.30.2 + 1.31.0 io.grpc grpc-stub - 1.30.2 + 1.31.0 org.apache.tomcat @@ -64,23 +64,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 -implementation 'io.grpc:grpc-netty-shaded:1.30.2' -implementation 'io.grpc:grpc-protobuf:1.30.2' -implementation 'io.grpc:grpc-stub:1.30.2' +implementation 'io.grpc:grpc-netty-shaded:1.31.0' +implementation 'io.grpc:grpc-protobuf:1.31.0' +implementation 'io.grpc:grpc-stub:1.31.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.30.2' -implementation 'io.grpc:grpc-protobuf-lite:1.30.2' -implementation 'io.grpc:grpc-stub:1.30.2' +implementation 'io.grpc:grpc-okhttp:1.31.0' +implementation 'io.grpc:grpc-protobuf-lite:1.31.0' +implementation 'io.grpc:grpc-stub:1.31.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.30.2 +https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.31.0 Development snapshots are available in [Sonatypes's snapshot repository](https://oss.sonatype.org/content/repositories/snapshots/). @@ -112,7 +112,7 @@ For protobuf-based codegen integrated with the Maven build system, you can use com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier} grpc-java - io.grpc:protoc-gen-grpc-java:1.30.2:exe:${os.detected.classifier} + io.grpc:protoc-gen-grpc-java:1.31.0:exe:${os.detected.classifier} @@ -142,7 +142,7 @@ protobuf { } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.30.2' + artifact = 'io.grpc:protoc-gen-grpc-java:1.31.0' } } generateProtoTasks { diff --git a/cronet/README.md b/cronet/README.md index 7b3ac880d0a..8115c0caa81 100644 --- a/cronet/README.md +++ b/cronet/README.md @@ -26,7 +26,7 @@ In your app module's `build.gradle` file, include a dependency on both `grpc-cro Google Play Services Client Library for Cronet ``` -implementation 'io.grpc:grpc-cronet:1.30.2' +implementation 'io.grpc:grpc-cronet:1.31.0' implementation 'com.google.android.gms:play-services-cronet:16.0.0' ``` diff --git a/documentation/android-channel-builder.md b/documentation/android-channel-builder.md index 314250243eb..03e67c904df 100644 --- a/documentation/android-channel-builder.md +++ b/documentation/android-channel-builder.md @@ -36,8 +36,8 @@ In your `build.gradle` file, include a dependency on both `grpc-android` and `grpc-okhttp`: ``` -implementation 'io.grpc:grpc-android:1.30.2' -implementation 'io.grpc:grpc-okhttp:1.30.2' +implementation 'io.grpc:grpc-android:1.31.0' +implementation 'io.grpc:grpc-okhttp:1.31.0' ``` You also need permission to access the device's network state in your From 2204beba2e580ec87bc9972bb8f2565877a0af91 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Thu, 30 Jul 2020 13:41:34 -0700 Subject: [PATCH 27/88] xds: refactor AdsStream to envoy-proto-and-version-agnostic AbstractAdsStream In preparation of xds-v3 support. --- .../main/java/io/grpc/xds/XdsClientImpl.java | 699 ++++++++++++------ 1 file changed, 481 insertions(+), 218 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/XdsClientImpl.java b/xds/src/main/java/io/grpc/xds/XdsClientImpl.java index ddaec29abee..4991cdf1d83 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClientImpl.java +++ b/xds/src/main/java/io/grpc/xds/XdsClientImpl.java @@ -29,8 +29,6 @@ import com.google.protobuf.MessageOrBuilder; import com.google.protobuf.util.JsonFormat; import com.google.rpc.Code; -import io.envoyproxy.envoy.api.v2.DiscoveryRequest; -import io.envoyproxy.envoy.api.v2.DiscoveryResponse; import io.envoyproxy.envoy.config.cluster.v3.Cluster; import io.envoyproxy.envoy.config.cluster.v3.Cluster.DiscoveryType; import io.envoyproxy.envoy.config.cluster.v3.Cluster.EdsClusterConfig; @@ -46,7 +44,9 @@ import io.envoyproxy.envoy.config.route.v3.VirtualHost; import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager; import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.Rds; -import io.envoyproxy.envoy.service.discovery.v2.AggregatedDiscoveryServiceGrpc; +import io.envoyproxy.envoy.service.discovery.v3.AggregatedDiscoveryServiceGrpc; +import io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest; +import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse; import io.grpc.InternalLogId; import io.grpc.ManagedChannel; import io.grpc.Status; @@ -167,7 +167,7 @@ final class XdsClientImpl extends XdsClient { private ScheduledHandle rdsRespTimer; @Nullable - private AdsStream adsStream; + private AbstractAdsStream adsStream; @Nullable private BackoffPolicy retryBackoffPolicy; @Nullable @@ -270,7 +270,7 @@ void watchConfigData(String targetAuthority, ConfigWatcher watcher) { if (adsStream == null) { startRpcStream(); } - adsStream.sendXdsRequest(ADS_TYPE_URL_LDS_V2, ImmutableList.of(ldsResourceName)); + adsStream.sendXdsRequest(ResourceType.LDS, ImmutableList.of(ldsResourceName)); ldsRespTimer = syncContext .schedule( @@ -311,7 +311,7 @@ void watchClusterData(String clusterName, ClusterWatcher watcher) { if (adsStream == null) { startRpcStream(); } - adsStream.sendXdsRequest(ADS_TYPE_URL_CDS_V2, clusterWatchers.keySet()); + adsStream.sendXdsRequest(ResourceType.CDS, clusterWatchers.keySet()); ScheduledHandle timeoutHandle = syncContext .schedule( @@ -348,7 +348,7 @@ void cancelClusterDataWatch(String clusterName, ClusterWatcher watcher) { } checkState(adsStream != null, "Severe bug: ADS stream was not created while an endpoint watcher was registered"); - adsStream.sendXdsRequest(ADS_TYPE_URL_CDS_V2, clusterWatchers.keySet()); + adsStream.sendXdsRequest(ResourceType.CDS, clusterWatchers.keySet()); } } @@ -389,7 +389,7 @@ void watchEndpointData(String clusterName, EndpointWatcher watcher) { if (adsStream == null) { startRpcStream(); } - adsStream.sendXdsRequest(ADS_TYPE_URL_EDS_V2, endpointWatchers.keySet()); + adsStream.sendXdsRequest(ResourceType.EDS, endpointWatchers.keySet()); ScheduledHandle timeoutHandle = syncContext .schedule( @@ -424,7 +424,7 @@ void cancelEndpointDataWatch(String clusterName, EndpointWatcher watcher) { // Currently in retry backoff. return; } - adsStream.sendXdsRequest(ADS_TYPE_URL_EDS_V2, endpointWatchers.keySet()); + adsStream.sendXdsRequest(ResourceType.EDS, endpointWatchers.keySet()); } } @@ -445,7 +445,7 @@ void watchListenerData(int port, ListenerWatcher watcher) { startRpcStream(); } updateNodeMetadataForListenerRequest(port); - adsStream.sendXdsRequest(ADS_TYPE_URL_LDS_V2, ImmutableList.of()); + adsStream.sendXdsRequest(ResourceType.LDS, ImmutableList.of()); ldsRespTimer = syncContext .schedule( @@ -515,9 +515,12 @@ public String toString() { */ private void startRpcStream() { checkState(adsStream == null, "Previous adsStream has not been cleared yet"); - AggregatedDiscoveryServiceGrpc.AggregatedDiscoveryServiceStub stub = - AggregatedDiscoveryServiceGrpc.newStub(channel); - adsStream = new AdsStream(stub); + io.envoyproxy.envoy.service.discovery.v2.AggregatedDiscoveryServiceGrpc + .AggregatedDiscoveryServiceStub + stub = + io.envoyproxy.envoy.service.discovery.v2.AggregatedDiscoveryServiceGrpc.newStub( + channel); + adsStream = new AdsStreamV2(stub); adsStream.start(); logger.log(XdsLogLevel.INFO, "ADS stream started"); adsStreamRetryStopwatch.reset().start(); @@ -527,13 +530,9 @@ private void startRpcStream() { * Calls handleLdsResponseForListener or handleLdsResponseForConfigUpdate based on which watcher * was set. */ - private void handleLdsResponse(DiscoveryResponse ldsResponse) { + private void handleLdsResponse(DiscoveryResponseData ldsResponse) { checkState((configWatcher != null) != (listenerWatcher != null), "No LDS request was ever sent. Management server is doing something wrong"); - if (logger.isLoggable(XdsLogLevel.DEBUG)) { - logger.log( - XdsLogLevel.DEBUG, "Received LDS response:\n{0}", respPrinter.print(ldsResponse)); - } if (listenerWatcher != null) { handleLdsResponseForListener(ldsResponse); } else { @@ -549,13 +548,13 @@ private void handleLdsResponse(DiscoveryResponse ldsResponse) { * resolution. The response is NACKed if contains invalid data for gRPC's usage. Otherwise, an * ACK request is sent to management server. */ - private void handleLdsResponseForConfigUpdate(DiscoveryResponse ldsResponse) { + private void handleLdsResponseForConfigUpdate(DiscoveryResponseData ldsResponse) { checkState(ldsResourceName != null && configWatcher != null, "LDS request for ConfigWatcher was never sent!"); // Unpack Listener messages. - List listeners = new ArrayList<>(ldsResponse.getResourcesCount()); - List listenerNames = new ArrayList<>(ldsResponse.getResourcesCount()); + List listeners = new ArrayList<>(ldsResponse.getResourcesList().size()); + List listenerNames = new ArrayList<>(ldsResponse.getResourcesList().size()); try { for (com.google.protobuf.Any res : ldsResponse.getResourcesList()) { if (res.getTypeUrl().equals(ADS_TYPE_URL_LDS_V2)) { @@ -568,7 +567,7 @@ private void handleLdsResponseForConfigUpdate(DiscoveryResponse ldsResponse) { } catch (InvalidProtocolBufferException e) { logger.log(XdsLogLevel.WARNING, "Failed to unpack Listeners in LDS response {0}", e); adsStream.sendNackRequest( - ADS_TYPE_URL_LDS_V2, ImmutableList.of(ldsResourceName), + ResourceType.LDS, ImmutableList.of(ldsResourceName), ldsResponse.getVersionInfo(), "Malformed LDS response: " + e); return; } @@ -593,7 +592,7 @@ private void handleLdsResponseForConfigUpdate(DiscoveryResponse ldsResponse) { XdsLogLevel.WARNING, "Failed to unpack HttpConnectionManagers in Listeners of LDS response {0}", e); adsStream.sendNackRequest( - ADS_TYPE_URL_LDS_V2, ImmutableList.of(ldsResourceName), + ResourceType.LDS, ImmutableList.of(ldsResourceName), ldsResponse.getVersionInfo(), "Malformed LDS response: " + e); return; } @@ -639,11 +638,11 @@ private void handleLdsResponseForConfigUpdate(DiscoveryResponse ldsResponse) { if (errorMessage != null) { adsStream.sendNackRequest( - ADS_TYPE_URL_LDS_V2, ImmutableList.of(ldsResourceName), + ResourceType.LDS, ImmutableList.of(ldsResourceName), ldsResponse.getVersionInfo(), errorMessage); return; } - adsStream.sendAckRequest(ADS_TYPE_URL_LDS_V2, ImmutableList.of(ldsResourceName), + adsStream.sendAckRequest(ResourceType.LDS, ImmutableList.of(ldsResourceName), ldsResponse.getVersionInfo()); if (routes != null || rdsRouteConfigName != null) { @@ -665,7 +664,7 @@ private void handleLdsResponseForConfigUpdate(DiscoveryResponse ldsResponse) { logger.log( XdsLogLevel.INFO, "Use RDS to dynamically resolve route config, resource name: {0}", rdsRouteConfigName); - adsStream.sendXdsRequest(ADS_TYPE_URL_RDS_V2, ImmutableList.of(rdsRouteConfigName)); + adsStream.sendXdsRequest(ResourceType.RDS, ImmutableList.of(rdsRouteConfigName)); // Cancel the timer for fetching the previous RDS resource. if (rdsRespTimer != null) { rdsRespTimer.cancel(); @@ -684,13 +683,13 @@ private void handleLdsResponseForConfigUpdate(DiscoveryResponse ldsResponse) { } } - private void handleLdsResponseForListener(DiscoveryResponse ldsResponse) { + private void handleLdsResponseForListener(DiscoveryResponseData ldsResponse) { checkState(ldsResourceName == null && listenerPort > 0 && listenerWatcher != null, "LDS request for ListenerWatcher was never sent!"); // Unpack Listener messages. Listener requestedListener = null; - logger.log(XdsLogLevel.DEBUG, "Listener count: {0}", ldsResponse.getResourcesCount()); + logger.log(XdsLogLevel.DEBUG, "Listener count: {0}", ldsResponse.getResourcesList().size()); try { for (com.google.protobuf.Any res : ldsResponse.getResourcesList()) { if (res.getTypeUrl().equals(ADS_TYPE_URL_LDS_V2)) { @@ -706,7 +705,7 @@ private void handleLdsResponseForListener(DiscoveryResponse ldsResponse) { } catch (InvalidProtocolBufferException e) { logger.log(XdsLogLevel.WARNING, "Failed to unpack Listeners in LDS response {0}", e); adsStream.sendNackRequest( - ADS_TYPE_URL_LDS_V2, ImmutableList.of(), + ResourceType.LDS, ImmutableList.of(), ldsResponse.getVersionInfo(), "Malformed LDS response: " + e); return; } @@ -723,7 +722,7 @@ private void handleLdsResponseForListener(DiscoveryResponse ldsResponse) { } catch (InvalidProtocolBufferException e) { logger.log(XdsLogLevel.WARNING, "Failed to unpack Listener in LDS response {0}", e); adsStream.sendNackRequest( - ADS_TYPE_URL_LDS_V2, ImmutableList.of(), + ResourceType.LDS, ImmutableList.of(), ldsResponse.getVersionInfo(), "Malformed LDS response: " + e); return; } @@ -732,7 +731,7 @@ private void handleLdsResponseForListener(DiscoveryResponse ldsResponse) { listenerWatcher.onResourceDoesNotExist(":" + listenerPort); } } - adsStream.sendAckRequest(ADS_TYPE_URL_LDS_V2, ImmutableList.of(), + adsStream.sendAckRequest(ResourceType.LDS, ImmutableList.of(), ldsResponse.getVersionInfo()); if (listenerUpdate != null) { listenerWatcher.onListenerChanged(listenerUpdate); @@ -769,15 +768,12 @@ private boolean hasMatchingFilter(List filterChainsList) { * for the "xds:" URI (with the port, if any, stripped off). The response is NACKed if contains * invalid data for gRPC's usage. Otherwise, an ACK request is sent to management server. */ - private void handleRdsResponse(DiscoveryResponse rdsResponse) { - if (logger.isLoggable(XdsLogLevel.DEBUG)) { - logger.log(XdsLogLevel.DEBUG, "Received RDS response:\n{0}", respPrinter.print(rdsResponse)); - } + private void handleRdsResponse(DiscoveryResponseData rdsResponse) { checkState(adsStream.rdsResourceName != null, "Never requested for RDS resources, management server is doing something wrong"); // Unpack RouteConfiguration messages. - List routeConfigNames = new ArrayList<>(rdsResponse.getResourcesCount()); + List routeConfigNames = new ArrayList<>(rdsResponse.getResourcesList().size()); RouteConfiguration requestedRouteConfig = null; try { for (com.google.protobuf.Any res : rdsResponse.getResourcesList()) { @@ -794,7 +790,7 @@ private void handleRdsResponse(DiscoveryResponse rdsResponse) { logger.log( XdsLogLevel.WARNING, "Failed to unpack RouteConfiguration in RDS response {0}", e); adsStream.sendNackRequest( - ADS_TYPE_URL_RDS_V2, ImmutableList.of(adsStream.rdsResourceName), + ResourceType.RDS, ImmutableList.of(adsStream.rdsResourceName), rdsResponse.getVersionInfo(), "Malformed RDS response: " + e); return; } @@ -809,7 +805,7 @@ private void handleRdsResponse(DiscoveryResponse rdsResponse) { } catch (InvalidProtoDataException e) { String errorDetail = e.getMessage(); adsStream.sendNackRequest( - ADS_TYPE_URL_RDS_V2, ImmutableList.of(adsStream.rdsResourceName), + ResourceType.RDS, ImmutableList.of(adsStream.rdsResourceName), rdsResponse.getVersionInfo(), "RouteConfiguration " + requestedRouteConfig.getName() + ": cannot find a " + "valid cluster name in any virtual hosts with domains matching: " @@ -819,7 +815,7 @@ private void handleRdsResponse(DiscoveryResponse rdsResponse) { } } - adsStream.sendAckRequest(ADS_TYPE_URL_RDS_V2, ImmutableList.of(adsStream.rdsResourceName), + adsStream.sendAckRequest(ResourceType.RDS, ImmutableList.of(adsStream.rdsResourceName), rdsResponse.getVersionInfo()); // Notify the ConfigWatcher if this RDS response contains the most recently requested @@ -929,15 +925,12 @@ static VirtualHost findVirtualHostForHostName( * Response data for requested clusters is cached locally, in case of new cluster watchers * interested in the same clusters are added later. */ - private void handleCdsResponse(DiscoveryResponse cdsResponse) { - if (logger.isLoggable(XdsLogLevel.DEBUG)) { - logger.log(XdsLogLevel.DEBUG, "Received CDS response:\n{0}", respPrinter.print(cdsResponse)); - } + private void handleCdsResponse(DiscoveryResponseData cdsResponse) { adsStream.cdsRespNonce = cdsResponse.getNonce(); // Unpack Cluster messages. - List clusters = new ArrayList<>(cdsResponse.getResourcesCount()); - List clusterNames = new ArrayList<>(cdsResponse.getResourcesCount()); + List clusters = new ArrayList<>(cdsResponse.getResourcesList().size()); + List clusterNames = new ArrayList<>(cdsResponse.getResourcesList().size()); try { for (com.google.protobuf.Any res : cdsResponse.getResourcesList()) { if (res.getTypeUrl().equals(ADS_TYPE_URL_CDS_V2)) { @@ -950,7 +943,7 @@ private void handleCdsResponse(DiscoveryResponse cdsResponse) { } catch (InvalidProtocolBufferException e) { logger.log(XdsLogLevel.WARNING, "Failed to unpack Clusters in CDS response {0}", e); adsStream.sendNackRequest( - ADS_TYPE_URL_CDS_V2, clusterWatchers.keySet(), + ResourceType.CDS, clusterWatchers.keySet(), cdsResponse.getVersionInfo(), "Malformed CDS response: " + e); return; } @@ -1025,13 +1018,13 @@ private void handleCdsResponse(DiscoveryResponse cdsResponse) { } if (errorMessage != null) { adsStream.sendNackRequest( - ADS_TYPE_URL_CDS_V2, + ResourceType.CDS, clusterWatchers.keySet(), cdsResponse.getVersionInfo(), errorMessage); return; } - adsStream.sendAckRequest(ADS_TYPE_URL_CDS_V2, clusterWatchers.keySet(), + adsStream.sendAckRequest(ResourceType.CDS, clusterWatchers.keySet(), cdsResponse.getVersionInfo()); // Update local CDS cache with data in this response. @@ -1107,15 +1100,11 @@ private static UpstreamTlsContext getTlsContextFromCluster(Cluster cluster) * cached locally, in case of new endpoint watchers interested in the same clusters * are added later. */ - private void handleEdsResponse(DiscoveryResponse edsResponse) { - if (logger.isLoggable(XdsLogLevel.DEBUG)) { - logger.log(XdsLogLevel.DEBUG, "Received EDS response:\n{0}", respPrinter.print(edsResponse)); - } - + private void handleEdsResponse(DiscoveryResponseData edsResponse) { // Unpack ClusterLoadAssignment messages. List clusterLoadAssignments = - new ArrayList<>(edsResponse.getResourcesCount()); - List claNames = new ArrayList<>(edsResponse.getResourcesCount()); + new ArrayList<>(edsResponse.getResourcesList().size()); + List claNames = new ArrayList<>(edsResponse.getResourcesList().size()); try { for (com.google.protobuf.Any res : edsResponse.getResourcesList()) { if (res.getTypeUrl().equals(ADS_TYPE_URL_EDS_V2)) { @@ -1129,7 +1118,7 @@ private void handleEdsResponse(DiscoveryResponse edsResponse) { logger.log( XdsLogLevel.WARNING, "Failed to unpack ClusterLoadAssignments in EDS response {0}", e); adsStream.sendNackRequest( - ADS_TYPE_URL_EDS_V2, endpointWatchers.keySet(), + ResourceType.EDS, endpointWatchers.keySet(), edsResponse.getVersionInfo(), "Malformed EDS response: " + e); return; } @@ -1202,13 +1191,13 @@ private void handleEdsResponse(DiscoveryResponse edsResponse) { } if (errorMessage != null) { adsStream.sendNackRequest( - ADS_TYPE_URL_EDS_V2, + ResourceType.EDS, endpointWatchers.keySet(), edsResponse.getVersionInfo(), errorMessage); return; } - adsStream.sendAckRequest(ADS_TYPE_URL_EDS_V2, endpointWatchers.keySet(), + adsStream.sendAckRequest(ResourceType.EDS, endpointWatchers.keySet(), edsResponse.getVersionInfo()); // Update local EDS cache by inserting updated endpoint information. @@ -1239,7 +1228,7 @@ final class RpcRetryTask implements Runnable { public void run() { startRpcStream(); if (configWatcher != null) { - adsStream.sendXdsRequest(ADS_TYPE_URL_LDS_V2, ImmutableList.of(ldsResourceName)); + adsStream.sendXdsRequest(ResourceType.LDS, ImmutableList.of(ldsResourceName)); ldsRespTimer = syncContext .schedule( @@ -1247,7 +1236,7 @@ public void run() { INITIAL_RESOURCE_FETCH_TIMEOUT_SEC, TimeUnit.SECONDS, timeService); } if (listenerWatcher != null) { - adsStream.sendXdsRequest(ADS_TYPE_URL_LDS_V2, ImmutableList.of()); + adsStream.sendXdsRequest(ResourceType.LDS, ImmutableList.of()); ldsRespTimer = syncContext .schedule( @@ -1255,7 +1244,7 @@ public void run() { INITIAL_RESOURCE_FETCH_TIMEOUT_SEC, TimeUnit.SECONDS, timeService); } if (!clusterWatchers.isEmpty()) { - adsStream.sendXdsRequest(ADS_TYPE_URL_CDS_V2, clusterWatchers.keySet()); + adsStream.sendXdsRequest(ResourceType.CDS, clusterWatchers.keySet()); for (String clusterName : clusterWatchers.keySet()) { ScheduledHandle timeoutHandle = syncContext @@ -1266,7 +1255,7 @@ public void run() { } } if (!endpointWatchers.isEmpty()) { - adsStream.sendXdsRequest(ADS_TYPE_URL_EDS_V2, endpointWatchers.keySet()); + adsStream.sendXdsRequest(ResourceType.EDS, endpointWatchers.keySet()); for (String clusterName : endpointWatchers.keySet()) { ScheduledHandle timeoutHandle = syncContext @@ -1279,10 +1268,159 @@ public void run() { } } - private final class AdsStream implements StreamObserver { - private final AggregatedDiscoveryServiceGrpc.AggregatedDiscoveryServiceStub stub; + private enum ResourceType { + UNKNOWN, LDS, RDS, CDS, EDS; + + String typeUrl() { + switch (this) { + case LDS: + return ADS_TYPE_URL_LDS; + case RDS: + return ADS_TYPE_URL_RDS; + case CDS: + return ADS_TYPE_URL_CDS; + case EDS: + return ADS_TYPE_URL_EDS; + case UNKNOWN: + default: + throw new AssertionError("Unknown or missing case in enum switch: " + this); + } + } + + String typeUrlV2() { + switch (this) { + case LDS: + return ADS_TYPE_URL_LDS_V2; + case RDS: + return ADS_TYPE_URL_RDS_V2; + case CDS: + return ADS_TYPE_URL_CDS_V2; + case EDS: + return ADS_TYPE_URL_EDS_V2; + case UNKNOWN: + default: + throw new AssertionError("Unknown or missing case in enum switch: " + this); + } + } + + static ResourceType fromTypeUrl(String typeUrl) { + switch (typeUrl) { + case ADS_TYPE_URL_LDS: + // fall trough + case ADS_TYPE_URL_LDS_V2: + return LDS; + case ADS_TYPE_URL_RDS: + // fall through + case ADS_TYPE_URL_RDS_V2: + return RDS; + case ADS_TYPE_URL_CDS: + // fall through + case ADS_TYPE_URL_CDS_V2: + return CDS; + case ADS_TYPE_URL_EDS: + // fall through + case ADS_TYPE_URL_EDS_V2: + return EDS; + default: + return UNKNOWN; + } + } + } - private StreamObserver requestWriter; + private static final class DiscoveryRequestData { + private final ResourceType resourceType; + private final Collection resourceNames; + private final String versionInfo; + private final String responseNonce; + private final Node node; + @Nullable + private final com.google.rpc.Status errorDetail; + + DiscoveryRequestData( + ResourceType resourceType, Collection resourceNames, String versionInfo, + String responseNonce, Node node, @Nullable com.google.rpc.Status errorDetail) { + this.resourceType = resourceType; + this.resourceNames = resourceNames; + this.versionInfo = versionInfo; + this.responseNonce = responseNonce; + this.node = node; + this.errorDetail = errorDetail; + } + + DiscoveryRequest toEnvoyProto() { + DiscoveryRequest.Builder builder = + DiscoveryRequest.newBuilder() + .setVersionInfo(versionInfo) + .setNode(node.toEnvoyProtoNode()) + .addAllResourceNames(resourceNames) + .setTypeUrl(resourceType.typeUrl()) + .setResponseNonce(responseNonce); + if (errorDetail != null) { + builder.setErrorDetail(errorDetail); + } + return builder.build(); + } + + io.envoyproxy.envoy.api.v2.DiscoveryRequest toEnvoyProtoV2() { + io.envoyproxy.envoy.api.v2.DiscoveryRequest.Builder builder = + io.envoyproxy.envoy.api.v2.DiscoveryRequest.newBuilder() + .setVersionInfo(versionInfo) + .setNode(node.toEnvoyProtoNodeV2()) + .addAllResourceNames(resourceNames) + .setTypeUrl(resourceType.typeUrlV2()) + .setResponseNonce(responseNonce); + if (errorDetail != null) { + builder.setErrorDetail(errorDetail); + } + return builder.build(); + } + } + + private static final class DiscoveryResponseData { + private final ResourceType resourceType; + private final List resources; + private final String versionInfo; + private final String nonce; + + DiscoveryResponseData( + ResourceType resourceType, List resources, String versionInfo, String nonce) { + this.resourceType = resourceType; + this.resources = resources; + this.versionInfo = versionInfo; + this.nonce = nonce; + } + + ResourceType getResourceType() { + return resourceType; + } + + List getResourcesList() { + return resources; + } + + String getVersionInfo() { + return versionInfo; + } + + String getNonce() { + return nonce; + } + + static DiscoveryResponseData fromEnvoyProto(DiscoveryResponse proto) { + return new DiscoveryResponseData( + ResourceType.fromTypeUrl(proto.getTypeUrl()), proto.getResourcesList(), + proto.getVersionInfo(), proto.getNonce()); + } + + static DiscoveryResponseData fromEnvoyProtoV2( + io.envoyproxy.envoy.api.v2.DiscoveryResponse proto) { + return new DiscoveryResponseData( + ResourceType.fromTypeUrl(proto.getTypeUrl()), proto.getResourcesList(), + proto.getVersionInfo(), proto.getNonce()); + } + } + + private abstract class AbstractAdsStream { private boolean responseReceived; private boolean closed; @@ -1313,51 +1451,58 @@ private final class AdsStream implements StreamObserver { @Nullable private String rdsResourceName; - private AdsStream(AggregatedDiscoveryServiceGrpc.AggregatedDiscoveryServiceStub stub) { - this.stub = checkNotNull(stub, "stub"); - } - - private void start() { - requestWriter = stub.withWaitForReady().streamAggregatedResources(this); - } - - @Override - public void onNext(final DiscoveryResponse response) { - syncContext.execute(new Runnable() { - @Override - public void run() { - if (closed) { - return; - } - responseReceived = true; - String typeUrl = response.getTypeUrl(); - // Nonce in each response is echoed back in the following ACK/NACK request. It is - // used for management server to identify which response the client is ACKing/NACking. - // To avoid confusion, client-initiated requests will always use the nonce in - // most recently received responses of each resource type. - if (typeUrl.equals(ADS_TYPE_URL_LDS_V2) || typeUrl.equals(ADS_TYPE_URL_LDS)) { - ldsRespNonce = response.getNonce(); - handleLdsResponse(response); - } else if (typeUrl.equals(ADS_TYPE_URL_RDS_V2) || typeUrl.equals(ADS_TYPE_URL_LDS)) { - rdsRespNonce = response.getNonce(); - handleRdsResponse(response); - } else if (typeUrl.equals(ADS_TYPE_URL_CDS_V2) || typeUrl.equals(ADS_TYPE_URL_CDS)) { - cdsRespNonce = response.getNonce(); - handleCdsResponse(response); - } else if (typeUrl.equals(ADS_TYPE_URL_EDS_V2) || typeUrl.equals(ADS_TYPE_URL_EDS)) { - edsRespNonce = response.getNonce(); - handleEdsResponse(response); - } else { - logger.log( - XdsLogLevel.WARNING, - "Received an unknown type of DiscoveryResponse\n{0}", response); - } - } - }); - } - - @Override - public void onError(final Throwable t) { + abstract void start(); + + abstract void sendDiscoveryRequest(DiscoveryRequestData request); + + abstract void sendError(Exception error); + + final void onDiscoveryResponse(final DiscoveryResponseData response) { + syncContext.execute( + new Runnable() { + @Override + public void run() { + if (closed) { + return; + } + responseReceived = true; + String respNonce = response.getNonce(); + // Nonce in each response is echoed back in the following ACK/NACK request. It is + // used for management server to identify which response the client is ACKing/NACking. + // To avoid confusion, client-initiated requests will always use the nonce in + // most recently received responses of each resource type. + ResourceType resourceType = response.getResourceType(); + switch (resourceType) { + case LDS: + ldsRespNonce = respNonce; + handleLdsResponse(response); + break; + case RDS: + rdsRespNonce = respNonce; + handleRdsResponse(response); + break; + case CDS: + cdsRespNonce = respNonce; + handleCdsResponse(response); + break; + case EDS: + edsRespNonce = respNonce; + handleEdsResponse(response); + break; + case UNKNOWN: + logger.log( + XdsLogLevel.WARNING, + "Received an unknown type of DiscoveryResponse\n{0}", + respNonce); + break; + default: + throw new AssertionError("Missing case in enum switch: " + resourceType); + } + } + }); + } + + final void onError(final Throwable t) { syncContext.execute(new Runnable() { @Override public void run() { @@ -1366,8 +1511,7 @@ public void run() { }); } - @Override - public void onCompleted() { + final void onCompleted() { syncContext.execute(new Runnable() { @Override public void run() { @@ -1430,7 +1574,7 @@ private void close(Exception error) { } closed = true; cleanUp(); - requestWriter.onError(error); + sendError(error); } private void cleanUp() { @@ -1444,126 +1588,245 @@ private void cleanUp() { * requested resource name (except for LDS as we always request for the singleton Listener) * as we need it to find resources in responses. */ - private void sendXdsRequest(String typeUrl, Collection resourceNames) { - checkState(requestWriter != null, "ADS stream has not been started"); - String version = ""; - String nonce = ""; - if (typeUrl.equals(ADS_TYPE_URL_LDS_V2)) { - version = ldsVersion; - nonce = ldsRespNonce; - logger.log(XdsLogLevel.INFO, "Sending LDS request for resources: {0}", resourceNames); - } else if (typeUrl.equals(ADS_TYPE_URL_RDS_V2)) { - checkArgument(resourceNames.size() == 1, - "RDS request requesting for more than one resource"); - version = rdsVersion; - nonce = rdsRespNonce; - rdsResourceName = resourceNames.iterator().next(); - logger.log(XdsLogLevel.INFO, "Sending RDS request for resources: {0}", resourceNames); - } else if (typeUrl.equals(ADS_TYPE_URL_CDS_V2)) { - version = cdsVersion; - nonce = cdsRespNonce; - logger.log(XdsLogLevel.INFO, "Sending CDS request for resources: {0}", resourceNames); - } else if (typeUrl.equals(ADS_TYPE_URL_EDS_V2)) { - version = edsVersion; - nonce = edsRespNonce; - logger.log(XdsLogLevel.INFO, "Sending EDS request for resources: {0}", resourceNames); - } - DiscoveryRequest request = - DiscoveryRequest - .newBuilder() - .setVersionInfo(version) - .setNode(node.toEnvoyProtoNodeV2()) - .addAllResourceNames(resourceNames) - .setTypeUrl(typeUrl) - .setResponseNonce(nonce) - .build(); - requestWriter.onNext(request); - logger.log(XdsLogLevel.DEBUG, "Sent DiscoveryRequest\n{0}", request); + private void sendXdsRequest(ResourceType resourceType, Collection resourceNames) { + String version; + String nonce; + switch (resourceType) { + case LDS: + version = ldsVersion; + nonce = ldsRespNonce; + logger.log(XdsLogLevel.INFO, "Sending LDS request for resources: {0}", resourceNames); + break; + case RDS: + checkArgument( + resourceNames.size() == 1, "RDS request requesting for more than one resource"); + version = rdsVersion; + nonce = rdsRespNonce; + rdsResourceName = resourceNames.iterator().next(); + logger.log(XdsLogLevel.INFO, "Sending RDS request for resources: {0}", resourceNames); + break; + case CDS: + version = cdsVersion; + nonce = cdsRespNonce; + logger.log(XdsLogLevel.INFO, "Sending CDS request for resources: {0}", resourceNames); + break; + case EDS: + version = edsVersion; + nonce = edsRespNonce; + logger.log(XdsLogLevel.INFO, "Sending EDS request for resources: {0}", resourceNames); + break; + case UNKNOWN: + default: + throw new AssertionError("Unknown or missing case in enum switch: " + resourceType); + } + DiscoveryRequestData request = + new DiscoveryRequestData(resourceType, resourceNames, version, nonce, node, null); + sendDiscoveryRequest(request); } /** * Sends a DiscoveryRequest with the given information as an ACK. Updates the latest accepted * version for the corresponding resource type. */ - private void sendAckRequest(String typeUrl, Collection resourceNames, + private void sendAckRequest(ResourceType resourceType, Collection resourceNames, String versionInfo) { - checkState(requestWriter != null, "ADS stream has not been started"); - String nonce = ""; - if (typeUrl.equals(ADS_TYPE_URL_LDS_V2)) { - ldsVersion = versionInfo; - nonce = ldsRespNonce; - } else if (typeUrl.equals(ADS_TYPE_URL_RDS_V2)) { - rdsVersion = versionInfo; - nonce = rdsRespNonce; - } else if (typeUrl.equals(ADS_TYPE_URL_CDS_V2)) { - cdsVersion = versionInfo; - nonce = cdsRespNonce; - } else if (typeUrl.equals(ADS_TYPE_URL_EDS_V2)) { - edsVersion = versionInfo; - nonce = edsRespNonce; - } - DiscoveryRequest request = - DiscoveryRequest - .newBuilder() - .setVersionInfo(versionInfo) - .setNode(node.toEnvoyProtoNodeV2()) - .addAllResourceNames(resourceNames) - .setTypeUrl(typeUrl) - .setResponseNonce(nonce) - .build(); - requestWriter.onNext(request); - logger.log(XdsLogLevel.DEBUG, "Sent ACK request\n{0}", request); + String nonce; + switch (resourceType) { + case LDS: + ldsVersion = versionInfo; + nonce = ldsRespNonce; + logger.log(XdsLogLevel.WARNING, "Sending ACK for LDS update, version: {0}", versionInfo); + break; + case RDS: + rdsVersion = versionInfo; + nonce = rdsRespNonce; + logger.log(XdsLogLevel.WARNING, "Sending ACK for RDS update, version: {0}", versionInfo); + break; + case CDS: + cdsVersion = versionInfo; + nonce = cdsRespNonce; + logger.log(XdsLogLevel.WARNING, "Sending ACK for CDS update, version: {0}", versionInfo); + break; + case EDS: + edsVersion = versionInfo; + nonce = edsRespNonce; + logger.log(XdsLogLevel.WARNING, "Sending ACK for EDS update, version: {0}", versionInfo); + break; + case UNKNOWN: + default: + throw new AssertionError("Unknown or missing case in enum switch: " + resourceType); + } + DiscoveryRequestData request = + new DiscoveryRequestData(resourceType, resourceNames, versionInfo, nonce, node, null); + sendDiscoveryRequest(request); } /** * Sends a DiscoveryRequest with the given information as an NACK. NACK takes the previous * accepted version. */ - private void sendNackRequest(String typeUrl, Collection resourceNames, + private void sendNackRequest(ResourceType resourceType, Collection resourceNames, String rejectVersion, String message) { + String versionInfo; + String nonce; + switch (resourceType) { + case LDS: + versionInfo = ldsVersion; + nonce = ldsRespNonce; + logger.log( + XdsLogLevel.WARNING, + "Sending NACK for LDS update, version: {0}, reason: {1}", + rejectVersion, + message); + break; + case RDS: + versionInfo = rdsVersion; + nonce = rdsRespNonce; + logger.log( + XdsLogLevel.WARNING, + "Sending NACK for RDS update, version: {0}, reason: {1}", + rejectVersion, + message); + break; + case CDS: + versionInfo = cdsVersion; + nonce = cdsRespNonce; + logger.log( + XdsLogLevel.WARNING, + "Sending NACK for CDS update, version: {0}, reason: {1}", + rejectVersion, + message); + break; + case EDS: + versionInfo = edsVersion; + nonce = edsRespNonce; + logger.log( + XdsLogLevel.WARNING, + "Sending NACK for EDS update, version: {0}, reason: {1}", + rejectVersion, + message); + break; + case UNKNOWN: + default: + throw new AssertionError("Unknown or missing case in enum switch: " + resourceType); + } + com.google.rpc.Status error = com.google.rpc.Status.newBuilder() + .setCode(Code.INVALID_ARGUMENT_VALUE) + .setMessage(message) + .build(); + DiscoveryRequestData request = + new DiscoveryRequestData(resourceType, resourceNames, versionInfo, nonce, node, error); + sendDiscoveryRequest(request); + } + } + + private final class AdsStreamV2 extends AbstractAdsStream { + private final io.envoyproxy.envoy.service.discovery.v2.AggregatedDiscoveryServiceGrpc + .AggregatedDiscoveryServiceStub stubV2; + private StreamObserver requestWriterV2; + + AdsStreamV2(io.envoyproxy.envoy.service.discovery.v2.AggregatedDiscoveryServiceGrpc + .AggregatedDiscoveryServiceStub stubV2) { + this.stubV2 = checkNotNull(stubV2, "stubV2"); + } + + @Override + void start() { + StreamObserver responseReaderV2 = + new StreamObserver() { + @Override + public void onNext(io.envoyproxy.envoy.api.v2.DiscoveryResponse response) { + DiscoveryResponseData responseData = + DiscoveryResponseData.fromEnvoyProtoV2(response); + if (logger.isLoggable(XdsLogLevel.DEBUG)) { + logger.log( + XdsLogLevel.DEBUG, + "Received {0} response:\n{1}", + responseData.getResourceType(), + respPrinter.print(response)); + } + onDiscoveryResponse(responseData); + } + + @Override + public void onError(Throwable t) { + AdsStreamV2.this.onError(t); + } + + @Override + public void onCompleted() { + AdsStreamV2.this.onCompleted(); + } + }; + requestWriterV2 = stubV2.withWaitForReady().streamAggregatedResources(responseReaderV2); + } + + @Override + void sendDiscoveryRequest(DiscoveryRequestData request) { + checkState(requestWriterV2 != null, "ADS stream has not been started"); + io.envoyproxy.envoy.api.v2.DiscoveryRequest requestProto = + request.toEnvoyProtoV2(); + requestWriterV2.onNext(requestProto); + logger.log(XdsLogLevel.DEBUG, "Sent DiscoveryRequest\n{0}", requestProto); + } + + @Override + void sendError(Exception error) { + requestWriterV2.onError(error); + } + } + + // AdsStream V3 + @SuppressWarnings("UnusedNestedClass") // Will be used once xds-v3 support is implemented. + private final class AdsStream extends AbstractAdsStream { + private final AggregatedDiscoveryServiceGrpc.AggregatedDiscoveryServiceStub stub; + private StreamObserver requestWriter; + + AdsStream(AggregatedDiscoveryServiceGrpc.AggregatedDiscoveryServiceStub stub) { + this.stub = checkNotNull(stub, "stub"); + } + + @Override + void start() { + StreamObserver responseReader = new StreamObserver() { + @Override + public void onNext(DiscoveryResponse response) { + DiscoveryResponseData responseData = + DiscoveryResponseData.fromEnvoyProto(response); + if (logger.isLoggable(XdsLogLevel.DEBUG)) { + logger.log( + XdsLogLevel.DEBUG, + "Received {0} response:\n{1}", + responseData.getResourceType(), + respPrinter.print(response)); + } + onDiscoveryResponse(responseData); + } + + @Override + public void onError(Throwable t) { + AdsStream.this.onError(t); + } + + @Override + public void onCompleted() { + AdsStream.this.onCompleted(); + } + }; + requestWriter = stub.withWaitForReady().streamAggregatedResources(responseReader); + } + + @Override + void sendDiscoveryRequest(DiscoveryRequestData request) { checkState(requestWriter != null, "ADS stream has not been started"); - String versionInfo = ""; - String nonce = ""; - if (typeUrl.equals(ADS_TYPE_URL_LDS_V2)) { - versionInfo = ldsVersion; - nonce = ldsRespNonce; - logger.log( - XdsLogLevel.WARNING, - "Rejecting LDS update, version: {0}, reason: {1}", rejectVersion, message); - } else if (typeUrl.equals(ADS_TYPE_URL_RDS_V2)) { - versionInfo = rdsVersion; - nonce = rdsRespNonce; - logger.log( - XdsLogLevel.WARNING, - "Rejecting RDS update, version: {0}, reason: {1}", rejectVersion, message); - } else if (typeUrl.equals(ADS_TYPE_URL_CDS_V2)) { - versionInfo = cdsVersion; - nonce = cdsRespNonce; - logger.log( - XdsLogLevel.WARNING, - "Rejecting CDS update, version: {0}, reason: {1}", rejectVersion, message); - } else if (typeUrl.equals(ADS_TYPE_URL_EDS_V2)) { - versionInfo = edsVersion; - nonce = edsRespNonce; - logger.log( - XdsLogLevel.WARNING, - "Rejecting EDS update, version: {0}, reason: {1}", rejectVersion, message); - } - DiscoveryRequest request = - DiscoveryRequest - .newBuilder() - .setVersionInfo(versionInfo) - .setNode(node.toEnvoyProtoNodeV2()) - .addAllResourceNames(resourceNames) - .setTypeUrl(typeUrl) - .setResponseNonce(nonce) - .setErrorDetail( - com.google.rpc.Status.newBuilder() - .setCode(Code.INVALID_ARGUMENT_VALUE) - .setMessage(message)) - .build(); - requestWriter.onNext(request); - logger.log(XdsLogLevel.DEBUG, "Sent NACK request\n{0}", request); + DiscoveryRequest requestProto = request.toEnvoyProto(); + requestWriter.onNext(requestProto); + logger.log(XdsLogLevel.DEBUG, "Sent DiscoveryRequest\n{0}", requestProto); + } + + @Override + void sendError(Exception error) { + requestWriter.onError(error); } } From 1ed24feecde019f8a8057baa0e3342ae429506a7 Mon Sep 17 00:00:00 2001 From: Chengyuan Zhang Date: Thu, 30 Jul 2020 22:21:38 +0000 Subject: [PATCH 28/88] xds: include a status for the result of ConfigSelector (#7260) --- .../java/io/grpc/InternalConfigSelector.java | 38 +++++++++++++++++-- .../io/grpc/InternalConfigSelectorTest.java | 12 ++++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/io/grpc/InternalConfigSelector.java b/api/src/main/java/io/grpc/InternalConfigSelector.java index 751ff017578..c2d204c5359 100644 --- a/api/src/main/java/io/grpc/InternalConfigSelector.java +++ b/api/src/main/java/io/grpc/InternalConfigSelector.java @@ -16,7 +16,9 @@ package io.grpc; +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 javax.annotation.Nullable; @@ -37,21 +39,43 @@ public abstract class InternalConfigSelector { public abstract Result selectConfig(LoadBalancer.PickSubchannelArgs args); public static final class Result { + private final Status status; + @Nullable private final Object config; + @Nullable private final CallOptions callOptions; @Nullable private final Runnable committedCallback; - private Result(Object config, CallOptions callOptions, @Nullable Runnable committedCallback) { - this.config = checkNotNull(config, "config"); - this.callOptions = checkNotNull(callOptions, "callOptions"); + private Result( + Status status, Object config, CallOptions callOptions, Runnable committedCallback) { + this.status = checkNotNull(status, "status"); + this.config = config; + this.callOptions = callOptions; this.committedCallback = committedCallback; } + /** + * Creates a {@code Result} with the given error status. + */ + public static Result forError(Status status) { + checkArgument(!status.isOk(), "status is OK"); + return new Result(status, null, null, null); + } + + /** + * Returns the status of the config selection operation. If status is not {@link Status#OK}, + * this result should not be used. + */ + public Status getStatus() { + return status; + } + /** * Returns a parsed config. Must have been returned via * ServiceConfigParser.parseServiceConfig().getConfig() */ + @Nullable public Object getConfig() { return config; } @@ -59,6 +83,7 @@ public Object getConfig() { /** * Returns a config-selector-modified CallOptions for the RPC. */ + @Nullable public CallOptions getCallOptions() { return callOptions; } @@ -112,8 +137,13 @@ public Builder setCommittedCallback(@Nullable Runnable committedCallback) { return this; } + /** + * Build this {@link Result}. + */ public Result build() { - return new Result(config, callOptions, committedCallback); + checkState(config != null, "config is not set"); + checkState(callOptions != null, "callOptions is not set"); + return new Result(Status.OK, config, callOptions, committedCallback); } } } diff --git a/api/src/test/java/io/grpc/InternalConfigSelectorTest.java b/api/src/test/java/io/grpc/InternalConfigSelectorTest.java index 37ab2e54cda..f094065cdd7 100644 --- a/api/src/test/java/io/grpc/InternalConfigSelectorTest.java +++ b/api/src/test/java/io/grpc/InternalConfigSelectorTest.java @@ -18,6 +18,8 @@ import static com.google.common.truth.Truth.assertThat; +import io.grpc.InternalConfigSelector.Result; +import io.grpc.Status.Code; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -37,6 +39,7 @@ public void run() {} InternalConfigSelector.Result result = builder.setConfig(config).setCallOptions(callOptions).build(); + assertThat(result.getStatus().isOk()).isTrue(); assertThat(result.getConfig()).isEqualTo(config); assertThat(result.getCallOptions()).isEqualTo(callOptions); assertThat(result.getCommittedCallback()).isNull(); @@ -46,8 +49,17 @@ public void run() {} .setCallOptions(callOptions) .setCommittedCallback(committedCallback) .build(); + assertThat(result.getStatus().isOk()).isTrue(); assertThat(result.getConfig()).isEqualTo(config); assertThat(result.getCallOptions()).isEqualTo(callOptions); assertThat(result.getCommittedCallback()).isSameInstanceAs(committedCallback); } + + @Test + public void errorResult() { + Result result = Result.forError(Status.INTERNAL.withDescription("failed")); + assertThat(result.getStatus().isOk()).isFalse(); + assertThat(result.getStatus().getCode()).isEqualTo(Code.INTERNAL); + assertThat(result.getStatus().getDescription()).isEqualTo("failed"); + } } From 22b5480aed6c22290f248929cf883aa58fb06771 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Thu, 30 Jul 2020 18:00:37 -0700 Subject: [PATCH 29/88] xds: Add server features support to Bootstrapper In preparation for xds-v3 support. --- .../main/java/io/grpc/xds/Bootstrapper.java | 18 +++++- xds/src/main/java/io/grpc/xds/XdsClient.java | 35 +++++++++-- .../main/java/io/grpc/xds/XdsClientImpl.java | 3 +- .../java/io/grpc/xds/BootstrapperTest.java | 4 ++ .../java/io/grpc/xds/EdsLoadBalancerTest.java | 10 ++-- .../java/io/grpc/xds/XdsClientImplTest.java | 12 ++-- .../xds/XdsClientImplTestForListener.java | 12 ++-- .../test/java/io/grpc/xds/XdsClientTest.java | 59 +++++++++++++++++++ .../xds/XdsNameResolverIntegrationTest.java | 9 ++- 9 files changed, 134 insertions(+), 28 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/Bootstrapper.java b/xds/src/main/java/io/grpc/xds/Bootstrapper.java index f967d43d8fd..6fa3c4e5710 100644 --- a/xds/src/main/java/io/grpc/xds/Bootstrapper.java +++ b/xds/src/main/java/io/grpc/xds/Bootstrapper.java @@ -111,7 +111,11 @@ static BootstrapInfo parseConfig(String rawData) throws IOException { channelCredsOptions.add(creds); } } - servers.add(new ServerInfo(serverUri, channelCredsOptions)); + List serverFeatures = JsonUtil.getListOfStrings(serverConfig, "server_features"); + if (serverFeatures != null) { + logger.log(XdsLogLevel.INFO, "Server features: {0}", serverFeatures); + } + servers.add(new ServerInfo(serverUri, channelCredsOptions, serverFeatures)); } Node.Builder nodeBuilder = Node.newBuilder(); @@ -196,11 +200,14 @@ String getType() { static class ServerInfo { private final String serverUri; private final List channelCredsList; + @Nullable + private final List serverFeatures; @VisibleForTesting - ServerInfo(String serverUri, List channelCredsList) { + ServerInfo(String serverUri, List channelCredsList, List serverFeatures) { this.serverUri = serverUri; this.channelCredsList = channelCredsList; + this.serverFeatures = serverFeatures; } String getServerUri() { @@ -210,6 +217,12 @@ String getServerUri() { List getChannelCredentials() { return Collections.unmodifiableList(channelCredsList); } + + List getServerFeatures() { + return serverFeatures == null + ? Collections.emptyList() + : Collections.unmodifiableList(serverFeatures); + } } /** @@ -240,6 +253,5 @@ List getServers() { public Node getNode() { return node; } - } } diff --git a/xds/src/main/java/io/grpc/xds/XdsClient.java b/xds/src/main/java/io/grpc/xds/XdsClient.java index b74ec3360b3..1c895c878ab 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClient.java +++ b/xds/src/main/java/io/grpc/xds/XdsClient.java @@ -601,13 +601,17 @@ public synchronized XdsClient returnObject(Object object) { * Factory for creating channels to xDS severs. */ abstract static class XdsChannelFactory { - private static final XdsChannelFactory DEFAULT_INSTANCE = new XdsChannelFactory() { + @VisibleForTesting + static boolean experimentalV3SupportEnvVar = Boolean.parseBoolean( + System.getenv("GRPC_XDS_EXPERIMENTAL_V3_SUPPORT")); + private static final String XDS_V3_SERVER_FEATURE = "xds_v3"; + private static final XdsChannelFactory DEFAULT_INSTANCE = new XdsChannelFactory() { /** * Creates a channel to the first server in the given list. */ @Override - ManagedChannel createChannel(List servers) { + XdsChannel createChannel(List servers) { checkArgument(!servers.isEmpty(), "No management server provided."); XdsLogger logger = XdsLogger.withPrefix("xds-client-channel-factory"); ServerInfo serverInfo = servers.get(0); @@ -629,9 +633,13 @@ ManagedChannel createChannel(List servers) { channelBuilder = ManagedChannelBuilder.forTarget(serverUri); } - return channelBuilder + ManagedChannel channel = channelBuilder .keepAliveTime(5, TimeUnit.MINUTES) .build(); + boolean useProtocolV3 = experimentalV3SupportEnvVar + && serverInfo.getServerFeatures().contains(XDS_V3_SERVER_FEATURE); + + return new XdsChannel(channel, useProtocolV3); } }; @@ -642,6 +650,25 @@ static XdsChannelFactory getInstance() { /** * Creates a channel to one of the provided management servers. */ - abstract ManagedChannel createChannel(List servers); + abstract XdsChannel createChannel(List servers); + } + + static final class XdsChannel { + private final ManagedChannel managedChannel; + private final boolean useProtocolV3; + + @VisibleForTesting + XdsChannel(ManagedChannel managedChannel, boolean useProtocolV3) { + this.managedChannel = managedChannel; + this.useProtocolV3 = useProtocolV3; + } + + ManagedChannel getManagedChannel() { + return managedChannel; + } + + boolean isUseProtocolV3() { + return useProtocolV3; + } } } diff --git a/xds/src/main/java/io/grpc/xds/XdsClientImpl.java b/xds/src/main/java/io/grpc/xds/XdsClientImpl.java index 4991cdf1d83..3b972c2b30d 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClientImpl.java +++ b/xds/src/main/java/io/grpc/xds/XdsClientImpl.java @@ -200,7 +200,8 @@ final class XdsClientImpl extends XdsClient { this.targetName = checkNotNull(targetName, "targetName"); this.channel = checkNotNull(channelFactory, "channelFactory") - .createChannel(checkNotNull(servers, "servers")); + .createChannel(checkNotNull(servers, "servers")) + .getManagedChannel(); this.node = checkNotNull(node, "node"); this.syncContext = checkNotNull(syncContext, "syncContext"); this.timeService = checkNotNull(timeService, "timeService"); diff --git a/xds/src/test/java/io/grpc/xds/BootstrapperTest.java b/xds/src/test/java/io/grpc/xds/BootstrapperTest.java index 10d2c109167..6c1fd2111ba 100644 --- a/xds/src/test/java/io/grpc/xds/BootstrapperTest.java +++ b/xds/src/test/java/io/grpc/xds/BootstrapperTest.java @@ -112,6 +112,9 @@ public void parseBootstrap_validData_multipleXdsServers() throws IOException { + " \"server_uri\": \"trafficdirector-foo.googleapis.com:443\",\n" + " \"channel_creds\": [\n" + " {\"type\": \"tls\"}, {\"type\": \"loas\"}, {\"type\": \"google_default\"}\n" + + " ],\n" + + " \"server_features\": [\n" + + " \"xds_v3\", \"foo\", \"bar\"\n" + " ]\n" + " },\n" + " {\n" @@ -134,6 +137,7 @@ public void parseBootstrap_validData_multipleXdsServers() throws IOException { assertThat(serverInfoList.get(0).getChannelCredentials().get(2).getType()) .isEqualTo("google_default"); assertThat(serverInfoList.get(0).getChannelCredentials().get(2).getConfig()).isNull(); + assertThat(serverInfoList.get(0).getServerFeatures()).contains("xds_v3"); assertThat(serverInfoList.get(1).getServerUri()) .isEqualTo("trafficdirector-bar.googleapis.com:443"); assertThat(serverInfoList.get(1).getChannelCredentials()).isEmpty(); diff --git a/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java index 66ce45c3ff4..a321933f867 100644 --- a/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java @@ -78,6 +78,7 @@ import io.grpc.xds.EnvoyProtoData.Node; import io.grpc.xds.LocalityStore.LocalityStoreFactory; import io.grpc.xds.XdsClient.EndpointUpdate; +import io.grpc.xds.XdsClient.XdsChannel; import io.grpc.xds.XdsClient.XdsChannelFactory; import java.net.InetSocketAddress; import java.util.ArrayDeque; @@ -135,10 +136,10 @@ public void uncaughtException(Thread t, Throwable e) { private final Map childBalancers = new HashMap<>(); private final XdsChannelFactory channelFactory = new XdsChannelFactory() { @Override - ManagedChannel createChannel(List servers) { + XdsChannel createChannel(List servers) { assertThat(Iterables.getOnlyElement(servers).getServerUri()) .isEqualTo("trafficdirector.googleapis.com"); - return channel; + return new XdsChannel(channel, false); } }; @@ -228,9 +229,8 @@ public StreamObserver streamAggregatedResources( .forName(serverName) .directExecutor() .build()); - final List serverList = - ImmutableList.of( - new ServerInfo("trafficdirector.googleapis.com", ImmutableList.of())); + final List serverList = ImmutableList.of( + new ServerInfo("trafficdirector.googleapis.com", ImmutableList.of(), null)); Node node = Node.newBuilder().build(); BootstrapInfo bootstrapInfo = new BootstrapInfo(serverList, node); doReturn(bootstrapInfo).when(bootstrapper).readBootstrap(); diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java index a63b5401c03..19c3ec30bee 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java @@ -100,6 +100,7 @@ import io.grpc.xds.XdsClient.ConfigWatcher; import io.grpc.xds.XdsClient.EndpointUpdate; import io.grpc.xds.XdsClient.EndpointWatcher; +import io.grpc.xds.XdsClient.XdsChannel; import io.grpc.xds.XdsClient.XdsChannelFactory; import io.grpc.xds.XdsClientImpl.MessagePrinter; import java.io.IOException; @@ -279,13 +280,14 @@ public void cancelled(Context context) { cleanupRule.register(InProcessChannelBuilder.forName(serverName).directExecutor().build()); List servers = - ImmutableList.of(new ServerInfo(serverName, ImmutableList.of())); + ImmutableList.of(new ServerInfo(serverName, ImmutableList.of(), null)); XdsChannelFactory channelFactory = new XdsChannelFactory() { @Override - ManagedChannel createChannel(List servers) { - assertThat(Iterables.getOnlyElement(servers).getServerUri()).isEqualTo(serverName); - assertThat(Iterables.getOnlyElement(servers).getChannelCredentials()).isEmpty(); - return channel; + XdsChannel createChannel(List servers) { + ServerInfo serverInfo = Iterables.getOnlyElement(servers); + assertThat(serverInfo.getServerUri()).isEqualTo(serverName); + assertThat(serverInfo.getChannelCredentials()).isEmpty(); + return new XdsChannel(channel, false); } }; diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTestForListener.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTestForListener.java index 91d22c69ee0..d2452c86239 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTestForListener.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTestForListener.java @@ -71,6 +71,7 @@ import io.grpc.xds.XdsClient.ConfigWatcher; import io.grpc.xds.XdsClient.ListenerUpdate; import io.grpc.xds.XdsClient.ListenerWatcher; +import io.grpc.xds.XdsClient.XdsChannel; import io.grpc.xds.XdsClient.XdsChannelFactory; import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil; import java.io.IOException; @@ -201,13 +202,14 @@ public void cancelled(Context context) { cleanupRule.register(InProcessChannelBuilder.forName(serverName).directExecutor().build()); List servers = - ImmutableList.of(new ServerInfo(serverName, ImmutableList.of())); + ImmutableList.of(new ServerInfo(serverName, ImmutableList.of(), null)); XdsChannelFactory channelFactory = new XdsChannelFactory() { @Override - ManagedChannel createChannel(List servers) { - assertThat(Iterables.getOnlyElement(servers).getServerUri()).isEqualTo(serverName); - assertThat(Iterables.getOnlyElement(servers).getChannelCredentials()).isEmpty(); - return channel; + XdsChannel createChannel(List servers) { + ServerInfo serverInfo = Iterables.getOnlyElement(servers); + assertThat(serverInfo.getServerUri()).isEqualTo(serverName); + assertThat(serverInfo.getChannelCredentials()).isEmpty(); + return new XdsChannel(channel, false); } }; diff --git a/xds/src/test/java/io/grpc/xds/XdsClientTest.java b/xds/src/test/java/io/grpc/xds/XdsClientTest.java index 56fca6dc09b..9278a80dbb5 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientTest.java @@ -21,7 +21,12 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import com.google.common.collect.ImmutableList; +import io.grpc.xds.Bootstrapper.ChannelCreds; +import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.XdsClient.RefCountedXdsClientObjectPool; +import io.grpc.xds.XdsClient.XdsChannel; +import io.grpc.xds.XdsClient.XdsChannelFactory; import io.grpc.xds.XdsClient.XdsClientFactory; import org.junit.Rule; import org.junit.Test; @@ -102,4 +107,58 @@ XdsClient createXdsClient() { XdsClient xdsClient2 = xdsClientPool.getObject(); assertThat(xdsClient2).isNotSameInstanceAs(xdsClient1); } + + @Test + public void channelFactorySupportsV3() { + boolean originalV3SupportEnvVar = XdsChannelFactory.experimentalV3SupportEnvVar; + try { + XdsChannelFactory xdsChannelFactory = XdsChannelFactory.getInstance(); + XdsChannelFactory.experimentalV3SupportEnvVar = true; + XdsChannel xdsChannel = + xdsChannelFactory.createChannel( + ImmutableList.of( + new ServerInfo( + "xdsserver.com", + ImmutableList.of(), + ImmutableList.of()), + new ServerInfo( + "xdsserver2.com", + ImmutableList.of(), + ImmutableList.of("xds_v3")))); + xdsChannel.getManagedChannel().shutdown(); + assertThat(xdsChannel.isUseProtocolV3()).isFalse(); + + XdsChannelFactory.experimentalV3SupportEnvVar = false; + xdsChannel = + xdsChannelFactory.createChannel( + ImmutableList.of( + new ServerInfo( + "xdsserver.com", + ImmutableList.of(), + ImmutableList.of("xds_v3")), + new ServerInfo( + "xdsserver2.com", + ImmutableList.of(), + ImmutableList.of("baz")))); + xdsChannel.getManagedChannel().shutdown(); + assertThat(xdsChannel.isUseProtocolV3()).isFalse(); + + XdsChannelFactory.experimentalV3SupportEnvVar = true; + xdsChannel = + xdsChannelFactory.createChannel( + ImmutableList.of( + new ServerInfo( + "xdsserver.com", + ImmutableList.of(), + ImmutableList.of("xds_v3")), + new ServerInfo( + "xdsserver2.com", + ImmutableList.of(), + ImmutableList.of("baz")))); + xdsChannel.getManagedChannel().shutdown(); + assertThat(xdsChannel.isUseProtocolV3()).isTrue(); + } finally { + XdsChannelFactory.experimentalV3SupportEnvVar = originalV3SupportEnvVar; + } + } } diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverIntegrationTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverIntegrationTest.java index 4584bcc6ea7..804c50f5106 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverIntegrationTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverIntegrationTest.java @@ -65,6 +65,7 @@ import io.grpc.testing.GrpcCleanupRule; import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.EnvoyProtoData.Node; +import io.grpc.xds.XdsClient.XdsChannel; import io.grpc.xds.XdsClient.XdsChannelFactory; import java.io.IOException; import java.util.ArrayDeque; @@ -159,18 +160,16 @@ public StreamObserver streamAggregatedResources( channelFactory = new XdsChannelFactory() { @Override - ManagedChannel createChannel(List servers) { + XdsChannel createChannel(List servers) { assertThat(Iterables.getOnlyElement(servers).getServerUri()).isEqualTo(serverName); - return channel; + return new XdsChannel(channel, false); } }; Bootstrapper bootstrapper = new Bootstrapper() { @Override public BootstrapInfo readBootstrap() { List serverList = - ImmutableList.of( - new ServerInfo(serverName, - ImmutableList.of())); + ImmutableList.of(new ServerInfo(serverName, ImmutableList.of(), null)); return new BootstrapInfo(serverList, FAKE_BOOTSTRAP_NODE); } }; From 96ad633868ff09144e0415c00118efc6c57990a2 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 30 Jul 2020 13:59:34 -0700 Subject: [PATCH 30/88] gradle: Use built-in javadoc and sources jars --- build.gradle | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/build.gradle b/build.gradle index e7e6b5383af..083475bb07f 100644 --- a/build.gradle +++ b/build.gradle @@ -492,14 +492,9 @@ subprojects { } plugins.withId("java") { - task javadocJar(type: Jar) { - classifier = 'javadoc' - from javadoc - } - - task sourcesJar(type: Jar) { - classifier = 'sources' - from sourceSets.main.allSource + java { + withJavadocJar() + withSourcesJar() } publishing { @@ -508,9 +503,6 @@ subprojects { if (project.name != 'grpc-netty-shaded') { from components.java } - - artifact javadocJar - artifact sourcesJar } } } From 96a5c5df578cf6c4c539957e09610f507f210879 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 30 Jul 2020 14:03:51 -0700 Subject: [PATCH 31/88] buildscripts: Avoid signing sha256 and sha512 checksums Starting in Gradle 6.0 maven-publish began including sha256 and sha512 checksums, in addition to the previous md5 and sha1 checksums. We don't want .sha256.asc and .sha512.asc files, as they serve no purpose. --- buildscripts/kokoro/upload_artifacts.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildscripts/kokoro/upload_artifacts.sh b/buildscripts/kokoro/upload_artifacts.sh index 45e49d1e368..8d7f2f5b365 100644 --- a/buildscripts/kokoro/upload_artifacts.sh +++ b/buildscripts/kokoro/upload_artifacts.sh @@ -56,7 +56,7 @@ gpg --version if gpg --version | grep 'gpg (GnuPG) 1.'; then # This command was tested on 1.4.16 find "$LOCAL_MVN_ARTIFACTS" -type f \ - -not -name "maven-metadata.xml*" -not -name "*.sha1" -not -name "*.md5" -exec \ + -not -name "maven-metadata.xml*" -not -name "*.sha*" -not -name "*.md5" -exec \ bash -c 'cat ~/java_signing/passphrase | gpg --batch --passphrase-fd 0 --detach-sign -a {}' \; fi @@ -64,7 +64,7 @@ fi if gpg --version | grep 'gpg (GnuPG) 2.'; then # This command was tested on 2.2.2 find "$LOCAL_MVN_ARTIFACTS" -type f \ - -not -name "maven-metadata.xml*" -not -name "*.sha1" -not -name "*.md5" -exec \ + -not -name "maven-metadata.xml*" -not -name "*.sha*" -not -name "*.md5" -exec \ gpg --batch --passphrase-file ~/java_signing/passphrase --pinentry-mode loopback \ --detach-sign -a {} \; fi From 026673cff5622c997da45a71d72e143dfe9443ff Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Fri, 31 Jul 2020 06:55:52 -0700 Subject: [PATCH 32/88] gradle: Fix Gradle 6 warnings Most of these are easy "replace X with Y." The CreateStartScripts changes were because the scripts were being included in the output zip/tar multiple times. The was because they were all using the same output directory, and the entire output directory was being included for each. The output directory tmp/ was particularly poor because other tasks were dumping things into it, so our zip/tar was including those junk files as well. --- benchmarks/build.gradle | 8 ++++---- build.gradle | 5 ++--- gae-interop-testing/gae-jdk8/build.gradle | 3 ++- interop-testing/build.gradle | 20 ++++++++++---------- settings.gradle | 2 +- testing-proto/build.gradle | 2 +- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index b3137ca272c..b653a9d2811 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -60,7 +60,7 @@ task qps_client(type: CreateStartScripts) { defaultJvmOpts = [ "-javaagent:" + configurations.alpnagent.asPath ].plus(vmArgs) - outputDir = new File(project.buildDir, 'tmp') + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) classpath = startScripts.classpath } @@ -70,14 +70,14 @@ task openloop_client(type: CreateStartScripts) { defaultJvmOpts = [ "-javaagent:" + configurations.alpnagent.asPath ].plus(vmArgs) - outputDir = new File(project.buildDir, 'tmp') + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) classpath = startScripts.classpath } task qps_server(type: CreateStartScripts) { mainClassName = "io.grpc.benchmarks.qps.AsyncServer" applicationName = "qps_server" - outputDir = new File(project.buildDir, 'tmp') + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) classpath = startScripts.classpath } @@ -87,7 +87,7 @@ task benchmark_worker(type: CreateStartScripts) { defaultJvmOpts = [ "-javaagent:" + configurations.alpnagent.asPath ].plus(vmArgs) - outputDir = new File(project.buildDir, 'tmp') + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) classpath = startScripts.classpath } diff --git a/build.gradle b/build.gradle index 083475bb07f..aaf4794b1c9 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,6 @@ import net.ltgt.gradle.errorprone.CheckSeverity subprojects { apply plugin: "checkstyle" - apply plugin: "maven" apply plugin: "idea" apply plugin: "signing" apply plugin: "jacoco" @@ -286,7 +285,7 @@ subprojects { jacoco { toolVersion = "0.8.2" } checkstyle { - configDir = file("$rootDir/buildscripts") + configDirectory = file("$rootDir/buildscripts") toolVersion = "6.17" ignoreFailures = false if (rootProject.hasProperty("checkstyle.ignoreFailures")) { @@ -315,7 +314,7 @@ subprojects { targetCompatibility = 1.7 dependencies { - testCompile libraries.junit, + testImplementation libraries.junit, libraries.mockito, libraries.truth } diff --git a/gae-interop-testing/gae-jdk8/build.gradle b/gae-interop-testing/gae-jdk8/build.gradle index 5991b20abb3..8ed1bc5372b 100644 --- a/gae-interop-testing/gae-jdk8/build.gradle +++ b/gae-interop-testing/gae-jdk8/build.gradle @@ -20,7 +20,7 @@ buildscript { url "https://maven-central.storage-download.googleapis.com/maven2/" } } dependencies { - classpath 'com.google.cloud.tools:appengine-gradle-plugin:1.3.5' + classpath 'com.google.cloud.tools:appengine-gradle-plugin:2.3.0' classpath 'com.squareup.okhttp:okhttp:2.7.4' } } @@ -75,6 +75,7 @@ appengine { deploy { // deploy configuration + projectId = 'GCLOUD_CONFIG' // default - stop the current version stopPreviousVersion = System.getProperty('gaeStopPreviousVersion') ?: true // default - do not make this the promoted version diff --git a/interop-testing/build.gradle b/interop-testing/build.gradle index 5f529c685a6..c4ddcc53f54 100644 --- a/interop-testing/build.gradle +++ b/interop-testing/build.gradle @@ -63,7 +63,7 @@ task test_client(type: CreateStartScripts) { defaultJvmOpts = [ "-javaagent:JAVAAGENT_APP_HOME" + configurations.alpnagent.singleFile.name ] - outputDir = new File(project.buildDir, 'tmp') + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) classpath = startScripts.classpath doLast { unixScript.text = unixScript.text.replace('JAVAAGENT_APP_HOME', '\$APP_HOME/lib/') @@ -74,21 +74,21 @@ task test_client(type: CreateStartScripts) { task test_server(type: CreateStartScripts) { mainClassName = "io.grpc.testing.integration.TestServiceServer" applicationName = "test-server" - outputDir = new File(project.buildDir, 'tmp') + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) classpath = startScripts.classpath } task reconnect_test_client(type: CreateStartScripts) { mainClassName = "io.grpc.testing.integration.ReconnectTestClient" applicationName = "reconnect-test-client" - outputDir = new File(project.buildDir, 'tmp') + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) classpath = startScripts.classpath } task stresstest_client(type: CreateStartScripts) { mainClassName = "io.grpc.testing.integration.StressTestClient" applicationName = "stresstest-client" - outputDir = new File(project.buildDir, 'tmp') + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) classpath = startScripts.classpath defaultJvmOpts = [ "-verbose:gc", @@ -99,14 +99,14 @@ task stresstest_client(type: CreateStartScripts) { task http2_client(type: CreateStartScripts) { mainClassName = "io.grpc.testing.integration.Http2Client" applicationName = "http2-client" - outputDir = new File(project.buildDir, 'tmp') + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) classpath = startScripts.classpath } task grpclb_long_lived_affinity_test_client(type: CreateStartScripts) { mainClassName = "io.grpc.testing.integration.GrpclbLongLivedAffinityTestClient" applicationName = "grpclb-long-lived-affinity-test-client" - outputDir = new File(project.buildDir, 'tmp') + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) classpath = startScripts.classpath defaultJvmOpts = [ "-Dio.grpc.internal.DnsNameResolverProvider.enable_service_config=true" @@ -116,8 +116,8 @@ task grpclb_long_lived_affinity_test_client(type: CreateStartScripts) { task grpclb_fallback_test_client (type: CreateStartScripts) { mainClassName = "io.grpc.testing.integration.GrpclbFallbackTestClient" applicationName = "grpclb-fallback-test-client" - outputDir = new File(project.buildDir, 'tmp') - classpath = jar.outputs.files + configurations.runtime + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath defaultJvmOpts = [ "-Dio.grpc.internal.DnsNameResolverProvider.enable_service_config=true" ] @@ -126,14 +126,14 @@ task grpclb_fallback_test_client (type: CreateStartScripts) { task xds_test_client(type: CreateStartScripts) { mainClassName = "io.grpc.testing.integration.XdsTestClient" applicationName = "xds-test-client" - outputDir = new File(project.buildDir, 'tmp') + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) classpath = startScripts.classpath } task xds_test_server(type: CreateStartScripts) { mainClassName = "io.grpc.testing.integration.XdsTestServer" applicationName = "xds-test-server" - outputDir = new File(project.buildDir, 'tmp') + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) classpath = startScripts.classpath } diff --git a/settings.gradle b/settings.gradle index 2891d918071..9592c7eda93 100644 --- a/settings.gradle +++ b/settings.gradle @@ -9,7 +9,7 @@ pluginManagement { id "digital.wup.android-maven-publish" version "3.6.2" id "me.champeau.gradle.japicmp" version "0.2.5" id "me.champeau.gradle.jmh" version "0.5.0" - id "net.ltgt.errorprone" version "0.8.1" + id "net.ltgt.errorprone" version "1.2.1" id "ru.vyarus.animalsniffer" version "1.5.0" } resolutionStrategy { diff --git a/testing-proto/build.gradle b/testing-proto/build.gradle index 145ded4ef11..f12fe9250cc 100644 --- a/testing-proto/build.gradle +++ b/testing-proto/build.gradle @@ -12,7 +12,7 @@ dependencies { project(':grpc-stub') compileOnly libraries.javax_annotation testImplementation libraries.truth - testRuntime libraries.javax_annotation + testRuntimeOnly libraries.javax_annotation } configureProtoCompilation() From 800ef216a8cc05a435f30cf02cfd775143d1d724 Mon Sep 17 00:00:00 2001 From: cindyxue <32377977+cindyxue@users.noreply.github.com> Date: Fri, 31 Jul 2020 10:47:19 -0700 Subject: [PATCH 33/88] xds: Added IncompleteData interface in mock CEL library (#7243) * Added the IncompleteData interface in Java Cel library stub --- .../rbac/engine/cel/IncompleteData.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/IncompleteData.java diff --git a/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/IncompleteData.java b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/IncompleteData.java new file mode 100644 index 00000000000..7de8d4282bb --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/cel/IncompleteData.java @@ -0,0 +1,27 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds.internal.rbac.engine.cel; + +/** + * Interpreter returns an instance of {@code IncompleteData} if + * an object is an instance of {@link PartialMessage}. + * + *

This is a Java stub for evaluating Common Expression Language (CEL). + * More information about CEL can be found in https://github.com/google/cel-spec. + * Once Java CEL has been open-sourced, this stub will be removed. + */ +public interface IncompleteData {} From 14af76cab1004dede876521f76d5bac49c5471b7 Mon Sep 17 00:00:00 2001 From: Chengyuan Zhang Date: Fri, 31 Jul 2020 19:12:00 +0000 Subject: [PATCH 34/88] xds: parse timeout from RDS responses (#7257) --- .../main/java/io/grpc/xds/EnvoyProtoData.java | 31 ++++++++++-- .../java/io/grpc/xds/EnvoyProtoDataTest.java | 48 +++++++++++++++---- .../java/io/grpc/xds/XdsClientImplTest.java | 9 ++-- .../java/io/grpc/xds/XdsNameResolverTest.java | 5 +- 4 files changed, 74 insertions(+), 19 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/EnvoyProtoData.java b/xds/src/main/java/io/grpc/xds/EnvoyProtoData.java index eeae2f96174..14a62f29f5a 100644 --- a/xds/src/main/java/io/grpc/xds/EnvoyProtoData.java +++ b/xds/src/main/java/io/grpc/xds/EnvoyProtoData.java @@ -26,6 +26,7 @@ import com.google.protobuf.NullValue; import com.google.protobuf.Struct; import com.google.protobuf.Value; +import com.google.protobuf.util.Durations; import com.google.re2j.Pattern; import com.google.re2j.PatternSyntaxException; import io.envoyproxy.envoy.type.v3.FractionalPercent; @@ -40,6 +41,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; /** @@ -1067,6 +1069,7 @@ static StructOrError convertEnvoyProtoHeaderMatcher( /** See corresponding Envoy proto message {@link io.envoyproxy.envoy.api.v2.route.RouteAction}. */ static final class RouteAction { + private final long timeoutNano; // Exactly one of the following fields is non-null. @Nullable private final String cluster; @@ -1074,11 +1077,20 @@ static final class RouteAction { private final List weightedClusters; @VisibleForTesting - RouteAction(@Nullable String cluster, @Nullable List weightedClusters) { + RouteAction( + long timeoutNano, + @Nullable String cluster, + @Nullable List weightedClusters) { + this.timeoutNano = timeoutNano; this.cluster = cluster; this.weightedClusters = weightedClusters; } + + Long getTimeoutNano() { + return timeoutNano; + } + @Nullable String getCluster() { return cluster; @@ -1098,18 +1110,20 @@ public boolean equals(Object o) { return false; } RouteAction that = (RouteAction) o; - return Objects.equals(cluster, that.cluster) + return Objects.equals(timeoutNano, that.timeoutNano) + && Objects.equals(cluster, that.cluster) && Objects.equals(weightedClusters, that.weightedClusters); } @Override public int hashCode() { - return Objects.hash(cluster, weightedClusters); + return Objects.hash(timeoutNano, cluster, weightedClusters); } @Override public String toString() { ToStringHelper toStringHelper = MoreObjects.toStringHelper(this); + toStringHelper.add("timeout", timeoutNano + "ns"); if (cluster != null) { toStringHelper.add("cluster", cluster); } @@ -1146,7 +1160,16 @@ static StructOrError fromEnvoyProtoRouteAction( return StructOrError.fromError( "Unknown cluster specifier: " + proto.getClusterSpecifierCase()); } - return StructOrError.fromStruct(new RouteAction(cluster, weightedClusters)); + long timeoutNano = TimeUnit.SECONDS.toNanos(15L); // default 15s + if (proto.hasMaxGrpcTimeout()) { + timeoutNano = Durations.toNanos(proto.getMaxGrpcTimeout()); + } else if (proto.hasTimeout()) { + timeoutNano = Durations.toNanos(proto.getTimeout()); + } + if (timeoutNano == 0) { + timeoutNano = Long.MAX_VALUE; + } + return StructOrError.fromStruct(new RouteAction(timeoutNano, cluster, weightedClusters)); } } diff --git a/xds/src/test/java/io/grpc/xds/EnvoyProtoDataTest.java b/xds/src/test/java/io/grpc/xds/EnvoyProtoDataTest.java index fa278fb8147..24e739bf560 100644 --- a/xds/src/test/java/io/grpc/xds/EnvoyProtoDataTest.java +++ b/xds/src/test/java/io/grpc/xds/EnvoyProtoDataTest.java @@ -24,6 +24,7 @@ import com.google.protobuf.Struct; import com.google.protobuf.UInt32Value; import com.google.protobuf.Value; +import com.google.protobuf.util.Durations; import com.google.re2j.Pattern; import io.envoyproxy.envoy.config.core.v3.RuntimeFractionalPercent; import io.envoyproxy.envoy.config.route.v3.QueryParameterMatcher; @@ -44,6 +45,7 @@ import io.grpc.xds.RouteMatch.PathMatcher; import java.util.Arrays; import java.util.Collections; +import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; import org.junit.Test; import org.junit.runner.RunWith; @@ -206,7 +208,7 @@ public void convertRoute() { new Route( new RouteMatch(new PathMatcher("/service/method", null, null), Collections.emptyList(), null), - new RouteAction("cluster-foo", null))); + new RouteAction(TimeUnit.SECONDS.toNanos(15L), "cluster-foo", null))); io.envoyproxy.envoy.config.route.v3.Route unsupportedProto = io.envoyproxy.envoy.config.route.v3.Route.newBuilder() @@ -393,27 +395,51 @@ public void convertRouteMatch_withRuntimeFraction() { @Test public void convertRouteAction() { - // cluster_specifier = cluster + // cluster_specifier = cluster, default timeout io.envoyproxy.envoy.config.route.v3.RouteAction proto1 = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() .setCluster("cluster-foo") .build(); StructOrError struct1 = RouteAction.fromEnvoyProtoRouteAction(proto1); assertThat(struct1.getErrorDetail()).isNull(); + assertThat(struct1.getStruct().getTimeoutNano()) + .isEqualTo(TimeUnit.SECONDS.toNanos(15L)); // default value assertThat(struct1.getStruct().getCluster()).isEqualTo("cluster-foo"); assertThat(struct1.getStruct().getWeightedCluster()).isNull(); - // cluster_specifier = cluster_header + // cluster_specifier = cluster, infinity timeout io.envoyproxy.envoy.config.route.v3.RouteAction proto2 = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() - .setClusterHeader("cluster-bar") + .setMaxGrpcTimeout(Durations.fromNanos(0)) + .setTimeout(Durations.fromMicros(20L)) + .setCluster("cluster-foo") .build(); StructOrError struct2 = RouteAction.fromEnvoyProtoRouteAction(proto2); - assertThat(struct2).isNull(); + assertThat(struct2.getStruct().getTimeoutNano()) + .isEqualTo(Long.MAX_VALUE); // infinite - // cluster_specifier = weighted_cluster + // cluster_specifier = cluster, infinity timeout io.envoyproxy.envoy.config.route.v3.RouteAction proto3 = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() + .setTimeout(Durations.fromNanos(0)) + .setCluster("cluster-foo") + .build(); + StructOrError struct3 = RouteAction.fromEnvoyProtoRouteAction(proto3); + assertThat(struct3.getStruct().getTimeoutNano()).isEqualTo(Long.MAX_VALUE); // infinite + + // cluster_specifier = cluster_header + io.envoyproxy.envoy.config.route.v3.RouteAction proto4 = + io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() + .setClusterHeader("cluster-bar") + .build(); + StructOrError struct4 = RouteAction.fromEnvoyProtoRouteAction(proto4); + assertThat(struct4).isNull(); + + // cluster_specifier = weighted_cluster + io.envoyproxy.envoy.config.route.v3.RouteAction proto5 = + io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() + .setMaxGrpcTimeout(Durations.fromSeconds(6L)) + .setTimeout(Durations.fromMicros(20L)) .setWeightedClusters( WeightedCluster.newBuilder() .addClusters( @@ -422,10 +448,12 @@ public void convertRouteAction() { .setName("cluster-baz") .setWeight(UInt32Value.newBuilder().setValue(100)))) .build(); - StructOrError struct3 = RouteAction.fromEnvoyProtoRouteAction(proto3); - assertThat(struct3.getErrorDetail()).isNull(); - assertThat(struct3.getStruct().getCluster()).isNull(); - assertThat(struct3.getStruct().getWeightedCluster()) + StructOrError struct5 = RouteAction.fromEnvoyProtoRouteAction(proto5); + assertThat(struct5.getErrorDetail()).isNull(); + assertThat(struct5.getStruct().getTimeoutNano()) + .isEqualTo(TimeUnit.SECONDS.toNanos(6L)); + assertThat(struct5.getStruct().getCluster()).isNull(); + assertThat(struct5.getStruct().getWeightedCluster()) .containsExactly(new ClusterWeight("cluster-baz", 100)); // cluster_specifier unset diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java index 19c3ec30bee..a771d9fb7df 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java @@ -750,7 +750,8 @@ public void resolveVirtualHostWithPathMatchingInRdsResponse() { new io.grpc.xds.RouteMatch( /* prefix= */ null, /* path= */ "/service1/method1"), - new EnvoyProtoData.RouteAction("cl1.googleapis.com", null))); + new EnvoyProtoData.RouteAction( + TimeUnit.SECONDS.toNanos(15L), "cl1.googleapis.com", null))); assertThat(routes.get(1)).isEqualTo( new EnvoyProtoData.Route( // path match with weighted cluster route @@ -758,6 +759,7 @@ public void resolveVirtualHostWithPathMatchingInRdsResponse() { /* prefix= */ null, /* path= */ "/service2/method2"), new EnvoyProtoData.RouteAction( + TimeUnit.SECONDS.toNanos(15L), null, ImmutableList.of( new EnvoyProtoData.ClusterWeight("cl21.googleapis.com", 30), @@ -769,7 +771,8 @@ public void resolveVirtualHostWithPathMatchingInRdsResponse() { new io.grpc.xds.RouteMatch( /* prefix= */ "/service1/", /* path= */ null), - new EnvoyProtoData.RouteAction("cl1.googleapis.com", null))); + new EnvoyProtoData.RouteAction( + TimeUnit.SECONDS.toNanos(15L), "cl1.googleapis.com", null))); assertThat(routes.get(3)).isEqualTo( new EnvoyProtoData.Route( // default match with cluster route @@ -777,7 +780,7 @@ public void resolveVirtualHostWithPathMatchingInRdsResponse() { /* prefix= */ "", /* path= */ null), new EnvoyProtoData.RouteAction( - "cluster.googleapis.com", null))); + TimeUnit.SECONDS.toNanos(15L), "cluster.googleapis.com", null))); } /** diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java index 584a53a8f6f..16ce37a6e51 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java @@ -83,7 +83,7 @@ public void generateXdsRoutingRawConfig() { new RouteMatch( new PathMatcher(null, "", null), Collections.emptyList(), new FractionMatcher(10, 20)), - new RouteAction("cluster-foo", null)); + new RouteAction(15L, "cluster-foo", null)); Route r2 = new Route( new RouteMatch( @@ -92,6 +92,7 @@ public void generateXdsRoutingRawConfig() { new HeaderMatcher(":scheme", "https", null, null, null, null, null, false)), null), new RouteAction( + 15L, null, Arrays.asList( new ClusterWeight("cluster-foo", 20), @@ -134,7 +135,7 @@ public void generateXdsRoutingRawConfig_allowDuplicateMatchers() { new RouteMatch( new PathMatcher("/service/method", null, null), Collections.emptyList(), null), - new RouteAction("cluster-foo", null)); + new RouteAction(15L, "cluster-foo", null)); Map config = XdsNameResolver.generateXdsRoutingRawConfig(Arrays.asList(route, route)); From 5bf68ff28c4dd67c1f44c5d156c73cb2c102c903 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Fri, 31 Jul 2020 14:14:10 -0700 Subject: [PATCH 35/88] xds: support v3 for XdsClient Duplicated `XdsClientImptTest` for V3. `XdsClientImptTestV2` and all other tests are still using V2. Even for `XdsClientImptTest`, although the protocol is V3, the test xds server still sends V2 resources in its V3 response. --- .../main/java/io/grpc/xds/XdsClientImpl.java | 42 +- .../java/io/grpc/xds/EdsLoadBalancerTest.java | 6 +- .../java/io/grpc/xds/XdsClientImplTest.java | 247 +- .../xds/XdsClientImplTestForListener.java | 21 +- .../java/io/grpc/xds/XdsClientImplTestV2.java | 3763 +++++++++++++++++ .../java/io/grpc/xds/XdsClientTestHelper.java | 33 +- .../xds/XdsNameResolverIntegrationTest.java | 14 +- 7 files changed, 3962 insertions(+), 164 deletions(-) create mode 100644 xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java diff --git a/xds/src/main/java/io/grpc/xds/XdsClientImpl.java b/xds/src/main/java/io/grpc/xds/XdsClientImpl.java index 3b972c2b30d..621eaa0ce47 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClientImpl.java +++ b/xds/src/main/java/io/grpc/xds/XdsClientImpl.java @@ -84,12 +84,14 @@ final class XdsClientImpl extends XdsClient { @VisibleForTesting static final String ADS_TYPE_URL_LDS_V2 = "type.googleapis.com/envoy.api.v2.Listener"; - private static final String ADS_TYPE_URL_LDS = + @VisibleForTesting + static final String ADS_TYPE_URL_LDS = "type.googleapis.com/envoy.config.listener.v3.Listener"; @VisibleForTesting static final String ADS_TYPE_URL_RDS_V2 = "type.googleapis.com/envoy.api.v2.RouteConfiguration"; - private static final String ADS_TYPE_URL_RDS = + @VisibleForTesting + static final String ADS_TYPE_URL_RDS = "type.googleapis.com/envoy.config.route.v3.RouteConfiguration"; private static final String TYPE_URL_HTTP_CONNECTION_MANAGER_V2 = "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2" @@ -99,12 +101,14 @@ final class XdsClientImpl extends XdsClient { + ".HttpConnectionManager"; @VisibleForTesting static final String ADS_TYPE_URL_CDS_V2 = "type.googleapis.com/envoy.api.v2.Cluster"; - private static final String ADS_TYPE_URL_CDS = + @VisibleForTesting + static final String ADS_TYPE_URL_CDS = "type.googleapis.com/envoy.config.cluster.v3.Cluster"; @VisibleForTesting static final String ADS_TYPE_URL_EDS_V2 = "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment"; - private static final String ADS_TYPE_URL_EDS = + @VisibleForTesting + static final String ADS_TYPE_URL_EDS = "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment"; private final MessagePrinter respPrinter = new MessagePrinter(); @@ -114,6 +118,7 @@ final class XdsClientImpl extends XdsClient { // Name of the target server this gRPC client is trying to talk to. private final String targetName; private final ManagedChannel channel; + private final boolean useProtocolV3; private final SynchronizationContext syncContext; private final ScheduledExecutorService timeService; private final BackoffPolicy.Provider backoffPolicyProvider; @@ -198,10 +203,11 @@ final class XdsClientImpl extends XdsClient { BackoffPolicy.Provider backoffPolicyProvider, Supplier stopwatchSupplier) { this.targetName = checkNotNull(targetName, "targetName"); - this.channel = + XdsChannel xdsChannel = checkNotNull(channelFactory, "channelFactory") - .createChannel(checkNotNull(servers, "servers")) - .getManagedChannel(); + .createChannel(checkNotNull(servers, "servers")); + this.channel = xdsChannel.getManagedChannel(); + this.useProtocolV3 = xdsChannel.isUseProtocolV3(); this.node = checkNotNull(node, "node"); this.syncContext = checkNotNull(syncContext, "syncContext"); this.timeService = checkNotNull(timeService, "timeService"); @@ -516,12 +522,11 @@ public String toString() { */ private void startRpcStream() { checkState(adsStream == null, "Previous adsStream has not been cleared yet"); - io.envoyproxy.envoy.service.discovery.v2.AggregatedDiscoveryServiceGrpc - .AggregatedDiscoveryServiceStub - stub = - io.envoyproxy.envoy.service.discovery.v2.AggregatedDiscoveryServiceGrpc.newStub( - channel); - adsStream = new AdsStreamV2(stub); + if (useProtocolV3) { + adsStream = new AdsStream(); + } else { + adsStream = new AdsStreamV2(); + } adsStream.start(); logger.log(XdsLogLevel.INFO, "ADS stream started"); adsStreamRetryStopwatch.reset().start(); @@ -1726,9 +1731,9 @@ private final class AdsStreamV2 extends AbstractAdsStream { .AggregatedDiscoveryServiceStub stubV2; private StreamObserver requestWriterV2; - AdsStreamV2(io.envoyproxy.envoy.service.discovery.v2.AggregatedDiscoveryServiceGrpc - .AggregatedDiscoveryServiceStub stubV2) { - this.stubV2 = checkNotNull(stubV2, "stubV2"); + AdsStreamV2() { + stubV2 = + io.envoyproxy.envoy.service.discovery.v2.AggregatedDiscoveryServiceGrpc.newStub(channel); } @Override @@ -1778,13 +1783,12 @@ void sendError(Exception error) { } // AdsStream V3 - @SuppressWarnings("UnusedNestedClass") // Will be used once xds-v3 support is implemented. private final class AdsStream extends AbstractAdsStream { private final AggregatedDiscoveryServiceGrpc.AggregatedDiscoveryServiceStub stub; private StreamObserver requestWriter; - AdsStream(AggregatedDiscoveryServiceGrpc.AggregatedDiscoveryServiceStub stub) { - this.stub = checkNotNull(stub, "stub"); + AdsStream() { + stub = AggregatedDiscoveryServiceGrpc.newStub(channel); } @Override diff --git a/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java index a321933f867..0b923b3033e 100644 --- a/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java @@ -22,7 +22,7 @@ import static io.grpc.ConnectivityState.READY; import static io.grpc.ConnectivityState.TRANSIENT_FAILURE; import static io.grpc.xds.XdsClientTestHelper.buildClusterLoadAssignment; -import static io.grpc.xds.XdsClientTestHelper.buildDiscoveryResponse; +import static io.grpc.xds.XdsClientTestHelper.buildDiscoveryResponseV2; import static io.grpc.xds.XdsClientTestHelper.buildDropOverload; import static io.grpc.xds.XdsClientTestHelper.buildLbEndpoint; import static io.grpc.xds.XdsClientTestHelper.buildLocalityLbEndpoints; @@ -680,7 +680,7 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { // The whole cluster is no longer accessible. // Note that EDS resource removal is achieved by CDS resource update. responseObserver.onNext( - buildDiscoveryResponse( + buildDiscoveryResponseV2( String.valueOf(versionIno++), Collections.emptyList(), XdsClientImpl.ADS_TYPE_URL_CDS_V2, @@ -768,7 +768,7 @@ private void deliverResolvedAddresses( private void deliverClusterLoadAssignments(ClusterLoadAssignment clusterLoadAssignment) { responseObserver.onNext( - buildDiscoveryResponse( + buildDiscoveryResponseV2( String.valueOf(versionIno++), ImmutableList.of(Any.pack(clusterLoadAssignment)), XdsClientImpl.ADS_TYPE_URL_EDS_V2, diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java index a771d9fb7df..2a353c77f58 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java @@ -51,8 +51,6 @@ import com.google.protobuf.util.Durations; import io.envoyproxy.envoy.api.v2.ClusterLoadAssignment; import io.envoyproxy.envoy.api.v2.ClusterLoadAssignment.Policy; -import io.envoyproxy.envoy.api.v2.DiscoveryRequest; -import io.envoyproxy.envoy.api.v2.DiscoveryResponse; import io.envoyproxy.envoy.api.v2.auth.UpstreamTlsContext; import io.envoyproxy.envoy.api.v2.core.AggregatedConfigSource; import io.envoyproxy.envoy.api.v2.core.ConfigSource; @@ -69,7 +67,9 @@ import io.envoyproxy.envoy.config.route.v3.RouteMatch; import io.envoyproxy.envoy.config.route.v3.VirtualHost; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.SdsSecretConfig; -import io.envoyproxy.envoy.service.discovery.v2.AggregatedDiscoveryServiceGrpc.AggregatedDiscoveryServiceImplBase; +import io.envoyproxy.envoy.service.discovery.v3.AggregatedDiscoveryServiceGrpc.AggregatedDiscoveryServiceImplBase; +import io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest; +import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse; import io.envoyproxy.envoy.service.load_stats.v2.LoadReportingServiceGrpc.LoadReportingServiceImplBase; import io.envoyproxy.envoy.service.load_stats.v2.LoadStatsRequest; import io.envoyproxy.envoy.service.load_stats.v2.LoadStatsResponse; @@ -126,7 +126,8 @@ import org.mockito.MockitoAnnotations; /** - * Tests for {@link XdsClientImpl}. + * Tests for {@link XdsClientImpl} with xDS v3 protocol. However, the test xDS server still sends + * update with v2 resources for testing compatibility. */ @RunWith(JUnit4.class) public class XdsClientImplTest { @@ -287,7 +288,7 @@ XdsChannel createChannel(List servers) { ServerInfo serverInfo = Iterables.getOnlyElement(servers); assertThat(serverInfo.getServerUri()).isEqualTo(serverName); assertThat(serverInfo.getChannelCredentials()).isEmpty(); - return new XdsChannel(channel, false); + return new XdsChannel(channel, /* useProtocolV3= */ true); } }; @@ -337,7 +338,7 @@ public void ldsResponseWithoutMatchingResource() { // Client sends an LDS request for the host name (with port) to management server. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_LDS, ""))); assertThat(fakeClock.getPendingTasks(LDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); @@ -367,7 +368,7 @@ public void ldsResponseWithoutMatchingResource() { // Client sends an ACK LDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "0", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_LDS, "0000"))); verify(configWatcher, never()).onConfigChanged(any(ConfigUpdate.class)); verify(configWatcher, never()).onResourceDoesNotExist(TARGET_AUTHORITY); @@ -393,7 +394,7 @@ public void failToFindVirtualHostInLdsResponseInLineRouteConfig() { // Client sends an LDS request for the host name (with port) to management server. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_LDS, ""))); assertThat(fakeClock.getPendingTasks(LDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); io.envoyproxy.envoy.api.v2.RouteConfiguration routeConfig = @@ -416,7 +417,7 @@ public void failToFindVirtualHostInLdsResponseInLineRouteConfig() { verify(requestObserver) .onNext( argThat(new DiscoveryRequestMatcher("", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_LDS, "0000"))); verify(configWatcher, never()).onConfigChanged(any(ConfigUpdate.class)); verify(configWatcher, never()).onResourceDoesNotExist(TARGET_AUTHORITY); @@ -442,7 +443,7 @@ public void resolveVirtualHostInLdsResponse() { // Client sends an LDS request for the host name (with port) to management server. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_LDS, ""))); ScheduledTask ldsRespTimer = Iterables.getOnlyElement( fakeClock.getPendingTasks(LDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)); @@ -489,7 +490,7 @@ public void resolveVirtualHostInLdsResponse() { // Client sends an ACK request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "0", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_LDS, "0000"))); ArgumentCaptor configUpdateCaptor = ArgumentCaptor.forClass(null); verify(configWatcher).onConfigChanged(configUpdateCaptor.capture()); @@ -515,7 +516,7 @@ public void rdsResponseWithoutMatchingResource() { // Client sends an LDS request for the host name (with port) to management server. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_LDS, ""))); Rds rdsConfig = Rds.newBuilder() @@ -535,12 +536,12 @@ public void rdsResponseWithoutMatchingResource() { // Client sends an ACK LDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "0", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_LDS, "0000"))); // Client sends an (first) RDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "route-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_RDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_RDS, ""))); assertThat(fakeClock.getPendingTasks(RDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); @@ -567,7 +568,7 @@ public void rdsResponseWithoutMatchingResource() { // Client sends an ACK RDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "0", "route-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_RDS, "0000"))); verify(configWatcher, never()).onConfigChanged(any(ConfigUpdate.class)); verify(configWatcher, never()).onResourceDoesNotExist(anyString()); @@ -633,7 +634,7 @@ public void resolveVirtualHostInRdsResponse() { // Client sent an ACK RDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "0", "route-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_RDS, "0000"))); ArgumentCaptor configUpdateCaptor = ArgumentCaptor.forClass(null); verify(configWatcher).onConfigChanged(configUpdateCaptor.capture()); @@ -738,7 +739,7 @@ public void resolveVirtualHostWithPathMatchingInRdsResponse() { // Client sent an ACK RDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "0", "route-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_RDS, "0000"))); ArgumentCaptor configUpdateCaptor = ArgumentCaptor.forClass(null); verify(configWatcher).onConfigChanged(configUpdateCaptor.capture()); @@ -839,7 +840,7 @@ public void failToFindVirtualHostInRdsResponse() { verify(requestObserver) .onNext( argThat(new DiscoveryRequestMatcher("", "route-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_RDS, "0000"))); verify(configWatcher, never()).onConfigChanged(any(ConfigUpdate.class)); verify(configWatcher, never()).onResourceDoesNotExist(anyString()); @@ -906,7 +907,7 @@ public void matchingVirtualHostDoesNotContainRouteAction() { verify(requestObserver) .onNext( argThat(new DiscoveryRequestMatcher("", "route-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_RDS, "0000"))); verify(configWatcher, never()).onConfigChanged(any(ConfigUpdate.class)); verify(configWatcher, never()).onResourceDoesNotExist(anyString()); @@ -930,7 +931,7 @@ public void notifyUpdatedResources() { // Client sends an LDS request for the host name (with port) to management server. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_LDS, ""))); // Management server sends back an LDS response containing a RouteConfiguration for the // requested Listener directly in-line. @@ -955,7 +956,7 @@ public void notifyUpdatedResources() { // Client sends an ACK LDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "0", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_LDS, "0000"))); // Cluster name is resolved and notified to config watcher. ArgumentCaptor configUpdateCaptor = ArgumentCaptor.forClass(null); @@ -984,7 +985,7 @@ public void notifyUpdatedResources() { // Client sends an ACK LDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "1", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0001"))); + XdsClientImpl.ADS_TYPE_URL_LDS, "0001"))); // Updated cluster name is notified to config watcher. configUpdateCaptor = ArgumentCaptor.forClass(null); @@ -1013,12 +1014,12 @@ public void notifyUpdatedResources() { // Client sends an ACK LDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "2", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0002"))); + XdsClientImpl.ADS_TYPE_URL_LDS, "0002"))); // Client sends an (first) RDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "some-route-to-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_RDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_RDS, ""))); // Management server sends back an RDS response containing the RouteConfiguration // for the requested resource. @@ -1037,7 +1038,7 @@ public void notifyUpdatedResources() { // Client sent an ACK RDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "0", "some-route-to-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_RDS, "0000"))); // Updated cluster name is notified to config watcher again. configUpdateCaptor = ArgumentCaptor.forClass(null); @@ -1060,7 +1061,7 @@ public void notifyUpdatedResources() { // Client sent an ACK RDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "1", "some-route-to-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0001"))); + XdsClientImpl.ADS_TYPE_URL_RDS, "0001"))); // Updated cluster name is notified to config watcher again. configUpdateCaptor = ArgumentCaptor.forClass(null); @@ -1096,7 +1097,7 @@ public void waitRdsResponsesForRequestedResource() { // Client sends an LDS request for the host name (with port) to management server. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_LDS, ""))); // Management sends back an LDS response telling client to do RDS. Rds rdsConfig = @@ -1118,12 +1119,12 @@ public void waitRdsResponsesForRequestedResource() { // Client sends an ACK LDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "0", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_LDS, "0000"))); // Client sends an (first) RDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "route-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_RDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_RDS, ""))); ScheduledTask rdsRespTimer = Iterables.getOnlyElement( @@ -1148,7 +1149,7 @@ public void waitRdsResponsesForRequestedResource() { // Client sent an ACK RDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "0", "route-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_RDS, "0000"))); // Client waits for future RDS responses silently. verifyNoMoreInteractions(configWatcher); @@ -1175,7 +1176,7 @@ public void waitRdsResponsesForRequestedResource() { // Client sent an ACK RDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "1", "route-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0001"))); + XdsClientImpl.ADS_TYPE_URL_RDS, "0001"))); // Updated cluster name is notified to config watcher. ArgumentCaptor configUpdateCaptor = ArgumentCaptor.forClass(null); @@ -1198,7 +1199,7 @@ public void routeConfigurationRemovedNotifiedToWatcher() { // Client sends an LDS request for the host name (with port) to management server. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_LDS, ""))); // Management sends back an LDS response telling client to do RDS. Rds rdsConfig = @@ -1220,12 +1221,12 @@ public void routeConfigurationRemovedNotifiedToWatcher() { // Client sends an ACK LDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "0", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_LDS, "0000"))); // Client sends an (first) RDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "route-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_RDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_RDS, ""))); // Management server sends back an RDS response containing RouteConfiguration requested. List routeConfigs = ImmutableList.of( @@ -1242,7 +1243,7 @@ public void routeConfigurationRemovedNotifiedToWatcher() { // Client sent an ACK RDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "0", "route-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_RDS, "0000"))); // Resolved cluster name is notified to config watcher. ArgumentCaptor configUpdateCaptor = ArgumentCaptor.forClass(null); @@ -1260,7 +1261,7 @@ public void routeConfigurationRemovedNotifiedToWatcher() { // Client sent an ACK LDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "1", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0001"))); + XdsClientImpl.ADS_TYPE_URL_LDS, "0001"))); verify(configWatcher).onResourceDoesNotExist(TARGET_AUTHORITY); } @@ -1295,7 +1296,7 @@ public void updateRdsRequestResourceWhileInitialResourceFetchInProgress() { // Client sends an (first) RDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "route-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_RDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_RDS, ""))); ScheduledTask rdsRespTimer = Iterables.getOnlyElement( @@ -1324,7 +1325,7 @@ public void updateRdsRequestResourceWhileInitialResourceFetchInProgress() { // Client sent a new RDS request with updated resource name. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "route-bar.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_RDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_RDS, ""))); assertThat(rdsRespTimer.isCancelled()).isTrue(); rdsRespTimer = @@ -1361,7 +1362,7 @@ public void cdsResponseWithoutMatchingResource() { // Client sends a CDS request for the only cluster being watched to management server. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_CDS, ""))); assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); // Management server sends back a CDS response without Cluster for the requested resource. @@ -1375,7 +1376,7 @@ public void cdsResponseWithoutMatchingResource() { // Client sent an ACK CDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "0", "cluster-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_CDS, "0000"))); verify(clusterWatcher, never()).onClusterChanged(any(ClusterUpdate.class)); verify(clusterWatcher, never()).onResourceDoesNotExist("cluster-foo.googleapis.com"); verify(clusterWatcher, never()).onError(any(Status.class)); @@ -1398,7 +1399,7 @@ public void cdsResponseWithMatchingResource() { // Client sends a CDS request for the only cluster being watched to management server. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_CDS, ""))); ScheduledTask cdsRespTimer = Iterables.getOnlyElement( fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)); @@ -1415,7 +1416,7 @@ public void cdsResponseWithMatchingResource() { // Client sent an ACK CDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "0", "cluster-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_CDS, "0000"))); assertThat(cdsRespTimer.isCancelled()).isTrue(); ArgumentCaptor clusterUpdateCaptor = ArgumentCaptor.forClass(null); @@ -1439,7 +1440,7 @@ public void cdsResponseWithMatchingResource() { // Client sent an ACK CDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "1", "cluster-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0001"))); + XdsClientImpl.ADS_TYPE_URL_CDS, "0001"))); verify(clusterWatcher, times(2)).onClusterChanged(clusterUpdateCaptor.capture()); clusterUpdate = clusterUpdateCaptor.getValue(); @@ -1474,7 +1475,7 @@ public void cdsResponseWithUpstreamTlsContext() { // Client sent an ACK CDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "0", "cluster-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_CDS, "0000"))); ArgumentCaptor clusterUpdateCaptor = ArgumentCaptor.forClass(null); verify(clusterWatcher, times(1)).onClusterChanged(clusterUpdateCaptor.capture()); ClusterUpdate clusterUpdate = clusterUpdateCaptor.getValue(); @@ -1512,7 +1513,7 @@ public void multipleClusterWatchers() { argThat( new DiscoveryRequestMatcher("", ImmutableList.of("cluster-foo.googleapis.com", "cluster-bar.googleapis.com"), - XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_CDS, ""))); assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(2); // Management server sends back a CDS response contains Cluster for only one of @@ -1530,7 +1531,7 @@ public void multipleClusterWatchers() { argThat( new DiscoveryRequestMatcher("0", ImmutableList.of("cluster-foo.googleapis.com", "cluster-bar.googleapis.com"), - XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_CDS, "0000"))); // Two watchers get notification of cluster update for the cluster they are interested in. ArgumentCaptor clusterUpdateCaptor1 = ArgumentCaptor.forClass(null); @@ -1577,7 +1578,7 @@ public void multipleClusterWatchers() { argThat( new DiscoveryRequestMatcher("1", ImmutableList.of("cluster-foo.googleapis.com", "cluster-bar.googleapis.com"), - XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0001"))); + XdsClientImpl.ADS_TYPE_URL_CDS, "0001"))); verifyNoMoreInteractions(watcher1, watcher2); // resource has no change ArgumentCaptor clusterUpdateCaptor3 = ArgumentCaptor.forClass(null); @@ -1607,7 +1608,7 @@ public void watchClusterAlreadyBeingWatched() { // Client sends an CDS request to management server. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_CDS, ""))); assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); // Management server sends back an CDS response with Cluster for the requested @@ -1621,7 +1622,7 @@ public void watchClusterAlreadyBeingWatched() { // Client sent an ACK CDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "0", "cluster-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_CDS, "0000"))); ArgumentCaptor clusterUpdateCaptor1 = ArgumentCaptor.forClass(null); verify(watcher1).onClusterChanged(clusterUpdateCaptor1.capture()); @@ -1665,7 +1666,7 @@ public void addRemoveClusterWatchers() { // Client sends an CDS request to management server. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_CDS, ""))); // Management server sends back a CDS response with Cluster for the requested // cluster. @@ -1678,7 +1679,7 @@ public void addRemoveClusterWatchers() { // Client sent an ACK CDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "0", "cluster-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_CDS, "0000"))); ArgumentCaptor clusterUpdateCaptor1 = ArgumentCaptor.forClass(null); verify(watcher1).onClusterChanged(clusterUpdateCaptor1.capture()); @@ -1698,7 +1699,7 @@ public void addRemoveClusterWatchers() { argThat( new DiscoveryRequestMatcher("0", ImmutableList.of("cluster-foo.googleapis.com", "cluster-bar.googleapis.com"), - XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_CDS, "0000"))); // Management server sends back a CDS response with Cluster for all requested cluster. clusters = ImmutableList.of( @@ -1716,7 +1717,7 @@ public void addRemoveClusterWatchers() { argThat( new DiscoveryRequestMatcher("1", ImmutableList.of("cluster-foo.googleapis.com", "cluster-bar.googleapis.com"), - XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0001"))); + XdsClientImpl.ADS_TYPE_URL_CDS, "0001"))); verifyNoMoreInteractions(watcher1); // resource has no change ArgumentCaptor clusterUpdateCaptor2 = ArgumentCaptor.forClass(null); verify(watcher2).onClusterChanged(clusterUpdateCaptor2.capture()); @@ -1735,7 +1736,7 @@ public void addRemoveClusterWatchers() { // that cluster. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "1", "cluster-bar.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0001"))); + XdsClientImpl.ADS_TYPE_URL_CDS, "0001"))); // Management server has nothing to respond. @@ -1746,7 +1747,7 @@ public void addRemoveClusterWatchers() { .onNext( argThat( new DiscoveryRequestMatcher("1", ImmutableList.of(), - XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0001"))); + XdsClientImpl.ADS_TYPE_URL_CDS, "0001"))); // Management server sends back a new CDS response. clusters = ImmutableList.of( @@ -1761,7 +1762,7 @@ public void addRemoveClusterWatchers() { .onNext( argThat( new DiscoveryRequestMatcher("2", ImmutableList.of(), - XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0002"))); + XdsClientImpl.ADS_TYPE_URL_CDS, "0002"))); // Cancelled watchers do not receive notification. verifyNoMoreInteractions(watcher1, watcher2); @@ -1774,7 +1775,7 @@ public void addRemoveClusterWatchers() { // A CDS request is sent to indicate subscription of "cluster-foo.googleapis.com" only. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "2", "cluster-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0002"))); + XdsClientImpl.ADS_TYPE_URL_CDS, "0002"))); // Management server sends back a new CDS response for at least newly requested resources // (it is required to do so). @@ -1800,7 +1801,7 @@ public void addRemoveClusterWatchers() { // A CDS request is sent to re-subscribe the cluster again. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "3", "cluster-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0003"))); + XdsClientImpl.ADS_TYPE_URL_CDS, "0003"))); } @Test @@ -1816,7 +1817,7 @@ public void addRemoveClusterWatcherWhileInitialResourceFetchInProgress() { .onNext( argThat( new DiscoveryRequestMatcher("", "cluster-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_CDS, ""))); assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); fakeClock.forwardTime(XdsClientImpl.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC - 1, TimeUnit.SECONDS); @@ -1834,7 +1835,7 @@ public void addRemoveClusterWatcherWhileInitialResourceFetchInProgress() { argThat( new DiscoveryRequestMatcher("", ImmutableList.of("cluster-foo.googleapis.com", "cluster-bar.googleapis.com"), - XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_CDS, ""))); assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(2); fakeClock.forwardTime(1, TimeUnit.SECONDS); @@ -1876,7 +1877,7 @@ public void cdsUpdateForClusterBeingRemoved() { verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_CDS, ""))); assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); // Management server sends back a CDS response containing requested resource. @@ -1922,7 +1923,7 @@ public void edsResponseWithoutMatchingResource() { // Client sends an EDS request for the only cluster being watched to management server. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_EDS, ""))); assertThat(fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); // Management server sends back an EDS response without ClusterLoadAssignment for the requested @@ -1951,7 +1952,7 @@ public void edsResponseWithoutMatchingResource() { // Client sent an ACK EDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "0", "cluster-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_EDS, "0000"))); verify(endpointWatcher, never()).onEndpointChanged(any(EndpointUpdate.class)); verify(endpointWatcher, never()).onResourceDoesNotExist("cluster-foo.googleapis.com"); @@ -1974,7 +1975,7 @@ public void edsResponseWithMatchingResource() { // Client sends an EDS request for the only cluster being watched to management server. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_EDS, ""))); ScheduledTask edsRespTimeoutTask = Iterables.getOnlyElement( fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)); @@ -2017,7 +2018,7 @@ public void edsResponseWithMatchingResource() { // Client sent an ACK EDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "0", "cluster-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_EDS, "0000"))); ArgumentCaptor endpointUpdateCaptor = ArgumentCaptor.forClass(null); verify(endpointWatcher).onEndpointChanged(endpointUpdateCaptor.capture()); @@ -2050,7 +2051,7 @@ public void edsResponseWithMatchingResource() { // Client sent an ACK EDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "1", "cluster-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0001"))); + XdsClientImpl.ADS_TYPE_URL_EDS, "0001"))); verify(endpointWatcher, times(2)).onEndpointChanged(endpointUpdateCaptor.capture()); endpointUpdate = endpointUpdateCaptor.getValue(); @@ -2077,7 +2078,7 @@ public void multipleEndpointWatchers() { argThat( new DiscoveryRequestMatcher("", ImmutableList.of("cluster-foo.googleapis.com", "cluster-bar.googleapis.com"), - XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_EDS, ""))); assertThat(fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(2); @@ -2105,7 +2106,7 @@ public void multipleEndpointWatchers() { argThat( new DiscoveryRequestMatcher("0", ImmutableList.of("cluster-foo.googleapis.com", "cluster-bar.googleapis.com"), - XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_EDS, "0000"))); // Two watchers get notification of endpoint update for the cluster they are interested in. ArgumentCaptor endpointUpdateCaptor1 = ArgumentCaptor.forClass(null); @@ -2155,7 +2156,7 @@ public void multipleEndpointWatchers() { argThat( new DiscoveryRequestMatcher("1", ImmutableList.of("cluster-foo.googleapis.com", "cluster-bar.googleapis.com"), - XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0001"))); + XdsClientImpl.ADS_TYPE_URL_EDS, "0001"))); // The corresponding watcher gets notified. ArgumentCaptor endpointUpdateCaptor3 = ArgumentCaptor.forClass(null); @@ -2186,7 +2187,7 @@ public void watchEndpointsForClusterAlreadyBeingWatched() { // Client sends first EDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_EDS, ""))); assertThat(fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); @@ -2211,7 +2212,7 @@ public void watchEndpointsForClusterAlreadyBeingWatched() { // Client sent an ACK EDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "0", "cluster-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_EDS, "0000"))); ArgumentCaptor endpointUpdateCaptor1 = ArgumentCaptor.forClass(null); verify(watcher1).onEndpointChanged(endpointUpdateCaptor1.capture()); @@ -2264,7 +2265,7 @@ public void addRemoveEndpointWatchers() { // Client sends an EDS request to management server. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_EDS, ""))); // Management server sends back an EDS response with ClusterLoadAssignment for the requested // cluster. @@ -2286,7 +2287,7 @@ public void addRemoveEndpointWatchers() { // Client sent an ACK EDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "0", "cluster-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_EDS, "0000"))); ArgumentCaptor endpointUpdateCaptor1 = ArgumentCaptor.forClass(null); verify(watcher1).onEndpointChanged(endpointUpdateCaptor1.capture()); @@ -2311,7 +2312,7 @@ public void addRemoveEndpointWatchers() { argThat( new DiscoveryRequestMatcher("0", ImmutableList.of("cluster-foo.googleapis.com", "cluster-bar.googleapis.com"), - XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_EDS, "0000"))); // Management server sends back an EDS response with ClusterLoadAssignment for one of requested // cluster. @@ -2334,7 +2335,7 @@ public void addRemoveEndpointWatchers() { argThat( new DiscoveryRequestMatcher("1", ImmutableList.of("cluster-foo.googleapis.com", "cluster-bar.googleapis.com"), - XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0001"))); + XdsClientImpl.ADS_TYPE_URL_EDS, "0001"))); ArgumentCaptor endpointUpdateCaptor2 = ArgumentCaptor.forClass(null); verify(watcher2).onEndpointChanged(endpointUpdateCaptor2.capture()); @@ -2355,7 +2356,7 @@ public void addRemoveEndpointWatchers() { // sent an new EDS request to unsubscribe from that cluster. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "1", "cluster-bar.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0001"))); + XdsClientImpl.ADS_TYPE_URL_EDS, "0001"))); // Management server should not respond as it had previously sent the requested resource. @@ -2369,7 +2370,7 @@ public void addRemoveEndpointWatchers() { argThat( new DiscoveryRequestMatcher("1", ImmutableList.of(), // empty resources - XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0001"))); + XdsClientImpl.ADS_TYPE_URL_EDS, "0001"))); // All endpoint watchers have been cancelled. @@ -2400,7 +2401,7 @@ public void addRemoveEndpointWatchers() { argThat( new DiscoveryRequestMatcher("2", ImmutableList.of(), // empty resources - XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0002"))); + XdsClientImpl.ADS_TYPE_URL_EDS, "0002"))); // Cancelled watchers do not receive notification. verifyNoMoreInteractions(watcher1, watcher2); @@ -2417,7 +2418,7 @@ public void addRemoveEndpointWatchers() { // An EDS request is sent to re-subscribe the cluster again. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "2", "cluster-bar.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0002"))); + XdsClientImpl.ADS_TYPE_URL_EDS, "0002"))); // Management server sends back an EDS response for re-subscribed resource. clusterLoadAssignments = ImmutableList.of( @@ -2451,7 +2452,7 @@ public void addRemoveEndpointWatchers() { argThat( new DiscoveryRequestMatcher("3", ImmutableList.of("cluster-bar.googleapis.com"), - XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0003"))); + XdsClientImpl.ADS_TYPE_URL_EDS, "0003"))); } @Test @@ -2467,7 +2468,7 @@ public void addRemoveEndpointWatcherWhileInitialResourceFetchInProgress() { .onNext( argThat( new DiscoveryRequestMatcher("", "cluster-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_EDS, ""))); assertThat(fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); fakeClock.forwardTime(XdsClientImpl.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC - 1, TimeUnit.SECONDS); @@ -2485,7 +2486,7 @@ public void addRemoveEndpointWatcherWhileInitialResourceFetchInProgress() { argThat( new DiscoveryRequestMatcher("", ImmutableList.of("cluster-foo.googleapis.com", "cluster-bar.googleapis.com"), - XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_EDS, ""))); assertThat(fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(2); fakeClock.forwardTime(1, TimeUnit.SECONDS); @@ -2594,7 +2595,7 @@ public void streamClosedAndRetryWhenResolvingConfig() { // Client sends an LDS request for the host name (with port) to management server. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_LDS, ""))); // Management server closes the RPC stream immediately. responseObserver.onCompleted(); @@ -2614,7 +2615,7 @@ public void streamClosedAndRetryWhenResolvingConfig() { // Client retried by sending an LDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_LDS, ""))); // Management server closes the RPC stream with an error. responseObserver.onError(Status.UNAVAILABLE.asException()); @@ -2634,7 +2635,7 @@ public void streamClosedAndRetryWhenResolvingConfig() { // Client retried again by sending an LDS. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_LDS, ""))); // Management server responses with a listener for the requested resource. Rds rdsConfig = @@ -2655,12 +2656,12 @@ public void streamClosedAndRetryWhenResolvingConfig() { // Client sent back an ACK LDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "0", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"))); + XdsClientImpl.ADS_TYPE_URL_LDS, "0000"))); // Client sent an RDS request based on the received listener. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "route-foo.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_RDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_RDS, ""))); // Management server encounters an error and closes the stream. responseObserver.onError(Status.UNKNOWN.asException()); @@ -2674,7 +2675,7 @@ public void streamClosedAndRetryWhenResolvingConfig() { requestObserver = requestObservers.poll(); verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_LDS, ""))); // RPC stream closed immediately responseObserver.onError(Status.UNKNOWN.asException()); @@ -2691,7 +2692,7 @@ public void streamClosedAndRetryWhenResolvingConfig() { requestObserver = requestObservers.poll(); verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_LDS, ""))); // Management server sends an LDS response. responseObserver.onNext(ldsResponse); @@ -2726,7 +2727,7 @@ public void streamClosedAndRetryWhenResolvingConfig() { requestObserver = requestObservers.poll(); verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_LDS, ""))); verifyNoMoreInteractions(backoffPolicyProvider, backoffPolicy1, backoffPolicy2); } @@ -2758,7 +2759,7 @@ public void streamClosedAndRetry() { // Client sent first CDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_CDS, ""))); // Start watching endpoint information. xdsClient.watchEndpointData("cluster.googleapis.com", endpointWatcher); @@ -2766,7 +2767,7 @@ public void streamClosedAndRetry() { // Client sent first EDS request. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_EDS, ""))); // Management server closes the RPC stream with an error. responseObserver.onError(Status.UNKNOWN.asException()); @@ -2788,13 +2789,13 @@ public void streamClosedAndRetry() { // Retry resumes requests for all wanted resources. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_LDS, ""))); verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_CDS, ""))); verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_EDS, ""))); // Management server becomes unreachable. responseObserver.onError(Status.UNAVAILABLE.asException()); @@ -2817,13 +2818,13 @@ public void streamClosedAndRetry() { requestObserver = requestObservers.poll(); verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_LDS, ""))); verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_CDS, ""))); verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_EDS, ""))); // Management server is still not reachable. responseObserver.onError(Status.UNAVAILABLE.asException()); @@ -2846,13 +2847,13 @@ public void streamClosedAndRetry() { requestObserver = requestObservers.poll(); verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_LDS, ""))); verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_CDS, ""))); verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_EDS, ""))); // Management server sends back a CDS response. List clusters = ImmutableList.of( @@ -2879,13 +2880,13 @@ public void streamClosedAndRetry() { verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_LDS, ""))); verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_CDS, ""))); verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_EDS, ""))); // Management server becomes unreachable again. responseObserver.onError(Status.UNAVAILABLE.asException()); @@ -2907,13 +2908,13 @@ public void streamClosedAndRetry() { requestObserver = requestObservers.poll(); verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_LDS, ""))); verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_CDS, ""))); verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_EDS, ""))); verifyNoMoreInteractions(mockedDiscoveryService, backoffPolicyProvider, backoffPolicy1, backoffPolicy2); @@ -2952,7 +2953,7 @@ public void streamClosedAndRetryRaceWithAddingAndRemovingWatchers() { verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_LDS, ""))); // Management server becomes unreachable. responseObserver.onError(Status.UNAVAILABLE.asException()); @@ -2973,10 +2974,10 @@ public void streamClosedAndRetryRaceWithAddingAndRemovingWatchers() { verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_LDS, ""))); verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_CDS, ""))); // Management server is still unreachable. responseObserver.onError(Status.UNAVAILABLE.asException()); @@ -2997,13 +2998,13 @@ public void streamClosedAndRetryRaceWithAddingAndRemovingWatchers() { verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_LDS, ""))); verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_CDS, ""))); verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_EDS, ""))); // Management server sends back a CDS response. List clusters = ImmutableList.of( @@ -3019,14 +3020,14 @@ public void streamClosedAndRetryRaceWithAddingAndRemovingWatchers() { // Client updates EDS resource subscription immediately. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", ImmutableList.of(), - XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_EDS, ""))); // Become interested in endpoints of another cluster. xdsClient.watchEndpointData("cluster2.googleapis.com", endpointWatcher); // Client updates EDS resource subscription immediately. verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster2.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_EDS, ""))); // Management server closes the RPC stream again. responseObserver.onCompleted(); @@ -3040,13 +3041,13 @@ public void streamClosedAndRetryRaceWithAddingAndRemovingWatchers() { requestObserver = requestObservers.poll(); verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_LDS, ""))); verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_CDS, ""))); verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster2.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_EDS, ""))); // Management server becomes unreachable again. responseObserver.onError(Status.UNAVAILABLE.asException()); @@ -3067,13 +3068,13 @@ public void streamClosedAndRetryRaceWithAddingAndRemovingWatchers() { verify(requestObserver) .onNext(eq(buildDiscoveryRequest(NODE, "", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_LDS, ""))); verify(requestObserver, never()) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_CDS, ""))); verify(requestObserver, never()) .onNext(eq(buildDiscoveryRequest(NODE, "", "cluster2.googleapis.com", - XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_EDS, ""))); verifyNoMoreInteractions(mockedDiscoveryService, backoffPolicyProvider, backoffPolicy1, backoffPolicy2); @@ -3139,7 +3140,7 @@ public void streamClosedAndRetryReschedulesAllResourceFetchTimer() { // Client resumed requests and management server sends back LDS resources again. verify(requestObserver).onNext( eq(buildDiscoveryRequest(NODE, "", TARGET_AUTHORITY, - XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + XdsClientImpl.ADS_TYPE_URL_LDS, ""))); responseObserver.onNext(response); // Client sent an RDS request for resource "route-foo.googleapis.com" (Omitted). @@ -3737,7 +3738,7 @@ public boolean matches(DiscoveryRequest argument) { if (!resourceNames.equals(new HashSet<>(argument.getResourceNamesList()))) { return false; } - return argument.getNode().equals(NODE.toEnvoyProtoNodeV2()); + return argument.getNode().equals(NODE.toEnvoyProtoNode()); } } diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTestForListener.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTestForListener.java index d2452c86239..6d1aeedaeb8 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTestForListener.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTestForListener.java @@ -17,7 +17,7 @@ package io.grpc.xds; import static com.google.common.truth.Truth.assertThat; -import static io.grpc.xds.XdsClientTestHelper.buildDiscoveryResponse; +import static io.grpc.xds.XdsClientTestHelper.buildDiscoveryResponseV2; import static io.grpc.xds.XdsClientTestHelper.buildListener; import static io.grpc.xds.XdsClientTestHelper.buildRouteConfiguration; import static io.grpc.xds.XdsClientTestHelper.buildVirtualHost; @@ -83,7 +83,6 @@ import java.util.Queue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; - import org.junit.After; import org.junit.Before; import org.junit.Ignore; @@ -331,7 +330,7 @@ public void ldsResponse_nonMatchingFilterChain_notFoundError() { "cluster-baz.googleapis.com")))) .build())))); DiscoveryResponse response = - buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + buildDiscoveryResponseV2("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); responseObserver.onNext(response); // Client sends an ACK LDS request. @@ -383,7 +382,7 @@ public void ldsResponseWith_listenerAddressPortMismatch() { filterChainInbound ))); DiscoveryResponse response = - buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + buildDiscoveryResponseV2("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); responseObserver.onNext(response); // Client sends an ACK LDS request. @@ -435,7 +434,7 @@ public void ldsResponseWith_matchingListenerFound() throws InvalidProtocolBuffer filterChainInbound ))); DiscoveryResponse response = - buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + buildDiscoveryResponseV2("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); responseObserver.onNext(response); // Client sends an ACK LDS request. @@ -510,7 +509,7 @@ public void notifyUpdatedListener() throws InvalidProtocolBufferException { filterChainInbound ))); DiscoveryResponse response = - buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + buildDiscoveryResponseV2("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); responseObserver.onNext(response); // Client sends an ACK LDS request. @@ -533,7 +532,7 @@ public void notifyUpdatedListener() throws InvalidProtocolBufferException { filterChainNewInbound ))); DiscoveryResponse response1 = - buildDiscoveryResponse("1", listeners1, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0001"); + buildDiscoveryResponseV2("1", listeners1, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0001"); responseObserver.onNext(response1); // Client sends an ACK LDS request. @@ -606,7 +605,7 @@ public void ldsResponse_nonMatchingIpAddress() { filterChainOutbound ))); DiscoveryResponse response = - buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + buildDiscoveryResponseV2("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); responseObserver.onNext(response); // Client sends an ACK LDS request. @@ -655,7 +654,7 @@ public void ldsResponse_nonMatchingPort() { filterChainOutbound ))); DiscoveryResponse response = - buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + buildDiscoveryResponseV2("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); responseObserver.onNext(response); // Client sends an ACK LDS request. @@ -706,7 +705,7 @@ public void streamClosedAndRetry() { filterChainInbound ))); DiscoveryResponse response = - buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + buildDiscoveryResponseV2("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); responseObserver.onNext(response); ArgumentCaptor statusCaptor = ArgumentCaptor.forClass(null); @@ -768,7 +767,7 @@ public void streamClosedAndRetry() { XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); // Management server sends back a LDS response. - response = buildDiscoveryResponse("1", listeners, + response = buildDiscoveryResponseV2("1", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0001"); responseObserver.onNext(response); diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java new file mode 100644 index 00000000000..f2ec57c8f2b --- /dev/null +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java @@ -0,0 +1,3763 @@ +/* + * Copyright 2019 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds; + +import static com.google.common.truth.Truth.assertThat; +import static io.grpc.xds.XdsClientTestHelper.buildCluster; +import static io.grpc.xds.XdsClientTestHelper.buildClusterLoadAssignment; +import static io.grpc.xds.XdsClientTestHelper.buildDiscoveryRequestV2; +import static io.grpc.xds.XdsClientTestHelper.buildDiscoveryResponseV2; +import static io.grpc.xds.XdsClientTestHelper.buildDropOverload; +import static io.grpc.xds.XdsClientTestHelper.buildLbEndpoint; +import static io.grpc.xds.XdsClientTestHelper.buildListener; +import static io.grpc.xds.XdsClientTestHelper.buildLocalityLbEndpoints; +import static io.grpc.xds.XdsClientTestHelper.buildRouteConfiguration; +import static io.grpc.xds.XdsClientTestHelper.buildSecureCluster; +import static io.grpc.xds.XdsClientTestHelper.buildUpstreamTlsContext; +import static io.grpc.xds.XdsClientTestHelper.buildVirtualHost; +import static org.mockito.AdditionalAnswers.delegatesTo; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.protobuf.Any; +import com.google.protobuf.BoolValue; +import com.google.protobuf.UInt32Value; +import com.google.protobuf.util.Durations; +import io.envoyproxy.envoy.api.v2.ClusterLoadAssignment.Policy; +import io.envoyproxy.envoy.api.v2.DiscoveryRequest; +import io.envoyproxy.envoy.api.v2.DiscoveryResponse; +import io.envoyproxy.envoy.api.v2.auth.UpstreamTlsContext; +import io.envoyproxy.envoy.api.v2.core.AggregatedConfigSource; +import io.envoyproxy.envoy.api.v2.core.ConfigSource; +import io.envoyproxy.envoy.api.v2.core.HealthStatus; +import io.envoyproxy.envoy.api.v2.endpoint.ClusterStats; +import io.envoyproxy.envoy.api.v2.route.RedirectAction; +import io.envoyproxy.envoy.api.v2.route.WeightedCluster; +import io.envoyproxy.envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager; +import io.envoyproxy.envoy.config.filter.network.http_connection_manager.v2.Rds; +import io.envoyproxy.envoy.config.route.v3.QueryParameterMatcher; +import io.envoyproxy.envoy.config.route.v3.Route; +import io.envoyproxy.envoy.config.route.v3.RouteAction; +import io.envoyproxy.envoy.config.route.v3.RouteConfiguration; +import io.envoyproxy.envoy.config.route.v3.RouteMatch; +import io.envoyproxy.envoy.config.route.v3.VirtualHost; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.SdsSecretConfig; +import io.envoyproxy.envoy.service.discovery.v2.AggregatedDiscoveryServiceGrpc.AggregatedDiscoveryServiceImplBase; +import io.envoyproxy.envoy.service.load_stats.v2.LoadReportingServiceGrpc.LoadReportingServiceImplBase; +import io.envoyproxy.envoy.service.load_stats.v2.LoadStatsRequest; +import io.envoyproxy.envoy.service.load_stats.v2.LoadStatsResponse; +import io.grpc.Context; +import io.grpc.Context.CancellationListener; +import io.grpc.ManagedChannel; +import io.grpc.Status; +import io.grpc.Status.Code; +import io.grpc.SynchronizationContext; +import io.grpc.inprocess.InProcessChannelBuilder; +import io.grpc.inprocess.InProcessServerBuilder; +import io.grpc.internal.BackoffPolicy; +import io.grpc.internal.FakeClock; +import io.grpc.internal.FakeClock.ScheduledTask; +import io.grpc.internal.FakeClock.TaskFilter; +import io.grpc.stub.StreamObserver; +import io.grpc.testing.GrpcCleanupRule; +import io.grpc.xds.Bootstrapper.ChannelCreds; +import io.grpc.xds.Bootstrapper.ServerInfo; +import io.grpc.xds.EnvoyProtoData.DropOverload; +import io.grpc.xds.EnvoyProtoData.LbEndpoint; +import io.grpc.xds.EnvoyProtoData.Locality; +import io.grpc.xds.EnvoyProtoData.LocalityLbEndpoints; +import io.grpc.xds.EnvoyProtoData.Node; +import io.grpc.xds.XdsClient.ClusterUpdate; +import io.grpc.xds.XdsClient.ClusterWatcher; +import io.grpc.xds.XdsClient.ConfigUpdate; +import io.grpc.xds.XdsClient.ConfigWatcher; +import io.grpc.xds.XdsClient.EndpointUpdate; +import io.grpc.xds.XdsClient.EndpointWatcher; +import io.grpc.xds.XdsClient.XdsChannel; +import io.grpc.xds.XdsClient.XdsChannelFactory; +import io.grpc.xds.XdsClientImpl.MessagePrinter; +import java.io.IOException; +import java.util.ArrayDeque; +import java.util.HashSet; +import java.util.List; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatcher; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link XdsClientImpl} with xDS v2 protocol. + */ +@RunWith(JUnit4.class) +public class XdsClientImplTestV2 { + + private static final String TARGET_AUTHORITY = "foo.googleapis.com:8080"; + + private static final Node NODE = Node.newBuilder().build(); + private static final TaskFilter RPC_RETRY_TASK_FILTER = + new TaskFilter() { + @Override + public boolean shouldAccept(Runnable command) { + return command.toString().contains(XdsClientImpl.RpcRetryTask.class.getSimpleName()); + } + }; + + private static final TaskFilter LDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER = + new TaskFilter() { + @Override + public boolean shouldAccept(Runnable command) { + return command.toString() + .contains(XdsClientImpl.LdsResourceFetchTimeoutTask.class.getSimpleName()); + } + }; + + private static final TaskFilter RDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER = + new TaskFilter() { + @Override + public boolean shouldAccept(Runnable command) { + return command.toString() + .contains(XdsClientImpl.RdsResourceFetchTimeoutTask.class.getSimpleName()); + } + }; + + private static final TaskFilter CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER = + new TaskFilter() { + @Override + public boolean shouldAccept(Runnable command) { + return command.toString() + .contains(XdsClientImpl.CdsResourceFetchTimeoutTask.class.getSimpleName()); + } + }; + + private static final TaskFilter EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER = + new TaskFilter() { + @Override + public boolean shouldAccept(Runnable command) { + return command.toString() + .contains(XdsClientImpl.EdsResourceFetchTimeoutTask.class.getSimpleName()); + } + }; + + @Rule + public final GrpcCleanupRule cleanupRule = new GrpcCleanupRule(); + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private final SynchronizationContext syncContext = new SynchronizationContext( + new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + throw new AssertionError(e); + } + }); + private final FakeClock fakeClock = new FakeClock(); + + private final Queue> responseObservers = new ArrayDeque<>(); + private final Queue> requestObservers = new ArrayDeque<>(); + private final AtomicBoolean adsEnded = new AtomicBoolean(true); + private final Queue loadReportCalls = new ArrayDeque<>(); + private final AtomicBoolean lrsEnded = new AtomicBoolean(true); + + @Mock + private AggregatedDiscoveryServiceImplBase mockedDiscoveryService; + @Mock + private BackoffPolicy.Provider backoffPolicyProvider; + @Mock + private BackoffPolicy backoffPolicy1; + @Mock + private BackoffPolicy backoffPolicy2; + @Mock + private ConfigWatcher configWatcher; + @Mock + private ClusterWatcher clusterWatcher; + @Mock + private EndpointWatcher endpointWatcher; + + private ManagedChannel channel; + private XdsClientImpl xdsClient; + + @Before + public void setUp() throws IOException { + MockitoAnnotations.initMocks(this); + when(backoffPolicyProvider.get()).thenReturn(backoffPolicy1, backoffPolicy2); + when(backoffPolicy1.nextBackoffNanos()).thenReturn(10L, 100L); + when(backoffPolicy2.nextBackoffNanos()).thenReturn(20L, 200L); + + final String serverName = InProcessServerBuilder.generateName(); + AggregatedDiscoveryServiceImplBase adsServiceImpl = new AggregatedDiscoveryServiceImplBase() { + @Override + public StreamObserver streamAggregatedResources( + final StreamObserver responseObserver) { + assertThat(adsEnded.get()).isTrue(); // ensure previous call was ended + adsEnded.set(false); + Context.current().addListener( + new CancellationListener() { + @Override + public void cancelled(Context context) { + adsEnded.set(true); + } + }, MoreExecutors.directExecutor()); + responseObservers.offer(responseObserver); + @SuppressWarnings("unchecked") + StreamObserver requestObserver = mock(StreamObserver.class); + requestObservers.offer(requestObserver); + return requestObserver; + } + }; + mockedDiscoveryService = + mock(AggregatedDiscoveryServiceImplBase.class, delegatesTo(adsServiceImpl)); + + LoadReportingServiceImplBase lrsServiceImpl = new LoadReportingServiceImplBase() { + @Override + public StreamObserver streamLoadStats( + StreamObserver responseObserver) { + assertThat(lrsEnded.get()).isTrue(); + lrsEnded.set(false); + @SuppressWarnings("unchecked") + StreamObserver requestObserver = mock(StreamObserver.class); + final LoadReportCall call = new LoadReportCall(requestObserver, responseObserver); + Context.current().addListener( + new CancellationListener() { + @Override + public void cancelled(Context context) { + lrsEnded.set(true); + } + }, MoreExecutors.directExecutor()); + loadReportCalls.offer(call); + return requestObserver; + } + }; + + cleanupRule.register( + InProcessServerBuilder + .forName(serverName) + .addService(mockedDiscoveryService) + .addService(lrsServiceImpl) + .directExecutor() + .build() + .start()); + channel = + cleanupRule.register(InProcessChannelBuilder.forName(serverName).directExecutor().build()); + + List servers = + ImmutableList.of(new ServerInfo(serverName, ImmutableList.of(), null)); + XdsChannelFactory channelFactory = new XdsChannelFactory() { + @Override + XdsChannel createChannel(List servers) { + ServerInfo serverInfo = Iterables.getOnlyElement(servers); + assertThat(serverInfo.getServerUri()).isEqualTo(serverName); + assertThat(serverInfo.getChannelCredentials()).isEmpty(); + return new XdsChannel(channel, false); + } + }; + + xdsClient = + new XdsClientImpl( + TARGET_AUTHORITY, + servers, + channelFactory, + Node.newBuilder().build(), + syncContext, + fakeClock.getScheduledExecutorService(), + backoffPolicyProvider, + fakeClock.getStopwatchSupplier()); + // Only the connection to management server is established, no RPC request is sent until at + // least one watcher is registered. + assertThat(responseObservers).isEmpty(); + assertThat(requestObservers).isEmpty(); + } + + @After + public void tearDown() { + xdsClient.shutdown(); + assertThat(adsEnded.get()).isTrue(); + assertThat(lrsEnded.get()).isTrue(); + assertThat(channel.isShutdown()).isTrue(); + assertThat(fakeClock.getPendingTasks()).isEmpty(); + } + + // Always test the real workflow and integrity of XdsClient: RDS protocol should always followed + // after at least one LDS request-response, from which the RDS resource name comes. CDS and EDS + // can be tested separately as they are used in a standalone way. + + // Discovery responses should follow management server spec and xDS protocol. See + // https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol. + + /** + * Client receives an LDS response that does not contain a Listener for the requested resource. + * The LDS response is ACKed. + * The config watcher is notified with resource unavailable after its response timer expires. + */ + @Test + public void ldsResponseWithoutMatchingResource() { + xdsClient.watchConfigData(TARGET_AUTHORITY, configWatcher); + StreamObserver responseObserver = responseObservers.poll(); + StreamObserver requestObserver = requestObservers.poll(); + + // Client sends an LDS request for the host name (with port) to management server. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + + assertThat(fakeClock.getPendingTasks(LDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); + + List listeners = ImmutableList.of( + Any.pack(buildListener("bar.googleapis.com", + Any.pack(HttpConnectionManager.newBuilder() + .setRouteConfig( + buildRouteConfiguration("route-bar.googleapis.com", + ImmutableList.of( + buildVirtualHost( + ImmutableList.of("bar.googleapis.com"), + "cluster-bar.googleapis.com")))) + .build()))), + Any.pack(buildListener("baz.googleapis.com", + Any.pack(HttpConnectionManager.newBuilder() + .setRouteConfig( + buildRouteConfiguration("route-baz.googleapis.com", + ImmutableList.of( + buildVirtualHost( + ImmutableList.of("baz.googleapis.com"), + "cluster-baz.googleapis.com")))) + .build())))); + DiscoveryResponse response = + buildDiscoveryResponseV2("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + responseObserver.onNext(response); + + // Client sends an ACK LDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "0", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"))); + + verify(configWatcher, never()).onConfigChanged(any(ConfigUpdate.class)); + verify(configWatcher, never()).onResourceDoesNotExist(TARGET_AUTHORITY); + verify(configWatcher, never()).onError(any(Status.class)); + fakeClock.forwardTime(XdsClientImpl.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC, TimeUnit.SECONDS); + verify(configWatcher).onResourceDoesNotExist(TARGET_AUTHORITY); + assertThat(fakeClock.getPendingTasks(LDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); + } + + /** + * An LDS response contains the requested listener and an in-lined RouteConfiguration message for + * that listener. But the RouteConfiguration message is invalid as it does not contain any + * VirtualHost with domains matching the requested hostname. + * The LDS response is NACKed, as if the XdsClient has not received this response. + * The config watcher is notified with an error after its response timer expires.. + */ + @Test + public void failToFindVirtualHostInLdsResponseInLineRouteConfig() { + xdsClient.watchConfigData(TARGET_AUTHORITY, configWatcher); + StreamObserver responseObserver = responseObservers.poll(); + StreamObserver requestObserver = requestObservers.poll(); + + // Client sends an LDS request for the host name (with port) to management server. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + assertThat(fakeClock.getPendingTasks(LDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); + + io.envoyproxy.envoy.api.v2.RouteConfiguration routeConfig = + buildRouteConfiguration( + "route.googleapis.com", + ImmutableList.of( + buildVirtualHost(ImmutableList.of("something does not match"), + "some cluster"), + buildVirtualHost(ImmutableList.of("something else does not match"), + "some other cluster"))); + + List listeners = ImmutableList.of( + Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(HttpConnectionManager.newBuilder().setRouteConfig(routeConfig).build())))); + DiscoveryResponse response = + buildDiscoveryResponseV2("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + responseObserver.onNext(response); + + // Client sends an NACK LDS request. + verify(requestObserver) + .onNext( + argThat(new DiscoveryRequestMatcher("", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"))); + + verify(configWatcher, never()).onConfigChanged(any(ConfigUpdate.class)); + verify(configWatcher, never()).onResourceDoesNotExist(TARGET_AUTHORITY); + verify(configWatcher, never()).onError(any(Status.class)); + + fakeClock.forwardTime(XdsClientImpl.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC, TimeUnit.SECONDS); + verify(configWatcher).onResourceDoesNotExist(TARGET_AUTHORITY); + assertThat(fakeClock.getPendingTasks(LDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); + } + + /** + * Client resolves the virtual host config from an LDS response that contains a + * RouteConfiguration message directly in-line for the requested resource. No RDS is needed. + * The LDS response is ACKed. + * The config watcher is notified with an update. + */ + @Test + public void resolveVirtualHostInLdsResponse() { + xdsClient.watchConfigData(TARGET_AUTHORITY, configWatcher); + StreamObserver responseObserver = responseObservers.poll(); + StreamObserver requestObserver = requestObservers.poll(); + + // Client sends an LDS request for the host name (with port) to management server. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + ScheduledTask ldsRespTimer = + Iterables.getOnlyElement( + fakeClock.getPendingTasks(LDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)); + assertThat(ldsRespTimer.isCancelled()).isFalse(); + + List listeners = ImmutableList.of( + Any.pack(buildListener("bar.googleapis.com", + Any.pack(HttpConnectionManager.newBuilder() + .setRouteConfig( + buildRouteConfiguration("route-bar.googleapis.com", + ImmutableList.of( + buildVirtualHost( + ImmutableList.of("bar.googleapis.com"), + "cluster-bar.googleapis.com")))) + .build()))), + Any.pack(buildListener("baz.googleapis.com", + Any.pack(HttpConnectionManager.newBuilder() + .setRouteConfig( + buildRouteConfiguration("route-baz.googleapis.com", + ImmutableList.of( + buildVirtualHost( + ImmutableList.of("baz.googleapis.com"), + "cluster-baz.googleapis.com")))) + .build()))), + Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack( + HttpConnectionManager.newBuilder() + .setRouteConfig( // target route configuration + buildRouteConfiguration("route-foo.googleapis.com", + ImmutableList.of( + buildVirtualHost( // matching virtual host + ImmutableList.of(TARGET_AUTHORITY, "bar.googleapis.com"), + "cluster.googleapis.com"), + buildVirtualHost( + ImmutableList.of("something does not match"), + "some cluster")))) + .build())))); + DiscoveryResponse response = + buildDiscoveryResponseV2("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + responseObserver.onNext(response); + + assertThat(ldsRespTimer.isCancelled()).isTrue(); + + // Client sends an ACK request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "0", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"))); + + ArgumentCaptor configUpdateCaptor = ArgumentCaptor.forClass(null); + verify(configWatcher).onConfigChanged(configUpdateCaptor.capture()); + assertConfigUpdateContainsSingleClusterRoute( + configUpdateCaptor.getValue(), "cluster.googleapis.com"); + + verifyNoMoreInteractions(requestObserver); + } + + /** + * Client receives an RDS response (after a previous LDS request-response) that does not contain a + * RouteConfiguration for the requested resource while each received RouteConfiguration is valid. + * The RDS response is ACKed. + * After the resource fetch timeout expires, watcher waiting for the resource is notified + * with resource unavailable. + */ + @Test + public void rdsResponseWithoutMatchingResource() { + xdsClient.watchConfigData(TARGET_AUTHORITY, configWatcher); + StreamObserver responseObserver = responseObservers.poll(); + StreamObserver requestObserver = requestObservers.poll(); + + // Client sends an LDS request for the host name (with port) to management server. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + + Rds rdsConfig = + Rds.newBuilder() + // Must set to use ADS. + .setConfigSource( + ConfigSource.newBuilder().setAds(AggregatedConfigSource.getDefaultInstance())) + .setRouteConfigName("route-foo.googleapis.com") + .build(); + List listeners = ImmutableList.of( + Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) + ); + DiscoveryResponse response = + buildDiscoveryResponseV2("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + responseObserver.onNext(response); + + // Client sends an ACK LDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "0", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"))); + + // Client sends an (first) RDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "route-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_RDS_V2, ""))); + + assertThat(fakeClock.getPendingTasks(RDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); + + // Management server should only sends RouteConfiguration messages with at least one + // VirtualHost with domains matching requested hostname. Otherwise, it is invalid data. + List routeConfigs = ImmutableList.of( + Any.pack( + buildRouteConfiguration( + "some resource name does not match route-foo.googleapis.com", + ImmutableList.of( + buildVirtualHost( + ImmutableList.of(TARGET_AUTHORITY), + "whatever cluster")))), + Any.pack( + buildRouteConfiguration( + "some other resource name does not match route-foo.googleapis.com", + ImmutableList.of( + buildVirtualHost( + ImmutableList.of(TARGET_AUTHORITY), + "some more whatever cluster"))))); + response = buildDiscoveryResponseV2( + "0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); + responseObserver.onNext(response); + + // Client sends an ACK RDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "0", "route-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"))); + + verify(configWatcher, never()).onConfigChanged(any(ConfigUpdate.class)); + verify(configWatcher, never()).onResourceDoesNotExist(anyString()); + verify(configWatcher, never()).onError(any(Status.class)); + fakeClock.forwardTime(XdsClientImpl.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC, TimeUnit.SECONDS); + verify(configWatcher).onResourceDoesNotExist("route-foo.googleapis.com"); + assertThat(fakeClock.getPendingTasks(RDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); + } + + /** + * Client resolves the virtual host config from an RDS response for the requested resource. The + * RDS response is ACKed. + * The config watcher is notified with an update. + */ + @Test + public void resolveVirtualHostInRdsResponse() { + xdsClient.watchConfigData(TARGET_AUTHORITY, configWatcher); + StreamObserver responseObserver = responseObservers.poll(); + StreamObserver requestObserver = requestObservers.poll(); + + Rds rdsConfig = + Rds.newBuilder() + // Must set to use ADS. + .setConfigSource( + ConfigSource.newBuilder().setAds(AggregatedConfigSource.getDefaultInstance())) + .setRouteConfigName("route-foo.googleapis.com") + .build(); + + List listeners = ImmutableList.of( + Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) + ); + DiscoveryResponse response = + buildDiscoveryResponseV2("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + responseObserver.onNext(response); + + // Client sends an ACK LDS request and an RDS request for "route-foo.googleapis.com". (Omitted) + + assertThat(fakeClock.getPendingTasks(RDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); + + // Management server should only sends RouteConfiguration messages with at least one + // VirtualHost with domains matching requested hostname. Otherwise, it is invalid data. + List routeConfigs = ImmutableList.of( + Any.pack( + buildRouteConfiguration( + "route-foo.googleapis.com", // target route configuration + ImmutableList.of( + buildVirtualHost(ImmutableList.of("something does not match"), + "some cluster"), + buildVirtualHost(ImmutableList.of(TARGET_AUTHORITY, "bar.googleapis.com:443"), + "cluster.googleapis.com")))), // matching virtual host + Any.pack( + buildRouteConfiguration( + "some resource name does not match route-foo.googleapis.com", + ImmutableList.of( + buildVirtualHost(ImmutableList.of("foo.googleapis.com"), + "some more cluster"))))); + response = buildDiscoveryResponseV2( + "0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); + responseObserver.onNext(response); + + assertThat(fakeClock.getPendingTasks(RDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); + + // Client sent an ACK RDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "0", "route-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"))); + + ArgumentCaptor configUpdateCaptor = ArgumentCaptor.forClass(null); + verify(configWatcher).onConfigChanged(configUpdateCaptor.capture()); + assertConfigUpdateContainsSingleClusterRoute( + configUpdateCaptor.getValue(), "cluster.googleapis.com"); + } + + /** + * Client resolves the virtual host config with path matching from an RDS response for the + * requested resource. The RDS response is ACKed. + * The config watcher is notified with an update. + */ + @Test + public void resolveVirtualHostWithPathMatchingInRdsResponse() { + xdsClient.watchConfigData(TARGET_AUTHORITY, configWatcher); + StreamObserver responseObserver = responseObservers.poll(); + StreamObserver requestObserver = requestObservers.poll(); + + Rds rdsConfig = + Rds.newBuilder() + // Must set to use ADS. + .setConfigSource( + ConfigSource.newBuilder().setAds(AggregatedConfigSource.getDefaultInstance())) + .setRouteConfigName("route-foo.googleapis.com") + .build(); + + List listeners = ImmutableList.of( + Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) + ); + DiscoveryResponse response = + buildDiscoveryResponseV2("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + responseObserver.onNext(response); + + // Client sends an ACK LDS request and an RDS request for "route-foo.googleapis.com". (Omitted) + + assertThat(fakeClock.getPendingTasks(RDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); + + // Management server should only sends RouteConfiguration messages with at least one + // VirtualHost with domains matching requested hostname. Otherwise, it is invalid data. + List routeConfigs = + ImmutableList.of( + Any.pack( + buildRouteConfiguration( + "route-foo.googleapis.com", + ImmutableList.of( + io.envoyproxy.envoy.api.v2.route.VirtualHost.newBuilder() + .setName("virtualhost00.googleapis.com") // don't care + // domains wit a match. + .addAllDomains(ImmutableList.of(TARGET_AUTHORITY, "bar.googleapis.com")) + .addRoutes( + io.envoyproxy.envoy.api.v2.route.Route.newBuilder() + // path match with cluster route + .setRoute( + io.envoyproxy.envoy.api.v2.route.RouteAction.newBuilder() + .setCluster("cl1.googleapis.com")) + .setMatch( + io.envoyproxy.envoy.api.v2.route.RouteMatch.newBuilder() + .setPath("/service1/method1"))) + .addRoutes( + io.envoyproxy.envoy.api.v2.route.Route.newBuilder() + // path match with weighted cluster route + .setRoute( + io.envoyproxy.envoy.api.v2.route.RouteAction.newBuilder() + .setWeightedClusters( + WeightedCluster.newBuilder() + .addClusters( + WeightedCluster.ClusterWeight.newBuilder() + .setWeight(UInt32Value.of(30)) + .setName("cl21.googleapis.com")) + .addClusters( + WeightedCluster.ClusterWeight.newBuilder() + .setWeight(UInt32Value.of(70)) + .setName("cl22.googleapis.com")))) + .setMatch( + io.envoyproxy.envoy.api.v2.route.RouteMatch.newBuilder() + .setPath("/service2/method2"))) + .addRoutes( + io.envoyproxy.envoy.api.v2.route.Route.newBuilder() + // prefix match with cluster route + .setRoute( + io.envoyproxy.envoy.api.v2.route.RouteAction.newBuilder() + .setCluster("cl1.googleapis.com")) + .setMatch( + io.envoyproxy.envoy.api.v2.route.RouteMatch.newBuilder() + .setPrefix("/service1/"))) + .addRoutes( + io.envoyproxy.envoy.api.v2.route.Route.newBuilder() + // default match with cluster route + .setRoute( + io.envoyproxy.envoy.api.v2.route.RouteAction.newBuilder() + .setCluster("cluster.googleapis.com")) + .setMatch( + io.envoyproxy.envoy.api.v2.route.RouteMatch.newBuilder() + .setPrefix(""))) + .build())))); + response = buildDiscoveryResponseV2( + "0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); + responseObserver.onNext(response); + + assertThat(fakeClock.getPendingTasks(RDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); + + // Client sent an ACK RDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "0", "route-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"))); + + ArgumentCaptor configUpdateCaptor = ArgumentCaptor.forClass(null); + verify(configWatcher).onConfigChanged(configUpdateCaptor.capture()); + List routes = configUpdateCaptor.getValue().getRoutes(); + assertThat(routes).hasSize(4); + assertThat(routes.get(0)).isEqualTo( + new EnvoyProtoData.Route( + // path match with cluster route + new io.grpc.xds.RouteMatch( + /* prefix= */ null, + /* path= */ "/service1/method1"), + new EnvoyProtoData.RouteAction("cl1.googleapis.com", null))); + assertThat(routes.get(1)).isEqualTo( + new EnvoyProtoData.Route( + // path match with weighted cluster route + new io.grpc.xds.RouteMatch( + /* prefix= */ null, + /* path= */ "/service2/method2"), + new EnvoyProtoData.RouteAction( + null, + ImmutableList.of( + new EnvoyProtoData.ClusterWeight("cl21.googleapis.com", 30), + new EnvoyProtoData.ClusterWeight("cl22.googleapis.com", 70) + )))); + assertThat(routes.get(2)).isEqualTo( + new EnvoyProtoData.Route( + // prefix match with cluster route + new io.grpc.xds.RouteMatch( + /* prefix= */ "/service1/", + /* path= */ null), + new EnvoyProtoData.RouteAction("cl1.googleapis.com", null))); + assertThat(routes.get(3)).isEqualTo( + new EnvoyProtoData.Route( + // default match with cluster route + new io.grpc.xds.RouteMatch( + /* prefix= */ "", + /* path= */ null), + new EnvoyProtoData.RouteAction( + "cluster.googleapis.com", null))); + } + + /** + * Client receives an RDS response (after a previous LDS request-response) containing a + * RouteConfiguration message for the requested resource. But the RouteConfiguration message + * is invalid as it does not contain any VirtualHost with domains matching the requested + * hostname. + * The RDS response is NACKed, as if the XdsClient has not received this response. + */ + @Test + public void failToFindVirtualHostInRdsResponse() { + xdsClient.watchConfigData(TARGET_AUTHORITY, configWatcher); + StreamObserver responseObserver = responseObservers.poll(); + StreamObserver requestObserver = requestObservers.poll(); + + Rds rdsConfig = + Rds.newBuilder() + // Must set to use ADS. + .setConfigSource( + ConfigSource.newBuilder().setAds(AggregatedConfigSource.getDefaultInstance())) + .setRouteConfigName("route-foo.googleapis.com") + .build(); + + List listeners = ImmutableList.of( + Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) + ); + DiscoveryResponse response = + buildDiscoveryResponseV2("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + responseObserver.onNext(response); + + // Client sends an ACK LDS request and an RDS request for "route-foo.googleapis.com". (Omitted) + + assertThat(fakeClock.getPendingTasks(RDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); + + List routeConfigs = ImmutableList.of( + Any.pack( + buildRouteConfiguration( + "route-foo.googleapis.com", + ImmutableList.of( + buildVirtualHost(ImmutableList.of("something does not match"), + "some cluster"), + buildVirtualHost( + ImmutableList.of("something else does not match", "also does not match"), + "cluster.googleapis.com")))), + Any.pack( + buildRouteConfiguration( + "some resource name does not match route-foo.googleapis.com", + ImmutableList.of( + buildVirtualHost(ImmutableList.of("one more does not match"), + "some more cluster"))))); + response = buildDiscoveryResponseV2( + "0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); + responseObserver.onNext(response); + + // Client sent an NACK RDS request. + verify(requestObserver) + .onNext( + argThat(new DiscoveryRequestMatcher("", "route-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"))); + + verify(configWatcher, never()).onConfigChanged(any(ConfigUpdate.class)); + verify(configWatcher, never()).onResourceDoesNotExist(anyString()); + verify(configWatcher, never()).onError(any(Status.class)); + fakeClock.forwardTime(XdsClientImpl.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC, TimeUnit.SECONDS); + verify(configWatcher).onResourceDoesNotExist("route-foo.googleapis.com"); + assertThat(fakeClock.getPendingTasks(RDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); + } + + /** + * Client receives an RDS response (after a previous LDS request-response) containing a + * RouteConfiguration message for the requested resource. But the RouteConfiguration message + * is invalid as the VirtualHost with domains matching the requested hostname contains invalid + * data, its RouteAction message is absent. + * The RDS response is NACKed, as if the XdsClient has not received this response. + */ + @Test + public void matchingVirtualHostDoesNotContainRouteAction() { + xdsClient.watchConfigData(TARGET_AUTHORITY, configWatcher); + StreamObserver responseObserver = responseObservers.poll(); + StreamObserver requestObserver = requestObservers.poll(); + + Rds rdsConfig = + Rds.newBuilder() + // Must set to use ADS. + .setConfigSource( + ConfigSource.newBuilder().setAds(AggregatedConfigSource.getDefaultInstance())) + .setRouteConfigName("route-foo.googleapis.com") + .build(); + + List listeners = ImmutableList.of( + Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) + ); + DiscoveryResponse response = + buildDiscoveryResponseV2("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + responseObserver.onNext(response); + + // Client sends an ACK LDS request and an RDS request for "route-foo.googleapis.com". (Omitted) + + assertThat(fakeClock.getPendingTasks(RDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); + + // A VirtualHost with a Route that contains only redirect configuration. + io.envoyproxy.envoy.api.v2.route.VirtualHost virtualHost = + io.envoyproxy.envoy.api.v2.route.VirtualHost.newBuilder() + .setName("virtualhost00.googleapis.com") // don't care + .addDomains(TARGET_AUTHORITY) + .addRoutes( + io.envoyproxy.envoy.api.v2.route.Route.newBuilder() + .setRedirect( + RedirectAction.newBuilder() + .setHostRedirect("bar.googleapis.com") + .setPortRedirect(443))) + .build(); + + List routeConfigs = ImmutableList.of( + Any.pack( + buildRouteConfiguration("route-foo.googleapis.com", + ImmutableList.of(virtualHost)))); + response = buildDiscoveryResponseV2( + "0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); + responseObserver.onNext(response); + + // Client sent an NACK RDS request. + verify(requestObserver) + .onNext( + argThat(new DiscoveryRequestMatcher("", "route-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"))); + + verify(configWatcher, never()).onConfigChanged(any(ConfigUpdate.class)); + verify(configWatcher, never()).onResourceDoesNotExist(anyString()); + verify(configWatcher, never()).onError(any(Status.class)); + fakeClock.forwardTime(XdsClientImpl.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC, TimeUnit.SECONDS); + verify(configWatcher).onResourceDoesNotExist("route-foo.googleapis.com"); + assertThat(fakeClock.getPendingTasks(RDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); + } + + /** + * Client receives LDS/RDS responses for updating resources previously received. + * + *

Tests for streaming behavior. + */ + @Test + public void notifyUpdatedResources() { + xdsClient.watchConfigData(TARGET_AUTHORITY, configWatcher); + StreamObserver responseObserver = responseObservers.poll(); + StreamObserver requestObserver = requestObservers.poll(); + + // Client sends an LDS request for the host name (with port) to management server. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + + // Management server sends back an LDS response containing a RouteConfiguration for the + // requested Listener directly in-line. + io.envoyproxy.envoy.api.v2.RouteConfiguration routeConfig = + buildRouteConfiguration( + "route-foo.googleapis.com", // target route configuration + ImmutableList.of( + buildVirtualHost( // matching virtual host + ImmutableList.of(TARGET_AUTHORITY, "bar.googleapis.com:443"), + "cluster.googleapis.com"), + buildVirtualHost(ImmutableList.of("something does not match"), + "some cluster"))); + + List listeners = ImmutableList.of( + Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(HttpConnectionManager.newBuilder().setRouteConfig(routeConfig).build()))) + ); + DiscoveryResponse response = + buildDiscoveryResponseV2("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + responseObserver.onNext(response); + + // Client sends an ACK LDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "0", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"))); + + // Cluster name is resolved and notified to config watcher. + ArgumentCaptor configUpdateCaptor = ArgumentCaptor.forClass(null); + verify(configWatcher).onConfigChanged(configUpdateCaptor.capture()); + assertConfigUpdateContainsSingleClusterRoute( + configUpdateCaptor.getValue(), "cluster.googleapis.com"); + + // Management sends back another LDS response containing updates for the requested Listener. + routeConfig = + buildRouteConfiguration( + "another-route-foo.googleapis.com", + ImmutableList.of( + buildVirtualHost(ImmutableList.of(TARGET_AUTHORITY, "bar.googleapis.com:443"), + "another-cluster.googleapis.com"), + buildVirtualHost(ImmutableList.of("something does not match"), + "some cluster"))); + + listeners = ImmutableList.of( + Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(HttpConnectionManager.newBuilder().setRouteConfig(routeConfig).build()))) + ); + response = + buildDiscoveryResponseV2("1", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0001"); + responseObserver.onNext(response); + + // Client sends an ACK LDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "1", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0001"))); + + // Updated cluster name is notified to config watcher. + configUpdateCaptor = ArgumentCaptor.forClass(null); + verify(configWatcher, times(2)).onConfigChanged(configUpdateCaptor.capture()); + assertConfigUpdateContainsSingleClusterRoute( + configUpdateCaptor.getValue(), "another-cluster.googleapis.com"); + + // Management server sends back another LDS response containing updates for the requested + // Listener and telling client to do RDS. + Rds rdsConfig = + Rds.newBuilder() + // Must set to use ADS. + .setConfigSource( + ConfigSource.newBuilder().setAds(AggregatedConfigSource.getDefaultInstance())) + .setRouteConfigName("some-route-to-foo.googleapis.com") + .build(); + + listeners = ImmutableList.of( + Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) + ); + response = + buildDiscoveryResponseV2("2", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0002"); + responseObserver.onNext(response); + + // Client sends an ACK LDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "2", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0002"))); + + // Client sends an (first) RDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "some-route-to-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_RDS_V2, ""))); + + // Management server sends back an RDS response containing the RouteConfiguration + // for the requested resource. + List routeConfigs = ImmutableList.of( + Any.pack( + buildRouteConfiguration( + "some-route-to-foo.googleapis.com", + ImmutableList.of( + buildVirtualHost(ImmutableList.of("something does not match"), + "some cluster"), + buildVirtualHost(ImmutableList.of(TARGET_AUTHORITY, "bar.googleapis.com:443"), + "some-other-cluster.googleapis.com"))))); + response = buildDiscoveryResponseV2( + "0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); + responseObserver.onNext(response); + + // Client sent an ACK RDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "0", "some-route-to-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"))); + + // Updated cluster name is notified to config watcher again. + configUpdateCaptor = ArgumentCaptor.forClass(null); + verify(configWatcher, times(3)).onConfigChanged(configUpdateCaptor.capture()); + assertConfigUpdateContainsSingleClusterRoute( + configUpdateCaptor.getValue(), "some-other-cluster.googleapis.com"); + + // Management server sends back another RDS response containing updated information for the + // RouteConfiguration currently in-use by client. + routeConfigs = ImmutableList.of( + Any.pack( + buildRouteConfiguration( + "some-route-to-foo.googleapis.com", + ImmutableList.of( + buildVirtualHost(ImmutableList.of(TARGET_AUTHORITY, "bar.googleapis.com:443"), + "an-updated-cluster.googleapis.com"))))); + response = buildDiscoveryResponseV2( + "1", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0001"); + responseObserver.onNext(response); + + // Client sent an ACK RDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "1", "some-route-to-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0001"))); + + // Updated cluster name is notified to config watcher again. + configUpdateCaptor = ArgumentCaptor.forClass(null); + verify(configWatcher, times(4)).onConfigChanged(configUpdateCaptor.capture()); + assertConfigUpdateContainsSingleClusterRoute( + configUpdateCaptor.getValue(), "an-updated-cluster.googleapis.com"); + + // Management server sends back an LDS response indicating all Listener resources are removed. + response = + buildDiscoveryResponseV2("3", ImmutableList.of(), + XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0003"); + responseObserver.onNext(response); + + verify(configWatcher).onResourceDoesNotExist(TARGET_AUTHORITY); + } + + // TODO(chengyuanzhang): tests for timeout waiting for responses for incremental + // protocols (RDS/EDS). + + /** + * Client receives multiple RDS responses without RouteConfiguration for the requested + * resource. It should continue waiting until such an RDS response arrives, as RDS + * protocol is incremental. + * + *

Tests for RDS incremental protocol behavior. + */ + @Test + public void waitRdsResponsesForRequestedResource() { + xdsClient.watchConfigData(TARGET_AUTHORITY, configWatcher); + StreamObserver responseObserver = responseObservers.poll(); + StreamObserver requestObserver = requestObservers.poll(); + + // Client sends an LDS request for the host name (with port) to management server. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + + // Management sends back an LDS response telling client to do RDS. + Rds rdsConfig = + Rds.newBuilder() + // Must set to use ADS. + .setConfigSource( + ConfigSource.newBuilder().setAds(AggregatedConfigSource.getDefaultInstance())) + .setRouteConfigName("route-foo.googleapis.com") + .build(); + + List listeners = ImmutableList.of( + Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) + ); + DiscoveryResponse response = + buildDiscoveryResponseV2("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + responseObserver.onNext(response); + + // Client sends an ACK LDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "0", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"))); + + // Client sends an (first) RDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "route-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_RDS_V2, ""))); + + ScheduledTask rdsRespTimer = + Iterables.getOnlyElement( + fakeClock.getPendingTasks(RDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)); + assertThat(rdsRespTimer.isCancelled()).isFalse(); + + fakeClock.forwardTime(XdsClientImpl.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC - 2, TimeUnit.SECONDS); + + // Management server sends back an RDS response that does not contain RouteConfiguration + // for the requested resource. + List routeConfigs = ImmutableList.of( + Any.pack( + buildRouteConfiguration( + "some resource name does not match route-foo.googleapis.com", + ImmutableList.of( + buildVirtualHost( + ImmutableList.of(TARGET_AUTHORITY), + "some more cluster"))))); + response = buildDiscoveryResponseV2( + "0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); + responseObserver.onNext(response); + + // Client sent an ACK RDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "0", "route-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"))); + + // Client waits for future RDS responses silently. + verifyNoMoreInteractions(configWatcher); + assertThat(rdsRespTimer.isCancelled()).isFalse(); + + fakeClock.forwardTime(1, TimeUnit.SECONDS); + + // Management server sends back another RDS response containing the RouteConfiguration + // for the requested resource. + routeConfigs = ImmutableList.of( + Any.pack( + buildRouteConfiguration( + "route-foo.googleapis.com", // target route configuration + ImmutableList.of( + buildVirtualHost( + ImmutableList.of("something does not match"), + "some cluster"), + buildVirtualHost( // matching virtual host + ImmutableList.of(TARGET_AUTHORITY, "bar.googleapis.com:443"), + "another-cluster.googleapis.com"))))); + response = buildDiscoveryResponseV2( + "1", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0001"); + responseObserver.onNext(response); + + // Client sent an ACK RDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "1", "route-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0001"))); + + // Updated cluster name is notified to config watcher. + ArgumentCaptor configUpdateCaptor = ArgumentCaptor.forClass(null); + verify(configWatcher).onConfigChanged(configUpdateCaptor.capture()); + assertConfigUpdateContainsSingleClusterRoute( + configUpdateCaptor.getValue(), "another-cluster.googleapis.com"); + assertThat(rdsRespTimer.isCancelled()).isTrue(); + } + + /** + * An RouteConfiguration is removed by server by sending client an LDS response removing the + * corresponding Listener. + */ + @Test + public void routeConfigurationRemovedNotifiedToWatcher() { + xdsClient.watchConfigData(TARGET_AUTHORITY, configWatcher); + StreamObserver responseObserver = responseObservers.poll(); + StreamObserver requestObserver = requestObservers.poll(); + + // Client sends an LDS request for the host name (with port) to management server. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + + // Management sends back an LDS response telling client to do RDS. + Rds rdsConfig = + Rds.newBuilder() + // Must set to use ADS. + .setConfigSource( + ConfigSource.newBuilder().setAds(AggregatedConfigSource.getDefaultInstance())) + .setRouteConfigName("route-foo.googleapis.com") + .build(); + + List listeners = ImmutableList.of( + Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) + ); + DiscoveryResponse response = + buildDiscoveryResponseV2("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + responseObserver.onNext(response); + + // Client sends an ACK LDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "0", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"))); + + // Client sends an (first) RDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "route-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_RDS_V2, ""))); + + // Management server sends back an RDS response containing RouteConfiguration requested. + List routeConfigs = ImmutableList.of( + Any.pack( + buildRouteConfiguration( + "route-foo.googleapis.com", // target route configuration + ImmutableList.of( + buildVirtualHost( + ImmutableList.of(TARGET_AUTHORITY), // matching virtual host + "cluster.googleapis.com"))))); + response = buildDiscoveryResponseV2( + "0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); + responseObserver.onNext(response); + + // Client sent an ACK RDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "0", "route-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"))); + + // Resolved cluster name is notified to config watcher. + ArgumentCaptor configUpdateCaptor = ArgumentCaptor.forClass(null); + verify(configWatcher).onConfigChanged(configUpdateCaptor.capture()); + assertConfigUpdateContainsSingleClusterRoute( + configUpdateCaptor.getValue(), "cluster.googleapis.com"); + + // Management server sends back another LDS response with the previous Listener (currently + // in-use by client) removed as the RouteConfiguration it references to is absent. + response = + buildDiscoveryResponseV2("1", ImmutableList.of(), // empty + XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0001"); + responseObserver.onNext(response); + + // Client sent an ACK LDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "1", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0001"))); + + verify(configWatcher).onResourceDoesNotExist(TARGET_AUTHORITY); + } + + /** + * Management server sends another LDS response for updating the RDS resource to be requested + * while client is currently requesting for a previously given RDS resource name. + */ + @Test + public void updateRdsRequestResourceWhileInitialResourceFetchInProgress() { + xdsClient.watchConfigData(TARGET_AUTHORITY, configWatcher); + StreamObserver responseObserver = responseObservers.poll(); + StreamObserver requestObserver = requestObservers.poll(); + + // Management sends back an LDS response telling client to do RDS. + Rds rdsConfig = + Rds.newBuilder() + // Must set to use ADS. + .setConfigSource( + ConfigSource.newBuilder().setAds(AggregatedConfigSource.getDefaultInstance())) + .setRouteConfigName("route-foo.googleapis.com") + .build(); + + List listeners = ImmutableList.of( + Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) + ); + DiscoveryResponse response = + buildDiscoveryResponseV2("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + responseObserver.onNext(response); + + // Client sends an (first) RDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "route-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_RDS_V2, ""))); + + ScheduledTask rdsRespTimer = + Iterables.getOnlyElement( + fakeClock.getPendingTasks(RDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)); + assertThat(rdsRespTimer.isCancelled()).isFalse(); + + // Management sends back another LDS response updating the Listener information to use + // another resource name for doing RDS. + rdsConfig = + Rds.newBuilder() + // Must set to use ADS. + .setConfigSource( + ConfigSource.newBuilder().setAds(AggregatedConfigSource.getDefaultInstance())) + .setRouteConfigName("route-bar.googleapis.com") + .build(); + + listeners = ImmutableList.of( + Any.pack( + buildListener( + TARGET_AUTHORITY, /* matching resource */ + Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) + ); + response = buildDiscoveryResponseV2("1", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0001"); + responseObserver.onNext(response); + + // Client sent a new RDS request with updated resource name. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "route-bar.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_RDS_V2, ""))); + + assertThat(rdsRespTimer.isCancelled()).isTrue(); + rdsRespTimer = + Iterables.getOnlyElement( + fakeClock.getPendingTasks(RDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)); + assertThat(rdsRespTimer.isCancelled()).isFalse(); + + // Management server sends back an RDS response containing RouteConfiguration requested. + List routeConfigs = ImmutableList.of( + Any.pack( + buildRouteConfiguration( + "route-bar.googleapis.com", // target route configuration + ImmutableList.of( + buildVirtualHost( + ImmutableList.of(TARGET_AUTHORITY), // matching virtual host + "cluster.googleapis.com"))))); + response = buildDiscoveryResponseV2( + "0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); + responseObserver.onNext(response); + + assertThat(rdsRespTimer.isCancelled()).isTrue(); + } + + /** + * Client receives an CDS response that does not contain a Cluster for the requested resource + * while each received Cluster is valid. The CDS response is ACKed. Cluster watchers are notified + * with resource unavailable after initial resource fetch timeout has expired. + */ + @Test + public void cdsResponseWithoutMatchingResource() { + xdsClient.watchClusterData("cluster-foo.googleapis.com", clusterWatcher); + StreamObserver responseObserver = responseObservers.poll(); + StreamObserver requestObserver = requestObservers.poll(); + + // Client sends a CDS request for the only cluster being watched to management server. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); + + // Management server sends back a CDS response without Cluster for the requested resource. + List clusters = ImmutableList.of( + Any.pack(buildCluster("cluster-bar.googleapis.com", null, false)), + Any.pack(buildCluster("cluster-baz.googleapis.com", null, false))); + DiscoveryResponse response = + buildDiscoveryResponseV2("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); + responseObserver.onNext(response); + + // Client sent an ACK CDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "0", "cluster-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"))); + verify(clusterWatcher, never()).onClusterChanged(any(ClusterUpdate.class)); + verify(clusterWatcher, never()).onResourceDoesNotExist("cluster-foo.googleapis.com"); + verify(clusterWatcher, never()).onError(any(Status.class)); + + fakeClock.forwardTime(XdsClientImpl.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC, TimeUnit.SECONDS); + verify(clusterWatcher).onResourceDoesNotExist("cluster-foo.googleapis.com"); + assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); + } + + /** + * Normal workflow of receiving a CDS response containing Cluster message for a requested + * cluster. + */ + @Test + public void cdsResponseWithMatchingResource() { + xdsClient.watchClusterData("cluster-foo.googleapis.com", clusterWatcher); + StreamObserver responseObserver = responseObservers.poll(); + StreamObserver requestObserver = requestObservers.poll(); + + // Client sends a CDS request for the only cluster being watched to management server. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + ScheduledTask cdsRespTimer = + Iterables.getOnlyElement( + fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)); + + // Management server sends back a CDS response without Cluster for the requested resource. + List clusters = ImmutableList.of( + Any.pack(buildCluster("cluster-bar.googleapis.com", null, false)), + Any.pack(buildCluster("cluster-foo.googleapis.com", null, false)), + Any.pack(buildCluster("cluster-baz.googleapis.com", null, false))); + DiscoveryResponse response = + buildDiscoveryResponseV2("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); + responseObserver.onNext(response); + + // Client sent an ACK CDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "0", "cluster-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"))); + assertThat(cdsRespTimer.isCancelled()).isTrue(); + + ArgumentCaptor clusterUpdateCaptor = ArgumentCaptor.forClass(null); + verify(clusterWatcher).onClusterChanged(clusterUpdateCaptor.capture()); + ClusterUpdate clusterUpdate = clusterUpdateCaptor.getValue(); + assertThat(clusterUpdate.getClusterName()).isEqualTo("cluster-foo.googleapis.com"); + assertThat(clusterUpdate.getEdsServiceName()).isNull(); + assertThat(clusterUpdate.getLbPolicy()).isEqualTo("round_robin"); + assertThat(clusterUpdate.getLrsServerName()).isNull(); + + // Management server sends back another CDS response updating the requested Cluster. + clusters = ImmutableList.of( + Any.pack(buildCluster("cluster-bar.googleapis.com", null, false)), + Any.pack( + buildCluster("cluster-foo.googleapis.com", "eds-cluster-foo.googleapis.com", true)), + Any.pack(buildCluster("cluster-baz.googleapis.com", null, false))); + response = + buildDiscoveryResponseV2("1", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0001"); + responseObserver.onNext(response); + + // Client sent an ACK CDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "1", "cluster-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0001"))); + + verify(clusterWatcher, times(2)).onClusterChanged(clusterUpdateCaptor.capture()); + clusterUpdate = clusterUpdateCaptor.getValue(); + assertThat(clusterUpdate.getClusterName()).isEqualTo("cluster-foo.googleapis.com"); + assertThat(clusterUpdate.getEdsServiceName()) + .isEqualTo("eds-cluster-foo.googleapis.com"); + assertThat(clusterUpdate.getLbPolicy()).isEqualTo("round_robin"); + assertThat(clusterUpdate.getLrsServerName()).isEqualTo(""); + } + + /** + * CDS response containing UpstreamTlsContext for a cluster. + */ + @Test + public void cdsResponseWithUpstreamTlsContext() { + xdsClient.watchClusterData("cluster-foo.googleapis.com", clusterWatcher); + StreamObserver responseObserver = responseObservers.poll(); + StreamObserver requestObserver = requestObservers.poll(); + + // Management server sends back CDS response with UpstreamTlsContext. + UpstreamTlsContext testUpstreamTlsContext = + buildUpstreamTlsContext("secret1", "unix:/var/uds2"); + List clusters = ImmutableList.of( + Any.pack(buildCluster("cluster-bar.googleapis.com", null, false)), + Any.pack(buildSecureCluster("cluster-foo.googleapis.com", + "eds-cluster-foo.googleapis.com", true, testUpstreamTlsContext)), + Any.pack(buildCluster("cluster-baz.googleapis.com", null, false))); + DiscoveryResponse response = + buildDiscoveryResponseV2("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); + responseObserver.onNext(response); + + // Client sent an ACK CDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "0", "cluster-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"))); + ArgumentCaptor clusterUpdateCaptor = ArgumentCaptor.forClass(null); + verify(clusterWatcher, times(1)).onClusterChanged(clusterUpdateCaptor.capture()); + ClusterUpdate clusterUpdate = clusterUpdateCaptor.getValue(); + EnvoyServerProtoData.UpstreamTlsContext upstreamTlsContext = clusterUpdate + .getUpstreamTlsContext(); + SdsSecretConfig validationContextSdsSecretConfig = upstreamTlsContext.getCommonTlsContext() + .getValidationContextSdsSecretConfig(); + assertThat(validationContextSdsSecretConfig.getName()).isEqualTo("secret1"); + assertThat( + Iterables.getOnlyElement( + validationContextSdsSecretConfig + .getSdsConfig() + .getApiConfigSource() + .getGrpcServicesList()) + .getGoogleGrpc() + .getTargetUri()) + .isEqualTo("unix:/var/uds2"); + } + + @Test + public void multipleClusterWatchers() { + ClusterWatcher watcher1 = mock(ClusterWatcher.class); + ClusterWatcher watcher2 = mock(ClusterWatcher.class); + ClusterWatcher watcher3 = mock(ClusterWatcher.class); + xdsClient.watchClusterData("cluster-foo.googleapis.com", watcher1); + xdsClient.watchClusterData("cluster-foo.googleapis.com", watcher2); + xdsClient.watchClusterData("cluster-bar.googleapis.com", watcher3); + + StreamObserver responseObserver = responseObservers.poll(); + StreamObserver requestObserver = requestObservers.poll(); + + // Client sends a CDS request containing all clusters being watched to management server. + verify(requestObserver) + .onNext( + argThat( + new DiscoveryRequestMatcher("", + ImmutableList.of("cluster-foo.googleapis.com", "cluster-bar.googleapis.com"), + XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(2); + + // Management server sends back a CDS response contains Cluster for only one of + // requested cluster. + List clusters = ImmutableList.of( + Any.pack(buildCluster("cluster-foo.googleapis.com", null, false))); + DiscoveryResponse response = + buildDiscoveryResponseV2("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); + responseObserver.onNext(response); + + assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); + // Client sent an ACK CDS request. + verify(requestObserver) + .onNext( + argThat( + new DiscoveryRequestMatcher("0", + ImmutableList.of("cluster-foo.googleapis.com", "cluster-bar.googleapis.com"), + XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"))); + + // Two watchers get notification of cluster update for the cluster they are interested in. + ArgumentCaptor clusterUpdateCaptor1 = ArgumentCaptor.forClass(null); + verify(watcher1).onClusterChanged(clusterUpdateCaptor1.capture()); + ClusterUpdate clusterUpdate1 = clusterUpdateCaptor1.getValue(); + assertThat(clusterUpdate1.getClusterName()).isEqualTo("cluster-foo.googleapis.com"); + assertThat(clusterUpdate1.getClusterName()).isEqualTo("cluster-foo.googleapis.com"); + assertThat(clusterUpdate1.getEdsServiceName()).isNull(); + assertThat(clusterUpdate1.getLbPolicy()).isEqualTo("round_robin"); + assertThat(clusterUpdate1.getLrsServerName()).isNull(); + + ArgumentCaptor clusterUpdateCaptor2 = ArgumentCaptor.forClass(null); + verify(watcher2).onClusterChanged(clusterUpdateCaptor2.capture()); + ClusterUpdate clusterUpdate2 = clusterUpdateCaptor2.getValue(); + assertThat(clusterUpdate2.getClusterName()).isEqualTo("cluster-foo.googleapis.com"); + assertThat(clusterUpdate2.getClusterName()).isEqualTo("cluster-foo.googleapis.com"); + assertThat(clusterUpdate2.getEdsServiceName()).isNull(); + assertThat(clusterUpdate2.getLbPolicy()).isEqualTo("round_robin"); + assertThat(clusterUpdate2.getLrsServerName()).isNull(); + + verify(watcher3, never()).onClusterChanged(any(ClusterUpdate.class)); + verify(watcher3, never()).onResourceDoesNotExist("cluster-bar.googleapis.com"); + verify(watcher3, never()).onError(any(Status.class)); + + // The other watcher gets an error notification for cluster not found after its timer expired. + fakeClock.forwardTime(XdsClientImpl.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC, TimeUnit.SECONDS); + verify(watcher3).onResourceDoesNotExist("cluster-bar.googleapis.com"); + assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); + + // Management server sends back another CDS response contains Clusters for all + // requested clusters. + clusters = ImmutableList.of( + Any.pack(buildCluster("cluster-foo.googleapis.com", null, false)), + Any.pack( + buildCluster("cluster-bar.googleapis.com", + "eds-cluster-bar.googleapis.com", true))); + response = buildDiscoveryResponseV2("1", clusters, + XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0001"); + responseObserver.onNext(response); + + // Client sent an ACK CDS request. + verify(requestObserver) + .onNext( + argThat( + new DiscoveryRequestMatcher("1", + ImmutableList.of("cluster-foo.googleapis.com", "cluster-bar.googleapis.com"), + XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0001"))); + + verifyNoMoreInteractions(watcher1, watcher2); // resource has no change + ArgumentCaptor clusterUpdateCaptor3 = ArgumentCaptor.forClass(null); + verify(watcher3).onClusterChanged(clusterUpdateCaptor3.capture()); + ClusterUpdate clusterUpdate3 = clusterUpdateCaptor3.getValue(); + assertThat(clusterUpdate3.getClusterName()).isEqualTo("cluster-bar.googleapis.com"); + assertThat(clusterUpdate3.getEdsServiceName()) + .isEqualTo("eds-cluster-bar.googleapis.com"); + assertThat(clusterUpdate3.getLbPolicy()).isEqualTo("round_robin"); + assertThat(clusterUpdate3.getLrsServerName()).isEqualTo(""); + } + + /** + * (CDS response caching behavior) Adding cluster watchers interested in some cluster that + * some other endpoint watcher had already been watching on will result in cluster update + * notified to the newly added watcher immediately, without sending new CDS requests. + */ + @Test + public void watchClusterAlreadyBeingWatched() { + ClusterWatcher watcher1 = mock(ClusterWatcher.class); + xdsClient.watchClusterData("cluster-foo.googleapis.com", watcher1); + + // Streaming RPC starts after a first watcher is added. + StreamObserver responseObserver = responseObservers.poll(); + StreamObserver requestObserver = requestObservers.poll(); + + // Client sends an CDS request to management server. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); + + // Management server sends back an CDS response with Cluster for the requested + // cluster. + List clusters = ImmutableList.of( + Any.pack(buildCluster("cluster-foo.googleapis.com", null, false))); + DiscoveryResponse response = + buildDiscoveryResponseV2("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); + responseObserver.onNext(response); + + // Client sent an ACK CDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "0", "cluster-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"))); + + ArgumentCaptor clusterUpdateCaptor1 = ArgumentCaptor.forClass(null); + verify(watcher1).onClusterChanged(clusterUpdateCaptor1.capture()); + ClusterUpdate clusterUpdate1 = clusterUpdateCaptor1.getValue(); + assertThat(clusterUpdate1.getClusterName()).isEqualTo("cluster-foo.googleapis.com"); + assertThat(clusterUpdate1.getEdsServiceName()).isNull(); + assertThat(clusterUpdate1.getLbPolicy()).isEqualTo("round_robin"); + assertThat(clusterUpdate1.getLrsServerName()).isNull(); + assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); + + // Another cluster watcher interested in the same cluster is added. + ClusterWatcher watcher2 = mock(ClusterWatcher.class); + xdsClient.watchClusterData("cluster-foo.googleapis.com", watcher2); + + // Since the client has received cluster update for this cluster before, cached result is + // notified to the newly added watcher immediately. + ArgumentCaptor clusterUpdateCaptor2 = ArgumentCaptor.forClass(null); + verify(watcher2).onClusterChanged(clusterUpdateCaptor2.capture()); + ClusterUpdate clusterUpdate2 = clusterUpdateCaptor2.getValue(); + assertThat(clusterUpdate2.getClusterName()).isEqualTo("cluster-foo.googleapis.com"); + assertThat(clusterUpdate2.getEdsServiceName()).isNull(); + assertThat(clusterUpdate2.getLbPolicy()).isEqualTo("round_robin"); + assertThat(clusterUpdate2.getLrsServerName()).isNull(); + + verifyNoMoreInteractions(requestObserver); + assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); + } + + /** + * Basic operations of adding/canceling cluster data watchers. + */ + @Test + public void addRemoveClusterWatchers() { + ClusterWatcher watcher1 = mock(ClusterWatcher.class); + xdsClient.watchClusterData("cluster-foo.googleapis.com", watcher1); + + // Streaming RPC starts after a first watcher is added. + StreamObserver responseObserver = responseObservers.poll(); + StreamObserver requestObserver = requestObservers.poll(); + + // Client sends an CDS request to management server. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + + // Management server sends back a CDS response with Cluster for the requested + // cluster. + List clusters = ImmutableList.of( + Any.pack(buildCluster("cluster-foo.googleapis.com", null, false))); + DiscoveryResponse response = + buildDiscoveryResponseV2("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); + responseObserver.onNext(response); + + // Client sent an ACK CDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "0", "cluster-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"))); + + ArgumentCaptor clusterUpdateCaptor1 = ArgumentCaptor.forClass(null); + verify(watcher1).onClusterChanged(clusterUpdateCaptor1.capture()); + ClusterUpdate clusterUpdate1 = clusterUpdateCaptor1.getValue(); + assertThat(clusterUpdate1.getClusterName()).isEqualTo("cluster-foo.googleapis.com"); + assertThat(clusterUpdate1.getEdsServiceName()).isNull(); + assertThat(clusterUpdate1.getLbPolicy()).isEqualTo("round_robin"); + assertThat(clusterUpdate1.getLrsServerName()).isNull(); + + // Add another cluster watcher for a different cluster. + ClusterWatcher watcher2 = mock(ClusterWatcher.class); + xdsClient.watchClusterData("cluster-bar.googleapis.com", watcher2); + + // Client sent a new CDS request for all interested resources. + verify(requestObserver) + .onNext( + argThat( + new DiscoveryRequestMatcher("0", + ImmutableList.of("cluster-foo.googleapis.com", "cluster-bar.googleapis.com"), + XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"))); + + // Management server sends back a CDS response with Cluster for all requested cluster. + clusters = ImmutableList.of( + Any.pack(buildCluster("cluster-foo.googleapis.com", null, false)), + Any.pack( + buildCluster("cluster-bar.googleapis.com", + "eds-cluster-bar.googleapis.com", true))); + response = buildDiscoveryResponseV2("1", clusters, + XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0001"); + responseObserver.onNext(response); + + // Client sent an ACK CDS request for all interested resources. + verify(requestObserver) + .onNext( + argThat( + new DiscoveryRequestMatcher("1", + ImmutableList.of("cluster-foo.googleapis.com", "cluster-bar.googleapis.com"), + XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0001"))); + verifyNoMoreInteractions(watcher1); // resource has no change + ArgumentCaptor clusterUpdateCaptor2 = ArgumentCaptor.forClass(null); + verify(watcher2).onClusterChanged(clusterUpdateCaptor2.capture()); + ClusterUpdate clusterUpdate2 = clusterUpdateCaptor2.getValue(); + assertThat(clusterUpdate2.getClusterName()).isEqualTo("cluster-bar.googleapis.com"); + assertThat(clusterUpdate2.getEdsServiceName()) + .isEqualTo("eds-cluster-bar.googleapis.com"); + assertThat(clusterUpdate2.getLbPolicy()).isEqualTo("round_robin"); + assertThat(clusterUpdate2.getLrsServerName()).isEqualTo(""); + + // Cancel one of the watcher. + xdsClient.cancelClusterDataWatch("cluster-foo.googleapis.com", watcher1); + + // Since the cancelled watcher was the last watcher interested in that cluster (but there + // is still interested resource), client sent an new CDS request to unsubscribe from + // that cluster. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "1", "cluster-bar.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0001"))); + + // Management server has nothing to respond. + + // Cancel the other watcher. All resources have been unsubscribed. + xdsClient.cancelClusterDataWatch("cluster-bar.googleapis.com", watcher2); + + verify(requestObserver) + .onNext( + argThat( + new DiscoveryRequestMatcher("1", ImmutableList.of(), + XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0001"))); + + // Management server sends back a new CDS response. + clusters = ImmutableList.of( + Any.pack(buildCluster("cluster-foo.googleapis.com", null, true)), + Any.pack( + buildCluster("cluster-bar.googleapis.com", null, false))); + response = + buildDiscoveryResponseV2("2", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0002"); + responseObserver.onNext(response); + + verify(requestObserver) + .onNext( + argThat( + new DiscoveryRequestMatcher("2", ImmutableList.of(), + XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0002"))); + + // Cancelled watchers do not receive notification. + verifyNoMoreInteractions(watcher1, watcher2); + + // A new cluster watcher is added to watch cluster foo again. + ClusterWatcher watcher3 = mock(ClusterWatcher.class); + xdsClient.watchClusterData("cluster-foo.googleapis.com", watcher3); + verify(watcher3, never()).onClusterChanged(any(ClusterUpdate.class)); + + // A CDS request is sent to indicate subscription of "cluster-foo.googleapis.com" only. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "2", "cluster-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0002"))); + + // Management server sends back a new CDS response for at least newly requested resources + // (it is required to do so). + clusters = ImmutableList.of( + Any.pack(buildCluster("cluster-foo.googleapis.com", null, true)), + Any.pack( + buildCluster("cluster-bar.googleapis.com", null, false))); + response = + buildDiscoveryResponseV2("3", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0003"); + responseObserver.onNext(response); + + // Notified with cached data immediately. + ArgumentCaptor clusterUpdateCaptor3 = ArgumentCaptor.forClass(null); + verify(watcher3).onClusterChanged(clusterUpdateCaptor3.capture()); + ClusterUpdate clusterUpdate3 = clusterUpdateCaptor3.getValue(); + assertThat(clusterUpdate3.getClusterName()).isEqualTo("cluster-foo.googleapis.com"); + assertThat(clusterUpdate3.getEdsServiceName()).isNull(); + assertThat(clusterUpdate3.getLbPolicy()).isEqualTo("round_robin"); + assertThat(clusterUpdate2.getLrsServerName()).isEqualTo(""); + + verifyNoMoreInteractions(watcher1, watcher2); + + // A CDS request is sent to re-subscribe the cluster again. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "3", "cluster-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0003"))); + } + + @Test + public void addRemoveClusterWatcherWhileInitialResourceFetchInProgress() { + ClusterWatcher watcher1 = mock(ClusterWatcher.class); + xdsClient.watchClusterData("cluster-foo.googleapis.com", watcher1); + + // Streaming RPC starts after a first watcher is added. + StreamObserver requestObserver = requestObservers.poll(); + + // Client sends an EDS request to management server. + verify(requestObserver) + .onNext( + argThat( + new DiscoveryRequestMatcher("", "cluster-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); + + fakeClock.forwardTime(XdsClientImpl.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC - 1, TimeUnit.SECONDS); + + ClusterWatcher watcher2 = mock(ClusterWatcher.class); + ClusterWatcher watcher3 = mock(ClusterWatcher.class); + ClusterWatcher watcher4 = mock(ClusterWatcher.class); + xdsClient.watchClusterData("cluster-foo.googleapis.com", watcher2); + xdsClient.watchClusterData("cluster-bar.googleapis.com", watcher3); + xdsClient.watchClusterData("cluster-bar.googleapis.com", watcher4); + + // Client sends a new CDS request for updating the latest resource subscription. + verify(requestObserver) + .onNext( + argThat( + new DiscoveryRequestMatcher("", + ImmutableList.of("cluster-foo.googleapis.com", "cluster-bar.googleapis.com"), + XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(2); + + fakeClock.forwardTime(1, TimeUnit.SECONDS); + assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); + + // CDS resource "cluster-foo.googleapis.com" is known to be absent. + verify(watcher1).onResourceDoesNotExist("cluster-foo.googleapis.com"); + verify(watcher2).onResourceDoesNotExist("cluster-foo.googleapis.com"); + + // The absence result is known immediately. + ClusterWatcher watcher5 = mock(ClusterWatcher.class); + xdsClient.watchClusterData("cluster-foo.googleapis.com", watcher5); + verify(watcher5).onResourceDoesNotExist("cluster-foo.googleapis.com"); + + assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); + ScheduledTask timeoutTask = Iterables.getOnlyElement(fakeClock.getPendingTasks()); + + // Cancel watchers while discovery for resource "cluster-bar.googleapis.com" is still + // in progress. + xdsClient.cancelClusterDataWatch("cluster-bar.googleapis.com", watcher3); + assertThat(timeoutTask.isCancelled()).isFalse(); + xdsClient.cancelClusterDataWatch("cluster-bar.googleapis.com", watcher4); + + // Client sends a CDS request for resource subscription update (Omitted). + + fakeClock.forwardTime(XdsClientImpl.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC, TimeUnit.SECONDS); + + assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); + assertThat(timeoutTask.isCancelled()).isTrue(); + + verifyZeroInteractions(watcher3, watcher4); + } + + @Test + public void cdsUpdateForClusterBeingRemoved() { + xdsClient.watchClusterData("cluster-foo.googleapis.com", clusterWatcher); + StreamObserver responseObserver = responseObservers.poll(); + StreamObserver requestObserver = requestObservers.poll(); + + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); + + // Management server sends back a CDS response containing requested resource. + List clusters = ImmutableList.of( + Any.pack(buildCluster("cluster-foo.googleapis.com", null, true))); + DiscoveryResponse response = + buildDiscoveryResponseV2("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); + responseObserver.onNext(response); + + // Client sent an ACK CDS request (Omitted). + + ArgumentCaptor clusterUpdateCaptor = ArgumentCaptor.forClass(null); + verify(clusterWatcher).onClusterChanged(clusterUpdateCaptor.capture()); + ClusterUpdate clusterUpdate = clusterUpdateCaptor.getValue(); + assertThat(clusterUpdate.getClusterName()).isEqualTo("cluster-foo.googleapis.com"); + assertThat(clusterUpdate.getEdsServiceName()).isNull(); + assertThat(clusterUpdate.getLbPolicy()).isEqualTo("round_robin"); + assertThat(clusterUpdate.getLrsServerName()).isEqualTo(""); + assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); + + // No cluster is available. + response = + buildDiscoveryResponseV2("1", ImmutableList.of(), + XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0001"); + responseObserver.onNext(response); + + verify(clusterWatcher).onResourceDoesNotExist("cluster-foo.googleapis.com"); + } + + /** + * Client receives an EDS response that does not contain a ClusterLoadAssignment for the + * requested resource while each received ClusterLoadAssignment is valid. + * The EDS response is ACKed. + * After the resource fetch timeout expires, watchers waiting for the resource is notified + * with resource unavailable. + */ + @Test + public void edsResponseWithoutMatchingResource() { + xdsClient.watchEndpointData("cluster-foo.googleapis.com", endpointWatcher); + StreamObserver responseObserver = responseObservers.poll(); + StreamObserver requestObserver = requestObservers.poll(); + + // Client sends an EDS request for the only cluster being watched to management server. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + assertThat(fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); + + // Management server sends back an EDS response without ClusterLoadAssignment for the requested + // cluster. + List clusterLoadAssignments = ImmutableList.of( + Any.pack(buildClusterLoadAssignment("cluster-bar.googleapis.com", + ImmutableList.of( + buildLocalityLbEndpoints("region1", "zone1", "subzone1", + ImmutableList.of( + buildLbEndpoint("192.168.0.1", 8080, HealthStatus.HEALTHY, 2)), + 1, 0)), + ImmutableList.of())), + Any.pack(buildClusterLoadAssignment("cluster-baz.googleapis.com", + ImmutableList.of( + buildLocalityLbEndpoints("region2", "zone2", "subzone2", + ImmutableList.of( + buildLbEndpoint("192.168.234.52", 8888, HealthStatus.UNKNOWN, 5)), + 6, 1)), + ImmutableList.of()))); + + DiscoveryResponse response = + buildDiscoveryResponseV2("0", clusterLoadAssignments, + XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0000"); + responseObserver.onNext(response); + + // Client sent an ACK EDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "0", "cluster-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0000"))); + + verify(endpointWatcher, never()).onEndpointChanged(any(EndpointUpdate.class)); + verify(endpointWatcher, never()).onResourceDoesNotExist("cluster-foo.googleapis.com"); + verify(endpointWatcher, never()).onError(any(Status.class)); + fakeClock.forwardTime(XdsClientImpl.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC, TimeUnit.SECONDS); + verify(endpointWatcher).onResourceDoesNotExist("cluster-foo.googleapis.com"); + assertThat(fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); + } + + /** + * Normal workflow of receiving an EDS response containing ClusterLoadAssignment message for + * a requested cluster. + */ + @Test + public void edsResponseWithMatchingResource() { + xdsClient.watchEndpointData("cluster-foo.googleapis.com", endpointWatcher); + StreamObserver responseObserver = responseObservers.poll(); + StreamObserver requestObserver = requestObservers.poll(); + + // Client sends an EDS request for the only cluster being watched to management server. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + ScheduledTask edsRespTimeoutTask = + Iterables.getOnlyElement( + fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)); + assertThat(edsRespTimeoutTask.isCancelled()).isFalse(); + + // Management server sends back an EDS response with ClusterLoadAssignment for the requested + // cluster. + List clusterLoadAssignments = ImmutableList.of( + Any.pack(buildClusterLoadAssignment("cluster-foo.googleapis.com", + ImmutableList.of( + buildLocalityLbEndpoints("region1", "zone1", "subzone1", + ImmutableList.of( + buildLbEndpoint("192.168.0.1", 8080, HealthStatus.HEALTHY, 2)), + 1, 0), + buildLocalityLbEndpoints("region3", "zone3", "subzone3", + ImmutableList.of(), + 2, 1), /* locality with 0 endpoint */ + buildLocalityLbEndpoints("region4", "zone4", "subzone4", + ImmutableList.of( + buildLbEndpoint("192.168.142.5", 80, HealthStatus.UNKNOWN, 5)), + 0, 2) /* locality with 0 weight */), + ImmutableList.of( + buildDropOverload("lb", 200), + buildDropOverload("throttle", 1000)))), + Any.pack(buildClusterLoadAssignment("cluster-baz.googleapis.com", + ImmutableList.of( + buildLocalityLbEndpoints("region2", "zone2", "subzone2", + ImmutableList.of( + buildLbEndpoint("192.168.234.52", 8888, HealthStatus.UNKNOWN, 5)), + 6, 1)), + ImmutableList.of()))); + + DiscoveryResponse response = + buildDiscoveryResponseV2("0", clusterLoadAssignments, + XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0000"); + responseObserver.onNext(response); + + assertThat(edsRespTimeoutTask.isCancelled()).isTrue(); + + // Client sent an ACK EDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "0", "cluster-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0000"))); + + ArgumentCaptor endpointUpdateCaptor = ArgumentCaptor.forClass(null); + verify(endpointWatcher).onEndpointChanged(endpointUpdateCaptor.capture()); + EndpointUpdate endpointUpdate = endpointUpdateCaptor.getValue(); + assertThat(endpointUpdate.getClusterName()).isEqualTo("cluster-foo.googleapis.com"); + assertThat(endpointUpdate.getDropPolicies()) + .containsExactly( + new DropOverload("lb", 200), + new DropOverload("throttle", 1000)); + assertThat(endpointUpdate.getLocalityLbEndpointsMap()) + .containsExactly( + new Locality("region1", "zone1", "subzone1"), + new LocalityLbEndpoints( + ImmutableList.of( + new LbEndpoint("192.168.0.1", 8080, + 2, true)), 1, 0), + new Locality("region3", "zone3", "subzone3"), + new LocalityLbEndpoints(ImmutableList.of(), 2, 1)); + + clusterLoadAssignments = ImmutableList.of( + Any.pack(buildClusterLoadAssignment("cluster-foo.googleapis.com", + // 0 locality + ImmutableList.of(), + ImmutableList.of()))); + response = + buildDiscoveryResponseV2( + "1", clusterLoadAssignments, XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0001"); + responseObserver.onNext(response); + + // Client sent an ACK EDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "1", "cluster-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0001"))); + + verify(endpointWatcher, times(2)).onEndpointChanged(endpointUpdateCaptor.capture()); + endpointUpdate = endpointUpdateCaptor.getValue(); + assertThat(endpointUpdate.getClusterName()).isEqualTo("cluster-foo.googleapis.com"); + assertThat(endpointUpdate.getDropPolicies()).isEmpty(); + assertThat(endpointUpdate.getLocalityLbEndpointsMap()).isEmpty(); + } + + @Test + public void multipleEndpointWatchers() { + EndpointWatcher watcher1 = mock(EndpointWatcher.class); + EndpointWatcher watcher2 = mock(EndpointWatcher.class); + EndpointWatcher watcher3 = mock(EndpointWatcher.class); + xdsClient.watchEndpointData("cluster-foo.googleapis.com", watcher1); + xdsClient.watchEndpointData("cluster-foo.googleapis.com", watcher2); + xdsClient.watchEndpointData("cluster-bar.googleapis.com", watcher3); + + StreamObserver responseObserver = responseObservers.poll(); + StreamObserver requestObserver = requestObservers.poll(); + + // Client sends an EDS request containing all clusters being watched to management server. + verify(requestObserver) + .onNext( + argThat( + new DiscoveryRequestMatcher("", + ImmutableList.of("cluster-foo.googleapis.com", "cluster-bar.googleapis.com"), + XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + + assertThat(fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(2); + + // Management server sends back an EDS response contains ClusterLoadAssignment for only one of + // requested cluster. + List clusterLoadAssignments = ImmutableList.of( + Any.pack(buildClusterLoadAssignment("cluster-foo.googleapis.com", + ImmutableList.of( + buildLocalityLbEndpoints("region1", "zone1", "subzone1", + ImmutableList.of( + buildLbEndpoint("192.168.0.1", 8080, HealthStatus.HEALTHY, 2)), + 1, 0)), + ImmutableList.of()))); + + DiscoveryResponse response = + buildDiscoveryResponseV2("0", clusterLoadAssignments, + XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0000"); + responseObserver.onNext(response); + + assertThat(fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); + + // Client sent an ACK EDS request. + verify(requestObserver) + .onNext( + argThat( + new DiscoveryRequestMatcher("0", + ImmutableList.of("cluster-foo.googleapis.com", "cluster-bar.googleapis.com"), + XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0000"))); + + // Two watchers get notification of endpoint update for the cluster they are interested in. + ArgumentCaptor endpointUpdateCaptor1 = ArgumentCaptor.forClass(null); + verify(watcher1).onEndpointChanged(endpointUpdateCaptor1.capture()); + EndpointUpdate endpointUpdate1 = endpointUpdateCaptor1.getValue(); + assertThat(endpointUpdate1.getClusterName()).isEqualTo("cluster-foo.googleapis.com"); + assertThat(endpointUpdate1.getLocalityLbEndpointsMap()) + .containsExactly( + new Locality("region1", "zone1", "subzone1"), + new LocalityLbEndpoints( + ImmutableList.of( + new LbEndpoint("192.168.0.1", 8080, + 2, true)), 1, 0)); + + ArgumentCaptor endpointUpdateCaptor2 = ArgumentCaptor.forClass(null); + verify(watcher1).onEndpointChanged(endpointUpdateCaptor2.capture()); + EndpointUpdate endpointUpdate2 = endpointUpdateCaptor2.getValue(); + assertThat(endpointUpdate2.getClusterName()).isEqualTo("cluster-foo.googleapis.com"); + assertThat(endpointUpdate2.getLocalityLbEndpointsMap()) + .containsExactly( + new Locality("region1", "zone1", "subzone1"), + new LocalityLbEndpoints( + ImmutableList.of( + new LbEndpoint("192.168.0.1", 8080, + 2, true)), 1, 0)); + + verifyZeroInteractions(watcher3); + + // Management server sends back another EDS response contains ClusterLoadAssignment for the + // other requested cluster. + clusterLoadAssignments = ImmutableList.of( + Any.pack(buildClusterLoadAssignment("cluster-bar.googleapis.com", + ImmutableList.of( + buildLocalityLbEndpoints("region2", "zone2", "subzone2", + ImmutableList.of( + buildLbEndpoint("192.168.234.52", 8888, HealthStatus.UNKNOWN, 5)), + 6, 0)), + ImmutableList.of()))); + + response = buildDiscoveryResponseV2("1", clusterLoadAssignments, + XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0001"); + responseObserver.onNext(response); + + // Client sent an ACK EDS request. + verify(requestObserver) + .onNext( + argThat( + new DiscoveryRequestMatcher("1", + ImmutableList.of("cluster-foo.googleapis.com", "cluster-bar.googleapis.com"), + XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0001"))); + + // The corresponding watcher gets notified. + ArgumentCaptor endpointUpdateCaptor3 = ArgumentCaptor.forClass(null); + verify(watcher3).onEndpointChanged(endpointUpdateCaptor3.capture()); + EndpointUpdate endpointUpdate3 = endpointUpdateCaptor3.getValue(); + assertThat(endpointUpdate3.getClusterName()).isEqualTo("cluster-bar.googleapis.com"); + assertThat(endpointUpdate3.getLocalityLbEndpointsMap()) + .containsExactly( + new Locality("region2", "zone2", "subzone2"), + new LocalityLbEndpoints( + ImmutableList.of( + new LbEndpoint("192.168.234.52", 8888, + 5, true)), 6, 0)); + } + + /** + * (EDS response caching behavior) An endpoint watcher is registered for a cluster that already + * has some other endpoint watchers watching on. Endpoint information received previously is + * in local cache and notified to the new watcher immediately. + */ + @Test + public void watchEndpointsForClusterAlreadyBeingWatched() { + EndpointWatcher watcher1 = mock(EndpointWatcher.class); + xdsClient.watchEndpointData("cluster-foo.googleapis.com", watcher1); + StreamObserver responseObserver = responseObservers.poll(); + StreamObserver requestObserver = requestObservers.poll(); + + // Client sends first EDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + + assertThat(fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); + + // Management server sends back an EDS response containing ClusterLoadAssignments for + // some cluster not requested. + List clusterLoadAssignments = ImmutableList.of( + Any.pack(buildClusterLoadAssignment("cluster-foo.googleapis.com", + ImmutableList.of( + buildLocalityLbEndpoints("region1", "zone1", "subzone1", + ImmutableList.of( + buildLbEndpoint("192.168.0.1", 8080, HealthStatus.HEALTHY, 2)), + 1, 0)), + ImmutableList.of()))); + + DiscoveryResponse response = + buildDiscoveryResponseV2("0", clusterLoadAssignments, + XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0000"); + responseObserver.onNext(response); + + assertThat(fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); + + // Client sent an ACK EDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "0", "cluster-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0000"))); + + ArgumentCaptor endpointUpdateCaptor1 = ArgumentCaptor.forClass(null); + verify(watcher1).onEndpointChanged(endpointUpdateCaptor1.capture()); + EndpointUpdate endpointUpdate1 = endpointUpdateCaptor1.getValue(); + assertThat(endpointUpdate1.getClusterName()).isEqualTo("cluster-foo.googleapis.com"); + assertThat(endpointUpdate1.getDropPolicies()).isEmpty(); + assertThat(endpointUpdate1.getLocalityLbEndpointsMap()) + .containsExactly( + new Locality("region1", "zone1", "subzone1"), + new LocalityLbEndpoints( + ImmutableList.of( + new LbEndpoint("192.168.0.1", 8080, + 2, true)), 1, 0)); + + // A second endpoint watcher is registered for endpoints in the same cluster. + EndpointWatcher watcher2 = mock(EndpointWatcher.class); + xdsClient.watchEndpointData("cluster-foo.googleapis.com", watcher2); + + // Cached endpoint information is notified to the new watcher immediately, without sending + // another EDS request. + ArgumentCaptor endpointUpdateCaptor2 = ArgumentCaptor.forClass(null); + verify(watcher2).onEndpointChanged(endpointUpdateCaptor2.capture()); + EndpointUpdate endpointUpdate2 = endpointUpdateCaptor2.getValue(); + assertThat(endpointUpdate2.getClusterName()).isEqualTo("cluster-foo.googleapis.com"); + assertThat(endpointUpdate2.getDropPolicies()).isEmpty(); + assertThat(endpointUpdate2.getLocalityLbEndpointsMap()) + .containsExactly( + new Locality("region1", "zone1", "subzone1"), + new LocalityLbEndpoints( + ImmutableList.of( + new LbEndpoint("192.168.0.1", 8080, + 2, true)), 1, 0)); + + verifyNoMoreInteractions(requestObserver); + assertThat(fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); + } + + /** + * Basic operations of adding/canceling endpoint data watchers. + */ + @Test + public void addRemoveEndpointWatchers() { + EndpointWatcher watcher1 = mock(EndpointWatcher.class); + xdsClient.watchEndpointData("cluster-foo.googleapis.com", watcher1); + + // Streaming RPC starts after a first watcher is added. + StreamObserver responseObserver = responseObservers.poll(); + StreamObserver requestObserver = requestObservers.poll(); + + // Client sends an EDS request to management server. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + + // Management server sends back an EDS response with ClusterLoadAssignment for the requested + // cluster. + List clusterLoadAssignments = ImmutableList.of( + Any.pack(buildClusterLoadAssignment("cluster-foo.googleapis.com", + ImmutableList.of( + buildLocalityLbEndpoints("region1", "zone1", "subzone1", + ImmutableList.of( + buildLbEndpoint("192.168.0.1", 8080, HealthStatus.HEALTHY, 2), + buildLbEndpoint("192.132.53.5", 80, HealthStatus.UNHEALTHY, 5)), + 1, 0)), + ImmutableList.of()))); + + DiscoveryResponse response = + buildDiscoveryResponseV2("0", clusterLoadAssignments, + XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0000"); + responseObserver.onNext(response); + + // Client sent an ACK EDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "0", "cluster-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0000"))); + + ArgumentCaptor endpointUpdateCaptor1 = ArgumentCaptor.forClass(null); + verify(watcher1).onEndpointChanged(endpointUpdateCaptor1.capture()); + EndpointUpdate endpointUpdate1 = endpointUpdateCaptor1.getValue(); + assertThat(endpointUpdate1.getClusterName()).isEqualTo("cluster-foo.googleapis.com"); + assertThat(endpointUpdate1.getLocalityLbEndpointsMap()) + .containsExactly( + new Locality("region1", "zone1", "subzone1"), + new LocalityLbEndpoints( + ImmutableList.of( + new LbEndpoint("192.168.0.1", 8080, 2, true), + new LbEndpoint("192.132.53.5", 80,5, false)), + 1, 0)); + + // Add another endpoint watcher for a different cluster. + EndpointWatcher watcher2 = mock(EndpointWatcher.class); + xdsClient.watchEndpointData("cluster-bar.googleapis.com", watcher2); + + // Client sent a new EDS request for all interested resources. + verify(requestObserver) + .onNext( + argThat( + new DiscoveryRequestMatcher("0", + ImmutableList.of("cluster-foo.googleapis.com", "cluster-bar.googleapis.com"), + XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0000"))); + + // Management server sends back an EDS response with ClusterLoadAssignment for one of requested + // cluster. + clusterLoadAssignments = ImmutableList.of( + Any.pack(buildClusterLoadAssignment("cluster-bar.googleapis.com", + ImmutableList.of( + buildLocalityLbEndpoints("region2", "zone2", "subzone2", + ImmutableList.of( + buildLbEndpoint("192.168.312.6", 443, HealthStatus.HEALTHY, 1)), + 6, 0)), + ImmutableList.of()))); + + response = buildDiscoveryResponseV2("1", clusterLoadAssignments, + XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0001"); + responseObserver.onNext(response); + + // Client sent an ACK EDS request for all interested resources. + verify(requestObserver) + .onNext( + argThat( + new DiscoveryRequestMatcher("1", + ImmutableList.of("cluster-foo.googleapis.com", "cluster-bar.googleapis.com"), + XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0001"))); + + ArgumentCaptor endpointUpdateCaptor2 = ArgumentCaptor.forClass(null); + verify(watcher2).onEndpointChanged(endpointUpdateCaptor2.capture()); + EndpointUpdate endpointUpdate2 = endpointUpdateCaptor2.getValue(); + assertThat(endpointUpdate2.getClusterName()).isEqualTo("cluster-bar.googleapis.com"); + assertThat(endpointUpdate2.getLocalityLbEndpointsMap()) + .containsExactly( + new Locality("region2", "zone2", "subzone2"), + new LocalityLbEndpoints( + ImmutableList.of( + new LbEndpoint("192.168.312.6", 443, 1, true)), + 6, 0)); + + // Cancel one of the watcher. + xdsClient.cancelEndpointDataWatch("cluster-foo.googleapis.com", watcher1); + + // Since the cancelled watcher was the last watcher interested in that cluster, client + // sent an new EDS request to unsubscribe from that cluster. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "1", "cluster-bar.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0001"))); + + // Management server should not respond as it had previously sent the requested resource. + + // Cancel the other watcher. + xdsClient.cancelEndpointDataWatch("cluster-bar.googleapis.com", watcher2); + + // Since the cancelled watcher was the last watcher interested in that cluster, client + // sent an new EDS request to unsubscribe from that cluster. + verify(requestObserver) + .onNext( + argThat( + new DiscoveryRequestMatcher("1", + ImmutableList.of(), // empty resources + XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0001"))); + + // All endpoint watchers have been cancelled. + + // Management server sends back an EDS response for updating previously sent resources. + clusterLoadAssignments = ImmutableList.of( + Any.pack(buildClusterLoadAssignment("cluster-foo.googleapis.com", + ImmutableList.of( + buildLocalityLbEndpoints("region3", "zone3", "subzone3", + ImmutableList.of( + buildLbEndpoint("192.168.432.6", 80, HealthStatus.HEALTHY, 2)), + 3, 0)), + ImmutableList.of())), + Any.pack(buildClusterLoadAssignment("cluster-bar.googleapis.com", + ImmutableList.of( + buildLocalityLbEndpoints("region4", "zone4", "subzone4", + ImmutableList.of( + buildLbEndpoint("192.168.75.6", 8888, HealthStatus.HEALTHY, 2)), + 3, 0)), + ImmutableList.of()))); + + response = buildDiscoveryResponseV2("2", clusterLoadAssignments, + XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0002"); + responseObserver.onNext(response); + + // Client sent an ACK EDS request. + verify(requestObserver) + .onNext( + argThat( + new DiscoveryRequestMatcher("2", + ImmutableList.of(), // empty resources + XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0002"))); + + // Cancelled watchers do not receive notification. + verifyNoMoreInteractions(watcher1, watcher2); + + // A new endpoint watcher is added to watch an old but was no longer interested in cluster. + EndpointWatcher watcher3 = mock(EndpointWatcher.class); + xdsClient.watchEndpointData("cluster-bar.googleapis.com", watcher3); + + // Nothing should be notified to the new watcher as we are still waiting management server's + // latest response. + // Cached endpoint data should have been purged. + verify(watcher3, never()).onEndpointChanged(any(EndpointUpdate.class)); + + // An EDS request is sent to re-subscribe the cluster again. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "2", "cluster-bar.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0002"))); + + // Management server sends back an EDS response for re-subscribed resource. + clusterLoadAssignments = ImmutableList.of( + Any.pack(buildClusterLoadAssignment("cluster-bar.googleapis.com", + ImmutableList.of( + buildLocalityLbEndpoints("region4", "zone4", "subzone4", + ImmutableList.of( + buildLbEndpoint("192.168.75.6", 8888, HealthStatus.HEALTHY, 2)), + 3, 0)), + ImmutableList.of()))); + + response = buildDiscoveryResponseV2("3", clusterLoadAssignments, + XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0003"); + responseObserver.onNext(response); + + ArgumentCaptor endpointUpdateCaptor3 = ArgumentCaptor.forClass(null); + verify(watcher3).onEndpointChanged(endpointUpdateCaptor3.capture()); + EndpointUpdate endpointUpdate3 = endpointUpdateCaptor3.getValue(); + assertThat(endpointUpdate3.getClusterName()).isEqualTo("cluster-bar.googleapis.com"); + assertThat(endpointUpdate3.getLocalityLbEndpointsMap()) + .containsExactly( + new Locality("region4", "zone4", "subzone4"), + new LocalityLbEndpoints( + ImmutableList.of( + new LbEndpoint("192.168.75.6", 8888, 2, true)), + 3, 0)); + + // Client sent an ACK EDS request. + verify(requestObserver) + .onNext( + argThat( + new DiscoveryRequestMatcher("3", + ImmutableList.of("cluster-bar.googleapis.com"), + XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0003"))); + } + + @Test + public void addRemoveEndpointWatcherWhileInitialResourceFetchInProgress() { + EndpointWatcher watcher1 = mock(EndpointWatcher.class); + xdsClient.watchEndpointData("cluster-foo.googleapis.com", watcher1); + + // Streaming RPC starts after a first watcher is added. + StreamObserver requestObserver = requestObservers.poll(); + + // Client sends an EDS request to management server. + verify(requestObserver) + .onNext( + argThat( + new DiscoveryRequestMatcher("", "cluster-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + assertThat(fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); + + fakeClock.forwardTime(XdsClientImpl.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC - 1, TimeUnit.SECONDS); + + EndpointWatcher watcher2 = mock(EndpointWatcher.class); + EndpointWatcher watcher3 = mock(EndpointWatcher.class); + EndpointWatcher watcher4 = mock(EndpointWatcher.class); + xdsClient.watchEndpointData("cluster-foo.googleapis.com", watcher2); + xdsClient.watchEndpointData("cluster-bar.googleapis.com", watcher3); + xdsClient.watchEndpointData("cluster-bar.googleapis.com", watcher4); + + // Client sends a new EDS request for updating the latest resource subscription. + verify(requestObserver) + .onNext( + argThat( + new DiscoveryRequestMatcher("", + ImmutableList.of("cluster-foo.googleapis.com", "cluster-bar.googleapis.com"), + XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + assertThat(fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(2); + + fakeClock.forwardTime(1, TimeUnit.SECONDS); + assertThat(fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); + + // EDS resource "cluster-foo.googleapis.com" is known to be absent. + verify(watcher1).onResourceDoesNotExist("cluster-foo.googleapis.com"); + verify(watcher2).onResourceDoesNotExist("cluster-foo.googleapis.com"); + + // The absence result is known immediately. + EndpointWatcher watcher5 = mock(EndpointWatcher.class); + xdsClient.watchEndpointData("cluster-foo.googleapis.com", watcher5); + verify(watcher5).onResourceDoesNotExist("cluster-foo.googleapis.com"); + + assertThat(fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); + ScheduledTask timeoutTask = Iterables.getOnlyElement(fakeClock.getPendingTasks()); + + // Cancel watchers while discovery for resource "cluster-bar.googleapis.com" is still + // in progress. + xdsClient.cancelEndpointDataWatch("cluster-bar.googleapis.com", watcher3); + assertThat(timeoutTask.isCancelled()).isFalse(); + xdsClient.cancelEndpointDataWatch("cluster-bar.googleapis.com", watcher4); + + // Client sends an EDS request for resource subscription update (Omitted). + + fakeClock.forwardTime(XdsClientImpl.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC, TimeUnit.SECONDS); + + assertThat(fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); + assertThat(timeoutTask.isCancelled()).isTrue(); + + verifyZeroInteractions(watcher3, watcher4); + } + + @Test + public void cdsUpdateForEdsServiceNameChange() { + xdsClient.watchClusterData("cluster-foo.googleapis.com", clusterWatcher); + StreamObserver responseObserver = responseObservers.poll(); + + // Management server sends back a CDS response containing requested resource. + List clusters = ImmutableList.of( + Any.pack(buildCluster("cluster-foo.googleapis.com", "cluster-foo:service-bar", false))); + DiscoveryResponse response = + buildDiscoveryResponseV2("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); + responseObserver.onNext(response); + + xdsClient.watchEndpointData("cluster-foo:service-bar", endpointWatcher); + + // Management server sends back an EDS response for resource "cluster-foo:service-bar". + List clusterLoadAssignments = ImmutableList.of( + Any.pack(buildClusterLoadAssignment("cluster-foo:service-bar", + ImmutableList.of( + buildLocalityLbEndpoints("region1", "zone1", "subzone1", + ImmutableList.of( + buildLbEndpoint("192.168.0.1", 8080, HealthStatus.HEALTHY, 2)), + 1, 0)), + ImmutableList.of()))); + response = + buildDiscoveryResponseV2("0", clusterLoadAssignments, + XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0000"); + responseObserver.onNext(response); + + ArgumentCaptor endpointUpdateCaptor = ArgumentCaptor.forClass(null); + verify(endpointWatcher).onEndpointChanged(endpointUpdateCaptor.capture()); + EndpointUpdate endpointUpdate = endpointUpdateCaptor.getValue(); + assertThat(endpointUpdate.getClusterName()).isEqualTo("cluster-foo:service-bar"); + assertThat(endpointUpdate.getDropPolicies()).isEmpty(); + assertThat(endpointUpdate.getLocalityLbEndpointsMap()) + .containsExactly( + new Locality("region1", "zone1", "subzone1"), + new LocalityLbEndpoints( + ImmutableList.of( + new LbEndpoint("192.168.0.1", 8080, + 2, true)), 1, 0)); + + // Management server sends another CDS response for removing cluster service + // "cluster-foo:service-blade" with replacement of "cluster-foo:service-blade". + clusters = ImmutableList.of( + Any.pack(buildCluster("cluster-foo.googleapis.com", "cluster-foo:service-blade", false))); + response = + buildDiscoveryResponseV2("1", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0001"); + responseObserver.onNext(response); + + // Watcher get notification for endpoint resource "cluster-foo:service-bar" being deleted. + verify(endpointWatcher).onResourceDoesNotExist("cluster-foo:service-bar"); + } + + /** + * RPC stream closed and retry during the period of first time resolving service config + * (LDS/RDS only). + */ + @Test + public void streamClosedAndRetryWhenResolvingConfig() { + InOrder inOrder = + Mockito.inOrder(mockedDiscoveryService, backoffPolicyProvider, backoffPolicy1, + backoffPolicy2); + xdsClient.watchConfigData(TARGET_AUTHORITY, configWatcher); + + ArgumentCaptor> responseObserverCaptor = + ArgumentCaptor.forClass(null); + inOrder.verify(mockedDiscoveryService) + .streamAggregatedResources(responseObserverCaptor.capture()); + StreamObserver responseObserver = + responseObserverCaptor.getValue(); // same as responseObservers.poll() + StreamObserver requestObserver = requestObservers.poll(); + + // Client sends an LDS request for the host name (with port) to management server. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + + // Management server closes the RPC stream immediately. + responseObserver.onCompleted(); + inOrder.verify(backoffPolicyProvider).get(); + inOrder.verify(backoffPolicy1).nextBackoffNanos(); + assertThat(fakeClock.getPendingTasks(RPC_RETRY_TASK_FILTER)).hasSize(1); + + // Retry after backoff. + fakeClock.forwardNanos(9L); + assertThat(requestObservers).isEmpty(); + fakeClock.forwardNanos(1L); + inOrder.verify(mockedDiscoveryService) + .streamAggregatedResources(responseObserverCaptor.capture()); + responseObserver = responseObserverCaptor.getValue(); + requestObserver = requestObservers.poll(); + + // Client retried by sending an LDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + + // Management server closes the RPC stream with an error. + responseObserver.onError(Status.UNAVAILABLE.asException()); + verifyNoMoreInteractions(backoffPolicyProvider); + inOrder.verify(backoffPolicy1).nextBackoffNanos(); + assertThat(fakeClock.getPendingTasks(RPC_RETRY_TASK_FILTER)).hasSize(1); + + // Retry after backoff. + fakeClock.forwardNanos(99L); + assertThat(requestObservers).isEmpty(); + fakeClock.forwardNanos(1L); + inOrder.verify(mockedDiscoveryService) + .streamAggregatedResources(responseObserverCaptor.capture()); + responseObserver = responseObserverCaptor.getValue(); + requestObserver = requestObservers.poll(); + + // Client retried again by sending an LDS. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + + // Management server responses with a listener for the requested resource. + Rds rdsConfig = + Rds.newBuilder() + .setConfigSource( + ConfigSource.newBuilder().setAds(AggregatedConfigSource.getDefaultInstance())) + .setRouteConfigName("route-foo.googleapis.com") + .build(); + + List listeners = ImmutableList.of( + Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) + ); + DiscoveryResponse ldsResponse = + buildDiscoveryResponseV2("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + responseObserver.onNext(ldsResponse); + + // Client sent back an ACK LDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "0", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"))); + + // Client sent an RDS request based on the received listener. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "route-foo.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_RDS_V2, ""))); + + // Management server encounters an error and closes the stream. + responseObserver.onError(Status.UNKNOWN.asException()); + + // Reset backoff and retry immediately. + inOrder.verify(backoffPolicyProvider).get(); + fakeClock.runDueTasks(); + inOrder.verify(mockedDiscoveryService) + .streamAggregatedResources(responseObserverCaptor.capture()); + responseObserver = responseObserverCaptor.getValue(); + requestObserver = requestObservers.poll(); + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + + // RPC stream closed immediately + responseObserver.onError(Status.UNKNOWN.asException()); + inOrder.verify(backoffPolicy2).nextBackoffNanos(); + assertThat(fakeClock.getPendingTasks(RPC_RETRY_TASK_FILTER)).hasSize(1); + + // Retry after backoff. + fakeClock.forwardNanos(19L); + assertThat(requestObservers).isEmpty(); + fakeClock.forwardNanos(1L); + inOrder.verify(mockedDiscoveryService) + .streamAggregatedResources(responseObserverCaptor.capture()); + responseObserver = responseObserverCaptor.getValue(); + requestObserver = requestObservers.poll(); + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + + // Management server sends an LDS response. + responseObserver.onNext(ldsResponse); + + // Client sends an ACK LDS request and an RDS request for "route-foo.googleapis.com". (Omitted) + + List routeConfigs = ImmutableList.of( + Any.pack( + buildRouteConfiguration( + "route-foo.googleapis.com", // target route configuration + ImmutableList.of( + buildVirtualHost( + ImmutableList.of(TARGET_AUTHORITY), // matching virtual host + "cluster.googleapis.com"))))); + DiscoveryResponse rdsResponse = + buildDiscoveryResponseV2("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); + // Management server sends an RDS response. + responseObserver.onNext(rdsResponse); + + // Client has resolved the cluster based on the RDS response. + ArgumentCaptor configUpdateCaptor = ArgumentCaptor.forClass(null); + verify(configWatcher).onConfigChanged(configUpdateCaptor.capture()); + assertConfigUpdateContainsSingleClusterRoute( + configUpdateCaptor.getValue(), "cluster.googleapis.com"); + + // RPC stream closed with an error again. + responseObserver.onError(Status.UNKNOWN.asException()); + + // Reset backoff and retry immediately. + inOrder.verify(backoffPolicyProvider).get(); + fakeClock.runDueTasks(); + requestObserver = requestObservers.poll(); + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + + verifyNoMoreInteractions(backoffPolicyProvider, backoffPolicy1, backoffPolicy2); + } + + /** + * RPC stream close and retry while there are config/cluster/endpoint watchers registered. + */ + @Test + public void streamClosedAndRetry() { + InOrder inOrder = + Mockito.inOrder(mockedDiscoveryService, backoffPolicyProvider, backoffPolicy1, + backoffPolicy2); + xdsClient.watchConfigData(TARGET_AUTHORITY, configWatcher); + + ArgumentCaptor> responseObserverCaptor = + ArgumentCaptor.forClass(null); + inOrder.verify(mockedDiscoveryService) + .streamAggregatedResources(responseObserverCaptor.capture()); + StreamObserver responseObserver = + responseObserverCaptor.getValue(); // same as responseObservers.poll() + StreamObserver requestObserver = requestObservers.poll(); + + waitUntilConfigResolved(responseObserver); + ArgumentCaptor statusCaptor = ArgumentCaptor.forClass(null); + + // Start watching cluster information. + xdsClient.watchClusterData("cluster.googleapis.com", clusterWatcher); + + // Client sent first CDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + + // Start watching endpoint information. + xdsClient.watchEndpointData("cluster.googleapis.com", endpointWatcher); + + // Client sent first EDS request. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + + // Management server closes the RPC stream with an error. + responseObserver.onError(Status.UNKNOWN.asException()); + verify(configWatcher).onError(statusCaptor.capture()); + assertThat(statusCaptor.getValue().getCode()).isEqualTo(Code.UNKNOWN); + verify(clusterWatcher).onError(statusCaptor.capture()); + assertThat(statusCaptor.getValue().getCode()).isEqualTo(Code.UNKNOWN); + verify(endpointWatcher).onError(statusCaptor.capture()); + assertThat(statusCaptor.getValue().getCode()).isEqualTo(Code.UNKNOWN); + + // Resets backoff and retry immediately. + inOrder.verify(backoffPolicyProvider).get(); + fakeClock.runDueTasks(); + inOrder.verify(mockedDiscoveryService) + .streamAggregatedResources(responseObserverCaptor.capture()); + responseObserver = responseObserverCaptor.getValue(); + requestObserver = requestObservers.poll(); + + // Retry resumes requests for all wanted resources. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + + // Management server becomes unreachable. + responseObserver.onError(Status.UNAVAILABLE.asException()); + verify(configWatcher, times(2)).onError(statusCaptor.capture()); + assertThat(statusCaptor.getValue().getCode()).isEqualTo(Code.UNAVAILABLE); + verify(clusterWatcher, times(2)).onError(statusCaptor.capture()); + assertThat(statusCaptor.getValue().getCode()).isEqualTo(Code.UNAVAILABLE); + verify(endpointWatcher, times(2)).onError(statusCaptor.capture()); + assertThat(statusCaptor.getValue().getCode()).isEqualTo(Code.UNAVAILABLE); + inOrder.verify(backoffPolicy1).nextBackoffNanos(); + assertThat(fakeClock.getPendingTasks(RPC_RETRY_TASK_FILTER)).hasSize(1); + + // Retry after backoff. + fakeClock.forwardNanos(9L); + assertThat(requestObservers).isEmpty(); + fakeClock.forwardNanos(1L); + inOrder.verify(mockedDiscoveryService) + .streamAggregatedResources(responseObserverCaptor.capture()); + responseObserver = responseObserverCaptor.getValue(); + requestObserver = requestObservers.poll(); + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + + // Management server is still not reachable. + responseObserver.onError(Status.UNAVAILABLE.asException()); + verify(configWatcher, times(3)).onError(statusCaptor.capture()); + assertThat(statusCaptor.getValue().getCode()).isEqualTo(Code.UNAVAILABLE); + verify(clusterWatcher, times(3)).onError(statusCaptor.capture()); + assertThat(statusCaptor.getValue().getCode()).isEqualTo(Code.UNAVAILABLE); + verify(endpointWatcher, times(3)).onError(statusCaptor.capture()); + assertThat(statusCaptor.getValue().getCode()).isEqualTo(Code.UNAVAILABLE); + inOrder.verify(backoffPolicy1).nextBackoffNanos(); + assertThat(fakeClock.getPendingTasks(RPC_RETRY_TASK_FILTER)).hasSize(1); + + // Retry after backoff. + fakeClock.forwardNanos(99L); + assertThat(requestObservers).isEmpty(); + fakeClock.forwardNanos(1L); + inOrder.verify(mockedDiscoveryService) + .streamAggregatedResources(responseObserverCaptor.capture()); + responseObserver = responseObserverCaptor.getValue(); + requestObserver = requestObservers.poll(); + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + + // Management server sends back a CDS response. + List clusters = ImmutableList.of( + Any.pack(buildCluster("cluster.googleapis.com", null, false))); + DiscoveryResponse cdsResponse = + buildDiscoveryResponseV2("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); + responseObserver.onNext(cdsResponse); + + // Client sent an CDS ACK request (Omitted). + + // Management server closes the RPC stream. + responseObserver.onCompleted(); + verify(configWatcher, times(4)).onError(any(Status.class)); + verify(clusterWatcher, times(4)).onError(any(Status.class)); + verify(endpointWatcher, times(4)).onError(any(Status.class)); + + // Resets backoff and retry immediately + inOrder.verify(backoffPolicyProvider).get(); + fakeClock.runDueTasks(); + inOrder.verify(mockedDiscoveryService) + .streamAggregatedResources(responseObserverCaptor.capture()); + responseObserver = responseObserverCaptor.getValue(); + requestObserver = requestObservers.poll(); + + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + + // Management server becomes unreachable again. + responseObserver.onError(Status.UNAVAILABLE.asException()); + verify(configWatcher, times(5)).onError(statusCaptor.capture()); + assertThat(statusCaptor.getValue().getCode()).isEqualTo(Code.UNAVAILABLE); + verify(clusterWatcher, times(5)).onError(statusCaptor.capture()); + assertThat(statusCaptor.getValue().getCode()).isEqualTo(Code.UNAVAILABLE); + verify(endpointWatcher, times(5)).onError(statusCaptor.capture()); + assertThat(statusCaptor.getValue().getCode()).isEqualTo(Code.UNAVAILABLE); + inOrder.verify(backoffPolicy2).nextBackoffNanos(); + assertThat(fakeClock.getPendingTasks(RPC_RETRY_TASK_FILTER)).hasSize(1); + + // Retry after backoff. + fakeClock.forwardNanos(19L); + assertThat(requestObservers).isEmpty(); + fakeClock.forwardNanos(1L); + inOrder.verify(mockedDiscoveryService) + .streamAggregatedResources(responseObserverCaptor.capture()); + requestObserver = requestObservers.poll(); + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + + verifyNoMoreInteractions(mockedDiscoveryService, backoffPolicyProvider, backoffPolicy1, + backoffPolicy2); + } + + /** + * RPC stream closed and retry while some cluster/endpoint watchers have changed (added/removed). + */ + @Test + public void streamClosedAndRetryRaceWithAddingAndRemovingWatchers() { + InOrder inOrder = + Mockito.inOrder(mockedDiscoveryService, backoffPolicyProvider, backoffPolicy1, + backoffPolicy2); + xdsClient.watchConfigData(TARGET_AUTHORITY, configWatcher); + + ArgumentCaptor> responseObserverCaptor = + ArgumentCaptor.forClass(null); + inOrder.verify(mockedDiscoveryService) + .streamAggregatedResources(responseObserverCaptor.capture()); + StreamObserver responseObserver = + responseObserverCaptor.getValue(); // same as responseObservers.poll() + requestObservers.poll(); + + waitUntilConfigResolved(responseObserver); + + // Management server closes RPC stream. + responseObserver.onCompleted(); + + // Resets backoff and retry immediately. + inOrder.verify(backoffPolicyProvider).get(); + fakeClock.runDueTasks(); + inOrder.verify(mockedDiscoveryService) + .streamAggregatedResources(responseObserverCaptor.capture()); + responseObserver = responseObserverCaptor.getValue(); + StreamObserver requestObserver = requestObservers.poll(); + + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + + // Management server becomes unreachable. + responseObserver.onError(Status.UNAVAILABLE.asException()); + inOrder.verify(backoffPolicy1).nextBackoffNanos(); + assertThat(fakeClock.getPendingTasks(RPC_RETRY_TASK_FILTER)).hasSize(1); + + // Start watching cluster information while RPC stream is still in retry backoff. + xdsClient.watchClusterData("cluster.googleapis.com", clusterWatcher); + + // Retry after backoff. + fakeClock.forwardNanos(9L); + assertThat(requestObservers).isEmpty(); + fakeClock.forwardNanos(1L); + inOrder.verify(mockedDiscoveryService) + .streamAggregatedResources(responseObserverCaptor.capture()); + responseObserver = responseObserverCaptor.getValue(); + requestObserver = requestObservers.poll(); + + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + + // Management server is still unreachable. + responseObserver.onError(Status.UNAVAILABLE.asException()); + inOrder.verify(backoffPolicy1).nextBackoffNanos(); + assertThat(fakeClock.getPendingTasks(RPC_RETRY_TASK_FILTER)).hasSize(1); + + // Start watching endpoint information while RPC stream is still in retry backoff. + xdsClient.watchEndpointData("cluster.googleapis.com", endpointWatcher); + + // Retry after backoff. + fakeClock.forwardNanos(99L); + assertThat(requestObservers).isEmpty(); + fakeClock.forwardNanos(1L); + inOrder.verify(mockedDiscoveryService) + .streamAggregatedResources(responseObserverCaptor.capture()); + responseObserver = responseObserverCaptor.getValue(); + requestObserver = requestObservers.poll(); + + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + + // Management server sends back a CDS response. + List clusters = ImmutableList.of( + Any.pack(buildCluster("cluster.googleapis.com", null, false))); + DiscoveryResponse cdsResponse = + buildDiscoveryResponseV2("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); + responseObserver.onNext(cdsResponse); + + // Client sent an CDS ACK request (Omitted). + + // No longer interested in endpoint information after RPC resumes. + xdsClient.cancelEndpointDataWatch("cluster.googleapis.com", endpointWatcher); + // Client updates EDS resource subscription immediately. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", ImmutableList.of(), + XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + + // Become interested in endpoints of another cluster. + xdsClient.watchEndpointData("cluster2.googleapis.com", endpointWatcher); + // Client updates EDS resource subscription immediately. + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster2.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + + // Management server closes the RPC stream again. + responseObserver.onCompleted(); + + // Resets backoff and retry immediately. + inOrder.verify(backoffPolicyProvider).get(); + fakeClock.runDueTasks(); + inOrder.verify(mockedDiscoveryService) + .streamAggregatedResources(responseObserverCaptor.capture()); + responseObserver = responseObserverCaptor.getValue(); + requestObserver = requestObservers.poll(); + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster2.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + + // Management server becomes unreachable again. + responseObserver.onError(Status.UNAVAILABLE.asException()); + inOrder.verify(backoffPolicy2).nextBackoffNanos(); + assertThat(fakeClock.getPendingTasks(RPC_RETRY_TASK_FILTER)).hasSize(1); + + // No longer interested in previous cluster and endpoints in that cluster. + xdsClient.cancelClusterDataWatch("cluster.googleapis.com", clusterWatcher); + xdsClient.cancelEndpointDataWatch("cluster2.googleapis.com", endpointWatcher); + + // Retry after backoff. + fakeClock.forwardNanos(19L); + assertThat(requestObservers).isEmpty(); + fakeClock.forwardNanos(1L); + inOrder.verify(mockedDiscoveryService) + .streamAggregatedResources(responseObserverCaptor.capture()); + requestObserver = requestObservers.poll(); + + verify(requestObserver) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + verify(requestObserver, never()) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_CDS_V2, ""))); + verify(requestObserver, never()) + .onNext(eq(buildDiscoveryRequestV2(NODE, "", "cluster2.googleapis.com", + XdsClientImpl.ADS_TYPE_URL_EDS_V2, ""))); + + verifyNoMoreInteractions(mockedDiscoveryService, backoffPolicyProvider, backoffPolicy1, + backoffPolicy2); + } + + @Test + public void streamClosedAndRetryReschedulesAllResourceFetchTimer() { + InOrder inOrder = + Mockito.inOrder(mockedDiscoveryService, backoffPolicyProvider, backoffPolicy1, + backoffPolicy2); + xdsClient.watchConfigData(TARGET_AUTHORITY, configWatcher); + + ArgumentCaptor> responseObserverCaptor = + ArgumentCaptor.forClass(null); + inOrder.verify(mockedDiscoveryService) + .streamAggregatedResources(responseObserverCaptor.capture()); + StreamObserver responseObserver = + responseObserverCaptor.getValue(); // same as responseObservers.poll() + + // Management server sends back an LDS response telling client to do RDS. + Rds rdsConfig = + Rds.newBuilder() + // Must set to use ADS. + .setConfigSource( + ConfigSource.newBuilder().setAds(AggregatedConfigSource.getDefaultInstance())) + .setRouteConfigName("route-foo.googleapis.com") + .build(); + + List listeners = ImmutableList.of( + Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) + ); + DiscoveryResponse response = + buildDiscoveryResponseV2("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + responseObserver.onNext(response); + + // Client sent an RDS request for resource "route-foo.googleapis.com" (Omitted). + + ScheduledTask rdsRespTimer = + Iterables.getOnlyElement( + fakeClock.getPendingTasks(RDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)); + assertThat(rdsRespTimer.isCancelled()).isFalse(); + fakeClock.forwardTime(XdsClientImpl.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC - 1, TimeUnit.SECONDS); + + // RPC stream is broken while the initial fetch for the resource is not complete. + responseObserver.onError(Status.UNAVAILABLE.asException()); + assertThat(rdsRespTimer.isCancelled()).isTrue(); + + // Reset backoff and retry immediately. + inOrder.verify(backoffPolicyProvider).get(); + fakeClock.runDueTasks(); + inOrder.verify(mockedDiscoveryService) + .streamAggregatedResources(responseObserverCaptor.capture()); + responseObserver = responseObserverCaptor.getValue(); + StreamObserver requestObserver = requestObservers.poll(); + + ScheduledTask ldsRespTimer = + Iterables.getOnlyElement( + fakeClock.getPendingTasks(LDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)); + assertThat(ldsRespTimer.getDelay(TimeUnit.SECONDS)) + .isEqualTo(XdsClientImpl.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC); + + // Client resumed requests and management server sends back LDS resources again. + verify(requestObserver).onNext( + eq(buildDiscoveryRequestV2(NODE, "", TARGET_AUTHORITY, + XdsClientImpl.ADS_TYPE_URL_LDS_V2, ""))); + responseObserver.onNext(response); + + // Client sent an RDS request for resource "route-foo.googleapis.com" (Omitted). + + assertThat(ldsRespTimer.isCancelled()).isTrue(); + rdsRespTimer = + Iterables.getOnlyElement( + fakeClock.getPendingTasks(RDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)); + assertThat(rdsRespTimer.getDelay(TimeUnit.SECONDS)) + .isEqualTo(XdsClientImpl.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC); + + // Management server sends back an RDS response containing the RouteConfiguration + // for the requested resource. + List routeConfigs = ImmutableList.of( + Any.pack( + buildRouteConfiguration( + "route-foo.googleapis.com", // target route configuration + ImmutableList.of( + buildVirtualHost( + ImmutableList.of(TARGET_AUTHORITY), // matching virtual host + "cluster-foo.googleapis.com"))))); + response = buildDiscoveryResponseV2( + "0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); + responseObserver.onNext(response); + + assertThat(rdsRespTimer.isCancelled()).isTrue(); + + // Resets RPC stream again. + responseObserver.onError(Status.UNAVAILABLE.asException()); + // Reset backoff and retry immediately. + inOrder.verify(backoffPolicyProvider).get(); + fakeClock.runDueTasks(); + inOrder.verify(mockedDiscoveryService) + .streamAggregatedResources(responseObserverCaptor.capture()); + responseObserver = responseObserverCaptor.getValue(); + + // Client/server resumed LDS/RDS request/response (Omitted). + + // Start watching cluster data. + xdsClient.watchClusterData("cluster-foo.googleapis.com", clusterWatcher); + ScheduledTask cdsRespTimeoutTask = + Iterables.getOnlyElement( + fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)); + assertThat(cdsRespTimeoutTask.isCancelled()).isFalse(); + fakeClock.forwardTime(XdsClientImpl.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC - 1, TimeUnit.SECONDS); + + // RPC stream is broken while the initial fetch for the resource is not complete. + responseObserver.onError(Status.UNAVAILABLE.asException()); + assertThat(cdsRespTimeoutTask.isCancelled()).isTrue(); + inOrder.verify(backoffPolicy2).nextBackoffNanos(); + assertThat(fakeClock.getPendingTasks(RPC_RETRY_TASK_FILTER)).hasSize(1); + + // Retry after backoff. + fakeClock.forwardNanos(20L); + inOrder.verify(mockedDiscoveryService) + .streamAggregatedResources(responseObserverCaptor.capture()); + responseObserver = responseObserverCaptor.getValue(); + + // Timer is rescheduled as the client restarts the resource fetch. + cdsRespTimeoutTask = + Iterables.getOnlyElement( + fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)); + assertThat(cdsRespTimeoutTask.isCancelled()).isFalse(); + assertThat(cdsRespTimeoutTask.getDelay(TimeUnit.SECONDS)) + .isEqualTo(XdsClientImpl.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC); + + // Start watching endpoint data. + xdsClient.watchEndpointData("cluster-foo.googleapis.com", endpointWatcher); + ScheduledTask edsTimeoutTask = + Iterables.getOnlyElement( + fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)); + assertThat(edsTimeoutTask.getDelay(TimeUnit.SECONDS)) + .isEqualTo(XdsClientImpl.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC); + + // RPC stream is broken again. + responseObserver.onError(Status.UNAVAILABLE.asException()); + + assertThat(edsTimeoutTask.isCancelled()).isTrue(); + inOrder.verify(backoffPolicy2).nextBackoffNanos(); + assertThat(fakeClock.getPendingTasks(RPC_RETRY_TASK_FILTER)).hasSize(1); + + fakeClock.forwardNanos(200L); + inOrder.verify(mockedDiscoveryService) + .streamAggregatedResources(responseObserverCaptor.capture()); + + assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); + assertThat(fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); + } + + /** + * Tests sending a streaming LRS RPC for each cluster to report loads for. + */ + @Test + public void reportLoadStatsToServer() { + String clusterName = "cluster-foo.googleapis.com"; + LoadStatsStore loadStatsStore = new LoadStatsStoreImpl(clusterName, null); + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(null); + xdsClient.reportClientStats(clusterName, null, loadStatsStore); + LoadReportCall lrsCall = loadReportCalls.poll(); + verify(lrsCall.requestObserver).onNext(requestCaptor.capture()); + assertThat(requestCaptor.getValue().getClusterStatsCount()) + .isEqualTo(0); // initial request + + lrsCall.responseObserver.onNext( + LoadStatsResponse.newBuilder() + .addClusters(clusterName) + .setLoadReportingInterval(Durations.fromNanos(1000L)) + .build()); + fakeClock.forwardNanos(1000L); + verify(lrsCall.requestObserver, times(2)).onNext(requestCaptor.capture()); + ClusterStats report = Iterables.getOnlyElement(requestCaptor.getValue().getClusterStatsList()); + assertThat(report.getClusterName()).isEqualTo(clusterName); + + xdsClient.cancelClientStatsReport(clusterName, null); + fakeClock.forwardNanos(1000L); + verify(lrsCall.requestObserver, times(3)).onNext(requestCaptor.capture()); + assertThat(requestCaptor.getValue().getClusterStatsCount()) + .isEqualTo(0); // no more stats reported + + // See more test on LoadReportClientTest.java + } + + // Simulates the use case of watching clusters/endpoints based on service config resolved by + // LDS/RDS. + private void waitUntilConfigResolved(StreamObserver responseObserver) { + // Client sent an LDS request for resource TARGET_AUTHORITY (Omitted). + + // Management server responses with a listener telling client to do RDS. + Rds rdsConfig = + Rds.newBuilder() + .setConfigSource( + ConfigSource.newBuilder().setAds(AggregatedConfigSource.getDefaultInstance())) + .setRouteConfigName("route-foo.googleapis.com") + .build(); + + List listeners = ImmutableList.of( + Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) + ); + DiscoveryResponse ldsResponse = + buildDiscoveryResponseV2("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + responseObserver.onNext(ldsResponse); + + // Client sent an LDS ACK request and an RDS request for resource + // "route-foo.googleapis.com" (Omitted). + + // Management server sends an RDS response. + List routeConfigs = ImmutableList.of( + Any.pack( + buildRouteConfiguration( + "route-foo.googleapis.com", // target route configuration + ImmutableList.of( + buildVirtualHost( + ImmutableList.of(TARGET_AUTHORITY), // matching virtual host + "cluster.googleapis.com"))))); + DiscoveryResponse rdsResponse = + buildDiscoveryResponseV2("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); + responseObserver.onNext(rdsResponse); + } + + @Test + public void matchHostName_exactlyMatch() { + String pattern = "foo.googleapis.com"; + assertThat(XdsClientImpl.matchHostName("bar.googleapis.com", pattern)).isFalse(); + assertThat(XdsClientImpl.matchHostName("fo.googleapis.com", pattern)).isFalse(); + assertThat(XdsClientImpl.matchHostName("oo.googleapis.com", pattern)).isFalse(); + assertThat(XdsClientImpl.matchHostName("googleapis.com", pattern)).isFalse(); + assertThat(XdsClientImpl.matchHostName("foo.googleapis", pattern)).isFalse(); + assertThat(XdsClientImpl.matchHostName("foo.googleapis.com", pattern)).isTrue(); + } + + @Test + public void matchHostName_prefixWildcard() { + String pattern = "*.foo.googleapis.com"; + assertThat(XdsClientImpl.matchHostName("foo.googleapis.com", pattern)).isFalse(); + assertThat(XdsClientImpl.matchHostName("bar-baz.foo.googleapis", pattern)).isFalse(); + assertThat(XdsClientImpl.matchHostName("bar.foo.googleapis.com", pattern)).isTrue(); + pattern = "*-bar.foo.googleapis.com"; + assertThat(XdsClientImpl.matchHostName("bar.foo.googleapis.com", pattern)).isFalse(); + assertThat(XdsClientImpl.matchHostName("baz-bar.foo.googleapis", pattern)).isFalse(); + assertThat(XdsClientImpl.matchHostName("-bar.foo.googleapis.com", pattern)).isFalse(); + assertThat(XdsClientImpl.matchHostName("baz-bar.foo.googleapis.com", pattern)) + .isTrue(); + } + + @Test + public void matchHostName_postfixWildCard() { + String pattern = "foo.*"; + assertThat(XdsClientImpl.matchHostName("bar.googleapis.com", pattern)).isFalse(); + assertThat(XdsClientImpl.matchHostName("bar.foo.googleapis.com", pattern)).isFalse(); + assertThat(XdsClientImpl.matchHostName("foo.googleapis.com", pattern)).isTrue(); + assertThat(XdsClientImpl.matchHostName("foo.com", pattern)).isTrue(); + pattern = "foo-*"; + assertThat(XdsClientImpl.matchHostName("bar-.googleapis.com", pattern)).isFalse(); + assertThat(XdsClientImpl.matchHostName("foo.googleapis.com", pattern)).isFalse(); + assertThat(XdsClientImpl.matchHostName("foo.googleapis.com", pattern)).isFalse(); + assertThat(XdsClientImpl.matchHostName("foo-", pattern)).isFalse(); + assertThat(XdsClientImpl.matchHostName("foo-bar.com", pattern)).isTrue(); + assertThat(XdsClientImpl.matchHostName("foo-.com", pattern)).isTrue(); + assertThat(XdsClientImpl.matchHostName("foo-bar", pattern)).isTrue(); + } + + @Test + public void findVirtualHostForHostName_exactMatchFirst() { + String hostname = "a.googleapis.com"; + VirtualHost vHost1 = + VirtualHost.newBuilder() + .setName("virtualhost01.googleapis.com") // don't care + .addAllDomains(ImmutableList.of("a.googleapis.com", "b.googleapis.com")) + .build(); + VirtualHost vHost2 = + VirtualHost.newBuilder() + .setName("virtualhost02.googleapis.com") // don't care + .addAllDomains(ImmutableList.of("*.googleapis.com")) + .build(); + VirtualHost vHost3 = + VirtualHost.newBuilder() + .setName("virtualhost03.googleapis.com") // don't care + .addAllDomains(ImmutableList.of("*")) + .build(); + RouteConfiguration routeConfig = + RouteConfiguration.newBuilder() + .setName("route-foo.googleapis.com") + .addAllVirtualHosts(ImmutableList.of(vHost1, vHost2, vHost3)) + .build(); + assertThat(XdsClientImpl.findVirtualHostForHostName(routeConfig, hostname)).isEqualTo(vHost1); + } + + @Test + public void findVirtualHostForHostName_preferSuffixDomainOverPrefixDomain() { + String hostname = "a.googleapis.com"; + VirtualHost vHost1 = + VirtualHost.newBuilder() + .setName("virtualhost01.googleapis.com") // don't care + .addAllDomains(ImmutableList.of("*.googleapis.com", "b.googleapis.com")) + .build(); + VirtualHost vHost2 = + VirtualHost.newBuilder() + .setName("virtualhost02.googleapis.com") // don't care + .addAllDomains(ImmutableList.of("a.googleapis.*")) + .build(); + VirtualHost vHost3 = + VirtualHost.newBuilder() + .setName("virtualhost03.googleapis.com") // don't care + .addAllDomains(ImmutableList.of("*")) + .build(); + RouteConfiguration routeConfig = + RouteConfiguration.newBuilder() + .setName("route-foo.googleapis.com") + .addAllVirtualHosts(ImmutableList.of(vHost1, vHost2, vHost3)) + .build(); + assertThat(XdsClientImpl.findVirtualHostForHostName(routeConfig, hostname)).isEqualTo(vHost1); + } + + @Test + public void findVirtualHostForHostName_asteriskMatchAnyDomain() { + String hostname = "a.googleapis.com"; + VirtualHost vHost1 = + VirtualHost.newBuilder() + .setName("virtualhost01.googleapis.com") // don't care + .addAllDomains(ImmutableList.of("*")) + .build(); + VirtualHost vHost2 = + VirtualHost.newBuilder() + .setName("virtualhost02.googleapis.com") // don't care + .addAllDomains(ImmutableList.of("b.googleapis.com")) + .build(); + RouteConfiguration routeConfig = + RouteConfiguration.newBuilder() + .setName("route-foo.googleapis.com") + .addAllVirtualHosts(ImmutableList.of(vHost1, vHost2)) + .build(); + assertThat(XdsClientImpl.findVirtualHostForHostName(routeConfig, hostname)).isEqualTo(vHost1); + } + + @Test + public void populateRoutesInVirtualHost_routeWithCaseInsensitiveMatch() { + VirtualHost virtualHost = + VirtualHost.newBuilder() + .setName("virtualhost00.googleapis.com") // don't care + .addDomains(TARGET_AUTHORITY) + .addRoutes( + Route.newBuilder() + .setRoute(RouteAction.newBuilder().setCluster("cluster.googleapis.com")) + .setMatch( + RouteMatch.newBuilder() + .setPrefix("") + .setCaseSensitive(BoolValue.newBuilder().setValue(false)))) + .build(); + + thrown.expect(XdsClientImpl.InvalidProtoDataException.class); + XdsClientImpl.populateRoutesInVirtualHost(virtualHost); + } + + @Test + public void populateRoutesInVirtualHost_NoUsableRoute() { + VirtualHost virtualHost = + VirtualHost.newBuilder() + .setName("virtualhost00.googleapis.com") // don't care + .addDomains(TARGET_AUTHORITY) + .addRoutes( + // route with unsupported action + Route.newBuilder() + .setRoute(RouteAction.newBuilder().setClusterHeader("cluster header string")) + .setMatch(RouteMatch.newBuilder().setPrefix("/"))) + .addRoutes( + // route with unsupported matcher type + Route.newBuilder() + .setRoute(RouteAction.newBuilder().setCluster("cluster.googleapis.com")) + .setMatch( + RouteMatch.newBuilder() + .setPrefix("/") + .addQueryParameters(QueryParameterMatcher.getDefaultInstance()))) + .build(); + + thrown.expect(XdsClientImpl.InvalidProtoDataException.class); + XdsClientImpl.populateRoutesInVirtualHost(virtualHost); + } + + @Test + public void messagePrinter_printLdsResponse() { + MessagePrinter printer = new MessagePrinter(); + List listeners = ImmutableList.of( + Any.pack(buildListener("foo.googleapis.com:8080", + Any.pack( + HttpConnectionManager.newBuilder() + .setRouteConfig( + buildRouteConfiguration("route-foo.googleapis.com", + ImmutableList.of( + buildVirtualHost( + ImmutableList.of("foo.googleapis.com", "bar.googleapis.com"), + "cluster.googleapis.com")))) + .build())))); + DiscoveryResponse response = + buildDiscoveryResponseV2("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + + String expectedString = "{\n" + + " \"versionInfo\": \"0\",\n" + + " \"resources\": [{\n" + + " \"@type\": \"type.googleapis.com/envoy.api.v2.Listener\",\n" + + " \"name\": \"foo.googleapis.com:8080\",\n" + + " \"address\": {\n" + + " },\n" + + " \"filterChains\": [{\n" + + " }],\n" + + " \"apiListener\": {\n" + + " \"apiListener\": {\n" + + " \"@type\": \"type.googleapis.com/envoy.config.filter.network" + + ".http_connection_manager.v2.HttpConnectionManager\",\n" + + " \"routeConfig\": {\n" + + " \"name\": \"route-foo.googleapis.com\",\n" + + " \"virtualHosts\": [{\n" + + " \"name\": \"virtualhost00.googleapis.com\",\n" + + " \"domains\": [\"foo.googleapis.com\", \"bar.googleapis.com\"],\n" + + " \"routes\": [{\n" + + " \"match\": {\n" + + " \"prefix\": \"\"\n" + + " },\n" + + " \"route\": {\n" + + " \"cluster\": \"cluster.googleapis.com\"\n" + + " }\n" + + " }]\n" + + " }]\n" + + " }\n" + + " }\n" + + " }\n" + + " }],\n" + + " \"typeUrl\": \"type.googleapis.com/envoy.api.v2.Listener\",\n" + + " \"nonce\": \"0000\"\n" + + "}"; + String res = printer.print(response); + assertThat(res).isEqualTo(expectedString); + } + + @Test + public void messagePrinter_printRdsResponse() { + MessagePrinter printer = new MessagePrinter(); + List routeConfigs = + ImmutableList.of( + Any.pack( + buildRouteConfiguration( + "route-foo.googleapis.com", + ImmutableList.of( + buildVirtualHost( + ImmutableList.of("foo.googleapis.com", "bar.googleapis.com"), + "cluster.googleapis.com"))))); + DiscoveryResponse response = + buildDiscoveryResponseV2("213", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0052"); + + String expectedString = "{\n" + + " \"versionInfo\": \"213\",\n" + + " \"resources\": [{\n" + + " \"@type\": \"type.googleapis.com/envoy.api.v2.RouteConfiguration\",\n" + + " \"name\": \"route-foo.googleapis.com\",\n" + + " \"virtualHosts\": [{\n" + + " \"name\": \"virtualhost00.googleapis.com\",\n" + + " \"domains\": [\"foo.googleapis.com\", \"bar.googleapis.com\"],\n" + + " \"routes\": [{\n" + + " \"match\": {\n" + + " \"prefix\": \"\"\n" + + " },\n" + + " \"route\": {\n" + + " \"cluster\": \"cluster.googleapis.com\"\n" + + " }\n" + + " }]\n" + + " }]\n" + + " }],\n" + + " \"typeUrl\": \"type.googleapis.com/envoy.api.v2.RouteConfiguration\",\n" + + " \"nonce\": \"0052\"\n" + + "}"; + String res = printer.print(response); + assertThat(res).isEqualTo(expectedString); + } + + @Test + public void messagePrinter_printCdsResponse() { + MessagePrinter printer = new MessagePrinter(); + List clusters = ImmutableList.of( + Any.pack(buildCluster("cluster-bar.googleapis.com", "service-blaze:cluster-bar", true)), + Any.pack(buildCluster("cluster-foo.googleapis.com", null, false))); + DiscoveryResponse response = + buildDiscoveryResponseV2("14", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "8"); + + String expectedString = "{\n" + + " \"versionInfo\": \"14\",\n" + + " \"resources\": [{\n" + + " \"@type\": \"type.googleapis.com/envoy.api.v2.Cluster\",\n" + + " \"name\": \"cluster-bar.googleapis.com\",\n" + + " \"type\": \"EDS\",\n" + + " \"edsClusterConfig\": {\n" + + " \"edsConfig\": {\n" + + " \"ads\": {\n" + + " }\n" + + " },\n" + + " \"serviceName\": \"service-blaze:cluster-bar\"\n" + + " },\n" + + " \"lrsServer\": {\n" + + " \"self\": {\n" + + " }\n" + + " }\n" + + " }, {\n" + + " \"@type\": \"type.googleapis.com/envoy.api.v2.Cluster\",\n" + + " \"name\": \"cluster-foo.googleapis.com\",\n" + + " \"type\": \"EDS\",\n" + + " \"edsClusterConfig\": {\n" + + " \"edsConfig\": {\n" + + " \"ads\": {\n" + + " }\n" + + " }\n" + + " }\n" + + " }],\n" + + " \"typeUrl\": \"type.googleapis.com/envoy.api.v2.Cluster\",\n" + + " \"nonce\": \"8\"\n" + + "}"; + String res = printer.print(response); + assertThat(res).isEqualTo(expectedString); + } + + @Test + public void messagePrinter_printEdsResponse() { + MessagePrinter printer = new MessagePrinter(); + List clusterLoadAssignments = ImmutableList.of( + Any.pack(buildClusterLoadAssignment("cluster-foo.googleapis.com", + ImmutableList.of( + buildLocalityLbEndpoints("region1", "zone1", "subzone1", + ImmutableList.of( + buildLbEndpoint("192.168.0.1", 8080, HealthStatus.HEALTHY, 2)), + 1, 0), + buildLocalityLbEndpoints("region3", "zone3", "subzone3", + ImmutableList.of( + buildLbEndpoint("192.168.142.5", 80, HealthStatus.UNHEALTHY, 5)), + 2, 1)), + ImmutableList.of( + buildDropOverload("lb", 200), + buildDropOverload("throttle", 1000))))); + + DiscoveryResponse response = + buildDiscoveryResponseV2("5", clusterLoadAssignments, + XdsClientImpl.ADS_TYPE_URL_EDS_V2, "004"); + + String expectedString = "{\n" + + " \"versionInfo\": \"5\",\n" + + " \"resources\": [{\n" + + " \"@type\": \"type.googleapis.com/envoy.api.v2.ClusterLoadAssignment\",\n" + + " \"clusterName\": \"cluster-foo.googleapis.com\",\n" + + " \"endpoints\": [{\n" + + " \"locality\": {\n" + + " \"region\": \"region1\",\n" + + " \"zone\": \"zone1\",\n" + + " \"subZone\": \"subzone1\"\n" + + " },\n" + + " \"lbEndpoints\": [{\n" + + " \"endpoint\": {\n" + + " \"address\": {\n" + + " \"socketAddress\": {\n" + + " \"address\": \"192.168.0.1\",\n" + + " \"portValue\": 8080\n" + + " }\n" + + " }\n" + + " },\n" + + " \"healthStatus\": \"HEALTHY\",\n" + + " \"loadBalancingWeight\": 2\n" + + " }],\n" + + " \"loadBalancingWeight\": 1\n" + + " }, {\n" + + " \"locality\": {\n" + + " \"region\": \"region3\",\n" + + " \"zone\": \"zone3\",\n" + + " \"subZone\": \"subzone3\"\n" + + " },\n" + + " \"lbEndpoints\": [{\n" + + " \"endpoint\": {\n" + + " \"address\": {\n" + + " \"socketAddress\": {\n" + + " \"address\": \"192.168.142.5\",\n" + + " \"portValue\": 80\n" + + " }\n" + + " }\n" + + " },\n" + + " \"healthStatus\": \"UNHEALTHY\",\n" + + " \"loadBalancingWeight\": 5\n" + + " }],\n" + + " \"loadBalancingWeight\": 2,\n" + + " \"priority\": 1\n" + + " }],\n" + + " \"policy\": {\n" + + " \"dropOverloads\": [{\n" + + " \"category\": \"lb\",\n" + + " \"dropPercentage\": {\n" + + " \"numerator\": 200,\n" + + " \"denominator\": \"MILLION\"\n" + + " }\n" + + " }, {\n" + + " \"category\": \"throttle\",\n" + + " \"dropPercentage\": {\n" + + " \"numerator\": 1000,\n" + + " \"denominator\": \"MILLION\"\n" + + " }\n" + + " }],\n" + + " \"disableOverprovisioning\": true\n" + + " }\n" + + " }],\n" + + " \"typeUrl\": \"type.googleapis.com/envoy.api.v2.ClusterLoadAssignment\",\n" + + " \"nonce\": \"004\"\n" + + "}"; + String res = printer.print(response); + assertThat(res).isEqualTo(expectedString); + } + + private static void assertConfigUpdateContainsSingleClusterRoute( + ConfigUpdate configUpdate, String expectedClusterName) { + List routes = configUpdate.getRoutes(); + assertThat(routes).hasSize(1); + assertThat(Iterables.getOnlyElement(routes).getRouteAction().getCluster()) + .isEqualTo(expectedClusterName); + } + + /** + * Matcher for DiscoveryRequest without the comparison of error_details field, which is used for + * management server debugging purposes. + * + *

In general, if you are sure error_details field should not be set in a DiscoveryRequest, + * compare with message equality. Otherwise, this matcher is handy for comparing other fields + * only. + */ + private static class DiscoveryRequestMatcher implements ArgumentMatcher { + private final String versionInfo; + private final String typeUrl; + private final Set resourceNames; + private final String responseNonce; + + private DiscoveryRequestMatcher(String versionInfo, String resourceName, String typeUrl, + String responseNonce) { + this(versionInfo, ImmutableList.of(resourceName), typeUrl, responseNonce); + } + + private DiscoveryRequestMatcher( + String versionInfo, List resourceNames, String typeUrl, String responseNonce) { + this.versionInfo = versionInfo; + this.resourceNames = new HashSet<>(resourceNames); + this.typeUrl = typeUrl; + this.responseNonce = responseNonce; + } + + @Override + public boolean matches(DiscoveryRequest argument) { + if (!typeUrl.equals(argument.getTypeUrl())) { + return false; + } + if (!versionInfo.equals(argument.getVersionInfo())) { + return false; + } + if (!responseNonce.equals(argument.getResponseNonce())) { + return false; + } + if (!resourceNames.equals(new HashSet<>(argument.getResourceNamesList()))) { + return false; + } + return argument.getNode().equals(NODE.toEnvoyProtoNodeV2()); + } + } + + private static class LoadReportCall { + private final StreamObserver requestObserver; + @SuppressWarnings("unused") + private final StreamObserver responseObserver; + + LoadReportCall(StreamObserver requestObserver, + StreamObserver responseObserver) { + this.requestObserver = requestObserver; + this.responseObserver = responseObserver; + } + } +} diff --git a/xds/src/test/java/io/grpc/xds/XdsClientTestHelper.java b/xds/src/test/java/io/grpc/xds/XdsClientTestHelper.java index 9918a9073e1..ab976e877e1 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientTestHelper.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientTestHelper.java @@ -25,8 +25,6 @@ import io.envoyproxy.envoy.api.v2.Cluster.LbPolicy; import io.envoyproxy.envoy.api.v2.ClusterLoadAssignment; import io.envoyproxy.envoy.api.v2.ClusterLoadAssignment.Policy; -import io.envoyproxy.envoy.api.v2.DiscoveryRequest; -import io.envoyproxy.envoy.api.v2.DiscoveryResponse; import io.envoyproxy.envoy.api.v2.Listener; import io.envoyproxy.envoy.api.v2.RouteConfiguration; import io.envoyproxy.envoy.api.v2.auth.CommonTlsContext; @@ -48,6 +46,8 @@ import io.envoyproxy.envoy.api.v2.route.RouteMatch; import io.envoyproxy.envoy.api.v2.route.VirtualHost; import io.envoyproxy.envoy.config.listener.v2.ApiListener; +import io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest; +import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse; import io.envoyproxy.envoy.type.FractionalPercent; import io.envoyproxy.envoy.type.FractionalPercent.DenominatorType; import io.grpc.xds.EnvoyProtoData.Node; @@ -69,6 +69,17 @@ static DiscoveryResponse buildDiscoveryResponse(String versionInfo, .build(); } + static io.envoyproxy.envoy.api.v2.DiscoveryResponse buildDiscoveryResponseV2(String versionInfo, + List resources, String typeUrl, String nonce) { + return + io.envoyproxy.envoy.api.v2.DiscoveryResponse.newBuilder() + .setVersionInfo(versionInfo) + .setTypeUrl(typeUrl) + .addAllResources(resources) + .setNonce(nonce) + .build(); + } + static DiscoveryRequest buildDiscoveryRequest(Node node, String versionInfo, String resourceName, String typeUrl, String nonce) { return buildDiscoveryRequest(node, versionInfo, ImmutableList.of(resourceName), typeUrl, nonce); @@ -78,6 +89,24 @@ static DiscoveryRequest buildDiscoveryRequest(Node node, String versionInfo, List resourceNames, String typeUrl, String nonce) { return DiscoveryRequest.newBuilder() + .setVersionInfo(versionInfo) + .setNode(node.toEnvoyProtoNode()) + .setTypeUrl(typeUrl) + .addAllResourceNames(resourceNames) + .setResponseNonce(nonce) + .build(); + } + + static io.envoyproxy.envoy.api.v2.DiscoveryRequest buildDiscoveryRequestV2( + Node node, String versionInfo, String resourceName, String typeUrl, String nonce) { + return buildDiscoveryRequestV2( + node, versionInfo, ImmutableList.of(resourceName), typeUrl, nonce); + } + + static io.envoyproxy.envoy.api.v2.DiscoveryRequest buildDiscoveryRequestV2( + Node node, String versionInfo, List resourceNames, String typeUrl, String nonce) { + return + io.envoyproxy.envoy.api.v2.DiscoveryRequest.newBuilder() .setVersionInfo(versionInfo) .setNode(node.toEnvoyProtoNodeV2()) .setTypeUrl(typeUrl) diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverIntegrationTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverIntegrationTest.java index 804c50f5106..7007f74ecb8 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverIntegrationTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverIntegrationTest.java @@ -17,7 +17,7 @@ package io.grpc.xds; import static com.google.common.truth.Truth.assertThat; -import static io.grpc.xds.XdsClientTestHelper.buildDiscoveryResponse; +import static io.grpc.xds.XdsClientTestHelper.buildDiscoveryResponseV2; import static io.grpc.xds.XdsClientTestHelper.buildListener; import static io.grpc.xds.XdsClientTestHelper.buildRouteConfiguration; import static io.grpc.xds.XdsClientTestHelper.buildVirtualHost; @@ -396,7 +396,7 @@ public void resolve_xdsRoutingLoadBalancing() { List listeners = ImmutableList.of(Any.pack(buildListener(AUTHORITY, Any.pack(httpConnectionManager)))); responseObserver.onNext( - buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000")); + buildDiscoveryResponseV2("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000")); verify(mockListener).onResult(resolutionResultCaptor.capture()); ResolutionResult result = resolutionResultCaptor.getValue(); @@ -478,7 +478,7 @@ public void resolve_weightedTargetLoadBalancing() { buildVirtualHostForRoutes( AUTHORITY, ImmutableList.of(weightedClustersDefaultRoute)))))); responseObserver.onNext( - buildDiscoveryResponse("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000")); + buildDiscoveryResponseV2("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000")); verify(mockListener).onResult(resolutionResultCaptor.capture()); ResolutionResult result = resolutionResultCaptor.getValue(); @@ -547,7 +547,8 @@ private static DiscoveryResponse buildLdsResponseForCluster( ImmutableList.of(host), // exact match clusterName)))) .build())))); - return buildDiscoveryResponse(versionInfo, listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, nonce); + return buildDiscoveryResponseV2( + versionInfo, listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, nonce); } /** @@ -569,7 +570,8 @@ private static DiscoveryResponse buildLdsResponseForRdsResource( Any.pack( buildListener( host, Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build())))); - return buildDiscoveryResponse(versionInfo, listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, nonce); + return buildDiscoveryResponseV2( + versionInfo, listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, nonce); } /** @@ -588,7 +590,7 @@ private static DiscoveryResponse buildRdsResponseForCluster( routeConfigName, ImmutableList.of( buildVirtualHost(ImmutableList.of(host), clusterName))))); - return buildDiscoveryResponse( + return buildDiscoveryResponseV2( versionInfo, routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, nonce); } From 9dc5eec4d76193334a37418db6114e7ae090d298 Mon Sep 17 00:00:00 2001 From: Chengyuan Zhang Date: Sat, 1 Aug 2020 01:48:52 +0000 Subject: [PATCH 36/88] xds: resolve conflicts by adding timeout field in parsed RouteAction for v2 tests (#7278) --- xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java index f2ec57c8f2b..178fe46bd42 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java @@ -752,7 +752,8 @@ public void resolveVirtualHostWithPathMatchingInRdsResponse() { new io.grpc.xds.RouteMatch( /* prefix= */ null, /* path= */ "/service1/method1"), - new EnvoyProtoData.RouteAction("cl1.googleapis.com", null))); + new EnvoyProtoData.RouteAction( + TimeUnit.SECONDS.toNanos(15L), "cl1.googleapis.com", null))); assertThat(routes.get(1)).isEqualTo( new EnvoyProtoData.Route( // path match with weighted cluster route @@ -760,6 +761,7 @@ public void resolveVirtualHostWithPathMatchingInRdsResponse() { /* prefix= */ null, /* path= */ "/service2/method2"), new EnvoyProtoData.RouteAction( + TimeUnit.SECONDS.toNanos(15L), null, ImmutableList.of( new EnvoyProtoData.ClusterWeight("cl21.googleapis.com", 30), @@ -771,7 +773,8 @@ public void resolveVirtualHostWithPathMatchingInRdsResponse() { new io.grpc.xds.RouteMatch( /* prefix= */ "/service1/", /* path= */ null), - new EnvoyProtoData.RouteAction("cl1.googleapis.com", null))); + new EnvoyProtoData.RouteAction( + TimeUnit.SECONDS.toNanos(15L), "cl1.googleapis.com", null))); assertThat(routes.get(3)).isEqualTo( new EnvoyProtoData.Route( // default match with cluster route @@ -779,7 +782,7 @@ public void resolveVirtualHostWithPathMatchingInRdsResponse() { /* prefix= */ "", /* path= */ null), new EnvoyProtoData.RouteAction( - "cluster.googleapis.com", null))); + TimeUnit.SECONDS.toNanos(15L), "cluster.googleapis.com", null))); } /** From c116d6846b0c1679437790430c2c2a749af2e240 Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Mon, 3 Aug 2020 09:49:57 -0700 Subject: [PATCH 37/88] xds: bring envoy and proto imports up to date for cert-provider-instance protos (#7280) --- xds/third_party/envoy/import.sh | 3 +- .../main/proto/envoy/api/v2/auth/common.proto | 4 +- .../main/proto/envoy/api/v2/core/base.proto | 2 +- .../envoy/api/v2/core/config_source.proto | 3 + .../envoy/api/v2/route/route_components.proto | 15 ++ .../envoy/config/accesslog/v3/accesslog.proto | 1 + .../envoy/config/cluster/v3/cluster.proto | 98 +++++++++++- .../proto/envoy/config/core/v3/base.proto | 6 +- .../envoy/config/core/v3/config_source.proto | 29 +++- .../envoy/config/core/v3/extension.proto | 31 ++++ .../envoy/config/core/v3/grpc_service.proto | 23 ++- .../proto/envoy/config/core/v3/protocol.proto | 23 ++- .../core/v3/substitution_format_string.proto | 61 +++++++ .../envoy/config/endpoint/v3/endpoint.proto | 2 +- .../v2/http_connection_manager.proto | 16 +- .../envoy/config/listener/v3/listener.proto | 8 + .../config/route/v3/route_components.proto | 81 +++++++++- .../v3/http_connection_manager.proto | 151 ++++++++++++++++-- .../transport_sockets/tls/v3/common.proto | 4 +- .../transport_sockets/tls/v3/secret.proto | 10 +- .../transport_sockets/tls/v3/tls.proto | 45 +++++- .../service/discovery/v3/discovery.proto | 39 ++++- .../main/proto/envoy/type/matcher/regex.proto | 10 ++ .../proto/envoy/type/matcher/v3/regex.proto | 12 +- xds/third_party/udpa/import.sh | 7 +- .../main/proto/udpa/core/v1/authority.proto | 21 +++ .../proto/udpa/core/v1/collection_entry.proto | 52 ++++++ .../proto/udpa/core/v1/context_params.proto | 16 ++ .../proto/udpa/core/v1/resource_locator.proto | 117 ++++++++++++++ .../proto/udpa/core/v1/resource_name.proto | 41 +++++ 30 files changed, 878 insertions(+), 53 deletions(-) create mode 100644 xds/third_party/envoy/src/main/proto/envoy/config/core/v3/substitution_format_string.proto create mode 100644 xds/third_party/udpa/src/main/proto/udpa/core/v1/authority.proto create mode 100644 xds/third_party/udpa/src/main/proto/udpa/core/v1/collection_entry.proto create mode 100644 xds/third_party/udpa/src/main/proto/udpa/core/v1/context_params.proto create mode 100644 xds/third_party/udpa/src/main/proto/udpa/core/v1/resource_locator.proto create mode 100644 xds/third_party/udpa/src/main/proto/udpa/core/v1/resource_name.proto diff --git a/xds/third_party/envoy/import.sh b/xds/third_party/envoy/import.sh index cd16468c50b..470c40b540d 100755 --- a/xds/third_party/envoy/import.sh +++ b/xds/third_party/envoy/import.sh @@ -18,7 +18,7 @@ set -e BRANCH=master # import VERSION from one of the google internal CLs -VERSION=440899714143b6a143917cbd8e3f0ccba0847cd4 +VERSION=3dedf1693f45239c670c5ba7598db44ff2e32c2f GIT_REPO="https://github.com/envoyproxy/envoy.git" GIT_BASE_DIR=envoy SOURCE_PROTO_BASE_DIR=envoy/api @@ -79,6 +79,7 @@ envoy/config/core/v3/http_uri.proto envoy/config/core/v3/protocol.proto envoy/config/core/v3/proxy_protocol.proto envoy/config/core/v3/socket_option.proto +envoy/config/core/v3/substitution_format_string.proto envoy/config/endpoint/v3/endpoint.proto envoy/config/endpoint/v3/endpoint_components.proto envoy/config/filter/accesslog/v2/accesslog.proto diff --git a/xds/third_party/envoy/src/main/proto/envoy/api/v2/auth/common.proto b/xds/third_party/envoy/src/main/proto/envoy/api/v2/auth/common.proto index ab4b9c13493..c8122f40102 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/api/v2/auth/common.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/api/v2/auth/common.proto @@ -45,8 +45,8 @@ message TlsParameters { // servers. TlsProtocol tls_minimum_protocol_version = 1 [(validate.rules).enum = {defined_only: true}]; - // Maximum TLS protocol version. By default, it's ``TLSv1_3`` for servers in non-FIPS builds, and - // ``TLSv1_2`` for clients and for servers using :ref:`BoringSSL FIPS `. + // Maximum TLS protocol version. By default, it's ``TLSv1_2`` for clients and ``TLSv1_3`` for + // servers. TlsProtocol tls_maximum_protocol_version = 2 [(validate.rules).enum = {defined_only: true}]; // If specified, the TLS listener will only support the specified `cipher list diff --git a/xds/third_party/envoy/src/main/proto/envoy/api/v2/core/base.proto b/xds/third_party/envoy/src/main/proto/envoy/api/v2/core/base.proto index b7145d77efd..39846bc658a 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/api/v2/core/base.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/api/v2/core/base.proto @@ -93,7 +93,7 @@ message BuildVersion { type.SemanticVersion version = 1; // Free-form build information. - // Envoy defines several well known keys in the source/common/common/version.h file + // Envoy defines several well known keys in the source/common/version/version.h file google.protobuf.Struct metadata = 2; } diff --git a/xds/third_party/envoy/src/main/proto/envoy/api/v2/core/config_source.proto b/xds/third_party/envoy/src/main/proto/envoy/api/v2/core/config_source.proto index 8bbb961c1a3..7032b2c10d8 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/api/v2/core/config_source.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/api/v2/core/config_source.proto @@ -106,6 +106,9 @@ message AggregatedConfigSource { // set in :ref:`ConfigSource ` can be used to // specify that other data can be obtained from the same server. message SelfConfigSource { + // API version for xDS transport protocol. This describes the xDS gRPC/REST + // endpoint and version of [Delta]DiscoveryRequest/Response used on the wire. + ApiVersion transport_api_version = 1 [(validate.rules).enum = {defined_only: true}]; } // Rate Limit settings to be applied for discovery requests made by Envoy. diff --git a/xds/third_party/envoy/src/main/proto/envoy/api/v2/route/route_components.proto b/xds/third_party/envoy/src/main/proto/envoy/api/v2/route/route_components.proto index c890134414e..007f71d57cb 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/api/v2/route/route_components.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/api/v2/route/route_components.proto @@ -1177,6 +1177,21 @@ message RedirectAction { oneof path_rewrite_specifier { // The path portion of the URL will be swapped with this value. + // Please note that query string in path_redirect will override the + // request's query string and will not be stripped. + // + // For example, let's say we have the following routes: + // + // - match: { path: "/old-path-1" } + // redirect: { path_redirect: "/new-path-1" } + // - match: { path: "/old-path-2" } + // redirect: { path_redirect: "/new-path-2", strip-query: "true" } + // - match: { path: "/old-path-3" } + // redirect: { path_redirect: "/new-path-3?foo=1", strip_query: "true" } + // + // 1. if request uri is "/old-path-1?bar=1", users will be redirected to "/new-path-1?bar=1" + // 2. if request uri is "/old-path-2?bar=1", users will be redirected to "/new-path-2" + // 3. if request uri is "/old-path-3?bar=1", users will be redirected to "/new-path-3?foo=1" string path_redirect = 2 [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}]; diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/accesslog/v3/accesslog.proto b/xds/third_party/envoy/src/main/proto/envoy/config/accesslog/v3/accesslog.proto index 9a2f276b34b..e1b5a2e58b9 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/accesslog/v3/accesslog.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/accesslog/v3/accesslog.proto @@ -242,6 +242,7 @@ message ResponseFlagFilter { in: "DPE" in: "UMSDR" in: "RFCF" + in: "NFCF" } } }]; diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/cluster/v3/cluster.proto b/xds/third_party/envoy/src/main/proto/envoy/config/cluster/v3/cluster.proto index be7710815b7..b4ea53bb093 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/cluster/v3/cluster.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/cluster/v3/cluster.proto @@ -8,6 +8,7 @@ import "envoy/config/cluster/v3/outlier_detection.proto"; import "envoy/config/core/v3/address.proto"; import "envoy/config/core/v3/base.proto"; import "envoy/config/core/v3/config_source.proto"; +import "envoy/config/core/v3/extension.proto"; import "envoy/config/core/v3/health_check.proto"; import "envoy/config/core/v3/protocol.proto"; import "envoy/config/endpoint/v3/endpoint.proto"; @@ -18,7 +19,12 @@ import "google/protobuf/duration.proto"; import "google/protobuf/struct.proto"; import "google/protobuf/wrappers.proto"; +import "udpa/core/v1/collection_entry.proto"; +import "udpa/core/v1/resource_locator.proto"; + import "envoy/annotations/deprecation.proto"; +import "udpa/annotations/migrate.proto"; +import "udpa/annotations/security.proto"; import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; @@ -30,8 +36,14 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Cluster configuration] +// Cluster list collections. Entries are *Cluster* resources or references. +// [#not-implemented-hide:] +message ClusterCollection { + udpa.core.v1.CollectionEntry entries = 1; +} + // Configuration for a single upstream cluster. -// [#next-free-field: 48] +// [#next-free-field: 50] message Cluster { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.Cluster"; @@ -176,7 +188,12 @@ message Cluster { // Optional alternative to cluster name to present to EDS. This does not // have the same restrictions as cluster name, i.e. it may be arbitrary // length. - string service_name = 2; + string service_name = 2 [(udpa.annotations.field_migrate).oneof_promotion = "name_specifier"]; + + // Resource locator for EDS. This is mutually exclusive to *service_name*. + // [#not-implemented-hide:] + udpa.core.v1.ResourceLocator eds_resource_locator = 3 + [(udpa.annotations.field_migrate).oneof_promotion = "name_specifier"]; } // Optionally divide the endpoints in this cluster into subsets defined by @@ -317,6 +334,31 @@ message Cluster { // The number of random healthy hosts from which the host with the fewest active requests will // be chosen. Defaults to 2 so that we perform two-choice selection if the field is not set. google.protobuf.UInt32Value choice_count = 1 [(validate.rules).uint32 = {gte: 2}]; + + // The following formula is used to calculate the dynamic weights when hosts have different load + // balancing weights: + // + // `weight = load_balancing_weight / (active_requests + 1)^active_request_bias` + // + // The larger the active request bias is, the more aggressively active requests will lower the + // effective weight when all host weights are not equal. + // + // `active_request_bias` must be greater than or equal to 0.0. + // + // When `active_request_bias == 0.0` the Least Request Load Balancer doesn't consider the number + // of active requests at the time it picks a host and behaves like the Round Robin Load + // Balancer. + // + // When `active_request_bias > 0.0` the Least Request Load Balancer scales the load balancing + // weight by the number of active requests at the time it does a pick. + // + // The value is cached for performance reasons and refreshed whenever one of the Load Balancer's + // host sets changes, e.g., whenever there is a host membership update or a host load balancing + // weight change. + // + // .. note:: + // This setting only takes effect if all host weights are not equal. + core.v3.RuntimeDouble active_request_bias = 2; } // Specific configuration for the :ref:`RingHash` @@ -584,11 +626,13 @@ message Cluster { // Soft limit on size of the cluster’s connections read and write buffers. If // unspecified, an implementation defined default is applied (1MiB). - google.protobuf.UInt32Value per_connection_buffer_limit_bytes = 5; + google.protobuf.UInt32Value per_connection_buffer_limit_bytes = 5 + [(udpa.annotations.security).configure_for_untrusted_upstream = true]; // The :ref:`load balancer type ` to use // when picking a host in the cluster. - LbPolicy lb_policy = 6 [(validate.rules).enum = {defined_only: true}]; + // [#comment:TODO: Remove enum constraint :ref:`LOAD_BALANCING_POLICY_CONFIG` when implemented.] + LbPolicy lb_policy = 6 [(validate.rules).enum = {defined_only: true not_in: 7}]; // Setting this is required for specifying members of // :ref:`STATIC`, @@ -635,7 +679,8 @@ message Cluster { // supports prior knowledge for upstream connections. Even if TLS is used // with ALPN, `http2_protocol_options` must be specified. As an aside this allows HTTP/2 // connections to happen over plain text. - core.v3.Http2ProtocolOptions http2_protocol_options = 14; + core.v3.Http2ProtocolOptions http2_protocol_options = 14 + [(udpa.annotations.security).configure_for_untrusted_upstream = true]; // The extension_protocol_options field is used to provide extension-specific protocol options // for upstream connections. The key should match the extension filter name, such as @@ -811,7 +856,34 @@ message Cluster { // request. These show what percentage of a request's per try and global timeout was used. A value // of 0 would indicate that none of the timeout was used or that the timeout was infinite. A value // of 100 would indicate that the request took the entirety of the timeout given to it. - bool track_timeout_budgets = 47; + // + // .. attention:: + // + // This field has been deprecated in favor of `timeout_budgets`, part of + // :ref:`track_cluster_stats `. + bool track_timeout_budgets = 47 [deprecated = true]; + + // Optional customization and configuration of upstream connection pool, and upstream type. + // + // Currently this field only applies for HTTP traffic but is designed for eventual use for custom + // TCP upstreams. + // + // For HTTP traffic, Envoy will generally take downstream HTTP and send it upstream as upstream + // HTTP, using the http connection pool and the codec from `http2_protocol_options` + // + // For routes where CONNECT termination is configured, Envoy will take downstream CONNECT + // requests and forward the CONNECT payload upstream over raw TCP using the tcp connection pool. + // + // The default pool used is the generic connection pool which creates the HTTP upstream for most + // HTTP requests, and the TCP upstream if CONNECT termination is configured. + // + // If users desire custom connection pool or upstream behavior, for example terminating + // CONNECT only if a custom filter indicates it is appropriate, the custom factories + // can be registered and configured here. + core.v3.TypedExtensionConfig upstream_config = 48; + + // Configuration to track optional cluster stats. + TrackClusterStats track_cluster_stats = 49; } // [#not-implemented-hide:] Extensible load balancing policy configuration. @@ -872,3 +944,17 @@ message UpstreamConnectionOptions { // If set then set SO_KEEPALIVE on the socket to enable TCP Keepalives. core.v3.TcpKeepalive tcp_keepalive = 1; } + +message TrackClusterStats { + // If timeout_budgets is true, the :ref:`timeout budget histograms + // ` will be published for each + // request. These show what percentage of a request's per try and global timeout was used. A value + // of 0 would indicate that none of the timeout was used or that the timeout was infinite. A value + // of 100 would indicate that the request took the entirety of the timeout given to it. + bool timeout_budgets = 1; + + // If request_response_sizes is true, then the :ref:`histograms + // ` tracking header and body sizes + // of requests and responses will be published. + bool request_response_sizes = 2; +} diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/base.proto b/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/base.proto index b8ce5bff4bd..4509c166256 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/base.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/base.proto @@ -13,6 +13,7 @@ import "google/protobuf/duration.proto"; import "google/protobuf/struct.proto"; import "google/protobuf/wrappers.proto"; +import "udpa/annotations/migrate.proto"; import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; @@ -94,7 +95,7 @@ message BuildVersion { type.v3.SemanticVersion version = 1; // Free-form build information. - // Envoy defines several well known keys in the source/common/common/version.h file + // Envoy defines several well known keys in the source/common/version/version.h file google.protobuf.Struct metadata = 2; } @@ -332,7 +333,8 @@ message RetryPolicy { // Specifies the allowed number of retries. This parameter is optional and // defaults to 1. - google.protobuf.UInt32Value num_retries = 2; + google.protobuf.UInt32Value num_retries = 2 + [(udpa.annotations.field_migrate).rename = "max_retries"]; } // The message specifies how to fetch data from remote and how to verify it. diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/config_source.proto b/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/config_source.proto index 7337403bc85..72837bb3bee 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/config_source.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/config_source.proto @@ -7,6 +7,8 @@ import "envoy/config/core/v3/grpc_service.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/wrappers.proto"; +import "udpa/core/v1/authority.proto"; + import "envoy/annotations/deprecation.proto"; import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; @@ -19,7 +21,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Configuration sources] -// xDS API version. This is used to describe both resource and transport +// xDS API and non-xDS services version. This is used to describe both resource and transport // protocol versions (in distinct configuration fields). enum ApiVersion { // When not specified, we assume v2, to ease migration to Envoy's stable API @@ -52,13 +54,23 @@ message ApiConfigSource { // the v2 protos is used. REST = 1; - // gRPC v2 API. + // SotW gRPC service. GRPC = 2; // Using the delta xDS gRPC service, i.e. DeltaDiscovery{Request,Response} // rather than Discovery{Request,Response}. Rather than sending Envoy the entire state // with every update, the xDS server only sends what has changed since the last update. DELTA_GRPC = 3; + + // SotW xDS gRPC with ADS. All resources which resolve to this configuration source will be + // multiplexed on a single connection to an ADS endpoint. + // [#not-implemented-hide:] + AGGREGATED_GRPC = 5; + + // Delta xDS gRPC with ADS. All resources which resolve to this configuration source will be + // multiplexed on a single connection to an ADS endpoint. + // [#not-implemented-hide:] + AGGREGATED_DELTA_GRPC = 6; } // API type (gRPC, REST, delta gRPC) @@ -110,6 +122,10 @@ message AggregatedConfigSource { // specify that other data can be obtained from the same server. message SelfConfigSource { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.SelfConfigSource"; + + // API version for xDS transport protocol. This describes the xDS gRPC/REST + // endpoint and version of [Delta]DiscoveryRequest/Response used on the wire. + ApiVersion transport_api_version = 1 [(validate.rules).enum = {defined_only: true}]; } // Rate Limit settings to be applied for discovery requests made by Envoy. @@ -132,10 +148,17 @@ message RateLimitSettings { // ` etc. may either be sourced from the // filesystem or from an xDS API source. Filesystem configs are watched with // inotify for updates. -// [#next-free-field: 7] +// [#next-free-field: 8] message ConfigSource { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.ConfigSource"; + // Authorities that this config source may be used for. An authority specified + // in a *udpa.core.v1.ResourceLocator* is resolved to a *ConfigSource* prior + // to configuration fetch. This field provides the association between + // authority name and configuration source. + // [#not-implemented-hide:] + repeated udpa.core.v1.Authority authorities = 7; + oneof config_source_specifier { option (validate.required) = true; diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/extension.proto b/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/extension.proto index 63639876078..ba66da6a8e3 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/extension.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/extension.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package envoy.config.core.v3; +import "envoy/config/core/v3/config_source.proto"; + import "google/protobuf/any.proto"; import "udpa/annotations/status.proto"; @@ -28,3 +30,32 @@ message TypedExtensionConfig { // ` for further details. google.protobuf.Any typed_config = 2 [(validate.rules).any = {required: true}]; } + +// Configuration source specifier for a late-bound extension configuration. The +// parent resource is warmed until all the initial extension configurations are +// received, unless the flag to apply the default configuration is set. +// Subsequent extension updates are atomic on a per-worker basis. Once an +// extension configuration is applied to a request or a connection, it remains +// constant for the duration of processing. If the initial delivery of the +// extension configuration fails, due to a timeout for example, the optional +// default configuration is applied. Without a default configuration, the +// extension is disabled, until an extension configuration is received. The +// behavior of a disabled extension depends on the context. For example, a +// filter chain with a disabled extension filter rejects all incoming streams. +message ExtensionConfigSource { + ConfigSource config_source = 1 [(validate.rules).any = {required: true}]; + + // Optional default configuration to use as the initial configuration if + // there is a failure to receive the initial extension configuration or if + // `apply_default_config_without_warming` flag is set. + google.protobuf.Any default_config = 2; + + // Use the default config as the initial configuration without warming and + // waiting for the first discovery response. Requires the default configuration + // to be supplied. + bool apply_default_config_without_warming = 3; + + // A set of permitted extension type URLs. Extension configuration updates are rejected + // if they do not match any type URL in the set. + repeated string type_urls = 4 [(validate.rules).repeated = {min_items: 1}]; +} diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/grpc_service.proto b/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/grpc_service.proto index 3acd3c1c9b9..3f62884df6e 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/grpc_service.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/grpc_service.proto @@ -38,7 +38,7 @@ message GrpcService { string cluster_name = 1 [(validate.rules).string = {min_bytes: 1}]; } - // [#next-free-field: 8] + // [#next-free-field: 9] message GoogleGrpc { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.GrpcService.GoogleGrpc"; @@ -203,6 +203,24 @@ message GrpcService { } } + // Channel arguments. + message ChannelArgs { + message Value { + // Pointer values are not supported, since they don't make any sense when + // delivered via the API. + oneof value_specifier { + option (validate.required) = true; + + string string_value = 1; + + int64 int_value = 2; + } + } + + // See grpc_types.h GRPC_ARG #defines for keys that work here. + map args = 1; + } + // The target URI when using the `Google C++ gRPC client // `_. SSL credentials will be supplied in // :ref:`channel_credentials `. @@ -237,6 +255,9 @@ message GrpcService { // How many bytes each stream can buffer internally. // If not set an implementation defined default is applied (1MiB). google.protobuf.UInt32Value per_stream_buffer_limit_bytes = 7; + + // Custom channels args. + ChannelArgs channel_args = 8; } reserved 4; diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/protocol.proto b/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/protocol.proto index 7866b87999e..0ab6289e965 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/protocol.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/protocol.proto @@ -159,7 +159,7 @@ message Http1ProtocolOptions { bool enable_trailers = 5; } -// [#next-free-field: 14] +// [#next-free-field: 15] message Http2ProtocolOptions { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.Http2ProtocolOptions"; @@ -172,7 +172,7 @@ message Http2ProtocolOptions { // The 16 bit parameter identifier. google.protobuf.UInt32Value identifier = 1 [ - (validate.rules).uint32 = {lte: 65536 gte: 1}, + (validate.rules).uint32 = {lte: 65535 gte: 0}, (validate.rules).message = {required: true} ]; @@ -280,8 +280,25 @@ message Http2ProtocolOptions { // the whole HTTP/2 connection is terminated upon receiving invalid HEADERS frame. However, // when this option is enabled, only the offending stream is terminated. // + // This is overridden by HCM :ref:`stream_error_on_invalid_http_messaging + // ` + // iff present. + // + // This is deprecated in favor of :ref:`override_stream_error_on_invalid_http_message + // ` + // + // See `RFC7540, sec. 8.1 `_ for details. + bool stream_error_on_invalid_http_messaging = 12 [deprecated = true]; + + // Allows invalid HTTP messaging and headers. When this option is disabled (default), then + // the whole HTTP/2 connection is terminated upon receiving invalid HEADERS frame. However, + // when this option is enabled, only the offending stream is terminated. + // + // This overrides any HCM :ref:`stream_error_on_invalid_http_messaging + // ` + // // See `RFC7540, sec. 8.1 `_ for details. - bool stream_error_on_invalid_http_messaging = 12; + google.protobuf.BoolValue override_stream_error_on_invalid_http_message = 14; // [#not-implemented-hide:] // Specifies SETTINGS frame parameters to be sent to the peer, with two exceptions: diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/substitution_format_string.proto b/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/substitution_format_string.proto new file mode 100644 index 00000000000..7537a1178b6 --- /dev/null +++ b/xds/third_party/envoy/src/main/proto/envoy/config/core/v3/substitution_format_string.proto @@ -0,0 +1,61 @@ +syntax = "proto3"; + +package envoy.config.core.v3; + +import "google/protobuf/struct.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.config.core.v3"; +option java_outer_classname = "SubstitutionFormatStringProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Substitution format string] + +// Configuration to use multiple :ref:`command operators ` +// to generate a new string in either plain text or JSON format. +message SubstitutionFormatString { + oneof format { + option (validate.required) = true; + + // Specify a format with command operators to form a text string. + // Its details is described in :ref:`format string`. + // + // .. code-block:: + // + // text_format: %LOCAL_REPLY_BODY%:%RESPONSE_CODE%:path=$REQ(:path)% + // + // The following plain text will be created: + // + // .. code-block:: + // + // upstream connect error:204:path=/foo + // + string text_format = 1 [(validate.rules).string = {min_bytes: 1}]; + + // Specify a format with command operators to form a JSON string. + // Its details is described in :ref:`format dictionary`. + // Values are rendered as strings, numbers, or boolean values as appropriate. + // Nested JSON objects may be produced by some command operators (e.g. FILTER_STATE or DYNAMIC_METADATA). + // See the documentation for a specific command operator for details. + // + // .. code-block:: + // + // json_format: + // status: %RESPONSE_CODE% + // message: %LOCAL_REPLY_BODY% + // + // The following JSON object would be created: + // + // .. code-block:: json + // + // { + // "status": 500, + // "message": "My error message" + // } + // + google.protobuf.Struct json_format = 2 [(validate.rules).message = {required: true}]; + } +} diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/endpoint/v3/endpoint.proto b/xds/third_party/envoy/src/main/proto/envoy/config/endpoint/v3/endpoint.proto index 63869fafcb5..e58c327156c 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/endpoint/v3/endpoint.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/endpoint/v3/endpoint.proto @@ -80,7 +80,7 @@ message ClusterLoadAssignment { // Priority levels and localities are considered overprovisioned with this // factor (in percentage). This means that we don't consider a priority - // level or locality unhealthy until the percentage of healthy hosts + // level or locality unhealthy until the fraction of healthy hosts // multiplied by the overprovisioning factor drops below 100. // With the default value 140(1.4), Envoy doesn't consider a priority level // or a locality unhealthy until their percentage of healthy hosts drops diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto b/xds/third_party/envoy/src/main/proto/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto index 742e5584bef..06b13acb2f6 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto @@ -332,6 +332,16 @@ message HttpConnectionManager { // is terminated with a 408 Request Timeout error code if no upstream response // header has been received, otherwise a stream reset occurs. // + // This timeout also specifies the amount of time that Envoy will wait for the peer to open enough + // window to write any remaining stream data once the entirety of stream data (local end stream is + // true) has been buffered pending available window. In other words, this timeout defends against + // a peer that does not release enough window to completely write the stream, even though all + // data has been proxied within available flow control windows. If the timeout is hit in this + // case, the :ref:`tx_flush_timeout ` counter will be + // incremented. Note that :ref:`max_stream_duration + // ` does not apply to this corner + // case. + // // Note that it is possible to idle timeout even if the wire traffic for a stream is non-idle, due // to the granularity of events presented to the connection manager. For example, while receiving // very large request headers, it may be the case that there is traffic regularly arriving on the @@ -487,17 +497,17 @@ message HttpConnectionManager { // true in the future. When not specified, this value may be overridden by the // runtime variable // :ref:`http_connection_manager.normalize_path`. - // See `Normalization and Comparison ` + // See `Normalization and Comparison `_ // for details of normalization. // Note that Envoy does not perform - // `case normalization ` + // `case normalization `_ google.protobuf.BoolValue normalize_path = 30; // Determines if adjacent slashes in the path are merged into one before any processing of // requests by HTTP filters or routing. This affects the upstream *:path* header as well. Without // setting this option, incoming requests with path `//dir///file` will not match against route // with `prefix` match set to `/dir`. Defaults to `false`. Note that slash merging is not part of - // `HTTP spec ` and is provided for convenience. + // `HTTP spec `_ and is provided for convenience. bool merge_slashes = 33; // The configuration of the request ID extension. This includes operations such as diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/listener/v3/listener.proto b/xds/third_party/envoy/src/main/proto/envoy/config/listener/v3/listener.proto index 03214150e77..ab0b0ecac7c 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/listener/v3/listener.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/listener/v3/listener.proto @@ -14,6 +14,8 @@ import "google/api/annotations.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/wrappers.proto"; +import "udpa/core/v1/collection_entry.proto"; + import "udpa/annotations/security.proto"; import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; @@ -27,6 +29,12 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Listener configuration] // Listener :ref:`configuration overview ` +// Listener list collections. Entries are *Listener* resources or references. +// [#not-implemented-hide:] +message ListenerCollection { + udpa.core.v1.CollectionEntry entries = 1; +} + // [#next-free-field: 23] message Listener { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.Listener"; diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/route/v3/route_components.proto b/xds/third_party/envoy/src/main/proto/envoy/config/route/v3/route_components.proto index f927f582bd1..c35e210691c 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/config/route/v3/route_components.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/config/route/v3/route_components.proto @@ -7,6 +7,7 @@ import "envoy/config/core/v3/extension.proto"; import "envoy/config/core/v3/proxy_protocol.proto"; import "envoy/type/matcher/v3/regex.proto"; import "envoy/type/matcher/v3/string.proto"; +import "envoy/type/metadata/v3/metadata.proto"; import "envoy/type/tracing/v3/custom_tag.proto"; import "envoy/type/v3/percent.proto"; import "envoy/type/v3/range.proto"; @@ -17,6 +18,7 @@ import "google/protobuf/struct.proto"; import "google/protobuf/wrappers.proto"; import "envoy/annotations/deprecation.proto"; +import "udpa/annotations/migrate.proto"; import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; @@ -125,7 +127,9 @@ message VirtualHost { // Specifies a list of HTTP headers that should be removed from each response // handled by this virtual host. - repeated string response_headers_to_remove = 11; + repeated string response_headers_to_remove = 11 [(validate.rules).repeated = { + items {string {min_bytes: 1 well_known_regex: HTTP_HEADER_NAME strict: false}} + }]; // Indicates that the virtual host has a CORS policy. CorsPolicy cors = 8; @@ -225,6 +229,8 @@ message Route { // [#not-implemented-hide:] // If true, a filter will define the action (e.g., it could dynamically generate the // RouteAction). + // [#comment: TODO(samflattery): Remove cleanup in route_fuzz_test.cc when + // implemented] FilterAction filter_action = 17; } @@ -271,7 +277,9 @@ message Route { // Specifies a list of HTTP headers that should be removed from each response // to requests matching this route. - repeated string response_headers_to_remove = 11; + repeated string response_headers_to_remove = 11 [(validate.rules).repeated = { + items {string {min_bytes: 1 well_known_regex: HTTP_HEADER_NAME strict: false}} + }]; // Presence of the object defines whether the connection manager's tracing configuration // is overridden by this route specific instance. @@ -611,6 +619,10 @@ message RouteAction { string header_name = 1 [ (validate.rules).string = {min_bytes: 1 well_known_regex: HTTP_HEADER_NAME strict: false} ]; + + // If specified, the request header value will be rewritten and used + // to produce the hash key. + type.matcher.v3.RegexMatchAndSubstitute regex_rewrite = 2; } // Envoy supports two types of cookie affinity: @@ -1083,7 +1095,8 @@ message RetryPolicy { // Specifies the allowed number of retries. This parameter is optional and // defaults to 1. These are the same conditions documented for // :ref:`config_http_filters_router_x-envoy-max-retries`. - google.protobuf.UInt32Value num_retries = 2; + google.protobuf.UInt32Value num_retries = 2 + [(udpa.annotations.field_migrate).rename = "max_retries"]; // Specifies a non-zero upstream timeout per retry attempt. This parameter is optional. The // same conditions documented for @@ -1203,6 +1216,21 @@ message RedirectAction { oneof path_rewrite_specifier { // The path portion of the URL will be swapped with this value. + // Please note that query string in path_redirect will override the + // request's query string and will not be stripped. + // + // For example, let's say we have the following routes: + // + // - match: { path: "/old-path-1" } + // redirect: { path_redirect: "/new-path-1" } + // - match: { path: "/old-path-2" } + // redirect: { path_redirect: "/new-path-2", strip-query: "true" } + // - match: { path: "/old-path-3" } + // redirect: { path_redirect: "/new-path-3?foo=1", strip_query: "true" } + // + // 1. if request uri is "/old-path-1?bar=1", users will be redirected to "/new-path-1?bar=1" + // 2. if request uri is "/old-path-2?bar=1", users will be redirected to "/new-path-2" + // 3. if request uri is "/old-path-3?bar=1", users will be redirected to "/new-path-3?foo=1" string path_redirect = 2 [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}]; @@ -1338,7 +1366,7 @@ message VirtualCluster { message RateLimit { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.RateLimit"; - // [#next-free-field: 7] + // [#next-free-field: 8] message Action { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.RateLimit.Action"; @@ -1452,6 +1480,24 @@ message RateLimit { repeated HeaderMatcher headers = 3 [(validate.rules).repeated = {min_items: 1}]; } + // The following descriptor entry is appended when the dynamic metadata contains a key value: + // + // .. code-block:: cpp + // + // ("", "") + message DynamicMetaData { + // The key to use in the descriptor entry. + string descriptor_key = 1 [(validate.rules).string = {min_bytes: 1}]; + + // Metadata struct that defines the key and path to retrieve the string value. A match will + // only happen if the value in the dynamic metadata is of type string. + type.metadata.v3.MetadataKey metadata_key = 2 [(validate.rules).message = {required: true}]; + + // An optional value to use if *metadata_key* is empty. If not set and + // no value is present under the metadata_key then no descriptor is generated. + string default_value = 3; + } + oneof action_specifier { option (validate.required) = true; @@ -1472,6 +1518,27 @@ message RateLimit { // Rate limit on the existence of request headers. HeaderValueMatch header_value_match = 6; + + // Rate limit on dynamic metadata. + DynamicMetaData dynamic_metadata = 7; + } + } + + message Override { + // Fetches the override from the dynamic metadata. + message DynamicMetadata { + // Metadata struct that defines the key and path to retrieve the struct value. + // The value must be a struct containing an integer "requests_per_unit" property + // and a "unit" property with a value parseable to :ref:`RateLimitUnit + // enum ` + type.metadata.v3.MetadataKey metadata_key = 1 [(validate.rules).message = {required: true}]; + } + + oneof override_specifier { + option (validate.required) = true; + + // Limit override from dynamic metadata. + DynamicMetadata dynamic_metadata = 1; } } @@ -1494,6 +1561,12 @@ message RateLimit { // configuration. See :ref:`composing actions // ` for additional documentation. repeated Action actions = 3 [(validate.rules).repeated = {min_items: 1}]; + + // An optional limit override to be appended to the descriptor produced by this + // rate limit configuration. If the override value is invalid or cannot be resolved + // from metadata, no override is provided. See :ref:`rate limit override + // ` for more information. + Override limit = 4; } // .. attention:: diff --git a/xds/third_party/envoy/src/main/proto/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto b/xds/third_party/envoy/src/main/proto/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto index ff083e29228..04a132ad267 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -3,8 +3,11 @@ syntax = "proto3"; package envoy.extensions.filters.network.http_connection_manager.v3; import "envoy/config/accesslog/v3/accesslog.proto"; +import "envoy/config/core/v3/base.proto"; import "envoy/config/core/v3/config_source.proto"; +import "envoy/config/core/v3/extension.proto"; import "envoy/config/core/v3/protocol.proto"; +import "envoy/config/core/v3/substitution_format_string.proto"; import "envoy/config/route/v3/route.proto"; import "envoy/config/route/v3/scoped_route.proto"; import "envoy/config/trace/v3/http_tracer.proto"; @@ -16,7 +19,11 @@ import "google/protobuf/duration.proto"; import "google/protobuf/struct.proto"; import "google/protobuf/wrappers.proto"; +import "udpa/core/v1/resource_locator.proto"; + import "envoy/annotations/deprecation.proto"; +import "udpa/annotations/migrate.proto"; +import "udpa/annotations/security.proto"; import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; import "validate/validate.proto"; @@ -30,7 +37,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // HTTP connection manager :ref:`configuration overview `. // [#extension: envoy.filters.network.http_connection_manager] -// [#next-free-field: 40] +// [#next-free-field: 41] message HttpConnectionManager { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager"; @@ -279,13 +286,15 @@ message HttpConnectionManager { // Additional settings for HTTP requests handled by the connection manager. These will be // applicable to both HTTP1 and HTTP2 requests. - config.core.v3.HttpProtocolOptions common_http_protocol_options = 35; + config.core.v3.HttpProtocolOptions common_http_protocol_options = 35 + [(udpa.annotations.security).configure_for_untrusted_downstream = true]; // Additional HTTP/1 settings that are passed to the HTTP/1 codec. config.core.v3.Http1ProtocolOptions http_protocol_options = 8; // Additional HTTP/2 settings that are passed directly to the HTTP/2 codec. - config.core.v3.Http2ProtocolOptions http2_protocol_options = 9; + config.core.v3.Http2ProtocolOptions http2_protocol_options = 9 + [(udpa.annotations.security).configure_for_untrusted_downstream = true]; // An optional override that the connection manager will write to the server // header in responses. If not set, the default is *envoy*. @@ -322,6 +331,16 @@ message HttpConnectionManager { // is terminated with a 408 Request Timeout error code if no upstream response // header has been received, otherwise a stream reset occurs. // + // This timeout also specifies the amount of time that Envoy will wait for the peer to open enough + // window to write any remaining stream data once the entirety of stream data (local end stream is + // true) has been buffered pending available window. In other words, this timeout defends against + // a peer that does not release enough window to completely write the stream, even though all + // data has been proxied within available flow control windows. If the timeout is hit in this + // case, the :ref:`tx_flush_timeout ` counter will be + // incremented. Note that :ref:`max_stream_duration + // ` does not apply to + // this corner case. + // // Note that it is possible to idle timeout even if the wire traffic for a stream is non-idle, due // to the granularity of events presented to the connection manager. For example, while receiving // very large request headers, it may be the case that there is traffic regularly arriving on the @@ -330,13 +349,15 @@ message HttpConnectionManager { // // A value of 0 will completely disable the connection manager stream idle // timeout, although per-route idle timeout overrides will continue to apply. - google.protobuf.Duration stream_idle_timeout = 24; + google.protobuf.Duration stream_idle_timeout = 24 + [(udpa.annotations.security).configure_for_untrusted_downstream = true]; // The amount of time that Envoy will wait for the entire request to be received. // The timer is activated when the request is initiated, and is disarmed when the last byte of the // request is sent upstream (i.e. all decoding filters have processed the request), OR when the // response is initiated. If not specified or set to 0, this timeout is disabled. - google.protobuf.Duration request_timeout = 28; + google.protobuf.Duration request_timeout = 28 + [(udpa.annotations.security).configure_for_untrusted_downstream = true]; // The time that Envoy will wait between sending an HTTP/2 “shutdown // notification” (GOAWAY frame with max stream ID) and a final GOAWAY frame. @@ -392,7 +413,8 @@ message HttpConnectionManager { // :ref:`config_http_conn_man_headers_x-forwarded-for`, // :ref:`config_http_conn_man_headers_x-envoy-internal`, and // :ref:`config_http_conn_man_headers_x-envoy-external-address` for more information. - google.protobuf.BoolValue use_remote_address = 14; + google.protobuf.BoolValue use_remote_address = 14 + [(udpa.annotations.security).configure_for_untrusted_downstream = true]; // The number of additional ingress proxy hops from the right side of the // :ref:`config_http_conn_man_headers_x-forwarded-for` HTTP header to trust when @@ -482,17 +504,17 @@ message HttpConnectionManager { // true in the future. When not specified, this value may be overridden by the // runtime variable // :ref:`http_connection_manager.normalize_path`. - // See `Normalization and Comparison ` + // See `Normalization and Comparison `_ // for details of normalization. // Note that Envoy does not perform - // `case normalization ` + // `case normalization `_ google.protobuf.BoolValue normalize_path = 30; // Determines if adjacent slashes in the path are merged into one before any processing of // requests by HTTP filters or routing. This affects the upstream *:path* header as well. Without // setting this option, incoming requests with path `//dir///file` will not match against route // with `prefix` match set to `/dir`. Defaults to `false`. Note that slash merging is not part of - // `HTTP spec ` and is provided for convenience. + // `HTTP spec `_ and is provided for convenience. bool merge_slashes = 33; // The configuration of the request ID extension. This includes operations such as @@ -507,13 +529,103 @@ message HttpConnectionManager { // 3. Tracing decision (sampled, forced, etc) is set in 14th byte of the UUID. RequestIDExtension request_id_extension = 36; + // The configuration to customize local reply returned by Envoy. It can customize status code, + // body text and response content type. If not specified, status code and text body are hard + // coded in Envoy, the response content type is plain text. + LocalReplyConfig local_reply_config = 38; + // Determines if the port part should be removed from host/authority header before any processing // of request by HTTP filters or routing. The port would be removed only if it is equal to the :ref:`listener's` // local port and request method is not CONNECT. This affects the upstream host header as well. // Without setting this option, incoming requests with host `example:443` will not match against // route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part - // of `HTTP spec ` and is provided for convenience. + // of `HTTP spec `_ and is provided for convenience. bool strip_matching_host_port = 39; + + // Governs Envoy's behavior when receiving invalid HTTP from downstream. + // If this option is false (default), Envoy will err on the conservative side handling HTTP + // errors, terminating both HTTP/1.1 and HTTP/2 connections when receiving an invalid request. + // If this option is set to true, Envoy will be more permissive, only resetting the invalid + // stream in the case of HTTP/2 and leaving the connection open where possible (if the entire + // request is read for HTTP/1.1) + // In general this should be true for deployments receiving trusted traffic (L2 Envoys, + // company-internal mesh) and false when receiving untrusted traffic (edge deployments). + // + // If different behaviors for invalid_http_message for HTTP/1 and HTTP/2 are + // desired, one *must* use the new HTTP/2 option + // :ref:`override_stream_error_on_invalid_http_message + // ` + // *not* the deprecated but similarly named :ref:`stream_error_on_invalid_http_messaging + // ` + google.protobuf.BoolValue stream_error_on_invalid_http_message = 40; +} + +// The configuration to customize local reply returned by Envoy. +message LocalReplyConfig { + // Configuration of list of mappers which allows to filter and change local response. + // The mappers will be checked by the specified order until one is matched. + repeated ResponseMapper mappers = 1; + + // The configuration to form response body from the :ref:`command operators ` + // and to specify response content type as one of: plain/text or application/json. + // + // Example one: plain/text body_format. + // + // .. code-block:: + // + // text_format: %LOCAL_REPLY_BODY%:%RESPONSE_CODE%:path=$REQ(:path)% + // + // The following response body in `plain/text` format will be generated for a request with + // local reply body of "upstream connection error", response_code=503 and path=/foo. + // + // .. code-block:: + // + // upstream connect error:503:path=/foo + // + // Example two: application/json body_format. + // + // .. code-block:: + // + // json_format: + // status: %RESPONSE_CODE% + // message: %LOCAL_REPLY_BODY% + // path: $REQ(:path)% + // + // The following response body in "application/json" format would be generated for a request with + // local reply body of "upstream connection error", response_code=503 and path=/foo. + // + // .. code-block:: json + // + // { + // "status": 503, + // "message": "upstream connection error", + // "path": "/foo" + // } + // + config.core.v3.SubstitutionFormatString body_format = 2; +} + +// The configuration to filter and change local response. +// [#next-free-field: 6] +message ResponseMapper { + // Filter to determine if this mapper should apply. + config.accesslog.v3.AccessLogFilter filter = 1 [(validate.rules).message = {required: true}]; + + // The new response status code if specified. + google.protobuf.UInt32Value status_code = 2 [(validate.rules).uint32 = {lt: 600 gte: 200}]; + + // The new local reply body text if specified. It will be used in the `%LOCAL_REPLY_BODY%` + // command operator in the `body_format`. + config.core.v3.DataSource body = 3; + + // A per mapper `body_format` to override the :ref:`body_format `. + // It will be used when this mapper is matched. + config.core.v3.SubstitutionFormatString body_format_override = 4; + + // HTTP headers to add to a local reply. This allows the response mapper to append, to add + // or to override headers of any local reply before it is sent to a downstream client. + repeated config.core.v3.HeaderValueOption headers_to_add = 5 + [(validate.rules).repeated = {max_items: 1000}]; } message Rds { @@ -527,7 +639,13 @@ message Rds { // API. This allows an Envoy configuration with multiple HTTP listeners (and // associated HTTP connection manager filters) to use different route // configurations. - string route_config_name = 2 [(validate.rules).string = {min_bytes: 1}]; + string route_config_name = 2 + [(udpa.annotations.field_migrate).oneof_promotion = "name_specifier"]; + + // Resource locator for RDS. This is mutually exclusive to *route_config_name*. + // [#not-implemented-hide:] + udpa.core.v1.ResourceLocator rds_resource_locator = 3 + [(udpa.annotations.field_migrate).oneof_promotion = "name_specifier"]; } // This message is used to work around the limitations with 'oneof' and repeated fields. @@ -675,6 +793,7 @@ message ScopedRds { [(validate.rules).message = {required: true}]; } +// [#next-free-field: 6] message HttpFilter { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.network.http_connection_manager.v2.HttpFilter"; @@ -683,14 +802,20 @@ message HttpFilter { reserved "config"; - // The name of the filter to instantiate. The name must match a - // :ref:`supported filter `. + // The name of the filter configuration. The name is used as a fallback to + // select an extension if the type of the configuration proto is not + // sufficient. It also serves as a resource name in ExtensionConfigDS. string name = 1 [(validate.rules).string = {min_bytes: 1}]; // Filter specific configuration which depends on the filter being instantiated. See the supported // filters for further documentation. oneof config_type { google.protobuf.Any typed_config = 4; + + // Configuration source specifier for an extension configuration discovery service. + // In case of a failure and without the default configuration, the HTTP listener responds with 500. + // Extension configs delivered through this mechanism are not expected to require warming (see https://github.com/envoyproxy/envoy/issues/12061). + config.core.v3.ExtensionConfigSource config_discovery = 5; } } diff --git a/xds/third_party/envoy/src/main/proto/envoy/extensions/transport_sockets/tls/v3/common.proto b/xds/third_party/envoy/src/main/proto/envoy/extensions/transport_sockets/tls/v3/common.proto index b468f5b7e41..115ecad72f9 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/extensions/transport_sockets/tls/v3/common.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/extensions/transport_sockets/tls/v3/common.proto @@ -45,8 +45,8 @@ message TlsParameters { // servers. TlsProtocol tls_minimum_protocol_version = 1 [(validate.rules).enum = {defined_only: true}]; - // Maximum TLS protocol version. By default, it's ``TLSv1_3`` for servers in non-FIPS builds, and - // ``TLSv1_2`` for clients and for servers using :ref:`BoringSSL FIPS `. + // Maximum TLS protocol version. By default, it's ``TLSv1_2`` for clients and ``TLSv1_3`` for + // servers. TlsProtocol tls_maximum_protocol_version = 2 [(validate.rules).enum = {defined_only: true}]; // If specified, the TLS listener will only support the specified `cipher list diff --git a/xds/third_party/envoy/src/main/proto/envoy/extensions/transport_sockets/tls/v3/secret.proto b/xds/third_party/envoy/src/main/proto/envoy/extensions/transport_sockets/tls/v3/secret.proto index 2a77ec765c8..80c68a56f5c 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/extensions/transport_sockets/tls/v3/secret.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/extensions/transport_sockets/tls/v3/secret.proto @@ -6,6 +6,9 @@ import "envoy/config/core/v3/base.proto"; import "envoy/config/core/v3/config_source.proto"; import "envoy/extensions/transport_sockets/tls/v3/common.proto"; +import "udpa/core/v1/resource_locator.proto"; + +import "udpa/annotations/migrate.proto"; import "udpa/annotations/sensitive.proto"; import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; @@ -30,7 +33,12 @@ message SdsSecretConfig { // Name (FQDN, UUID, SPKI, SHA256, etc.) by which the secret can be uniquely referred to. // When both name and config are specified, then secret can be fetched and/or reloaded via // SDS. When only name is specified, then secret will be loaded from static resources. - string name = 1; + string name = 1 [(udpa.annotations.field_migrate).oneof_promotion = "name_specifier"]; + + // Resource locator for SDS. This is mutually exclusive to *name*. + // [#not-implemented-hide:] + udpa.core.v1.ResourceLocator sds_resource_locator = 3 + [(udpa.annotations.field_migrate).oneof_promotion = "name_specifier"]; config.core.v3.ConfigSource sds_config = 2; } diff --git a/xds/third_party/envoy/src/main/proto/envoy/extensions/transport_sockets/tls/v3/tls.proto b/xds/third_party/envoy/src/main/proto/envoy/extensions/transport_sockets/tls/v3/tls.proto index 1806a44666e..7ee7920c724 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/extensions/transport_sockets/tls/v3/tls.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/extensions/transport_sockets/tls/v3/tls.proto @@ -99,7 +99,7 @@ message DownstreamTlsContext { } // TLS context shared by both client and server TLS contexts. -// [#next-free-field: 11] +// [#next-free-field: 13] message CommonTlsContext { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.auth.CommonTlsContext"; @@ -123,6 +123,26 @@ message CommonTlsContext { } } + // Similar to CertificateProvider above, but allows the provider instances to be configured on + // the client side instead of being sent from the control plane. + message CertificateProviderInstance { + // Provider instance name. This name must be defined in the client's configuration (e.g., a + // bootstrap file) to correspond to a provider instance (i.e., the same data in the typed_config + // field that would be sent in the CertificateProvider message if the config was sent by the + // control plane). If not present, defaults to "default". + // + // Instance names should generally be defined not in terms of the underlying provider + // implementation (e.g., "file_watcher") but rather in terms of the function of the + // certificates (e.g., "foo_deployment_identity"). + string instance_name = 1; + + // Opaque name used to specify certificate instances or types. For example, "ROOTCA" to specify + // a root-certificate (validation context) or "example.com" to specify a certificate for a + // particular domain. Not all provider instances will actually use this field, so the value + // defaults to the empty string. + string certificate_name = 2; + } + message CombinedCertificateValidationContext { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.auth.CommonTlsContext.CombinedCertificateValidationContext"; @@ -133,17 +153,26 @@ message CommonTlsContext { // Config for fetching validation context via SDS API. Note SDS API allows certificates to be // fetched/refreshed over the network asynchronously with respect to the TLS handshake. - // Only to be used when validation_context_certificate_provider is not used. + // Only one of validation_context_sds_secret_config, validation_context_certificate_provider, + // or validation_context_certificate_provider_instance may be used. SdsSecretConfig validation_context_sds_secret_config = 2 [ (validate.rules).message = {required: true}, (udpa.annotations.field_migrate).oneof_promotion = "dynamic_validation_context" ]; - // Certificate provider for fetching validation context - only to be used when - // validation_context_sds_secret_config is not used. + // Certificate provider for fetching validation context. + // Only one of validation_context_sds_secret_config, validation_context_certificate_provider, + // or validation_context_certificate_provider_instance may be used. // [#not-implemented-hide:] CertificateProvider validation_context_certificate_provider = 3 [(udpa.annotations.field_migrate).oneof_promotion = "dynamic_validation_context"]; + + // Certificate provider instance for fetching validation context. + // Only one of validation_context_sds_secret_config, validation_context_certificate_provider, + // or validation_context_certificate_provider_instance may be used. + // [#not-implemented-hide:] + CertificateProviderInstance validation_context_certificate_provider_instance = 4 + [(udpa.annotations.field_migrate).oneof_promotion = "dynamic_validation_context"]; } reserved 5; @@ -168,6 +197,10 @@ message CommonTlsContext { // [#not-implemented-hide:] CertificateProvider tls_certificate_certificate_provider = 9; + // Certificate provider instance for fetching TLS certificates. + // [#not-implemented-hide:] + CertificateProviderInstance tls_certificate_certificate_provider_instance = 11; + oneof validation_context_type { // How to validate peer certificates. CertificateValidationContext validation_context = 3; @@ -188,6 +221,10 @@ message CommonTlsContext { // Certificate provider for fetching validation context. // [#not-implemented-hide:] CertificateProvider validation_context_certificate_provider = 10; + + // Certificate provider instance for fetching validation context. + // [#not-implemented-hide:] + CertificateProviderInstance validation_context_certificate_provider_instance = 12; } // Supplies the list of ALPN protocols that the listener should expose. In diff --git a/xds/third_party/envoy/src/main/proto/envoy/service/discovery/v3/discovery.proto b/xds/third_party/envoy/src/main/proto/envoy/service/discovery/v3/discovery.proto index b8e31160a88..40479539213 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/service/discovery/v3/discovery.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/service/discovery/v3/discovery.proto @@ -7,6 +7,10 @@ import "envoy/config/core/v3/base.proto"; import "google/protobuf/any.proto"; import "google/rpc/status.proto"; +import "udpa/core/v1/resource_locator.proto"; +import "udpa/core/v1/resource_name.proto"; + +import "udpa/annotations/migrate.proto"; import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; @@ -140,7 +144,7 @@ message DiscoveryResponse { // In particular, initial_resource_versions being sent at the "start" of every // gRPC stream actually entails a message for each type_url, each with its own // initial_resource_versions. -// [#next-free-field: 8] +// [#next-free-field: 10] message DeltaDiscoveryRequest { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.DeltaDiscoveryRequest"; @@ -148,7 +152,9 @@ message DeltaDiscoveryRequest { config.core.v3.Node node = 1; // Type of the resource that is being requested, e.g. - // "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment". + // "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment". This does not need to be set if + // resources are only referenced via *udpa_resource_subscribe* and + // *udpa_resources_unsubscribe*. string type_url = 2; // DeltaDiscoveryRequests allow the client to add or remove individual @@ -174,9 +180,22 @@ message DeltaDiscoveryRequest { // A list of Resource names to add to the list of tracked resources. repeated string resource_names_subscribe = 3; + // As with *resource_names_subscribe* but used when subscribing to resources indicated + // by a *udpa.core.v1.ResourceLocator*. The directives in the resource locator + // are ignored and the context parameters are matched with + // *context_param_specifier* specific semantics. + // [#not-implemented-hide:] + repeated udpa.core.v1.ResourceLocator udpa_resources_subscribe = 8; + // A list of Resource names to remove from the list of tracked resources. repeated string resource_names_unsubscribe = 4; + // As with *resource_names_unsubscribe* but used when unsubscribing to resources indicated by a + // *udpa.core.v1.ResourceLocator*. This must match a previously subscribed + // resource locator provided in *udpa_resources_subscribe*. + // [#not-implemented-hide:] + repeated udpa.core.v1.ResourceLocator udpa_resources_unsubscribe = 9; + // Informs the server of the versions of the resources the xDS client knows of, to enable the // client to continue the same logical xDS session even in the face of gRPC stream reconnection. // It will not be populated: [1] in the very first stream of a session, since the client will @@ -199,7 +218,7 @@ message DeltaDiscoveryRequest { google.rpc.Status error_detail = 7; } -// [#next-free-field: 7] +// [#next-free-field: 8] message DeltaDiscoveryResponse { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.DeltaDiscoveryResponse"; @@ -215,22 +234,34 @@ message DeltaDiscoveryResponse { // Type URL for resources. Identifies the xDS API when muxing over ADS. // Must be consistent with the type_url in the Any within 'resources' if 'resources' is non-empty. + // This does not need to be set if *udpa_removed_resources* is used instead of + // *removed_resources*. string type_url = 4; // Resources names of resources that have be deleted and to be removed from the xDS Client. // Removed resources for missing resources can be ignored. repeated string removed_resources = 6; + // As with *removed_resources* but used when a removed resource was named in + // its *Resource*s with a *udpa.core.v1.ResourceName*. + // [#not-implemented-hide:] + repeated udpa.core.v1.ResourceName udpa_removed_resources = 7; + // The nonce provides a way for DeltaDiscoveryRequests to uniquely // reference a DeltaDiscoveryResponse when (N)ACKing. The nonce is required. string nonce = 5; } +// [#next-free-field: 6] message Resource { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.Resource"; // The resource's name, to distinguish it from others of the same type of resource. - string name = 3; + string name = 3 [(udpa.annotations.field_migrate).oneof_promotion = "name_specifier"]; + + // Used instead of *name* when a resource with a *udpa.core.v1.ResourceName* is delivered. + udpa.core.v1.ResourceName udpa_resource_name = 5 + [(udpa.annotations.field_migrate).oneof_promotion = "name_specifier"]; // The aliases are a list of other names that this resource can go by. repeated string aliases = 4; diff --git a/xds/third_party/envoy/src/main/proto/envoy/type/matcher/regex.proto b/xds/third_party/envoy/src/main/proto/envoy/type/matcher/regex.proto index 9e41637ab70..b23c0bff307 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/type/matcher/regex.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/type/matcher/regex.proto @@ -19,6 +19,16 @@ message RegexMatcher { // Google's `RE2 `_ regex engine. The regex string must adhere to // the documented `syntax `_. The engine is designed // to complete execution in linear time as well as limit the amount of memory used. + // + // Envoy supports program size checking via runtime. The runtime keys `re2.max_program_size.error_level` + // and `re2.max_program_size.warn_level` can be set to integers as the maximum program size or + // complexity that a compiled regex can have before an exception is thrown or a warning is + // logged, respectively. `re2.max_program_size.error_level` defaults to 100, and + // `re2.max_program_size.warn_level` has no default if unset (will not check/log a warning). + // + // Envoy emits two stats for tracking the program size of regexes: the histogram `re2.program_size`, + // which records the program size, and the counter `re2.exceeded_warn_level`, which is incremented + // each time the program size exceeds the warn level threshold. message GoogleRE2 { // This field controls the RE2 "program size" which is a rough estimate of how complex a // compiled regex is to evaluate. A regex that has a program size greater than the configured diff --git a/xds/third_party/envoy/src/main/proto/envoy/type/matcher/v3/regex.proto b/xds/third_party/envoy/src/main/proto/envoy/type/matcher/v3/regex.proto index e318cb5457d..6087c6f90fa 100644 --- a/xds/third_party/envoy/src/main/proto/envoy/type/matcher/v3/regex.proto +++ b/xds/third_party/envoy/src/main/proto/envoy/type/matcher/v3/regex.proto @@ -22,6 +22,16 @@ message RegexMatcher { // Google's `RE2 `_ regex engine. The regex string must adhere to // the documented `syntax `_. The engine is designed // to complete execution in linear time as well as limit the amount of memory used. + // + // Envoy supports program size checking via runtime. The runtime keys `re2.max_program_size.error_level` + // and `re2.max_program_size.warn_level` can be set to integers as the maximum program size or + // complexity that a compiled regex can have before an exception is thrown or a warning is + // logged, respectively. `re2.max_program_size.error_level` defaults to 100, and + // `re2.max_program_size.warn_level` has no default if unset (will not check/log a warning). + // + // Envoy emits two stats for tracking the program size of regexes: the histogram `re2.program_size`, + // which records the program size, and the counter `re2.exceeded_warn_level`, which is incremented + // each time the program size exceeds the warn level threshold. message GoogleRE2 { option (udpa.annotations.versioning).previous_message_type = "envoy.type.matcher.RegexMatcher.GoogleRE2"; @@ -62,7 +72,7 @@ message RegexMatchAndSubstitute { // so as to replace just one occurrence of a pattern. Capture groups can be // used in the pattern to extract portions of the subject string, and then // referenced in the substitution string. - RegexMatcher pattern = 1; + RegexMatcher pattern = 1 [(validate.rules).message = {required: true}]; // The string that should be substituted into matching portions of the // subject string during a substitution operation to produce a new string. diff --git a/xds/third_party/udpa/import.sh b/xds/third_party/udpa/import.sh index a625579d293..f06a4f6c920 100755 --- a/xds/third_party/udpa/import.sh +++ b/xds/third_party/udpa/import.sh @@ -18,7 +18,7 @@ set -e BRANCH=master # import VERSION from one of the google internal CLs -VERSION=3b31d022a144b334eb2224838e4d6952ab5253aa +VERSION=efcf912fb35470672231c7b7bef620f3d17f655a GIT_REPO="https://github.com/cncf/udpa.git" GIT_BASE_DIR=udpa SOURCE_PROTO_BASE_DIR=udpa @@ -29,6 +29,11 @@ udpa/annotations/security.proto udpa/annotations/sensitive.proto udpa/annotations/status.proto udpa/annotations/versioning.proto +udpa/core/v1/authority.proto +udpa/core/v1/collection_entry.proto +udpa/core/v1/context_params.proto +udpa/core/v1/resource_locator.proto +udpa/core/v1/resource_name.proto udpa/data/orca/v1/orca_load_report.proto udpa/service/orca/v1/orca.proto ) diff --git a/xds/third_party/udpa/src/main/proto/udpa/core/v1/authority.proto b/xds/third_party/udpa/src/main/proto/udpa/core/v1/authority.proto new file mode 100644 index 00000000000..f250a6cb0e3 --- /dev/null +++ b/xds/third_party/udpa/src/main/proto/udpa/core/v1/authority.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +package udpa.core.v1; + +import "udpa/annotations/status.proto"; + +import "validate/validate.proto"; + +option java_outer_classname = "AuthorityProto"; +option java_multiple_files = true; +option java_package = "com.github.udpa.udpa.core.v1"; + +option (udpa.annotations.file_status).work_in_progress = true; + +// UDPA authority information. +message Authority { + string name = 1 [(validate.rules).string = {min_len: 1}]; + + // .. space reserved for additional authority addressing information, e.g. for + // resource signing, items such as CA trust chain, cert pinning may be added. +} diff --git a/xds/third_party/udpa/src/main/proto/udpa/core/v1/collection_entry.proto b/xds/third_party/udpa/src/main/proto/udpa/core/v1/collection_entry.proto new file mode 100644 index 00000000000..8e4f0c87486 --- /dev/null +++ b/xds/third_party/udpa/src/main/proto/udpa/core/v1/collection_entry.proto @@ -0,0 +1,52 @@ +syntax = "proto3"; + +package udpa.core.v1; + +import "google/protobuf/any.proto"; + +import "udpa/annotations/status.proto"; +import "udpa/core/v1/resource_locator.proto"; + +import "validate/validate.proto"; + +option java_outer_classname = "CollectionEntryProto"; +option java_multiple_files = true; +option java_package = "com.github.udpa.udpa.core.v1"; + +option (udpa.annotations.file_status).work_in_progress = true; + +// UDPA collection resource wrapper. This encapsulates a UDPA resource when +// appearing inside a list collection resource. List collection resources are +// regular Resource messages of type: +// +// message Collection { +// repeated CollectionEntry resources = 1; +// } +// +message CollectionEntry { + // Inlined resource entry. + message InlineEntry { + // Optional name to describe the inlined resource. Resource names must + // [a-zA-Z0-9_-\./]+ (TODO(htuch): turn this into a PGV constraint once + // finalized, probably should be a RFC3986 pchar). This name allows + // reference via the #entry directive in ResourceLocator. + string name = 1 [(validate.rules).string.pattern = "^[0-9a-zA-Z_\\-\\.~:]+$"]; + + // The resource's logical version. It is illegal to have the same named UDPA + // resource name at a given version with different resource payloads. + string version = 2; + + // The resource payload, including type URL. + google.protobuf.Any resource = 3; + } + + oneof resource_specifier { + option (validate.required) = true; + + // A resource locator describing how the member resource is to be located. + ResourceLocator locator = 1; + + // The resource is inlined in the list collection. + InlineEntry inline_entry = 2; + } +} diff --git a/xds/third_party/udpa/src/main/proto/udpa/core/v1/context_params.proto b/xds/third_party/udpa/src/main/proto/udpa/core/v1/context_params.proto new file mode 100644 index 00000000000..ccc4638f06c --- /dev/null +++ b/xds/third_party/udpa/src/main/proto/udpa/core/v1/context_params.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package udpa.core.v1; + +import "udpa/annotations/status.proto"; + +option java_outer_classname = "ContextParamsProto"; +option java_multiple_files = true; +option java_package = "com.github.udpa.udpa.core.v1"; + +option (udpa.annotations.file_status).work_in_progress = true; + +// Additional parameters that can be used to select resource variants. +message ContextParams { + map params = 1; +} diff --git a/xds/third_party/udpa/src/main/proto/udpa/core/v1/resource_locator.proto b/xds/third_party/udpa/src/main/proto/udpa/core/v1/resource_locator.proto new file mode 100644 index 00000000000..a3c903144cc --- /dev/null +++ b/xds/third_party/udpa/src/main/proto/udpa/core/v1/resource_locator.proto @@ -0,0 +1,117 @@ +syntax = "proto3"; + +package udpa.core.v1; + +import "udpa/annotations/status.proto"; +import "udpa/core/v1/context_params.proto"; + +import "validate/validate.proto"; + +option java_outer_classname = "ResourceLocatorProto"; +option java_multiple_files = true; +option java_package = "com.github.udpa.udpa.core.v1"; + +option (udpa.annotations.file_status).work_in_progress = true; + +// UDPA resource locators identify a UDPA resource name and instruct the +// data-plane load balancer on how the resource may be located. +// +// Resource locators have a canonical udpa:// URI representation: +// +// udpa://{authority}/{type_url}/{id/*}?{context_params}{#directive,*} +// +// where context_params take the form of URI query parameters. +// +// Resource locators have a similar canonical http:// URI representation: +// +// http://{authority}/{type_url}/{id/*}?{context_params}{#directive,*} +// +// Resource locators also have a simplified file:// URI representation: +// +// file:///{id/*}{#directive,*} +// +message ResourceLocator { + enum Scheme { + UDPA = 0; + HTTP = 1; + FILE = 2; + } + + // URI scheme. + Scheme scheme = 1 [(validate.rules).enum = {defined_only: true}]; + + // Opaque identifiers for the resource. These are effectively concatenated + // with ‘/’ to form the non-query param path as resource ID. This may end + // with ‘*’ for glob collection references. + repeated string id = 2 [(validate.rules).repeated = {min_items: 1}]; + + // Logical authority for resource (not necessarily transport network address). + // Authorities are opaque in the UDPA API, data-plane load balancers will map + // them to concrete network transports such as an xDS management server, e.g. + // via envoy.config.core.v3.ConfigSource. + string authority = 3; + + // Fully qualified resource type (as in type URL without types.googleapis.com/ + // prefix). + string resource_type = 4 [(validate.rules).string = {min_len: 1}]; + + oneof context_param_specifier { + // Additional parameters that can be used to select resource variants. + // Matches must be exact, i.e. all context parameters must match exactly and + // there must be no additional context parameters set on the matched + // resource. + ContextParams exact_context = 5; + + // .. space reserved for future potential matchers, e.g. CEL expressions. + } + + // Directives provide information to data-plane load balancers on how UDPA + // resource names are to be interpreted and potentially further resolved. For + // example, they may provide alternative resource locators for when primary + // resolution fails. Directives are not part of resource names and do not + // appear in a xDS transport discovery request. + // + // When encoding to URIs, directives take the form: + // + // = + // + // For example, we can have alt=udpa://foo/bar or entry=some%20thing. Each + // directive value type may have its own string encoding, in the case of + // ResourceLocator there is a recursive URI encoding. + // + // Percent encoding applies to the URI encoding of the directive value. + // Multiple directives are comma-separated, so the reserved characters that + // require percent encoding in a directive value are [',', '#', '[', ']', + // '%']. These are the RFC3986 fragment reserved characters with the addition + // of the UDPA scheme specific ','. See + // https://tools.ietf.org/html/rfc3986#page-49 for further details on URI ABNF + // and reserved characters. + message Directive { + oneof directive { + option (validate.required) = true; + + // An alternative resource locator for fallback if the resource is + // unavailable. For example, take the resource locator: + // + // udpa://foo/some-type/some-route-table#alt=udpa://bar/some-type/another-route-table + // + // If the data-plane load balancer is unable to reach `foo` to fetch the + // resource, it will fallback to `bar`. Alternative resources do not need + // to have equivalent content, but they should be functional substitutes. + ResourceLocator alt = 1; + + // List collections support inlining of resources via the entry field in + // Resource. These inlined Resource objects may have an optional name + // field specified. When specified, the entry directive allows + // UdpaResourceLocator to directly reference these inlined resources, e.g. + // udpa://.../foo#entry=bar. + string entry = 2 [(validate.rules).string = {min_len: 1, pattern: "^[0-9a-zA-Z_\\-\\./~:]+$"}]; + } + } + + // A list of directives that appear in the UDPA resource locator #fragment. + // + // When encoding to URI form, directives are percent encoded with comma + // separation. + repeated Directive directives = 6; +} diff --git a/xds/third_party/udpa/src/main/proto/udpa/core/v1/resource_name.proto b/xds/third_party/udpa/src/main/proto/udpa/core/v1/resource_name.proto new file mode 100644 index 00000000000..d6188853f9f --- /dev/null +++ b/xds/third_party/udpa/src/main/proto/udpa/core/v1/resource_name.proto @@ -0,0 +1,41 @@ +syntax = "proto3"; + +package udpa.core.v1; + +import "udpa/annotations/status.proto"; +import "udpa/core/v1/context_params.proto"; + +import "validate/validate.proto"; + +option java_outer_classname = "ResourceNameProto"; +option java_multiple_files = true; +option java_package = "com.github.udpa.udpa.core.v1"; + +option (udpa.annotations.file_status).work_in_progress = true; + +// UDPA resource name. This has a canonical udpa:// URI representation: +// +// udpa://{authority}/{type_url}/{id/*}?{context_params} +// +// where context_params take the form of URI query parameters. +// +// A UDPA resource name fully identifies a network resource for transport +// purposes. UDPA resource names in this form appear only in discovery +// request/response messages used with the xDS transport. +message ResourceName { + // Opaque identifiers for the resource. These are effectively concatenated + // with ‘/’ to form the non-query param path as resource ID. + repeated string id = 1 [(validate.rules).repeated = {min_items: 1}]; + + // Logical authority for resource (not necessarily transport network address). + // Authorities are opaque in the UDPA API, data-plane load balancers will map + // them to concrete network transports such as an xDS management server. + string authority = 2; + + // Fully qualified resource type (as in type URL without types.googleapis.com/ + // prefix). + string resource_type = 3 [(validate.rules).string = {min_len: 1}]; + + // Additional parameters that can be used to select resource variants. + ContextParams context = 4; +} From afcce8d3c02bfc21bbdb9cd91113fd937e20b7c8 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Tue, 4 Aug 2020 16:54:34 -0700 Subject: [PATCH 38/88] core: Add DelayedClientCall Adding `DelayedClientCall` in preparation of implementing `ConfigSelector` in core. `DelayedClientCall` is implemented exactly in the same way as `DelayedStream`. Only added logic to monitor initial DEADLINE. Note that `ClientCall.cancel()` is not thread-safe and will cause exceptions if trying to start call after it, which is different from in the stream where cancel() is thread-safe and wouldn't trigger any checkState()s. The initial DEADLINE monitor should not call `ClientCall.cancel()` directly. --- .../io/grpc/internal/DelayedClientCall.java | 527 ++++++++++++++++++ .../grpc/internal/DelayedClientCallTest.java | 122 ++++ 2 files changed, 649 insertions(+) create mode 100644 core/src/main/java/io/grpc/internal/DelayedClientCall.java create mode 100644 core/src/test/java/io/grpc/internal/DelayedClientCallTest.java diff --git a/core/src/main/java/io/grpc/internal/DelayedClientCall.java b/core/src/main/java/io/grpc/internal/DelayedClientCall.java new file mode 100644 index 00000000000..fd3a4ccac66 --- /dev/null +++ b/core/src/main/java/io/grpc/internal/DelayedClientCall.java @@ -0,0 +1,527 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.internal; + +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.base.MoreObjects; +import io.grpc.Attributes; +import io.grpc.ClientCall; +import io.grpc.Context; +import io.grpc.Deadline; +import io.grpc.Metadata; +import io.grpc.Status; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.Nullable; +import javax.annotation.concurrent.GuardedBy; + +/** + * A call that queues requests before the transport is available, and delegates to a real call + * implementation when the transport is available. + * + *

{@code ClientCall} itself doesn't require thread-safety. However, the state of {@code + * DelayedCall} may be internally altered by different threads, thus internal synchronization is + * necessary. + */ +final class DelayedClientCall extends ClientCall { + private static final Logger logger = Logger.getLogger(DelayedClientCall.class.getName()); + /** + * A timer to monitor the initial deadline. The timer must be cancelled on transition to the real + * call. + */ + @Nullable + private final ScheduledFuture initialDeadlineMonitor; + private final Executor callExecutor; + private final Context context; + /** {@code true} once realCall is valid and all pending calls have been drained. */ + private volatile boolean passThrough; + /** + * Non-{@code null} iff start has been called. Used to assert methods are called in appropriate + * order, but also used if an error occurs before {@code realCall} is set. + */ + private Listener listener; + // Must hold {@code this} lock when setting. + private ClientCall realCall; + @GuardedBy("this") + private Status error; + @GuardedBy("this") + private List pendingRunnables = new ArrayList<>(); + @GuardedBy("this") + private DelayedListener delayedListener; + + DelayedClientCall( + Executor callExecutor, ScheduledExecutorService scheduler, @Nullable Deadline deadline) { + this.callExecutor = checkNotNull(callExecutor, "callExecutor"); + checkNotNull(scheduler, "scheduler"); + context = Context.current(); + initialDeadlineMonitor = scheduleDeadlineIfNeeded(scheduler, deadline); + } + + @Nullable + private ScheduledFuture scheduleDeadlineIfNeeded( + ScheduledExecutorService scheduler, @Nullable Deadline deadline) { + Deadline contextDeadline = context.getDeadline(); + if (deadline == null && contextDeadline == null) { + return null; + } + long remainingNanos = Long.MAX_VALUE; + if (deadline != null) { + remainingNanos = Math.min(remainingNanos, deadline.timeRemaining(NANOSECONDS)); + } + if (contextDeadline != null && contextDeadline.timeRemaining(NANOSECONDS) < remainingNanos) { + remainingNanos = contextDeadline.timeRemaining(NANOSECONDS); + if (logger.isLoggable(Level.FINE)) { + StringBuilder builder = + new StringBuilder( + String.format( + "Call timeout set to '%d' ns, due to context deadline.", remainingNanos)); + if (deadline == null) { + builder.append(" Explicit call timeout was not set."); + } else { + long callTimeout = deadline.timeRemaining(TimeUnit.NANOSECONDS); + builder.append(String.format(" Explicit call timeout was '%d' ns.", callTimeout)); + } + logger.fine(builder.toString()); + } + } + long seconds = Math.abs(remainingNanos) / TimeUnit.SECONDS.toNanos(1); + long nanos = Math.abs(remainingNanos) % TimeUnit.SECONDS.toNanos(1); + final StringBuilder buf = new StringBuilder(); + if (remainingNanos < 0) { + buf.append("ClientCall started after deadline exceeded. Deadline exceeded after -"); + } else { + buf.append("Deadline exceeded after "); + } + buf.append(seconds); + buf.append(String.format(".%09d", nanos)); + buf.append("s. "); + /** Cancels the call if deadline exceeded prior to the real call being set. */ + class DeadlineExceededRunnable implements Runnable { + @Override + public void run() { + cancel( + Status.DEADLINE_EXCEEDED.withDescription(buf.toString()), + // We should not cancel the call if the realCall is set because there could be a + // race between cancel() and realCall.start(). The realCall will handle deadline by + // itself. + /* onlyCancelPendingCall= */ true); + } + } + + return scheduler.schedule(new DeadlineExceededRunnable(), remainingNanos, NANOSECONDS); + } + + /** + * Transfers all pending and future requests and mutations to the given call. + * + *

No-op if either this method or {@link #cancel} have already been called. + */ + // When this method returns, passThrough is guaranteed to be true + final void setCall(ClientCall call) { + synchronized (this) { + // If realCall != null, then either setCall() or cancel() has been called. + if (realCall != null) { + return; + } + setRealCall(checkNotNull(call, "call")); + } + drainPendingCalls(); + } + + @Override + public void start(Listener listener, final Metadata headers) { + checkState(this.listener == null, "already started"); + Status savedError; + boolean savedPassThrough; + synchronized (this) { + this.listener = checkNotNull(listener, "listener"); + // If error != null, then cancel() has been called and was unable to close the listener + savedError = error; + savedPassThrough = passThrough; + if (!savedPassThrough) { + listener = delayedListener = new DelayedListener<>(listener); + } + } + if (savedError != null) { + callExecutor.execute(new CloseListenerRunnable(listener, savedError)); + return; + } + if (savedPassThrough) { + realCall.start(listener, headers); + } else { + final Listener finalListener = listener; + delayOrExecute(new Runnable() { + @Override + public void run() { + realCall.start(finalListener, headers); + } + }); + } + } + + // When this method returns, passThrough is guaranteed to be true + @Override + public void cancel(@Nullable final String message, @Nullable final Throwable cause) { + Status status = Status.CANCELLED; + if (message != null) { + status = status.withDescription(message); + } else { + status = status.withDescription("Call cancelled without message"); + } + if (cause != null) { + status = status.withCause(cause); + } + cancel(status, false); + } + + /** + * Cancels the call unless {@code realCall} is set and {@code onlyCancelPendingCall} is true. + */ + private void cancel(final Status status, boolean onlyCancelPendingCall) { + boolean delegateToRealCall = true; + Listener listenerToClose = null; + synchronized (this) { + // If realCall != null, then either setCall() or cancel() has been called + if (realCall == null) { + @SuppressWarnings("unchecked") + ClientCall noopCall = (ClientCall) NOOP_CALL; + setRealCall(noopCall); + delegateToRealCall = false; + // If listener == null, then start() will later call listener with 'error' + listenerToClose = listener; + error = status; + } else if (onlyCancelPendingCall) { + return; + } + } + if (delegateToRealCall) { + delayOrExecute(new Runnable() { + @Override + public void run() { + realCall.cancel(status.getDescription(), status.getCause()); + } + }); + } else { + if (listenerToClose != null) { + callExecutor.execute(new CloseListenerRunnable(listenerToClose, status)); + } + drainPendingCalls(); + } + } + + private void delayOrExecute(Runnable runnable) { + synchronized (this) { + if (!passThrough) { + pendingRunnables.add(runnable); + return; + } + } + runnable.run(); + } + + /** + * Called to transition {@code passThrough} to {@code true}. This method is not safe to be called + * multiple times; the caller must ensure it will only be called once, ever. {@code this} lock + * should not be held when calling this method. + */ + private void drainPendingCalls() { + assert realCall != null; + assert !passThrough; + List toRun = new ArrayList<>(); + DelayedListener delayedListener ; + while (true) { + synchronized (this) { + if (pendingRunnables.isEmpty()) { + pendingRunnables = null; + passThrough = true; + delayedListener = this.delayedListener; + break; + } + // Since there were pendingCalls, we need to process them. To maintain ordering we can't set + // passThrough=true until we run all pendingCalls, but new Runnables may be added after we + // drop the lock. So we will have to re-check pendingCalls. + List tmp = toRun; + toRun = pendingRunnables; + pendingRunnables = tmp; + } + for (Runnable runnable : toRun) { + // Must not call transport while lock is held to prevent deadlocks. + // TODO(ejona): exception handling + runnable.run(); + } + toRun.clear(); + } + if (delayedListener != null) { + final DelayedListener listener = delayedListener; + class DrainListenerRunnable extends ContextRunnable { + DrainListenerRunnable() { + super(context); + } + + @Override + public void runInContext() { + listener.drainPendingCallbacks(); + } + } + + callExecutor.execute(new DrainListenerRunnable()); + } + } + + @GuardedBy("this") + private void setRealCall(ClientCall realCall) { + checkState(this.realCall == null, "realCall already set to %s", this.realCall); + if (initialDeadlineMonitor != null) { + initialDeadlineMonitor.cancel(false); + } + this.realCall = realCall; + } + + @VisibleForTesting + ClientCall getRealCall() { + return realCall; + } + + @Override + public void sendMessage(final ReqT message) { + if (passThrough) { + realCall.sendMessage(message); + } else { + delayOrExecute(new Runnable() { + @Override + public void run() { + realCall.sendMessage(message); + } + }); + } + } + + @Override + public void setMessageCompression(final boolean enable) { + if (passThrough) { + realCall.setMessageCompression(enable); + } else { + delayOrExecute(new Runnable() { + @Override + public void run() { + realCall.setMessageCompression(enable); + } + }); + } + } + + @Override + public void request(final int numMessages) { + if (passThrough) { + realCall.request(numMessages); + } else { + delayOrExecute(new Runnable() { + @Override + public void run() { + realCall.request(numMessages); + } + }); + } + } + + @Override + public void halfClose() { + delayOrExecute(new Runnable() { + @Override + public void run() { + realCall.halfClose(); + } + }); + } + + @Override + public boolean isReady() { + if (passThrough) { + return realCall.isReady(); + } else { + return false; + } + } + + @Override + public Attributes getAttributes() { + ClientCall savedRealCall; + synchronized (this) { + savedRealCall = realCall; + } + if (savedRealCall != null) { + return savedRealCall.getAttributes(); + } else { + return Attributes.EMPTY; + } + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("realCall", realCall) + .toString(); + } + + private final class CloseListenerRunnable extends ContextRunnable { + final Listener listener; + final Status status; + + CloseListenerRunnable(Listener listener, Status status) { + super(context); + this.listener = listener; + this.status = status; + } + + @Override + public void runInContext() { + listener.onClose(status, new Metadata()); + } + } + + private static final class DelayedListener extends Listener { + private final Listener realListener; + private volatile boolean passThrough; + @GuardedBy("this") + private List pendingCallbacks = new ArrayList<>(); + + public DelayedListener(Listener listener) { + this.realListener = listener; + } + + private void delayOrExecute(Runnable runnable) { + synchronized (this) { + if (!passThrough) { + pendingCallbacks.add(runnable); + return; + } + } + runnable.run(); + } + + @Override + public void onHeaders(final Metadata headers) { + if (passThrough) { + realListener.onHeaders(headers); + } else { + delayOrExecute(new Runnable() { + @Override + public void run() { + realListener.onHeaders(headers); + } + }); + } + } + + @Override + public void onMessage(final RespT message) { + if (passThrough) { + realListener.onMessage(message); + } else { + delayOrExecute(new Runnable() { + @Override + public void run() { + realListener.onMessage(message); + } + }); + } + } + + @Override + public void onClose(final Status status, final Metadata trailers) { + delayOrExecute(new Runnable() { + @Override + public void run() { + realListener.onClose(status, trailers); + } + }); + } + + @Override + public void onReady() { + if (passThrough) { + realListener.onReady(); + } else { + delayOrExecute(new Runnable() { + @Override + public void run() { + realListener.onReady(); + } + }); + } + } + + void drainPendingCallbacks() { + assert !passThrough; + List toRun = new ArrayList<>(); + while (true) { + synchronized (this) { + if (pendingCallbacks.isEmpty()) { + pendingCallbacks = null; + passThrough = true; + break; + } + // Since there were pendingCallbacks, we need to process them. To maintain ordering we + // can't set passThrough=true until we run all pendingCallbacks, but new Runnables may be + // added after we drop the lock. So we will have to re-check pendingCallbacks. + List tmp = toRun; + toRun = pendingCallbacks; + pendingCallbacks = tmp; + } + for (Runnable runnable : toRun) { + // Avoid calling listener while lock is held to prevent deadlocks. + // TODO(ejona): exception handling + runnable.run(); + } + toRun.clear(); + } + } + } + + private static final ClientCall NOOP_CALL = new ClientCall() { + @Override + public void start(Listener responseListener, Metadata headers) {} + + @Override + public void request(int numMessages) {} + + @Override + public void cancel(String message, Throwable cause) {} + + @Override + public void halfClose() {} + + @Override + public void sendMessage(Object message) {} + + // Always returns {@code false}, since this is only used when the startup of the call fails. + @Override + public boolean isReady() { + return false; + } + }; +} diff --git a/core/src/test/java/io/grpc/internal/DelayedClientCallTest.java b/core/src/test/java/io/grpc/internal/DelayedClientCallTest.java new file mode 100644 index 00000000000..290e2b9de65 --- /dev/null +++ b/core/src/test/java/io/grpc/internal/DelayedClientCallTest.java @@ -0,0 +1,122 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.internal; + +import static com.google.common.truth.Truth.assertThat; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import com.google.common.util.concurrent.MoreExecutors; +import io.grpc.ClientCall; +import io.grpc.ClientCall.Listener; +import io.grpc.Deadline; +import io.grpc.ForwardingTestUtil; +import io.grpc.Metadata; +import io.grpc.Status; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.concurrent.Executor; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +/** Tests for {@link DelayedClientCall}. */ +@RunWith(JUnit4.class) +public class DelayedClientCallTest { + @Rule + public final MockitoRule mockitoRule = MockitoJUnit.rule(); + @Mock + private ClientCall mockRealCall; + @Mock + private ClientCall.Listener listener; + @Captor + ArgumentCaptor statusCaptor; + + private final FakeClock fakeClock = new FakeClock(); + private final Executor callExecutor = MoreExecutors.directExecutor(); + + @Test + public void allMethodsForwarded() throws Exception { + DelayedClientCall delayedClientCall = + new DelayedClientCall<>(callExecutor, fakeClock.getScheduledExecutorService(), null); + delayedClientCall.setCall(mockRealCall); + ForwardingTestUtil.testMethodsForwarded( + ClientCall.class, + mockRealCall, + delayedClientCall, + Arrays.asList(ClientCall.class.getMethod("toString")), + new ForwardingTestUtil.ArgumentProvider() { + @Override + public Object get(Method method, int argPos, Class clazz) { + if (!Modifier.isFinal(clazz.getModifiers())) { + return mock(clazz); + } + if (clazz.equals(String.class)) { + return "message"; + } + return null; + } + }); + } + + // Coverage for deadline exceeded before call started is enforced by + // AbstractInteropTest.deadlineInPast(). + @Test + public void deadlineExceededWhileCallIsStartedButStillPending() { + DelayedClientCall delayedClientCall = new DelayedClientCall<>( + callExecutor, fakeClock.getScheduledExecutorService(), Deadline.after(10, SECONDS)); + + delayedClientCall.start(listener, new Metadata()); + fakeClock.forwardTime(10, SECONDS); + verify(listener).onClose(statusCaptor.capture(), any(Metadata.class)); + assertThat(statusCaptor.getValue().getCode()).isEqualTo(Status.Code.DEADLINE_EXCEEDED); + } + + @Test + public void listenerEventsPropagated() { + DelayedClientCall delayedClientCall = new DelayedClientCall<>( + callExecutor, fakeClock.getScheduledExecutorService(), Deadline.after(10, SECONDS)); + delayedClientCall.start(listener, new Metadata()); + delayedClientCall.setCall(mockRealCall); + ArgumentCaptor> listenerCaptor = ArgumentCaptor.forClass(null); + verify(mockRealCall).start(listenerCaptor.capture(), any(Metadata.class)); + Listener realCallListener = listenerCaptor.getValue(); + Metadata metadata = new Metadata(); + metadata.put(Metadata.Key.of("key", Metadata.ASCII_STRING_MARSHALLER), "value"); + realCallListener.onHeaders(metadata); + verify(listener).onHeaders(metadata); + realCallListener.onMessage(3); + verify(listener).onMessage(3); + realCallListener.onReady(); + verify(listener).onReady(); + Metadata trailer = new Metadata(); + trailer.put(Metadata.Key.of("key2", Metadata.ASCII_STRING_MARSHALLER), "value2"); + realCallListener.onClose(Status.DATA_LOSS, trailer); + verify(listener).onClose(statusCaptor.capture(), eq(trailer)); + assertThat(statusCaptor.getValue().getCode()).isEqualTo(Status.Code.DATA_LOSS); + } +} From 34513d7ed8912e700632aa06b9016760e852cc9a Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Tue, 4 Aug 2020 22:32:26 -0700 Subject: [PATCH 39/88] xds: add support for cert-providers to bootstrap file (#7285) --- .../main/java/io/grpc/xds/Bootstrapper.java | 60 +++++- .../java/io/grpc/xds/BootstrapperTest.java | 202 ++++++++++++++++++ .../java/io/grpc/xds/EdsLoadBalancerTest.java | 2 +- .../xds/XdsNameResolverIntegrationTest.java | 4 +- 4 files changed, 263 insertions(+), 5 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/Bootstrapper.java b/xds/src/main/java/io/grpc/xds/Bootstrapper.java index 6fa3c4e5710..c2a29ab10c3 100644 --- a/xds/src/main/java/io/grpc/xds/Bootstrapper.java +++ b/xds/src/main/java/io/grpc/xds/Bootstrapper.java @@ -16,6 +16,8 @@ package io.grpc.xds; +import static com.google.common.base.Preconditions.checkNotNull; + import com.google.common.annotations.VisibleForTesting; import io.grpc.Internal; import io.grpc.internal.GrpcUtil; @@ -31,6 +33,7 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.Nullable; @@ -160,7 +163,28 @@ static BootstrapInfo parseConfig(String rawData) throws IOException { nodeBuilder.setUserAgentVersion(buildVersion.getImplementationVersion()); nodeBuilder.addClientFeatures(CLIENT_FEATURE_DISABLE_OVERPROVISIONING); - return new BootstrapInfo(servers, nodeBuilder.build()); + Map certProvidersBlob = JsonUtil.getObject(rawBootstrap, "certificate_providers"); + Map certProviders = null; + if (certProvidersBlob != null) { + certProviders = new HashMap<>(certProvidersBlob.size()); + for (String name : certProvidersBlob.keySet()) { + Map valueMap = JsonUtil.getObject(certProvidersBlob, name); + String pluginName = + checkForNull(JsonUtil.getString(valueMap, "plugin_name"), "plugin_name"); + Map config = checkForNull(JsonUtil.getObject(valueMap, "config"), "config"); + CertificateProviderInfo certificateProviderInfo = + new CertificateProviderInfo(pluginName, config); + certProviders.put(name, certificateProviderInfo); + } + } + return new BootstrapInfo(servers, nodeBuilder.build(), certProviders); + } + + static T checkForNull(T value, String fieldName) throws IOException { + if (value == null) { + throw new IOException("Invalid bootstrap: '" + fieldName + "' does not exist."); + } + return value; } /** @@ -225,6 +249,30 @@ List getServerFeatures() { } } + /** + * Data class containing Certificate provider information: the plugin-name and an opaque + * Map that represents the config for that plugin. + */ + @Internal + @Immutable + public static class CertificateProviderInfo { + private final String pluginName; + private final Map config; + + CertificateProviderInfo(String pluginName, Map config) { + this.pluginName = checkNotNull(pluginName, "pluginName"); + this.config = checkNotNull(config, "config"); + } + + String getPluginName() { + return pluginName; + } + + Map getConfig() { + return config; + } + } + /** * Data class containing the results of reading bootstrap. */ @@ -233,11 +281,14 @@ List getServerFeatures() { public static class BootstrapInfo { private List servers; private final Node node; + @Nullable private final Map certProviders; @VisibleForTesting - BootstrapInfo(List servers, Node node) { + BootstrapInfo( + List servers, Node node, Map certProviders) { this.servers = servers; this.node = node; + this.certProviders = certProviders; } /** @@ -253,5 +304,10 @@ List getServers() { public Node getNode() { return node; } + + /** Returns the cert-providers config map. */ + public Map getCertProviders() { + return Collections.unmodifiableMap(certProviders); + } } } diff --git a/xds/src/test/java/io/grpc/xds/BootstrapperTest.java b/xds/src/test/java/io/grpc/xds/BootstrapperTest.java index 6c1fd2111ba..17f42aad73b 100644 --- a/xds/src/test/java/io/grpc/xds/BootstrapperTest.java +++ b/xds/src/test/java/io/grpc/xds/BootstrapperTest.java @@ -17,6 +17,7 @@ package io.grpc.xds; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; @@ -28,6 +29,7 @@ import io.grpc.xds.EnvoyProtoData.Node; import java.io.IOException; import java.util.List; +import java.util.Map; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -298,6 +300,206 @@ public void parseBootstrap_serverWithoutServerUri() throws IOException { Bootstrapper.parseConfig(rawData); } + @Test + public void parseBootstrap_validData_certProviderInstances() throws IOException { + String rawData = + "{\n" + + " \"xds_servers\": [],\n" + + " \"certificate_providers\": {\n" + + " \"gcp_id\": {\n" + + " \"plugin_name\": \"meshca\",\n" + + " \"config\": {\n" + + " \"server\": {\n" + + " \"api_type\": \"GRPC\",\n" + + " \"grpc_services\": [{\n" + + " \"google_grpc\": {\n" + + " \"target_uri\": \"meshca.com\",\n" + + " \"channel_credentials\": {\"google_default\": {}},\n" + + " \"call_credentials\": [{\n" + + " \"sts_service\": {\n" + + " \"token_exchange_service\": \"securetoken.googleapis.com\",\n" + + " \"subject_token_path\": \"/etc/secret/sajwt.token\"\n" + + " }\n" + + " }]\n" // end call_credentials + + " },\n" // end google_grpc + + " \"time_out\": {\"seconds\": 10}\n" + + " }]\n" // end grpc_services + + " },\n" // end server + + " \"certificate_lifetime\": {\"seconds\": 86400},\n" + + " \"renewal_grace_period\": {\"seconds\": 3600},\n" + + " \"key_type\": \"RSA\",\n" + + " \"key_size\": 2048,\n" + + " \"location\": \"https://container.googleapis.com/v1/project/test-project1/locations/test-zone2/clusters/test-cluster3\"\n" + + " }\n" // end config + + " },\n" // end gcp_id + + " \"file_provider\": {\n" + + " \"plugin_name\": \"file_watcher\",\n" + + " \"config\": {\"path\": \"/etc/secret/certs\"}\n" + + " }\n" + + " }\n" + + "}"; + + BootstrapInfo info = Bootstrapper.parseConfig(rawData); + assertThat(info.getServers()).isEmpty(); + assertThat(info.getNode()).isEqualTo(getNodeBuilder().build()); + Map certProviders = info.getCertProviders(); + assertThat(certProviders).isNotNull(); + Bootstrapper.CertificateProviderInfo gcpId = certProviders.get("gcp_id"); + Bootstrapper.CertificateProviderInfo fileProvider = certProviders.get("file_provider"); + assertThat(gcpId.getPluginName()).isEqualTo("meshca"); + assertThat(gcpId.getConfig()).isInstanceOf(Map.class); + assertThat(fileProvider.getPluginName()).isEqualTo("file_watcher"); + assertThat(fileProvider.getConfig()).isInstanceOf(Map.class); + Map meshCaConfig = (Map)gcpId.getConfig(); + assertThat(meshCaConfig.get("key_size")).isEqualTo(2048); + } + + @Test + public void parseBootstrap_badPluginName() throws IOException { + String rawData = + "{\n" + + " \"xds_servers\": [],\n" + + " \"certificate_providers\": {\n" + + " \"gcp_id\": {\n" + + " \"plugin_name\": 234,\n" + + " \"config\": {\n" + + " \"server\": {\n" + + " \"api_type\": \"GRPC\",\n" + + " \"grpc_services\": [{\n" + + " \"google_grpc\": {\n" + + " \"target_uri\": \"meshca.com\",\n" + + " \"channel_credentials\": {\"google_default\": {}},\n" + + " \"call_credentials\": [{\n" + + " \"sts_service\": {\n" + + " \"token_exchange_service\": \"securetoken.googleapis.com\",\n" + + " \"subject_token_path\": \"/etc/secret/sajwt.token\"\n" + + " }\n" + + " }]\n" // end call_credentials + + " },\n" // end google_grpc + + " \"time_out\": {\"seconds\": 10}\n" + + " }]\n" // end grpc_services + + " },\n" // end server + + " \"certificate_lifetime\": {\"seconds\": 86400},\n" + + " \"renewal_grace_period\": {\"seconds\": 3600},\n" + + " \"key_type\": \"RSA\",\n" + + " \"key_size\": 2048,\n" + + " \"location\": \"https://container.googleapis.com/v1/project/test-project1/locations/test-zone2/clusters/test-cluster3\"\n" + + " }\n" // end config + + " },\n" // end gcp_id + + " \"file_provider\": {\n" + + " \"plugin_name\": \"file_watcher\",\n" + + " \"config\": {\"path\": \"/etc/secret/certs\"}\n" + + " }\n" + + " }\n" + + "}"; + + try { + Bootstrapper.parseConfig(rawData); + fail("exception expected"); + } catch (ClassCastException expected) { + assertThat(expected).hasMessageThat().contains("value '234.0' for key 'plugin_name' in"); + } + } + + @Test + public void parseBootstrap_badConfig() throws IOException { + String rawData = + "{\n" + + " \"xds_servers\": [],\n" + + " \"certificate_providers\": {\n" + + " \"gcp_id\": {\n" + + " \"plugin_name\": \"meshca\",\n" + + " \"config\": \"badValue\"\n" + + " },\n" // end gcp_id + + " \"file_provider\": {\n" + + " \"plugin_name\": \"file_watcher\",\n" + + " \"config\": {\"path\": \"/etc/secret/certs\"}\n" + + " }\n" + + " }\n" + + "}"; + + try { + Bootstrapper.parseConfig(rawData); + fail("exception expected"); + } catch (ClassCastException expected) { + assertThat(expected).hasMessageThat().contains("value 'badValue' for key 'config' in"); + } + } + + @Test + public void parseBootstrap_missingConfig() throws IOException { + String rawData = + "{\n" + + " \"xds_servers\": [],\n" + + " \"certificate_providers\": {\n" + + " \"gcp_id\": {\n" + + " \"plugin_name\": \"meshca\"\n" + + " },\n" // end gcp_id + + " \"file_provider\": {\n" + + " \"plugin_name\": \"file_watcher\",\n" + + " \"config\": {\"path\": \"/etc/secret/certs\"}\n" + + " }\n" + + " }\n" + + "}"; + + try { + Bootstrapper.parseConfig(rawData); + fail("exception expected"); + } catch (IOException expected) { + assertThat(expected) + .hasMessageThat() + .isEqualTo("Invalid bootstrap: 'config' does not exist."); + } + } + + @Test + public void parseBootstrap_missingPluginName() throws IOException { + String rawData = + "{\n" + + " \"xds_servers\": [],\n" + + " \"certificate_providers\": {\n" + + " \"gcp_id\": {\n" + + " \"plugin_name\": \"meshca\",\n" + + " \"config\": {\n" + + " \"server\": {\n" + + " \"api_type\": \"GRPC\",\n" + + " \"grpc_services\": [{\n" + + " \"google_grpc\": {\n" + + " \"target_uri\": \"meshca.com\",\n" + + " \"channel_credentials\": {\"google_default\": {}},\n" + + " \"call_credentials\": [{\n" + + " \"sts_service\": {\n" + + " \"token_exchange_service\": \"securetoken.googleapis.com\",\n" + + " \"subject_token_path\": \"/etc/secret/sajwt.token\"\n" + + " }\n" + + " }]\n" // end call_credentials + + " },\n" // end google_grpc + + " \"time_out\": {\"seconds\": 10}\n" + + " }]\n" // end grpc_services + + " },\n" // end server + + " \"certificate_lifetime\": {\"seconds\": 86400},\n" + + " \"renewal_grace_period\": {\"seconds\": 3600},\n" + + " \"key_type\": \"RSA\",\n" + + " \"key_size\": 2048,\n" + + " \"location\": \"https://container.googleapis.com/v1/project/test-project1/locations/test-zone2/clusters/test-cluster3\"\n" + + " }\n" // end config + + " },\n" // end gcp_id + + " \"file_provider\": {\n" + + " \"config\": {\"path\": \"/etc/secret/certs\"}\n" + + " }\n" + + " }\n" + + "}"; + + try { + Bootstrapper.parseConfig(rawData); + fail("exception expected"); + } catch (IOException expected) { + assertThat(expected) + .hasMessageThat() + .isEqualTo("Invalid bootstrap: 'plugin_name' does not exist."); + } + } + private static Node.Builder getNodeBuilder() { GrpcBuildVersion buildVersion = GrpcUtil.getGrpcBuildVersion(); return diff --git a/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java index 0b923b3033e..62c72ba9e0d 100644 --- a/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java @@ -232,7 +232,7 @@ public StreamObserver streamAggregatedResources( final List serverList = ImmutableList.of( new ServerInfo("trafficdirector.googleapis.com", ImmutableList.of(), null)); Node node = Node.newBuilder().build(); - BootstrapInfo bootstrapInfo = new BootstrapInfo(serverList, node); + BootstrapInfo bootstrapInfo = new BootstrapInfo(serverList, node, null); doReturn(bootstrapInfo).when(bootstrapper).readBootstrap(); if (isFullFlow) { diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverIntegrationTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverIntegrationTest.java index 7007f74ecb8..3c844e08e99 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverIntegrationTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverIntegrationTest.java @@ -170,7 +170,7 @@ XdsChannel createChannel(List servers) { public BootstrapInfo readBootstrap() { List serverList = ImmutableList.of(new ServerInfo(serverName, ImmutableList.of(), null)); - return new BootstrapInfo(serverList, FAKE_BOOTSTRAP_NODE); + return new BootstrapInfo(serverList, FAKE_BOOTSTRAP_NODE, null); } }; xdsNameResolver = @@ -194,7 +194,7 @@ public void resolve_bootstrapProvidesNoTrafficDirectorInfo() { Bootstrapper bootstrapper = new Bootstrapper() { @Override public BootstrapInfo readBootstrap() { - return new BootstrapInfo(ImmutableList.of(), FAKE_BOOTSTRAP_NODE); + return new BootstrapInfo(ImmutableList.of(), FAKE_BOOTSTRAP_NODE, null); } }; From de6b747e92d2b23c8378695891b5f81d8a041a8e Mon Sep 17 00:00:00 2001 From: Patrice Chalin Date: Wed, 5 Aug 2020 11:55:47 -0400 Subject: [PATCH 40/88] examples/README: update links into grpc.io Java pages - Update links into grpc.io - A few copy edits --- examples/README.md | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/examples/README.md b/examples/README.md index 1f26076137b..f57ad169897 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,12 +1,12 @@ -grpc Examples +gRPC Examples ============================================== -The examples require grpc-java to already be built. You are strongly encouraged -to check out a git release tag, since there will already be a build of grpc +The examples require `grpc-java` to already be built. You are strongly encouraged +to check out a git release tag, since there will already be a build of gRPC available. Otherwise you must follow [COMPILING](../COMPILING.md). You may want to read through the -[Quick Start Guide](https://grpc.io/docs/quickstart/java.html) +[Quick Start](https://grpc.io/docs/languages/java/quickstart) before trying out the examples. ## Basic examples @@ -90,31 +90,31 @@ before trying out the examples. -
Retrying - The [retrying example](src/main/java/io/grpc/examples/retrying) provides a HelloWorld GRPC client & + The [retrying example](src/main/java/io/grpc/examples/retrying) provides a HelloWorld gRPC client & server which demos the effect of client retry policy configured on the [ManagedChannel]( - ../api/src/main/java/io/grpc/ManagedChannel.java) via [GRPC ServiceConfig]( - https://github.com/grpc/grpc/blob/master/doc/service_config.md). Retry policy implementation & + ../api/src/main/java/io/grpc/ManagedChannel.java) via [gRPC ServiceConfig]( + https://github.com/grpc/grpc/blob/master/doc/service_config.md). Retry policy implementation & configuration details are outlined in the [proposal](https://github.com/grpc/proposal/blob/master/A6-client-retries.md). - - This retrying example is very similar to the [hedging example](src/main/java/io/grpc/examples/hedging) in its setup. + + This retrying example is very similar to the [hedging example](src/main/java/io/grpc/examples/hedging) in its setup. The [RetryingHelloWorldServer](src/main/java/io/grpc/examples/retrying/RetryingHelloWorldServer.java) responds with - a status UNAVAILABLE error response to a specified percentage of requests to simulate server resource exhaustion and - general flakiness. The [RetryingHelloWorldClient](src/main/java/io/grpc/examples/retrying/RetryingHelloWorldClient.java) makes - a number of sequential requests to the server, several of which will be retried depending on the configured policy in - [retrying_service_config.json](src/main/resources/io/grpc/examples/retrying/retrying_service_config.json). Although - the requests are blocking unary calls for simplicity, these could easily be changed to future unary calls in order to + a status UNAVAILABLE error response to a specified percentage of requests to simulate server resource exhaustion and + general flakiness. The [RetryingHelloWorldClient](src/main/java/io/grpc/examples/retrying/RetryingHelloWorldClient.java) makes + a number of sequential requests to the server, several of which will be retried depending on the configured policy in + [retrying_service_config.json](src/main/resources/io/grpc/examples/retrying/retrying_service_config.json). Although + the requests are blocking unary calls for simplicity, these could easily be changed to future unary calls in order to test the result of request concurrency with retry policy enabled. - + One can experiment with the [RetryingHelloWorldServer](src/main/java/io/grpc/examples/retrying/RetryingHelloWorldServer.java) failure conditions to simulate server throttling, as well as alter policy values in the [retrying_service_config.json]( - src/main/resources/io/grpc/examples/retrying/retrying_service_config.json) to see their effects. To disable retrying - entirely, set environment variable `DISABLE_RETRYING_IN_RETRYING_EXAMPLE=true` before running the client. - Disabling the retry policy should produce many more failed GRPC calls as seen in the output log. - + src/main/resources/io/grpc/examples/retrying/retrying_service_config.json) to see their effects. To disable retrying + entirely, set environment variable `DISABLE_RETRYING_IN_RETRYING_EXAMPLE=true` before running the client. + Disabling the retry policy should produce many more failed gRPC calls as seen in the output log. + See [the section below](#to-build-the-examples) for how to build and run the example. The executables for the server and the client are `retrying-hello-world-server` and `retrying-hello-world-client`. - +
### To build the examples @@ -145,9 +145,8 @@ $ ./build/install/examples/bin/hello-world-client That's it! -Please refer to gRPC Java's [README](../README.md) and -[tutorial](https://grpc.io/docs/tutorials/basic/java.html) for more -information. +For more information, refer to gRPC Java's [README](../README.md) and +[tutorial](https://grpc.io/docs/languages/java/basics). ### Maven From d4166f0a029650898695eea05b39e4f8a8c3d0f1 Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Wed, 5 Aug 2020 14:30:48 -0700 Subject: [PATCH 41/88] xds: shade the meshCA proto generated code (#7290) --- xds/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/xds/build.gradle b/xds/build.gradle index 5371d085d38..0aa5f151479 100644 --- a/xds/build.gradle +++ b/xds/build.gradle @@ -96,6 +96,7 @@ shadowJar { relocate 'io.envoyproxy', 'io.grpc.xds.shaded.io.envoyproxy' relocate 'io.grpc.netty', 'io.grpc.netty.shaded.io.grpc.netty' relocate 'io.netty', 'io.grpc.netty.shaded.io.netty' + relocate 'google.security', 'io.grpc.xds.shaded.google.security' exclude "**/*.proto" } From 80d62bfce2a86b3cacfea71d1f3943ea58918975 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Mon, 3 Aug 2020 10:01:37 -0700 Subject: [PATCH 42/88] Upgrade to Mockito 3.3.3 verifyZeroInteractions has the same behavior as verifyNoMoreInteractions. It was deprecated in Mockito 3.0.1 and replaced with verifyNoInteractions, which does not change behavior depending on previous verify() calls. All instances were replaced with verifyNoInteractions, except those in ApplicationThreadDeframerTest which were replaced with verifyNoMoreInteractions since there is a verify() call in `@Before`. --- .../java/io/grpc/ServerInterceptorsTest.java | 8 +++---- build.gradle | 2 +- .../io/grpc/census/CensusModulesTest.java | 4 ++-- .../ApplicationThreadDeframerTest.java | 7 +++--- ...AutoConfiguredLoadBalancerFactoryTest.java | 4 ++-- .../io/grpc/internal/ClientCallImplTest.java | 8 +++---- .../grpc/internal/ManagedChannelImplTest.java | 22 +++++++++---------- .../io/grpc/internal/MessageFramerTest.java | 4 ++-- .../grpc/util/MutableHandlerRegistryTest.java | 4 ++-- .../io/grpc/netty/NettyServerStreamTest.java | 6 ++--- .../okhttp/OkHttpClientTransportTest.java | 4 ++-- ...HealthCheckingLoadBalancerFactoryTest.java | 6 ++--- .../java/io/grpc/xds/OrcaOobUtilTest.java | 8 +++---- .../io/grpc/xds/OrcaPerRequestUtilTest.java | 6 ++--- .../java/io/grpc/xds/XdsClientImplTest.java | 8 +++---- .../java/io/grpc/xds/XdsClientImplTestV2.java | 8 +++---- 16 files changed, 54 insertions(+), 55 deletions(-) diff --git a/api/src/test/java/io/grpc/ServerInterceptorsTest.java b/api/src/test/java/io/grpc/ServerInterceptorsTest.java index 4d17338119a..a12de3fc282 100644 --- a/api/src/test/java/io/grpc/ServerInterceptorsTest.java +++ b/api/src/test/java/io/grpc/ServerInterceptorsTest.java @@ -25,8 +25,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import io.grpc.MethodDescriptor.Marshaller; import io.grpc.MethodDescriptor.MethodType; @@ -102,9 +102,9 @@ public void setUp() { /** Final checks for all tests. */ @After public void makeSureExpectedMocksUnused() { - verifyZeroInteractions(requestMarshaller); - verifyZeroInteractions(responseMarshaller); - verifyZeroInteractions(listener); + verifyNoInteractions(requestMarshaller); + verifyNoInteractions(responseMarshaller); + verifyNoInteractions(listener); } @Test diff --git a/build.gradle b/build.gradle index aaf4794b1c9..f7ea90dd5cd 100644 --- a/build.gradle +++ b/build.gradle @@ -179,7 +179,7 @@ subprojects { // Test dependencies. junit: 'junit:junit:4.12', - mockito: 'org.mockito:mockito-core:2.28.2', + mockito: 'org.mockito:mockito-core:3.3.3', truth: 'com.google.truth:truth:1.0.1', guava_testlib: "com.google.guava:guava-testlib:${guavaVersion}", androidx_test: "androidx.test:core:1.2.0", diff --git a/census/src/test/java/io/grpc/census/CensusModulesTest.java b/census/src/test/java/io/grpc/census/CensusModulesTest.java index 8187d38456a..248195bc797 100644 --- a/census/src/test/java/io/grpc/census/CensusModulesTest.java +++ b/census/src/test/java/io/grpc/census/CensusModulesTest.java @@ -37,8 +37,8 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableList; @@ -1039,7 +1039,7 @@ public void serverBasicTracingNoHeaders() { ServerStreamTracer.Factory tracerFactory = censusTracing.getServerTracerFactory(); ServerStreamTracer serverStreamTracer = tracerFactory.newServerStreamTracer(method.getFullMethodName(), new Metadata()); - verifyZeroInteractions(mockTracingPropagationHandler); + verifyNoInteractions(mockTracingPropagationHandler); verify(tracer).spanBuilderWithRemoteParent( eq("Recv.package1.service2.method3"), ArgumentMatchers.isNull()); verify(spyServerSpanBuilder).setRecordEvents(eq(true)); diff --git a/core/src/test/java/io/grpc/internal/ApplicationThreadDeframerTest.java b/core/src/test/java/io/grpc/internal/ApplicationThreadDeframerTest.java index cd35eeff4b6..764fbed49ed 100644 --- a/core/src/test/java/io/grpc/internal/ApplicationThreadDeframerTest.java +++ b/core/src/test/java/io/grpc/internal/ApplicationThreadDeframerTest.java @@ -24,7 +24,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import com.google.common.io.ByteStreams; import com.google.common.primitives.Bytes; @@ -57,7 +56,7 @@ public void setUp() { @Test public void requestInvokesMessagesAvailableOnListener() { applicationThreadDeframer.request(1); - verifyZeroInteractions(mockDeframer); + verifyNoMoreInteractions(mockDeframer); listener.runStoredProducer(); verify(mockDeframer).request(1); } @@ -66,7 +65,7 @@ public void requestInvokesMessagesAvailableOnListener() { public void deframeInvokesMessagesAvailableOnListener() { ReadableBuffer frame = ReadableBuffers.wrap(new byte[1]); applicationThreadDeframer.deframe(frame); - verifyZeroInteractions(mockDeframer); + verifyNoMoreInteractions(mockDeframer); listener.runStoredProducer(); verify(mockDeframer).deframe(frame); } @@ -74,7 +73,7 @@ public void deframeInvokesMessagesAvailableOnListener() { @Test public void closeWhenCompleteInvokesMessagesAvailableOnListener() { applicationThreadDeframer.closeWhenComplete(); - verifyZeroInteractions(mockDeframer); + verifyNoMoreInteractions(mockDeframer); listener.runStoredProducer(); verify(mockDeframer).closeWhenComplete(); } diff --git a/core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest.java b/core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest.java index 65879f67acf..1f60bb97950 100644 --- a/core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest.java +++ b/core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest.java @@ -27,8 +27,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import com.google.common.base.Preconditions; @@ -634,7 +634,7 @@ public void parseLoadBalancerConfig_lbConfigPropagated() throws Exception { PolicySelection policySelection = (PolicySelection) parsed.getConfig(); assertThat(policySelection.config).isNotNull(); assertThat(policySelection.provider).isInstanceOf(GrpclbLoadBalancerProvider.class); - verifyZeroInteractions(channelLogger); + verifyNoInteractions(channelLogger); } public static class ForwardingLoadBalancer extends LoadBalancer { diff --git a/core/src/test/java/io/grpc/internal/ClientCallImplTest.java b/core/src/test/java/io/grpc/internal/ClientCallImplTest.java index 268264f848d..e5c944646f6 100644 --- a/core/src/test/java/io/grpc/internal/ClientCallImplTest.java +++ b/core/src/test/java/io/grpc/internal/ClientCallImplTest.java @@ -35,7 +35,7 @@ import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableSet; @@ -154,7 +154,7 @@ public Void answer(InvocationOnMock in) { @After public void tearDown() { - verifyZeroInteractions(streamTracerFactory); + verifyNoInteractions(streamTracerFactory); } @Test @@ -696,7 +696,7 @@ public void onClose(Status status, Metadata trailers) { call.halfClose(); // Stream should never be created. - verifyZeroInteractions(clientStreamProvider); + verifyNoInteractions(clientStreamProvider); try { call.sendMessage(null); @@ -729,7 +729,7 @@ public void deadlineExceededBeforeCallStarted() { assertEquals(Status.Code.DEADLINE_EXCEEDED, statusCaptor.getValue().getCode()); assertThat(statusCaptor.getValue().getDescription()) .startsWith("ClientCall started after deadline exceeded"); - verifyZeroInteractions(clientStreamProvider); + verifyNoInteractions(clientStreamProvider); } @Test diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java index fb65dc45d92..5d286ba6594 100644 --- a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java +++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java @@ -46,8 +46,8 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import com.google.common.base.Throwables; @@ -1192,7 +1192,7 @@ private void subtestFailRpcFromBalancer(boolean waitForReady, boolean drop, bool if (shouldFail) { verify(mockCallListener).onClose(same(status), any(Metadata.class)); } else { - verifyZeroInteractions(mockCallListener); + verifyNoInteractions(mockCallListener); } // This call doesn't involve delayed transport @@ -1203,7 +1203,7 @@ private void subtestFailRpcFromBalancer(boolean waitForReady, boolean drop, bool if (shouldFail) { verify(mockCallListener2).onClose(same(status), any(Metadata.class)); } else { - verifyZeroInteractions(mockCallListener2); + verifyNoInteractions(mockCallListener2); } } @@ -1691,7 +1691,7 @@ public void subchannelChannel_failWhenNotReady() { verify(mockTransport, never()).newStream( any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class)); - verifyZeroInteractions(mockCallListener); + verifyNoInteractions(mockCallListener); assertEquals(1, balancerRpcExecutor.runDueTasks()); verify(mockCallListener).onClose( same(SubchannelChannel.NOT_READY_ERROR), any(Metadata.class)); @@ -1723,7 +1723,7 @@ public void subchannelChannel_failWaitForReady() { verify(mockTransport, never()).newStream( any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class)); - verifyZeroInteractions(mockCallListener); + verifyNoInteractions(mockCallListener); assertEquals(1, balancerRpcExecutor.runDueTasks()); verify(mockCallListener).onClose( same(SubchannelChannel.WAIT_FOR_READY_ERROR), any(Metadata.class)); @@ -1994,8 +1994,8 @@ public void pickerReturnsStreamTracer_noDelay() { Arrays.asList(factory1, factory2), callOptionsCaptor.getValue().getStreamTracerFactories()); // The factories are safely not stubbed because we do not expect any usage of them. - verifyZeroInteractions(factory1); - verifyZeroInteractions(factory2); + verifyNoInteractions(factory1); + verifyNoInteractions(factory2); } @Test @@ -2030,8 +2030,8 @@ public void pickerReturnsStreamTracer_delayed() { Arrays.asList(factory1, factory2), callOptionsCaptor.getValue().getStreamTracerFactories()); // The factories are safely not stubbed because we do not expect any usage of them. - verifyZeroInteractions(factory1); - verifyZeroInteractions(factory2); + verifyNoInteractions(factory1); + verifyNoInteractions(factory2); } @Test @@ -2261,7 +2261,7 @@ public void run() { assertEquals(SHUTDOWN, channel.getState(false)); // We didn't stub mockPicker, because it should have never been called in this test. - verifyZeroInteractions(mockPicker); + verifyNoInteractions(mockPicker); } @Test @@ -2282,7 +2282,7 @@ public void panic_bufferedCallsWillFail() { call2.start(mockCallListener2, new Metadata()); executor.runDueTasks(); - verifyZeroInteractions(mockCallListener, mockCallListener2); + verifyNoInteractions(mockCallListener, mockCallListener2); // Enter panic final Throwable panicReason = new Exception("Simulated uncaught exception"); diff --git a/core/src/test/java/io/grpc/internal/MessageFramerTest.java b/core/src/test/java/io/grpc/internal/MessageFramerTest.java index bbbffa69ee7..91698acced7 100644 --- a/core/src/test/java/io/grpc/internal/MessageFramerTest.java +++ b/core/src/test/java/io/grpc/internal/MessageFramerTest.java @@ -23,8 +23,8 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import io.grpc.Codec; import io.grpc.StreamTracer; @@ -177,7 +177,7 @@ public void emptyPayloadYieldsFrame() { @Test public void emptyUnknownLengthPayloadYieldsFrame() { writeUnknownLength(framer, new byte[0]); - verifyZeroInteractions(sink); + verifyNoInteractions(sink); framer.flush(); verify(sink).deliverFrame(toWriteBuffer(new byte[] {0, 0, 0, 0, 0}), false, true, 1); // One alloc for the header diff --git a/core/src/test/java/io/grpc/util/MutableHandlerRegistryTest.java b/core/src/test/java/io/grpc/util/MutableHandlerRegistryTest.java index 3ffd14d8ff8..c80d42f8eaa 100644 --- a/core/src/test/java/io/grpc/util/MutableHandlerRegistryTest.java +++ b/core/src/test/java/io/grpc/util/MutableHandlerRegistryTest.java @@ -99,8 +99,8 @@ public void setUp() throws Exception { /** Final checks for all tests. */ @After public void makeSureMocksUnused() { - Mockito.verifyZeroInteractions(requestMarshaller); - Mockito.verifyZeroInteractions(responseMarshaller); + Mockito.verifyNoInteractions(requestMarshaller); + Mockito.verifyNoInteractions(responseMarshaller); Mockito.verifyNoMoreInteractions(flowHandler); Mockito.verifyNoMoreInteractions(coupleHandler); Mockito.verifyNoMoreInteractions(fewHandler); diff --git a/netty/src/test/java/io/grpc/netty/NettyServerStreamTest.java b/netty/src/test/java/io/grpc/netty/NettyServerStreamTest.java index 39ebf63eca1..4463d55eb74 100644 --- a/netty/src/test/java/io/grpc/netty/NettyServerStreamTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyServerStreamTest.java @@ -28,8 +28,8 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableListMultimap; @@ -160,7 +160,7 @@ public void closeBeforeClientHalfCloseShouldSucceed() throws Exception { assertThat(ImmutableListMultimap.copyOf(sendHeaders.headers())) .containsExactlyEntriesIn(expectedHeaders); assertThat(sendHeaders.endOfStream()).isTrue(); - verifyZeroInteractions(serverListener); + verifyNoInteractions(serverListener); // Sending complete. Listener gets closed() stream().transportState().complete(); @@ -188,7 +188,7 @@ public void closeWithErrorBeforeClientHalfCloseShouldSucceed() throws Exception assertThat(ImmutableListMultimap.copyOf(sendHeaders.headers())) .containsExactlyEntriesIn(expectedHeaders); assertThat(sendHeaders.endOfStream()).isTrue(); - verifyZeroInteractions(serverListener); + verifyNoInteractions(serverListener); // Sending complete. Listener gets closed() stream().transportState().complete(); diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java index 016fea83ea9..0994bc90004 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java @@ -44,8 +44,8 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import com.google.common.base.Stopwatch; import com.google.common.base.Supplier; @@ -1586,7 +1586,7 @@ public void notifyOnReady() throws Exception { @Test public void transportReady() throws Exception { initTransport(); - verifyZeroInteractions(transportListener); + verifyNoInteractions(transportListener); frameHandler().settings(false, new Settings()); verify(transportListener).transportReady(); shutdownAndVerify(); diff --git a/services/src/test/java/io/grpc/services/HealthCheckingLoadBalancerFactoryTest.java b/services/src/test/java/io/grpc/services/HealthCheckingLoadBalancerFactoryTest.java index f22bf41422c..e8a80b6d908 100644 --- a/services/src/test/java/io/grpc/services/HealthCheckingLoadBalancerFactoryTest.java +++ b/services/src/test/java/io/grpc/services/HealthCheckingLoadBalancerFactoryTest.java @@ -34,8 +34,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import com.google.common.util.concurrent.MoreExecutors; @@ -364,7 +364,7 @@ public void run() { assertThat(healthImpls[i].calls).hasSize(1); } - verifyZeroInteractions(backoffPolicyProvider); + verifyNoInteractions(backoffPolicyProvider); } @Test @@ -433,7 +433,7 @@ public void healthCheckDisabledWhenServiceNotImplemented() { unavailableStateWithMsg("Health-check service responded SERVICE_UNKNOWN for 'BarService'")); verifyNoMoreInteractions(origLb, mockStateListeners[0], mockStateListeners[1]); - verifyZeroInteractions(backoffPolicyProvider); + verifyNoInteractions(backoffPolicyProvider); } @Test diff --git a/xds/src/test/java/io/grpc/xds/OrcaOobUtilTest.java b/xds/src/test/java/io/grpc/xds/OrcaOobUtilTest.java index d3cf6cacbe4..b6cf1c742e7 100644 --- a/xds/src/test/java/io/grpc/xds/OrcaOobUtilTest.java +++ b/xds/src/test/java/io/grpc/xds/OrcaOobUtilTest.java @@ -30,8 +30,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import com.github.udpa.udpa.data.orca.v1.OrcaLoadReport; @@ -302,7 +302,7 @@ public void singlePolicyTypicalWorkflow() { assertThat(orcaServiceImps[i].calls).hasSize(1); } - verifyZeroInteractions(backoffPolicyProvider); + verifyNoInteractions(backoffPolicyProvider); } @Test @@ -388,7 +388,7 @@ public void twoLevelPoliciesTypicalWorkflow() { assertThat(orcaServiceImps[i].calls).hasSize(1); } - verifyZeroInteractions(backoffPolicyProvider); + verifyNoInteractions(backoffPolicyProvider); } @Test @@ -424,7 +424,7 @@ public void orcReportingDisabledWhenServiceNotImplemented() { assertLog(subchannel.logs, "DEBUG: Received an ORCA report: " + report); verify(mockOrcaListener0).onLoadReport(eq(report)); - verifyZeroInteractions(backoffPolicyProvider); + verifyNoInteractions(backoffPolicyProvider); } @Test diff --git a/xds/src/test/java/io/grpc/xds/OrcaPerRequestUtilTest.java b/xds/src/test/java/io/grpc/xds/OrcaPerRequestUtilTest.java index dd0cf3aa0d0..bee68c9e634 100644 --- a/xds/src/test/java/io/grpc/xds/OrcaPerRequestUtilTest.java +++ b/xds/src/test/java/io/grpc/xds/OrcaPerRequestUtilTest.java @@ -23,8 +23,8 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import com.github.udpa.udpa.data.orca.v1.OrcaLoadReport; @@ -164,7 +164,7 @@ public void onlyParentPolicyReceivesReportsIfCreatesOwnTracer() { OrcaLoadReport.getDefaultInstance()); parentTracer.inboundTrailers(trailer); verify(orcaListener1).onLoadReport(eq(OrcaLoadReport.getDefaultInstance())); - verifyZeroInteractions(childFactory); - verifyZeroInteractions(orcaListener2); + verifyNoInteractions(childFactory); + verifyNoInteractions(orcaListener2); } } diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java index 2a353c77f58..aa294eb3809 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java @@ -38,8 +38,8 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableList; @@ -1866,7 +1866,7 @@ public void addRemoveClusterWatcherWhileInitialResourceFetchInProgress() { assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); assertThat(timeoutTask.isCancelled()).isTrue(); - verifyZeroInteractions(watcher3, watcher4); + verifyNoInteractions(watcher3, watcher4); } @Test @@ -2133,7 +2133,7 @@ public void multipleEndpointWatchers() { new LbEndpoint("192.168.0.1", 8080, 2, true)), 1, 0)); - verifyZeroInteractions(watcher3); + verifyNoInteractions(watcher3); // Management server sends back another EDS response contains ClusterLoadAssignment for the // other requested cluster. @@ -2517,7 +2517,7 @@ public void addRemoveEndpointWatcherWhileInitialResourceFetchInProgress() { assertThat(fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); assertThat(timeoutTask.isCancelled()).isTrue(); - verifyZeroInteractions(watcher3, watcher4); + verifyNoInteractions(watcher3, watcher4); } @Test diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java index 178fe46bd42..692cba94c38 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java @@ -38,8 +38,8 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableList; @@ -1875,7 +1875,7 @@ public void addRemoveClusterWatcherWhileInitialResourceFetchInProgress() { assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); assertThat(timeoutTask.isCancelled()).isTrue(); - verifyZeroInteractions(watcher3, watcher4); + verifyNoInteractions(watcher3, watcher4); } @Test @@ -2142,7 +2142,7 @@ public void multipleEndpointWatchers() { new LbEndpoint("192.168.0.1", 8080, 2, true)), 1, 0)); - verifyZeroInteractions(watcher3); + verifyNoInteractions(watcher3); // Management server sends back another EDS response contains ClusterLoadAssignment for the // other requested cluster. @@ -2526,7 +2526,7 @@ public void addRemoveEndpointWatcherWhileInitialResourceFetchInProgress() { assertThat(fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); assertThat(timeoutTask.isCancelled()).isTrue(); - verifyZeroInteractions(watcher3, watcher4); + verifyNoInteractions(watcher3, watcher4); } @Test From e92b2275f95c89cc99921f6c70a821de529e14bf Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Mon, 3 Aug 2020 12:51:05 -0700 Subject: [PATCH 43/88] Update to Error Prone 2.4 Most of the changes should be semi-clear why they were made. However, BadImport may not be as obvious: https://errorprone.info/bugpattern/BadImport . That impacted classes named Type, Entry, and Factory. Also PublicContructorForAbstractClass: https://errorprone.info/bugpattern/PublicConstructorForAbstractClass The JdkObsolete issue is already resolved but is not yet in a release. --- .../java/io/grpc/alts/AltsServerBuilder.java | 4 ++-- .../java/io/grpc/alts/internal/TsiPeer.java | 2 +- api/src/main/java/io/grpc/Attributes.java | 7 +++---- .../java/io/grpc/DecompressorRegistry.java | 3 +-- .../main/java/io/grpc/InternalMetadata.java | 5 ++--- .../main/java/io/grpc/InternalWithLogId.java | 2 +- api/src/main/java/io/grpc/Metadata.java | 6 +++--- api/src/test/java/io/grpc/MetadataTest.java | 20 +++++++++---------- .../benchmarks/netty/AbstractBenchmark.java | 3 ++- build.gradle | 4 +++- .../io/grpc/census/CensusTracingModule.java | 9 ++++----- .../io/grpc/census/CensusModulesTest.java | 17 +++++++++------- .../java/io/grpc/AttachDetachBenchmark.java | 3 +-- context/src/main/java/io/grpc/Context.java | 2 ++ .../grpc/internal/AbstractClientStream.java | 2 +- .../io/grpc/internal/AtomicLongCounter.java | 2 +- .../java/io/grpc/internal/BackoffPolicy.java | 2 +- .../grpc/internal/ClientTransportFactory.java | 8 ++++---- .../io/grpc/internal/ContextRunnable.java | 2 +- .../io/grpc/internal/DnsNameResolver.java | 3 +-- .../internal/ForwardingReadableBuffer.java | 2 +- .../internal/JndiResourceResolverFactory.java | 3 ++- .../io/grpc/internal/RetriableStream.java | 4 ++-- .../io/grpc/internal/ServiceConfigState.java | 1 - .../inprocess/InProcessServerBuilderTest.java | 10 +++++----- .../io/grpc/internal/ClientCallImplTest.java | 5 ++--- .../io/grpc/internal/DelayedStreamTest.java | 3 +-- ...ManagedChannelImplGetNameResolverTest.java | 7 +++---- .../test/java/io/grpc/internal/TestUtils.java | 2 +- .../grpc/internal/TransportFrameUtilTest.java | 16 ++++++++------- .../grpc/grpclb/GrpclbClientLoadRecorder.java | 3 +-- .../main/java/io/grpc/grpclb/GrpclbState.java | 3 +-- .../integration/AbstractInteropTest.java | 2 +- .../GrpclbLongLivedAffinityTestClient.java | 4 ++-- .../grpc/testing/integration/Http2Client.java | 6 +++--- .../io/grpc/netty/AbstractHttp2Headers.java | 4 ++-- .../netty/GrpcHttp2ConnectionHandler.java | 2 +- .../grpc/netty/GrpcHttp2OutboundHeaders.java | 12 +++++------ .../java/io/grpc/netty/NettyClientStream.java | 2 +- .../io/grpc/netty/ProtocolNegotiator.java | 2 +- netty/src/main/java/io/grpc/netty/Utils.java | 5 ++--- .../io/grpc/netty/NettyClientHandlerTest.java | 3 ++- .../io/grpc/netty/NettyServerBuilderTest.java | 5 +++-- .../io/grpc/okhttp/OkHttpClientTransport.java | 3 ++- .../io/grpc/okhttp/OkHttpTlsUpgrader.java | 2 +- .../io/grpc/okhttp/internal/Platform.java | 13 +++++++++++- .../java/io/grpc/okhttp/internal/Util.java | 3 +++ .../okhttp/internal/framed/FrameReader.java | 2 +- .../okhttp/internal/framed/FrameWriter.java | 2 +- .../io/grpc/rls/CachingRlsLbClientTest.java | 4 ++-- .../java/io/grpc/rls/RlsLoadBalancerTest.java | 4 ++-- .../java/io/grpc/services/BinlogHelper.java | 9 ++++----- .../io/grpc/services/ChannelzProtoUtil.java | 4 ++-- .../HealthCheckingLoadBalancerFactory.java | 7 +++---- .../HealthCheckingLoadBalancerUtil.java | 12 +++++------ .../io/grpc/services/BinlogHelperTest.java | 9 ++++----- .../grpc/services/ChannelzProtoUtilTest.java | 4 ++-- ...HealthCheckingLoadBalancerFactoryTest.java | 9 ++++----- .../java/io/grpc/xds/LoadStatsStoreImpl.java | 2 ++ .../MeshCaCertificateProvider.java | 2 +- .../internal/sds/ReferenceCountingMap.java | 2 +- .../grpc/xds/internal/sts/StsCredentials.java | 1 + .../io/grpc/xds/ClientLoadCounterTest.java | 4 ++-- 63 files changed, 162 insertions(+), 148 deletions(-) diff --git a/alts/src/main/java/io/grpc/alts/AltsServerBuilder.java b/alts/src/main/java/io/grpc/alts/AltsServerBuilder.java index fdda8caac6e..f23e6612f9e 100644 --- a/alts/src/main/java/io/grpc/alts/AltsServerBuilder.java +++ b/alts/src/main/java/io/grpc/alts/AltsServerBuilder.java @@ -30,7 +30,7 @@ import io.grpc.ServerCallHandler; import io.grpc.ServerInterceptor; import io.grpc.ServerServiceDefinition; -import io.grpc.ServerStreamTracer.Factory; +import io.grpc.ServerStreamTracer; import io.grpc.ServerTransportFilter; import io.grpc.Status; import io.grpc.alts.internal.AltsProtocolNegotiator; @@ -102,7 +102,7 @@ public AltsServerBuilder directExecutor() { /** {@inheritDoc} */ @Override - public AltsServerBuilder addStreamTracerFactory(Factory factory) { + public AltsServerBuilder addStreamTracerFactory(ServerStreamTracer.Factory factory) { delegate.addStreamTracerFactory(factory); return this; } diff --git a/alts/src/main/java/io/grpc/alts/internal/TsiPeer.java b/alts/src/main/java/io/grpc/alts/internal/TsiPeer.java index 01e3c821ad8..c02188c50ac 100644 --- a/alts/src/main/java/io/grpc/alts/internal/TsiPeer.java +++ b/alts/src/main/java/io/grpc/alts/internal/TsiPeer.java @@ -54,7 +54,7 @@ public abstract static class Property { private final String name; private final T value; - public Property(@Nonnull String name, @Nonnull T value) { + protected Property(@Nonnull String name, @Nonnull T value) { this.name = name; this.value = value; } diff --git a/api/src/main/java/io/grpc/Attributes.java b/api/src/main/java/io/grpc/Attributes.java index cadafece3ec..b70cc2bc8c3 100644 --- a/api/src/main/java/io/grpc/Attributes.java +++ b/api/src/main/java/io/grpc/Attributes.java @@ -22,7 +22,6 @@ import java.util.Collections; import java.util.IdentityHashMap; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; @@ -179,7 +178,7 @@ public boolean equals(Object o) { if (data.size() != that.data.size()) { return false; } - for (Entry, Object> e : data.entrySet()) { + for (Map.Entry, Object> e : data.entrySet()) { if (!that.data.containsKey(e.getKey())) { return false; } @@ -202,7 +201,7 @@ public boolean equals(Object o) { @Override public int hashCode() { int hashCode = 0; - for (Entry, Object> e : data.entrySet()) { + for (Map.Entry, Object> e : data.entrySet()) { hashCode += Objects.hashCode(e.getKey(), e.getValue()); } return hashCode; @@ -262,7 +261,7 @@ public Builder setAll(Attributes other) { */ public Attributes build() { if (newdata != null) { - for (Entry, Object> entry : base.data.entrySet()) { + for (Map.Entry, Object> entry : base.data.entrySet()) { if (!newdata.containsKey(entry.getKey())) { newdata.put(entry.getKey(), entry.getValue()); } diff --git a/api/src/main/java/io/grpc/DecompressorRegistry.java b/api/src/main/java/io/grpc/DecompressorRegistry.java index 305c5700aab..ea0433d8d0a 100644 --- a/api/src/main/java/io/grpc/DecompressorRegistry.java +++ b/api/src/main/java/io/grpc/DecompressorRegistry.java @@ -25,7 +25,6 @@ import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; @@ -116,7 +115,7 @@ byte[] getRawAdvertisedMessageEncodings() { @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1704") public Set getAdvertisedMessageEncodings() { Set advertisedDecompressors = new HashSet<>(decompressors.size()); - for (Entry entry : decompressors.entrySet()) { + for (Map.Entry entry : decompressors.entrySet()) { if (entry.getValue().advertised) { advertisedDecompressors.add(entry.getKey()); } diff --git a/api/src/main/java/io/grpc/InternalMetadata.java b/api/src/main/java/io/grpc/InternalMetadata.java index d011eb9e8c7..2823882952f 100644 --- a/api/src/main/java/io/grpc/InternalMetadata.java +++ b/api/src/main/java/io/grpc/InternalMetadata.java @@ -19,7 +19,6 @@ import com.google.common.io.BaseEncoding; import io.grpc.Metadata.AsciiMarshaller; import io.grpc.Metadata.BinaryStreamMarshaller; -import io.grpc.Metadata.Key; import java.nio.charset.Charset; /** @@ -53,13 +52,13 @@ public interface TrustedAsciiMarshaller extends Metadata.TrustedAsciiMarshall = Metadata.BASE64_ENCODING_OMIT_PADDING; @Internal - public static Key keyOf(String name, TrustedAsciiMarshaller marshaller) { + public static Metadata.Key keyOf(String name, TrustedAsciiMarshaller marshaller) { boolean isPseudo = name != null && !name.isEmpty() && name.charAt(0) == ':'; return Metadata.Key.of(name, isPseudo, marshaller); } @Internal - public static Key keyOf(String name, AsciiMarshaller marshaller) { + public static Metadata.Key keyOf(String name, AsciiMarshaller marshaller) { boolean isPseudo = name != null && !name.isEmpty() && name.charAt(0) == ':'; return Metadata.Key.of(name, isPseudo, marshaller); } diff --git a/api/src/main/java/io/grpc/InternalWithLogId.java b/api/src/main/java/io/grpc/InternalWithLogId.java index 930c34583f0..e5051233e34 100644 --- a/api/src/main/java/io/grpc/InternalWithLogId.java +++ b/api/src/main/java/io/grpc/InternalWithLogId.java @@ -28,7 +28,7 @@ public interface InternalWithLogId { * numeric ID that is unique among the instances. * *

The subclasses of this interface usually want to include the log ID in their {@link - * #toString} results. + * Object#toString} results. */ InternalLogId getLogId(); } diff --git a/api/src/main/java/io/grpc/Metadata.java b/api/src/main/java/io/grpc/Metadata.java index a9963cb5b8a..e153fd55691 100644 --- a/api/src/main/java/io/grpc/Metadata.java +++ b/api/src/main/java/io/grpc/Metadata.java @@ -753,14 +753,14 @@ private Key(String name, boolean pseudo, Object marshaller) { } /** - * @return The original name used to create this key. + * Returns the original name used to create this key. */ public final String originalName() { return originalName; } /** - * @return The normalized name for this key. + * Returns the normalized name for this key. */ public final String name() { return name; @@ -870,7 +870,7 @@ T parseBytes(byte[] serialized) { } } - /** A binary key for values which should be serialized lazily to {@Link InputStream}s. */ + /** A binary key for values which should be serialized lazily to {@link InputStream}s. */ private static class LazyStreamBinaryKey extends Key { private final BinaryStreamMarshaller marshaller; diff --git a/api/src/test/java/io/grpc/MetadataTest.java b/api/src/test/java/io/grpc/MetadataTest.java index c9095a82d5a..122a11f85e5 100644 --- a/api/src/test/java/io/grpc/MetadataTest.java +++ b/api/src/test/java/io/grpc/MetadataTest.java @@ -30,7 +30,6 @@ import com.google.common.collect.Lists; import com.google.common.io.ByteStreams; -import io.grpc.Metadata.Key; import io.grpc.internal.GrpcUtil; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -115,9 +114,9 @@ public Fish parseStream(InputStream stream) { private static final byte[] LANCE_BYTES = LANCE.getBytes(US_ASCII); private static final Metadata.Key KEY = Metadata.Key.of("test-bin", FISH_MARSHALLER); private static final Metadata.Key KEY_STREAMED = - Key.of("streamed-bin", FISH_STREAM_MARSHALLER); + Metadata.Key.of("streamed-bin", FISH_STREAM_MARSHALLER); private static final Metadata.Key KEY_IMMUTABLE = - Key.of("immutable-bin", IMMUTABLE_FISH_MARSHALLER); + Metadata.Key.of("immutable-bin", IMMUTABLE_FISH_MARSHALLER); @Test public void noPseudoHeaders() { @@ -348,7 +347,7 @@ public void testKeyCaseHandling() { public void removeIgnoresMissingValue() { Metadata m = new Metadata(); // Any key will work. - Key key = GrpcUtil.USER_AGENT_KEY; + Metadata.Key key = GrpcUtil.USER_AGENT_KEY; boolean success = m.remove(key, "agent"); assertFalse(success); @@ -358,7 +357,7 @@ public void removeIgnoresMissingValue() { public void removeAllIgnoresMissingValue() { Metadata m = new Metadata(); // Any key will work. - Key key = GrpcUtil.USER_AGENT_KEY; + Metadata.Key key = GrpcUtil.USER_AGENT_KEY; Iterable removed = m.removeAll(key); assertNull(removed); @@ -366,9 +365,9 @@ public void removeAllIgnoresMissingValue() { @Test public void keyEqualsHashNameWorks() { - Key k1 = Key.of("case", Metadata.ASCII_STRING_MARSHALLER); + Metadata.Key k1 = Metadata.Key.of("case", Metadata.ASCII_STRING_MARSHALLER); - Key k2 = Key.of("CASE", Metadata.ASCII_STRING_MARSHALLER); + Metadata.Key k2 = Metadata.Key.of("CASE", Metadata.ASCII_STRING_MARSHALLER); assertEquals(k1, k1); assertNotEquals(k1, null); assertNotEquals(k1, new Object(){}); @@ -383,7 +382,7 @@ public void keyEqualsHashNameWorks() { @Test public void invalidKeyName() { try { - Key.of("io.grpc/key1", Metadata.ASCII_STRING_MARSHALLER); + Metadata.Key.of("io.grpc/key1", Metadata.ASCII_STRING_MARSHALLER); fail("Should have thrown"); } catch (IllegalArgumentException e) { assertEquals("Invalid character '/' in key name 'io.grpc/key1'", e.getMessage()); @@ -512,7 +511,8 @@ public String toString() { } } - private static Key copyKey(Key key, Metadata.BinaryStreamMarshaller marshaller) { - return Key.of(key.originalName(), marshaller); + private static Metadata.Key copyKey( + Metadata.Key key, Metadata.BinaryStreamMarshaller marshaller) { + return Metadata.Key.of(key.originalName(), marshaller); } } diff --git a/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/AbstractBenchmark.java b/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/AbstractBenchmark.java index 09c5879018c..0a4d4089d16 100644 --- a/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/AbstractBenchmark.java +++ b/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/AbstractBenchmark.java @@ -137,6 +137,7 @@ public enum ChannelType { * sudo ip addr add dev lo 127.127.127.127/32 label lo:benchmark * */ + @SuppressWarnings("JdkObsolete") // No choice but to use Enumeration private static InetAddress buildBenchmarkAddr() { InetAddress tmp = null; try { @@ -180,7 +181,7 @@ private static InetAddress buildBenchmarkAddr() { private MethodDescriptor flowControlledStreaming; protected ManagedChannel[] channels; - public AbstractBenchmark() { + protected AbstractBenchmark() { } /** diff --git a/build.gradle b/build.gradle index f7ea90dd5cd..8eb98f21b03 100644 --- a/build.gradle +++ b/build.gradle @@ -295,7 +295,7 @@ subprojects { if (rootProject.properties.get('errorProne', true)) { dependencies { - errorprone 'com.google.errorprone:error_prone_core:2.3.4' + errorprone 'com.google.errorprone:error_prone_core:2.4.0' errorproneJavac 'com.google.errorprone:javac:9+181-r4173-1' } } else { @@ -373,6 +373,8 @@ subprojects { options.errorprone.check("UnnecessaryAnonymousClass", CheckSeverity.OFF) // This project targets Java 7 (no time.Duration class) options.errorprone.check("PreferJavaTimeOverload", CheckSeverity.OFF) + // The warning fails to provide a source location + options.errorprone.check("MissingSummary", CheckSeverity.OFF) } compileTestJava { // LinkedList doesn't hurt much in tests and has lots of usages diff --git a/census/src/main/java/io/grpc/census/CensusTracingModule.java b/census/src/main/java/io/grpc/census/CensusTracingModule.java index e9e12941ed4..fc35d89db55 100644 --- a/census/src/main/java/io/grpc/census/CensusTracingModule.java +++ b/census/src/main/java/io/grpc/census/CensusTracingModule.java @@ -34,7 +34,6 @@ import io.opencensus.trace.BlankSpan; import io.opencensus.trace.EndSpanOptions; import io.opencensus.trace.MessageEvent; -import io.opencensus.trace.MessageEvent.Type; import io.opencensus.trace.Span; import io.opencensus.trace.SpanContext; import io.opencensus.trace.Status; @@ -283,14 +282,14 @@ private static final class ClientTracer extends ClientStreamTracer { public void outboundMessageSent( int seqNo, long optionalWireSize, long optionalUncompressedSize) { recordMessageEvent( - span, Type.SENT, seqNo, optionalWireSize, optionalUncompressedSize); + span, MessageEvent.Type.SENT, seqNo, optionalWireSize, optionalUncompressedSize); } @Override public void inboundMessageRead( int seqNo, long optionalWireSize, long optionalUncompressedSize) { recordMessageEvent( - span, Type.RECEIVED, seqNo, optionalWireSize, optionalUncompressedSize); + span, MessageEvent.Type.RECEIVED, seqNo, optionalWireSize, optionalUncompressedSize); } } @@ -349,14 +348,14 @@ public Context filterContext(Context context) { public void outboundMessageSent( int seqNo, long optionalWireSize, long optionalUncompressedSize) { recordMessageEvent( - span, Type.SENT, seqNo, optionalWireSize, optionalUncompressedSize); + span, MessageEvent.Type.SENT, seqNo, optionalWireSize, optionalUncompressedSize); } @Override public void inboundMessageRead( int seqNo, long optionalWireSize, long optionalUncompressedSize) { recordMessageEvent( - span, Type.RECEIVED, seqNo, optionalWireSize, optionalUncompressedSize); + span, MessageEvent.Type.RECEIVED, seqNo, optionalWireSize, optionalUncompressedSize); } } diff --git a/census/src/test/java/io/grpc/census/CensusModulesTest.java b/census/src/test/java/io/grpc/census/CensusModulesTest.java index 248195bc797..135d5012c92 100644 --- a/census/src/test/java/io/grpc/census/CensusModulesTest.java +++ b/census/src/test/java/io/grpc/census/CensusModulesTest.java @@ -84,7 +84,6 @@ import io.opencensus.trace.BlankSpan; import io.opencensus.trace.EndSpanOptions; import io.opencensus.trace.MessageEvent; -import io.opencensus.trace.MessageEvent.Type; import io.opencensus.trace.Span; import io.opencensus.trace.SpanBuilder; import io.opencensus.trace.SpanContext; @@ -542,11 +541,13 @@ public void clientBasicTracingDefaultSpan() { inOrder.verify(spyClientSpan, times(3)).addMessageEvent(messageEventCaptor.capture()); List events = messageEventCaptor.getAllValues(); assertEquals( - MessageEvent.builder(Type.SENT, 0).setCompressedMessageSize(882).build(), events.get(0)); + MessageEvent.builder(MessageEvent.Type.SENT, 0).setCompressedMessageSize(882).build(), + events.get(0)); assertEquals( - MessageEvent.builder(Type.SENT, 1).setUncompressedMessageSize(27).build(), events.get(1)); + MessageEvent.builder(MessageEvent.Type.SENT, 1).setUncompressedMessageSize(27).build(), + events.get(1)); assertEquals( - MessageEvent.builder(Type.RECEIVED, 0) + MessageEvent.builder(MessageEvent.Type.RECEIVED, 0) .setCompressedMessageSize(255) .setUncompressedMessageSize(90) .build(), @@ -1065,11 +1066,13 @@ public void serverBasicTracingNoHeaders() { inOrder.verify(spyServerSpan, times(3)).addMessageEvent(messageEventCaptor.capture()); List events = messageEventCaptor.getAllValues(); assertEquals( - MessageEvent.builder(Type.SENT, 0).setCompressedMessageSize(882).build(), events.get(0)); + MessageEvent.builder(MessageEvent.Type.SENT, 0).setCompressedMessageSize(882).build(), + events.get(0)); assertEquals( - MessageEvent.builder(Type.SENT, 1).setUncompressedMessageSize(27).build(), events.get(1)); + MessageEvent.builder(MessageEvent.Type.SENT, 1).setUncompressedMessageSize(27).build(), + events.get(1)); assertEquals( - MessageEvent.builder(Type.RECEIVED, 0) + MessageEvent.builder(MessageEvent.Type.RECEIVED, 0) .setCompressedMessageSize(255) .setUncompressedMessageSize(90) .build(), diff --git a/context/src/jmh/java/io/grpc/AttachDetachBenchmark.java b/context/src/jmh/java/io/grpc/AttachDetachBenchmark.java index 8a9321e968a..10e845e7928 100644 --- a/context/src/jmh/java/io/grpc/AttachDetachBenchmark.java +++ b/context/src/jmh/java/io/grpc/AttachDetachBenchmark.java @@ -16,7 +16,6 @@ package io.grpc; -import io.grpc.Context.Key; import java.util.concurrent.TimeUnit; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; @@ -30,7 +29,7 @@ @State(Scope.Benchmark) public class AttachDetachBenchmark { - private final Key key = Context.keyWithDefault("key", 9999); + private final Context.Key key = Context.keyWithDefault("key", 9999); private final Context cu = Context.current().withValue(key, 8888); /** diff --git a/context/src/main/java/io/grpc/Context.java b/context/src/main/java/io/grpc/Context.java index da540215bef..ee926b48662 100644 --- a/context/src/main/java/io/grpc/Context.java +++ b/context/src/main/java/io/grpc/Context.java @@ -943,6 +943,8 @@ public void close() { */ public interface CancellationListener { /** + * Notifies that a context was cancelled. + * * @param context the newly cancelled context. */ void cancelled(Context context); diff --git a/core/src/main/java/io/grpc/internal/AbstractClientStream.java b/core/src/main/java/io/grpc/internal/AbstractClientStream.java index 88d5be6eafa..bf032da3148 100644 --- a/core/src/main/java/io/grpc/internal/AbstractClientStream.java +++ b/core/src/main/java/io/grpc/internal/AbstractClientStream.java @@ -75,7 +75,7 @@ protected interface Sink { * @param endOfStream {@code true} if this is the last frame; {@code flush} is guaranteed to be * {@code true} if this is {@code true} * @param flush {@code true} if more data may not be arriving soon - * @Param numMessages the number of messages this series of frames represents, must be >= 0. + * @param numMessages the number of messages this series of frames represents, must be >= 0. */ void writeFrame( @Nullable WritableBuffer frame, boolean endOfStream, boolean flush, int numMessages); diff --git a/core/src/main/java/io/grpc/internal/AtomicLongCounter.java b/core/src/main/java/io/grpc/internal/AtomicLongCounter.java index 83c685afde6..6fab7930a06 100644 --- a/core/src/main/java/io/grpc/internal/AtomicLongCounter.java +++ b/core/src/main/java/io/grpc/internal/AtomicLongCounter.java @@ -19,7 +19,7 @@ import java.util.concurrent.atomic.AtomicLong; /** - * An implementation of {@link LongCounter} that is just an {@Link AtomicLong}. + * An implementation of {@link LongCounter} that is just an {@link AtomicLong}. */ final class AtomicLongCounter implements LongCounter { private final AtomicLong counter = new AtomicLong(); diff --git a/core/src/main/java/io/grpc/internal/BackoffPolicy.java b/core/src/main/java/io/grpc/internal/BackoffPolicy.java index acbe84b6b3b..cdca4a22606 100644 --- a/core/src/main/java/io/grpc/internal/BackoffPolicy.java +++ b/core/src/main/java/io/grpc/internal/BackoffPolicy.java @@ -25,7 +25,7 @@ public interface Provider { } /** - * @return The number of nanoseconds to wait. + * Returns the number of nanoseconds to wait. */ long nextBackoffNanos(); } diff --git a/core/src/main/java/io/grpc/internal/ClientTransportFactory.java b/core/src/main/java/io/grpc/internal/ClientTransportFactory.java index 80767ddb0b9..9be5431144f 100644 --- a/core/src/main/java/io/grpc/internal/ClientTransportFactory.java +++ b/core/src/main/java/io/grpc/internal/ClientTransportFactory.java @@ -63,10 +63,10 @@ ConnectionClientTransport newClientTransport( void close(); /** - * Options passed to {@link #newClientTransport(SocketAddress, ClientTransportOptions)}. Although - * it is safe to save this object if received, it is generally expected that the useful fields are - * copied and then the options object is discarded. This allows using {@code final} for those - * fields as well as avoids retaining unused objects contained in the options. + * Options passed to {@link #newClientTransport}. Although it is safe to save this object if + * received, it is generally expected that the useful fields are copied and then the options + * object is discarded. This allows using {@code final} for those fields as well as avoids + * retaining unused objects contained in the options. */ final class ClientTransportOptions { private ChannelLogger channelLogger; diff --git a/core/src/main/java/io/grpc/internal/ContextRunnable.java b/core/src/main/java/io/grpc/internal/ContextRunnable.java index 9d82b3962b9..b4c16a3249f 100644 --- a/core/src/main/java/io/grpc/internal/ContextRunnable.java +++ b/core/src/main/java/io/grpc/internal/ContextRunnable.java @@ -26,7 +26,7 @@ abstract class ContextRunnable implements Runnable { private final Context context; - public ContextRunnable(Context context) { + protected ContextRunnable(Context context) { this.context = context; } diff --git a/core/src/main/java/io/grpc/internal/DnsNameResolver.java b/core/src/main/java/io/grpc/internal/DnsNameResolver.java index 7241b31e7c1..bc994a01035 100644 --- a/core/src/main/java/io/grpc/internal/DnsNameResolver.java +++ b/core/src/main/java/io/grpc/internal/DnsNameResolver.java @@ -46,7 +46,6 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Random; import java.util.Set; import java.util.concurrent.Executor; @@ -488,7 +487,7 @@ private static long getNetworkAddressCacheTtlNanos(boolean isAndroid) { @VisibleForTesting static Map maybeChooseServiceConfig( Map choice, Random random, String hostname) { - for (Entry entry : choice.entrySet()) { + for (Map.Entry entry : choice.entrySet()) { Verify.verify(SERVICE_CONFIG_CHOICE_KEYS.contains(entry.getKey()), "Bad key: %s", entry); } diff --git a/core/src/main/java/io/grpc/internal/ForwardingReadableBuffer.java b/core/src/main/java/io/grpc/internal/ForwardingReadableBuffer.java index 03fafee768b..954d0ac5486 100644 --- a/core/src/main/java/io/grpc/internal/ForwardingReadableBuffer.java +++ b/core/src/main/java/io/grpc/internal/ForwardingReadableBuffer.java @@ -37,7 +37,7 @@ public abstract class ForwardingReadableBuffer implements ReadableBuffer { * * @param buf the underlying buffer */ - public ForwardingReadableBuffer(ReadableBuffer buf) { + protected ForwardingReadableBuffer(ReadableBuffer buf) { this.buf = Preconditions.checkNotNull(buf, "buf"); } diff --git a/core/src/main/java/io/grpc/internal/JndiResourceResolverFactory.java b/core/src/main/java/io/grpc/internal/JndiResourceResolverFactory.java index c9cb567b9c1..a13c9fe4665 100644 --- a/core/src/main/java/io/grpc/internal/JndiResourceResolverFactory.java +++ b/core/src/main/java/io/grpc/internal/JndiResourceResolverFactory.java @@ -194,6 +194,8 @@ static String unquote(String txtRecord) { @VisibleForTesting @IgnoreJRERequirement + // Hashtable is required. https://github.com/google/error-prone/issues/1766 + @SuppressWarnings("JdkObsolete") // javax.naming.* is only loaded reflectively and is never loaded for Android // The lint issue id is supposed to be "InvalidPackage" but it doesn't work, don't know why. // Use "all" as the lint issue id to suppress all types of lint error. @@ -205,7 +207,6 @@ public List getAllRecords(String recordType, String name) throws NamingE String[] rrType = new String[]{recordType}; List records = new ArrayList<>(); - @SuppressWarnings("JdkObsolete") Hashtable env = new Hashtable<>(); env.put("com.sun.jndi.ldap.connect.timeout", "5000"); env.put("com.sun.jndi.ldap.read.timeout", "5000"); diff --git a/core/src/main/java/io/grpc/internal/RetriableStream.java b/core/src/main/java/io/grpc/internal/RetriableStream.java index f9705edbfda..cf3c9afe48d 100644 --- a/core/src/main/java/io/grpc/internal/RetriableStream.java +++ b/core/src/main/java/io/grpc/internal/RetriableStream.java @@ -469,8 +469,8 @@ private void delayOrExecute(BufferEntry bufferEntry) { } /** - * Do not use it directly. Use {@link #sendMessage(ReqT)} instead because we don't use InputStream - * for buffering. + * Do not use it directly. Use {@link #sendMessage(Object)} instead because we don't use + * InputStream for buffering. */ @Override public final void writeMessage(InputStream message) { diff --git a/core/src/main/java/io/grpc/internal/ServiceConfigState.java b/core/src/main/java/io/grpc/internal/ServiceConfigState.java index da4c4ce1016..73486bb0bd9 100644 --- a/core/src/main/java/io/grpc/internal/ServiceConfigState.java +++ b/core/src/main/java/io/grpc/internal/ServiceConfigState.java @@ -37,7 +37,6 @@ final class ServiceConfigState { /** * @param defaultServiceConfig The initial service config, or {@code null} if absent. * @param lookUpServiceConfig {@code true} if service config updates might occur. - * @param syncCtx The synchronization context that this is accessed from. */ ServiceConfigState( @Nullable ManagedChannelServiceConfig defaultServiceConfig, diff --git a/core/src/test/java/io/grpc/inprocess/InProcessServerBuilderTest.java b/core/src/test/java/io/grpc/inprocess/InProcessServerBuilderTest.java index 769f0f28fe8..9f56a3562d2 100644 --- a/core/src/test/java/io/grpc/inprocess/InProcessServerBuilderTest.java +++ b/core/src/test/java/io/grpc/inprocess/InProcessServerBuilderTest.java @@ -23,7 +23,7 @@ import static org.junit.Assert.assertSame; import com.google.common.collect.Iterables; -import io.grpc.ServerStreamTracer.Factory; +import io.grpc.ServerStreamTracer; import io.grpc.internal.FakeClock; import io.grpc.internal.ObjectPool; import io.grpc.internal.SharedResourcePool; @@ -55,8 +55,8 @@ public void generateName() { @Test public void scheduledExecutorService_default() { InProcessServerBuilder builder = InProcessServerBuilder.forName("foo"); - InProcessServer server = - Iterables.getOnlyElement(builder.buildTransportServers(new ArrayList())); + InProcessServer server = Iterables.getOnlyElement( + builder.buildTransportServers(new ArrayList())); ObjectPool scheduledExecutorServicePool = server.getScheduledExecutorServicePool(); @@ -80,8 +80,8 @@ public void scheduledExecutorService_custom() { InProcessServerBuilder builder1 = builder.scheduledExecutorService(scheduledExecutorService); assertSame(builder, builder1); - InProcessServer server = - Iterables.getOnlyElement(builder1.buildTransportServers(new ArrayList())); + InProcessServer server = Iterables.getOnlyElement( + builder1.buildTransportServers(new ArrayList())); ObjectPool scheduledExecutorServicePool = server.getScheduledExecutorServicePool(); diff --git a/core/src/test/java/io/grpc/internal/ClientCallImplTest.java b/core/src/test/java/io/grpc/internal/ClientCallImplTest.java index e5c944646f6..ac49f9e3c8c 100644 --- a/core/src/test/java/io/grpc/internal/ClientCallImplTest.java +++ b/core/src/test/java/io/grpc/internal/ClientCallImplTest.java @@ -42,7 +42,6 @@ import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.SettableFuture; import io.grpc.Attributes; -import io.grpc.Attributes.Key; import io.grpc.CallOptions; import io.grpc.ClientCall; import io.grpc.ClientStreamTracer; @@ -1007,8 +1006,8 @@ public void getAttributes() { ClientCallImpl call = new ClientCallImpl<>( method, MoreExecutors.directExecutor(), baseCallOptions, clientStreamProvider, deadlineCancellationExecutor, channelCallTracer); - Attributes attrs = - Attributes.newBuilder().set(Key.create("fake key"), "fake value").build(); + Attributes attrs = Attributes.newBuilder().set( + Attributes.Key.create("fake key"), "fake value").build(); when(stream.getAttributes()).thenReturn(attrs); assertNotEquals(attrs, call.getAttributes()); diff --git a/core/src/test/java/io/grpc/internal/DelayedStreamTest.java b/core/src/test/java/io/grpc/internal/DelayedStreamTest.java index 564b7e59ceb..393a6c6e6d0 100644 --- a/core/src/test/java/io/grpc/internal/DelayedStreamTest.java +++ b/core/src/test/java/io/grpc/internal/DelayedStreamTest.java @@ -33,7 +33,6 @@ import static org.mockito.Mockito.when; import io.grpc.Attributes; -import io.grpc.Attributes.Key; import io.grpc.Codec; import io.grpc.DecompressorRegistry; import io.grpc.Metadata; @@ -184,7 +183,7 @@ public void setStream_isReady() { @Test public void setStream_getAttributes() { Attributes attributes = - Attributes.newBuilder().set(Key.create("fakeKey"), "fakeValue").build(); + Attributes.newBuilder().set(Attributes.Key.create("fakeKey"), "fakeValue").build(); when(realStream.getAttributes()).thenReturn(attributes); stream.start(listener); diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplGetNameResolverTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplGetNameResolverTest.java index c1d84eb76bf..ff0659db16c 100644 --- a/core/src/test/java/io/grpc/internal/ManagedChannelImplGetNameResolverTest.java +++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplGetNameResolverTest.java @@ -23,7 +23,6 @@ import io.grpc.ChannelLogger; import io.grpc.NameResolver; -import io.grpc.NameResolver.Factory; import io.grpc.NameResolver.ServiceConfigParser; import io.grpc.ProxyDetector; import io.grpc.SynchronizationContext; @@ -104,7 +103,7 @@ public void validTargetStartingWithSlash() throws Exception { @Test public void validTargetNoResovler() { - Factory nameResolverFactory = new NameResolver.Factory() { + NameResolver.Factory nameResolverFactory = new NameResolver.Factory() { @Override public NameResolver newNameResolver(URI targetUri, NameResolver.Args args) { return null; @@ -125,7 +124,7 @@ public String getDefaultScheme() { } private void testValidTarget(String target, String expectedUriString, URI expectedUri) { - Factory nameResolverFactory = new FakeNameResolverFactory(expectedUri.getScheme()); + NameResolver.Factory nameResolverFactory = new FakeNameResolverFactory(expectedUri.getScheme()); FakeNameResolver nameResolver = (FakeNameResolver) ManagedChannelImpl.getNameResolver( target, nameResolverFactory, NAMERESOLVER_ARGS); assertNotNull(nameResolver); @@ -134,7 +133,7 @@ private void testValidTarget(String target, String expectedUriString, URI expect } private void testInvalidTarget(String target) { - Factory nameResolverFactory = new FakeNameResolverFactory("dns"); + NameResolver.Factory nameResolverFactory = new FakeNameResolverFactory("dns"); try { FakeNameResolver nameResolver = (FakeNameResolver) ManagedChannelImpl.getNameResolver( diff --git a/core/src/test/java/io/grpc/internal/TestUtils.java b/core/src/test/java/io/grpc/internal/TestUtils.java index 8ea9afc28a7..7152b4931fa 100644 --- a/core/src/test/java/io/grpc/internal/TestUtils.java +++ b/core/src/test/java/io/grpc/internal/TestUtils.java @@ -47,7 +47,7 @@ public final class TestUtils { public abstract static class StandardLoadBalancerProvider extends LoadBalancerProvider { private final String policyName; - public StandardLoadBalancerProvider(String policyName) { + protected StandardLoadBalancerProvider(String policyName) { this.policyName = policyName; } diff --git a/core/src/test/java/io/grpc/internal/TransportFrameUtilTest.java b/core/src/test/java/io/grpc/internal/TransportFrameUtilTest.java index 8d6bba0f743..3de0a207ddf 100644 --- a/core/src/test/java/io/grpc/internal/TransportFrameUtilTest.java +++ b/core/src/test/java/io/grpc/internal/TransportFrameUtilTest.java @@ -31,7 +31,6 @@ import io.grpc.InternalMetadata; import io.grpc.Metadata; import io.grpc.Metadata.BinaryMarshaller; -import io.grpc.Metadata.Key; import java.util.Arrays; import org.junit.Test; import org.junit.runner.RunWith; @@ -58,11 +57,14 @@ public String parseBytes(byte[] serialized) { } }; - private static final Key PLAIN_STRING = Key.of("plainstring", ASCII_STRING_MARSHALLER); - private static final Key BINARY_STRING = Key.of("string-bin", UTF8_STRING_MARSHALLER); - private static final Key BINARY_STRING_WITHOUT_SUFFIX = - Key.of("string", ASCII_STRING_MARSHALLER); - private static final Key BINARY_BYTES = Key.of("bytes-bin", BINARY_BYTE_MARSHALLER); + private static final Metadata.Key PLAIN_STRING = + Metadata.Key.of("plainstring", ASCII_STRING_MARSHALLER); + private static final Metadata.Key BINARY_STRING = + Metadata.Key.of("string-bin", UTF8_STRING_MARSHALLER); + private static final Metadata.Key BINARY_STRING_WITHOUT_SUFFIX = + Metadata.Key.of("string", ASCII_STRING_MARSHALLER); + private static final Metadata.Key BINARY_BYTES = + Metadata.Key.of("bytes-bin", BINARY_BYTE_MARSHALLER); @Test public void testToHttp2Headers() { @@ -86,7 +88,7 @@ public void testToHttp2Headers() { @Test(expected = IllegalArgumentException.class) public void binaryHeaderWithoutSuffix() { - Key.of("plainstring", UTF8_STRING_MARSHALLER); + Metadata.Key.of("plainstring", UTF8_STRING_MARSHALLER); } @Test diff --git a/grpclb/src/main/java/io/grpc/grpclb/GrpclbClientLoadRecorder.java b/grpclb/src/main/java/io/grpc/grpclb/GrpclbClientLoadRecorder.java index e29296ad307..d27c485dc13 100644 --- a/grpclb/src/main/java/io/grpc/grpclb/GrpclbClientLoadRecorder.java +++ b/grpclb/src/main/java/io/grpc/grpclb/GrpclbClientLoadRecorder.java @@ -28,7 +28,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.Map.Entry; import java.util.concurrent.atomic.AtomicLongFieldUpdater; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; @@ -115,7 +114,7 @@ ClientStats generateLoadReport() { callsDroppedPerToken = new HashMap<>(localCallsDroppedPerToken.size()); } } - for (Entry entry : localCallsDroppedPerToken.entrySet()) { + for (Map.Entry entry : localCallsDroppedPerToken.entrySet()) { statsBuilder.addCallsFinishedWithDrop( ClientStatsPerToken.newBuilder() .setLoadBalanceToken(entry.getKey()) diff --git a/grpclb/src/main/java/io/grpc/grpclb/GrpclbState.java b/grpclb/src/main/java/io/grpc/grpclb/GrpclbState.java index a713b7f7685..b6c99135e22 100644 --- a/grpclb/src/main/java/io/grpc/grpclb/GrpclbState.java +++ b/grpclb/src/main/java/io/grpc/grpclb/GrpclbState.java @@ -71,7 +71,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -424,7 +423,7 @@ private void useRoundRobinLists( newBackendList.add(entry); } // Close Subchannels whose addresses have been delisted - for (Entry, Subchannel> entry : subchannels.entrySet()) { + for (Map.Entry, Subchannel> entry : subchannels.entrySet()) { List eagList = entry.getKey(); if (!newSubchannelMap.containsKey(eagList)) { returnSubchannelToPool(entry.getValue()); diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java b/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java index 14b4b923d13..46122d5f256 100644 --- a/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java +++ b/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java @@ -193,7 +193,7 @@ public Context filterContext(Context context) { /** * Constructor for tests. */ - public AbstractInteropTest() { + protected AbstractInteropTest() { TestRule timeout = Timeout.seconds(60); try { timeout = new DisableOnDebug(timeout); diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/GrpclbLongLivedAffinityTestClient.java b/interop-testing/src/main/java/io/grpc/testing/integration/GrpclbLongLivedAffinityTestClient.java index 4cf8dedbeb9..b98e4f67d38 100644 --- a/interop-testing/src/main/java/io/grpc/testing/integration/GrpclbLongLivedAffinityTestClient.java +++ b/interop-testing/src/main/java/io/grpc/testing/integration/GrpclbLongLivedAffinityTestClient.java @@ -72,8 +72,8 @@ public void run() { private long rpcIntermissionSeconds = 1; private long totalTestSeconds = 60; - protected ManagedChannel channel; - protected TestServiceGrpc.TestServiceBlockingStub blockingStub; + ManagedChannel channel; + TestServiceGrpc.TestServiceBlockingStub blockingStub; private void parseArgs(String[] args) { boolean usage = false; diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/Http2Client.java b/interop-testing/src/main/java/io/grpc/testing/integration/Http2Client.java index d5423c9071b..fbe9e3fa28f 100644 --- a/interop-testing/src/main/java/io/grpc/testing/integration/Http2Client.java +++ b/interop-testing/src/main/java/io/grpc/testing/integration/Http2Client.java @@ -85,9 +85,9 @@ public void run() { private Tester tester = new Tester(); private ListeningExecutorService threadpool; - protected ManagedChannel channel; - protected TestServiceGrpc.TestServiceBlockingStub blockingStub; - protected TestServiceGrpc.TestServiceStub asyncStub; + ManagedChannel channel; + TestServiceGrpc.TestServiceBlockingStub blockingStub; + TestServiceGrpc.TestServiceStub asyncStub; private void parseArgs(String[] args) { boolean usage = false; diff --git a/netty/src/main/java/io/grpc/netty/AbstractHttp2Headers.java b/netty/src/main/java/io/grpc/netty/AbstractHttp2Headers.java index 1dc3f1175ac..7e3c7144952 100644 --- a/netty/src/main/java/io/grpc/netty/AbstractHttp2Headers.java +++ b/netty/src/main/java/io/grpc/netty/AbstractHttp2Headers.java @@ -20,7 +20,7 @@ import io.netty.handler.codec.http2.Http2Headers; import java.util.Iterator; import java.util.List; -import java.util.Map.Entry; +import java.util.Map; import java.util.Set; abstract class AbstractHttp2Headers implements Http2Headers { @@ -496,7 +496,7 @@ public Http2Headers clear() { } @Override - public Iterator> iterator() { + public Iterator> iterator() { throw new UnsupportedOperationException(); } diff --git a/netty/src/main/java/io/grpc/netty/GrpcHttp2ConnectionHandler.java b/netty/src/main/java/io/grpc/netty/GrpcHttp2ConnectionHandler.java index 2c127b16b10..65cac8d1eaf 100644 --- a/netty/src/main/java/io/grpc/netty/GrpcHttp2ConnectionHandler.java +++ b/netty/src/main/java/io/grpc/netty/GrpcHttp2ConnectionHandler.java @@ -35,7 +35,7 @@ public abstract class GrpcHttp2ConnectionHandler extends Http2ConnectionHandler @Nullable protected final ChannelPromise channelUnused; - public GrpcHttp2ConnectionHandler( + protected GrpcHttp2ConnectionHandler( ChannelPromise channelUnused, Http2ConnectionDecoder decoder, Http2ConnectionEncoder encoder, diff --git a/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java b/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java index 1efbf234e7a..0489e135813 100644 --- a/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java +++ b/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java @@ -19,7 +19,7 @@ import io.netty.handler.codec.http2.Http2Headers; import io.netty.util.AsciiString; import java.util.Iterator; -import java.util.Map.Entry; +import java.util.Map; import java.util.NoSuchElementException; /** @@ -77,7 +77,7 @@ public CharSequence status() { } @Override - public Iterator> iterator() { + public Iterator> iterator() { return new Itr(); } @@ -86,8 +86,8 @@ public int size() { return (normalHeaders.length + preHeaders.length) / 2; } - private class Itr implements Entry, - Iterator> { + private class Itr implements Map.Entry, + Iterator> { private int idx; private AsciiString[] current = preHeaders.length != 0 ? preHeaders : normalHeaders; private AsciiString key; @@ -104,7 +104,7 @@ public boolean hasNext() { * speeds before and after. */ @Override - public Entry next() { + public Map.Entry next() { if (hasNext()) { key = current[idx]; value = current[idx + 1]; @@ -144,7 +144,7 @@ public void remove() { public String toString() { StringBuilder builder = new StringBuilder(getClass().getSimpleName()).append('['); String separator = ""; - for (Entry e : this) { + for (Map.Entry e : this) { CharSequence name = e.getKey(); CharSequence value = e.getValue(); builder.append(separator); diff --git a/netty/src/main/java/io/grpc/netty/NettyClientStream.java b/netty/src/main/java/io/grpc/netty/NettyClientStream.java index 1b7ee1301ca..3e044a39649 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientStream.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientStream.java @@ -231,7 +231,7 @@ public abstract static class TransportState extends Http2ClientStreamTransportSt private Http2Stream http2Stream; private Tag tag; - public TransportState( + protected TransportState( NettyClientHandler handler, EventLoop eventLoop, int maxMessageSize, diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java index 7dad1af20a9..c0bf5564805 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java @@ -40,7 +40,7 @@ interface ProtocolNegotiator { /** * Releases resources held by this negotiator. Called when the Channel transitions to terminated * or when InternalServer is shutdown (depending on client or server). That means handlers - * returned by {@link #newHandler()} can outlive their parent negotiator on server-side, but not + * returned by {@link #newHandler} can outlive their parent negotiator on server-side, but not * on client-side. */ void close(); diff --git a/netty/src/main/java/io/grpc/netty/Utils.java b/netty/src/main/java/io/grpc/netty/Utils.java index a3582b4004d..ebc54a14da5 100644 --- a/netty/src/main/java/io/grpc/netty/Utils.java +++ b/netty/src/main/java/io/grpc/netty/Utils.java @@ -56,7 +56,6 @@ import java.nio.channels.ClosedChannelException; import java.nio.channels.UnresolvedAddressException; import java.util.Map; -import java.util.Map.Entry; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.logging.Level; @@ -455,7 +454,7 @@ static InternalChannelz.SocketOptions getSocketOptions(Channel channel) { b.setSocketOptionTimeoutMillis(timeoutMillis); } - for (Entry, Object> opt : config.getOptions().entrySet()) { + for (Map.Entry, Object> opt : config.getOptions().entrySet()) { ChannelOption key = opt.getKey(); // Constants are pooled, so there should only be one instance of each constant if (key.equals(SO_LINGER) || key.equals(SO_TIMEOUT)) { @@ -470,7 +469,7 @@ static InternalChannelz.SocketOptions getSocketOptions(Channel channel) { = NettySocketSupport.getNativeSocketOptions(channel); if (nativeOptions != null) { b.setTcpInfo(nativeOptions.tcpInfo); // may be null - for (Entry entry : nativeOptions.otherInfo.entrySet()) { + for (Map.Entry entry : nativeOptions.otherInfo.entrySet()) { b.addOption(entry.getKey(), entry.getValue()); } } diff --git a/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java b/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java index 324f073d974..17586ef9256 100644 --- a/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java @@ -62,6 +62,7 @@ import io.grpc.internal.ClientTransport.PingCallback; import io.grpc.internal.GrpcUtil; import io.grpc.internal.KeepAliveManager; +import io.grpc.internal.ManagedClientTransport; import io.grpc.internal.StatsTraceContext; import io.grpc.internal.StreamListener; import io.grpc.internal.TransportTracer; @@ -129,7 +130,7 @@ public class NettyClientHandlerTest extends NettyHandlerTestBase servers = builder.buildTransportServers(ImmutableList.of()); + List servers = + builder.buildTransportServers(ImmutableList.of()); Truth.assertThat(servers).hasSize(2); } diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java index 8bfd50a2c2f..78b1aa82064 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java @@ -75,6 +75,7 @@ import java.net.Socket; import java.net.URI; import java.util.Collections; +import java.util.Deque; import java.util.EnumMap; import java.util.HashMap; import java.util.Iterator; @@ -188,7 +189,7 @@ private static Map buildErrorCodeToStatusMap() { private int maxConcurrentStreams = 0; @SuppressWarnings("JdkObsolete") // Usage is bursty; want low memory usage when empty @GuardedBy("lock") - private final LinkedList pendingStreams = new LinkedList<>(); + private final Deque pendingStreams = new LinkedList<>(); private final ConnectionSpec connectionSpec; private FrameWriter testFrameWriter; private ScheduledExecutorService scheduler; diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpTlsUpgrader.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpTlsUpgrader.java index d5cf69bba11..1004dcd93f9 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpTlsUpgrader.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpTlsUpgrader.java @@ -85,7 +85,7 @@ public static SSLSocket upgrade(SSLSocketFactory sslSocketFactory, * @see RFC2732 * @see RFC5280 * - * @return {@param host} in a form consistent with X509 certificates + * @return {@code host} in a form consistent with X509 certificates */ @VisibleForTesting static String canonicalizeHost(String host) { diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Platform.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Platform.java index 81dadb5d889..45d874e300c 100644 --- a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Platform.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Platform.java @@ -177,7 +177,9 @@ private static Platform findPlatform() { trafficStatsTagSocket = trafficStats.getMethod("tagSocket", Socket.class); trafficStatsUntagSocket = trafficStats.getMethod("untagSocket", Socket.class); } catch (ClassNotFoundException ignored) { + // On older Android } catch (NoSuchMethodException ignored) { + // On older Android } TlsExtensionType tlsExtensionType; @@ -244,10 +246,15 @@ public Method run() throws Exception { }); return new JdkAlpnPlatform(sslProvider, setApplicationProtocols, getApplicationProtocol); } catch (NoSuchAlgorithmException ignored) { + // On older Java } catch (KeyManagementException ignored) { + // On older Java } catch (PrivilegedActionException ignored) { + // On older Java } catch (IllegalAccessException ignored) { + // On older Java } catch (InvocationTargetException ignored) { + // On older Java } // Find Jetty's ALPN extension for OpenJDK. @@ -264,7 +271,9 @@ public Method run() throws Exception { putMethod, getMethod, removeMethod, clientProviderClass, serverProviderClass, sslProvider); } catch (ClassNotFoundException ignored) { + // No Jetty ALPN } catch (NoSuchMethodException ignored) { + // Weird Jetty ALPN } // TODO(ericgribkoff) Return null here @@ -512,7 +521,9 @@ public TlsExtensionType getTlsExtensionType() { removeMethod.invoke(null, sslSocket); } catch (IllegalAccessException ignored) { throw new AssertionError(); - } catch (InvocationTargetException ignored) { + } catch (InvocationTargetException ex) { + // This would be very surprising and there's not much to do about it + logger.log(Level.FINE, "Failed to remove SSLSocket from Jetty ALPN", ex); } } diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Util.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Util.java index d0239d1e71d..556d849c705 100644 --- a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Util.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Util.java @@ -74,6 +74,7 @@ public static void closeQuietly(Closeable closeable) { } catch (RuntimeException rethrown) { throw rethrown; } catch (Exception ignored) { + // The method is defined to ignore checked exceptions } } } @@ -91,6 +92,7 @@ public static void closeQuietly(Socket socket) { } catch (RuntimeException rethrown) { throw rethrown; } catch (Exception ignored) { + // The method is defined to ignore checked exceptions } } } @@ -106,6 +108,7 @@ public static void closeQuietly(ServerSocket serverSocket) { } catch (RuntimeException rethrown) { throw rethrown; } catch (Exception ignored) { + // The method is defined to ignore checked exceptions } } } diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/FrameReader.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/FrameReader.java index 20c50f7cca2..585ccb15355 100644 --- a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/FrameReader.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/FrameReader.java @@ -111,7 +111,7 @@ void headers(boolean outFinished, boolean inFinished, int streamId, int associat * @param promisedStreamId server-initiated stream ID. Must be an even * number. * @param requestHeaders minimally includes {@code :method}, {@code :scheme}, - * {@code :authority}, and (@code :path}. + * {@code :authority}, and {@code :path}. */ void pushPromise(int streamId, int promisedStreamId, List requestHeaders) throws IOException; diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/FrameWriter.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/FrameWriter.java index 333e06c86f2..26087f3ce1c 100644 --- a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/FrameWriter.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/FrameWriter.java @@ -45,7 +45,7 @@ public interface FrameWriter extends Closeable { * @param promisedStreamId server-initiated stream ID. Must be an even * number. * @param requestHeaders minimally includes {@code :method}, {@code :scheme}, - * {@code :authority}, and (@code :path}. + * {@code :authority}, and {@code :path}. */ void pushPromise(int streamId, int promisedStreamId, List

requestHeaders) throws IOException; diff --git a/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java b/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java index c846f1e26dd..554dcdd6625 100644 --- a/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java +++ b/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java @@ -42,7 +42,7 @@ import io.grpc.LoadBalancerProvider; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; -import io.grpc.NameResolver.Factory; +import io.grpc.NameResolver; import io.grpc.Status; import io.grpc.SynchronizationContext; import io.grpc.inprocess.InProcessChannelBuilder; @@ -521,7 +521,7 @@ public void updateBalancingState( @Override @Deprecated - public Factory getNameResolverFactory() { + public NameResolver.Factory getNameResolverFactory() { throw new UnsupportedOperationException(); } diff --git a/rls/src/test/java/io/grpc/rls/RlsLoadBalancerTest.java b/rls/src/test/java/io/grpc/rls/RlsLoadBalancerTest.java index a8ac6c3a05b..045c00c3032 100644 --- a/rls/src/test/java/io/grpc/rls/RlsLoadBalancerTest.java +++ b/rls/src/test/java/io/grpc/rls/RlsLoadBalancerTest.java @@ -52,7 +52,7 @@ import io.grpc.MethodDescriptor.Marshaller; import io.grpc.MethodDescriptor.MethodType; import io.grpc.NameResolver.ConfigOrError; -import io.grpc.NameResolver.Factory; +import io.grpc.NameResolver; import io.grpc.Status; import io.grpc.SynchronizationContext; import io.grpc.inprocess.InProcessChannelBuilder; @@ -509,7 +509,7 @@ public void updateBalancingState( @Override @Deprecated - public Factory getNameResolverFactory() { + public NameResolver.Factory getNameResolverFactory() { throw new UnsupportedOperationException(); } diff --git a/services/src/main/java/io/grpc/services/BinlogHelper.java b/services/src/main/java/io/grpc/services/BinlogHelper.java index 27250f05342..d8923160e5c 100644 --- a/services/src/main/java/io/grpc/services/BinlogHelper.java +++ b/services/src/main/java/io/grpc/services/BinlogHelper.java @@ -50,7 +50,6 @@ import io.grpc.ServerInterceptor; import io.grpc.Status; import io.grpc.binarylog.v1.Address; -import io.grpc.binarylog.v1.Address.Type; import io.grpc.binarylog.v1.GrpcLogEntry; import io.grpc.binarylog.v1.GrpcLogEntry.EventType; import io.grpc.binarylog.v1.Message; @@ -791,10 +790,10 @@ static Address socketToProto(SocketAddress address) { if (address instanceof InetSocketAddress) { InetAddress inetAddress = ((InetSocketAddress) address).getAddress(); if (inetAddress instanceof Inet4Address) { - builder.setType(Type.TYPE_IPV4) + builder.setType(Address.Type.TYPE_IPV4) .setAddress(InetAddressUtil.toAddrString(inetAddress)); } else if (inetAddress instanceof Inet6Address) { - builder.setType(Type.TYPE_IPV6) + builder.setType(Address.Type.TYPE_IPV6) .setAddress(InetAddressUtil.toAddrString(inetAddress)); } else { logger.log(Level.SEVERE, "unknown type of InetSocketAddress: {}", address); @@ -803,10 +802,10 @@ static Address socketToProto(SocketAddress address) { builder.setIpPort(((InetSocketAddress) address).getPort()); } else if (address.getClass().getName().equals("io.netty.channel.unix.DomainSocketAddress")) { // To avoid a compile time dependency on grpc-netty, we check against the runtime class name. - builder.setType(Type.TYPE_UNIX) + builder.setType(Address.Type.TYPE_UNIX) .setAddress(address.toString()); } else { - builder.setType(Type.TYPE_UNKNOWN).setAddress(address.toString()); + builder.setType(Address.Type.TYPE_UNKNOWN).setAddress(address.toString()); } return builder.build(); } diff --git a/services/src/main/java/io/grpc/services/ChannelzProtoUtil.java b/services/src/main/java/io/grpc/services/ChannelzProtoUtil.java index 16c4b045bd4..349995d9f80 100644 --- a/services/src/main/java/io/grpc/services/ChannelzProtoUtil.java +++ b/services/src/main/java/io/grpc/services/ChannelzProtoUtil.java @@ -72,7 +72,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map.Entry; +import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; @@ -332,7 +332,7 @@ static List toSocketOptionsList(InternalChannelz.SocketOptions opt if (options.tcpInfo != null) { ret.add(toSocketOptionTcpInfo(options.tcpInfo)); } - for (Entry entry : options.others.entrySet()) { + for (Map.Entry entry : options.others.entrySet()) { ret.add(toSocketOptionAdditional(entry.getKey(), entry.getValue())); } return ret; diff --git a/services/src/main/java/io/grpc/services/HealthCheckingLoadBalancerFactory.java b/services/src/main/java/io/grpc/services/HealthCheckingLoadBalancerFactory.java index 6238ab62eb6..e233d94a98a 100644 --- a/services/src/main/java/io/grpc/services/HealthCheckingLoadBalancerFactory.java +++ b/services/src/main/java/io/grpc/services/HealthCheckingLoadBalancerFactory.java @@ -35,7 +35,6 @@ import io.grpc.ConnectivityStateInfo; import io.grpc.LoadBalancer; import io.grpc.LoadBalancer.CreateSubchannelArgs; -import io.grpc.LoadBalancer.Factory; import io.grpc.LoadBalancer.Helper; import io.grpc.LoadBalancer.Subchannel; import io.grpc.LoadBalancer.SubchannelStateListener; @@ -69,16 +68,16 @@ *

Note the original LoadBalancer must call {@code Helper.createSubchannel()} from the * SynchronizationContext, or it will throw. */ -final class HealthCheckingLoadBalancerFactory extends Factory { +final class HealthCheckingLoadBalancerFactory extends LoadBalancer.Factory { private static final Logger logger = Logger.getLogger(HealthCheckingLoadBalancerFactory.class.getName()); - private final Factory delegateFactory; + private final LoadBalancer.Factory delegateFactory; private final BackoffPolicy.Provider backoffPolicyProvider; private final Supplier stopwatchSupplier; public HealthCheckingLoadBalancerFactory( - Factory delegateFactory, BackoffPolicy.Provider backoffPolicyProvider, + LoadBalancer.Factory delegateFactory, BackoffPolicy.Provider backoffPolicyProvider, Supplier stopwatchSupplier) { this.delegateFactory = checkNotNull(delegateFactory, "delegateFactory"); this.backoffPolicyProvider = checkNotNull(backoffPolicyProvider, "backoffPolicyProvider"); diff --git a/services/src/main/java/io/grpc/services/HealthCheckingLoadBalancerUtil.java b/services/src/main/java/io/grpc/services/HealthCheckingLoadBalancerUtil.java index ab00b6e74d5..c22716f46cf 100644 --- a/services/src/main/java/io/grpc/services/HealthCheckingLoadBalancerUtil.java +++ b/services/src/main/java/io/grpc/services/HealthCheckingLoadBalancerUtil.java @@ -18,7 +18,6 @@ import io.grpc.ExperimentalApi; import io.grpc.LoadBalancer; -import io.grpc.LoadBalancer.Factory; import io.grpc.LoadBalancer.Helper; import io.grpc.internal.ExponentialBackoffPolicy; import io.grpc.internal.GrpcUtil; @@ -35,7 +34,8 @@ private HealthCheckingLoadBalancerUtil() { /** * Creates a health-checking-capable LoadBalancer. This method is used to implement - * health-checking-capable {@link Factory}s, which will typically written this way: + * health-checking-capable {@link io.grpc.LoadBalancer.Factory}s, which will typically written + * this way: * *

    * public class HealthCheckingFooLbFactory extends LoadBalancer.Factory {
@@ -52,15 +52,15 @@ private HealthCheckingLoadBalancerUtil() {
    * 
* *

As a requirement for the original LoadBalancer, it must call - * {@code Helper.createSubchannel()} from the {@link - * io.grpc.LoadBalancer.Helper#getSynchronizationContext() Synchronization Context}, or - * {@code createSubchannel()} will throw. + * {@code Helper.createSubchannel()} from the {@link Helper#getSynchronizationContext() + * Synchronization Context}, or {@code createSubchannel()} will throw. * * @param factory the original factory that implements load-balancing logic without health * checking * @param helper the helper passed to the resulting health-checking LoadBalancer. */ - public static LoadBalancer newHealthCheckingLoadBalancer(Factory factory, Helper helper) { + public static LoadBalancer newHealthCheckingLoadBalancer( + LoadBalancer.Factory factory, Helper helper) { HealthCheckingLoadBalancerFactory hcFactory = new HealthCheckingLoadBalancerFactory( factory, new ExponentialBackoffPolicy.Provider(), diff --git a/services/src/test/java/io/grpc/services/BinlogHelperTest.java b/services/src/test/java/io/grpc/services/BinlogHelperTest.java index 81621437914..4229294d272 100644 --- a/services/src/test/java/io/grpc/services/BinlogHelperTest.java +++ b/services/src/test/java/io/grpc/services/BinlogHelperTest.java @@ -59,7 +59,6 @@ import io.grpc.Status; import io.grpc.StatusException; import io.grpc.binarylog.v1.Address; -import io.grpc.binarylog.v1.Address.Type; import io.grpc.binarylog.v1.ClientHeader; import io.grpc.binarylog.v1.GrpcLogEntry; import io.grpc.binarylog.v1.GrpcLogEntry.EventType; @@ -377,7 +376,7 @@ public void socketToProto_ipv4() throws Exception { assertEquals( Address .newBuilder() - .setType(Type.TYPE_IPV4) + .setType(Address.Type.TYPE_IPV4) .setAddress("127.0.0.1") .setIpPort(12345) .build(), @@ -393,7 +392,7 @@ public void socketToProto_ipv6() throws Exception { assertEquals( Address .newBuilder() - .setType(Type.TYPE_IPV6) + .setType(Address.Type.TYPE_IPV6) .setAddress("2001:db8::2:1") // RFC 5952 section 4: ipv6 canonical form required .setIpPort(12345) .build(), @@ -407,7 +406,7 @@ public void socketToProto_unix() throws Exception { assertEquals( Address .newBuilder() - .setType(Type.TYPE_UNIX) + .setType(Address.Type.TYPE_UNIX) .setAddress("/some/path") .build(), BinlogHelper.socketToProto(socketAddress) @@ -424,7 +423,7 @@ public String toString() { }; assertEquals( Address.newBuilder() - .setType(Type.TYPE_UNKNOWN) + .setType(Address.Type.TYPE_UNKNOWN) .setAddress("some-socket-address") .build(), BinlogHelper.socketToProto(unknownSocket)); diff --git a/services/src/test/java/io/grpc/services/ChannelzProtoUtilTest.java b/services/src/test/java/io/grpc/services/ChannelzProtoUtilTest.java index 37a1ae56fc7..45dd8056ddf 100644 --- a/services/src/test/java/io/grpc/services/ChannelzProtoUtilTest.java +++ b/services/src/test/java/io/grpc/services/ChannelzProtoUtilTest.java @@ -85,7 +85,7 @@ import java.security.cert.Certificate; import java.util.Arrays; import java.util.Collections; -import java.util.Map.Entry; +import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -932,7 +932,7 @@ private static SocketOptions.Builder toBuilder(SocketOptions options) { SocketOptions.Builder builder = new SocketOptions.Builder() .setSocketOptionTimeoutMillis(options.soTimeoutMillis) .setSocketOptionLingerSeconds(options.lingerSeconds); - for (Entry entry : options.others.entrySet()) { + for (Map.Entry entry : options.others.entrySet()) { builder.addOption(entry.getKey(), entry.getValue()); } return builder; diff --git a/services/src/test/java/io/grpc/services/HealthCheckingLoadBalancerFactoryTest.java b/services/src/test/java/io/grpc/services/HealthCheckingLoadBalancerFactoryTest.java index e8a80b6d908..d62ef14559b 100644 --- a/services/src/test/java/io/grpc/services/HealthCheckingLoadBalancerFactoryTest.java +++ b/services/src/test/java/io/grpc/services/HealthCheckingLoadBalancerFactoryTest.java @@ -49,7 +49,6 @@ import io.grpc.EquivalentAddressGroup; import io.grpc.LoadBalancer; import io.grpc.LoadBalancer.CreateSubchannelArgs; -import io.grpc.LoadBalancer.Factory; import io.grpc.LoadBalancer.Helper; import io.grpc.LoadBalancer.ResolvedAddresses; import io.grpc.LoadBalancer.Subchannel; @@ -131,8 +130,8 @@ public void uncaughtException(Thread t, Throwable e) { private final Helper origHelper = mock(Helper.class, delegatesTo(new FakeHelper())); // The helper seen by the origLb private Helper wrappedHelper; - private final Factory origLbFactory = - mock(Factory.class, delegatesTo(new Factory() { + private final LoadBalancer.Factory origLbFactory = + mock(LoadBalancer.Factory.class, delegatesTo(new LoadBalancer.Factory() { @Override public LoadBalancer newLoadBalancer(Helper helper) { checkState(wrappedHelper == null, "LoadBalancer already created"); @@ -1056,8 +1055,8 @@ public Void answer(InvocationOnMock invocation) { @Test public void util_newHealthCheckingLoadBalancer() { - Factory hcFactory = - new Factory() { + LoadBalancer.Factory hcFactory = + new LoadBalancer.Factory() { @Override public LoadBalancer newLoadBalancer(Helper helper) { return HealthCheckingLoadBalancerUtil.newHealthCheckingLoadBalancer( diff --git a/xds/src/main/java/io/grpc/xds/LoadStatsStoreImpl.java b/xds/src/main/java/io/grpc/xds/LoadStatsStoreImpl.java index 42ba84e8508..4483854cb4f 100644 --- a/xds/src/main/java/io/grpc/xds/LoadStatsStoreImpl.java +++ b/xds/src/main/java/io/grpc/xds/LoadStatsStoreImpl.java @@ -39,6 +39,8 @@ * client's perspective by maintaining a set of locality counters for each locality it is tracking * loads for. */ +// https://github.com/google/error-prone/issues/1767 +@SuppressWarnings("ModifyCollectionInEnhancedForLoop") @NotThreadSafe final class LoadStatsStoreImpl implements LoadStatsStore { private final String clusterName; diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProvider.java index a4d22faa6a9..348f7013cd4 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProvider.java @@ -30,7 +30,7 @@ final class MeshCaCertificateProvider extends CertificateProvider { private static final Logger logger = Logger.getLogger(MeshCaCertificateProvider.class.getName()); - protected MeshCaCertificateProvider(DistributorWatcher watcher, boolean notifyCertUpdates, + MeshCaCertificateProvider(DistributorWatcher watcher, boolean notifyCertUpdates, String meshCaUrl, String zone, long validitySeconds, int keySize, String alg, String signatureAlg, MeshCaChannelFactory meshCaChannelFactory, BackoffPolicy.Provider backoffPolicyProvider, long renewalGracePeriodSeconds, diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/ReferenceCountingMap.java b/xds/src/main/java/io/grpc/xds/internal/sds/ReferenceCountingMap.java index 9f520ea7bc7..6a3a03a2870 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/ReferenceCountingMap.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/ReferenceCountingMap.java @@ -59,7 +59,7 @@ public V get(K key) { /** * Releases an instance of the given value. * - *

The instance must have been obtained from {@link #get(K)}. Otherwise will throw + *

The instance must have been obtained from {@link #get(Object)}. Otherwise will throw * IllegalArgumentException. * *

Caller must not release a reference more than once. It's advised that you clear the diff --git a/xds/src/main/java/io/grpc/xds/internal/sts/StsCredentials.java b/xds/src/main/java/io/grpc/xds/internal/sts/StsCredentials.java index fde5f1f0a96..9d85ffcecce 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sts/StsCredentials.java +++ b/xds/src/main/java/io/grpc/xds/internal/sts/StsCredentials.java @@ -67,6 +67,7 @@ private StsCredentials( this.transportFactory = transportFactory; } + @SuppressWarnings("JdkObsolete") // We can't use java.time @Override public AccessToken refreshAccessToken() throws IOException { AccessToken tok = getSourceAccessTokenFromFileLocation(); diff --git a/xds/src/test/java/io/grpc/xds/ClientLoadCounterTest.java b/xds/src/test/java/io/grpc/xds/ClientLoadCounterTest.java index bedeaf4eaea..23a9993888b 100644 --- a/xds/src/test/java/io/grpc/xds/ClientLoadCounterTest.java +++ b/xds/src/test/java/io/grpc/xds/ClientLoadCounterTest.java @@ -25,7 +25,6 @@ import com.github.udpa.udpa.data.orca.v1.OrcaLoadReport; import io.grpc.ClientStreamTracer; -import io.grpc.ClientStreamTracer.Factory; import io.grpc.ClientStreamTracer.StreamInfo; import io.grpc.LoadBalancer.PickResult; import io.grpc.LoadBalancer.PickSubchannelArgs; @@ -224,7 +223,8 @@ protected SubchannelPicker delegate() { } @Override - protected Factory wrapTracerFactory(Factory originFactory) { + protected ClientStreamTracer.Factory wrapTracerFactory( + ClientStreamTracer.Factory originFactory) { // NO-OP return originFactory; } From 8c4088a9e95f9bc9af5632da13be19f816808c35 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Mon, 3 Aug 2020 13:15:14 -0700 Subject: [PATCH 44/88] benchmarks: Avoid implementing Future It's a lot of code and there are classes in Guava are better. This was noticed with a lint checker. This commit does change the error-handling behavior, as previous the code wrongly cancelled the Future instead of setting it to have an exception. --- .../io/grpc/benchmarks/qps/AsyncClient.java | 82 ++----------------- 1 file changed, 9 insertions(+), 73 deletions(-) diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/qps/AsyncClient.java b/benchmarks/src/main/java/io/grpc/benchmarks/qps/AsyncClient.java index c740c9cde06..358e4474bb5 100644 --- a/benchmarks/src/main/java/io/grpc/benchmarks/qps/AsyncClient.java +++ b/benchmarks/src/main/java/io/grpc/benchmarks/qps/AsyncClient.java @@ -34,11 +34,10 @@ import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.TRANSPORT; import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.WARMUP_DURATION; -import com.google.common.base.Preconditions; +import com.google.common.util.concurrent.SettableFuture; import com.google.protobuf.ByteString; import io.grpc.Channel; import io.grpc.ManagedChannel; -import io.grpc.Status; import io.grpc.benchmarks.proto.BenchmarkServiceGrpc; import io.grpc.benchmarks.proto.BenchmarkServiceGrpc.BenchmarkServiceStub; import io.grpc.benchmarks.proto.Messages.Payload; @@ -47,7 +46,6 @@ import io.grpc.stub.StreamObserver; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CancellationException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.HdrHistogram.Histogram; @@ -152,7 +150,7 @@ private Future doUnaryCalls(Channel channel, final SimpleRequest requ final long endTime) { final BenchmarkServiceStub stub = BenchmarkServiceGrpc.newStub(channel); final Histogram histogram = new Histogram(HISTOGRAM_MAX_VALUE, HISTOGRAM_PRECISION); - final HistogramFuture future = new HistogramFuture(histogram); + final SettableFuture future = SettableFuture.create(); stub.unaryCall(request, new StreamObserver() { long lastCall = System.nanoTime(); @@ -163,11 +161,7 @@ public void onNext(SimpleResponse value) { @Override public void onError(Throwable t) { - Status status = Status.fromThrowable(t); - System.err.println("Encountered an error in unaryCall. Status is " + status); - t.printStackTrace(); - - future.cancel(true); + future.setException(new RuntimeException("Encountered an error in unaryCall", t)); } @Override @@ -180,7 +174,7 @@ public void onCompleted() { if (endTime - now > 0) { stub.unaryCall(request, this); } else { - future.done(); + future.set(histogram); } } }); @@ -192,7 +186,7 @@ private static Future doStreamingCalls(Channel channel, final SimpleR final long endTime) { final BenchmarkServiceStub stub = BenchmarkServiceGrpc.newStub(channel); final Histogram histogram = new Histogram(HISTOGRAM_MAX_VALUE, HISTOGRAM_PRECISION); - final HistogramFuture future = new HistogramFuture(histogram); + final SettableFuture future = SettableFuture.create(); ThisIsAHackStreamObserver responseObserver = new ThisIsAHackStreamObserver(request, histogram, future, endTime); @@ -211,7 +205,7 @@ private static class ThisIsAHackStreamObserver implements StreamObserver future; final long endTime; long lastCall = System.nanoTime(); @@ -219,7 +213,7 @@ private static class ThisIsAHackStreamObserver implements StreamObserver future, long endTime) { this.request = request; this.histogram = histogram; @@ -243,16 +237,12 @@ public void onNext(SimpleResponse value) { @Override public void onError(Throwable t) { - Status status = Status.fromThrowable(t); - System.err.println("Encountered an error in streamingCall. Status is " + status); - t.printStackTrace(); - - future.cancel(true); + future.setException(new RuntimeException("Encountered an error in streamingCall", t)); } @Override public void onCompleted() { - future.done(); + future.set(histogram); } } @@ -318,58 +308,4 @@ public static void main(String... args) throws Exception { AsyncClient client = new AsyncClient(config); client.run(); } - - private static class HistogramFuture implements Future { - private final Histogram histogram; - private boolean canceled; - private boolean done; - - HistogramFuture(Histogram histogram) { - Preconditions.checkNotNull(histogram, "histogram"); - this.histogram = histogram; - } - - @Override - public synchronized boolean cancel(boolean mayInterruptIfRunning) { - if (!done && !canceled) { - canceled = true; - notifyAll(); - return true; - } - return false; - } - - @Override - public synchronized boolean isCancelled() { - return canceled; - } - - @Override - public synchronized boolean isDone() { - return done || canceled; - } - - @Override - public synchronized Histogram get() throws InterruptedException { - while (!isDone() && !isCancelled()) { - wait(); - } - - if (isCancelled()) { - throw new CancellationException(); - } - - return histogram; - } - - @Override - public Histogram get(long timeout, TimeUnit unit) { - throw new UnsupportedOperationException(); - } - - private synchronized void done() { - done = true; - notifyAll(); - } - } } From f59c9a9c6b9fe0194d21115a83b554d4027a4669 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 6 Aug 2020 08:18:40 -0700 Subject: [PATCH 45/88] compiler: Swap to ABSL_FALLTHROUGH_INTENDED FALLTHROUGH_INTENDED was defined by Abseil, but is now getting an ABSL prefix and the old name will be removed. Swapping to a new define name to avoid redefining the existing-but-soon-to-be-deleted FALLTHROUGH_INTENDED. --- compiler/src/java_plugin/cpp/java_generator.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/compiler/src/java_plugin/cpp/java_generator.cpp b/compiler/src/java_plugin/cpp/java_generator.cpp index 695ef8581ba..7a883d2b84a 100644 --- a/compiler/src/java_plugin/cpp/java_generator.cpp +++ b/compiler/src/java_plugin/cpp/java_generator.cpp @@ -37,8 +37,10 @@ #define XSTR(s) STR(s) #endif -#ifndef FALLTHROUGH_INTENDED -#define FALLTHROUGH_INTENDED +#ifdef ABSL_FALLTHROUGH_INTENDED +#define FALLTHROUGH ABSL_FALLTHROUGH_INTENDED +#else +#define FALLTHROUGH #endif namespace java_grpc_generator { @@ -542,7 +544,7 @@ static void PrintStub( break; case BLOCKING_CLIENT_INTERFACE: interface = true; - FALLTHROUGH_INTENDED; + FALLTHROUGH; case BLOCKING_CLIENT_IMPL: call_type = BLOCKING_CALL; stub_name += "BlockingStub"; @@ -551,7 +553,7 @@ static void PrintStub( break; case FUTURE_CLIENT_INTERFACE: interface = true; - FALLTHROUGH_INTENDED; + FALLTHROUGH; case FUTURE_CLIENT_IMPL: call_type = FUTURE_CALL; stub_name += "FutureStub"; From b749cb6210aafda3f04877e60dbde30e8540f930 Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Fri, 7 Aug 2020 09:22:42 -0700 Subject: [PATCH 46/88] xds: get the correct meshca.proto version that has the needed Java annotations (#7298) --- xds/build.gradle | 2 +- .../meshca/v1/MeshCertificateServiceGrpc.java | 46 +++++++++---------- xds/third_party/istio/import.sh | 2 +- .../proto/providers/google/meshca.proto | 4 ++ 4 files changed, 29 insertions(+), 25 deletions(-) rename xds/src/generated/main/grpc/{ => com}/google/security/meshca/v1/MeshCertificateServiceGrpc.java (82%) diff --git a/xds/build.gradle b/xds/build.gradle index 0aa5f151479..43902d8a19d 100644 --- a/xds/build.gradle +++ b/xds/build.gradle @@ -96,7 +96,7 @@ shadowJar { relocate 'io.envoyproxy', 'io.grpc.xds.shaded.io.envoyproxy' relocate 'io.grpc.netty', 'io.grpc.netty.shaded.io.grpc.netty' relocate 'io.netty', 'io.grpc.netty.shaded.io.netty' - relocate 'google.security', 'io.grpc.xds.shaded.google.security' + relocate 'com.google.security', 'io.grpc.xds.shaded.com.google.security' exclude "**/*.proto" } diff --git a/xds/src/generated/main/grpc/google/security/meshca/v1/MeshCertificateServiceGrpc.java b/xds/src/generated/main/grpc/com/google/security/meshca/v1/MeshCertificateServiceGrpc.java similarity index 82% rename from xds/src/generated/main/grpc/google/security/meshca/v1/MeshCertificateServiceGrpc.java rename to xds/src/generated/main/grpc/com/google/security/meshca/v1/MeshCertificateServiceGrpc.java index 50a522ef84a..f8c127552d2 100644 --- a/xds/src/generated/main/grpc/google/security/meshca/v1/MeshCertificateServiceGrpc.java +++ b/xds/src/generated/main/grpc/com/google/security/meshca/v1/MeshCertificateServiceGrpc.java @@ -1,4 +1,4 @@ -package google.security.meshca.v1; +package com.google.security.meshca.v1; import static io.grpc.MethodDescriptor.generateFullMethodName; import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall; @@ -30,29 +30,29 @@ private MeshCertificateServiceGrpc() {} public static final String SERVICE_NAME = "google.security.meshca.v1.MeshCertificateService"; // Static method descriptors that strictly reflect the proto. - private static volatile io.grpc.MethodDescriptor getCreateCertificateMethod; + private static volatile io.grpc.MethodDescriptor getCreateCertificateMethod; @io.grpc.stub.annotations.RpcMethod( fullMethodName = SERVICE_NAME + '/' + "CreateCertificate", - requestType = google.security.meshca.v1.Meshca.MeshCertificateRequest.class, - responseType = google.security.meshca.v1.Meshca.MeshCertificateResponse.class, + requestType = com.google.security.meshca.v1.MeshCertificateRequest.class, + responseType = com.google.security.meshca.v1.MeshCertificateResponse.class, methodType = io.grpc.MethodDescriptor.MethodType.UNARY) - public static io.grpc.MethodDescriptor getCreateCertificateMethod() { - io.grpc.MethodDescriptor getCreateCertificateMethod; + public static io.grpc.MethodDescriptor getCreateCertificateMethod() { + io.grpc.MethodDescriptor getCreateCertificateMethod; if ((getCreateCertificateMethod = MeshCertificateServiceGrpc.getCreateCertificateMethod) == null) { synchronized (MeshCertificateServiceGrpc.class) { if ((getCreateCertificateMethod = MeshCertificateServiceGrpc.getCreateCertificateMethod) == null) { MeshCertificateServiceGrpc.getCreateCertificateMethod = getCreateCertificateMethod = - io.grpc.MethodDescriptor.newBuilder() + io.grpc.MethodDescriptor.newBuilder() .setType(io.grpc.MethodDescriptor.MethodType.UNARY) .setFullMethodName(generateFullMethodName(SERVICE_NAME, "CreateCertificate")) .setSampledToLocalTracing(true) .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( - google.security.meshca.v1.Meshca.MeshCertificateRequest.getDefaultInstance())) + com.google.security.meshca.v1.MeshCertificateRequest.getDefaultInstance())) .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( - google.security.meshca.v1.Meshca.MeshCertificateResponse.getDefaultInstance())) + com.google.security.meshca.v1.MeshCertificateResponse.getDefaultInstance())) .setSchemaDescriptor(new MeshCertificateServiceMethodDescriptorSupplier("CreateCertificate")) .build(); } @@ -118,8 +118,8 @@ public static abstract class MeshCertificateServiceImplBase implements io.grpc.B * service account identity. * */ - public void createCertificate(google.security.meshca.v1.Meshca.MeshCertificateRequest request, - io.grpc.stub.StreamObserver responseObserver) { + public void createCertificate(com.google.security.meshca.v1.MeshCertificateRequest request, + io.grpc.stub.StreamObserver responseObserver) { asyncUnimplementedUnaryCall(getCreateCertificateMethod(), responseObserver); } @@ -129,8 +129,8 @@ public void createCertificate(google.security.meshca.v1.Meshca.MeshCertificateRe getCreateCertificateMethod(), asyncUnaryCall( new MethodHandlers< - google.security.meshca.v1.Meshca.MeshCertificateRequest, - google.security.meshca.v1.Meshca.MeshCertificateResponse>( + com.google.security.meshca.v1.MeshCertificateRequest, + com.google.security.meshca.v1.MeshCertificateResponse>( this, METHODID_CREATE_CERTIFICATE))) .build(); } @@ -159,8 +159,8 @@ protected MeshCertificateServiceStub build( * service account identity. * */ - public void createCertificate(google.security.meshca.v1.Meshca.MeshCertificateRequest request, - io.grpc.stub.StreamObserver responseObserver) { + public void createCertificate(com.google.security.meshca.v1.MeshCertificateRequest request, + io.grpc.stub.StreamObserver responseObserver) { asyncUnaryCall( getChannel().newCall(getCreateCertificateMethod(), getCallOptions()), request, responseObserver); } @@ -189,7 +189,7 @@ protected MeshCertificateServiceBlockingStub build( * service account identity. * */ - public google.security.meshca.v1.Meshca.MeshCertificateResponse createCertificate(google.security.meshca.v1.Meshca.MeshCertificateRequest request) { + public com.google.security.meshca.v1.MeshCertificateResponse createCertificate(com.google.security.meshca.v1.MeshCertificateRequest request) { return blockingUnaryCall( getChannel(), getCreateCertificateMethod(), getCallOptions(), request); } @@ -218,8 +218,8 @@ protected MeshCertificateServiceFutureStub build( * service account identity. * */ - public com.google.common.util.concurrent.ListenableFuture createCertificate( - google.security.meshca.v1.Meshca.MeshCertificateRequest request) { + public com.google.common.util.concurrent.ListenableFuture createCertificate( + com.google.security.meshca.v1.MeshCertificateRequest request) { return futureUnaryCall( getChannel().newCall(getCreateCertificateMethod(), getCallOptions()), request); } @@ -245,8 +245,8 @@ private static final class MethodHandlers implements public void invoke(Req request, io.grpc.stub.StreamObserver responseObserver) { switch (methodId) { case METHODID_CREATE_CERTIFICATE: - serviceImpl.createCertificate((google.security.meshca.v1.Meshca.MeshCertificateRequest) request, - (io.grpc.stub.StreamObserver) responseObserver); + serviceImpl.createCertificate((com.google.security.meshca.v1.MeshCertificateRequest) request, + (io.grpc.stub.StreamObserver) responseObserver); break; default: throw new AssertionError(); @@ -270,7 +270,7 @@ private static abstract class MeshCertificateServiceBaseDescriptorSupplier @java.lang.Override public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() { - return google.security.meshca.v1.Meshca.getDescriptor(); + return com.google.security.meshca.v1.MeshCaProto.getDescriptor(); } @java.lang.Override diff --git a/xds/third_party/istio/import.sh b/xds/third_party/istio/import.sh index aecd3cb7605..d1416ac8821 100755 --- a/xds/third_party/istio/import.sh +++ b/xds/third_party/istio/import.sh @@ -18,7 +18,7 @@ set -e BRANCH=master -VERSION=4c14c4673ab8748a0c77922290d91b6ce68b7b7c +VERSION=e0ce39487b4806bdd9062b2c0d0cae0bebbbac7b GIT_REPO="https://github.com/istio/istio.git" GIT_BASE_DIR=istio SOURCE_PROTO_BASE_DIR=istio diff --git a/xds/third_party/istio/src/main/proto/security/proto/providers/google/meshca.proto b/xds/third_party/istio/src/main/proto/security/proto/providers/google/meshca.proto index 9c3c004adb6..c02b7f58287 100644 --- a/xds/third_party/istio/src/main/proto/security/proto/providers/google/meshca.proto +++ b/xds/third_party/istio/src/main/proto/security/proto/providers/google/meshca.proto @@ -18,6 +18,10 @@ package google.security.meshca.v1; import "google/protobuf/duration.proto"; +option java_multiple_files = true; +option java_outer_classname = "MeshCaProto"; +option java_package = "com.google.security.meshca.v1"; + // Certificate request message. message MeshCertificateRequest { // The request ID must be a valid UUID with the exception that zero UUID is From 47301752b189bd5354e00091f2952cc55ed24eeb Mon Sep 17 00:00:00 2001 From: Chengyuan Zhang Date: Fri, 7 Aug 2020 16:59:47 +0000 Subject: [PATCH 47/88] examples: add mavenCentral for android example's dependency repository (#7293) --- examples/android/clientcache/build.gradle | 3 +++ examples/android/helloworld/build.gradle | 3 +++ examples/android/routeguide/build.gradle | 3 +++ examples/android/strictmode/build.gradle | 3 +++ 4 files changed, 12 insertions(+) diff --git a/examples/android/clientcache/build.gradle b/examples/android/clientcache/build.gradle index 2075f4cc7ac..436383e2270 100644 --- a/examples/android/clientcache/build.gradle +++ b/examples/android/clientcache/build.gradle @@ -18,6 +18,9 @@ allprojects { repositories { google() jcenter() + // Some grpc-java releases may be missing in JCenter. + // See https://github.com/grpc/grpc-java/issues/5782 + mavenCentral() mavenLocal() } } diff --git a/examples/android/helloworld/build.gradle b/examples/android/helloworld/build.gradle index 2075f4cc7ac..436383e2270 100644 --- a/examples/android/helloworld/build.gradle +++ b/examples/android/helloworld/build.gradle @@ -18,6 +18,9 @@ allprojects { repositories { google() jcenter() + // Some grpc-java releases may be missing in JCenter. + // See https://github.com/grpc/grpc-java/issues/5782 + mavenCentral() mavenLocal() } } diff --git a/examples/android/routeguide/build.gradle b/examples/android/routeguide/build.gradle index 4003a2cedb8..cafba46c04b 100644 --- a/examples/android/routeguide/build.gradle +++ b/examples/android/routeguide/build.gradle @@ -18,6 +18,9 @@ allprojects { repositories { google() jcenter() + // Some grpc-java releases may be missing in JCenter. + // See https://github.com/grpc/grpc-java/issues/5782 + mavenCentral() mavenLocal() } } diff --git a/examples/android/strictmode/build.gradle b/examples/android/strictmode/build.gradle index 2075f4cc7ac..436383e2270 100644 --- a/examples/android/strictmode/build.gradle +++ b/examples/android/strictmode/build.gradle @@ -18,6 +18,9 @@ allprojects { repositories { google() jcenter() + // Some grpc-java releases may be missing in JCenter. + // See https://github.com/grpc/grpc-java/issues/5782 + mavenCentral() mavenLocal() } } From 020fb367538e96f6e8f754144ef47efdf943bd72 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Fri, 7 Aug 2020 09:17:19 -0700 Subject: [PATCH 48/88] Fix lint warnings --- .../java/io/grpc/ServerInterceptorsTest.java | 1 - .../io/grpc/census/CensusModulesTest.java | 1 - context/src/main/java/io/grpc/Context.java | 1 - .../grpc/internal/ManagedChannelImplTest.java | 2 -- .../io/grpc/services/BinlogHelperTest.java | 30 ++++++++++--------- 5 files changed, 16 insertions(+), 19 deletions(-) diff --git a/api/src/test/java/io/grpc/ServerInterceptorsTest.java b/api/src/test/java/io/grpc/ServerInterceptorsTest.java index a12de3fc282..8ea2801a2d4 100644 --- a/api/src/test/java/io/grpc/ServerInterceptorsTest.java +++ b/api/src/test/java/io/grpc/ServerInterceptorsTest.java @@ -284,7 +284,6 @@ public ServerCall.Listener interceptCall( @Test public void argumentsPassed() { - @SuppressWarnings("unchecked") final ServerCall call2 = new NoopServerCall<>(); @SuppressWarnings("unchecked") final ServerCall.Listener listener2 = mock(ServerCall.Listener.class); diff --git a/census/src/test/java/io/grpc/census/CensusModulesTest.java b/census/src/test/java/io/grpc/census/CensusModulesTest.java index 135d5012c92..fbbcd44150c 100644 --- a/census/src/test/java/io/grpc/census/CensusModulesTest.java +++ b/census/src/test/java/io/grpc/census/CensusModulesTest.java @@ -199,7 +199,6 @@ public String parse(InputStream stream) { private CensusTracingModule censusTracing; @Before - @SuppressWarnings("unchecked") public void setUp() throws Exception { when(spyClientSpanBuilder.startSpan()).thenReturn(spyClientSpan); when(tracer.spanBuilderWithExplicitParent(anyString(), ArgumentMatchers.any())) diff --git a/context/src/main/java/io/grpc/Context.java b/context/src/main/java/io/grpc/Context.java index ee926b48662..3a500b07fb2 100644 --- a/context/src/main/java/io/grpc/Context.java +++ b/context/src/main/java/io/grpc/Context.java @@ -969,7 +969,6 @@ public static final class Key { /** * Get the value from the {@link #current()} context for this key. */ - @SuppressWarnings("unchecked") public T get() { return get(Context.current()); } diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java index 5d286ba6594..110392117ef 100644 --- a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java +++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java @@ -140,7 +140,6 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.ArgumentCaptor; @@ -210,7 +209,6 @@ public boolean shouldAccept(Runnable command) { private final InternalChannelz channelz = new InternalChannelz(); - @Rule public final ExpectedException thrown = ExpectedException.none(); @Rule public final MockitoRule mocks = MockitoJUnit.rule(); private ManagedChannelImpl channel; diff --git a/services/src/test/java/io/grpc/services/BinlogHelperTest.java b/services/src/test/java/io/grpc/services/BinlogHelperTest.java index 4229294d272..f7d0d40726c 100644 --- a/services/src/test/java/io/grpc/services/BinlogHelperTest.java +++ b/services/src/test/java/io/grpc/services/BinlogHelperTest.java @@ -927,11 +927,11 @@ public void getPeerSocketTest() { } @Test - @SuppressWarnings({"rawtypes", "unchecked"}) public void serverDeadlineLogged() { - final AtomicReference interceptedCall = + final AtomicReference> interceptedCall = new AtomicReference<>(); - final ServerCall.Listener mockListener = mock(ServerCall.Listener.class); + @SuppressWarnings("unchecked") + final ServerCall.Listener mockListener = mock(ServerCall.Listener.class); final MethodDescriptor method = MethodDescriptor.newBuilder() @@ -986,7 +986,6 @@ public ServerCall.Listener startCall( } @Test - @SuppressWarnings({"unchecked"}) public void clientDeadlineLogged_deadlineSetViaCallOption() { MethodDescriptor method = MethodDescriptor.newBuilder() @@ -995,6 +994,7 @@ public void clientDeadlineLogged_deadlineSetViaCallOption() { .setRequestMarshaller(BYTEARRAY_MARSHALLER) .setResponseMarshaller(BYTEARRAY_MARSHALLER) .build(); + @SuppressWarnings("unchecked") ClientCall.Listener mockListener = mock(ClientCall.Listener.class); ClientCall call = @@ -1035,7 +1035,6 @@ public String authority() { } @Test - @SuppressWarnings({"unchecked"}) public void clientDeadlineLogged_deadlineSetViaContext() throws Exception { // important: deadline is read from the ctx where call was created final SettableFuture> callFuture = SettableFuture.create(); @@ -1073,6 +1072,7 @@ public String authority() { })); } }); + @SuppressWarnings("unchecked") ClientCall.Listener mockListener = mock(ClientCall.Listener.class); callFuture.get().start(mockListener, new Metadata()); ArgumentCaptor callOptTimeoutCaptor = ArgumentCaptor.forClass(Duration.class); @@ -1093,9 +1093,8 @@ public String authority() { } @Test - @SuppressWarnings({"rawtypes", "unchecked"}) public void clientInterceptor() throws Exception { - final AtomicReference interceptedListener = + final AtomicReference> interceptedListener = new AtomicReference<>(); // capture these manually because ClientCall can not be mocked final AtomicReference actualClientInitial = new AtomicReference<>(); @@ -1109,8 +1108,9 @@ public ClientCall newCall( MethodDescriptor methodDescriptor, CallOptions callOptions) { return new NoopClientCall() { @Override + @SuppressWarnings("unchecked") public void start(Listener responseListener, Metadata headers) { - interceptedListener.set(responseListener); + interceptedListener.set((Listener) responseListener); actualClientInitial.set(headers); } @@ -1142,6 +1142,7 @@ public String authority() { } }; + @SuppressWarnings("unchecked") ClientCall.Listener mockListener = mock(ClientCall.Listener.class); MethodDescriptor method = @@ -1260,9 +1261,8 @@ public String authority() { } @Test - @SuppressWarnings({"rawtypes", "unchecked"}) public void clientInterceptor_trailersOnlyResponseLogsPeerAddress() throws Exception { - final AtomicReference interceptedListener = + final AtomicReference> interceptedListener = new AtomicReference<>(); // capture these manually because ClientCall can not be mocked final AtomicReference actualClientInitial = new AtomicReference<>(); @@ -1274,8 +1274,9 @@ public ClientCall newCall( MethodDescriptor methodDescriptor, CallOptions callOptions) { return new NoopClientCall() { @Override + @SuppressWarnings("unchecked") public void start(Listener responseListener, Metadata headers) { - interceptedListener.set(responseListener); + interceptedListener.set((Listener) responseListener); actualClientInitial.set(headers); } @@ -1297,6 +1298,7 @@ public String authority() { } }; + @SuppressWarnings("unchecked") ClientCall.Listener mockListener = mock(ClientCall.Listener.class); MethodDescriptor method = @@ -1345,12 +1347,12 @@ public String authority() { } @Test - @SuppressWarnings({"rawtypes", "unchecked"}) public void serverInterceptor() throws Exception { - final AtomicReference interceptedCall = + final AtomicReference> interceptedCall = new AtomicReference<>(); ServerCall.Listener capturedListener; - final ServerCall.Listener mockListener = mock(ServerCall.Listener.class); + @SuppressWarnings("unchecked") + final ServerCall.Listener mockListener = mock(ServerCall.Listener.class); // capture these manually because ServerCall can not be mocked final AtomicReference actualServerInitial = new AtomicReference<>(); final AtomicReference actualResponse = new AtomicReference<>(); From 19d690b4351b2ecddde147ac7a6714522113a887 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 6 Aug 2020 08:43:32 -0700 Subject: [PATCH 49/88] compiler: Use namespace aliases to reduce internal diff Due to historical reasons, protobuf is in the proto2:: namespace internally instead of google::protobuf. We have been maintaining diffs that replace each occurence of one with the other. Instead we can simply create a namespace alias and use that alias instead of the canonical name. That greatly reduces the size of the diff and its likelihood to break. If the names ever align in the future, we can swap back to the canonical names. --- .../src/java_plugin/cpp/java_generator.cpp | 28 ++++++++++--------- compiler/src/java_plugin/cpp/java_generator.h | 14 ++++++---- compiler/src/java_plugin/cpp/java_plugin.cpp | 16 ++++++----- 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/compiler/src/java_plugin/cpp/java_generator.cpp b/compiler/src/java_plugin/cpp/java_generator.cpp index 7a883d2b84a..facb95de145 100644 --- a/compiler/src/java_plugin/cpp/java_generator.cpp +++ b/compiler/src/java_plugin/cpp/java_generator.cpp @@ -45,12 +45,14 @@ namespace java_grpc_generator { -using google::protobuf::FileDescriptor; -using google::protobuf::ServiceDescriptor; -using google::protobuf::MethodDescriptor; -using google::protobuf::Descriptor; -using google::protobuf::io::Printer; -using google::protobuf::SourceLocation; +namespace protobuf = google::protobuf; + +using protobuf::Descriptor; +using protobuf::FileDescriptor; +using protobuf::MethodDescriptor; +using protobuf::ServiceDescriptor; +using protobuf::SourceLocation; +using protobuf::io::Printer; using std::to_string; // java keywords from: https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.9 @@ -164,7 +166,7 @@ static inline std::string MethodIdFieldName(const MethodDescriptor* method) { } static inline std::string MessageFullJavaName(const Descriptor* desc) { - return google::protobuf::compiler::java::ClassName(desc); + return protobuf::compiler::java::ClassName(desc); } // TODO(nmittler): Remove once protobuf includes javadoc methods in distribution. @@ -427,12 +429,12 @@ static void PrintMethodFields( " .setFullMethodName(generateFullMethodName(SERVICE_NAME, \"$method_name$\"))\n"); bool safe = method->options().idempotency_level() - == google::protobuf::MethodOptions_IdempotencyLevel_NO_SIDE_EFFECTS; + == protobuf::MethodOptions_IdempotencyLevel_NO_SIDE_EFFECTS; if (safe) { p->Print(*vars, " .setSafe(true)\n"); } else { bool idempotent = method->options().idempotency_level() - == google::protobuf::MethodOptions_IdempotencyLevel_IDEMPOTENT; + == protobuf::MethodOptions_IdempotencyLevel_IDEMPOTENT; if (idempotent) { p->Print(*vars, " .setIdempotent(true)\n"); } @@ -918,7 +920,7 @@ static void PrintGetServiceDescriptorMethod(const ServiceDescriptor* service, (*vars)["proto_base_descriptor_supplier"] = service->name() + "BaseDescriptorSupplier"; (*vars)["proto_file_descriptor_supplier"] = service->name() + "FileDescriptorSupplier"; (*vars)["proto_method_descriptor_supplier"] = service->name() + "MethodDescriptorSupplier"; - (*vars)["proto_class_name"] = google::protobuf::compiler::java::ClassName(service->file()); + (*vars)["proto_class_name"] = protobuf::compiler::java::ClassName(service->file()); p->Print( *vars, "private static abstract class $proto_base_descriptor_supplier$\n" @@ -1188,7 +1190,7 @@ void PrintImports(Printer* p) { } void GenerateService(const ServiceDescriptor* service, - google::protobuf::io::ZeroCopyOutputStream* out, + protobuf::io::ZeroCopyOutputStream* out, ProtoFlavor flavor, bool disable_version) { // All non-generated classes must be referred by fully qualified names to @@ -1244,7 +1246,7 @@ void GenerateService(const ServiceDescriptor* service, } std::string ServiceJavaPackage(const FileDescriptor* file) { - std::string result = google::protobuf::compiler::java::ClassName(file); + std::string result = protobuf::compiler::java::ClassName(file); size_t last_dot_pos = result.find_last_of('.'); if (last_dot_pos != std::string::npos) { result.resize(last_dot_pos); @@ -1254,7 +1256,7 @@ std::string ServiceJavaPackage(const FileDescriptor* file) { return result; } -std::string ServiceClassName(const google::protobuf::ServiceDescriptor* service) { +std::string ServiceClassName(const ServiceDescriptor* service) { return service->name() + "Grpc"; } diff --git a/compiler/src/java_plugin/cpp/java_generator.h b/compiler/src/java_plugin/cpp/java_generator.h index 66a4e9c9c43..2ecc884efd4 100644 --- a/compiler/src/java_plugin/cpp/java_generator.h +++ b/compiler/src/java_plugin/cpp/java_generator.h @@ -47,24 +47,26 @@ class LogHelper { // Abort the program after logging the mesage. #define GRPC_CODEGEN_FAIL GRPC_CODEGEN_CHECK(false) -using namespace std; - namespace java_grpc_generator { +namespace impl { +namespace protobuf = google::protobuf; +} // namespace impl + enum ProtoFlavor { NORMAL, LITE }; // Returns the package name of the gRPC services defined in the given file. -std::string ServiceJavaPackage(const google::protobuf::FileDescriptor* file); +std::string ServiceJavaPackage(const impl::protobuf::FileDescriptor* file); // Returns the name of the outer class that wraps in all the generated code for // the given service. -std::string ServiceClassName(const google::protobuf::ServiceDescriptor* service); +std::string ServiceClassName(const impl::protobuf::ServiceDescriptor* service); // Writes the generated service interface into the given ZeroCopyOutputStream -void GenerateService(const google::protobuf::ServiceDescriptor* service, - google::protobuf::io::ZeroCopyOutputStream* out, +void GenerateService(const impl::protobuf::ServiceDescriptor* service, + impl::protobuf::io::ZeroCopyOutputStream* out, ProtoFlavor flavor, bool disable_version); diff --git a/compiler/src/java_plugin/cpp/java_plugin.cpp b/compiler/src/java_plugin/cpp/java_plugin.cpp index d8b10c52422..2eed9d260d1 100644 --- a/compiler/src/java_plugin/cpp/java_plugin.cpp +++ b/compiler/src/java_plugin/cpp/java_plugin.cpp @@ -27,6 +27,8 @@ #include #include +namespace protobuf = google::protobuf; + static std::string JavaPackageToDir(const std::string& package_name) { std::string package_dir = package_name; for (size_t i = 0; i < package_dir.size(); ++i) { @@ -38,7 +40,7 @@ static std::string JavaPackageToDir(const std::string& package_name) { return package_dir; } -class JavaGrpcGenerator : public google::protobuf::compiler::CodeGenerator { +class JavaGrpcGenerator : public protobuf::compiler::CodeGenerator { public: JavaGrpcGenerator() {} virtual ~JavaGrpcGenerator() {} @@ -47,12 +49,12 @@ class JavaGrpcGenerator : public google::protobuf::compiler::CodeGenerator { return FEATURE_PROTO3_OPTIONAL; } - virtual bool Generate(const google::protobuf::FileDescriptor* file, + virtual bool Generate(const protobuf::FileDescriptor* file, const std::string& parameter, - google::protobuf::compiler::GeneratorContext* context, + protobuf::compiler::GeneratorContext* context, std::string* error) const override { std::vector > options; - google::protobuf::compiler::ParseGeneratorParameter(parameter, &options); + protobuf::compiler::ParseGeneratorParameter(parameter, &options); java_grpc_generator::ProtoFlavor flavor = java_grpc_generator::ProtoFlavor::NORMAL; @@ -69,10 +71,10 @@ class JavaGrpcGenerator : public google::protobuf::compiler::CodeGenerator { std::string package_name = java_grpc_generator::ServiceJavaPackage(file); std::string package_filename = JavaPackageToDir(package_name); for (int i = 0; i < file->service_count(); ++i) { - const google::protobuf::ServiceDescriptor* service = file->service(i); + const protobuf::ServiceDescriptor* service = file->service(i); std::string filename = package_filename + java_grpc_generator::ServiceClassName(service) + ".java"; - std::unique_ptr output( + std::unique_ptr output( context->Open(filename)); java_grpc_generator::GenerateService( service, output.get(), flavor, disable_version); @@ -83,5 +85,5 @@ class JavaGrpcGenerator : public google::protobuf::compiler::CodeGenerator { int main(int argc, char* argv[]) { JavaGrpcGenerator generator; - return google::protobuf::compiler::PluginMain(argc, argv, &generator); + return protobuf::compiler::PluginMain(argc, argv, &generator); } From 0a99a20b705f5d4b7a57bd57f6f31329281f21c2 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Fri, 7 Aug 2020 14:33:37 -0700 Subject: [PATCH 50/88] netty: Provide an environment variable to toggle BDP monitoring A user has reported a GOAWAY with too_many_pings when using BDP. We aren't certain why it is happening, but want to provide a way to disable BDP while we continue investigating. b/162162973 --- .../main/java/io/grpc/netty/NettyChannelBuilder.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java b/netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java index 02a4f4ff3b8..59e86a117da 100644 --- a/netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java +++ b/netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java @@ -68,6 +68,7 @@ public final class NettyChannelBuilder // 1MiB. public static final int DEFAULT_FLOW_CONTROL_WINDOW = 1024 * 1024; + private static final boolean DEFAULT_AUTO_FLOW_CONTROL; private static final long AS_LARGE_AS_INFINITE = TimeUnit.DAYS.toNanos(1000L); @@ -76,6 +77,14 @@ public final class NettyChannelBuilder private static final ObjectPool DEFAULT_EVENT_LOOP_GROUP_POOL = SharedResourcePool.forResource(Utils.DEFAULT_WORKER_EVENT_LOOP_GROUP); + static { + String autoFlowControl = System.getenv("GRPC_EXPERIMENTAL_AUTOFLOWCONTROL"); + if (autoFlowControl == null) { + autoFlowControl = "true"; + } + DEFAULT_AUTO_FLOW_CONTROL = Boolean.parseBoolean(autoFlowControl); + } + private final Map, Object> channelOptions = new HashMap<>(); @@ -84,7 +93,7 @@ public final class NettyChannelBuilder private ChannelFactory channelFactory = DEFAULT_CHANNEL_FACTORY; private ObjectPool eventLoopGroupPool = DEFAULT_EVENT_LOOP_GROUP_POOL; private SslContext sslContext; - private boolean autoFlowControl = true; + private boolean autoFlowControl = DEFAULT_AUTO_FLOW_CONTROL; private int flowControlWindow = DEFAULT_FLOW_CONTROL_WINDOW; private int maxHeaderListSize = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE; private long keepAliveTimeNanos = KEEPALIVE_TIME_NANOS_DISABLED; From 65e7ffc788197174d4d1389120ec6bafc63a9f94 Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Fri, 7 Aug 2020 16:16:22 -0700 Subject: [PATCH 51/88] xds: implement MeshCACertificateProvider (#7274) --- build.gradle | 2 + xds/build.gradle | 3 +- .../certprovider/CertificateProvider.java | 50 +- .../MeshCaCertificateProvider.java | 385 ++++++++++++- .../MeshCaCertificateProviderProvider.java | 81 ++- .../internal/sds/trust/CertificateUtils.java | 25 +- .../sds/trust/SdsTrustManagerFactory.java | 7 +- .../CertificateProviderStoreTest.java | 10 +- ...MeshCaCertificateProviderProviderTest.java | 39 +- .../MeshCaCertificateProviderTest.java | 538 ++++++++++++++++++ .../sds/CommonTlsContextTestsUtil.java | 29 + 11 files changed, 1107 insertions(+), 62 deletions(-) create mode 100644 xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderTest.java diff --git a/build.gradle b/build.gradle index 8eb98f21b03..f541539599d 100644 --- a/build.gradle +++ b/build.gradle @@ -177,6 +177,8 @@ subprojects { conscrypt: 'org.conscrypt:conscrypt-openjdk-uber:2.2.1', re2j: 'com.google.re2j:re2j:1.2', + bouncycastle: 'org.bouncycastle:bcpkix-jdk15on:1.61', + // Test dependencies. junit: 'junit:junit:4.12', mockito: 'org.mockito:mockito-core:3.3.3', diff --git a/xds/build.gradle b/xds/build.gradle index 43902d8a19d..29033663ef5 100644 --- a/xds/build.gradle +++ b/xds/build.gradle @@ -26,7 +26,8 @@ dependencies { project(':grpc-auth'), project(path: ':grpc-alts', configuration: 'shadow'), libraries.gson, - libraries.re2j + libraries.re2j, + libraries.bouncycastle def nettyDependency = implementation project(':grpc-netty') implementation (libraries.opencensus_proto) { diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProvider.java index 9b96f9957fb..b5d149777ed 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProvider.java @@ -49,42 +49,42 @@ public interface Watcher { @VisibleForTesting static final class DistributorWatcher implements Watcher { - private PrivateKey lastKey; - private List lastCertChain; - private List lastTrustedRoots; + private PrivateKey privateKey; + private List certChain; + private List trustedRoots; @VisibleForTesting - final Set downsstreamWatchers = new HashSet<>(); + final Set downstreamWatchers = new HashSet<>(); synchronized void addWatcher(Watcher watcher) { - downsstreamWatchers.add(watcher); - if (lastKey != null && lastCertChain != null) { + downstreamWatchers.add(watcher); + if (privateKey != null && certChain != null) { sendLastCertificateUpdate(watcher); } - if (lastTrustedRoots != null) { + if (trustedRoots != null) { sendLastTrustedRootsUpdate(watcher); } } synchronized void removeWatcher(Watcher watcher) { - downsstreamWatchers.remove(watcher); + downstreamWatchers.remove(watcher); } private void sendLastCertificateUpdate(Watcher watcher) { - watcher.updateCertificate(lastKey, lastCertChain); + watcher.updateCertificate(privateKey, certChain); } private void sendLastTrustedRootsUpdate(Watcher watcher) { - watcher.updateTrustedRoots(lastTrustedRoots); + watcher.updateTrustedRoots(trustedRoots); } @Override public synchronized void updateCertificate(PrivateKey key, List certChain) { checkNotNull(key, "key"); checkNotNull(certChain, "certChain"); - lastKey = key; - lastCertChain = certChain; - for (Watcher watcher : downsstreamWatchers) { + privateKey = key; + this.certChain = certChain; + for (Watcher watcher : downstreamWatchers) { sendLastCertificateUpdate(watcher); } } @@ -92,18 +92,36 @@ public synchronized void updateCertificate(PrivateKey key, List @Override public synchronized void updateTrustedRoots(List trustedRoots) { checkNotNull(trustedRoots, "trustedRoots"); - lastTrustedRoots = trustedRoots; - for (Watcher watcher : downsstreamWatchers) { + this.trustedRoots = trustedRoots; + for (Watcher watcher : downstreamWatchers) { sendLastTrustedRootsUpdate(watcher); } } @Override public synchronized void onError(Status errorStatus) { - for (Watcher watcher : downsstreamWatchers) { + for (Watcher watcher : downstreamWatchers) { watcher.onError(errorStatus); } } + + X509Certificate getLastIdentityCert() { + if (certChain != null && !certChain.isEmpty()) { + return certChain.get(0); + } + return null; + } + + void close() { + downstreamWatchers.clear(); + clearValues(); + } + + void clearValues() { + privateKey = null; + certChain = null; + trustedRoots = null; + } } /** diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProvider.java index 348f7013cd4..c03829766f8 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProvider.java @@ -17,35 +17,336 @@ package io.grpc.xds.internal.certprovider; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.grpc.Status.Code.ABORTED; +import static io.grpc.Status.Code.CANCELLED; +import static io.grpc.Status.Code.DEADLINE_EXCEEDED; +import static io.grpc.Status.Code.INTERNAL; +import static io.grpc.Status.Code.RESOURCE_EXHAUSTED; +import static io.grpc.Status.Code.UNAVAILABLE; +import static io.grpc.Status.Code.UNKNOWN; +import static java.nio.charset.StandardCharsets.UTF_8; import com.google.auth.oauth2.GoogleCredentials; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.protobuf.Duration; +import google.security.meshca.v1.MeshCertificateServiceGrpc; +import google.security.meshca.v1.Meshca; +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ForwardingClientCall; +import io.grpc.InternalLogId; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.Status; +import io.grpc.SynchronizationContext; +import io.grpc.auth.MoreCallCredentials; import io.grpc.internal.BackoffPolicy; +import io.grpc.internal.TimeProvider; +import io.grpc.xds.internal.sds.trust.CertificateUtils; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.StringWriter; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; +import javax.security.auth.x500.X500Principal; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder; +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; +import org.bouncycastle.util.io.pem.PemObject; /** Implementation of {@link CertificateProvider} for the Google Mesh CA. */ final class MeshCaCertificateProvider extends CertificateProvider { private static final Logger logger = Logger.getLogger(MeshCaCertificateProvider.class.getName()); - MeshCaCertificateProvider(DistributorWatcher watcher, boolean notifyCertUpdates, - String meshCaUrl, String zone, long validitySeconds, - int keySize, String alg, String signatureAlg, MeshCaChannelFactory meshCaChannelFactory, - BackoffPolicy.Provider backoffPolicyProvider, long renewalGracePeriodSeconds, - int maxRetryAttempts, GoogleCredentials oauth2Creds) { + MeshCaCertificateProvider( + DistributorWatcher watcher, + boolean notifyCertUpdates, + String meshCaUrl, + String zone, + long validitySeconds, + int keySize, + String alg, + String signatureAlg, MeshCaChannelFactory meshCaChannelFactory, + BackoffPolicy.Provider backoffPolicyProvider, + long renewalGracePeriodSeconds, + int maxRetryAttempts, + GoogleCredentials oauth2Creds, + ScheduledExecutorService scheduledExecutorService, + TimeProvider timeProvider, + long rpcTimeoutMillis) { super(watcher, notifyCertUpdates); + this.meshCaUrl = checkNotNull(meshCaUrl, "meshCaUrl"); + checkArgument( + validitySeconds > INITIAL_DELAY_SECONDS, + "validitySeconds must be greater than " + INITIAL_DELAY_SECONDS); + this.validitySeconds = validitySeconds; + this.keySize = keySize; + this.alg = checkNotNull(alg, "alg"); + this.signatureAlg = checkNotNull(signatureAlg, "signatureAlg"); + this.meshCaChannelFactory = checkNotNull(meshCaChannelFactory, "meshCaChannelFactory"); + this.backoffPolicyProvider = checkNotNull(backoffPolicyProvider, "backoffPolicyProvider"); + checkArgument( + renewalGracePeriodSeconds > 0L && renewalGracePeriodSeconds < validitySeconds, + "renewalGracePeriodSeconds should be between 0 and " + validitySeconds); + this.renewalGracePeriodSeconds = renewalGracePeriodSeconds; + checkArgument(maxRetryAttempts >= 0, "maxRetryAttempts must be >= 0"); + this.maxRetryAttempts = maxRetryAttempts; + this.oauth2Creds = checkNotNull(oauth2Creds, "oauth2Creds"); + this.scheduledExecutorService = + checkNotNull(scheduledExecutorService, "scheduledExecutorService"); + this.timeProvider = checkNotNull(timeProvider, "timeProvider"); + this.headerInterceptor = new ZoneInfoClientInterceptor(checkNotNull(zone, "zone")); + this.syncContext = createSynchronizationContext(meshCaUrl); + this.rpcTimeoutMillis = rpcTimeoutMillis; + } + + private SynchronizationContext createSynchronizationContext(String details) { + final InternalLogId logId = InternalLogId.allocate("MeshCaCertificateProvider", details); + return new SynchronizationContext( + new Thread.UncaughtExceptionHandler() { + private boolean panicMode; + + @Override + public void uncaughtException(Thread t, Throwable e) { + logger.log( + Level.SEVERE, + "[" + logId + "] Uncaught exception in the SynchronizationContext. Panic!", + e); + panic(e); + } + + void panic(final Throwable t) { + if (panicMode) { + // Preserve the first panic information + return; + } + panicMode = true; + close(); + } + }); } @Override public void start() { - // TODO implement + scheduleNextRefreshCertificate(INITIAL_DELAY_SECONDS); } @Override public void close() { - // TODO implement + if (scheduledHandle != null) { + scheduledHandle.cancel(); + scheduledHandle = null; + } + getWatcher().close(); + } + + private void scheduleNextRefreshCertificate(long delayInSeconds) { + if (scheduledHandle != null && scheduledHandle.isPending()) { + logger.log(Level.SEVERE, "Pending task found: inconsistent state in scheduledHandle!"); + scheduledHandle.cancel(); + } + RefreshCertificateTask runnable = new RefreshCertificateTask(); + scheduledHandle = syncContext.schedule( + runnable, delayInSeconds, TimeUnit.SECONDS, scheduledExecutorService); + } + + @VisibleForTesting + void refreshCertificate() + throws NoSuchAlgorithmException, IOException, OperatorCreationException { + long refreshDelaySeconds = computeRefreshSecondsFromCurrentCertExpiry(); + ManagedChannel channel = meshCaChannelFactory.createChannel(meshCaUrl); + try { + String uniqueReqIdForAllRetries = UUID.randomUUID().toString(); + Duration duration = Duration.newBuilder().setSeconds(validitySeconds).build(); + KeyPair keyPair = generateKeyPair(); + String csr = generateCsr(keyPair); + MeshCertificateServiceGrpc.MeshCertificateServiceBlockingStub stub = + createStubToMeshCa(channel); + List x509Chain = makeRequestWithRetries(stub, uniqueReqIdForAllRetries, + duration, csr); + if (x509Chain != null) { + refreshDelaySeconds = + computeDelaySecondsToCertExpiry(x509Chain.get(0)) - renewalGracePeriodSeconds; + getWatcher().updateCertificate(keyPair.getPrivate(), x509Chain); + getWatcher().updateTrustedRoots(ImmutableList.of(x509Chain.get(x509Chain.size() - 1))); + } + } finally { + shutdownChannel(channel); + scheduleNextRefreshCertificate(refreshDelaySeconds); + } + } + + private MeshCertificateServiceGrpc.MeshCertificateServiceBlockingStub createStubToMeshCa( + ManagedChannel channel) { + return MeshCertificateServiceGrpc + .newBlockingStub(channel) + .withCallCredentials(MoreCallCredentials.from(oauth2Creds)) + .withInterceptors(headerInterceptor); + } + + private List makeRequestWithRetries( + MeshCertificateServiceGrpc.MeshCertificateServiceBlockingStub stub, + String reqId, + Duration duration, + String csr) { + Meshca.MeshCertificateRequest request = + Meshca.MeshCertificateRequest.newBuilder() + .setValidity(duration) + .setCsr(csr) + .setRequestId(reqId) + .build(); + + BackoffPolicy backoffPolicy = backoffPolicyProvider.get(); + Throwable lastException = null; + for (int i = 0; i <= maxRetryAttempts; i++) { + try { + Meshca.MeshCertificateResponse response = + stub.withDeadlineAfter(rpcTimeoutMillis, TimeUnit.MILLISECONDS) + .createCertificate(request); + return getX509CertificatesFromResponse(response); + } catch (Throwable t) { + if (!retriable(t)) { + generateErrorIfCurrentCertExpired(t); + return null; + } + lastException = t; + sleepForNanos(backoffPolicy.nextBackoffNanos()); + } + } + generateErrorIfCurrentCertExpired(lastException); + return null; + } + + private void sleepForNanos(long nanos) { + ScheduledFuture future = scheduledExecutorService.schedule(new Runnable() { + @Override + public void run() { + // do nothing + } + }, nanos, TimeUnit.NANOSECONDS); + try { + future.get(nanos, TimeUnit.NANOSECONDS); + } catch (InterruptedException ie) { + logger.log(Level.SEVERE, "Inside sleep", ie); + Thread.currentThread().interrupt(); + } catch (ExecutionException | TimeoutException ex) { + logger.log(Level.SEVERE, "Inside sleep", ex); + } + } + + private static boolean retriable(Throwable t) { + return RETRIABLE_CODES.contains(Status.fromThrowable(t).getCode()); + } + + private void generateErrorIfCurrentCertExpired(Throwable t) { + X509Certificate currentCert = getWatcher().getLastIdentityCert(); + if (currentCert != null) { + long delaySeconds = computeDelaySecondsToCertExpiry(currentCert); + if (delaySeconds > INITIAL_DELAY_SECONDS) { + return; + } + getWatcher().clearValues(); + } + getWatcher().onError(Status.fromThrowable(t)); + } + + private KeyPair generateKeyPair() throws NoSuchAlgorithmException { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(alg); + keyPairGenerator.initialize(keySize); + return keyPairGenerator.generateKeyPair(); + } + + private String generateCsr(KeyPair pair) throws IOException, OperatorCreationException { + PKCS10CertificationRequestBuilder p10Builder = + new JcaPKCS10CertificationRequestBuilder( + new X500Principal("CN=EXAMPLE.COM"), pair.getPublic()); + JcaContentSignerBuilder csBuilder = new JcaContentSignerBuilder(signatureAlg); + ContentSigner signer = csBuilder.build(pair.getPrivate()); + PKCS10CertificationRequest csr = p10Builder.build(signer); + PemObject pemObject = new PemObject("NEW CERTIFICATE REQUEST", csr.getEncoded()); + try (StringWriter str = new StringWriter()) { + try (JcaPEMWriter pemWriter = new JcaPEMWriter(str)) { + pemWriter.writeObject(pemObject); + } + return str.toString(); + } + } + + /** Compute refresh interval as half of interval to current cert expiry. */ + private long computeRefreshSecondsFromCurrentCertExpiry() { + X509Certificate lastCert = getWatcher().getLastIdentityCert(); + if (lastCert == null) { + return INITIAL_DELAY_SECONDS; + } + long delayToCertExpirySeconds = computeDelaySecondsToCertExpiry(lastCert) / 2; + return Math.max(delayToCertExpirySeconds, INITIAL_DELAY_SECONDS); + } + + @SuppressWarnings("JdkObsolete") + private long computeDelaySecondsToCertExpiry(X509Certificate lastCert) { + checkNotNull(lastCert, "lastCert"); + return TimeUnit.NANOSECONDS.toSeconds( + TimeUnit.MILLISECONDS.toNanos(lastCert.getNotAfter().getTime()) - timeProvider + .currentTimeNanos()); + } + + private static void shutdownChannel(ManagedChannel channel) { + channel.shutdown(); + try { + channel.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, "awaiting channel Termination", ex); + channel.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + + private List getX509CertificatesFromResponse( + Meshca.MeshCertificateResponse response) throws CertificateException, IOException { + List certChain = response.getCertChainList(); + List x509Chain = new ArrayList<>(certChain.size()); + for (String certString : certChain) { + try (ByteArrayInputStream bais = new ByteArrayInputStream(certString.getBytes(UTF_8))) { + x509Chain.add(CertificateUtils.toX509Certificate(bais)); + } + } + return x509Chain; + } + + @VisibleForTesting + class RefreshCertificateTask implements Runnable { + @Override + public void run() { + try { + refreshCertificate(); + } catch (NoSuchAlgorithmException | OperatorCreationException | IOException ex) { + logger.log(Level.SEVERE, "refreshing certificate", ex); + } + } } /** Factory for creating channels to MeshCA sever. */ @@ -94,7 +395,10 @@ MeshCaCertificateProvider create( BackoffPolicy.Provider backoffPolicyProvider, long renewalGracePeriodSeconds, int maxRetryAttempts, - GoogleCredentials oauth2Creds) { + GoogleCredentials oauth2Creds, + ScheduledExecutorService scheduledExecutorService, + TimeProvider timeProvider, + long rpcTimeoutMillis) { return new MeshCaCertificateProvider( watcher, notifyCertUpdates, @@ -108,7 +412,10 @@ MeshCaCertificateProvider create( backoffPolicyProvider, renewalGracePeriodSeconds, maxRetryAttempts, - oauth2Creds); + oauth2Creds, + scheduledExecutorService, + timeProvider, + rpcTimeoutMillis); } }; @@ -129,6 +436,64 @@ abstract MeshCaCertificateProvider create( BackoffPolicy.Provider backoffPolicyProvider, long renewalGracePeriodSeconds, int maxRetryAttempts, - GoogleCredentials oauth2Creds); + GoogleCredentials oauth2Creds, + ScheduledExecutorService scheduledExecutorService, + TimeProvider timeProvider, + long rpcTimeoutMillis); + } + + private class ZoneInfoClientInterceptor implements ClientInterceptor { + private final String zone; + + ZoneInfoClientInterceptor(String zone) { + this.zone = zone; + } + + @Override + public ClientCall interceptCall( + MethodDescriptor method, CallOptions callOptions, Channel next) { + return new ForwardingClientCall.SimpleForwardingClientCall( + next.newCall(method, callOptions)) { + + @Override + public void start(Listener responseListener, Metadata headers) { + headers.put(KEY_FOR_ZONE_INFO, zone); + super.start(responseListener, headers); + } + }; + } } + + @VisibleForTesting + static final Metadata.Key KEY_FOR_ZONE_INFO = + Metadata.Key.of("x-goog-request-params", Metadata.ASCII_STRING_MARSHALLER); + @VisibleForTesting + static final long INITIAL_DELAY_SECONDS = 4L; + + private static final EnumSet RETRIABLE_CODES = + EnumSet.of( + CANCELLED, + UNKNOWN, + DEADLINE_EXCEEDED, + RESOURCE_EXHAUSTED, + ABORTED, + INTERNAL, + UNAVAILABLE); + + private final SynchronizationContext syncContext; + private final ScheduledExecutorService scheduledExecutorService; + private final int maxRetryAttempts; + private final ZoneInfoClientInterceptor headerInterceptor; + private final BackoffPolicy.Provider backoffPolicyProvider; + private final String meshCaUrl; + private final long validitySeconds; + private final long renewalGracePeriodSeconds; + private final int keySize; + private final String alg; + private final String signatureAlg; + private final GoogleCredentials oauth2Creds; + private final TimeProvider timeProvider; + private final MeshCaChannelFactory meshCaChannelFactory; + @VisibleForTesting SynchronizationContext.ScheduledHandle scheduledHandle; + private final long rpcTimeoutMillis; } diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderProvider.java index a9c1b01ba0c..669b0f28591 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderProvider.java @@ -21,10 +21,15 @@ import static com.google.common.base.Preconditions.checkState; import com.google.common.annotations.VisibleForTesting; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.grpc.internal.BackoffPolicy; import io.grpc.internal.ExponentialBackoffPolicy; +import io.grpc.internal.TimeProvider; import io.grpc.xds.internal.sts.StsCredentials; import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -46,16 +51,20 @@ final class MeshCaCertificateProviderProvider implements CertificateProviderProv private static final String STS_URL_KEY = "stsUrl"; private static final String GKE_SA_JWT_LOCATION_KEY = "gkeSaJwtLocation"; - static final String MESHCA_URL_DEFAULT = "meshca.googleapis.com"; - static final long RPC_TIMEOUT_SECONDS_DEFAULT = 5L; - static final long CERT_VALIDITY_SECONDS_DEFAULT = 9L * 3600L; // 9 hours - static final long RENEWAL_GRACE_PERIOD_SECONDS_DEFAULT = 1L * 3600L; // 1 hour - static final String KEY_ALGO_DEFAULT = "RSA"; // aka keyType - static final int KEY_SIZE_DEFAULT = 2048; - static final String SIGNATURE_ALGO_DEFAULT = "SHA256withRSA"; - static final int MAX_RETRY_ATTEMPTS_DEFAULT = 3; + @VisibleForTesting static final String MESHCA_URL_DEFAULT = "meshca.googleapis.com"; + @VisibleForTesting static final long RPC_TIMEOUT_SECONDS_DEFAULT = 5L; + @VisibleForTesting static final long CERT_VALIDITY_SECONDS_DEFAULT = 9L * 3600L; + @VisibleForTesting static final long RENEWAL_GRACE_PERIOD_SECONDS_DEFAULT = 1L * 3600L; + @VisibleForTesting static final String KEY_ALGO_DEFAULT = "RSA"; // aka keyType + @VisibleForTesting static final int KEY_SIZE_DEFAULT = 2048; + @VisibleForTesting static final String SIGNATURE_ALGO_DEFAULT = "SHA256withRSA"; + @VisibleForTesting static final int MAX_RETRY_ATTEMPTS_DEFAULT = 3; + @VisibleForTesting static final String STS_URL_DEFAULT = "https://securetoken.googleapis.com/v1/identitybindingtoken"; + @VisibleForTesting + static final long RPC_TIMEOUT_SECONDS = 10L; + private static final Pattern CLUSTER_URL_PATTERN = Pattern .compile(".*/projects/(.*)/locations/(.*)/clusters/.*"); @@ -70,23 +79,32 @@ final class MeshCaCertificateProviderProvider implements CertificateProviderProv StsCredentials.Factory.getInstance(), MeshCaCertificateProvider.MeshCaChannelFactory.getInstance(), new ExponentialBackoffPolicy.Provider(), - MeshCaCertificateProvider.Factory.getInstance())); + MeshCaCertificateProvider.Factory.getInstance(), + ScheduledExecutorServiceFactory.DEFAULT_INSTANCE, + TimeProvider.SYSTEM_TIME_PROVIDER)); } final StsCredentials.Factory stsCredentialsFactory; final MeshCaCertificateProvider.MeshCaChannelFactory meshCaChannelFactory; final BackoffPolicy.Provider backoffPolicyProvider; final MeshCaCertificateProvider.Factory meshCaCertificateProviderFactory; + final ScheduledExecutorServiceFactory scheduledExecutorServiceFactory; + final TimeProvider timeProvider; @VisibleForTesting - MeshCaCertificateProviderProvider(StsCredentials.Factory stsCredentialsFactory, + MeshCaCertificateProviderProvider( + StsCredentials.Factory stsCredentialsFactory, MeshCaCertificateProvider.MeshCaChannelFactory meshCaChannelFactory, BackoffPolicy.Provider backoffPolicyProvider, - MeshCaCertificateProvider.Factory meshCaCertificateProviderFactory) { + MeshCaCertificateProvider.Factory meshCaCertificateProviderFactory, + ScheduledExecutorServiceFactory scheduledExecutorServiceFactory, + TimeProvider timeProvider) { this.stsCredentialsFactory = stsCredentialsFactory; this.meshCaChannelFactory = meshCaChannelFactory; this.backoffPolicyProvider = backoffPolicyProvider; this.meshCaCertificateProviderFactory = meshCaCertificateProviderFactory; + this.scheduledExecutorServiceFactory = scheduledExecutorServiceFactory; + this.timeProvider = timeProvider; } @Override @@ -106,12 +124,23 @@ public CertificateProvider createCertificateProvider( StsCredentials stsCredentials = stsCredentialsFactory .create(configObj.stsUrl, audience, configObj.gkeSaJwtLocation); - return meshCaCertificateProviderFactory.create(watcher, notifyCertUpdates, configObj.meshCaUrl, + return meshCaCertificateProviderFactory.create( + watcher, + notifyCertUpdates, + configObj.meshCaUrl, configObj.zone, - configObj.certValiditySeconds, configObj.keySize, configObj.keyAlgo, + configObj.certValiditySeconds, + configObj.keySize, + configObj.keyAlgo, configObj.signatureAlgo, - meshCaChannelFactory, backoffPolicyProvider, - configObj.renewalGracePeriodSeconds, configObj.maxRetryAttempts, stsCredentials); + meshCaChannelFactory, + backoffPolicyProvider, + configObj.renewalGracePeriodSeconds, + configObj.maxRetryAttempts, + stsCredentials, + scheduledExecutorServiceFactory.create(configObj.meshCaUrl), + timeProvider, + TimeUnit.SECONDS.toMillis(RPC_TIMEOUT_SECONDS)); } private static Config validateAndTranslateConfig(Object config) { @@ -177,6 +206,28 @@ private static void parseProjectAndZone(String gkeClusterUrl, Config configObj) configObj.zone = matcher.group(2); } + abstract static class ScheduledExecutorServiceFactory { + + private static final ScheduledExecutorServiceFactory DEFAULT_INSTANCE = + new ScheduledExecutorServiceFactory() { + + @Override + ScheduledExecutorService create(String serverUri) { + return Executors.newSingleThreadScheduledExecutor( + new ThreadFactoryBuilder() + .setNameFormat("meshca-" + serverUri + "-%d") + .setDaemon(true) + .build()); + } + }; + + static ScheduledExecutorServiceFactory getInstance() { + return DEFAULT_INSTANCE; + } + + abstract ScheduledExecutorService create(String serverUri); + } + /** POJO class for storing various config values. */ @VisibleForTesting static class Config { diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/trust/CertificateUtils.java b/xds/src/main/java/io/grpc/xds/internal/sds/trust/CertificateUtils.java index 834065a3cb5..a85ea3d6af1 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/trust/CertificateUtils.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/trust/CertificateUtils.java @@ -30,7 +30,7 @@ /** * Contains certificate utility method(s). */ -final class CertificateUtils { +public final class CertificateUtils { private static CertificateFactory factory; @@ -46,19 +46,26 @@ private static synchronized void initInstance() throws CertificateException { * @param file a {@link File} containing the cert data */ static X509Certificate[] toX509Certificates(File file) throws CertificateException, IOException { - FileInputStream fis = new FileInputStream(file); - return toX509Certificates(new BufferedInputStream(fis)); + try (FileInputStream fis = new FileInputStream(file); + BufferedInputStream bis = new BufferedInputStream(fis)) { + return toX509Certificates(bis); + } } static synchronized X509Certificate[] toX509Certificates(InputStream inputStream) throws CertificateException, IOException { initInstance(); - try { - Collection certs = factory.generateCertificates(inputStream); - return certs.toArray(new X509Certificate[0]); - } finally { - inputStream.close(); - } + Collection certs = factory.generateCertificates(inputStream); + return certs.toArray(new X509Certificate[0]); + + } + + /** See {@link CertificateFactory#generateCertificate(InputStream)}. */ + public static synchronized X509Certificate toX509Certificate(InputStream inputStream) + throws CertificateException, IOException { + initInstance(); + Certificate cert = factory.generateCertificate(inputStream); + return (X509Certificate) cert; } private CertificateUtils() {} diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/trust/SdsTrustManagerFactory.java b/xds/src/main/java/io/grpc/xds/internal/sds/trust/SdsTrustManagerFactory.java index b495570573a..6ff63c0e5f0 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/trust/SdsTrustManagerFactory.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/trust/SdsTrustManagerFactory.java @@ -27,6 +27,7 @@ import io.netty.handler.ssl.util.SimpleTrustManagerFactory; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; @@ -69,8 +70,10 @@ private static X509Certificate[] getTrustedCaFromCertContext( "trustedCa.file-name in certificateValidationContext cannot be empty"); return CertificateUtils.toX509Certificates(new File(certsFile)); } else if (specifierCase == SpecifierCase.INLINE_BYTES) { - return CertificateUtils.toX509Certificates( - certificateValidationContext.getTrustedCa().getInlineBytes().newInput()); + try (InputStream is = + certificateValidationContext.getTrustedCa().getInlineBytes().newInput()) { + return CertificateUtils.toX509Certificates(is); + } } else { throw new IllegalArgumentException("Not supported: " + specifierCase); } diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/CertificateProviderStoreTest.java b/xds/src/test/java/io/grpc/xds/internal/certprovider/CertificateProviderStoreTest.java index 8a0dfeb1d7e..569d72bf43d 100644 --- a/xds/src/test/java/io/grpc/xds/internal/certprovider/CertificateProviderStoreTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/certprovider/CertificateProviderStoreTest.java @@ -169,7 +169,7 @@ public void onePluginSameConfig_sameInstance() { (TestCertificateProvider) handle1.certProvider; assertThat(testCertificateProvider.startCalled).isEqualTo(1); CertificateProvider.DistributorWatcher distWatcher = testCertificateProvider.getWatcher(); - assertThat(distWatcher.downsstreamWatchers).hasSize(2); + assertThat(distWatcher.downstreamWatchers).hasSize(2); PrivateKey testKey = mock(PrivateKey.class); X509Certificate cert = mock(X509Certificate.class); List testList = ImmutableList.of(cert); @@ -185,7 +185,7 @@ public void onePluginSameConfig_sameInstance() { reset(mockWatcher2); handle1.close(); assertThat(testCertificateProvider.closeCalled).isEqualTo(0); - assertThat(distWatcher.downsstreamWatchers).hasSize(1); + assertThat(distWatcher.downstreamWatchers).hasSize(1); testCertificateProvider.getWatcher().updateCertificate(testKey, testList); verify(mockWatcher1, never()) .updateCertificate(any(PrivateKey.class), anyListOf(X509Certificate.class)); @@ -221,7 +221,7 @@ public void onePluginSameConfig_secondWatcherAfterFirstNotify() { CertificateProvider.Watcher mockWatcher2 = mock(CertificateProvider.Watcher.class); CertificateProviderStore.Handle unused = certificateProviderStore.createOrGetProvider( "cert-name1", "plugin1", "config", mockWatcher2, true); - assertThat(distWatcher.downsstreamWatchers).hasSize(2); + assertThat(distWatcher.downstreamWatchers).hasSize(2); // updates sent to the second watcher verify(mockWatcher2, times(1)).updateCertificate(eq(testKey), eq(testList)); verify(mockWatcher2, times(1)).updateTrustedRoots(eq(testList)); @@ -323,9 +323,9 @@ private static void checkDifferentInstances( assertThat(testCertificateProvider2.certProviderProvider) .isSameInstanceAs(certProviderProvider2); CertificateProvider.DistributorWatcher distWatcher1 = testCertificateProvider1.getWatcher(); - assertThat(distWatcher1.downsstreamWatchers).hasSize(1); + assertThat(distWatcher1.downstreamWatchers).hasSize(1); CertificateProvider.DistributorWatcher distWatcher2 = testCertificateProvider2.getWatcher(); - assertThat(distWatcher2.downsstreamWatchers).hasSize(1); + assertThat(distWatcher2.downstreamWatchers).hasSize(1); PrivateKey testKey1 = mock(PrivateKey.class); X509Certificate cert1 = mock(X509Certificate.class); List testList1 = ImmutableList.of(cert1); diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderProviderTest.java index d9d4da9350b..b28bd4980b8 100644 --- a/xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderProviderTest.java @@ -17,19 +17,25 @@ package io.grpc.xds.internal.certprovider; import static com.google.common.truth.Truth.assertThat; +import static io.grpc.xds.internal.certprovider.MeshCaCertificateProviderProvider.RPC_TIMEOUT_SECONDS; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.google.auth.oauth2.GoogleCredentials; import io.grpc.internal.BackoffPolicy; import io.grpc.internal.ExponentialBackoffPolicy; +import io.grpc.internal.TimeProvider; import io.grpc.xds.internal.sts.StsCredentials; import java.io.IOException; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -59,6 +65,14 @@ public class MeshCaCertificateProviderProviderTest { @Mock MeshCaCertificateProvider.Factory meshCaCertificateProviderFactory; + + @Mock + private MeshCaCertificateProviderProvider.ScheduledExecutorServiceFactory + scheduledExecutorServiceFactory; + + @Mock + private TimeProvider timeProvider; + private MeshCaCertificateProviderProvider provider; @Before @@ -69,7 +83,9 @@ public void setUp() throws IOException { stsCredentialsFactory, meshCaChannelFactory, backoffPolicyProvider, - meshCaCertificateProviderFactory); + meshCaCertificateProviderFactory, + scheduledExecutorServiceFactory, + timeProvider); } @Test @@ -94,6 +110,10 @@ public void createProvider_minimalConfig() { CertificateProvider.DistributorWatcher distWatcher = new CertificateProvider.DistributorWatcher(); Map map = buildMinimalMap(); + ScheduledExecutorService mockService = mock(ScheduledExecutorService.class); + when(scheduledExecutorServiceFactory.create( + eq(MeshCaCertificateProviderProvider.MESHCA_URL_DEFAULT))) + .thenReturn(mockService); provider.createCertificateProvider(map, distWatcher, true); verify(stsCredentialsFactory, times(1)) .create( @@ -114,7 +134,10 @@ public void createProvider_minimalConfig() { eq(backoffPolicyProvider), eq(MeshCaCertificateProviderProvider.RENEWAL_GRACE_PERIOD_SECONDS_DEFAULT), eq(MeshCaCertificateProviderProvider.MAX_RETRY_ATTEMPTS_DEFAULT), - (GoogleCredentials) isNull()); + (GoogleCredentials) isNull(), + eq(mockService), + eq(timeProvider), + eq(TimeUnit.SECONDS.toMillis(RPC_TIMEOUT_SECONDS))); } @Test @@ -150,7 +173,9 @@ public void createProvider_missingProject_expectException() { CertificateProvider.DistributorWatcher distWatcher = new CertificateProvider.DistributorWatcher(); Map map = buildMinimalMap(); - map.put("gkeClusterUrl", "https://container.googleapis.com/v1/project/test-project1/locations/test-zone2/clusters/test-cluster3"); + map.put( + "gkeClusterUrl", + "https://container.googleapis.com/v1/project/test-project1/locations/test-zone2/clusters/test-cluster3"); try { provider.createCertificateProvider(map, distWatcher, true); fail("exception expected"); @@ -164,6 +189,9 @@ public void createProvider_nonDefaultFullConfig() { CertificateProvider.DistributorWatcher distWatcher = new CertificateProvider.DistributorWatcher(); Map map = buildFullMap(); + ScheduledExecutorService mockService = mock(ScheduledExecutorService.class); + when(scheduledExecutorServiceFactory.create(eq(NON_DEFAULT_MESH_CA_URL))) + .thenReturn(mockService); provider.createCertificateProvider(map, distWatcher, true); verify(stsCredentialsFactory, times(1)) .create( @@ -184,7 +212,10 @@ public void createProvider_nonDefaultFullConfig() { eq(backoffPolicyProvider), eq(4321L), eq(9), - (GoogleCredentials) isNull()); + (GoogleCredentials) isNull(), + eq(mockService), + eq(timeProvider), + eq(TimeUnit.SECONDS.toMillis(RPC_TIMEOUT_SECONDS))); } private Map buildFullMap() { diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderTest.java new file mode 100644 index 00000000000..7117b8cccbe --- /dev/null +++ b/xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderTest.java @@ -0,0 +1,538 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds.internal.certprovider; + +import static com.google.common.truth.Truth.assertThat; +import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CA_PEM_FILE; +import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_0_PEM_FILE; +import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; +import static org.mockito.AdditionalAnswers.delegatesTo; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.auth.http.AuthHttpConstants; +import com.google.auth.oauth2.AccessToken; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.MoreExecutors; +import google.security.meshca.v1.MeshCertificateServiceGrpc; +import google.security.meshca.v1.Meshca; +import io.grpc.Context; +import io.grpc.ManagedChannel; +import io.grpc.Metadata; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import io.grpc.SynchronizationContext; +import io.grpc.inprocess.InProcessChannelBuilder; +import io.grpc.inprocess.InProcessServerBuilder; +import io.grpc.internal.BackoffPolicy; +import io.grpc.internal.TimeProvider; +import io.grpc.testing.GrpcCleanupRule; +import io.grpc.xds.internal.certprovider.CertificateProvider.DistributorWatcher; +import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil; +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayDeque; +import java.util.Date; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import org.bouncycastle.operator.OperatorCreationException; +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.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +/** Unit tests for {@link MeshCaCertificateProvider}. */ +@RunWith(JUnit4.class) +public class MeshCaCertificateProviderTest { + + private static final String TEST_STS_TOKEN = "test-stsToken"; + private static final long RENEWAL_GRACE_PERIOD_SECONDS = TimeUnit.HOURS.toSeconds(1L); + private static final Metadata.Key KEY_FOR_AUTHORIZATION = + Metadata.Key.of(AuthHttpConstants.AUTHORIZATION, Metadata.ASCII_STRING_MARSHALLER); + private static final String ZONE = "us-west2-a"; + private static final long START_DELAY = 200_000_000L; // 0.2 seconds + private static final long[] DELAY_VALUES = {START_DELAY, START_DELAY * 2, START_DELAY * 4}; + private static final long RPC_TIMEOUT_MILLIS = 100L; + /** + * Expire time of cert SERVER_0_PEM_FILE. + */ + private static final long CERT0_EXPIRY_TIME_MILLIS = 1899853658000L; + /** + * Cert validity of 12 hours for the above cert. + */ + private static final long CERT0_VALIDITY_MILLIS = TimeUnit.MILLISECONDS + .convert(12, TimeUnit.HOURS); + /** + * Compute current time based on cert expiry and cert validity. + */ + private static final long CURRENT_TIME_NANOS = + TimeUnit.MILLISECONDS.toNanos(CERT0_EXPIRY_TIME_MILLIS - CERT0_VALIDITY_MILLIS); + @Rule + public final GrpcCleanupRule cleanupRule = new GrpcCleanupRule(); + + private static class ResponseToSend { + Throwable getThrowable() { + throw new UnsupportedOperationException("Called on " + getClass().getCanonicalName()); + } + + List getList() { + throw new UnsupportedOperationException("Called on " + getClass().getCanonicalName()); + } + } + + private static class ResponseThrowable extends ResponseToSend { + final Throwable throwableToSend; + + ResponseThrowable(Throwable throwable) { + throwableToSend = throwable; + } + + @Override + Throwable getThrowable() { + return throwableToSend; + } + } + + private static class ResponseList extends ResponseToSend { + final List listToSend; + + ResponseList(List list) { + listToSend = list; + } + + @Override + List getList() { + return listToSend; + } + } + + private final Queue receivedRequests = new ArrayDeque<>(); + private final Queue receivedStsCreds = new ArrayDeque<>(); + private final Queue receivedZoneValues = new ArrayDeque<>(); + private final Queue responsesToSend = new ArrayDeque<>(); + private final Queue oauth2Tokens = new ArrayDeque<>(); + private final AtomicBoolean callEnded = new AtomicBoolean(true); + + @Mock private MeshCertificateServiceGrpc.MeshCertificateServiceImplBase mockedMeshCaService; + @Mock private CertificateProvider.Watcher mockWatcher; + @Mock private BackoffPolicy.Provider backoffPolicyProvider; + @Mock private BackoffPolicy backoffPolicy; + @Spy private GoogleCredentials oauth2Creds; + @Mock private ScheduledExecutorService timeService; + @Mock private TimeProvider timeProvider; + + private ManagedChannel channel; + private MeshCaCertificateProvider provider; + + @Before + public void setUp() throws IOException { + MockitoAnnotations.initMocks(this); + when(backoffPolicyProvider.get()).thenReturn(backoffPolicy); + when(backoffPolicy.nextBackoffNanos()) + .thenReturn(DELAY_VALUES[0], DELAY_VALUES[1], DELAY_VALUES[2]); + doAnswer( + new Answer() { + @Override + public AccessToken answer(InvocationOnMock invocation) throws Throwable { + return new AccessToken( + oauth2Tokens.poll(), new Date(System.currentTimeMillis() + 1000L)); + } + }) + .when(oauth2Creds) + .refreshAccessToken(); + final String meshCaUri = InProcessServerBuilder.generateName(); + MeshCertificateServiceGrpc.MeshCertificateServiceImplBase meshCaServiceImpl = + new MeshCertificateServiceGrpc.MeshCertificateServiceImplBase() { + + @Override + public void createCertificate( + google.security.meshca.v1.Meshca.MeshCertificateRequest request, + io.grpc.stub.StreamObserver + responseObserver) { + assertThat(callEnded.get()).isTrue(); // ensure previous call was ended + callEnded.set(false); + Context.current() + .addListener( + new Context.CancellationListener() { + @Override + public void cancelled(Context context) { + callEnded.set(true); + } + }, + MoreExecutors.directExecutor()); + receivedRequests.offer(request); + ResponseToSend response = responsesToSend.poll(); + if (response instanceof ResponseThrowable) { + responseObserver.onError(response.getThrowable()); + } else if (response instanceof ResponseList) { + List certChainInResponse = response.getList(); + Meshca.MeshCertificateResponse responseToSend = + Meshca.MeshCertificateResponse.newBuilder() + .addAllCertChain(certChainInResponse) + .build(); + responseObserver.onNext(responseToSend); + responseObserver.onCompleted(); + } else { + callEnded.set(true); + } + } + }; + mockedMeshCaService = + mock( + MeshCertificateServiceGrpc.MeshCertificateServiceImplBase.class, + delegatesTo(meshCaServiceImpl)); + ServerInterceptor interceptor = + new ServerInterceptor() { + @Override + public ServerCall.Listener interceptCall( + ServerCall call, Metadata headers, ServerCallHandler next) { + receivedStsCreds.offer(headers.get(KEY_FOR_AUTHORIZATION)); + receivedZoneValues.offer(headers.get(MeshCaCertificateProvider.KEY_FOR_ZONE_INFO)); + return next.startCall(call, headers); + } + }; + cleanupRule.register( + InProcessServerBuilder.forName(meshCaUri) + .addService(mockedMeshCaService) + .intercept(interceptor) + .directExecutor() + .build() + .start()); + channel = + cleanupRule.register(InProcessChannelBuilder.forName(meshCaUri).directExecutor().build()); + MeshCaCertificateProvider.MeshCaChannelFactory channelFactory = + new MeshCaCertificateProvider.MeshCaChannelFactory() { + @Override + ManagedChannel createChannel(String serverUri) { + assertThat(serverUri).isEqualTo(meshCaUri); + return channel; + } + }; + CertificateProvider.DistributorWatcher watcher = new CertificateProvider.DistributorWatcher(); + watcher.addWatcher(mockWatcher); // + provider = + new MeshCaCertificateProvider( + watcher, + true, + meshCaUri, + ZONE, + TimeUnit.HOURS.toSeconds(9L), + 2048, + "RSA", + "SHA256withRSA", + channelFactory, + backoffPolicyProvider, + RENEWAL_GRACE_PERIOD_SECONDS, + MeshCaCertificateProviderProvider.MAX_RETRY_ATTEMPTS_DEFAULT, + oauth2Creds, + timeService, + timeProvider, + RPC_TIMEOUT_MILLIS); + } + + @Test + public void startAndClose() { + ScheduledFuture scheduledFuture = mock(ScheduledFuture.class); + doReturn(scheduledFuture) + .when(timeService) + .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); + provider.start(); + SynchronizationContext.ScheduledHandle savedScheduledHandle = provider.scheduledHandle; + assertThat(savedScheduledHandle).isNotNull(); + assertThat(savedScheduledHandle.isPending()).isTrue(); + verify(timeService, times(1)) + .schedule( + any(Runnable.class), + eq(MeshCaCertificateProvider.INITIAL_DELAY_SECONDS), + eq(TimeUnit.SECONDS)); + DistributorWatcher distWatcher = provider.getWatcher(); + assertThat(distWatcher.downstreamWatchers).hasSize(1); + PrivateKey mockKey = mock(PrivateKey.class); + X509Certificate mockCert = mock(X509Certificate.class); + distWatcher.updateCertificate(mockKey, ImmutableList.of(mockCert)); + distWatcher.updateTrustedRoots(ImmutableList.of(mockCert)); + provider.close(); + assertThat(provider.scheduledHandle).isNull(); + assertThat(savedScheduledHandle.isPending()).isFalse(); + assertThat(distWatcher.downstreamWatchers).isEmpty(); + assertThat(distWatcher.getLastIdentityCert()).isNull(); + } + + @Test + public void startTwice_noException() { + ScheduledFuture scheduledFuture = mock(ScheduledFuture.class); + doReturn(scheduledFuture) + .when(timeService) + .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); + provider.start(); + SynchronizationContext.ScheduledHandle savedScheduledHandle1 = provider.scheduledHandle; + provider.start(); + SynchronizationContext.ScheduledHandle savedScheduledHandle2 = provider.scheduledHandle; + assertThat(savedScheduledHandle2).isNotSameInstanceAs(savedScheduledHandle1); + assertThat(savedScheduledHandle2.isPending()).isTrue(); + } + + @Test + public void getCertificate() + throws IOException, CertificateException, OperatorCreationException, + NoSuchAlgorithmException { + oauth2Tokens.offer(TEST_STS_TOKEN + "0"); + responsesToSend.offer( + new ResponseList(ImmutableList.of( + CommonTlsContextTestsUtil.getResourceContents(SERVER_0_PEM_FILE), + CommonTlsContextTestsUtil.getResourceContents(SERVER_1_PEM_FILE), + CommonTlsContextTestsUtil.getResourceContents(CA_PEM_FILE)))); + when(timeProvider.currentTimeNanos()).thenReturn(CURRENT_TIME_NANOS); + ScheduledFuture scheduledFuture = mock(ScheduledFuture.class); + doReturn(scheduledFuture) + .when(timeService) + .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); + provider.refreshCertificate(); + Meshca.MeshCertificateRequest receivedReq = receivedRequests.poll(); + assertThat(receivedReq.getValidity().getSeconds()).isEqualTo(TimeUnit.HOURS.toSeconds(9L)); + // cannot decode CSR: just check the PEM format delimiters + String csr = receivedReq.getCsr(); + assertThat(csr).startsWith("-----BEGIN NEW CERTIFICATE REQUEST-----"); + verifyReceivedMetadataValues(1); + verify(timeService, times(1)) + .schedule( + any(Runnable.class), + eq( + TimeUnit.MILLISECONDS.toSeconds( + CERT0_VALIDITY_MILLIS + - TimeUnit.SECONDS.toMillis(RENEWAL_GRACE_PERIOD_SECONDS))), + eq(TimeUnit.SECONDS)); + verifyMockWatcher(); + } + + @Test + public void getCertificate_withError() + throws IOException, OperatorCreationException, NoSuchAlgorithmException { + oauth2Tokens.offer(TEST_STS_TOKEN + "0"); + responsesToSend + .offer(new ResponseThrowable(new StatusRuntimeException(Status.FAILED_PRECONDITION))); + ScheduledFuture scheduledFuture = mock(ScheduledFuture.class); + doReturn(scheduledFuture).when(timeService) + .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); + provider.refreshCertificate(); + verify(mockWatcher, never()) + .updateCertificate(any(PrivateKey.class), ArgumentMatchers.anyList()); + verify(mockWatcher, never()).updateTrustedRoots(ArgumentMatchers.anyList()); + verify(mockWatcher, times(1)).onError(Status.FAILED_PRECONDITION); + verify(timeService, times(1)).schedule(any(Runnable.class), + eq(MeshCaCertificateProvider.INITIAL_DELAY_SECONDS), + eq(TimeUnit.SECONDS)); + verifyReceivedMetadataValues(1); + } + + @Test + public void getCertificate_withError_withExistingCert() + throws IOException, OperatorCreationException, NoSuchAlgorithmException { + PrivateKey mockKey = mock(PrivateKey.class); + X509Certificate mockCert = mock(X509Certificate.class); + // have current cert expire in 3 hours from current time + long threeHoursFromNowMillis = TimeUnit.NANOSECONDS + .toMillis(CURRENT_TIME_NANOS + TimeUnit.HOURS.toNanos(3)); + when(mockCert.getNotAfter()).thenReturn(new Date(threeHoursFromNowMillis)); + provider.getWatcher().updateCertificate(mockKey, ImmutableList.of(mockCert)); + reset(mockWatcher); + oauth2Tokens.offer(TEST_STS_TOKEN + "0"); + responsesToSend + .offer(new ResponseThrowable(new StatusRuntimeException(Status.FAILED_PRECONDITION))); + when(timeProvider.currentTimeNanos()).thenReturn(CURRENT_TIME_NANOS); + ScheduledFuture scheduledFuture = mock(ScheduledFuture.class); + doReturn(scheduledFuture).when(timeService) + .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); + provider.refreshCertificate(); + verify(mockWatcher, never()) + .updateCertificate(any(PrivateKey.class), ArgumentMatchers.anyList()); + verify(mockWatcher, never()).updateTrustedRoots(ArgumentMatchers.anyList()); + verify(mockWatcher, never()).onError(any(Status.class)); + verify(timeService, times(1)).schedule(any(Runnable.class), + eq(5400L), + eq(TimeUnit.SECONDS)); + assertThat(provider.getWatcher().getLastIdentityCert()).isNotNull(); + verifyReceivedMetadataValues(1); + } + + @Test + public void getCertificate_withError_withExistingExpiredCert() + throws IOException, OperatorCreationException, NoSuchAlgorithmException { + PrivateKey mockKey = mock(PrivateKey.class); + X509Certificate mockCert = mock(X509Certificate.class); + // have current cert expire in 3 seconds from current time + long threeSecondsFromNowMillis = TimeUnit.NANOSECONDS + .toMillis(CURRENT_TIME_NANOS + TimeUnit.SECONDS.toNanos(3)); + when(mockCert.getNotAfter()).thenReturn(new Date(threeSecondsFromNowMillis)); + provider.getWatcher().updateCertificate(mockKey, ImmutableList.of(mockCert)); + reset(mockWatcher); + oauth2Tokens.offer(TEST_STS_TOKEN + "0"); + responsesToSend + .offer(new ResponseThrowable(new StatusRuntimeException(Status.FAILED_PRECONDITION))); + when(timeProvider.currentTimeNanos()).thenReturn(CURRENT_TIME_NANOS); + ScheduledFuture scheduledFuture = mock(ScheduledFuture.class); + doReturn(scheduledFuture).when(timeService) + .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); + provider.refreshCertificate(); + verify(mockWatcher, never()) + .updateCertificate(any(PrivateKey.class), ArgumentMatchers.anyList()); + verify(mockWatcher, never()).updateTrustedRoots(ArgumentMatchers.anyList()); + verify(mockWatcher, times(1)).onError(Status.FAILED_PRECONDITION); + verify(timeService, times(1)).schedule(any(Runnable.class), + eq(MeshCaCertificateProvider.INITIAL_DELAY_SECONDS), + eq(TimeUnit.SECONDS)); + assertThat(provider.getWatcher().getLastIdentityCert()).isNull(); + verifyReceivedMetadataValues(1); + } + + @Test + public void getCertificate_retriesWithErrors() + throws IOException, CertificateException, OperatorCreationException, + NoSuchAlgorithmException, InterruptedException, ExecutionException, TimeoutException { + oauth2Tokens.offer(TEST_STS_TOKEN + "0"); + oauth2Tokens.offer(TEST_STS_TOKEN + "1"); + oauth2Tokens.offer(TEST_STS_TOKEN + "2"); + responsesToSend.offer(new ResponseThrowable(new StatusRuntimeException(Status.UNKNOWN))); + responsesToSend.offer( + new ResponseThrowable( + new Exception(new StatusRuntimeException(Status.RESOURCE_EXHAUSTED)))); + responsesToSend.offer(new ResponseList(ImmutableList.of( + CommonTlsContextTestsUtil.getResourceContents(SERVER_0_PEM_FILE), + CommonTlsContextTestsUtil.getResourceContents(SERVER_1_PEM_FILE), + CommonTlsContextTestsUtil.getResourceContents(CA_PEM_FILE)))); + when(timeProvider.currentTimeNanos()).thenReturn(CURRENT_TIME_NANOS); + ScheduledFuture scheduledFuture = mock(ScheduledFuture.class); + doReturn(scheduledFuture).when(timeService) + .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); + ScheduledFuture scheduledFutureSleep = mock(ScheduledFuture.class); + doReturn(scheduledFutureSleep).when(timeService) + .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.NANOSECONDS)); + provider.refreshCertificate(); + assertThat(receivedRequests.size()).isEqualTo(3); + verify(timeService, times(1)).schedule(any(Runnable.class), + eq(TimeUnit.MILLISECONDS.toSeconds( + CERT0_VALIDITY_MILLIS - TimeUnit.SECONDS.toMillis(RENEWAL_GRACE_PERIOD_SECONDS))), + eq(TimeUnit.SECONDS)); + verifyRetriesWithBackoff(scheduledFutureSleep, 2); + verifyMockWatcher(); + verifyReceivedMetadataValues(3); + } + + @Test + public void getCertificate_retriesWithTimeouts() + throws IOException, CertificateException, OperatorCreationException, + NoSuchAlgorithmException, InterruptedException, ExecutionException, TimeoutException { + oauth2Tokens.offer(TEST_STS_TOKEN + "0"); + oauth2Tokens.offer(TEST_STS_TOKEN + "1"); + oauth2Tokens.offer(TEST_STS_TOKEN + "2"); + oauth2Tokens.offer(TEST_STS_TOKEN + "3"); + responsesToSend.offer(new ResponseToSend()); + responsesToSend.offer(new ResponseToSend()); + responsesToSend.offer(new ResponseToSend()); + responsesToSend.offer(new ResponseList(ImmutableList.of( + CommonTlsContextTestsUtil.getResourceContents(SERVER_0_PEM_FILE), + CommonTlsContextTestsUtil.getResourceContents(SERVER_1_PEM_FILE), + CommonTlsContextTestsUtil.getResourceContents(CA_PEM_FILE)))); + when(timeProvider.currentTimeNanos()).thenReturn(CURRENT_TIME_NANOS); + ScheduledFuture scheduledFuture = mock(ScheduledFuture.class); + doReturn(scheduledFuture).when(timeService) + .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); + ScheduledFuture scheduledFutureSleep = mock(ScheduledFuture.class); + doReturn(scheduledFutureSleep).when(timeService) + .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.NANOSECONDS)); + provider.refreshCertificate(); + assertThat(receivedRequests.size()).isEqualTo(4); + verify(timeService, times(1)).schedule(any(Runnable.class), + eq(TimeUnit.MILLISECONDS.toSeconds( + CERT0_VALIDITY_MILLIS - TimeUnit.SECONDS.toMillis(RENEWAL_GRACE_PERIOD_SECONDS))), + eq(TimeUnit.SECONDS)); + verifyRetriesWithBackoff(scheduledFutureSleep, 3); + verifyMockWatcher(); + verifyReceivedMetadataValues(4); + } + + private void verifyRetriesWithBackoff(ScheduledFuture scheduledFutureSleep, int numOfRetries) + throws InterruptedException, ExecutionException, TimeoutException { + for (int i = 0; i < numOfRetries; i++) { + long delayValue = DELAY_VALUES[i]; + verify(timeService, times(1)).schedule(any(Runnable.class), + eq(delayValue), + eq(TimeUnit.NANOSECONDS)); + verify(scheduledFutureSleep, times(1)).get(eq(delayValue), eq(TimeUnit.NANOSECONDS)); + } + } + + private void verifyMockWatcher() throws IOException, CertificateException { + ArgumentCaptor> certChainCaptor = ArgumentCaptor.forClass(null); + verify(mockWatcher, times(1)) + .updateCertificate(any(PrivateKey.class), certChainCaptor.capture()); + List certChain = certChainCaptor.getValue(); + assertThat(certChain).hasSize(3); + assertThat(certChain.get(0)) + .isEqualTo(CommonTlsContextTestsUtil.getCertFromResourceName(SERVER_0_PEM_FILE)); + assertThat(certChain.get(1)) + .isEqualTo(CommonTlsContextTestsUtil.getCertFromResourceName(SERVER_1_PEM_FILE)); + assertThat(certChain.get(2)) + .isEqualTo(CommonTlsContextTestsUtil.getCertFromResourceName(CA_PEM_FILE)); + + ArgumentCaptor> rootsCaptor = ArgumentCaptor.forClass(null); + verify(mockWatcher, times(1)).updateTrustedRoots(rootsCaptor.capture()); + List roots = rootsCaptor.getValue(); + assertThat(roots).hasSize(1); + assertThat(roots.get(0)) + .isEqualTo(CommonTlsContextTestsUtil.getCertFromResourceName(CA_PEM_FILE)); + verify(mockWatcher, never()).onError(any(Status.class)); + } + + private void verifyReceivedMetadataValues(int count) { + assertThat(receivedStsCreds).hasSize(count); + assertThat(receivedZoneValues).hasSize(count); + for (int i = 0; i < count; i++) { + assertThat(receivedStsCreds.poll()).isEqualTo("Bearer " + TEST_STS_TOKEN + i); + assertThat(receivedZoneValues.poll()).isEqualTo("us-west2-a"); + } + } +} diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/CommonTlsContextTestsUtil.java b/xds/src/test/java/io/grpc/xds/internal/sds/CommonTlsContextTestsUtil.java index b6782b25626..afa57f96c46 100644 --- a/xds/src/test/java/io/grpc/xds/internal/sds/CommonTlsContextTestsUtil.java +++ b/xds/src/test/java/io/grpc/xds/internal/sds/CommonTlsContextTestsUtil.java @@ -16,7 +16,10 @@ package io.grpc.xds.internal.sds; +import static java.nio.charset.StandardCharsets.UTF_8; + import com.google.common.base.Strings; +import com.google.common.io.CharStreams; import com.google.protobuf.BoolValue; import com.google.protobuf.Struct; import com.google.protobuf.Value; @@ -36,7 +39,14 @@ import io.envoyproxy.envoy.type.matcher.v3.StringMatcher; import io.grpc.internal.testing.TestUtils; import io.grpc.xds.EnvoyServerProtoData; +import io.grpc.xds.internal.sds.trust.CertificateUtils; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; import java.util.Arrays; import javax.annotation.Nullable; @@ -432,4 +442,23 @@ static EnvoyServerProtoData.UpstreamTlsContext buildUpstreamTlsContext( return EnvoyServerProtoData.UpstreamTlsContext.fromEnvoyProtoUpstreamTlsContext( upstreamTlsContext); } + + /** Gets a cert from contents of a resource. */ + public static X509Certificate getCertFromResourceName(String resourceName) + throws IOException, CertificateException { + try (ByteArrayInputStream bais = + new ByteArrayInputStream(getResourceContents(resourceName).getBytes(UTF_8))) { + return CertificateUtils.toX509Certificate(bais); + } + } + + /** Gets contents of a resource from TestUtils.class loader. */ + public static String getResourceContents(String resourceName) throws IOException { + InputStream inputStream = TestUtils.class.getResourceAsStream("/certs/" + resourceName); + String text = null; + try (Reader reader = new InputStreamReader(inputStream, UTF_8)) { + text = CharStreams.toString(reader); + } + return text; + } } From 93b1830838a55fd7e6c1bbd744dbc1707bf8751e Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Fri, 7 Aug 2020 17:09:24 -0700 Subject: [PATCH 52/88] xds: fix broken references to MeshCa proto objects (#7304) --- .../MeshCaCertificateProvider.java | 13 +++++++------ .../MeshCaCertificateProviderTest.java | 19 ++++++++++--------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProvider.java index c03829766f8..18fc27b9461 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProvider.java @@ -31,8 +31,9 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.protobuf.Duration; -import google.security.meshca.v1.MeshCertificateServiceGrpc; -import google.security.meshca.v1.Meshca; +import com.google.security.meshca.v1.MeshCertificateRequest; +import com.google.security.meshca.v1.MeshCertificateResponse; +import com.google.security.meshca.v1.MeshCertificateServiceGrpc; import io.grpc.CallOptions; import io.grpc.Channel; import io.grpc.ClientCall; @@ -213,8 +214,8 @@ private List makeRequestWithRetries( String reqId, Duration duration, String csr) { - Meshca.MeshCertificateRequest request = - Meshca.MeshCertificateRequest.newBuilder() + MeshCertificateRequest request = + MeshCertificateRequest.newBuilder() .setValidity(duration) .setCsr(csr) .setRequestId(reqId) @@ -224,7 +225,7 @@ private List makeRequestWithRetries( Throwable lastException = null; for (int i = 0; i <= maxRetryAttempts; i++) { try { - Meshca.MeshCertificateResponse response = + MeshCertificateResponse response = stub.withDeadlineAfter(rpcTimeoutMillis, TimeUnit.MILLISECONDS) .createCertificate(request); return getX509CertificatesFromResponse(response); @@ -326,7 +327,7 @@ private static void shutdownChannel(ManagedChannel channel) { } private List getX509CertificatesFromResponse( - Meshca.MeshCertificateResponse response) throws CertificateException, IOException { + MeshCertificateResponse response) throws CertificateException, IOException { List certChain = response.getCertChainList(); List x509Chain = new ArrayList<>(certChain.size()); for (String certString : certChain) { diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderTest.java index 7117b8cccbe..7f1f04a7f93 100644 --- a/xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderTest.java @@ -37,8 +37,9 @@ import com.google.auth.oauth2.GoogleCredentials; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.MoreExecutors; -import google.security.meshca.v1.MeshCertificateServiceGrpc; -import google.security.meshca.v1.Meshca; +import com.google.security.meshca.v1.MeshCertificateRequest; +import com.google.security.meshca.v1.MeshCertificateResponse; +import com.google.security.meshca.v1.MeshCertificateServiceGrpc; import io.grpc.Context; import io.grpc.ManagedChannel; import io.grpc.Metadata; @@ -52,6 +53,7 @@ import io.grpc.inprocess.InProcessServerBuilder; import io.grpc.internal.BackoffPolicy; import io.grpc.internal.TimeProvider; +import io.grpc.stub.StreamObserver; import io.grpc.testing.GrpcCleanupRule; import io.grpc.xds.internal.certprovider.CertificateProvider.DistributorWatcher; import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil; @@ -149,7 +151,7 @@ List getList() { } } - private final Queue receivedRequests = new ArrayDeque<>(); + private final Queue receivedRequests = new ArrayDeque<>(); private final Queue receivedStsCreds = new ArrayDeque<>(); private final Queue receivedZoneValues = new ArrayDeque<>(); private final Queue responsesToSend = new ArrayDeque<>(); @@ -189,9 +191,8 @@ public AccessToken answer(InvocationOnMock invocation) throws Throwable { @Override public void createCertificate( - google.security.meshca.v1.Meshca.MeshCertificateRequest request, - io.grpc.stub.StreamObserver - responseObserver) { + MeshCertificateRequest request, + StreamObserver responseObserver) { assertThat(callEnded.get()).isTrue(); // ensure previous call was ended callEnded.set(false); Context.current() @@ -209,8 +210,8 @@ public void cancelled(Context context) { responseObserver.onError(response.getThrowable()); } else if (response instanceof ResponseList) { List certChainInResponse = response.getList(); - Meshca.MeshCertificateResponse responseToSend = - Meshca.MeshCertificateResponse.newBuilder() + MeshCertificateResponse responseToSend = + MeshCertificateResponse.newBuilder() .addAllCertChain(certChainInResponse) .build(); responseObserver.onNext(responseToSend); @@ -331,7 +332,7 @@ public void getCertificate() .when(timeService) .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); provider.refreshCertificate(); - Meshca.MeshCertificateRequest receivedReq = receivedRequests.poll(); + MeshCertificateRequest receivedReq = receivedRequests.poll(); assertThat(receivedReq.getValidity().getSeconds()).isEqualTo(TimeUnit.HOURS.toSeconds(9L)); // cannot decode CSR: just check the PEM format delimiters String csr = receivedReq.getCsr(); From 77c3d21e39322110947489d56ce9cc99c4c00933 Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Tue, 11 Aug 2020 08:57:29 -0700 Subject: [PATCH 53/88] xds: use a compile time constant to address an error (#7311) --- .../internal/certprovider/MeshCaCertificateProvider.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProvider.java index 18fc27b9461..c6fa9a39b60 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProvider.java @@ -90,7 +90,7 @@ final class MeshCaCertificateProvider extends CertificateProvider { String zone, long validitySeconds, int keySize, - String alg, + String unused, //TODO(sanjaypujare): to remove during refactoring String signatureAlg, MeshCaChannelFactory meshCaChannelFactory, BackoffPolicy.Provider backoffPolicyProvider, long renewalGracePeriodSeconds, @@ -106,7 +106,6 @@ final class MeshCaCertificateProvider extends CertificateProvider { "validitySeconds must be greater than " + INITIAL_DELAY_SECONDS); this.validitySeconds = validitySeconds; this.keySize = keySize; - this.alg = checkNotNull(alg, "alg"); this.signatureAlg = checkNotNull(signatureAlg, "signatureAlg"); this.meshCaChannelFactory = checkNotNull(meshCaChannelFactory, "meshCaChannelFactory"); this.backoffPolicyProvider = checkNotNull(backoffPolicyProvider, "backoffPolicyProvider"); @@ -276,7 +275,7 @@ private void generateErrorIfCurrentCertExpired(Throwable t) { } private KeyPair generateKeyPair() throws NoSuchAlgorithmException { - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(alg); + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(keySize); return keyPairGenerator.generateKeyPair(); } @@ -490,7 +489,6 @@ public void start(Listener responseListener, Metadata headers) { private final long validitySeconds; private final long renewalGracePeriodSeconds; private final int keySize; - private final String alg; private final String signatureAlg; private final GoogleCredentials oauth2Creds; private final TimeProvider timeProvider; From f0a0e67e7b20afd5fe64708fdacd8928e0cdef13 Mon Sep 17 00:00:00 2001 From: Chengyuan Zhang Date: Tue, 11 Aug 2020 17:58:03 +0000 Subject: [PATCH 54/88] xds: manage load stats for all clusters in XdsClient (#7299) Move the creation of LoadStatsStore (aka, the stats object) into XdsClient. The XdsClient is responsible for managing the lifetime of stats objects. Creations of LoadStatsStores are reference counted so that multiple EDS policies can retrieve the same stats object for load recording. Counters for recording loads per locality also need to be reference counted, as each EDS policy for the same cluster will receive endpoints for the same group of localities, they will use the same load counters for recording each locality's loads. --- .../java/io/grpc/xds/ClientLoadCounter.java | 45 ++-- .../java/io/grpc/xds/EdsLoadBalancer.java | 33 +-- .../java/io/grpc/xds/LoadReportClient.java | 134 ++-------- .../java/io/grpc/xds/LoadStatsManager.java | 164 ++++++++++++ .../main/java/io/grpc/xds/LoadStatsStore.java | 107 -------- .../java/io/grpc/xds/LoadStatsStoreImpl.java | 61 +++-- .../main/java/io/grpc/xds/LocalityStore.java | 8 +- .../java/io/grpc/xds/LrsLoadBalancer.java | 4 +- .../java/io/grpc/xds/ReferenceCounted.java | 60 +++++ .../main/java/io/grpc/xds/XdsAttributes.java | 1 + xds/src/main/java/io/grpc/xds/XdsClient.java | 32 ++- .../main/java/io/grpc/xds/XdsClientImpl.java | 51 ++-- .../java/io/grpc/xds/CdsLoadBalancerTest.java | 51 ---- .../io/grpc/xds/ClientLoadCounterTest.java | 8 +- .../java/io/grpc/xds/EdsLoadBalancerTest.java | 1 + .../io/grpc/xds/LoadReportClientTest.java | 242 +++++++++++------- .../io/grpc/xds/LoadStatsStoreImplTest.java | 104 +++----- .../java/io/grpc/xds/LocalityStoreTest.java | 34 ++- .../java/io/grpc/xds/LrsLoadBalancerTest.java | 17 +- .../java/io/grpc/xds/XdsClientImplTest.java | 18 +- .../java/io/grpc/xds/XdsClientImplTestV2.java | 18 +- 21 files changed, 606 insertions(+), 587 deletions(-) create mode 100644 xds/src/main/java/io/grpc/xds/LoadStatsManager.java delete mode 100644 xds/src/main/java/io/grpc/xds/LoadStatsStore.java create mode 100644 xds/src/main/java/io/grpc/xds/ReferenceCounted.java diff --git a/xds/src/main/java/io/grpc/xds/ClientLoadCounter.java b/xds/src/main/java/io/grpc/xds/ClientLoadCounter.java index 8e8754d2542..f86002b683f 100644 --- a/xds/src/main/java/io/grpc/xds/ClientLoadCounter.java +++ b/xds/src/main/java/io/grpc/xds/ClientLoadCounter.java @@ -54,27 +54,10 @@ final class ClientLoadCounter { private final AtomicLong callsIssued = new AtomicLong(); private final MetricRecorder[] metricRecorders = new MetricRecorder[THREAD_BALANCING_FACTOR]; - // True if this counter continues to record stats after next snapshot. Otherwise, it will be - // discarded. - private boolean active; - ClientLoadCounter() { for (int i = 0; i < THREAD_BALANCING_FACTOR; i++) { metricRecorders[i] = new MetricRecorder(); } - active = true; - } - - /** - * Must only be used for testing. - */ - @VisibleForTesting - ClientLoadCounter(long callsSucceeded, long callsInProgress, long callsFailed, long callsIssued) { - this(); - this.callsSucceeded.set(callsSucceeded); - this.callsInProgress.set(callsInProgress); - this.callsFailed.set(callsFailed); - this.callsIssued.set(callsIssued); } void recordCallStarted() { @@ -98,12 +81,8 @@ void recordMetric(String name, double value) { } /** - * Generates a snapshot for load stats recorded in this counter. Successive snapshots represent - * load stats recorded for the interval since the previous snapshot. So taking a snapshot clears - * the counter state except for ongoing RPC recordings. - * - *

This method is not thread-safe and must be called from {@link - * io.grpc.LoadBalancer.Helper#getSynchronizationContext()}. + * Generates a snapshot for load stats recorded in this counter for the interval between calls + * of this method. */ ClientLoadSnapshot snapshot() { Map aggregatedValues = new HashMap<>(); @@ -127,12 +106,24 @@ ClientLoadSnapshot snapshot() { aggregatedValues); } - void setActive(boolean value) { - active = value; + @VisibleForTesting + void setCallsIssued(long callsIssued) { + this.callsIssued.set(callsIssued); + } + + @VisibleForTesting + void setCallsInProgress(long callsInProgress) { + this.callsInProgress.set(callsInProgress); } - boolean isActive() { - return active; + @VisibleForTesting + void setCallsSucceeded(long callsSucceeded) { + this.callsSucceeded.set(callsSucceeded); + } + + @VisibleForTesting + void setCallsFailed(long callsFailed) { + this.callsFailed.set(callsFailed); } /** diff --git a/xds/src/main/java/io/grpc/xds/EdsLoadBalancer.java b/xds/src/main/java/io/grpc/xds/EdsLoadBalancer.java index 5c5c1889fca..b009ea7d530 100644 --- a/xds/src/main/java/io/grpc/xds/EdsLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/EdsLoadBalancer.java @@ -38,6 +38,7 @@ import io.grpc.xds.EnvoyProtoData.Locality; import io.grpc.xds.EnvoyProtoData.LocalityLbEndpoints; import io.grpc.xds.EnvoyProtoData.Node; +import io.grpc.xds.LoadStatsManager.LoadStatsStore; import io.grpc.xds.LocalityStore.LocalityStoreFactory; import io.grpc.xds.XdsClient.EndpointUpdate; import io.grpc.xds.XdsClient.EndpointWatcher; @@ -208,11 +209,9 @@ public void shutdown() { */ private final class ClusterEndpointsBalancerFactory extends LoadBalancer.Factory { @Nullable final String clusterServiceName; - final LoadStatsStore loadStatsStore; ClusterEndpointsBalancerFactory(@Nullable String clusterServiceName) { this.clusterServiceName = clusterServiceName; - loadStatsStore = new LoadStatsStoreImpl(clusterName, clusterServiceName); } @Override @@ -248,6 +247,7 @@ final class ClusterEndpointsBalancer extends LoadBalancer { ClusterEndpointsBalancer(Helper helper) { this.helper = helper; resourceName = clusterServiceName != null ? clusterServiceName : clusterName; + LoadStatsStore loadStatsStore = xdsClient.addClientStats(clusterName, clusterServiceName); localityStore = localityStoreFactory.newLocalityStore(logId, helper, lbRegistry, loadStatsStore); endpointWatcher = new EndpointWatcherImpl(); @@ -267,22 +267,12 @@ public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { throw new AssertionError("Can only report load to the same management server"); } if (!isReportingLoad) { - logger.log( - XdsLogLevel.INFO, - "Start reporting loads for cluster: {0}, cluster_service: {1}", - clusterName, - clusterServiceName); - xdsClient.reportClientStats(clusterName, clusterServiceName, loadStatsStore); + xdsClient.reportClientStats(); isReportingLoad = true; } } else { if (isReportingLoad) { - logger.log( - XdsLogLevel.INFO, - "Stop reporting loads for cluster: {0}, cluster_service: {1}", - clusterName, - clusterServiceName); - xdsClient.cancelClientStatsReport(clusterName, clusterServiceName); + xdsClient.cancelClientStatsReport(); isReportingLoad = false; } } @@ -304,15 +294,11 @@ public boolean canHandleEmptyAddressListFromNameResolution() { @Override public void shutdown() { if (isReportingLoad) { - logger.log( - XdsLogLevel.INFO, - "Stop reporting loads for cluster: {0}, cluster_service: {1}", - clusterName, - clusterServiceName); - xdsClient.cancelClientStatsReport(clusterName, clusterServiceName); + xdsClient.cancelClientStatsReport(); isReportingLoad = false; } localityStore.reset(); + xdsClient.removeClientStats(clusterName, clusterServiceName); xdsClient.cancelEndpointDataWatch(resourceName, endpointWatcher); logger.log( XdsLogLevel.INFO, @@ -365,12 +351,7 @@ public void onEndpointChanged(EndpointUpdate endpointUpdate) { public void onResourceDoesNotExist(String resourceName) { logger.log(XdsLogLevel.INFO, "Resource {0} is unavailable", resourceName); if (isReportingLoad) { - logger.log( - XdsLogLevel.INFO, - "Stop reporting loads for cluster: {0}, cluster_service: {1}", - clusterName, - clusterServiceName); - xdsClient.cancelClientStatsReport(clusterName, clusterServiceName); + xdsClient.cancelClientStatsReport(); isReportingLoad = false; } localityStore.reset(); diff --git a/xds/src/main/java/io/grpc/xds/LoadReportClient.java b/xds/src/main/java/io/grpc/xds/LoadReportClient.java index 1314583cfa7..84d2b340cbd 100644 --- a/xds/src/main/java/io/grpc/xds/LoadReportClient.java +++ b/xds/src/main/java/io/grpc/xds/LoadReportClient.java @@ -27,7 +27,6 @@ import com.google.protobuf.Value; import com.google.protobuf.util.Durations; import io.envoyproxy.envoy.api.v2.core.Node; -import io.envoyproxy.envoy.api.v2.endpoint.ClusterStats; import io.envoyproxy.envoy.service.load_stats.v2.LoadReportingServiceGrpc; import io.envoyproxy.envoy.service.load_stats.v2.LoadStatsRequest; import io.envoyproxy.envoy.service.load_stats.v2.LoadStatsResponse; @@ -39,10 +38,7 @@ import io.grpc.internal.BackoffPolicy; import io.grpc.stub.StreamObserver; import io.grpc.xds.XdsLogger.XdsLogLevel; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.List; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; @@ -57,6 +53,7 @@ final class LoadReportClient { @VisibleForTesting static final String TARGET_NAME_METADATA_KEY = "PROXYLESS_CLIENT_HOSTNAME"; + private final InternalLogId logId; private final XdsLogger logger; private final ManagedChannel channel; private final Node node; @@ -65,9 +62,8 @@ final class LoadReportClient { private final Supplier stopwatchSupplier; private final Stopwatch retryStopwatch; private final BackoffPolicy.Provider backoffPolicyProvider; + private final LoadStatsManager loadStatsManager; - // Sources of load stats data for each cluster:cluster_service. - private final Map> loadStatsEntities = new HashMap<>(); private boolean started; @Nullable @@ -76,18 +72,17 @@ final class LoadReportClient { private ScheduledHandle lrsRpcRetryTimer; @Nullable private LrsStream lrsStream; - @Nullable - private LoadReportCallback callback; LoadReportClient( - InternalLogId logId, String targetName, + LoadStatsManager loadStatsManager, ManagedChannel channel, Node node, SynchronizationContext syncContext, ScheduledExecutorService scheduledExecutorService, BackoffPolicy.Provider backoffPolicyProvider, Supplier stopwatchSupplier) { + this.loadStatsManager = checkNotNull(loadStatsManager, "loadStatsManager"); this.channel = checkNotNull(channel, "channel"); this.syncContext = checkNotNull(syncContext, "syncContext"); this.timerService = checkNotNull(scheduledExecutorService, "timeService"); @@ -104,8 +99,9 @@ final class LoadReportClient { Value.newBuilder().setStringValue(targetName).build()) .build(); this.node = node.toBuilder().setMetadata(metadata).build(); - String logPrefix = checkNotNull(logId, "logId").toString().concat("-lrs-client"); - logger = XdsLogger.withPrefix(logPrefix); + logId = InternalLogId.allocate("lrs-client", targetName); + logger = XdsLogger.withLogId(logId); + logger.log(XdsLogLevel.INFO, "Created"); } /** @@ -113,12 +109,12 @@ final class LoadReportClient { * stats periodically. Calling this method on an already started {@link LoadReportClient} is * no-op. */ - void startLoadReporting(LoadReportCallback callback) { + void startLoadReporting() { if (started) { return; } - this.callback = callback; started = true; + logger.log(XdsLogLevel.INFO, "Starting load reporting RPC"); startLrsRpc(); } @@ -130,6 +126,7 @@ void stopLoadReporting() { if (!started) { return; } + logger.log(XdsLogLevel.INFO, "Stopping load reporting RPC"); if (lrsRpcRetryTimer != null) { lrsRpcRetryTimer.cancel(); } @@ -140,49 +137,6 @@ void stopLoadReporting() { // Do not shutdown channel as it is not owned by LrsClient. } - /** - * Provides this LoadReportClient source of load stats data for the given - * cluster:cluster_service. If requested, data from the given loadStatsStore is - * periodically queried and sent to traffic director by this LoadReportClient. - */ - void addLoadStatsStore( - String clusterName, @Nullable String clusterServiceName, LoadStatsStore loadStatsStore) { - checkState( - !loadStatsEntities.containsKey(clusterName) - || !loadStatsEntities.get(clusterName).containsKey(clusterServiceName), - "load stats for cluster: %s, cluster service: %s already exists", - clusterName, clusterServiceName); - logger.log( - XdsLogLevel.INFO, - "Add load stats for cluster: {0}, cluster_service: {1}", clusterName, clusterServiceName); - if (!loadStatsEntities.containsKey(clusterName)) { - loadStatsEntities.put(clusterName, new HashMap()); - } - Map clusterLoadStatsEntities = loadStatsEntities.get(clusterName); - clusterLoadStatsEntities.put(clusterServiceName, new LoadStatsEntity(loadStatsStore)); - } - - /** - * Stops providing load stats data for the given cluster:cluster_service. - */ - void removeLoadStatsStore(String clusterName, @Nullable String clusterServiceName) { - checkState( - loadStatsEntities.containsKey(clusterName) - && loadStatsEntities.get(clusterName).containsKey(clusterServiceName), - "load stats for cluster: %s, cluster service: %s does not exist", - clusterName, clusterServiceName); - logger.log( - XdsLogLevel.INFO, - "Remove load stats for cluster: {0}, cluster_service: {1}", - clusterName, - clusterServiceName); - Map clusterLoadStatsEntities = loadStatsEntities.get(clusterName); - clusterLoadStatsEntities.remove(clusterServiceName); - if (clusterLoadStatsEntities.isEmpty()) { - loadStatsEntities.remove(clusterName); - } - } - @VisibleForTesting static class LoadReportingTask implements Runnable { private final LrsStream stream; @@ -217,12 +171,13 @@ private void startLrsRpc() { private class LrsStream implements StreamObserver { - final Set clusterNames = new HashSet<>(); final LoadReportingServiceGrpc.LoadReportingServiceStub stub; StreamObserver lrsRequestWriter; boolean initialResponseReceived; boolean closed; long loadReportIntervalNano = -1; + boolean reportAllClusters; + List clusterNames; // clusters to report loads for, if not report all. ScheduledHandle loadReportTimer; LrsStream(LoadReportingServiceGrpc.LoadReportingServiceStub stub, Stopwatch stopwatch) { @@ -272,12 +227,11 @@ public void run() { private void sendLoadReport() { LoadStatsRequest.Builder requestBuilder = LoadStatsRequest.newBuilder().setNode(node); - for (String name : clusterNames) { - if (loadStatsEntities.containsKey(name)) { - Map clusterLoadStatsEntities = loadStatsEntities.get(name); - for (LoadStatsEntity entity : clusterLoadStatsEntities.values()) { - requestBuilder.addClusterStats(entity.getLoadStats()); - } + if (reportAllClusters) { + requestBuilder.addAllClusterStats(loadStatsManager.getAllLoadReports()); + } else { + for (String name : clusterNames) { + requestBuilder.addAllClusterStats(loadStatsManager.getClusterLoadReports(name)); } } LoadStatsRequest request = requestBuilder.build(); @@ -309,21 +263,17 @@ private void handleResponse(LoadStatsResponse response) { } else { logger.log(XdsLogLevel.DEBUG, "Received LRS response:\n{0}", response); } - clusterNames.clear(); - if (response.getSendAllClusters()) { - clusterNames.addAll(loadStatsEntities.keySet()); - logger.log(XdsLogLevel.INFO, "Update to report loads for all clusters"); + reportAllClusters = response.getSendAllClusters(); + if (reportAllClusters) { + logger.log(XdsLogLevel.INFO, "Report loads for all clusters"); } else { - logger.log( - XdsLogLevel.INFO, - "Update load reporting clusters to {0}", response.getClustersList()); - clusterNames.addAll(response.getClustersList()); + logger.log(XdsLogLevel.INFO, "Report loads for clusters: ", response.getClustersList()); + clusterNames = response.getClustersList(); } long interval = Durations.toNanos(response.getLoadReportingInterval()); logger.log(XdsLogLevel.INFO, "Update load reporting interval to {0} ns", interval); loadReportIntervalNano = interval; scheduleNextLoadReport(); - callback.onReportResponse(loadReportIntervalNano); } private void handleStreamClosed(Status status) { @@ -385,42 +335,4 @@ private void cleanUp() { } } } - - private final class LoadStatsEntity { - private final LoadStatsStore loadStatsStore; - private final Stopwatch stopwatch; - - private LoadStatsEntity(LoadStatsStore loadStatsStore) { - this.loadStatsStore = loadStatsStore; - this.stopwatch = stopwatchSupplier.get().reset().start(); - } - - private ClusterStats getLoadStats() { - ClusterStats stats = - loadStatsStore.generateLoadReport() - .toBuilder() - .setLoadReportInterval( - Durations.fromNanos(stopwatch.elapsed(TimeUnit.NANOSECONDS))) - .build(); - stopwatch.reset().start(); - return stats; - } - } - - /** - * Callbacks for passing information received from client load reporting responses to xDS load - * balancer, such as the load reporting interval requested by the traffic director. - * - *

Implementations are not required to be thread-safe as callbacks will be invoked in xDS load - * balancer's {@link io.grpc.SynchronizationContext}. - */ - interface LoadReportCallback { - - /** - * The load reporting interval has been received. - * - * @param reportIntervalNano load reporting interval requested by remote traffic director. - */ - void onReportResponse(long reportIntervalNano); - } } diff --git a/xds/src/main/java/io/grpc/xds/LoadStatsManager.java b/xds/src/main/java/io/grpc/xds/LoadStatsManager.java new file mode 100644 index 00000000000..5372d37b0ea --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/LoadStatsManager.java @@ -0,0 +1,164 @@ +/* + * Copyright 2019 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds; + +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.VisibleForTesting; +import io.envoyproxy.envoy.api.v2.endpoint.ClusterStats; +import io.grpc.xds.EnvoyProtoData.Locality; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; + +/** + * Manages all stats for client side load. + */ +final class LoadStatsManager { + private final LoadStatsStoreFactory loadStatsStoreFactory; + private final Map>> loadStatsStores + = new HashMap<>(); + + LoadStatsManager() { + this(LoadStatsStoreImpl.getDefaultFactory()); + } + + @VisibleForTesting + LoadStatsManager(LoadStatsStoreFactory factory) { + this.loadStatsStoreFactory = factory; + } + + /** + * Adds and retrieves the stats object for tracking loads for the given cluster:cluster_service. + * The returned {@link LoadStatsStore} is reference-counted, caller should use + * {@link #removeLoadStats} to release the reference when it is no longer used. + */ + LoadStatsStore addLoadStats(String cluster, @Nullable String clusterService) { + if (!loadStatsStores.containsKey(cluster)) { + loadStatsStores.put(cluster, new HashMap>()); + } + Map> clusterLoadStatsStores + = loadStatsStores.get(cluster); + if (!clusterLoadStatsStores.containsKey(clusterService)) { + clusterLoadStatsStores.put( + clusterService, + ReferenceCounted.wrap(loadStatsStoreFactory.newLoadStatsStore(cluster, clusterService))); + } + ReferenceCounted ref = clusterLoadStatsStores.get(clusterService); + ref.retain(); + return ref.get(); + } + + /** + * Discards stats object used for tracking loads for the given cluster:cluster_service. + */ + void removeLoadStats(String cluster, @Nullable String clusterService) { + checkState( + loadStatsStores.containsKey(cluster) + && loadStatsStores.get(cluster).containsKey(clusterService), + "stats for cluster %s, cluster service %s not exits"); + Map> clusterLoadStatsStores = + loadStatsStores.get(cluster); + ReferenceCounted ref = clusterLoadStatsStores.get(clusterService); + ref.release(); + if (ref.getReferenceCount() == 0) { + clusterLoadStatsStores.remove(clusterService); + } + if (clusterLoadStatsStores.isEmpty()) { + loadStatsStores.remove(cluster); + } + } + + /** + * Generates reports summarizing the stats recorded for loads sent to the given cluster for + * the interval between calls of this method or {@link #getAllLoadReports}. A cluster may send + * loads to more than one cluster_service, they are included in separate stats reports. + */ + // TODO(chengyuanzhang): do not use proto type directly. + List getClusterLoadReports(String cluster) { + List res = new ArrayList<>(); + Map> clusterLoadStatsStores = + loadStatsStores.get(cluster); + if (clusterLoadStatsStores == null) { + return res; + } + for (ReferenceCounted ref : clusterLoadStatsStores.values()) { + res.add(ref.get().generateLoadReport()); + } + return res; + } + + /** + * Generates reports summarized the stats recorded for loads sent to all clusters for the + * interval between calls of this method or {@link #getClusterLoadReports}. Each report + * includes stats for one cluster:cluster_service. + */ + // TODO(chengyuanzhang): do not use proto type directly. + List getAllLoadReports() { + List res = new ArrayList<>(); + for (Map> clusterLoadStatsStores + : loadStatsStores.values()) { + for (ReferenceCounted ref : clusterLoadStatsStores.values()) { + res.add(ref.get().generateLoadReport()); + } + } + return res; + } + + @VisibleForTesting + interface LoadStatsStoreFactory { + LoadStatsStore newLoadStatsStore(String cluster, String clusterService); + } + + /** + * Interface for client side load stats store. An {@code LoadStatsStore} maintains load stats per + * cluster:cluster_service exposed by traffic director from a gRPC client's perspective, + * including dropped calls. Load stats for endpoints are aggregated in locality granularity + * while the numbers of dropped calls are aggregated in cluster:cluster_service granularity. + */ + interface LoadStatsStore { + + /** + * Generates a report based on recorded load stats (including RPC counts, backend metrics and + * dropped calls) for the interval since the previous call of this method. + */ + // TODO(chengyuanzhang): do not use proto type directly. + ClusterStats generateLoadReport(); + + /** + * Track load stats for endpoints in the provided locality. Only load stats for endpoints + * in tracked localities will be included in generated load reports. + */ + ClientLoadCounter addLocality(Locality locality); + + /** + * Drop tracking load stats for endpoints in the provided locality. Load stats for endpoints + * in removed localities will no longer be included in future generated load reports after + * their currently recording stats have been fully reported. + */ + void removeLocality(Locality locality); + + /** + * Records a drop decision. + * + *

This method is thread-safe. + */ + void recordDroppedRequest(String category); + } +} diff --git a/xds/src/main/java/io/grpc/xds/LoadStatsStore.java b/xds/src/main/java/io/grpc/xds/LoadStatsStore.java deleted file mode 100644 index cd76be41edf..00000000000 --- a/xds/src/main/java/io/grpc/xds/LoadStatsStore.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2019 The gRPC Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.grpc.xds; - -import io.envoyproxy.envoy.api.v2.endpoint.ClusterStats; -import io.grpc.xds.EnvoyProtoData.Locality; -import javax.annotation.Nullable; - -/** - * Interface for client side load stats store. An {@code LoadStatsStore} maintains load stats per - * cluster:cluster_service exposed by traffic director from a gRPC client's perspective, - * including dropped calls. Load stats for endpoints (i.e., Google backends) are aggregated in - * locality granularity (i.e., Google cluster) while the numbers of dropped calls are aggregated - * in cluster:cluster_service granularity. - * - *

An {@code LoadStatsStore} only tracks loads for localities exposed by remote traffic - * director. A proper usage should be - * - *

    - *
  1. Let {@link LoadStatsStore} track the locality newly exposed by traffic director by - * calling {@link #addLocality(Locality)}. - *
  2. Use the locality counter returned by {@link #getLocalityCounter(Locality)} to record - * load stats for the corresponding locality. - *
  3. Tell {@link LoadStatsStore} to stop tracking the locality no longer exposed by traffic - * director by calling {@link #removeLocality(Locality)}. - *
- * - *

No locality information is needed for recording dropped calls since they are aggregated in - * cluster granularity. - */ -interface LoadStatsStore { - - /** - * Generates a {@link ClusterStats} proto message as the load report based on recorded load stats - * (including RPC * counts, backend metrics and dropped calls) for the interval since the previous - * call of this method. - * - *

Loads for localities no longer under tracking will not be included in generated load reports - * once all of theirs loads are completed and reported. - * - *

The fields {@code cluster_name} and {@code load_report_interval} in the returned {@link - * ClusterStats} needs to be set before it is ready to be sent to the traffic director for load - * reporting. - * - *

This method is not thread-safe and should be called from the same synchronized context - * used by {@link LoadReportClient}. - */ - ClusterStats generateLoadReport(); - - /** - * Starts tracking load stats for endpoints in the provided locality. Only load stats for - * endpoints in added localities will be recorded and included in generated load reports. - * - *

This method needs to be called at locality updates only for newly assigned localities in - * endpoint discovery responses before recording loads for those localities. - * - *

This method is not thread-safe and should be called from the same synchronized context - * used by {@link LoadReportClient}. - */ - void addLocality(Locality locality); - - /** - * Stops tracking load stats for endpoints in the provided locality. gRPC clients are expected not - * to send loads to localities no longer exposed by traffic director. Load stats for endpoints in - * removed localities will no longer be included in future generated load reports after their - * recorded and ongoing loads have been reported. - * - *

This method needs to be called at locality updates only for newly removed localities. - * Forgetting calling this method for localities no longer under track will result in memory - * waste and keep including zero-load upstream locality stats in generated load reports. - * - *

This method is not thread-safe and should be called from the same synchronized context - * used by {@link LoadReportClient}. - */ - void removeLocality(Locality locality); - - /** - * Returns the locality counter that does locality level stats aggregation for the provided - * locality. If the provided locality is not tracked, {@code null} will be returned. - * - *

This method is thread-safe. - */ - @Nullable - ClientLoadCounter getLocalityCounter(Locality locality); - - /** - * Records a drop decision made by a {@link io.grpc.LoadBalancer.SubchannelPicker} instance - * with the provided category. Drops are aggregated in cluster granularity. - * - *

This method is thread-safe. - */ - void recordDroppedRequest(String category); -} diff --git a/xds/src/main/java/io/grpc/xds/LoadStatsStoreImpl.java b/xds/src/main/java/io/grpc/xds/LoadStatsStoreImpl.java index 4483854cb4f..6a8b5447216 100644 --- a/xds/src/main/java/io/grpc/xds/LoadStatsStoreImpl.java +++ b/xds/src/main/java/io/grpc/xds/LoadStatsStoreImpl.java @@ -17,19 +17,24 @@ package io.grpc.xds; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Stopwatch; +import com.google.protobuf.util.Durations; import io.envoyproxy.envoy.api.v2.endpoint.ClusterStats; import io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.DroppedRequests; import io.envoyproxy.envoy.api.v2.endpoint.EndpointLoadMetricStats; import io.envoyproxy.envoy.api.v2.endpoint.UpstreamLocalityStats; +import io.grpc.internal.GrpcUtil; import io.grpc.xds.ClientLoadCounter.ClientLoadSnapshot; import io.grpc.xds.ClientLoadCounter.MetricValue; import io.grpc.xds.EnvoyProtoData.Locality; +import io.grpc.xds.LoadStatsManager.LoadStatsStore; +import io.grpc.xds.LoadStatsManager.LoadStatsStoreFactory; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import javax.annotation.Nullable; import javax.annotation.concurrent.NotThreadSafe; @@ -47,12 +52,14 @@ final class LoadStatsStoreImpl implements LoadStatsStore { @Nullable @SuppressWarnings("unused") private final String clusterServiceName; - private final ConcurrentMap localityLoadCounters; + private final ConcurrentMap> localityLoadCounters + = new ConcurrentHashMap<>(); // Cluster level dropped request counts for each category decision made by xDS load balancer. private final ConcurrentMap dropCounters; + private final Stopwatch stopwatch; LoadStatsStoreImpl(String clusterName, @Nullable String clusterServiceName) { - this(clusterName, clusterServiceName, new ConcurrentHashMap(), + this(clusterName, clusterServiceName, GrpcUtil.STOPWATCH_SUPPLIER.get(), new ConcurrentHashMap()); } @@ -60,12 +67,13 @@ final class LoadStatsStoreImpl implements LoadStatsStore { LoadStatsStoreImpl( String clusterName, @Nullable String clusterServiceName, - ConcurrentMap localityLoadCounters, + Stopwatch stopwatch, ConcurrentMap dropCounters) { this.clusterName = checkNotNull(clusterName, "clusterName"); this.clusterServiceName = clusterServiceName; - this.localityLoadCounters = checkNotNull(localityLoadCounters, "localityLoadCounters"); + this.stopwatch = checkNotNull(stopwatch, "stopwatch"); this.dropCounters = checkNotNull(dropCounters, "dropCounters"); + stopwatch.reset().start(); } @Override @@ -73,8 +81,9 @@ public ClusterStats generateLoadReport() { ClusterStats.Builder statsBuilder = ClusterStats.newBuilder(); statsBuilder.setClusterName(clusterName); // TODO(chengyuangzhang): also set cluster_service_name if provided. - for (Map.Entry entry : localityLoadCounters.entrySet()) { - ClientLoadSnapshot snapshot = entry.getValue().snapshot(); + for (Map.Entry> entry + : localityLoadCounters.entrySet()) { + ClientLoadSnapshot snapshot = entry.getValue().get().snapshot(); UpstreamLocalityStats.Builder localityStatsBuilder = UpstreamLocalityStats.newBuilder().setLocality(entry.getKey().toEnvoyProtoLocalityV2()); localityStatsBuilder @@ -92,7 +101,7 @@ public ClusterStats generateLoadReport() { statsBuilder.addUpstreamLocalityStats(localityStatsBuilder); // Discard counters for localities that are no longer exposed by the remote balancer and // no RPCs ongoing. - if (!entry.getValue().isActive() && snapshot.getCallsInProgress() == 0) { + if (entry.getValue().getReferenceCount() == 0 && snapshot.getCallsInProgress() == 0) { localityLoadCounters.remove(entry.getKey()); } } @@ -105,32 +114,27 @@ public ClusterStats generateLoadReport() { .setDroppedCount(drops)); } statsBuilder.setTotalDroppedRequests(totalDrops); + statsBuilder.setLoadReportInterval( + Durations.fromNanos(stopwatch.elapsed(TimeUnit.NANOSECONDS))); + stopwatch.reset().start(); return statsBuilder.build(); } @Override - public void addLocality(final Locality locality) { - ClientLoadCounter counter = localityLoadCounters.get(locality); - checkState(counter == null || !counter.isActive(), - "An active counter for locality %s already exists", locality); + public ClientLoadCounter addLocality(final Locality locality) { + ReferenceCounted counter = localityLoadCounters.get(locality); if (counter == null) { - localityLoadCounters.put(locality, new ClientLoadCounter()); - } else { - counter.setActive(true); + counter = ReferenceCounted.wrap(new ClientLoadCounter()); + localityLoadCounters.put(locality, counter); } + counter.retain(); + return counter.get(); } @Override public void removeLocality(final Locality locality) { - ClientLoadCounter counter = localityLoadCounters.get(locality); - checkState(counter != null && counter.isActive(), - "No active counter for locality %s exists", locality); - counter.setActive(false); - } - - @Override - public ClientLoadCounter getLocalityCounter(final Locality locality) { - return localityLoadCounters.get(locality); + ReferenceCounted counter = localityLoadCounters.get(locality); + counter.release(); } @Override @@ -144,4 +148,13 @@ public void recordDroppedRequest(String category) { } counter.getAndIncrement(); } + + static LoadStatsStoreFactory getDefaultFactory() { + return new LoadStatsStoreFactory() { + @Override + public LoadStatsStore newLoadStatsStore(String cluster, String clusterService) { + return new LoadStatsStoreImpl(cluster, clusterService); + } + }; + } } diff --git a/xds/src/main/java/io/grpc/xds/LocalityStore.java b/xds/src/main/java/io/grpc/xds/LocalityStore.java index 183b4bbb7f5..4fa507e6a12 100644 --- a/xds/src/main/java/io/grpc/xds/LocalityStore.java +++ b/xds/src/main/java/io/grpc/xds/LocalityStore.java @@ -47,6 +47,7 @@ import io.grpc.xds.EnvoyProtoData.LbEndpoint; import io.grpc.xds.EnvoyProtoData.Locality; import io.grpc.xds.EnvoyProtoData.LocalityLbEndpoints; +import io.grpc.xds.LoadStatsManager.LoadStatsStore; import io.grpc.xds.OrcaOobUtil.OrcaReportingConfig; import io.grpc.xds.OrcaOobUtil.OrcaReportingHelperWrapper; import io.grpc.xds.WeightedRandomPicker.WeightedChildPicker; @@ -311,8 +312,8 @@ private final class LocalityLbInfo { LocalityLbInfo(Locality locality) { this.locality = checkNotNull(locality, "locality"); - loadStatsStore.addLocality(locality); - childHelper = new ChildHelper(); + ClientLoadCounter counter = loadStatsStore.addLocality(locality); + childHelper = new ChildHelper(counter); childBalancer = loadBalancerProvider.newLoadBalancer(childHelper); } @@ -368,8 +369,7 @@ class ChildHelper extends ForwardingLoadBalancerHelper { private SubchannelPicker currentChildPicker = XdsSubchannelPickers.BUFFER_PICKER; private ConnectivityState currentChildState = CONNECTING; - ChildHelper() { - final ClientLoadCounter counter = loadStatsStore.getLocalityCounter(locality); + ChildHelper(final ClientLoadCounter counter) { Helper delegate = new ForwardingLoadBalancerHelper() { @Override protected Helper delegate() { diff --git a/xds/src/main/java/io/grpc/xds/LrsLoadBalancer.java b/xds/src/main/java/io/grpc/xds/LrsLoadBalancer.java index 55ca451271b..edcfcc0cc39 100644 --- a/xds/src/main/java/io/grpc/xds/LrsLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/LrsLoadBalancer.java @@ -26,6 +26,7 @@ import io.grpc.util.GracefulSwitchLoadBalancer; import io.grpc.xds.ClientLoadCounter.LoadRecordingSubchannelPicker; import io.grpc.xds.EnvoyProtoData.Locality; +import io.grpc.xds.LoadStatsManager.LoadStatsStore; import io.grpc.xds.LrsLoadBalancerProvider.LrsConfig; import io.grpc.xds.XdsSubchannelPickers.ErrorPicker; import java.util.Objects; @@ -58,8 +59,7 @@ public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) { checkAndSetUp(config, store); if (switchingLoadBalancer == null) { - loadStatsStore.addLocality(config.locality); - final ClientLoadCounter counter = loadStatsStore.getLocalityCounter(config.locality); + final ClientLoadCounter counter = loadStatsStore.addLocality(config.locality); LoadBalancer.Helper loadRecordingHelper = new ForwardingLoadBalancerHelper() { @Override protected Helper delegate() { diff --git a/xds/src/main/java/io/grpc/xds/ReferenceCounted.java b/xds/src/main/java/io/grpc/xds/ReferenceCounted.java new file mode 100644 index 00000000000..b62340ffa0a --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/ReferenceCounted.java @@ -0,0 +1,60 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +/** + * A reference count wrapper for objects. This class does not take the ownership for the object, + * but only provides usage counting. The real owner of the wrapped object is responsible for + * managing the lifecycle of the object. + * + *

Intended for a container class to keep track of lifecycle for elements it contains. This + * wrapper itself should never be returned to the consumers of the elements to avoid reference + * counts being leaked. + */ +final class ReferenceCounted { + private final T instance; + private int refs; + + private ReferenceCounted(T instance) { + this.instance = instance; + } + + static ReferenceCounted wrap(T instance) { + checkNotNull(instance, "instance"); + return new ReferenceCounted<>(instance); + } + + void retain() { + refs++; + } + + void release() { + checkState(refs > 0, "reference reached 0"); + refs--; + } + + int getReferenceCount() { + return refs; + } + + T get() { + return instance; + } +} diff --git a/xds/src/main/java/io/grpc/xds/XdsAttributes.java b/xds/src/main/java/io/grpc/xds/XdsAttributes.java index 662c9a69a5a..57796658636 100644 --- a/xds/src/main/java/io/grpc/xds/XdsAttributes.java +++ b/xds/src/main/java/io/grpc/xds/XdsAttributes.java @@ -22,6 +22,7 @@ import io.grpc.NameResolver; import io.grpc.internal.ObjectPool; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; +import io.grpc.xds.LoadStatsManager.LoadStatsStore; /** * Special attributes that are only useful to gRPC in the XDS context. diff --git a/xds/src/main/java/io/grpc/xds/XdsClient.java b/xds/src/main/java/io/grpc/xds/XdsClient.java index 1c895c878ab..69a12843003 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClient.java +++ b/xds/src/main/java/io/grpc/xds/XdsClient.java @@ -37,6 +37,7 @@ import io.grpc.xds.EnvoyProtoData.Route; import io.grpc.xds.EnvoyServerProtoData.Listener; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; +import io.grpc.xds.LoadStatsManager.LoadStatsStore; import io.grpc.xds.XdsLogger.XdsLogLevel; import java.util.ArrayList; import java.util.Collection; @@ -518,21 +519,34 @@ void watchListenerData(int port, ListenerWatcher watcher) { } /** - * Report client load stats to a remote server for the given cluster:cluster_service. - * - *

Note: currently we can only report loads for a single cluster:cluster_service, - * as the design for adding clusters to report loads for while load reporting is - * happening is undefined. + * Starts client side load reporting via LRS. All clusters report load through one LRS stream, + * only the first call of this method effectively starts the LRS stream. + */ + void reportClientStats() { + } + + /** + * Stops client side load reporting via LRS. All clusters report load through one LRS stream, + * only the last call of this method effectively stops the LRS stream. */ - void reportClientStats( - String clusterName, @Nullable String clusterServiceName, LoadStatsStore loadStatsStore) { + void cancelClientStatsReport() { + } + + /** + * Starts recording client load stats for the given cluster:cluster_service. Caller should use + * the returned {@link LoadStatsStore} to record and aggregate stats for load sent to the given + * cluster:cluster_service. Recorded stats may be reported to a load reporting server if enabled. + */ + LoadStatsStore addClientStats(String clusterName, @Nullable String clusterServiceName) { throw new UnsupportedOperationException(); } /** - * Stops reporting client load stats to the remote server for the given cluster:cluster_service. + * Stops recording client load stats for the given cluster:cluster_service. The load reporting + * server will no longer receive stats for the given cluster:cluster_service after this call. */ - void cancelClientStatsReport(String clusterName, @Nullable String clusterServiceName) { + void removeClientStats(String clusterName, @Nullable String clusterServiceName) { + throw new UnsupportedOperationException(); } abstract static class XdsClientFactory { diff --git a/xds/src/main/java/io/grpc/xds/XdsClientImpl.java b/xds/src/main/java/io/grpc/xds/XdsClientImpl.java index 621eaa0ce47..c386daca3c6 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClientImpl.java +++ b/xds/src/main/java/io/grpc/xds/XdsClientImpl.java @@ -61,7 +61,7 @@ import io.grpc.xds.EnvoyProtoData.Node; import io.grpc.xds.EnvoyProtoData.StructOrError; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; -import io.grpc.xds.LoadReportClient.LoadReportCallback; +import io.grpc.xds.LoadStatsManager.LoadStatsStore; import io.grpc.xds.XdsLogger.XdsLogLevel; import java.util.ArrayList; import java.util.Collection; @@ -163,6 +163,8 @@ final class XdsClientImpl extends XdsClient { // Timers for concluding EDS resources not found. private final Map edsRespTimers = new HashMap<>(); + private final LoadStatsManager loadStatsManager = new LoadStatsManager(); + // Timer for concluding the currently requesting LDS resource not found. @Nullable private ScheduledHandle ldsRespTimer; @@ -179,6 +181,7 @@ final class XdsClientImpl extends XdsClient { private ScheduledHandle rpcRetryTimer; @Nullable private LoadReportClient lrsClient; + private int loadReportCount; // number of clusters enabling load reporting // Following fields are set only after the ConfigWatcher registered. Once set, they should // never change. Only a ConfigWatcher or ListenerWatcher can be registered. @@ -474,41 +477,45 @@ private void updateNodeMetadataForListenerRequest(int port) { } @Override - void reportClientStats( - String clusterName, @Nullable String clusterServiceName, LoadStatsStore loadStatsStore) { + void reportClientStats() { if (lrsClient == null) { + logger.log(XdsLogLevel.INFO, "Turning on load reporting"); lrsClient = new LoadReportClient( - logId, targetName, + loadStatsManager, channel, node.toEnvoyProtoNodeV2(), syncContext, timeService, backoffPolicyProvider, stopwatchSupplier); - lrsClient.startLoadReporting(new LoadReportCallback() { - @Override - public void onReportResponse(long reportIntervalNano) {} - }); } - logger.log( - XdsLogLevel.INFO, - "Report loads for cluster: {0}, cluster_service: {1}", clusterName, clusterServiceName); - lrsClient.addLoadStatsStore(clusterName, clusterServiceName, loadStatsStore); + if (loadReportCount == 0) { + lrsClient.startLoadReporting(); + } + loadReportCount++; } @Override - void cancelClientStatsReport(String clusterName, @Nullable String clusterServiceName) { - checkState(lrsClient != null, "load reporting was never started"); - logger.log( - XdsLogLevel.INFO, - "Stop reporting loads for cluster: {0}, cluster_service: {1}", - clusterName, - clusterServiceName); - lrsClient.removeLoadStatsStore(clusterName, clusterServiceName); - // TODO(chengyuanzhang): can be optimized to stop load reporting if no more loads need - // to be reported. + void cancelClientStatsReport() { + checkState(loadReportCount > 0, "load reporting was never started"); + loadReportCount--; + if (loadReportCount == 0) { + logger.log(XdsLogLevel.INFO, "Turning off load reporting"); + lrsClient.stopLoadReporting(); + lrsClient = null; + } + } + + @Override + LoadStatsStore addClientStats(String clusterName, @Nullable String clusterServiceName) { + return loadStatsManager.addLoadStats(clusterName, clusterServiceName); + } + + @Override + void removeClientStats(String clusterName, @Nullable String clusterServiceName) { + loadStatsManager.removeLoadStats(clusterName, clusterServiceName); } @Override diff --git a/xds/src/test/java/io/grpc/xds/CdsLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/CdsLoadBalancerTest.java index 34d57be999b..9a9bb69241f 100644 --- a/xds/src/test/java/io/grpc/xds/CdsLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/CdsLoadBalancerTest.java @@ -29,7 +29,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.same; -import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -61,8 +60,6 @@ import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.XdsClient.ClusterUpdate; import io.grpc.xds.XdsClient.ClusterWatcher; -import io.grpc.xds.XdsClient.EndpointUpdate; -import io.grpc.xds.XdsClient.EndpointWatcher; import io.grpc.xds.XdsClient.RefCountedXdsClientObjectPool; import io.grpc.xds.XdsClient.XdsClientFactory; import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil; @@ -592,52 +589,4 @@ public void clusterWatcher_onErrorCalledBeforeAndAfterOnClusterChanged() { verify(helper, times(1)) .updateBalancingState(eq(TRANSIENT_FAILURE), any(SubchannelPicker.class)); } - - @Test - public void cdsBalancerIntegrateWithEdsBalancer() { - lbRegistry.deregister(fakeEdsLoadBlancerProvider); - lbRegistry.register(new EdsLoadBalancerProvider()); - - ResolvedAddresses resolvedAddresses1 = ResolvedAddresses.newBuilder() - .setAddresses(ImmutableList.of()) - .setAttributes(Attributes.newBuilder() - .set(XdsAttributes.XDS_CLIENT_POOL, xdsClientPool) - .build()) - .setLoadBalancingPolicyConfig(new CdsConfig("foo.googleapis.com")) - .build(); - cdsLoadBalancer.handleResolvedAddresses(resolvedAddresses1); - ArgumentCaptor clusterWatcherCaptor = ArgumentCaptor.forClass(null); - verify(xdsClient).watchClusterData(eq("foo.googleapis.com"), clusterWatcherCaptor.capture()); - ClusterWatcher clusterWatcher = clusterWatcherCaptor.getValue(); - clusterWatcher.onClusterChanged( - ClusterUpdate.newBuilder() - .setClusterName("foo.googleapis.com") - .setEdsServiceName("edsServiceFoo.googleapis.com") - .setLbPolicy("round_robin") - .build()); - - ArgumentCaptor endpointWatcherCaptor = ArgumentCaptor.forClass(null); - verify(xdsClient).watchEndpointData( - eq("edsServiceFoo.googleapis.com"), endpointWatcherCaptor.capture()); - EndpointWatcher endpointWatcher = endpointWatcherCaptor.getValue(); - - verify(helper, never()).updateBalancingState( - eq(TRANSIENT_FAILURE), any(SubchannelPicker.class)); - // Update endpoints with all backends unhealthy, the EDS will update channel state to - // TRANSIENT_FAILURE. - // Not able to test with healthy endpoints because the real EDS balancer is using real - // round-robin balancer to balance endpoints. - endpointWatcher.onEndpointChanged(EndpointUpdate.newBuilder() - .setClusterName("edsServiceFoo.googleapis.com") - .addLocalityLbEndpoints( - new EnvoyProtoData.Locality("region", "zone", "subzone"), - new EnvoyProtoData.LocalityLbEndpoints( - // All unhealthy. - ImmutableList.of(new EnvoyProtoData.LbEndpoint("127.0.0.1", 8080, 1, false)), 1, 0)) - .build()); - verify(helper, atLeastOnce()).updateBalancingState( - eq(TRANSIENT_FAILURE), any(SubchannelPicker.class)); - - cdsLoadBalancer.shutdown(); - } } diff --git a/xds/src/test/java/io/grpc/xds/ClientLoadCounterTest.java b/xds/src/test/java/io/grpc/xds/ClientLoadCounterTest.java index 23a9993888b..a6d30b55c5d 100644 --- a/xds/src/test/java/io/grpc/xds/ClientLoadCounterTest.java +++ b/xds/src/test/java/io/grpc/xds/ClientLoadCounterTest.java @@ -76,9 +76,11 @@ public void snapshotContainsDataInCounter() { long numInProgressCalls = ThreadLocalRandom.current().nextLong(Long.MAX_VALUE); long numFailedCalls = ThreadLocalRandom.current().nextLong(Long.MAX_VALUE); long numIssuedCalls = ThreadLocalRandom.current().nextLong(Long.MAX_VALUE); - counter = - new ClientLoadCounter(numSucceededCalls, numInProgressCalls, numFailedCalls, - numIssuedCalls); + counter = new ClientLoadCounter(); + counter.setCallsSucceeded(numSucceededCalls); + counter.setCallsInProgress(numInProgressCalls); + counter.setCallsFailed(numFailedCalls); + counter.setCallsIssued(numIssuedCalls); ClientLoadSnapshot snapshot = counter.snapshot(); assertQueryCounts(snapshot, numSucceededCalls, numInProgressCalls, numFailedCalls, numIssuedCalls); diff --git a/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java index 62c72ba9e0d..17706682bc0 100644 --- a/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java @@ -76,6 +76,7 @@ import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.EdsLoadBalancerProvider.EdsConfig; import io.grpc.xds.EnvoyProtoData.Node; +import io.grpc.xds.LoadStatsManager.LoadStatsStore; import io.grpc.xds.LocalityStore.LocalityStoreFactory; import io.grpc.xds.XdsClient.EndpointUpdate; import io.grpc.xds.XdsClient.XdsChannel; diff --git a/xds/src/test/java/io/grpc/xds/LoadReportClientTest.java b/xds/src/test/java/io/grpc/xds/LoadReportClientTest.java index 4b8645a7e93..be68f08b110 100644 --- a/xds/src/test/java/io/grpc/xds/LoadReportClientTest.java +++ b/xds/src/test/java/io/grpc/xds/LoadReportClientTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.MoreExecutors; @@ -44,7 +45,6 @@ import io.envoyproxy.envoy.service.load_stats.v2.LoadStatsResponse; import io.grpc.Context; import io.grpc.Context.CancellationListener; -import io.grpc.InternalLogId; import io.grpc.ManagedChannel; import io.grpc.Status; import io.grpc.SynchronizationContext; @@ -54,7 +54,8 @@ import io.grpc.internal.FakeClock; import io.grpc.stub.StreamObserver; import io.grpc.testing.GrpcCleanupRule; -import io.grpc.xds.LoadReportClient.LoadReportCallback; +import io.grpc.xds.LoadStatsManager.LoadStatsStore; +import io.grpc.xds.LoadStatsManager.LoadStatsStoreFactory; import java.util.ArrayDeque; import java.util.Arrays; import java.util.Collection; @@ -62,10 +63,10 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Queue; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import javax.annotation.Nullable; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -95,6 +96,8 @@ public class LoadReportClientTest { "TRAFFICDIRECTOR_NETWORK_HOSTNAME", Value.newBuilder().setStringValue("default").build())) .build(); + private static final String CLUSTER1 = "cluster-foo.googleapis.com"; + private static final String CLUSTER2 = "cluster-bar.googleapis.com"; private static final FakeClock.TaskFilter LOAD_REPORTING_TASK_FILTER = new FakeClock.TaskFilter() { @Override @@ -121,11 +124,18 @@ public void uncaughtException(Thread t, Throwable e) { throw new AssertionError(e); } }); - private final InternalLogId logId = InternalLogId.allocate("lrs-client-test", null); private final FakeClock fakeClock = new FakeClock(); private final ArrayDeque> lrsRequestObservers = new ArrayDeque<>(); private final AtomicBoolean callEnded = new AtomicBoolean(true); + private final LoadStatsManager loadStatsManager = + new LoadStatsManager(new LoadStatsStoreFactory() { + @Override + public LoadStatsStore newLoadStatsStore(String cluster, String clusterService) { + return new FakeLoadStatsStore( + cluster, clusterService, fakeClock.getStopwatchSupplier().get()); + } + }); @Mock private BackoffPolicy.Provider backoffPolicyProvider; @@ -133,12 +143,6 @@ public void uncaughtException(Thread t, Throwable e) { private BackoffPolicy backoffPolicy1; @Mock private BackoffPolicy backoffPolicy2; - @Mock - private LoadStatsStore loadStatsStore1; - @Mock - private LoadStatsStore loadStatsStore2; - @Mock - private LoadReportCallback callback; @Captor private ArgumentCaptor> lrsResponseObserverCaptor; @@ -183,15 +187,15 @@ public void cancelled(Context context) { .thenReturn(TimeUnit.SECONDS.toNanos(2L), TimeUnit.SECONDS.toNanos(20L)); lrsClient = new LoadReportClient( - logId, TARGET_NAME, + loadStatsManager, channel, NODE, syncContext, fakeClock.getScheduledExecutorService(), backoffPolicyProvider, fakeClock.getStopwatchSupplier()); - lrsClient.startLoadReporting(callback); + lrsClient.startLoadReporting(); } @After @@ -201,52 +205,55 @@ public void tearDown() { } @Test - public void typicalWorkflow() { + public void periodicLoadReporting() { verify(mockLoadReportingService).streamLoadStats(lrsResponseObserverCaptor.capture()); StreamObserver responseObserver = lrsResponseObserverCaptor.getValue(); StreamObserver requestObserver = Iterables.getOnlyElement(lrsRequestObservers); - InOrder inOrder = inOrder(requestObserver, callback); + InOrder inOrder = inOrder(requestObserver); inOrder.verify(requestObserver).onNext(eq(buildInitialRequest())); - String cluster1 = "cluster-foo.googleapis.com"; - ClusterStats rawStats1 = generateClusterLoadStats(cluster1, null); - when(loadStatsStore1.generateLoadReport()).thenReturn(rawStats1); - lrsClient.addLoadStatsStore(cluster1, null, loadStatsStore1); + FakeLoadStatsStore loadStatsStore1 = + (FakeLoadStatsStore) loadStatsManager.addLoadStats(CLUSTER1, null); + loadStatsStore1.refresh(); // Management server asks to report loads for cluster1. - responseObserver.onNext(buildLrsResponse(ImmutableList.of(cluster1), 1000)); - inOrder.verify(callback).onReportResponse(1000); + responseObserver.onNext(buildLrsResponse(ImmutableList.of(CLUSTER1), 1000)); - ClusterStats expectedStats1 = - rawStats1.toBuilder().setLoadReportInterval(Durations.fromNanos(1000)).build(); fakeClock.forwardNanos(999); inOrder.verifyNoMoreInteractions(); fakeClock.forwardNanos(1); + assertThat(loadStatsStore1.reported).hasSize(1); + ClusterStats report1 = loadStatsStore1.reported.poll(); + assertThat(Durations.toNanos(report1.getLoadReportInterval())).isEqualTo(1000); inOrder.verify(requestObserver) - .onNext(argThat(new LoadStatsRequestMatcher(Collections.singletonList(expectedStats1)))); + .onNext(argThat(new LoadStatsRequestMatcher(Collections.singletonList(report1)))); + loadStatsStore1.refresh(); fakeClock.forwardNanos(1000); + assertThat(loadStatsStore1.reported).hasSize(1); + report1 = loadStatsStore1.reported.poll(); + assertThat(Durations.toNanos(report1.getLoadReportInterval())).isEqualTo(1000); inOrder.verify(requestObserver) - .onNext(argThat(new LoadStatsRequestMatcher(Collections.singletonList(expectedStats1)))); + .onNext(argThat(new LoadStatsRequestMatcher(Collections.singletonList(report1)))); - String cluster2 = "cluster-bar.googleapis.com"; - ClusterStats rawStats2 = generateClusterLoadStats(cluster2, null); - when(loadStatsStore2.generateLoadReport()).thenReturn(rawStats2); - lrsClient.addLoadStatsStore(cluster2, null, loadStatsStore2); + FakeLoadStatsStore loadStatsStore2 = + (FakeLoadStatsStore) loadStatsManager.addLoadStats(CLUSTER2, null); + loadStatsStore2.refresh(); // Management server updates the interval of sending load reports, while still asking for // loads to cluster1 only. - responseObserver.onNext(buildLrsResponse(ImmutableList.of(cluster1), 2000)); - inOrder.verify(callback).onReportResponse(2000); + responseObserver.onNext(buildLrsResponse(ImmutableList.of(CLUSTER1), 2000)); - expectedStats1 = - rawStats1.toBuilder().setLoadReportInterval(Durations.fromNanos(2000)).build(); fakeClock.forwardNanos(1000); inOrder.verifyNoMoreInteractions(); fakeClock.forwardNanos(1000); + assertThat(loadStatsStore1.reported).hasSize(1); + report1 = loadStatsStore1.reported.poll(); + assertThat(Durations.toNanos(report1.getLoadReportInterval())).isEqualTo(2000); + assertThat(loadStatsStore2.reported).isEmpty(); inOrder.verify(requestObserver) - .onNext(argThat(new LoadStatsRequestMatcher(Collections.singletonList(expectedStats1)))); + .onNext(argThat(new LoadStatsRequestMatcher(Collections.singletonList(report1)))); // Management server asks to report loads for all clusters. responseObserver.onNext( @@ -254,30 +261,39 @@ public void typicalWorkflow() { .setSendAllClusters(true) .setLoadReportingInterval(Durations.fromNanos(2000)) .build()); - inOrder.verify(callback).onReportResponse(2000); - ClusterStats expectedStats2 = - rawStats2.toBuilder().setLoadReportInterval(Durations.fromNanos(2000 + 2000)).build(); + loadStatsStore1.refresh(); + loadStatsStore2.refresh(); fakeClock.forwardNanos(2000); + assertThat(loadStatsStore1.reported).hasSize(1); + report1 = loadStatsStore1.reported.poll(); + assertThat(loadStatsStore2.reported).hasSize(1); + ClusterStats report2 = loadStatsStore2.reported.poll(); + assertThat(Durations.toNanos(report1.getLoadReportInterval())).isEqualTo(2000); + assertThat(Durations.toNanos(report2.getLoadReportInterval())).isEqualTo(2000 + 2000); inOrder.verify(requestObserver) - .onNext( - argThat( - new LoadStatsRequestMatcher(Arrays.asList(expectedStats1, expectedStats2)))); + .onNext(argThat(new LoadStatsRequestMatcher(Arrays.asList(report1, report2)))); // Load reports for cluster1 is no longer wanted. - responseObserver.onNext(buildLrsResponse(Collections.singletonList(cluster2), 2000)); + responseObserver.onNext(buildLrsResponse(Collections.singletonList(CLUSTER2), 2000)); - expectedStats2 = - rawStats2.toBuilder().setLoadReportInterval(Durations.fromNanos(2000)).build(); + loadStatsStore1.refresh(); + loadStatsStore2.refresh(); fakeClock.forwardNanos(2000); + assertThat(loadStatsStore1.reported).isEmpty(); + assertThat(loadStatsStore2.reported).hasSize(1); + report2 = loadStatsStore2.reported.poll(); + assertThat(Durations.toNanos(report2.getLoadReportInterval())).isEqualTo(2000); inOrder.verify(requestObserver) - .onNext(argThat(new LoadStatsRequestMatcher(Collections.singletonList(expectedStats2)))); + .onNext(argThat(new LoadStatsRequestMatcher(Collections.singletonList(report2)))); // Management server asks loads for a cluster that client has no load data. responseObserver .onNext(buildLrsResponse(ImmutableList.of("cluster-unknown.googleapis.com"), 2000)); fakeClock.forwardNanos(2000); + assertThat(loadStatsStore1.reported).isEmpty(); + assertThat(loadStatsStore2.reported).isEmpty(); ArgumentCaptor reportCaptor = ArgumentCaptor.forClass(null); inOrder.verify(requestObserver).onNext(reportCaptor.capture()); assertThat(reportCaptor.getValue().getClusterStatsCount()).isEqualTo(0); @@ -296,9 +312,9 @@ public void lrsStreamClosedAndRetried() { String clusterName = "cluster-foo.googleapis.com"; String clusterServiceName = "service-blade.googleapis.com"; - ClusterStats stats = generateClusterLoadStats(clusterName, clusterServiceName); - when(loadStatsStore1.generateLoadReport()).thenReturn(stats); - lrsClient.addLoadStatsStore(clusterName, null, loadStatsStore1); + FakeLoadStatsStore loadStatsStore = + (FakeLoadStatsStore) loadStatsManager.addLoadStats(clusterName, clusterServiceName); + loadStatsStore.refresh(); // First balancer RPC verify(requestObserver).onNext(eq(buildInitialRequest())); @@ -382,13 +398,11 @@ public void lrsStreamClosedAndRetried() { responseObserver .onNext(buildLrsResponse(ImmutableList.of(clusterName), 10)); fakeClock.forwardNanos(10); - ClusterStats expectedStats = - stats.toBuilder() - .setLoadReportInterval( - Durations.add(Durations.fromSeconds(1 + 10 + 2), Durations.fromNanos(10))) - .build(); + ClusterStats report = Iterables.getOnlyElement(loadStatsStore.reported); + assertThat(Durations.toNanos(report.getLoadReportInterval())) + .isEqualTo(TimeUnit.SECONDS.toNanos(1 + 10 + 2) + 10); verify(requestObserver) - .onNext(argThat(new LoadStatsRequestMatcher(Collections.singletonList(expectedStats)))); + .onNext(argThat(new LoadStatsRequestMatcher(Collections.singletonList(report)))); // Wrapping up verify(backoffPolicyProvider, times(2)).get(); @@ -405,9 +419,9 @@ public void raceBetweenLoadReportingAndLbStreamClosure() { String clusterName = "cluster-foo.googleapis.com"; String clusterServiceName = "service-blade.googleapis.com"; - ClusterStats stats = generateClusterLoadStats(clusterName, clusterServiceName); - when(loadStatsStore1.generateLoadReport()).thenReturn(stats); - lrsClient.addLoadStatsStore(clusterName, null, loadStatsStore1); + FakeLoadStatsStore loadStatsStore = + (FakeLoadStatsStore) loadStatsManager.addLoadStats(clusterName, clusterServiceName); + loadStatsStore.refresh(); // First balancer RPC verify(requestObserver).onNext(eq(buildInitialRequest())); @@ -464,46 +478,6 @@ private static LoadStatsRequest buildInitialRequest() { .build(); } - /** - * Generates a raw service load stats report with random data. - */ - private static ClusterStats generateClusterLoadStats( - String clusterName, @Nullable String clusterServiceName) { - long callsInProgress = ThreadLocalRandom.current().nextLong(Long.MAX_VALUE); - long callsSucceeded = ThreadLocalRandom.current().nextLong(Long.MAX_VALUE); - long callsFailed = ThreadLocalRandom.current().nextLong(Long.MAX_VALUE); - long callsIssued = ThreadLocalRandom.current().nextLong(Long.MAX_VALUE); - long numLbDrops = ThreadLocalRandom.current().nextLong(Long.MAX_VALUE); - long numThrottleDrops = ThreadLocalRandom.current().nextLong(Long.MAX_VALUE); - - ClusterStats.Builder clusterStatsBuilder = ClusterStats.newBuilder(); - clusterStatsBuilder.setClusterName(clusterName); - if (clusterServiceName != null) { - clusterStatsBuilder.setClusterServiceName(clusterServiceName); - } - clusterStatsBuilder.addUpstreamLocalityStats( - UpstreamLocalityStats.newBuilder() - .setLocality( - Locality.newBuilder() - .setRegion(clusterName + "-region-foo") - .setZone(clusterName + "-zone-bar") - .setSubZone(clusterName + "-subzone-baz")) - .setTotalRequestsInProgress(callsInProgress) - .setTotalSuccessfulRequests(callsSucceeded) - .setTotalErrorRequests(callsFailed) - .setTotalIssuedRequests(callsIssued)) - .addDroppedRequests( - DroppedRequests.newBuilder() - .setCategory("lb") - .setDroppedCount(numLbDrops)) - .addDroppedRequests( - DroppedRequests.newBuilder() - .setCategory("throttle") - .setDroppedCount(numThrottleDrops)) - .setTotalDroppedRequests(numLbDrops + numThrottleDrops); - return clusterStatsBuilder.build(); - } - /** * For comparing LoadStatsRequest stats data regardless of . */ @@ -534,4 +508,82 @@ public boolean matches(LoadStatsRequest argument) { return true; } } + + private static final class FakeLoadStatsStore implements LoadStatsStore { + private final String cluster; + private final String clusterService; + private final Stopwatch stopwatch; + private final Queue reported = new ArrayDeque<>(); + private ClusterStats stats; + + private FakeLoadStatsStore(String cluster, String clusterService, Stopwatch stopwatch) { + this.cluster = cluster; + this.clusterService = clusterService; + this.stopwatch = stopwatch; + stopwatch.reset().start(); + refresh(); + } + + @Override + public ClusterStats generateLoadReport() { + ClusterStats report = + stats.toBuilder() + .setLoadReportInterval(Durations.fromNanos(stopwatch.elapsed(TimeUnit.NANOSECONDS))) + .build(); + stopwatch.reset().start(); + reported.offer(report); + return report; + } + + @Override + public ClientLoadCounter addLocality(EnvoyProtoData.Locality locality) { + throw new UnsupportedOperationException("should not used"); + } + + @Override + public void removeLocality(EnvoyProtoData.Locality locality) { + throw new UnsupportedOperationException("should not used"); + } + + @Override + public void recordDroppedRequest(String category) { + throw new UnsupportedOperationException("should not used"); + } + + private void refresh() { + long callsInProgress = ThreadLocalRandom.current().nextLong(Long.MAX_VALUE); + long callsSucceeded = ThreadLocalRandom.current().nextLong(Long.MAX_VALUE); + long callsFailed = ThreadLocalRandom.current().nextLong(Long.MAX_VALUE); + long callsIssued = ThreadLocalRandom.current().nextLong(Long.MAX_VALUE); + long numLbDrops = ThreadLocalRandom.current().nextLong(Long.MAX_VALUE); + long numThrottleDrops = ThreadLocalRandom.current().nextLong(Long.MAX_VALUE); + + ClusterStats.Builder clusterStatsBuilder = ClusterStats.newBuilder(); + clusterStatsBuilder.setClusterName(cluster); + if (clusterService != null) { + clusterStatsBuilder.setClusterServiceName(clusterService); + } + clusterStatsBuilder.addUpstreamLocalityStats( + UpstreamLocalityStats.newBuilder() + .setLocality( + Locality.newBuilder() + .setRegion(cluster + "-region-foo") + .setZone(cluster + "-zone-bar") + .setSubZone(cluster + "-subzone-baz")) + .setTotalRequestsInProgress(callsInProgress) + .setTotalSuccessfulRequests(callsSucceeded) + .setTotalErrorRequests(callsFailed) + .setTotalIssuedRequests(callsIssued)) + .addDroppedRequests( + DroppedRequests.newBuilder() + .setCategory("lb") + .setDroppedCount(numLbDrops)) + .addDroppedRequests( + DroppedRequests.newBuilder() + .setCategory("throttle") + .setDroppedCount(numThrottleDrops)) + .setTotalDroppedRequests(numLbDrops + numThrottleDrops); + stats = clusterStatsBuilder.build(); + } + } } diff --git a/xds/src/test/java/io/grpc/xds/LoadStatsStoreImplTest.java b/xds/src/test/java/io/grpc/xds/LoadStatsStoreImplTest.java index 96b96b67684..4320ff279fd 100644 --- a/xds/src/test/java/io/grpc/xds/LoadStatsStoreImplTest.java +++ b/xds/src/test/java/io/grpc/xds/LoadStatsStoreImplTest.java @@ -18,13 +18,17 @@ import static com.google.common.truth.Truth.assertThat; +import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableMap; +import com.google.protobuf.util.Durations; import io.envoyproxy.envoy.api.v2.endpoint.ClusterStats; import io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.DroppedRequests; import io.envoyproxy.envoy.api.v2.endpoint.EndpointLoadMetricStats; import io.envoyproxy.envoy.api.v2.endpoint.UpstreamLocalityStats; +import io.grpc.internal.FakeClock; import io.grpc.xds.ClientLoadCounter.MetricValue; import io.grpc.xds.EnvoyProtoData.Locality; +import io.grpc.xds.LoadStatsManager.LoadStatsStore; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -35,7 +39,6 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; import javax.annotation.Nullable; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -49,16 +52,15 @@ public class LoadStatsStoreImplTest { new Locality("test_region1", "test_zone", "test_subzone"); private static final Locality LOCALITY2 = new Locality("test_region2", "test_zone", "test_subzone"); - private ConcurrentMap localityLoadCounters; + private final FakeClock fakeClock = new FakeClock(); private ConcurrentMap dropCounters; private LoadStatsStore loadStatsStore; @Before public void setUp() { - localityLoadCounters = new ConcurrentHashMap<>(); dropCounters = new ConcurrentHashMap<>(); - loadStatsStore = - new LoadStatsStoreImpl(CLUSTER_NAME, null, localityLoadCounters, dropCounters); + Stopwatch stopwatch = fakeClock.getStopwatchSupplier().get(); + loadStatsStore = new LoadStatsStoreImpl(CLUSTER_NAME, null, stopwatch, dropCounters); } private static List buildEndpointLoadMetricStatsList( @@ -103,7 +105,7 @@ private static DroppedRequests buildDroppedRequests(String category, long counts private static ClusterStats buildClusterStats( @Nullable List upstreamLocalityStatsList, - @Nullable List droppedRequestsList) { + @Nullable List droppedRequestsList, long intervalNano) { ClusterStats.Builder clusterStatsBuilder = ClusterStats.newBuilder(); clusterStatsBuilder.setClusterName(CLUSTER_NAME); if (upstreamLocalityStatsList != null) { @@ -117,6 +119,7 @@ private static ClusterStats buildClusterStats( } clusterStatsBuilder.setTotalDroppedRequests(dropCount); } + clusterStatsBuilder.setLoadReportInterval(Durations.fromNanos(intervalNano)); return clusterStatsBuilder.build(); } @@ -159,74 +162,47 @@ private static void assertUpstreamLocalityStatsEqual(UpstreamLocalityStats expec } @Test - public void addAndGetAndRemoveLocality() { - loadStatsStore.addLocality(LOCALITY1); - assertThat(localityLoadCounters).containsKey(LOCALITY1); - - // Adding the same locality counter again causes an exception. - try { - loadStatsStore.addLocality(LOCALITY1); - Assert.fail(); - } catch (IllegalStateException expected) { - assertThat(expected).hasMessageThat() - .contains("An active counter for locality " + LOCALITY1 + " already exists"); - } - - assertThat(loadStatsStore.getLocalityCounter(LOCALITY1)) - .isSameInstanceAs(localityLoadCounters.get(LOCALITY1)); - assertThat(loadStatsStore.getLocalityCounter(LOCALITY2)).isNull(); - - // Removing an non-existing locality counter causes an exception. - try { - loadStatsStore.removeLocality(LOCALITY2); - Assert.fail(); - } catch (IllegalStateException expected) { - assertThat(expected).hasMessageThat() - .contains("No active counter for locality " + LOCALITY2 + " exists"); - } - - // Removing the locality counter only mark it as inactive, but not throw it away. - loadStatsStore.removeLocality(LOCALITY1); - assertThat(localityLoadCounters.get(LOCALITY1).isActive()).isFalse(); - - // Removing an inactive locality counter causes an exception. - try { - loadStatsStore.removeLocality(LOCALITY1); - Assert.fail(); - } catch (IllegalStateException expected) { - assertThat(expected).hasMessageThat() - .contains("No active counter for locality " + LOCALITY1 + " exists"); - } - - // Adding it back simply mark it as active again. + public void removeInactiveCountersAfterGeneratingLoadReport() { loadStatsStore.addLocality(LOCALITY1); - assertThat(localityLoadCounters.get(LOCALITY1).isActive()).isTrue(); + assertThat(loadStatsStore.generateLoadReport().getUpstreamLocalityStatsCount()).isEqualTo(1); + loadStatsStore.removeLocality(LOCALITY1); // becomes inactive + assertThat(loadStatsStore.generateLoadReport().getUpstreamLocalityStatsCount()).isEqualTo(1); + assertThat(loadStatsStore.generateLoadReport().getUpstreamLocalityStatsCount()).isEqualTo(0); } @Test - public void removeInactiveCountersAfterGeneratingLoadReport() { - localityLoadCounters.put(LOCALITY1, new ClientLoadCounter()); - ClientLoadCounter inactiveCounter = new ClientLoadCounter(); - inactiveCounter.setActive(false); - localityLoadCounters.put(LOCALITY2, inactiveCounter); - loadStatsStore.generateLoadReport(); - assertThat(localityLoadCounters).containsKey(LOCALITY1); - assertThat(localityLoadCounters).doesNotContainKey(LOCALITY2); + public void localityCountersReferenceCounted() { + loadStatsStore.addLocality(LOCALITY1); + loadStatsStore.addLocality(LOCALITY1); + loadStatsStore.removeLocality(LOCALITY1); + assertThat(loadStatsStore.generateLoadReport().getUpstreamLocalityStatsCount()).isEqualTo(1); + assertThat(loadStatsStore.generateLoadReport().getUpstreamLocalityStatsCount()) + .isEqualTo(1); // still active + loadStatsStore.removeLocality(LOCALITY1); // becomes inactive + assertThat(loadStatsStore.generateLoadReport().getUpstreamLocalityStatsCount()).isEqualTo(1); + assertThat(loadStatsStore.generateLoadReport().getUpstreamLocalityStatsCount()).isEqualTo(0); } @Test public void loadReportContainsRecordedStats() { - ClientLoadCounter counter1 = new ClientLoadCounter(4315, 3421, 23, 593); + ClientLoadCounter counter1 = loadStatsStore.addLocality(LOCALITY1); + counter1.setCallsSucceeded(4315); + counter1.setCallsInProgress(3421); + counter1.setCallsFailed(23); + counter1.setCallsIssued(593); counter1.recordMetric("cpu_utilization", 0.3244); counter1.recordMetric("mem_utilization", 0.01233); counter1.recordMetric("named_cost_or_utilization", 3221.6543); - ClientLoadCounter counter2 = new ClientLoadCounter(41234, 432, 431, 702); + ClientLoadCounter counter2 = loadStatsStore.addLocality(LOCALITY2); + counter2.setCallsSucceeded(41234); + counter2.setCallsInProgress(432); + counter2.setCallsFailed(431); + counter2.setCallsIssued(702); counter2.recordMetric("cpu_utilization", 0.6526); counter2.recordMetric("mem_utilization", 0.3473); counter2.recordMetric("named_cost_or_utilization", 87653.4234); - localityLoadCounters.put(LOCALITY1, counter1); - localityLoadCounters.put(LOCALITY2, counter2); + fakeClock.forwardNanos(1000L); Map metrics1 = ImmutableMap.of( "cpu_utilization", new MetricValue(1, 0.3244), @@ -245,16 +221,17 @@ public void loadReportContainsRecordedStats() { buildUpstreamLocalityStats(LOCALITY2, 41234, 432, 431, 702, buildEndpointLoadMetricStatsList(metrics2)) ), - null); + null, 1000L); assertClusterStatsEqual(expectedReport, loadStatsStore.generateLoadReport()); + fakeClock.forwardNanos(2000L); expectedReport = buildClusterStats( Arrays.asList( buildUpstreamLocalityStats(LOCALITY1, 0, 3421, 0, 0, null), buildUpstreamLocalityStats(LOCALITY2, 0, 432, 0, 0, null) ), - null); + null, 2000L); assertClusterStatsEqual(expectedReport, loadStatsStore.generateLoadReport()); } @@ -270,10 +247,13 @@ public void recordingDroppedRequests() { } assertThat(dropCounters.get("lb").get()).isEqualTo(numLbDrop); assertThat(dropCounters.get("throttle").get()).isEqualTo(numThrottleDrop); + + fakeClock.forwardNanos(1000L); ClusterStats expectedLoadReport = buildClusterStats(null, Arrays.asList(buildDroppedRequests("lb", numLbDrop), - buildDroppedRequests("throttle", numThrottleDrop))); + buildDroppedRequests("throttle", numThrottleDrop)), + 1000L); assertClusterStatsEqual(expectedLoadReport, loadStatsStore.generateLoadReport()); assertThat(dropCounters.get("lb").get()).isEqualTo(0); assertThat(dropCounters.get("throttle").get()).isEqualTo(0); diff --git a/xds/src/test/java/io/grpc/xds/LocalityStoreTest.java b/xds/src/test/java/io/grpc/xds/LocalityStoreTest.java index 4f9c8f51753..6c079b54edb 100644 --- a/xds/src/test/java/io/grpc/xds/LocalityStoreTest.java +++ b/xds/src/test/java/io/grpc/xds/LocalityStoreTest.java @@ -69,6 +69,7 @@ import io.grpc.xds.EnvoyProtoData.LbEndpoint; import io.grpc.xds.EnvoyProtoData.Locality; import io.grpc.xds.EnvoyProtoData.LocalityLbEndpoints; +import io.grpc.xds.LoadStatsManager.LoadStatsStore; import io.grpc.xds.LocalityStore.LocalityStoreImpl; import io.grpc.xds.OrcaOobUtil.OrcaOobReportListener; import io.grpc.xds.OrcaOobUtil.OrcaReportingConfig; @@ -84,7 +85,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; -import javax.annotation.Nullable; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -1053,8 +1053,8 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { ArgumentCaptor subchannelPickerCaptor = ArgumentCaptor.forClass(null); inOrder.verify(helper).updateBalancingState(same(READY), subchannelPickerCaptor.capture()); assertThat(subchannelPickerCaptor.getValue() - .pickSubchannel(mock(PickSubchannelArgs.class)) - .getSubchannel()) + .pickSubchannel(mock(PickSubchannelArgs.class)) + .getSubchannel()) .isSameInstanceAs(mockSubchannel3); // P0 gets READY - P0 R, P1 F&D, P2 R&D, P3 N/A @@ -1078,8 +1078,8 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { assertThat(deactivationTasks).hasSize(2); inOrder.verify(helper).updateBalancingState(same(READY), subchannelPickerCaptor.capture()); assertThat(subchannelPickerCaptor.getValue() - .pickSubchannel(mock(PickSubchannelArgs.class)) - .getSubchannel()) + .pickSubchannel(mock(PickSubchannelArgs.class)) + .getSubchannel()) .isSameInstanceAs(mockSubchannel1); // P1 gets READY - P0 R, P1 R&D, P2 R&D, P3 N/A @@ -1188,8 +1188,8 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { assertThat(fakeClock.getPendingTasks(deactivationTaskFilter)).hasSize(2); inOrder.verify(helper).updateBalancingState(same(READY), subchannelPickerCaptor.capture()); assertThat(subchannelPickerCaptor.getValue() - .pickSubchannel(mock(PickSubchannelArgs.class)) - .getSubchannel()) + .pickSubchannel(mock(PickSubchannelArgs.class)) + .getSubchannel()) .isSameInstanceAs(mockSubchannel22); // EDS update, localities moved: P0 sz1, sz3; P1 sz4; P2 sz2 - P0 C, P1 R, P2 R&D @@ -1214,8 +1214,8 @@ public void run() { assertThat(loadBalancers.values()).containsExactly(lb1, lb2, lb3, lb4); inOrder.verify(helper).updateBalancingState(same(READY), subchannelPickerCaptor.capture()); assertThat(subchannelPickerCaptor.getValue() - .pickSubchannel(mock(PickSubchannelArgs.class)) - .getSubchannel()) + .pickSubchannel(mock(PickSubchannelArgs.class)) + .getSubchannel()) .isSameInstanceAs(mockSubchannel4); // The order of the following four handleResolvedAddresses() does not matter. We want to verify // they are after helper.updateBalancingState() @@ -1283,8 +1283,8 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { assertThat(fakeClock.getPendingTasks(deactivationTaskFilter)).hasSize(1); inOrder.verify(helper).updateBalancingState(same(READY), subchannelPickerCaptor.capture()); assertThat(subchannelPickerCaptor.getValue() - .pickSubchannel(mock(PickSubchannelArgs.class)) - .getSubchannel()) + .pickSubchannel(mock(PickSubchannelArgs.class)) + .getSubchannel()) .isSameInstanceAs(newMockSubchannel3); inOrder.verifyNoMoreInteractions(); @@ -1337,9 +1337,11 @@ public ClusterStats generateLoadReport() { } @Override - public void addLocality(Locality locality) { + public ClientLoadCounter addLocality(Locality locality) { assertThat(localityCounters).doesNotContainKey(locality); - localityCounters.put(locality, new ClientLoadCounter()); + ClientLoadCounter counter = new ClientLoadCounter(); + localityCounters.put(locality, counter); + return counter; } @Override @@ -1348,12 +1350,6 @@ public void removeLocality(Locality locality) { localityCounters.remove(locality); } - @Nullable - @Override - public ClientLoadCounter getLocalityCounter(Locality locality) { - return localityCounters.get(locality); - } - @Override public void recordDroppedRequest(String category) { // NO-OP, verify by invocations. diff --git a/xds/src/test/java/io/grpc/xds/LrsLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/LrsLoadBalancerTest.java index e845a2ccc97..bea3a2f2faa 100644 --- a/xds/src/test/java/io/grpc/xds/LrsLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/LrsLoadBalancerTest.java @@ -40,6 +40,7 @@ import io.grpc.xds.ClientLoadCounter.LoadRecordingStreamTracerFactory; import io.grpc.xds.ClientLoadCounter.LoadRecordingSubchannelPicker; import io.grpc.xds.EnvoyProtoData.Locality; +import io.grpc.xds.LoadStatsManager.LoadStatsStore; import io.grpc.xds.LrsLoadBalancerProvider.LrsConfig; import java.net.SocketAddress; import java.util.ArrayDeque; @@ -72,8 +73,6 @@ public class LrsLoadBalancerTest { private static final String LRS_SERVER_NAME = "trafficdirector.googleapis.com"; private static final Locality TEST_LOCALITY = new Locality("test-region", "test-zone", "test-subzone"); - - private final ClientLoadCounter counter = new ClientLoadCounter(); private final LoadRecorder loadRecorder = new LoadRecorder(); private final Queue childBalancers = new ArrayDeque<>(); @@ -106,7 +105,7 @@ public void subchannelPickerInterceptedWithLoadRecording() { PickResult result = picker.pickSubchannel(mock(PickSubchannelArgs.class)); ClientStreamTracer.Factory tracerFactory = result.getStreamTracerFactory(); assertThat(((LoadRecordingStreamTracerFactory) tracerFactory).getCounter()) - .isSameInstanceAs(counter); + .isSameInstanceAs(loadRecorder.counter); loadBalancer.shutdown(); assertThat(childBalancer.shutdown).isTrue(); assertThat(loadRecorder.recording).isFalse(); @@ -300,7 +299,8 @@ public int hashCode() { } } - private final class LoadRecorder implements LoadStatsStore { + private static final class LoadRecorder implements LoadStatsStore { + private final ClientLoadCounter counter = new ClientLoadCounter(); private boolean recording = false; @Override @@ -309,9 +309,10 @@ public ClusterStats generateLoadReport() { } @Override - public void addLocality(Locality locality) { + public ClientLoadCounter addLocality(Locality locality) { assertThat(locality).isEqualTo(TEST_LOCALITY); recording = true; + return counter; } @Override @@ -320,12 +321,6 @@ public void removeLocality(Locality locality) { recording = false; } - @Override - public ClientLoadCounter getLocalityCounter(Locality locality) { - assertThat(locality).isEqualTo(TEST_LOCALITY); - return counter; - } - @Override public void recordDroppedRequest(String category) { throw new UnsupportedOperationException("should not be called"); diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java index aa294eb3809..ec81022c924 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java @@ -38,7 +38,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -1866,7 +1865,8 @@ public void addRemoveClusterWatcherWhileInitialResourceFetchInProgress() { assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); assertThat(timeoutTask.isCancelled()).isTrue(); - verifyNoInteractions(watcher3, watcher4); + // TODO(chengyuanzhang): migrate to verifyNoInteractions. + verifyNoMoreInteractions(watcher3, watcher4); } @Test @@ -2133,7 +2133,8 @@ public void multipleEndpointWatchers() { new LbEndpoint("192.168.0.1", 8080, 2, true)), 1, 0)); - verifyNoInteractions(watcher3); + // TODO(chengyuanzhang): migrate to verifyNoInteractions. + verifyNoMoreInteractions(watcher3); // Management server sends back another EDS response contains ClusterLoadAssignment for the // other requested cluster. @@ -2517,7 +2518,8 @@ public void addRemoveEndpointWatcherWhileInitialResourceFetchInProgress() { assertThat(fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); assertThat(timeoutTask.isCancelled()).isTrue(); - verifyNoInteractions(watcher3, watcher4); + // TODO(chengyuanzhang): migrate to verifyNoInteractions. + verifyNoMoreInteractions(watcher3, watcher4); } @Test @@ -3235,9 +3237,9 @@ public void streamClosedAndRetryReschedulesAllResourceFetchTimer() { @Test public void reportLoadStatsToServer() { String clusterName = "cluster-foo.googleapis.com"; - LoadStatsStore loadStatsStore = new LoadStatsStoreImpl(clusterName, null); + xdsClient.addClientStats(clusterName, null); ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(null); - xdsClient.reportClientStats(clusterName, null, loadStatsStore); + xdsClient.reportClientStats(); LoadReportCall lrsCall = loadReportCalls.poll(); verify(lrsCall.requestObserver).onNext(requestCaptor.capture()); assertThat(requestCaptor.getValue().getClusterStatsCount()) @@ -3253,12 +3255,14 @@ public void reportLoadStatsToServer() { ClusterStats report = Iterables.getOnlyElement(requestCaptor.getValue().getClusterStatsList()); assertThat(report.getClusterName()).isEqualTo(clusterName); - xdsClient.cancelClientStatsReport(clusterName, null); + xdsClient.removeClientStats(clusterName, null); fakeClock.forwardNanos(1000L); verify(lrsCall.requestObserver, times(3)).onNext(requestCaptor.capture()); assertThat(requestCaptor.getValue().getClusterStatsCount()) .isEqualTo(0); // no more stats reported + xdsClient.cancelClientStatsReport(); + assertThat(lrsEnded.get()).isTrue(); // See more test on LoadReportClientTest.java } diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java index 692cba94c38..fcd268e0193 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java @@ -38,7 +38,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -1875,7 +1874,8 @@ public void addRemoveClusterWatcherWhileInitialResourceFetchInProgress() { assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); assertThat(timeoutTask.isCancelled()).isTrue(); - verifyNoInteractions(watcher3, watcher4); + // TODO(chengyuanzhang): migrate to verifyNoInteractions. + verifyNoMoreInteractions(watcher3, watcher4); } @Test @@ -2142,7 +2142,8 @@ public void multipleEndpointWatchers() { new LbEndpoint("192.168.0.1", 8080, 2, true)), 1, 0)); - verifyNoInteractions(watcher3); + // TODO(chengyuanzhang): migrate to verifyNoInteractions. + verifyNoMoreInteractions(watcher3); // Management server sends back another EDS response contains ClusterLoadAssignment for the // other requested cluster. @@ -2526,7 +2527,8 @@ public void addRemoveEndpointWatcherWhileInitialResourceFetchInProgress() { assertThat(fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); assertThat(timeoutTask.isCancelled()).isTrue(); - verifyNoInteractions(watcher3, watcher4); + // TODO(chengyuanzhang): migrate to verifyNoInteractions. + verifyNoMoreInteractions(watcher3, watcher4); } @Test @@ -3245,9 +3247,9 @@ public void streamClosedAndRetryReschedulesAllResourceFetchTimer() { @Test public void reportLoadStatsToServer() { String clusterName = "cluster-foo.googleapis.com"; - LoadStatsStore loadStatsStore = new LoadStatsStoreImpl(clusterName, null); ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(null); - xdsClient.reportClientStats(clusterName, null, loadStatsStore); + xdsClient.addClientStats(clusterName, null); + xdsClient.reportClientStats(); LoadReportCall lrsCall = loadReportCalls.poll(); verify(lrsCall.requestObserver).onNext(requestCaptor.capture()); assertThat(requestCaptor.getValue().getClusterStatsCount()) @@ -3263,12 +3265,14 @@ public void reportLoadStatsToServer() { ClusterStats report = Iterables.getOnlyElement(requestCaptor.getValue().getClusterStatsList()); assertThat(report.getClusterName()).isEqualTo(clusterName); - xdsClient.cancelClientStatsReport(clusterName, null); + xdsClient.removeClientStats(clusterName, null); fakeClock.forwardNanos(1000L); verify(lrsCall.requestObserver, times(3)).onNext(requestCaptor.capture()); assertThat(requestCaptor.getValue().getClusterStatsCount()) .isEqualTo(0); // no more stats reported + xdsClient.cancelClientStatsReport(); + assertThat(lrsEnded.get()).isTrue(); // See more test on LoadReportClientTest.java } From 676e5d250960b689cba5ea7f7c39e1875e10de10 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Tue, 11 Aug 2020 13:01:08 -0700 Subject: [PATCH 55/88] core: Remove unused mocks --- .../test/java/io/grpc/internal/DelayedClientTransportTest.java | 2 -- .../src/test/java/io/grpc/util/MutableHandlerRegistryTest.java | 3 --- 2 files changed, 5 deletions(-) diff --git a/core/src/test/java/io/grpc/internal/DelayedClientTransportTest.java b/core/src/test/java/io/grpc/internal/DelayedClientTransportTest.java index 0f51d3f62d8..41a97d62f9a 100644 --- a/core/src/test/java/io/grpc/internal/DelayedClientTransportTest.java +++ b/core/src/test/java/io/grpc/internal/DelayedClientTransportTest.java @@ -46,7 +46,6 @@ import io.grpc.SynchronizationContext; import io.grpc.internal.ClientStreamListener.RpcProgress; import java.util.concurrent.CyclicBarrier; -import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.After; @@ -81,7 +80,6 @@ public class DelayedClientTransportTest { @Mock private ClientStream mockRealStream; @Mock private ClientStream mockRealStream2; @Mock private ClientStreamListener streamListener; - @Mock private Executor mockExecutor; @Captor private ArgumentCaptor statusCaptor; @Captor private ArgumentCaptor listenerCaptor; diff --git a/core/src/test/java/io/grpc/util/MutableHandlerRegistryTest.java b/core/src/test/java/io/grpc/util/MutableHandlerRegistryTest.java index c80d42f8eaa..d499528cf05 100644 --- a/core/src/test/java/io/grpc/util/MutableHandlerRegistryTest.java +++ b/core/src/test/java/io/grpc/util/MutableHandlerRegistryTest.java @@ -60,9 +60,6 @@ public class MutableHandlerRegistryTest { @Mock private ServerCallHandler fewHandler; - @Mock - private ServerCallHandler otherFlowHandler; - private ServerServiceDefinition basicServiceDefinition; private ServerServiceDefinition multiServiceDefinition; From 40b331e8869730a31550cfe600b8994f2a1f7e11 Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Tue, 11 Aug 2020 16:51:38 -0700 Subject: [PATCH 56/88] xds: increase the RPC timeout to 1-sec to avoid deadline exceeding locally at the client and request never reaching server (#7316) --- .../internal/certprovider/MeshCaCertificateProviderTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderTest.java index 7f1f04a7f93..b38ba701a79 100644 --- a/xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderTest.java @@ -97,7 +97,7 @@ public class MeshCaCertificateProviderTest { private static final String ZONE = "us-west2-a"; private static final long START_DELAY = 200_000_000L; // 0.2 seconds private static final long[] DELAY_VALUES = {START_DELAY, START_DELAY * 2, START_DELAY * 4}; - private static final long RPC_TIMEOUT_MILLIS = 100L; + private static final long RPC_TIMEOUT_MILLIS = 1000L; /** * Expire time of cert SERVER_0_PEM_FILE. */ From eb6110cefc1ba96a1a1e1a036867726d60c98523 Mon Sep 17 00:00:00 2001 From: Chengyuan Zhang Date: Wed, 12 Aug 2020 09:50:54 -0700 Subject: [PATCH 57/88] rls, xds: fix parameter comments that do not match the formal parameter name (#7319) --- .../io/grpc/rls/CachingRlsLbClientTest.java | 2 +- .../io/grpc/rls/RlsProtoConvertersTest.java | 2 +- .../io/grpc/rls/RlsRequestFactoryTest.java | 2 +- .../java/io/grpc/xds/XdsClientImplTest.java | 73 +++++++++---------- .../java/io/grpc/xds/XdsClientImplTestV2.java | 73 +++++++++---------- 5 files changed, 75 insertions(+), 77 deletions(-) diff --git a/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java b/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java index 554dcdd6625..c58ff3e210a 100644 --- a/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java +++ b/rls/src/test/java/io/grpc/rls/CachingRlsLbClientTest.java @@ -356,7 +356,7 @@ private static RouteLookupConfig getRouteLookupConfig() { /* lookupServiceTimeoutInMillis= */ TimeUnit.SECONDS.toMillis(2), /* maxAgeInMillis= */ TimeUnit.SECONDS.toMillis(300), /* staleAgeInMillis= */ TimeUnit.SECONDS.toMillis(240), - /* cacheSize= */ 1000, + /* cacheSizeBytes= */ 1000, /* validTargets= */ ImmutableList.of("a valid target"), /* defaultTarget= */ "us_east_1.cloudbigtable.googleapis.com"); } diff --git a/rls/src/test/java/io/grpc/rls/RlsProtoConvertersTest.java b/rls/src/test/java/io/grpc/rls/RlsProtoConvertersTest.java index cb19b3c79f5..2f7d84d6b13 100644 --- a/rls/src/test/java/io/grpc/rls/RlsProtoConvertersTest.java +++ b/rls/src/test/java/io/grpc/rls/RlsProtoConvertersTest.java @@ -195,7 +195,7 @@ public void convert_jsonRlsConfig() throws IOException { /* lookupServiceTimeoutInMillis= */ TimeUnit.SECONDS.toMillis(2), /* maxAgeInMillis= */ TimeUnit.SECONDS.toMillis(300), /* staleAgeInMillis= */ TimeUnit.SECONDS.toMillis(240), - /* cacheSize= */ 1000, + /* cacheSizeBytes= */ 1000, /* validTargets= */ ImmutableList.of("a valid target"), /* defaultTarget= */ "us_east_1.cloudbigtable.googleapis.com"); diff --git a/rls/src/test/java/io/grpc/rls/RlsRequestFactoryTest.java b/rls/src/test/java/io/grpc/rls/RlsRequestFactoryTest.java index 157688b7e46..57d21e4fdaf 100644 --- a/rls/src/test/java/io/grpc/rls/RlsRequestFactoryTest.java +++ b/rls/src/test/java/io/grpc/rls/RlsRequestFactoryTest.java @@ -62,7 +62,7 @@ public class RlsRequestFactoryTest { /* lookupServiceTimeoutInMillis= */ TimeUnit.SECONDS.toMillis(2), /* maxAgeInMillis= */ TimeUnit.SECONDS.toMillis(300), /* staleAgeInMillis= */ TimeUnit.SECONDS.toMillis(240), - /* cacheSize= */ 1000, + /* cacheSizeBytes= */ 1000, /* validTargets= */ ImmutableList.of("a valid target"), /* defaultTarget= */ "us_east_1.cloudbigtable.googleapis.com"); diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java index ec81022c924..e9f8efda60b 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java @@ -744,43 +744,42 @@ public void resolveVirtualHostWithPathMatchingInRdsResponse() { verify(configWatcher).onConfigChanged(configUpdateCaptor.capture()); List routes = configUpdateCaptor.getValue().getRoutes(); assertThat(routes).hasSize(4); - assertThat(routes.get(0)).isEqualTo( - new EnvoyProtoData.Route( - // path match with cluster route - new io.grpc.xds.RouteMatch( - /* prefix= */ null, - /* path= */ "/service1/method1"), - new EnvoyProtoData.RouteAction( - TimeUnit.SECONDS.toNanos(15L), "cl1.googleapis.com", null))); - assertThat(routes.get(1)).isEqualTo( - new EnvoyProtoData.Route( - // path match with weighted cluster route - new io.grpc.xds.RouteMatch( - /* prefix= */ null, - /* path= */ "/service2/method2"), - new EnvoyProtoData.RouteAction( - TimeUnit.SECONDS.toNanos(15L), - null, - ImmutableList.of( - new EnvoyProtoData.ClusterWeight("cl21.googleapis.com", 30), - new EnvoyProtoData.ClusterWeight("cl22.googleapis.com", 70) - )))); - assertThat(routes.get(2)).isEqualTo( - new EnvoyProtoData.Route( - // prefix match with cluster route - new io.grpc.xds.RouteMatch( - /* prefix= */ "/service1/", - /* path= */ null), - new EnvoyProtoData.RouteAction( - TimeUnit.SECONDS.toNanos(15L), "cl1.googleapis.com", null))); - assertThat(routes.get(3)).isEqualTo( - new EnvoyProtoData.Route( - // default match with cluster route - new io.grpc.xds.RouteMatch( - /* prefix= */ "", - /* path= */ null), - new EnvoyProtoData.RouteAction( - TimeUnit.SECONDS.toNanos(15L), "cluster.googleapis.com", null))); + assertThat(routes.get(0)) + .isEqualTo( + new EnvoyProtoData.Route( + // path match with cluster route + new io.grpc.xds.RouteMatch( + /* pathPrefixMatch= */ null,/* pathExactMatch= */ "/service1/method1"), + new EnvoyProtoData.RouteAction( + TimeUnit.SECONDS.toNanos(15L), "cl1.googleapis.com", null))); + assertThat(routes.get(1)) + .isEqualTo( + new EnvoyProtoData.Route( + // path match with weighted cluster route + new io.grpc.xds.RouteMatch( + /* pathPrefixMatch= */ null,/* pathExactMatch= */ "/service2/method2"), + new EnvoyProtoData.RouteAction( + TimeUnit.SECONDS.toNanos(15L), + null, + ImmutableList.of( + new EnvoyProtoData.ClusterWeight("cl21.googleapis.com", 30), + new EnvoyProtoData.ClusterWeight("cl22.googleapis.com", 70))))); + assertThat(routes.get(2)) + .isEqualTo( + new EnvoyProtoData.Route( + // prefix match with cluster route + new io.grpc.xds.RouteMatch( + /* pathPrefixMatch= */ "/service1/",/* pathExactMatch= */ null), + new EnvoyProtoData.RouteAction( + TimeUnit.SECONDS.toNanos(15L), "cl1.googleapis.com", null))); + assertThat(routes.get(3)) + .isEqualTo( + new EnvoyProtoData.Route( + // default match with cluster route + new io.grpc.xds.RouteMatch( + /* pathPrefixMatch= */ "",/* pathExactMatch= */ null), + new EnvoyProtoData.RouteAction( + TimeUnit.SECONDS.toNanos(15L), "cluster.googleapis.com", null))); } /** diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java index fcd268e0193..b1faa4e3c33 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java @@ -745,43 +745,42 @@ public void resolveVirtualHostWithPathMatchingInRdsResponse() { verify(configWatcher).onConfigChanged(configUpdateCaptor.capture()); List routes = configUpdateCaptor.getValue().getRoutes(); assertThat(routes).hasSize(4); - assertThat(routes.get(0)).isEqualTo( - new EnvoyProtoData.Route( - // path match with cluster route - new io.grpc.xds.RouteMatch( - /* prefix= */ null, - /* path= */ "/service1/method1"), - new EnvoyProtoData.RouteAction( - TimeUnit.SECONDS.toNanos(15L), "cl1.googleapis.com", null))); - assertThat(routes.get(1)).isEqualTo( - new EnvoyProtoData.Route( - // path match with weighted cluster route - new io.grpc.xds.RouteMatch( - /* prefix= */ null, - /* path= */ "/service2/method2"), - new EnvoyProtoData.RouteAction( - TimeUnit.SECONDS.toNanos(15L), - null, - ImmutableList.of( - new EnvoyProtoData.ClusterWeight("cl21.googleapis.com", 30), - new EnvoyProtoData.ClusterWeight("cl22.googleapis.com", 70) - )))); - assertThat(routes.get(2)).isEqualTo( - new EnvoyProtoData.Route( - // prefix match with cluster route - new io.grpc.xds.RouteMatch( - /* prefix= */ "/service1/", - /* path= */ null), - new EnvoyProtoData.RouteAction( - TimeUnit.SECONDS.toNanos(15L), "cl1.googleapis.com", null))); - assertThat(routes.get(3)).isEqualTo( - new EnvoyProtoData.Route( - // default match with cluster route - new io.grpc.xds.RouteMatch( - /* prefix= */ "", - /* path= */ null), - new EnvoyProtoData.RouteAction( - TimeUnit.SECONDS.toNanos(15L), "cluster.googleapis.com", null))); + assertThat(routes.get(0)) + .isEqualTo( + new EnvoyProtoData.Route( + // path match with cluster route + new io.grpc.xds.RouteMatch( + /* pathPrefixMatch= */ null,/* pathExactMatch= */ "/service1/method1"), + new EnvoyProtoData.RouteAction( + TimeUnit.SECONDS.toNanos(15L), "cl1.googleapis.com", null))); + assertThat(routes.get(1)) + .isEqualTo( + new EnvoyProtoData.Route( + // path match with weighted cluster route + new io.grpc.xds.RouteMatch( + /* pathPrefixMatch= */ null,/* pathExactMatch= */ "/service2/method2"), + new EnvoyProtoData.RouteAction( + TimeUnit.SECONDS.toNanos(15L), + null, + ImmutableList.of( + new EnvoyProtoData.ClusterWeight("cl21.googleapis.com", 30), + new EnvoyProtoData.ClusterWeight("cl22.googleapis.com", 70))))); + assertThat(routes.get(2)) + .isEqualTo( + new EnvoyProtoData.Route( + // prefix match with cluster route + new io.grpc.xds.RouteMatch( + /* pathPrefixMatch= */ "/service1/",/* pathExactMatch= */ null), + new EnvoyProtoData.RouteAction( + TimeUnit.SECONDS.toNanos(15L), "cl1.googleapis.com", null))); + assertThat(routes.get(3)) + .isEqualTo( + new EnvoyProtoData.Route( + // default match with cluster route + new io.grpc.xds.RouteMatch( + /* pathPrefixMatch= */ "",/* pathExactMatch= */ null), + new EnvoyProtoData.RouteAction( + TimeUnit.SECONDS.toNanos(15L), "cluster.googleapis.com", null))); } /** From 7bcfb59ff44fbcb761ffe3284841eeb21a310bf2 Mon Sep 17 00:00:00 2001 From: Chengyuan Zhang Date: Wed, 12 Aug 2020 09:51:50 -0700 Subject: [PATCH 58/88] xds: replace verifyNoMoreInteractions with verifyNoInteractions (#7320) --- xds/src/test/java/io/grpc/xds/XdsClientImplTest.java | 10 ++++------ xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java | 10 ++++------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java index e9f8efda60b..d2cf73ab71c 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java @@ -38,6 +38,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -1864,8 +1865,7 @@ public void addRemoveClusterWatcherWhileInitialResourceFetchInProgress() { assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); assertThat(timeoutTask.isCancelled()).isTrue(); - // TODO(chengyuanzhang): migrate to verifyNoInteractions. - verifyNoMoreInteractions(watcher3, watcher4); + verifyNoInteractions(watcher3, watcher4); } @Test @@ -2132,8 +2132,7 @@ public void multipleEndpointWatchers() { new LbEndpoint("192.168.0.1", 8080, 2, true)), 1, 0)); - // TODO(chengyuanzhang): migrate to verifyNoInteractions. - verifyNoMoreInteractions(watcher3); + verifyNoInteractions(watcher3); // Management server sends back another EDS response contains ClusterLoadAssignment for the // other requested cluster. @@ -2517,8 +2516,7 @@ public void addRemoveEndpointWatcherWhileInitialResourceFetchInProgress() { assertThat(fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); assertThat(timeoutTask.isCancelled()).isTrue(); - // TODO(chengyuanzhang): migrate to verifyNoInteractions. - verifyNoMoreInteractions(watcher3, watcher4); + verifyNoInteractions(watcher3, watcher4); } @Test diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java index b1faa4e3c33..9a9171cfac7 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java @@ -38,6 +38,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -1873,8 +1874,7 @@ public void addRemoveClusterWatcherWhileInitialResourceFetchInProgress() { assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); assertThat(timeoutTask.isCancelled()).isTrue(); - // TODO(chengyuanzhang): migrate to verifyNoInteractions. - verifyNoMoreInteractions(watcher3, watcher4); + verifyNoInteractions(watcher3, watcher4); } @Test @@ -2141,8 +2141,7 @@ public void multipleEndpointWatchers() { new LbEndpoint("192.168.0.1", 8080, 2, true)), 1, 0)); - // TODO(chengyuanzhang): migrate to verifyNoInteractions. - verifyNoMoreInteractions(watcher3); + verifyNoInteractions(watcher3); // Management server sends back another EDS response contains ClusterLoadAssignment for the // other requested cluster. @@ -2526,8 +2525,7 @@ public void addRemoveEndpointWatcherWhileInitialResourceFetchInProgress() { assertThat(fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); assertThat(timeoutTask.isCancelled()).isTrue(); - // TODO(chengyuanzhang): migrate to verifyNoInteractions. - verifyNoMoreInteractions(watcher3, watcher4); + verifyNoInteractions(watcher3, watcher4); } @Test From e19bdf33b1d647c282169bc32a98362d53dd340b Mon Sep 17 00:00:00 2001 From: Patrice Chalin Date: Wed, 5 Aug 2020 11:46:03 -0400 Subject: [PATCH 59/88] README: link to v1.31.0 example directories Followup to #7267. The rest of the README page refers to v1.31.0, so it would seem reasonable to link to the example folders of v1.31.0 too -- rather than v1.30.0. @ejona86 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f9c504d03f8..f3fa13fb0cb 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,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.30.0/examples) and the -[Android example](https://github.com/grpc/grpc-java/tree/v1.30.0/examples/android) +The [examples](https://github.com/grpc/grpc-java/tree/v1.31.0/examples) and the +[Android example](https://github.com/grpc/grpc-java/tree/v1.31.0/examples/android) are standalone projects that showcase the usage of gRPC. Download From 6eced95a547be94f6cce31b4af9d28d48bd3f208 Mon Sep 17 00:00:00 2001 From: wanyingd1996 <68657274+wanyingd1996@users.noreply.github.com> Date: Wed, 12 Aug 2020 15:29:30 -0700 Subject: [PATCH 60/88] api: removed deprecated method blockingExecutor (#7242) --- api/src/main/java/io/grpc/ForwardingChannelBuilder.java | 7 ------- api/src/main/java/io/grpc/ManagedChannelBuilder.java | 6 ------ 2 files changed, 13 deletions(-) diff --git a/api/src/main/java/io/grpc/ForwardingChannelBuilder.java b/api/src/main/java/io/grpc/ForwardingChannelBuilder.java index ca53b4df4c8..612779a7848 100644 --- a/api/src/main/java/io/grpc/ForwardingChannelBuilder.java +++ b/api/src/main/java/io/grpc/ForwardingChannelBuilder.java @@ -77,13 +77,6 @@ public T offloadExecutor(Executor executor) { return thisT(); } - @Deprecated - @Override - public T blockingExecutor(Executor executor) { - delegate().blockingExecutor(executor); - return thisT(); - } - @Override public T intercept(List interceptors) { delegate().intercept(interceptors); diff --git a/api/src/main/java/io/grpc/ManagedChannelBuilder.java b/api/src/main/java/io/grpc/ManagedChannelBuilder.java index 0f915f9a341..e0c87d30715 100644 --- a/api/src/main/java/io/grpc/ManagedChannelBuilder.java +++ b/api/src/main/java/io/grpc/ManagedChannelBuilder.java @@ -124,12 +124,6 @@ public T offloadExecutor(Executor executor) { throw new UnsupportedOperationException(); } - @Deprecated - @ExperimentalApi("https://github.com/grpc/grpc-java/issues/6279") - public T blockingExecutor(Executor executor) { - return offloadExecutor(executor); - } - /** * Adds interceptors that will be called before the channel performs its real work. This is * functionally equivalent to using {@link ClientInterceptors#intercept(Channel, List)}, but while From 0773c1aa0577e16e620b553f99690067d4e0e728 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 13 Aug 2020 07:39:59 -0700 Subject: [PATCH 61/88] interop-testing: Remove comparison of proto field to null Proto fields aren't null. The getter will return an empty string instead of null. cl/326415191 --- .../testing/integration/GrpclbLongLivedAffinityTestClient.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/GrpclbLongLivedAffinityTestClient.java b/interop-testing/src/main/java/io/grpc/testing/integration/GrpclbLongLivedAffinityTestClient.java index b98e4f67d38..efda4b99868 100644 --- a/interop-testing/src/main/java/io/grpc/testing/integration/GrpclbLongLivedAffinityTestClient.java +++ b/interop-testing/src/main/java/io/grpc/testing/integration/GrpclbLongLivedAffinityTestClient.java @@ -16,8 +16,6 @@ package io.grpc.testing.integration; -import static com.google.common.base.Preconditions.checkNotNull; - import com.google.protobuf.ByteString; import io.grpc.ManagedChannel; import io.grpc.StatusRuntimeException; @@ -175,7 +173,6 @@ private void run() throws Exception { blockingStub.withDeadlineAfter(1, TimeUnit.MINUTES).unaryCall(request); logger.info("Received response"); String serverId = response.getServerId(); - checkNotNull(serverId, "serverId is null"); if (lastServerId != null && !lastServerId.equals(serverId)) { String msg = "Expected serverId " + lastServerId + ", but got " + serverId; logger.warning(msg + ". affinityBreakageBudget=" + affinityBreakageBudget); From 4185081fd6c80cbd9c4f6de8e7fc202b8add5f2a Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Wed, 12 Aug 2020 10:21:56 -0700 Subject: [PATCH 62/88] Update README etc to reference 1.31.1 --- README.md | 28 ++++++++++++------------ cronet/README.md | 2 +- documentation/android-channel-builder.md | 4 ++-- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index f3fa13fb0cb..164145d8c01 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,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.31.0/examples) and the -[Android example](https://github.com/grpc/grpc-java/tree/v1.31.0/examples/android) +The [examples](https://github.com/grpc/grpc-java/tree/v1.31.1/examples) and the +[Android example](https://github.com/grpc/grpc-java/tree/v1.31.1/examples/android) are standalone projects that showcase the usage of gRPC. Download @@ -42,17 +42,17 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`: io.grpc grpc-netty-shaded - 1.31.0 + 1.31.1 io.grpc grpc-protobuf - 1.31.0 + 1.31.1 io.grpc grpc-stub - 1.31.0 + 1.31.1 org.apache.tomcat @@ -64,23 +64,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 -implementation 'io.grpc:grpc-netty-shaded:1.31.0' -implementation 'io.grpc:grpc-protobuf:1.31.0' -implementation 'io.grpc:grpc-stub:1.31.0' +implementation 'io.grpc:grpc-netty-shaded:1.31.1' +implementation 'io.grpc:grpc-protobuf:1.31.1' +implementation 'io.grpc:grpc-stub:1.31.1' compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+ ``` For Android client, use `grpc-okhttp` instead of `grpc-netty-shaded` and `grpc-protobuf-lite` instead of `grpc-protobuf`: ```gradle -implementation 'io.grpc:grpc-okhttp:1.31.0' -implementation 'io.grpc:grpc-protobuf-lite:1.31.0' -implementation 'io.grpc:grpc-stub:1.31.0' +implementation 'io.grpc:grpc-okhttp:1.31.1' +implementation 'io.grpc:grpc-protobuf-lite:1.31.1' +implementation 'io.grpc:grpc-stub:1.31.1' compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+ ``` [the JARs]: -https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.31.0 +https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.31.1 Development snapshots are available in [Sonatypes's snapshot repository](https://oss.sonatype.org/content/repositories/snapshots/). @@ -112,7 +112,7 @@ For protobuf-based codegen integrated with the Maven build system, you can use com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier} grpc-java - io.grpc:protoc-gen-grpc-java:1.31.0:exe:${os.detected.classifier} + io.grpc:protoc-gen-grpc-java:1.31.1:exe:${os.detected.classifier} @@ -142,7 +142,7 @@ protobuf { } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.31.0' + artifact = 'io.grpc:protoc-gen-grpc-java:1.31.1' } } generateProtoTasks { diff --git a/cronet/README.md b/cronet/README.md index 8115c0caa81..5cfe8858bd6 100644 --- a/cronet/README.md +++ b/cronet/README.md @@ -26,7 +26,7 @@ In your app module's `build.gradle` file, include a dependency on both `grpc-cro Google Play Services Client Library for Cronet ``` -implementation 'io.grpc:grpc-cronet:1.31.0' +implementation 'io.grpc:grpc-cronet:1.31.1' implementation 'com.google.android.gms:play-services-cronet:16.0.0' ``` diff --git a/documentation/android-channel-builder.md b/documentation/android-channel-builder.md index 03e67c904df..9e6e233806f 100644 --- a/documentation/android-channel-builder.md +++ b/documentation/android-channel-builder.md @@ -36,8 +36,8 @@ In your `build.gradle` file, include a dependency on both `grpc-android` and `grpc-okhttp`: ``` -implementation 'io.grpc:grpc-android:1.31.0' -implementation 'io.grpc:grpc-okhttp:1.31.0' +implementation 'io.grpc:grpc-android:1.31.1' +implementation 'io.grpc:grpc-okhttp:1.31.1' ``` You also need permission to access the device's network state in your From 6593fc8d356ac01c817a6e9e13abfd395b20e3ed Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Thu, 13 Aug 2020 10:35:19 -0700 Subject: [PATCH 63/88] RELEASING.md: Add missing repo+branch for gh-pages git pull --- RELEASING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASING.md b/RELEASING.md index 8d3a8b14fe4..897e8c2bf72 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -225,7 +225,7 @@ Now we need to update gh-pages with the new Javadoc: ```bash git checkout gh-pages -git pull --ff-only +git pull --ff-only upstream gh-pages rm -r javadoc/ wget -O grpc-all-javadoc.jar "http://search.maven.org/remotecontent?filepath=io/grpc/grpc-all/$MAJOR.$MINOR.$PATCH/grpc-all-$MAJOR.$MINOR.$PATCH-javadoc.jar" unzip -d javadoc grpc-all-javadoc.jar From cd0cc95553778cb640666ee50bdf37ae2ff10a8f Mon Sep 17 00:00:00 2001 From: cindyxue <32377977+cindyxue@users.noreply.github.com> Date: Thu, 13 Aug 2020 16:08:35 -0700 Subject: [PATCH 64/88] xds: Added a CEL-based Authorization Engine (#7191) * xds: add a CEL-based authorization engine that uses the mock CEL library --- .../rbac/engine/AuthorizationDecision.java | 91 ++++ .../rbac/engine/AuthorizationEngine.java | 214 ++++++++ .../internal/rbac/engine/EvaluateArgs.java | 123 +++++ .../engine/AuthzEngineEvaluationTest.java | 475 ++++++++++++++++++ .../internal/rbac/engine/AuthzEngineTest.java | 141 ++++++ .../rbac/engine/EvaluateArgsTest.java | 133 +++++ 6 files changed, 1177 insertions(+) create mode 100644 xds/src/main/java/io/grpc/xds/internal/rbac/engine/AuthorizationDecision.java create mode 100644 xds/src/main/java/io/grpc/xds/internal/rbac/engine/AuthorizationEngine.java create mode 100644 xds/src/main/java/io/grpc/xds/internal/rbac/engine/EvaluateArgs.java create mode 100644 xds/src/test/java/io/grpc/xds/internal/rbac/engine/AuthzEngineEvaluationTest.java create mode 100644 xds/src/test/java/io/grpc/xds/internal/rbac/engine/AuthzEngineTest.java create mode 100644 xds/src/test/java/io/grpc/xds/internal/rbac/engine/EvaluateArgsTest.java diff --git a/xds/src/main/java/io/grpc/xds/internal/rbac/engine/AuthorizationDecision.java b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/AuthorizationDecision.java new file mode 100644 index 00000000000..474ff7c180f --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/AuthorizationDecision.java @@ -0,0 +1,91 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds.internal.rbac.engine; + +import com.google.common.collect.ImmutableList; +import java.lang.StringBuilder; +import java.util.List; + +/** + * The AuthorizationDecision class holds authorization decision + * returned by CEL-based Authorization Engine. + */ +public class AuthorizationDecision { + /** Output represents the possible decisions generated by CEL-based Authorization Engine.*/ + public enum Output { + /** + * ALLOW indicates that CEL Evaluate Engine + * had authorized the gRPC call and allowed the gRPC call to go through. + */ + ALLOW, + /** + * DENY indicates that CEL Evaluate Engine + * had authorized the gRPC call and denied the gRPC call from going through. + */ + DENY, + /** + * UNKNOWN indicates that CEL Evaluate Engine + * did not have enough information to authorize the gRPC call. + * */ + UNKNOWN, + } + + private final Output decision; + private final ImmutableList policyNames; + + /** + * Creates a new authorization decision using the input {@code decision} + * for resolving authorization decision + * and {@code policyNames} for resolving authorization context. + */ + public AuthorizationDecision(Output decision, List policyNames) { + this.decision = decision; + this.policyNames = ImmutableList.copyOf(policyNames); + } + + /** Returns the authorization decision. */ + public Output getDecision() { + return this.decision; + } + + /** Returns the policy list. */ + public ImmutableList getPolicyNames() { + return this.policyNames; + } + + @Override + public String toString() { + StringBuilder authzStr = new StringBuilder(); + switch (this.decision) { + case ALLOW: + authzStr.append("Authorization Decision: ALLOW. \n"); + break; + case DENY: + authzStr.append("Authorization Decision: DENY. \n"); + break; + case UNKNOWN: + authzStr.append("Authorization Decision: UNKNOWN. \n"); + break; + default: + break; + } + for (String policyName : this.policyNames) { + authzStr.append(policyName + "; \n"); + } + return authzStr.toString(); + } +} diff --git a/xds/src/main/java/io/grpc/xds/internal/rbac/engine/AuthorizationEngine.java b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/AuthorizationEngine.java new file mode 100644 index 00000000000..bb3005883f4 --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/AuthorizationEngine.java @@ -0,0 +1,214 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds.internal.rbac.engine; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.api.expr.v1alpha1.Expr; +import com.google.common.collect.ImmutableMap; +import com.google.protobuf.Descriptors.Descriptor; +import io.envoyproxy.envoy.config.rbac.v2.Policy; +import io.envoyproxy.envoy.config.rbac.v2.RBAC; +import io.envoyproxy.envoy.config.rbac.v2.RBAC.Action; +import io.grpc.xds.internal.rbac.engine.cel.Activation; +import io.grpc.xds.internal.rbac.engine.cel.DefaultDispatcher; +import io.grpc.xds.internal.rbac.engine.cel.DefaultInterpreter; +import io.grpc.xds.internal.rbac.engine.cel.DescriptorMessageProvider; +import io.grpc.xds.internal.rbac.engine.cel.Dispatcher; +import io.grpc.xds.internal.rbac.engine.cel.IncompleteData; +import io.grpc.xds.internal.rbac.engine.cel.Interpretable; +import io.grpc.xds.internal.rbac.engine.cel.Interpreter; +import io.grpc.xds.internal.rbac.engine.cel.InterpreterException; +import io.grpc.xds.internal.rbac.engine.cel.RuntimeTypeProvider; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * CEL-based Authorization Engine is part of the authorization framework in gRPC. + * CEL-based Authorization Engine takes one or two Envoy RBAC policies as input + * and uses CEL library to evaluate the condition field + * inside each RBAC policy based on the provided Envoy Attributes. + * CEL-based Authorization Engine will generate an authorization decision which + * could be ALLOW, DENY or UNKNOWN. + * + *

Use as in: + * + *

+ *  AuthorizationEngine engine = new AuthorizationEngine(rbacPolicy);
+ *  AuthorizationDecision result = engine.evaluate(new EvaluateArgs(call, headers));
+ * 
+ */ +public class AuthorizationEngine { + /** + * RbacEngine is an inner class that holds RBAC action + * and a list of conditions in RBAC policy. + */ + private static class RbacEngine { + @SuppressWarnings("UnusedVariable") + private final Action action; + private final ImmutableMap conditions; + + public RbacEngine(Action action, ImmutableMap conditions) { + this.action = checkNotNull(action); + this.conditions = checkNotNull(conditions); + } + } + + private static final Logger log = Logger.getLogger(AuthorizationEngine.class.getName()); + private final RbacEngine allowEngine; + private final RbacEngine denyEngine; + + /** + * Creates a CEL-based Authorization Engine from one Envoy RBAC policy. + * @param rbacPolicy input Envoy RBAC policy. + */ + public AuthorizationEngine(RBAC rbacPolicy) { + Map conditions = new LinkedHashMap<>(); + for (Map.Entry policy: rbacPolicy.getPolicies().entrySet()) { + conditions.put(policy.getKey(), policy.getValue().getCondition()); + } + allowEngine = (rbacPolicy.getAction() == Action.ALLOW) + ? new RbacEngine(Action.ALLOW, ImmutableMap.copyOf(conditions)) : null; + denyEngine = (rbacPolicy.getAction() == Action.DENY) + ? new RbacEngine(Action.DENY, ImmutableMap.copyOf(conditions)) : null; + } + + /** + * Creates a CEL-based Authorization Engine from two Envoy RBAC policies. + * When it takes two RBAC policies, + * the order has to be a DENY policy followed by an ALLOW policy. + * @param denyPolicy input Envoy RBAC policy with DENY action. + * @param allowPolicy input Envoy RBAC policy with ALLOW action. + * @throws IllegalArgumentException if the user inputs an invalid RBAC list. + */ + public AuthorizationEngine(RBAC denyPolicy, RBAC allowPolicy) throws IllegalArgumentException { + checkArgument( + denyPolicy.getAction() == Action.DENY && allowPolicy.getAction() == Action.ALLOW, + "Invalid RBAC list, " + + "must provide a RBAC with DENY action followed by a RBAC with ALLOW action. "); + Map denyConditions = new LinkedHashMap<>(); + for (Map.Entry policy: denyPolicy.getPolicies().entrySet()) { + denyConditions.put(policy.getKey(), policy.getValue().getCondition()); + } + denyEngine = new RbacEngine(Action.DENY, ImmutableMap.copyOf(denyConditions)); + Map allowConditions = new LinkedHashMap<>(); + for (Map.Entry policy: allowPolicy.getPolicies().entrySet()) { + allowConditions.put(policy.getKey(), policy.getValue().getCondition()); + } + allowEngine = new RbacEngine(Action.ALLOW, ImmutableMap.copyOf(allowConditions)); + } + + /** + * The evaluate function performs the core authorization mechanism + * of CEL-based Authorization Engine. + * It determines whether a gRPC call is allowed, denied, or unable to be decided. + * @param args evaluate argument that is used to evaluate the RBAC conditions. + * @return an AuthorizationDecision generated by CEL-based Authorization Engine. + */ + public AuthorizationDecision evaluate(EvaluateArgs args) { + List unknownPolicyNames = new ArrayList<>(); + // Set up activation used in CEL library's eval function. + Activation activation = Activation.copyOf(args.generateEnvoyAttributes()); + // Iterate through denyEngine's map. + // If there is match, immediately return DENY. + // If there are unknown conditions, return UNKNOWN. + // If all non-match, then iterate through allowEngine. + if (denyEngine != null) { + AuthorizationDecision authzDecision = evaluateEngine(denyEngine.conditions.entrySet(), + AuthorizationDecision.Output.DENY, unknownPolicyNames, activation); + if (authzDecision != null) { + return authzDecision; + } + if (unknownPolicyNames.size() > 0) { + return new AuthorizationDecision( + AuthorizationDecision.Output.UNKNOWN, unknownPolicyNames); + } + } + // Once we enter allowEngine, if there is a match, immediately return ALLOW. + // In the end of iteration, if there are unknown conditions, return UNKNOWN. + // If all non-match, return DENY. + if (allowEngine != null) { + AuthorizationDecision authzDecision = evaluateEngine(allowEngine.conditions.entrySet(), + AuthorizationDecision.Output.ALLOW, unknownPolicyNames, activation); + if (authzDecision != null) { + return authzDecision; + } + if (unknownPolicyNames.size() > 0) { + return new AuthorizationDecision( + AuthorizationDecision.Output.UNKNOWN, unknownPolicyNames); + } + } + // Return ALLOW if it only has a denyEngine and it’s unmatched. + if (this.allowEngine == null && this.denyEngine != null) { + return new AuthorizationDecision( + AuthorizationDecision.Output.ALLOW, new ArrayList()); + } + // Return DENY if none of denyEngine and allowEngine matched, + // or the single allowEngine is unmatched when there is only one allowEngine. + return new AuthorizationDecision(AuthorizationDecision.Output.DENY, new ArrayList()); + } + + /** Evaluate a single RbacEngine. */ + protected AuthorizationDecision evaluateEngine(Set> entrySet, + AuthorizationDecision.Output decision, List unknownPolicyNames, + Activation activation) { + for (Map.Entry condition : entrySet) { + try { + if (matches(condition.getValue(), activation)) { + return new AuthorizationDecision(decision, + new ArrayList(Arrays.asList(new String[] {condition.getKey()}))); + } + } catch (InterpreterException e) { + unknownPolicyNames.add(condition.getKey()); + } + } + return null; + } + + /** Evaluate if a condition matches the given Enovy Attributes using CEL library. */ + protected boolean matches(Expr condition, Activation activation) throws InterpreterException { + // Set up interpretable used in CEL library's eval function. + List descriptors = new ArrayList<>(); + RuntimeTypeProvider messageProvider = DescriptorMessageProvider.dynamicMessages(descriptors); + Dispatcher dispatcher = DefaultDispatcher.create(); + Interpreter interpreter = new DefaultInterpreter(messageProvider, dispatcher); + Interpretable interpretable = interpreter.createInterpretable(condition); + // Parse the generated result object to a boolean variable. + try { + Object result = interpretable.eval(activation); + if (result instanceof Boolean) { + return Boolean.valueOf(result.toString()); + } + // Throw an InterpreterException if there are missing Envoy Attributes. + if (result instanceof IncompleteData) { + throw new InterpreterException.Builder("Envoy Attributes gotten are incomplete.").build(); + } + } catch (InterpreterException e) { + // If any InterpreterExceptions are catched, throw it and log the error. + log.log(Level.WARNING, e.toString(), e); + throw e; + } + return false; + } +} diff --git a/xds/src/main/java/io/grpc/xds/internal/rbac/engine/EvaluateArgs.java b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/EvaluateArgs.java new file mode 100644 index 00000000000..e8ed1828a22 --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/internal/rbac/engine/EvaluateArgs.java @@ -0,0 +1,123 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds.internal.rbac.engine; + +import com.google.common.collect.ImmutableMap; +import io.grpc.Grpc; +import io.grpc.Metadata; +import io.grpc.ServerCall; + +/** The EvaluateArgs class holds evaluate arguments used in CEL-based Authorization Engine. */ +public class EvaluateArgs { + private Metadata headers; + private ServerCall call; + + /** + * Creates a new EvaluateArgs using the input {@code headers} for resolving headers + * and {@code call} for resolving gRPC call. + */ + public EvaluateArgs(Metadata headers, ServerCall call) { + this.headers = headers; + this.call = call; + } + + /** Extract the request.url_path field. */ + protected String getRequestUrlPath() { + String requestUrlPath = this.call.getMethodDescriptor().getFullMethodName(); + return requestUrlPath; + } + + /** Extract the request.host field. */ + protected String getRequestHost() { + String requestHost = this.call.getAuthority(); + return requestHost; + } + + /** Extract the request.method field. */ + protected String getRequestMethod() { + // TODO(@zhenlian): confirm extraction for request.method. + String requestMethod = this.call.getMethodDescriptor().getServiceName(); + return requestMethod; + } + + /** Extract the request.headers field. */ + protected Metadata getRequestHeaders() { + // TODO(@zhenlian): convert request.headers from Metadata to a String Map. + Metadata requestHeaders = this.headers; + return requestHeaders; + } + + /** Extract the source.address field. */ + protected String getSourceAddress() { + String sourceAddress = + this.call.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR).toString(); + return sourceAddress; + } + + /** Extract the source.port field. */ + protected int getSourcePort() { + // TODO(@zhenlian): fill out extraction for source.port. + int sourcePort = 0; + return sourcePort; + } + + /** Extract the destination.address field. */ + protected String getDestinationAddress() { + String destinationAddress = + this.call.getAttributes().get(Grpc.TRANSPORT_ATTR_LOCAL_ADDR).toString(); + return destinationAddress; + } + + /** Extract the destination.port field. */ + protected int getDestinationPort() { + // TODO(@zhenlian): fill out extraction for destination.port. + int destinationPort = 0; + return destinationPort; + } + + /** Extract the connection.uri_san_peer_certificate field. */ + protected String getConnectionUriSanPeerCertificate() { + // TODO(@zhenlian): fill out extraction for connection.uri_san_peer_certificate. + String connectionUriSanPeerCertificate = "placeholder"; + return connectionUriSanPeerCertificate; + } + + /** Extract the source.principal field. */ + protected String getSourcePrincipal() { + // TODO(@zhenlian): fill out extraction for source.principal. + String sourcePrincipal = "placeholder"; + return sourcePrincipal; + } + + /** Extract Envoy Attributes from EvaluateArgs. */ + public ImmutableMap generateEnvoyAttributes() { + ImmutableMap attributes = ImmutableMap.builder() + .put("request.url_path", this.getRequestUrlPath()) + .put("request.host", this.getRequestHost()) + .put("request.method", this.getRequestMethod()) + .put("request.headers", this.getRequestHeaders()) + .put("source.address", this.getSourceAddress()) + .put("source.port", this.getSourcePort()) + .put("destination.address", this.getDestinationAddress()) + .put("destination.port", this.getDestinationPort()) + .put("connection.uri_san_peer_certificate", + this.getConnectionUriSanPeerCertificate()) + .put("source.principal", this.getSourcePrincipal()) + .build(); + return attributes; + } +} diff --git a/xds/src/test/java/io/grpc/xds/internal/rbac/engine/AuthzEngineEvaluationTest.java b/xds/src/test/java/io/grpc/xds/internal/rbac/engine/AuthzEngineEvaluationTest.java new file mode 100644 index 00000000000..61172d576c0 --- /dev/null +++ b/xds/src/test/java/io/grpc/xds/internal/rbac/engine/AuthzEngineEvaluationTest.java @@ -0,0 +1,475 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds.internal.rbac.engine; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; + +import com.google.api.expr.v1alpha1.Expr; +import com.google.api.expr.v1alpha1.Expr.Ident; +import com.google.common.collect.ImmutableMap; +import io.envoyproxy.envoy.config.rbac.v2.Policy; +import io.envoyproxy.envoy.config.rbac.v2.RBAC; +import io.envoyproxy.envoy.config.rbac.v2.RBAC.Action; +import io.grpc.xds.internal.rbac.engine.cel.Activation; +import io.grpc.xds.internal.rbac.engine.cel.InterpreterException; +import java.lang.StringBuilder; +import java.util.Map; +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.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +/** Unit tests for evaluate function of CEL Evaluation Engine. */ +@RunWith(JUnit4.class) +public class AuthzEngineEvaluationTest { + @Rule + public final MockitoRule mocks = MockitoJUnit.rule(); + + @Mock + private EvaluateArgs args; + + @Mock + private Activation activation; + + @Mock + private Map attributes; + + private AuthorizationEngine engine; + private AuthorizationEngine spyEngine; + private AuthorizationDecision evaluateResult; + + // Mock RBAC engine with ALLOW action. + private RBAC rbacAllow; + // Mock RBAC engine with DENY action. + private RBAC rbacDeny; + + // Mock policies that will be used to construct RBAC Engine. + private Policy policy1; + private Policy policy2; + private Policy policy3; + private Policy policy4; + private Policy policy5; + private Policy policy6; + + // Mock conditions that will be used to construct RBAC poilcies. + private Expr condition1; + private Expr condition2; + private Expr condition3; + private Expr condition4; + private Expr condition5; + private Expr condition6; + + @Before + public void buildRbac() { + // Set up RBAC conditions. + condition1 = Expr.newBuilder() + .setIdentExpr(Ident.newBuilder().setName("Condition 1").build()) + .build(); + condition2 = Expr.newBuilder() + .setIdentExpr(Ident.newBuilder().setName("Condition 2").build()) + .build(); + condition3 = Expr.newBuilder() + .setIdentExpr(Ident.newBuilder().setName("Condition 3").build()) + .build(); + condition4 = Expr.newBuilder() + .setIdentExpr(Ident.newBuilder().setName("Condition 4").build()) + .build(); + condition5 = Expr.newBuilder() + .setIdentExpr(Ident.newBuilder().setName("Condition 5").build()) + .build(); + condition6 = Expr.newBuilder() + .setIdentExpr(Ident.newBuilder().setName("Condition 6").build()) + .build(); + // Set up RBAC policies. + policy1 = Policy.newBuilder().setCondition(condition1).build(); + policy2 = Policy.newBuilder().setCondition(condition2).build(); + policy3 = Policy.newBuilder().setCondition(condition3).build(); + policy4 = Policy.newBuilder().setCondition(condition4).build(); + policy5 = Policy.newBuilder().setCondition(condition5).build(); + policy6 = Policy.newBuilder().setCondition(condition6).build(); + // Set up RBACs. + rbacAllow = RBAC.newBuilder() + .setAction(Action.ALLOW) + .putPolicies("Policy 1", policy1) + .putPolicies("Policy 2", policy2) + .putPolicies("Policy 3", policy3) + .build(); + rbacDeny = RBAC.newBuilder() + .setAction(Action.DENY) + .putPolicies("Policy 4", policy4) + .putPolicies("Policy 5", policy5) + .putPolicies("Policy 6", policy6) + .build(); + } + + /** Build an ALLOW engine from Policy 1, 2, 3. */ + @Before + public void setupEngineSingleRbacAllow() { + buildRbac(); + engine = new AuthorizationEngine(rbacAllow); + spyEngine = Mockito.spy(engine); + doReturn(ImmutableMap.copyOf(attributes)).when(args).generateEnvoyAttributes(); + } + + /** Build a DENY engine from Policy 4, 5, 6. */ + @Before + public void setupEngineSingleRbacDeny() { + buildRbac(); + engine = new AuthorizationEngine(rbacDeny); + spyEngine = Mockito.spy(engine); + doReturn(ImmutableMap.copyOf(attributes)).when(args).generateEnvoyAttributes(); + } + + /** Build a pair of engines with a DENY engine followed by an ALLOW engine. */ + @Before + public void setupEngineRbacPair() { + buildRbac(); + engine = new AuthorizationEngine(rbacDeny, rbacAllow); + spyEngine = Mockito.spy(engine); + doReturn(ImmutableMap.copyOf(attributes)).when(args).generateEnvoyAttributes(); + } + + /** + * Test on the ALLOW engine. + * The evaluation result of all the CEL expressions is set to true, + * so the gRPC authorization returns ALLOW. + */ + @Test + public void testAllowEngineWithAllMatchedPolicies() throws InterpreterException { + setupEngineSingleRbacAllow(); + // Policy 1 - matched; Policy 2 - matched; Policy 3 - matched + doReturn(true).when(spyEngine).matches(eq(condition1), any(Activation.class)); + doReturn(true).when(spyEngine).matches(eq(condition2), any(Activation.class)); + doReturn(true).when(spyEngine).matches(eq(condition3), any(Activation.class)); + evaluateResult = spyEngine.evaluate(args); + assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.ALLOW); + assertEquals(evaluateResult.getPolicyNames().size(), 1); + assertTrue(evaluateResult.getPolicyNames().contains("Policy 1")); + } + + /** + * Test on the ALLOW engine. + * The evaluation result of all the CEL expressions is set to false, + * so the gRPC authorization returns DENY. + */ + @Test + public void testAllowEngineWithAllUnmatchedPolicies() throws InterpreterException { + setupEngineSingleRbacAllow(); + // Policy 1 - unmatched; Policy 2 - unmatched; Policy 3 - unmatched + doReturn(false).when(spyEngine).matches(eq(condition1), any(Activation.class)); + doReturn(false).when(spyEngine).matches(eq(condition2), any(Activation.class)); + doReturn(false).when(spyEngine).matches(eq(condition3), any(Activation.class)); + evaluateResult = spyEngine.evaluate(args); + assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.DENY); + assertEquals(evaluateResult.getPolicyNames().size(), 0); + assertEquals(evaluateResult.toString(), + new StringBuilder("Authorization Decision: DENY. \n").toString()); + } + + /** + * Test on the ALLOW engine. + * The evaluation result of two CEL expressions is set to true, + * and the evaluation result of one CEL expression is set to false, + * so the gRPC authorization returns ALLOW. + */ + @Test + public void testAllowEngineWithMatchedAndUnmatchedPolicies() + throws InterpreterException { + setupEngineSingleRbacAllow(); + // Policy 1 - unmatched; Policy 2 - matched; Policy 3 - matched + doReturn(false).when(spyEngine).matches(eq(condition1), any(Activation.class)); + doReturn(true).when(spyEngine).matches(eq(condition2), any(Activation.class)); + doReturn(true).when(spyEngine).matches(eq(condition3), any(Activation.class)); + evaluateResult = spyEngine.evaluate(args); + assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.ALLOW); + assertEquals(evaluateResult.getPolicyNames().size(), 1); + assertTrue(evaluateResult.getPolicyNames().contains("Policy 2")); + } + + /** + * Test on the ALLOW engine. + * The evaluation result of one CEL expression is set to unknown, + * so the gRPC authorization returns UNKNOWN. + */ + @Test + public void testAllowEngineWithUnknownAndUnmatchedPolicies() + throws InterpreterException { + setupEngineSingleRbacAllow(); + // Policy 1 - unmatched; Policy 2 - unknown; Policy 3 - unknown + doReturn(false).when(spyEngine).matches(eq(condition1), any(Activation.class)); + doThrow(new InterpreterException.Builder("Unknown result").build()) + .when(spyEngine).matches(eq(condition2), any(Activation.class)); + doThrow(new InterpreterException.Builder("Unknown result").build()) + .when(spyEngine).matches(eq(condition3), any(Activation.class)); + evaluateResult = spyEngine.evaluate(args); + assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.UNKNOWN); + assertEquals(evaluateResult.getPolicyNames().size(), 2); + assertTrue(evaluateResult.getPolicyNames().contains("Policy 2")); + assertTrue(evaluateResult.getPolicyNames().contains("Policy 3")); + assertEquals(evaluateResult.toString(), + new StringBuilder("Authorization Decision: UNKNOWN. \n" + + "Policy 2; \n" + "Policy 3; \n").toString()); + } + + /** + * Test on the ALLOW engine. + * The evaluation result of one CEL expression is set to unknown, + * so the gRPC authorization returns UNKNOWN. + */ + @Test + public void testAllowEngineWithMatchedUnmatchedAndUnknownPolicies() + throws InterpreterException { + setupEngineSingleRbacAllow(); + // Policy 1 - unmatched; Policy 2 - matched; Policy 3 - unknown + doReturn(false).when(spyEngine).matches(eq(condition1), any(Activation.class)); + doReturn(true).when(spyEngine).matches(eq(condition2), any(Activation.class)); + doThrow(new InterpreterException.Builder("Unknown result").build()) + .when(spyEngine).matches(eq(condition3), any(Activation.class)); + evaluateResult = spyEngine.evaluate(args); + assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.ALLOW); + assertEquals(evaluateResult.getPolicyNames().size(), 1); + assertTrue(evaluateResult.getPolicyNames().contains("Policy 2")); + assertEquals(evaluateResult.toString(), + new StringBuilder("Authorization Decision: ALLOW. \n" + "Policy 2; \n").toString()); + } + + /** + * Test on the DENY engine. + * The evaluation result of all the CEL expressions is set to true, + * so the gRPC authorization returns DENY. + */ + @Test + public void testDenyEngineWithAllMatchedPolicies() throws InterpreterException { + setupEngineSingleRbacDeny(); + // Policy 4 - matched; Policy 5 - matched; Policy 6 - matched + doReturn(true).when(spyEngine).matches(eq(condition4), any(Activation.class)); + doReturn(true).when(spyEngine).matches(eq(condition5), any(Activation.class)); + doReturn(true).when(spyEngine).matches(eq(condition6), any(Activation.class)); + evaluateResult = spyEngine.evaluate(args); + assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.DENY); + assertEquals(evaluateResult.getPolicyNames().size(), 1); + assertTrue(evaluateResult.getPolicyNames().contains("Policy 4")); + } + + /** + * Test on the DENY engine. + * The evaluation result of all the CEL expressions is set to false, + * so the gRPC authorization returns ALLOW. + */ + @Test + public void testDenyEngineWithAllUnmatchedPolicies() throws InterpreterException { + setupEngineSingleRbacDeny(); + // Policy 4 - unmatched; Policy 5 - unmatched; Policy 6 - unmatched + doReturn(false).when(spyEngine).matches(eq(condition4), any(Activation.class)); + doReturn(false).when(spyEngine).matches(eq(condition5), any(Activation.class)); + doReturn(false).when(spyEngine).matches(eq(condition6), any(Activation.class)); + evaluateResult = spyEngine.evaluate(args); + assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.ALLOW); + assertEquals(evaluateResult.getPolicyNames().size(), 0); + } + + /** + * Test on the DENY engine. + * The evaluation result of two CEL expressions is set to true, + * and the evaluation result of one CEL expression is set to false, + * so the gRPC authorization returns DENY. + */ + @Test + public void testDenyEngineWithMatchedAndUnmatchedPolicies() + throws InterpreterException { + setupEngineSingleRbacDeny(); + // Policy 4 - unmatched; Policy 5 - matched; Policy 6 - matched + doReturn(false).when(spyEngine).matches(eq(condition4), any(Activation.class)); + doReturn(true).when(spyEngine).matches(eq(condition5), any(Activation.class)); + doReturn(true).when(spyEngine).matches(eq(condition6), any(Activation.class)); + evaluateResult = spyEngine.evaluate(args); + assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.DENY); + assertEquals(evaluateResult.getPolicyNames().size(), 1); + assertTrue(evaluateResult.getPolicyNames().contains("Policy 5")); + } + + /** + * Test on the DENY engine. + * The evaluation result of one CEL expression is set to unknown, + * so the gRPC authorization returns UNKNOWN. + */ + @Test + public void testDenyEngineWithUnknownAndUnmatchedPolicies() + throws InterpreterException { + setupEngineSingleRbacDeny(); + // Policy 4 - unmatched; Policy 5 - unknown; Policy 6 - unknown + doReturn(false).when(spyEngine).matches(eq(condition4), any(Activation.class)); + doThrow(new InterpreterException.Builder("Unknown result").build()) + .when(spyEngine).matches(eq(condition5), any(Activation.class)); + doThrow(new InterpreterException.Builder("Unknown result").build()) + .when(spyEngine).matches(eq(condition6), any(Activation.class)); + evaluateResult = spyEngine.evaluate(args); + assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.UNKNOWN); + assertEquals(evaluateResult.getPolicyNames().size(), 2); + assertTrue(evaluateResult.getPolicyNames().contains("Policy 5")); + assertTrue(evaluateResult.getPolicyNames().contains("Policy 6")); + } + + /** + * Test on the DENY engine. + * The evaluation result of one CEL expression is set to unknown, + * so the gRPC authorization returns UNKNOWN. + */ + @Test + public void testDenyEngineWithMatchedUnmatchedAndUnknownPolicies() + throws InterpreterException { + setupEngineSingleRbacDeny(); + // Policy 4 - unmatched; Policy 5 - matched; Policy 6 - unknown + doReturn(false).when(spyEngine).matches(eq(condition4), any(Activation.class)); + doReturn(true).when(spyEngine).matches(eq(condition5), any(Activation.class)); + doThrow(new InterpreterException.Builder("Unknown result").build()) + .when(spyEngine).matches(eq(condition6), any(Activation.class)); + evaluateResult = spyEngine.evaluate(args); + assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.DENY); + assertEquals(evaluateResult.getPolicyNames().size(), 1); + assertTrue(evaluateResult.getPolicyNames().contains("Policy 5")); + } + + /** + * Test on the DENY engine and ALLOW engine pair. + * The evaluation result of all the CEL expressions is set to true in DENY engine, + * so the gRPC authorization returns DENY. + */ + @Test + public void testEnginePairWithAllMatchedDenyEngine() throws InterpreterException { + setupEngineRbacPair(); + // Policy 4 - matched; Policy 5 - matched; Policy 6 - matched + // Policy 1 - matched; Policy 2 - matched; Policy 3 - matched + doReturn(true).when(spyEngine).matches(eq(condition1), any(Activation.class)); + doReturn(true).when(spyEngine).matches(eq(condition2), any(Activation.class)); + doReturn(true).when(spyEngine).matches(eq(condition3), any(Activation.class)); + doReturn(true).when(spyEngine).matches(eq(condition4), any(Activation.class)); + doReturn(true).when(spyEngine).matches(eq(condition5), any(Activation.class)); + doReturn(true).when(spyEngine).matches(eq(condition6), any(Activation.class)); + evaluateResult = spyEngine.evaluate(args); + assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.DENY); + assertEquals(evaluateResult.getPolicyNames().size(), 1); + assertTrue(evaluateResult.getPolicyNames().contains("Policy 4")); + } + + /** + * Test on the DENY engine and ALLOW engine pair. + * The evaluation result of two CEL expressions is set to true, + * and the evaluation result of one CEL expression is set to false in DENY engine, + * so the gRPC authorization returns DENY. + */ + @Test + public void testEnginePairWithPartiallyMatchedDenyEngine() + throws InterpreterException { + setupEngineRbacPair(); + // Policy 4 - unmatched; Policy 5 - matched; Policy 6 - unknown + // Policy 1 - matched; Policy 2 - matched; Policy 3 - matched + doReturn(true).when(spyEngine).matches(eq(condition1), any(Activation.class)); + doReturn(true).when(spyEngine).matches(eq(condition2), any(Activation.class)); + doReturn(true).when(spyEngine).matches(eq(condition3), any(Activation.class)); + doReturn(false).when(spyEngine).matches(eq(condition4), any(Activation.class)); + doReturn(true).when(spyEngine).matches(eq(condition5), any(Activation.class)); + doThrow(new InterpreterException.Builder("Unknown result").build()) + .when(spyEngine).matches(eq(condition6), any(Activation.class)); + evaluateResult = spyEngine.evaluate(args); + assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.DENY); + assertEquals(evaluateResult.getPolicyNames().size(), 1); + assertTrue(evaluateResult.getPolicyNames().contains("Policy 5")); + } + + /** + * Test on the DENY engine and ALLOW engine pair. + * The DENY engine has unknown policies, so the gRPC authorization returns UNKNOWN. + */ + @Test + public void testEnginePairWithUnknownDenyEngine() throws InterpreterException { + setupEngineRbacPair(); + // Policy 4 - unmatched; Policy 5 - unknown; Policy 6 - unknown + // Policy 1 - matched; Policy 2 - matched; Policy 3 - matched + doReturn(true).when(spyEngine).matches(eq(condition1), any(Activation.class)); + doReturn(true).when(spyEngine).matches(eq(condition2), any(Activation.class)); + doReturn(true).when(spyEngine).matches(eq(condition3), any(Activation.class)); + doReturn(false).when(spyEngine).matches(eq(condition4), any(Activation.class)); + doThrow(new InterpreterException.Builder("Unknown result").build()) + .when(spyEngine).matches(eq(condition5), any(Activation.class)); + doThrow(new InterpreterException.Builder("Unknown result").build()) + .when(spyEngine).matches(eq(condition6), any(Activation.class)); + evaluateResult = spyEngine.evaluate(args); + assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.UNKNOWN); + assertEquals(evaluateResult.getPolicyNames().size(), 2); + assertTrue(evaluateResult.getPolicyNames().contains("Policy 5")); + assertTrue(evaluateResult.getPolicyNames().contains("Policy 6")); + } + + /** + * Test on the DENY engine and ALLOW engine pair. + * The evaluation result of all the CEL expressions is set to false in DENY engine, + * and the ALLOW engine has unknown policies, + * so the gRPC authorization returns UNKNOWN. + */ + @Test + public void testEnginePairWithUnmatchedDenyEngineAndUnknownAllowEngine() + throws InterpreterException { + setupEngineRbacPair(); + // Policy 4 - unmatched; Policy 5 - unmatched; Policy 6 - unmatched + // Policy 1 - unmatched; Policy 2 - unknown; Policy 3 - unknown + doReturn(false).when(spyEngine).matches(eq(condition1), any(Activation.class)); + doThrow(new InterpreterException.Builder("Unknown result").build()) + .when(spyEngine).matches(eq(condition2), any(Activation.class)); + doThrow(new InterpreterException.Builder("Unknown result").build()) + .when(spyEngine).matches(eq(condition3), any(Activation.class)); + doReturn(false).when(spyEngine).matches(eq(condition4), any(Activation.class)); + doReturn(false).when(spyEngine).matches(eq(condition5), any(Activation.class)); + doReturn(false).when(spyEngine).matches(eq(condition6), any(Activation.class)); + evaluateResult = spyEngine.evaluate(args); + assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.UNKNOWN); + assertEquals(evaluateResult.getPolicyNames().size(), 2); + assertTrue(evaluateResult.getPolicyNames().contains("Policy 2")); + assertTrue(evaluateResult.getPolicyNames().contains("Policy 3")); + } + + /** + * Test on the DENY engine and ALLOW engine pair. + * The evaluation result of all the CEL expressions is set to false in both engines, + * so the gRPC authorization returns DENY. + */ + @Test + public void testUnmatchedEnginePair() throws InterpreterException { + setupEngineRbacPair(); + // Policy 4 - unmatched; Policy 5 - unmatched; Policy 6 - unmatched + // Policy 1 - unmatched; Policy 2 - unmatched; Policy 3 - unmatched + doReturn(false).when(spyEngine).matches(eq(condition1), any(Activation.class)); + doReturn(false).when(spyEngine).matches(eq(condition2), any(Activation.class)); + doReturn(false).when(spyEngine).matches(eq(condition3), any(Activation.class)); + doReturn(false).when(spyEngine).matches(eq(condition4), any(Activation.class)); + doReturn(false).when(spyEngine).matches(eq(condition5), any(Activation.class)); + doReturn(false).when(spyEngine).matches(eq(condition6), any(Activation.class)); + evaluateResult = spyEngine.evaluate(args); + assertEquals(evaluateResult.getDecision(), AuthorizationDecision.Output.DENY); + assertEquals(evaluateResult.getPolicyNames().size(), 0); + } +} diff --git a/xds/src/test/java/io/grpc/xds/internal/rbac/engine/AuthzEngineTest.java b/xds/src/test/java/io/grpc/xds/internal/rbac/engine/AuthzEngineTest.java new file mode 100644 index 00000000000..17407d3ce18 --- /dev/null +++ b/xds/src/test/java/io/grpc/xds/internal/rbac/engine/AuthzEngineTest.java @@ -0,0 +1,141 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds.internal.rbac.engine; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import com.google.api.expr.v1alpha1.Expr; +import io.envoyproxy.envoy.config.rbac.v2.RBAC; +import io.envoyproxy.envoy.config.rbac.v2.RBAC.Action; +import io.grpc.xds.internal.rbac.engine.cel.Activation; +import io.grpc.xds.internal.rbac.engine.cel.Dispatcher; +import io.grpc.xds.internal.rbac.engine.cel.Interpretable; +import io.grpc.xds.internal.rbac.engine.cel.Interpreter; +import io.grpc.xds.internal.rbac.engine.cel.InterpreterException; +import io.grpc.xds.internal.rbac.engine.cel.RuntimeTypeProvider; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +/** Unit tests for constructor of CEL-based Authorization Engine. */ +@RunWith(JUnit4.class) +public class AuthzEngineTest { + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + @Rule + public final MockitoRule mocks = MockitoJUnit.rule(); + + @Mock + private Activation activation; + + @Mock + private RuntimeTypeProvider messageProvider; + + @Mock + private Dispatcher dispatcher; + + @Mock + private Interpreter interpreter; + + @Mock + private Interpretable interpretable; + + private AuthorizationEngine engine; + private RBAC rbacDeny; + private RBAC rbacAllow; + private Expr expr; + private Object result; + + @Before + public void setup() { + rbacAllow = RBAC.newBuilder() + .setAction(Action.ALLOW) + .build(); + rbacDeny = RBAC.newBuilder() + .setAction(Action.DENY) + .build(); + } + + @Test + public void createEngineAllowPolicy() { + engine = new AuthorizationEngine(rbacAllow); + assertNotNull(engine); + } + + @Test + public void createEngineDenyPolicy() { + engine = new AuthorizationEngine(rbacDeny); + assertNotNull(engine); + } + + @Test + public void createEngineDenyAllowPolicies() { + engine = new AuthorizationEngine(rbacDeny, rbacAllow); + assertNotNull(engine); + } + + @Test + public void failToCreateEngineIfRbacPairOfAllowAllow() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Invalid RBAC list, " + + "must provide a RBAC with DENY action followed by a RBAC with ALLOW action. "); + engine = new AuthorizationEngine(rbacAllow, rbacAllow); + assertNull(engine); + } + + @Test + public void failToCreateEngineIfRbacPairOfAllowDeny() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Invalid RBAC list, " + + "must provide a RBAC with DENY action followed by a RBAC with ALLOW action. "); + engine = new AuthorizationEngine(rbacAllow, rbacDeny); + assertNull(engine); + } + + @Test + public void failToCreateEngineIfRbacPairOfDenyDeny() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Invalid RBAC list, " + + "must provide a RBAC with DENY action followed by a RBAC with ALLOW action. "); + engine = new AuthorizationEngine(rbacDeny, rbacDeny); + assertNull(engine); + } + + @Test + public void testCelInterface() throws InterpreterException { + engine = new AuthorizationEngine(rbacAllow); + when(interpretable.eval(any(Activation.class))).thenReturn(true); + expr = Expr.newBuilder().build(); + result = engine.matches(expr, activation); + assertThat(messageProvider).isNotNull(); + assertThat(dispatcher).isNotNull(); + assertThat(interpreter).isNotNull(); + assertThat(activation).isNotNull(); + assertThat(result).isNotNull(); + } +} diff --git a/xds/src/test/java/io/grpc/xds/internal/rbac/engine/EvaluateArgsTest.java b/xds/src/test/java/io/grpc/xds/internal/rbac/engine/EvaluateArgsTest.java new file mode 100644 index 00000000000..3fa09bda451 --- /dev/null +++ b/xds/src/test/java/io/grpc/xds/internal/rbac/engine/EvaluateArgsTest.java @@ -0,0 +1,133 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds.internal.rbac.engine; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.common.collect.ImmutableMap; +import io.grpc.Attributes; +import io.grpc.Grpc; +import io.grpc.Metadata; +import io.grpc.SecurityLevel; +import io.grpc.ServerCall; +import io.grpc.internal.GrpcAttributes; +import io.netty.channel.local.LocalAddress; +import java.net.SocketAddress; +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.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +/** Unit tests for evaluate argument. */ +@RunWith(JUnit4.class) +public class EvaluateArgsTest { + @Rule + public final MockitoRule mocks = MockitoJUnit.rule(); + + @Mock + private ServerCall call; + + private EvaluateArgs args; + private EvaluateArgs spyArgs; + + private Metadata metadata; + private ImmutableMap attributesMap; + + @Before + public void setup() { + // Set up metadata. + metadata = new Metadata(); + // Set up spyArgs. + args = new EvaluateArgs(metadata, call); + spyArgs = Mockito.spy(args); + // Set up attributes map. + attributesMap = ImmutableMap.builder() + .put("request.url_path", "package.service/method") + .put("request.host", "fooapi.googleapis.com") + .put("request.method", "GET") + .put("request.headers", metadata) + .put("source.address", "1.2.3.4") + .put("source.port", 5050) + .put("destination.address", "4.3.2.1") + .put("destination.port", 8080) + .put("connection.uri_san_peer_certificate", "foo") + .put("source.principal", "spiffe") + .build(); + // Set up evaluate args. + doReturn("package.service/method").when(spyArgs).getRequestUrlPath(); + doReturn("fooapi.googleapis.com").when(spyArgs).getRequestHost(); + doReturn("GET").when(spyArgs).getRequestMethod(); + doReturn(metadata).when(spyArgs).getRequestHeaders(); + doReturn("1.2.3.4").when(spyArgs).getSourceAddress(); + doReturn(5050).when(spyArgs).getSourcePort(); + doReturn("4.3.2.1").when(spyArgs).getDestinationAddress(); + doReturn(8080).when(spyArgs).getDestinationPort(); + doReturn("foo").when(spyArgs).getConnectionUriSanPeerCertificate(); + doReturn("spiffe").when(spyArgs).getSourcePrincipal(); + } + + @Test + public void testGenerateEnvoyAttributes() { + setup(); + ImmutableMap attributes = spyArgs.generateEnvoyAttributes(); + assertEquals(attributesMap, attributes); + verify(spyArgs, times(1)).getRequestUrlPath(); + verify(spyArgs, times(1)).getRequestHost(); + verify(spyArgs, times(1)).getRequestMethod(); + verify(spyArgs, times(1)).getRequestHeaders(); + verify(spyArgs, times(1)).getSourceAddress(); + verify(spyArgs, times(1)).getSourcePort(); + verify(spyArgs, times(1)).getDestinationAddress(); + verify(spyArgs, times(1)).getDestinationPort(); + verify(spyArgs, times(1)).getConnectionUriSanPeerCertificate(); + verify(spyArgs, times(1)).getSourcePrincipal(); + } + + @Test + public void testEvaluateArgsAccessorFunctions() { + // Set up args and call. + args = new EvaluateArgs(new Metadata(), call); + SocketAddress localAddr = new LocalAddress("local_addr"); + SocketAddress remoteAddr = new LocalAddress("remote_addr"); + Attributes attrs = Attributes.newBuilder() + .set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, localAddr) + .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, remoteAddr) + .set(GrpcAttributes.ATTR_SECURITY_LEVEL, SecurityLevel.NONE) + .build(); + when(call.getAttributes()).thenReturn(attrs); + when(call.getAuthority()).thenReturn("fooapi.googleapis.com"); + // Check the behavior of accessor functions. + assertEquals(args.getRequestHost(), "fooapi.googleapis.com"); + assertNotNull(args.getRequestHeaders()); + assertEquals(args.getSourcePort(), 0); + assertEquals(args.getDestinationPort(), 0); + assertEquals(args.getSourceAddress(), "local:remote_addr"); + assertEquals(args.getDestinationAddress(), "local:local_addr"); + assertEquals(args.getConnectionUriSanPeerCertificate(), "placeholder"); + assertEquals(args.getSourcePrincipal(), "placeholder"); + } +} From 1c269e42894e86f41a6c8921c2d74a2388bcb845 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Fri, 14 Aug 2020 08:49:29 -0700 Subject: [PATCH 65/88] xds: import LRS v3 proto --- .../v3/LoadReportingServiceGrpc.java | 330 ++++++++++++++++++ xds/third_party/envoy/import.sh | 2 + .../config/endpoint/v3/load_report.proto | 167 +++++++++ .../envoy/service/load_stats/v3/lrs.proto | 103 ++++++ 4 files changed, 602 insertions(+) create mode 100644 xds/src/generated/main/grpc/io/envoyproxy/envoy/service/load_stats/v3/LoadReportingServiceGrpc.java create mode 100644 xds/third_party/envoy/src/main/proto/envoy/config/endpoint/v3/load_report.proto create mode 100644 xds/third_party/envoy/src/main/proto/envoy/service/load_stats/v3/lrs.proto diff --git a/xds/src/generated/main/grpc/io/envoyproxy/envoy/service/load_stats/v3/LoadReportingServiceGrpc.java b/xds/src/generated/main/grpc/io/envoyproxy/envoy/service/load_stats/v3/LoadReportingServiceGrpc.java new file mode 100644 index 00000000000..d7db02aed32 --- /dev/null +++ b/xds/src/generated/main/grpc/io/envoyproxy/envoy/service/load_stats/v3/LoadReportingServiceGrpc.java @@ -0,0 +1,330 @@ +package io.envoyproxy.envoy.service.load_stats.v3; + +import static io.grpc.MethodDescriptor.generateFullMethodName; +import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall; +import static io.grpc.stub.ClientCalls.asyncClientStreamingCall; +import static io.grpc.stub.ClientCalls.asyncServerStreamingCall; +import static io.grpc.stub.ClientCalls.asyncUnaryCall; +import static io.grpc.stub.ClientCalls.blockingServerStreamingCall; +import static io.grpc.stub.ClientCalls.blockingUnaryCall; +import static io.grpc.stub.ClientCalls.futureUnaryCall; +import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall; +import static io.grpc.stub.ServerCalls.asyncClientStreamingCall; +import static io.grpc.stub.ServerCalls.asyncServerStreamingCall; +import static io.grpc.stub.ServerCalls.asyncUnaryCall; +import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall; +import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall; + +/** + */ +@javax.annotation.Generated( + value = "by gRPC proto compiler", + comments = "Source: envoy/service/load_stats/v3/lrs.proto") +public final class LoadReportingServiceGrpc { + + private LoadReportingServiceGrpc() {} + + public static final String SERVICE_NAME = "envoy.service.load_stats.v3.LoadReportingService"; + + // Static method descriptors that strictly reflect the proto. + private static volatile io.grpc.MethodDescriptor getStreamLoadStatsMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "StreamLoadStats", + requestType = io.envoyproxy.envoy.service.load_stats.v3.LoadStatsRequest.class, + responseType = io.envoyproxy.envoy.service.load_stats.v3.LoadStatsResponse.class, + methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING) + public static io.grpc.MethodDescriptor getStreamLoadStatsMethod() { + io.grpc.MethodDescriptor getStreamLoadStatsMethod; + if ((getStreamLoadStatsMethod = LoadReportingServiceGrpc.getStreamLoadStatsMethod) == null) { + synchronized (LoadReportingServiceGrpc.class) { + if ((getStreamLoadStatsMethod = LoadReportingServiceGrpc.getStreamLoadStatsMethod) == null) { + LoadReportingServiceGrpc.getStreamLoadStatsMethod = getStreamLoadStatsMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "StreamLoadStats")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + io.envoyproxy.envoy.service.load_stats.v3.LoadStatsRequest.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + io.envoyproxy.envoy.service.load_stats.v3.LoadStatsResponse.getDefaultInstance())) + .setSchemaDescriptor(new LoadReportingServiceMethodDescriptorSupplier("StreamLoadStats")) + .build(); + } + } + } + return getStreamLoadStatsMethod; + } + + /** + * Creates a new async stub that supports all call types for the service + */ + public static LoadReportingServiceStub newStub(io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @java.lang.Override + public LoadReportingServiceStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new LoadReportingServiceStub(channel, callOptions); + } + }; + return LoadReportingServiceStub.newStub(factory, channel); + } + + /** + * Creates a new blocking-style stub that supports unary and streaming output calls on the service + */ + public static LoadReportingServiceBlockingStub newBlockingStub( + io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @java.lang.Override + public LoadReportingServiceBlockingStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new LoadReportingServiceBlockingStub(channel, callOptions); + } + }; + return LoadReportingServiceBlockingStub.newStub(factory, channel); + } + + /** + * Creates a new ListenableFuture-style stub that supports unary calls on the service + */ + public static LoadReportingServiceFutureStub newFutureStub( + io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @java.lang.Override + public LoadReportingServiceFutureStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new LoadReportingServiceFutureStub(channel, callOptions); + } + }; + return LoadReportingServiceFutureStub.newStub(factory, channel); + } + + /** + */ + public static abstract class LoadReportingServiceImplBase implements io.grpc.BindableService { + + /** + *
+     * Advanced API to allow for multi-dimensional load balancing by remote
+     * server. For receiving LB assignments, the steps are:
+     * 1, The management server is configured with per cluster/zone/load metric
+     *    capacity configuration. The capacity configuration definition is
+     *    outside of the scope of this document.
+     * 2. Envoy issues a standard {Stream,Fetch}Endpoints request for the clusters
+     *    to balance.
+     * Independently, Envoy will initiate a StreamLoadStats bidi stream with a
+     * management server:
+     * 1. Once a connection establishes, the management server publishes a
+     *    LoadStatsResponse for all clusters it is interested in learning load
+     *    stats about.
+     * 2. For each cluster, Envoy load balances incoming traffic to upstream hosts
+     *    based on per-zone weights and/or per-instance weights (if specified)
+     *    based on intra-zone LbPolicy. This information comes from the above
+     *    {Stream,Fetch}Endpoints.
+     * 3. When upstream hosts reply, they optionally add header <define header
+     *    name> with ASCII representation of EndpointLoadMetricStats.
+     * 4. Envoy aggregates load reports over the period of time given to it in
+     *    LoadStatsResponse.load_reporting_interval. This includes aggregation
+     *    stats Envoy maintains by itself (total_requests, rpc_errors etc.) as
+     *    well as load metrics from upstream hosts.
+     * 5. When the timer of load_reporting_interval expires, Envoy sends new
+     *    LoadStatsRequest filled with load reports for each cluster.
+     * 6. The management server uses the load reports from all reported Envoys
+     *    from around the world, computes global assignment and prepares traffic
+     *    assignment destined for each zone Envoys are located in. Goto 2.
+     * 
+ */ + public io.grpc.stub.StreamObserver streamLoadStats( + io.grpc.stub.StreamObserver responseObserver) { + return asyncUnimplementedStreamingCall(getStreamLoadStatsMethod(), responseObserver); + } + + @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() { + return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor()) + .addMethod( + getStreamLoadStatsMethod(), + asyncBidiStreamingCall( + new MethodHandlers< + io.envoyproxy.envoy.service.load_stats.v3.LoadStatsRequest, + io.envoyproxy.envoy.service.load_stats.v3.LoadStatsResponse>( + this, METHODID_STREAM_LOAD_STATS))) + .build(); + } + } + + /** + */ + public static final class LoadReportingServiceStub extends io.grpc.stub.AbstractAsyncStub { + private LoadReportingServiceStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @java.lang.Override + protected LoadReportingServiceStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new LoadReportingServiceStub(channel, callOptions); + } + + /** + *
+     * Advanced API to allow for multi-dimensional load balancing by remote
+     * server. For receiving LB assignments, the steps are:
+     * 1, The management server is configured with per cluster/zone/load metric
+     *    capacity configuration. The capacity configuration definition is
+     *    outside of the scope of this document.
+     * 2. Envoy issues a standard {Stream,Fetch}Endpoints request for the clusters
+     *    to balance.
+     * Independently, Envoy will initiate a StreamLoadStats bidi stream with a
+     * management server:
+     * 1. Once a connection establishes, the management server publishes a
+     *    LoadStatsResponse for all clusters it is interested in learning load
+     *    stats about.
+     * 2. For each cluster, Envoy load balances incoming traffic to upstream hosts
+     *    based on per-zone weights and/or per-instance weights (if specified)
+     *    based on intra-zone LbPolicy. This information comes from the above
+     *    {Stream,Fetch}Endpoints.
+     * 3. When upstream hosts reply, they optionally add header <define header
+     *    name> with ASCII representation of EndpointLoadMetricStats.
+     * 4. Envoy aggregates load reports over the period of time given to it in
+     *    LoadStatsResponse.load_reporting_interval. This includes aggregation
+     *    stats Envoy maintains by itself (total_requests, rpc_errors etc.) as
+     *    well as load metrics from upstream hosts.
+     * 5. When the timer of load_reporting_interval expires, Envoy sends new
+     *    LoadStatsRequest filled with load reports for each cluster.
+     * 6. The management server uses the load reports from all reported Envoys
+     *    from around the world, computes global assignment and prepares traffic
+     *    assignment destined for each zone Envoys are located in. Goto 2.
+     * 
+ */ + public io.grpc.stub.StreamObserver streamLoadStats( + io.grpc.stub.StreamObserver responseObserver) { + return asyncBidiStreamingCall( + getChannel().newCall(getStreamLoadStatsMethod(), getCallOptions()), responseObserver); + } + } + + /** + */ + public static final class LoadReportingServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub { + private LoadReportingServiceBlockingStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @java.lang.Override + protected LoadReportingServiceBlockingStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new LoadReportingServiceBlockingStub(channel, callOptions); + } + } + + /** + */ + public static final class LoadReportingServiceFutureStub extends io.grpc.stub.AbstractFutureStub { + private LoadReportingServiceFutureStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @java.lang.Override + protected LoadReportingServiceFutureStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new LoadReportingServiceFutureStub(channel, callOptions); + } + } + + private static final int METHODID_STREAM_LOAD_STATS = 0; + + private static final class MethodHandlers implements + io.grpc.stub.ServerCalls.UnaryMethod, + io.grpc.stub.ServerCalls.ServerStreamingMethod, + io.grpc.stub.ServerCalls.ClientStreamingMethod, + io.grpc.stub.ServerCalls.BidiStreamingMethod { + private final LoadReportingServiceImplBase serviceImpl; + private final int methodId; + + MethodHandlers(LoadReportingServiceImplBase serviceImpl, int methodId) { + this.serviceImpl = serviceImpl; + this.methodId = methodId; + } + + @java.lang.Override + @java.lang.SuppressWarnings("unchecked") + public void invoke(Req request, io.grpc.stub.StreamObserver responseObserver) { + switch (methodId) { + default: + throw new AssertionError(); + } + } + + @java.lang.Override + @java.lang.SuppressWarnings("unchecked") + public io.grpc.stub.StreamObserver invoke( + io.grpc.stub.StreamObserver responseObserver) { + switch (methodId) { + case METHODID_STREAM_LOAD_STATS: + return (io.grpc.stub.StreamObserver) serviceImpl.streamLoadStats( + (io.grpc.stub.StreamObserver) responseObserver); + default: + throw new AssertionError(); + } + } + } + + private static abstract class LoadReportingServiceBaseDescriptorSupplier + implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier { + LoadReportingServiceBaseDescriptorSupplier() {} + + @java.lang.Override + public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() { + return io.envoyproxy.envoy.service.load_stats.v3.LrsProto.getDescriptor(); + } + + @java.lang.Override + public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() { + return getFileDescriptor().findServiceByName("LoadReportingService"); + } + } + + private static final class LoadReportingServiceFileDescriptorSupplier + extends LoadReportingServiceBaseDescriptorSupplier { + LoadReportingServiceFileDescriptorSupplier() {} + } + + private static final class LoadReportingServiceMethodDescriptorSupplier + extends LoadReportingServiceBaseDescriptorSupplier + implements io.grpc.protobuf.ProtoMethodDescriptorSupplier { + private final String methodName; + + LoadReportingServiceMethodDescriptorSupplier(String methodName) { + this.methodName = methodName; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() { + return getServiceDescriptor().findMethodByName(methodName); + } + } + + private static volatile io.grpc.ServiceDescriptor serviceDescriptor; + + public static io.grpc.ServiceDescriptor getServiceDescriptor() { + io.grpc.ServiceDescriptor result = serviceDescriptor; + if (result == null) { + synchronized (LoadReportingServiceGrpc.class) { + result = serviceDescriptor; + if (result == null) { + serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME) + .setSchemaDescriptor(new LoadReportingServiceFileDescriptorSupplier()) + .addMethod(getStreamLoadStatsMethod()) + .build(); + } + } + } + return result; + } +} diff --git a/xds/third_party/envoy/import.sh b/xds/third_party/envoy/import.sh index 470c40b540d..80352ca520a 100755 --- a/xds/third_party/envoy/import.sh +++ b/xds/third_party/envoy/import.sh @@ -82,6 +82,7 @@ envoy/config/core/v3/socket_option.proto envoy/config/core/v3/substitution_format_string.proto envoy/config/endpoint/v3/endpoint.proto envoy/config/endpoint/v3/endpoint_components.proto +envoy/config/endpoint/v3/load_report.proto envoy/config/filter/accesslog/v2/accesslog.proto envoy/config/filter/fault/v2/fault.proto envoy/config/filter/http/fault/v2/fault.proto @@ -121,6 +122,7 @@ envoy/service/discovery/v2/sds.proto envoy/service/discovery/v3/ads.proto envoy/service/discovery/v3/discovery.proto envoy/service/load_stats/v2/lrs.proto +envoy/service/load_stats/v3/lrs.proto envoy/type/http.proto envoy/type/matcher/regex.proto envoy/type/matcher/string.proto diff --git a/xds/third_party/envoy/src/main/proto/envoy/config/endpoint/v3/load_report.proto b/xds/third_party/envoy/src/main/proto/envoy/config/endpoint/v3/load_report.proto new file mode 100644 index 00000000000..3f067737ec2 --- /dev/null +++ b/xds/third_party/envoy/src/main/proto/envoy/config/endpoint/v3/load_report.proto @@ -0,0 +1,167 @@ +syntax = "proto3"; + +package envoy.config.endpoint.v3; + +import "envoy/config/core/v3/address.proto"; +import "envoy/config/core/v3/base.proto"; + +import "google/protobuf/duration.proto"; +import "google/protobuf/struct.proto"; + +import "udpa/annotations/status.proto"; +import "udpa/annotations/versioning.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.config.endpoint.v3"; +option java_outer_classname = "LoadReportProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Load Report] + +// These are stats Envoy reports to the management server at a frequency defined by +// :ref:`LoadStatsResponse.load_reporting_interval`. +// Stats per upstream region/zone and optionally per subzone. +// [#next-free-field: 9] +message UpstreamLocalityStats { + option (udpa.annotations.versioning).previous_message_type = + "envoy.api.v2.endpoint.UpstreamLocalityStats"; + + // Name of zone, region and optionally endpoint group these metrics were + // collected from. Zone and region names could be empty if unknown. + core.v3.Locality locality = 1; + + // The total number of requests successfully completed by the endpoints in the + // locality. + uint64 total_successful_requests = 2; + + // The total number of unfinished requests + uint64 total_requests_in_progress = 3; + + // The total number of requests that failed due to errors at the endpoint, + // aggregated over all endpoints in the locality. + uint64 total_error_requests = 4; + + // The total number of requests that were issued by this Envoy since + // the last report. This information is aggregated over all the + // upstream endpoints in the locality. + uint64 total_issued_requests = 8; + + // Stats for multi-dimensional load balancing. + repeated EndpointLoadMetricStats load_metric_stats = 5; + + // Endpoint granularity stats information for this locality. This information + // is populated if the Server requests it by setting + // :ref:`LoadStatsResponse.report_endpoint_granularity`. + repeated UpstreamEndpointStats upstream_endpoint_stats = 7; + + // [#not-implemented-hide:] The priority of the endpoint group these metrics + // were collected from. + uint32 priority = 6; +} + +// [#next-free-field: 8] +message UpstreamEndpointStats { + option (udpa.annotations.versioning).previous_message_type = + "envoy.api.v2.endpoint.UpstreamEndpointStats"; + + // Upstream host address. + core.v3.Address address = 1; + + // Opaque and implementation dependent metadata of the + // endpoint. Envoy will pass this directly to the management server. + google.protobuf.Struct metadata = 6; + + // The total number of requests successfully completed by the endpoints in the + // locality. These include non-5xx responses for HTTP, where errors + // originate at the client and the endpoint responded successfully. For gRPC, + // the grpc-status values are those not covered by total_error_requests below. + uint64 total_successful_requests = 2; + + // The total number of unfinished requests for this endpoint. + uint64 total_requests_in_progress = 3; + + // The total number of requests that failed due to errors at the endpoint. + // For HTTP these are responses with 5xx status codes and for gRPC the + // grpc-status values: + // + // - DeadlineExceeded + // - Unimplemented + // - Internal + // - Unavailable + // - Unknown + // - DataLoss + uint64 total_error_requests = 4; + + // The total number of requests that were issued to this endpoint + // since the last report. A single TCP connection, HTTP or gRPC + // request or stream is counted as one request. + uint64 total_issued_requests = 7; + + // Stats for multi-dimensional load balancing. + repeated EndpointLoadMetricStats load_metric_stats = 5; +} + +message EndpointLoadMetricStats { + option (udpa.annotations.versioning).previous_message_type = + "envoy.api.v2.endpoint.EndpointLoadMetricStats"; + + // Name of the metric; may be empty. + string metric_name = 1; + + // Number of calls that finished and included this metric. + uint64 num_requests_finished_with_metric = 2; + + // Sum of metric values across all calls that finished with this metric for + // load_reporting_interval. + double total_metric_value = 3; +} + +// Per cluster load stats. Envoy reports these stats a management server in a +// :ref:`LoadStatsRequest` +// Next ID: 7 +// [#next-free-field: 7] +message ClusterStats { + option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.endpoint.ClusterStats"; + + message DroppedRequests { + option (udpa.annotations.versioning).previous_message_type = + "envoy.api.v2.endpoint.ClusterStats.DroppedRequests"; + + // Identifier for the policy specifying the drop. + string category = 1 [(validate.rules).string = {min_bytes: 1}]; + + // Total number of deliberately dropped requests for the category. + uint64 dropped_count = 2; + } + + // The name of the cluster. + string cluster_name = 1 [(validate.rules).string = {min_bytes: 1}]; + + // The eds_cluster_config service_name of the cluster. + // It's possible that two clusters send the same service_name to EDS, + // in that case, the management server is supposed to do aggregation on the load reports. + string cluster_service_name = 6; + + // Need at least one. + repeated UpstreamLocalityStats upstream_locality_stats = 2 + [(validate.rules).repeated = {min_items: 1}]; + + // Cluster-level stats such as total_successful_requests may be computed by + // summing upstream_locality_stats. In addition, below there are additional + // cluster-wide stats. + // + // The total number of dropped requests. This covers requests + // deliberately dropped by the drop_overload policy and circuit breaking. + uint64 total_dropped_requests = 3; + + // Information about deliberately dropped requests for each category specified + // in the DropOverload policy. + repeated DroppedRequests dropped_requests = 5; + + // Period over which the actual load report occurred. This will be guaranteed to include every + // request reported. Due to system load and delays between the *LoadStatsRequest* sent from Envoy + // and the *LoadStatsResponse* message sent from the management server, this may be longer than + // the requested load reporting interval in the *LoadStatsResponse*. + google.protobuf.Duration load_report_interval = 4; +} diff --git a/xds/third_party/envoy/src/main/proto/envoy/service/load_stats/v3/lrs.proto b/xds/third_party/envoy/src/main/proto/envoy/service/load_stats/v3/lrs.proto new file mode 100644 index 00000000000..76705ba7777 --- /dev/null +++ b/xds/third_party/envoy/src/main/proto/envoy/service/load_stats/v3/lrs.proto @@ -0,0 +1,103 @@ +syntax = "proto3"; + +package envoy.service.load_stats.v3; + +import "envoy/config/core/v3/base.proto"; +import "envoy/config/endpoint/v3/load_report.proto"; + +import "google/protobuf/duration.proto"; + +import "udpa/annotations/status.proto"; +import "udpa/annotations/versioning.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.service.load_stats.v3"; +option java_outer_classname = "LrsProto"; +option java_multiple_files = true; +option java_generic_services = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Load Reporting service (LRS)] + +// Load Reporting Service is an Envoy API to emit load reports. Envoy will initiate a bi-directional +// stream with a management server. Upon connecting, the management server can send a +// :ref:`LoadStatsResponse ` to a node it is +// interested in getting the load reports for. Envoy in this node will start sending +// :ref:`LoadStatsRequest `. This is done periodically +// based on the :ref:`load reporting interval ` +// For details, take a look at the :ref:`Load Reporting Service sandbox example `. + +service LoadReportingService { + // Advanced API to allow for multi-dimensional load balancing by remote + // server. For receiving LB assignments, the steps are: + // 1, The management server is configured with per cluster/zone/load metric + // capacity configuration. The capacity configuration definition is + // outside of the scope of this document. + // 2. Envoy issues a standard {Stream,Fetch}Endpoints request for the clusters + // to balance. + // + // Independently, Envoy will initiate a StreamLoadStats bidi stream with a + // management server: + // 1. Once a connection establishes, the management server publishes a + // LoadStatsResponse for all clusters it is interested in learning load + // stats about. + // 2. For each cluster, Envoy load balances incoming traffic to upstream hosts + // based on per-zone weights and/or per-instance weights (if specified) + // based on intra-zone LbPolicy. This information comes from the above + // {Stream,Fetch}Endpoints. + // 3. When upstream hosts reply, they optionally add header with ASCII representation of EndpointLoadMetricStats. + // 4. Envoy aggregates load reports over the period of time given to it in + // LoadStatsResponse.load_reporting_interval. This includes aggregation + // stats Envoy maintains by itself (total_requests, rpc_errors etc.) as + // well as load metrics from upstream hosts. + // 5. When the timer of load_reporting_interval expires, Envoy sends new + // LoadStatsRequest filled with load reports for each cluster. + // 6. The management server uses the load reports from all reported Envoys + // from around the world, computes global assignment and prepares traffic + // assignment destined for each zone Envoys are located in. Goto 2. + rpc StreamLoadStats(stream LoadStatsRequest) returns (stream LoadStatsResponse) { + } +} + +// A load report Envoy sends to the management server. +message LoadStatsRequest { + option (udpa.annotations.versioning).previous_message_type = + "envoy.service.load_stats.v2.LoadStatsRequest"; + + // Node identifier for Envoy instance. + config.core.v3.Node node = 1; + + // A list of load stats to report. + repeated config.endpoint.v3.ClusterStats cluster_stats = 2; +} + +// The management server sends envoy a LoadStatsResponse with all clusters it +// is interested in learning load stats about. +message LoadStatsResponse { + option (udpa.annotations.versioning).previous_message_type = + "envoy.service.load_stats.v2.LoadStatsResponse"; + + // Clusters to report stats for. + // Not populated if *send_all_clusters* is true. + repeated string clusters = 1; + + // If true, the client should send all clusters it knows about. + // Only clients that advertise the "envoy.lrs.supports_send_all_clusters" capability in their + // :ref:`client_features` field will honor this field. + bool send_all_clusters = 4; + + // The minimum interval of time to collect stats over. This is only a minimum for two reasons: + // + // 1. There may be some delay from when the timer fires until stats sampling occurs. + // 2. For clusters that were already feature in the previous *LoadStatsResponse*, any traffic + // that is observed in between the corresponding previous *LoadStatsRequest* and this + // *LoadStatsResponse* will also be accumulated and billed to the cluster. This avoids a period + // of inobservability that might otherwise exists between the messages. New clusters are not + // subject to this consideration. + google.protobuf.Duration load_reporting_interval = 2; + + // Set to *true* if the management server supports endpoint granularity + // report. + bool report_endpoint_granularity = 3; +} From 39c49b04080739fdb3f4b45a72efb80863a580ec Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Mon, 17 Aug 2020 09:45:13 -0700 Subject: [PATCH 66/88] xds: add CertProviderSslContextProvider support (#7309) --- .../main/java/io/grpc/xds/Bootstrapper.java | 7 +- .../main/java/io/grpc/xds/EnvoyProtoData.java | 3 +- .../CertProviderClientSslContextProvider.java | 123 ++++++++ .../CertProviderSslContextProvider.java | 155 +++++++++ .../CertificateProviderStore.java | 2 +- .../sds/DynamicSslContextProvider.java | 143 +++++++++ .../sds/SdsClientSslContextProvider.java | 2 +- .../internal/sds/SdsProtocolNegotiators.java | 12 +- .../sds/SdsServerSslContextProvider.java | 2 +- .../internal/sds/SdsSslContextProvider.java | 113 +------ .../SecretVolumeClientSslContextProvider.java | 8 +- .../SecretVolumeServerSslContextProvider.java | 8 +- .../xds/internal/sds/SslContextProvider.java | 25 +- .../sds/trust/SdsTrustManagerFactory.java | 28 +- .../sds/trust/SdsX509TrustManager.java | 27 +- ...tProviderClientSslContextProviderTest.java | 293 ++++++++++++++++++ .../CertificateProviderStoreTest.java | 37 +-- .../CommonCertProviderTestUtils.java | 177 +++++++++++ .../certprovider/TestCertificateProvider.java | 84 +++++ .../sds/CommonTlsContextTestsUtil.java | 123 ++++++++ .../sds/SdsSslContextProviderTest.java | 28 +- .../SecretVolumeSslContextProviderTest.java | 47 +-- .../sds/trust/SdsTrustManagerFactoryTest.java | 104 +++++++ 23 files changed, 1301 insertions(+), 250 deletions(-) create mode 100644 xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProvider.java create mode 100644 xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderSslContextProvider.java create mode 100644 xds/src/main/java/io/grpc/xds/internal/sds/DynamicSslContextProvider.java create mode 100644 xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProviderTest.java create mode 100644 xds/src/test/java/io/grpc/xds/internal/certprovider/CommonCertProviderTestUtils.java create mode 100644 xds/src/test/java/io/grpc/xds/internal/certprovider/TestCertificateProvider.java diff --git a/xds/src/main/java/io/grpc/xds/Bootstrapper.java b/xds/src/main/java/io/grpc/xds/Bootstrapper.java index c2a29ab10c3..a0bacd4190b 100644 --- a/xds/src/main/java/io/grpc/xds/Bootstrapper.java +++ b/xds/src/main/java/io/grpc/xds/Bootstrapper.java @@ -76,9 +76,10 @@ public static Bootstrapper getInstance() { */ public abstract BootstrapInfo readBootstrap() throws IOException; + /** Parses a raw string into {@link BootstrapInfo}. */ @VisibleForTesting @SuppressWarnings("deprecation") - static BootstrapInfo parseConfig(String rawData) throws IOException { + public static BootstrapInfo parseConfig(String rawData) throws IOException { XdsLogger logger = XdsLogger.withPrefix(LOG_PREFIX); logger.log(XdsLogLevel.INFO, "Reading bootstrap information"); @SuppressWarnings("unchecked") @@ -264,11 +265,11 @@ public static class CertificateProviderInfo { this.config = checkNotNull(config, "config"); } - String getPluginName() { + public String getPluginName() { return pluginName; } - Map getConfig() { + public Map getConfig() { return config; } } diff --git a/xds/src/main/java/io/grpc/xds/EnvoyProtoData.java b/xds/src/main/java/io/grpc/xds/EnvoyProtoData.java index 14a62f29f5a..cbb73423a16 100644 --- a/xds/src/main/java/io/grpc/xds/EnvoyProtoData.java +++ b/xds/src/main/java/io/grpc/xds/EnvoyProtoData.java @@ -325,7 +325,8 @@ List
getListeningAddresses() { return listeningAddresses; } - io.envoyproxy.envoy.config.core.v3.Node toEnvoyProtoNode() { + @VisibleForTesting + public io.envoyproxy.envoy.config.core.v3.Node toEnvoyProtoNode() { io.envoyproxy.envoy.config.core.v3.Node.Builder builder = io.envoyproxy.envoy.config.core.v3.Node.newBuilder(); builder.setId(id); diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProvider.java new file mode 100644 index 00000000000..562f27431c7 --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProvider.java @@ -0,0 +1,123 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds.internal.certprovider; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.VisibleForTesting; +import io.envoyproxy.envoy.config.core.v3.Node; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext.CombinedCertificateValidationContext; +import io.grpc.netty.GrpcSslContexts; +import io.grpc.xds.Bootstrapper.CertificateProviderInfo; +import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; +import io.grpc.xds.internal.sds.trust.SdsTrustManagerFactory; +import io.netty.handler.ssl.SslContextBuilder; +import java.security.cert.CertStoreException; +import java.security.cert.X509Certificate; +import java.util.Map; + +/** A client SslContext provider using CertificateProviderInstance to fetch secrets. */ +final class CertProviderClientSslContextProvider extends CertProviderSslContextProvider { + + private CertProviderClientSslContextProvider( + Node node, + Map certProviders, + CommonTlsContext.CertificateProviderInstance certInstance, + CommonTlsContext.CertificateProviderInstance rootCertInstance, + CertificateValidationContext staticCertValidationContext, + UpstreamTlsContext upstreamTlsContext, + CertificateProviderStore certificateProviderStore) { + super( + node, + certProviders, + certInstance, + checkNotNull(rootCertInstance, "Client SSL requires rootCertInstance"), + staticCertValidationContext, + upstreamTlsContext, + certificateProviderStore); + } + + @Override + protected final SslContextBuilder getSslContextBuilder( + CertificateValidationContext certificateValidationContextdationContext) + throws CertStoreException { + SslContextBuilder sslContextBuilder = + GrpcSslContexts.forClient() + .trustManager( + new SdsTrustManagerFactory( + savedTrustedRoots.toArray(new X509Certificate[0]), + certificateValidationContextdationContext)); + if (isMtls()) { + sslContextBuilder.keyManager(savedKey, savedCertChain); + } + return sslContextBuilder; + } + + /** Creates CertProviderClientSslContextProvider. */ + static final class Factory { + private static final Factory DEFAULT_INSTANCE = + new Factory(CertificateProviderStore.getInstance()); + private final CertificateProviderStore certificateProviderStore; + + @VisibleForTesting Factory(CertificateProviderStore certificateProviderStore) { + this.certificateProviderStore = certificateProviderStore; + } + + static Factory getInstance() { + return DEFAULT_INSTANCE; + } + + CertProviderClientSslContextProvider getProvider( + UpstreamTlsContext upstreamTlsContext, + Node node, + Map certProviders) { + checkNotNull(upstreamTlsContext, "upstreamTlsContext"); + CommonTlsContext commonTlsContext = upstreamTlsContext.getCommonTlsContext(); + CommonTlsContext.CertificateProviderInstance rootCertInstance = null; + CertificateValidationContext staticCertValidationContext = null; + if (commonTlsContext.hasCombinedValidationContext()) { + CombinedCertificateValidationContext combinedValidationContext = + commonTlsContext.getCombinedValidationContext(); + if (combinedValidationContext.hasValidationContextCertificateProviderInstance()) { + rootCertInstance = + combinedValidationContext.getValidationContextCertificateProviderInstance(); + } + if (combinedValidationContext.hasDefaultValidationContext()) { + staticCertValidationContext = combinedValidationContext.getDefaultValidationContext(); + } + } else if (commonTlsContext.hasValidationContextCertificateProviderInstance()) { + rootCertInstance = commonTlsContext.getValidationContextCertificateProviderInstance(); + } else if (commonTlsContext.hasValidationContext()) { + staticCertValidationContext = commonTlsContext.getValidationContext(); + } + CommonTlsContext.CertificateProviderInstance certInstance = null; + if (commonTlsContext.hasTlsCertificateCertificateProviderInstance()) { + certInstance = commonTlsContext.getTlsCertificateCertificateProviderInstance(); + } + return new CertProviderClientSslContextProvider( + node, + certProviders, + certInstance, + rootCertInstance, + staticCertValidationContext, + upstreamTlsContext, + certificateProviderStore); + } + } +} diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderSslContextProvider.java new file mode 100644 index 00000000000..5f03e3becca --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderSslContextProvider.java @@ -0,0 +1,155 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds.internal.certprovider; + +import io.envoyproxy.envoy.config.core.v3.Node; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext.CertificateProviderInstance; +import io.grpc.xds.Bootstrapper.CertificateProviderInfo; +import io.grpc.xds.EnvoyServerProtoData.BaseTlsContext; +import io.grpc.xds.internal.sds.DynamicSslContextProvider; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; + +/** Base class for {@link CertProviderClientSslContextProvider}. */ +abstract class CertProviderSslContextProvider extends DynamicSslContextProvider implements + CertificateProvider.Watcher { + + @Nullable private final CertificateProviderStore.Handle certHandle; + @Nullable private final CertificateProviderStore.Handle rootCertHandle; + @Nullable private final CertificateProviderInstance certInstance; + @Nullable private final CertificateProviderInstance rootCertInstance; + @Nullable protected PrivateKey savedKey; + @Nullable protected List savedCertChain; + @Nullable protected List savedTrustedRoots; + + protected CertProviderSslContextProvider( + Node node, + Map certProviders, + CertificateProviderInstance certInstance, + CertificateProviderInstance rootCertInstance, + CertificateValidationContext staticCertValidationContext, + BaseTlsContext tlsContext, + CertificateProviderStore certificateProviderStore) { + super(tlsContext, staticCertValidationContext); + this.certInstance = certInstance; + this.rootCertInstance = rootCertInstance; + String certInstanceName = null; + if (certInstance != null && certInstance.isInitialized()) { + certInstanceName = certInstance.getInstanceName(); + CertificateProviderInfo certProviderInstanceConfig = + getCertProviderConfig(certProviders, certInstanceName); + certHandle = + certificateProviderStore.createOrGetProvider( + certInstance.getCertificateName(), + certProviderInstanceConfig.getPluginName(), + certProviderInstanceConfig.getConfig(), + this, + true); + } else { + certHandle = null; + } + if (rootCertInstance != null + && rootCertInstance.isInitialized() + && !rootCertInstance.getInstanceName().equals(certInstanceName)) { + CertificateProviderInfo certProviderInstanceConfig = + getCertProviderConfig(certProviders, rootCertInstance.getInstanceName()); + rootCertHandle = + certificateProviderStore.createOrGetProvider( + rootCertInstance.getCertificateName(), + certProviderInstanceConfig.getPluginName(), + certProviderInstanceConfig.getConfig(), + this, + true); + } else { + rootCertHandle = null; + } + } + + private CertificateProviderInfo getCertProviderConfig( + Map certProviders, String pluginInstanceName) { + return certProviders.get(pluginInstanceName); + } + + @Override + public final void updateCertificate(PrivateKey key, List certChain) { + savedKey = key; + savedCertChain = certChain; + updateSslContextWhenReady(); + } + + @Override + public final void updateTrustedRoots(List trustedRoots) { + savedTrustedRoots = trustedRoots; + updateSslContextWhenReady(); + } + + private void updateSslContextWhenReady() { + if (isMtls()) { + if (savedKey != null && savedTrustedRoots != null) { + updateSslContext(); + clearKeysAndCerts(); + } + } else if (isClientSideTls()) { + if (savedTrustedRoots != null) { + updateSslContext(); + clearKeysAndCerts(); + } + } else if (isServerSideTls()) { + if (savedKey != null) { + updateSslContext(); + clearKeysAndCerts(); + } + } + } + + private void clearKeysAndCerts() { + savedKey = null; + savedTrustedRoots = null; + savedCertChain = null; + } + + protected final boolean isMtls() { + return certInstance != null && rootCertInstance != null; + } + + protected final boolean isClientSideTls() { + return rootCertInstance != null && certInstance == null; + } + + protected final boolean isServerSideTls() { + return certInstance != null && rootCertInstance == null; + } + + @Override + protected final CertificateValidationContext generateCertificateValidationContext() { + return staticCertificateValidationContext; + } + + @Override + public final void close() { + if (certHandle != null) { + certHandle.close(); + } + if (rootCertHandle != null) { + rootCertHandle.close(); + } + } +} diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderStore.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderStore.java index 5b09a581278..18f8a11ec2e 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderStore.java +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderStore.java @@ -129,7 +129,7 @@ public CertificateProvider create(CertProviderKey key) { CertificateProviderProvider certProviderProvider = certificateProviderRegistry.getProvider(key.pluginName); if (certProviderProvider == null) { - throw new IllegalArgumentException("Provider not found."); + throw new IllegalArgumentException("Provider not found for " + key.pluginName); } CertificateProvider certProvider = certProviderProvider.createCertificateProvider( key.config, new CertificateProvider.DistributorWatcher(), key.notifyCertUpdates); diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/DynamicSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/sds/DynamicSslContextProvider.java new file mode 100644 index 00000000000..7f40b822f01 --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/internal/sds/DynamicSslContextProvider.java @@ -0,0 +1,143 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds.internal.sds; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableList; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; +import io.grpc.Status; +import io.grpc.xds.EnvoyServerProtoData.BaseTlsContext; +import io.netty.handler.ssl.ApplicationProtocolConfig; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import java.io.IOException; +import java.security.cert.CertStoreException; +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nullable; + +/** Base class for dynamic {@link SslContextProvider}s. */ +public abstract class DynamicSslContextProvider extends SslContextProvider { + + protected final List pendingCallbacks = new ArrayList<>(); + @Nullable protected final CertificateValidationContext staticCertificateValidationContext; + @Nullable protected SslContext sslContext; + + protected DynamicSslContextProvider( + BaseTlsContext tlsContext, CertificateValidationContext staticCertValidationContext) { + super(tlsContext); + this.staticCertificateValidationContext = staticCertValidationContext; + } + + @Nullable + public SslContext getSslContext() { + return sslContext; + } + + protected abstract CertificateValidationContext generateCertificateValidationContext(); + + /** Gets a server or client side SslContextBuilder. */ + protected abstract SslContextBuilder getSslContextBuilder( + CertificateValidationContext certificateValidationContext) + throws CertificateException, IOException, CertStoreException; + + // this gets called only when requested secrets are ready... + protected final void updateSslContext() { + try { + CertificateValidationContext localCertValidationContext = + generateCertificateValidationContext(); + SslContextBuilder sslContextBuilder = getSslContextBuilder(localCertValidationContext); + CommonTlsContext commonTlsContext = getCommonTlsContext(); + if (commonTlsContext != null && commonTlsContext.getAlpnProtocolsCount() > 0) { + List alpnList = commonTlsContext.getAlpnProtocolsList(); + ApplicationProtocolConfig apn = + new ApplicationProtocolConfig( + ApplicationProtocolConfig.Protocol.ALPN, + ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, + ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, + alpnList); + sslContextBuilder.applicationProtocolConfig(apn); + } + List pendingCallbacksCopy = null; + SslContext sslContextCopy = null; + synchronized (pendingCallbacks) { + sslContext = sslContextBuilder.build(); + sslContextCopy = sslContext; + pendingCallbacksCopy = clonePendingCallbacksAndClear(); + } + makePendingCallbacks(sslContextCopy, pendingCallbacksCopy); + } catch (Exception e) { + onError(Status.fromThrowable(e)); + throw new RuntimeException(e); + } + } + + protected final void callPerformCallback( + Callback callback, final SslContext sslContextCopy) { + performCallback( + new SslContextGetter() { + @Override + public SslContext get() { + return sslContextCopy; + } + }, + callback + ); + } + + @Override + public final void addCallback(Callback callback) { + checkNotNull(callback, "callback"); + // if there is a computed sslContext just send it + SslContext sslContextCopy = null; + synchronized (pendingCallbacks) { + if (sslContext != null) { + sslContextCopy = sslContext; + } else { + pendingCallbacks.add(callback); + } + } + if (sslContextCopy != null) { + callPerformCallback(callback, sslContextCopy); + } + } + + private final void makePendingCallbacks( + SslContext sslContextCopy, List pendingCallbacksCopy) { + for (Callback callback : pendingCallbacksCopy) { + callPerformCallback(callback, sslContextCopy); + } + } + + /** Propagates error to all the callback receivers. */ + public final void onError(Status error) { + for (Callback callback : clonePendingCallbacksAndClear()) { + callback.onException(error.asException()); + } + } + + private List clonePendingCallbacksAndClear() { + synchronized (pendingCallbacks) { + List copy = ImmutableList.copyOf(pendingCallbacks); + pendingCallbacks.clear(); + return copy; + } + } +} diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/SdsClientSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/sds/SdsClientSslContextProvider.java index 943d205fbf8..eef2faf2e23 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/SdsClientSslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/SdsClientSslContextProvider.java @@ -90,7 +90,7 @@ static SdsClientSslContextProvider getProvider( } @Override - SslContextBuilder getSslContextBuilder( + protected final SslContextBuilder getSslContextBuilder( CertificateValidationContext localCertValidationContext) throws CertificateException, IOException, CertStoreException { SslContextBuilder sslContextBuilder = diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/SdsProtocolNegotiators.java b/xds/src/main/java/io/grpc/xds/internal/sds/SdsProtocolNegotiators.java index 412d11ec6e9..d157c796833 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/SdsProtocolNegotiators.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/SdsProtocolNegotiators.java @@ -197,7 +197,7 @@ protected void handlerAdded0(final ChannelHandlerContext ctx) { .findOrCreateClientSslContextProvider(upstreamTlsContext); sslContextProvider.addCallback( - new SslContextProvider.Callback() { + new SslContextProvider.Callback(ctx.executor()) { @Override public void updateSecret(SslContext sslContext) { @@ -220,8 +220,8 @@ public void updateSecret(SslContext sslContext) { public void onException(Throwable throwable) { ctx.fireExceptionCaught(throwable); } - }, - ctx.executor()); + } + ); } @Override @@ -370,7 +370,7 @@ protected void handlerAdded0(final ChannelHandlerContext ctx) { } final SslContextProvider sslContextProvider = sslContextProviderTemp; sslContextProvider.addCallback( - new SslContextProvider.Callback() { + new SslContextProvider.Callback(ctx.executor()) { @Override public void updateSecret(SslContext sslContext) { @@ -389,8 +389,8 @@ public void updateSecret(SslContext sslContext) { public void onException(Throwable throwable) { ctx.fireExceptionCaught(throwable); } - }, - ctx.executor()); + } + ); } } } diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/SdsServerSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/sds/SdsServerSslContextProvider.java index 7d31f8f5c8a..27afaa455e3 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/SdsServerSslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/SdsServerSslContextProvider.java @@ -75,7 +75,7 @@ static SdsServerSslContextProvider getProvider( } @Override - SslContextBuilder getSslContextBuilder( + protected final SslContextBuilder getSslContextBuilder( CertificateValidationContext localCertValidationContext) throws CertificateException, IOException, CertStoreException { SslContextBuilder sslContextBuilder = diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/SdsSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/sds/SdsSslContextProvider.java index 26ff694cf5d..b70c2f1e5cb 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/SdsSslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/SdsSslContextProvider.java @@ -21,27 +21,18 @@ import io.envoyproxy.envoy.api.v2.core.Node; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; -import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.SdsSecretConfig; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.Secret; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.TlsCertificate; -import io.grpc.Status; import io.grpc.xds.EnvoyServerProtoData.BaseTlsContext; -import io.netty.handler.ssl.ApplicationProtocolConfig; -import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslContextBuilder; -import java.io.IOException; -import java.security.cert.CertStoreException; -import java.security.cert.CertificateException; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.Executor; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; /** Base class for SdsClientSslContextProvider and SdsServerSslContextProvider. */ -abstract class SdsSslContextProvider extends SslContextProvider implements SdsClient.SecretWatcher { +abstract class SdsSslContextProvider extends DynamicSslContextProvider implements + SdsClient.SecretWatcher { private static final Logger logger = Logger.getLogger(SdsSslContextProvider.class.getName()); @@ -49,13 +40,10 @@ abstract class SdsSslContextProvider extends SslContextProvider implements SdsCl @Nullable private final SdsClient validationContextSdsClient; @Nullable private final SdsSecretConfig certSdsConfig; @Nullable private final SdsSecretConfig validationContextSdsConfig; - @Nullable private final CertificateValidationContext staticCertificateValidationContext; - private final List pendingCallbacks = new ArrayList<>(); @Nullable protected TlsCertificate tlsCertificate; @Nullable private CertificateValidationContext certificateValidationContext; - @Nullable private SslContext sslContext; - SdsSslContextProvider( + protected SdsSslContextProvider( Node node, SdsSecretConfig certSdsConfig, SdsSecretConfig validationContextSdsConfig, @@ -63,10 +51,9 @@ abstract class SdsSslContextProvider extends SslContextProvider implements SdsCl Executor watcherExecutor, Executor channelExecutor, BaseTlsContext tlsContext) { - super(tlsContext); + super(tlsContext, staticCertValidationContext); this.certSdsConfig = certSdsConfig; this.validationContextSdsConfig = validationContextSdsConfig; - this.staticCertificateValidationContext = staticCertValidationContext; if (certSdsConfig != null && certSdsConfig.isInitialized()) { certSdsClient = SdsClient.Factory.createSdsClient(certSdsConfig, node, watcherExecutor, channelExecutor); @@ -87,35 +74,7 @@ abstract class SdsSslContextProvider extends SslContextProvider implements SdsCl } @Override - public void addCallback(Callback callback, Executor executor) { - checkNotNull(callback, "callback"); - checkNotNull(executor, "executor"); - // if there is a computed sslContext just send it - SslContext sslContextCopy = sslContext; - if (sslContextCopy != null) { - callPerformCallback(callback, executor, sslContextCopy); - } else { - synchronized (pendingCallbacks) { - pendingCallbacks.add(new CallbackPair(callback, executor)); - } - } - } - - private void callPerformCallback( - Callback callback, Executor executor, final SslContext sslContextCopy) { - performCallback( - new SslContextGetter() { - @Override - public SslContext get() { - return sslContextCopy; - } - }, - callback, - executor); - } - - @Override - public synchronized void onSecretChanged(Secret secretUpdate) { + public final synchronized void onSecretChanged(Secret secretUpdate) { checkNotNull(secretUpdate); if (secretUpdate.hasTlsCertificate()) { checkState( @@ -143,35 +102,8 @@ public synchronized void onSecretChanged(Secret secretUpdate) { } } - /** Gets a server or client side SslContextBuilder. */ - abstract SslContextBuilder getSslContextBuilder( - CertificateValidationContext localCertValidationContext) - throws CertificateException, IOException, CertStoreException; - - // this gets called only when requested secrets are ready... - private void updateSslContext() { - try { - CertificateValidationContext localCertValidationContext = mergeStaticAndDynamicCertContexts(); - SslContextBuilder sslContextBuilder = getSslContextBuilder(localCertValidationContext); - CommonTlsContext commonTlsContext = getCommonTlsContext(); - if (commonTlsContext != null && commonTlsContext.getAlpnProtocolsCount() > 0) { - List alpnList = commonTlsContext.getAlpnProtocolsList(); - ApplicationProtocolConfig apn = new ApplicationProtocolConfig( - ApplicationProtocolConfig.Protocol.ALPN, - ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, - ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, - alpnList); - sslContextBuilder.applicationProtocolConfig(apn); - } - SslContext sslContextCopy = sslContextBuilder.build(); - sslContext = sslContextCopy; - makePendingCallbacks(sslContextCopy); - } catch (CertificateException | IOException | CertStoreException e) { - logger.log(Level.SEVERE, "exception in updateSslContext", e); - } - } - - private CertificateValidationContext mergeStaticAndDynamicCertContexts() { + @Override + protected final CertificateValidationContext generateCertificateValidationContext() { if (staticCertificateValidationContext == null) { return certificateValidationContext; } @@ -183,27 +115,8 @@ private CertificateValidationContext mergeStaticAndDynamicCertContexts() { return localCertContextBuilder.mergeFrom(staticCertificateValidationContext).build(); } - private void makePendingCallbacks(SslContext sslContextCopy) { - synchronized (pendingCallbacks) { - for (CallbackPair pair : pendingCallbacks) { - callPerformCallback(pair.callback, pair.executor, sslContextCopy); - } - pendingCallbacks.clear(); - } - } - @Override - public void onError(Status error) { - synchronized (pendingCallbacks) { - for (CallbackPair callbackPair : pendingCallbacks) { - callbackPair.callback.onException(error.asException()); - } - pendingCallbacks.clear(); - } - } - - @Override - public void close() { + public final void close() { if (certSdsClient != null) { certSdsClient.cancelSecretWatch(this); certSdsClient.shutdown(); @@ -213,14 +126,4 @@ public void close() { validationContextSdsClient.shutdown(); } } - - private static class CallbackPair { - private final Callback callback; - private final Executor executor; - - private CallbackPair(Callback callback, Executor executor) { - this.callback = callback; - this.executor = executor; - } - } } diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/SecretVolumeClientSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/sds/SecretVolumeClientSslContextProvider.java index f2ea0546c8f..fc3befd5f1c 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/SecretVolumeClientSslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/SecretVolumeClientSslContextProvider.java @@ -34,7 +34,6 @@ import java.io.IOException; import java.security.cert.CertStoreException; import java.security.cert.CertificateException; -import java.util.concurrent.Executor; import javax.annotation.Nullable; /** A client SslContext provider that uses file-based secrets (secret volume). */ @@ -92,9 +91,8 @@ static SecretVolumeClientSslContextProvider getProvider(UpstreamTlsContext upstr } @Override - public void addCallback(final Callback callback, Executor executor) { + public void addCallback(final Callback callback) { checkNotNull(callback, "callback"); - checkNotNull(executor, "executor"); // as per the contract we will read the current secrets on disk // this involves I/O which can potentially block the executor performCallback( @@ -104,8 +102,8 @@ public SslContext get() throws CertificateException, IOException, CertStoreExcep return buildSslContextFromSecrets(); } }, - callback, - executor); + callback + ); } @Override diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/SecretVolumeServerSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/sds/SecretVolumeServerSslContextProvider.java index 05afb1f4cbf..3282fc555c3 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/SecretVolumeServerSslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/SecretVolumeServerSslContextProvider.java @@ -33,7 +33,6 @@ import java.io.IOException; import java.security.cert.CertStoreException; import java.security.cert.CertificateException; -import java.util.concurrent.Executor; import javax.annotation.Nullable; /** A server SslContext provider that uses file-based secrets (secret volume). */ @@ -85,9 +84,8 @@ static SecretVolumeServerSslContextProvider getProvider( } @Override - public void addCallback(final Callback callback, Executor executor) { + public void addCallback(final Callback callback) { checkNotNull(callback, "callback"); - checkNotNull(executor, "executor"); // as per the contract we will read the current secrets on disk // this involves I/O which can potentially block the executor performCallback( @@ -97,8 +95,8 @@ public SslContext get() throws CertificateException, IOException, CertStoreExcep return buildSslContextFromSecrets(); } }, - callback, - executor); + callback + ); } @Override diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/SslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/sds/SslContextProvider.java index 6c73b7c7371..08e93d3a4e6 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/SslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/SslContextProvider.java @@ -47,19 +47,25 @@ public abstract class SslContextProvider implements Closeable { protected final BaseTlsContext tlsContext; - public interface Callback { + abstract static class Callback { + private final Executor executor; + + protected Callback(Executor executor) { + this.executor = executor; + } + /** Informs callee of new/updated SslContext. */ - void updateSecret(SslContext sslContext); + abstract void updateSecret(SslContext sslContext); /** Informs callee of an exception that was generated. */ - void onException(Throwable throwable); + abstract void onException(Throwable throwable); } - SslContextProvider(BaseTlsContext tlsContext) { + protected SslContextProvider(BaseTlsContext tlsContext) { this.tlsContext = checkNotNull(tlsContext, "tlsContext"); } - CommonTlsContext getCommonTlsContext() { + protected CommonTlsContext getCommonTlsContext() { return tlsContext.getCommonTlsContext(); } @@ -100,14 +106,13 @@ public UpstreamTlsContext getUpstreamTlsContext() { * Registers a callback on the given executor. The callback will run when SslContext becomes * available or immediately if the result is already available. */ - public abstract void addCallback(Callback callback, Executor executor); + public abstract void addCallback(Callback callback); - final void performCallback( - final SslContextGetter sslContextGetter, final Callback callback, Executor executor) { + protected final void performCallback( + final SslContextGetter sslContextGetter, final Callback callback) { checkNotNull(sslContextGetter, "sslContextGetter"); checkNotNull(callback, "callback"); - checkNotNull(executor, "executor"); - executor.execute( + callback.executor.execute( new Runnable() { @Override public void run() { diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/trust/SdsTrustManagerFactory.java b/xds/src/main/java/io/grpc/xds/internal/sds/trust/SdsTrustManagerFactory.java index 6ff63c0e5f0..479569f1596 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/trust/SdsTrustManagerFactory.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/trust/SdsTrustManagerFactory.java @@ -16,7 +16,7 @@ package io.grpc.xds.internal.sds.trust; -import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import com.google.common.annotations.VisibleForTesting; @@ -53,9 +53,29 @@ public final class SdsTrustManagerFactory extends SimpleTrustManagerFactory { /** Constructor constructs from a {@link CertificateValidationContext}. */ public SdsTrustManagerFactory(CertificateValidationContext certificateValidationContext) throws CertificateException, IOException, CertStoreException { - checkNotNull(certificateValidationContext, "certificateValidationContext"); - sdsX509TrustManager = createSdsX509TrustManager( - getTrustedCaFromCertContext(certificateValidationContext), certificateValidationContext); + this( + getTrustedCaFromCertContext(certificateValidationContext), + certificateValidationContext, + false); + } + + public SdsTrustManagerFactory( + X509Certificate[] certs, CertificateValidationContext staticCertificateValidationContext) + throws CertStoreException { + this(certs, staticCertificateValidationContext, true); + } + + private SdsTrustManagerFactory( + X509Certificate[] certs, + CertificateValidationContext certificateValidationContext, + boolean validationContextIsStatic) + throws CertStoreException { + if (validationContextIsStatic) { + checkArgument( + certificateValidationContext == null || !certificateValidationContext.hasTrustedCa(), + "only static certificateValidationContext expected"); + } + sdsX509TrustManager = createSdsX509TrustManager(certs, certificateValidationContext); } private static X509Certificate[] getTrustedCaFromCertContext( diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/trust/SdsX509TrustManager.java b/xds/src/main/java/io/grpc/xds/internal/sds/trust/SdsX509TrustManager.java index bb23a59bb7e..6b7324ddf7b 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/trust/SdsX509TrustManager.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/trust/SdsX509TrustManager.java @@ -59,7 +59,8 @@ final class SdsX509TrustManager extends X509ExtendedTrustManager implements X509 } // Copied from OkHostnameVerifier.verifyHostName(). - private static boolean verifyDnsNameInPattern(String pattern, String sanToVerify) { + private static boolean verifyDnsNameInPattern(String pattern, StringMatcher sanToVerifyMatcher) { + String sanToVerify = sanToVerifyMatcher.getExact(); // Basic sanity checks // Check length == 0 instead of .isEmpty() to support Java 5. if (sanToVerify == null @@ -150,9 +151,9 @@ private static boolean verifyDnsNameInPattern(String pattern, String sanToVerify // sanToVerify matches pattern } - private static boolean verifyDnsNameInSanList(String altNameFromCert, - List verifySanList) { - for (String verifySan : verifySanList) { + private static boolean verifyDnsNameInSanList( + String altNameFromCert, List verifySanList) { + for (StringMatcher verifySan : verifySanList) { if (verifyDnsNameInPattern(altNameFromCert, verifySan)) { return true; } @@ -168,16 +169,17 @@ private static boolean verifyDnsNameInSanList(String altNameFromCert, * @param verifySanList list of SANs from certificate context * @return true if there is a match */ - private static boolean verifyStringInSanList(String stringFromCert, List verifySanList) { - for (String sanToVerify : verifySanList) { - if (Ascii.equalsIgnoreCase(sanToVerify, stringFromCert)) { + private static boolean verifyStringInSanList( + String stringFromCert, List verifySanList) { + for (StringMatcher sanToVerify : verifySanList) { + if (Ascii.equalsIgnoreCase(sanToVerify.getExact(), stringFromCert)) { return true; } } return false; } - private static boolean verifyOneSanInList(List entry, List verifySanList) + private static boolean verifyOneSanInList(List entry, List verifySanList) throws CertificateParsingException { // from OkHostnameVerifier.getSubjectAltNames if (entry == null || entry.size() < 2) { @@ -200,9 +202,8 @@ private static boolean verifyOneSanInList(List entry, List verifySanL } // logic from Envoy::Extensions::TransportSockets::Tls::ContextImpl::verifySubjectAltName - @SuppressWarnings("UnusedMethod") // TODO(#7166): support StringMatcher list. - private static void verifySubjectAltNameInLeaf(X509Certificate cert, List verifyList) - throws CertificateException { + private static void verifySubjectAltNameInLeaf( + X509Certificate cert, List verifyList) throws CertificateException { Collection> names = cert.getSubjectAlternativeNames(); if (names == null || names.isEmpty()) { throw new CertificateException("Peer certificate SAN check failed"); @@ -233,9 +234,7 @@ void verifySubjectAltNameInChain(X509Certificate[] peerCertChain) throws Certifi throw new CertificateException("Peer certificate(s) missing"); } // verify SANs only in the top cert (leaf cert) - // v2 version: verifySubjectAltNameInLeaf(peerCertChain[0], verifyList); - // TODO(#7166): Implement v3 version. - throw new UnsupportedOperationException(); + verifySubjectAltNameInLeaf(peerCertChain[0], verifyList); } @Override diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProviderTest.java new file mode 100644 index 00000000000..6e9ae15ee5b --- /dev/null +++ b/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProviderTest.java @@ -0,0 +1,293 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds.internal.certprovider; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.truth.Truth.assertThat; +import static io.grpc.xds.internal.certprovider.CommonCertProviderTestUtils.getCertFromResourceName; +import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CA_PEM_FILE; +import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_KEY_FILE; +import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; +import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_0_PEM_FILE; +import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_KEY_FILE; +import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; +import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.doChecksOnSslContext; +import static org.junit.Assert.fail; + +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.MoreExecutors; +import io.envoyproxy.envoy.config.core.v3.DataSource; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; +import io.grpc.xds.Bootstrapper; +import io.grpc.xds.EnvoyServerProtoData; +import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil; +import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.TestCallback; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Executor; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link CertProviderClientSslContextProvider}. */ +@RunWith(JUnit4.class) +public class CertProviderClientSslContextProviderTest { + private static final Logger logger = + Logger.getLogger(CertProviderClientSslContextProviderTest.class.getName()); + + CertificateProviderRegistry certificateProviderRegistry; + CertificateProviderStore certificateProviderStore; + private CertProviderClientSslContextProvider.Factory certProviderClientSslContextProviderFactory; + + @Before + public void setUp() throws Exception { + certificateProviderRegistry = new CertificateProviderRegistry(); + certificateProviderStore = new CertificateProviderStore(certificateProviderRegistry); + certProviderClientSslContextProviderFactory = + new CertProviderClientSslContextProvider.Factory(certificateProviderStore); + } + + /** Helper method to build CertProviderClientSslContextProvider. */ + private CertProviderClientSslContextProvider getSslContextProvider( + String certInstanceName, + String rootInstanceName, + Bootstrapper.BootstrapInfo bootstrapInfo, + Iterable alpnProtocols, + CertificateValidationContext staticCertValidationContext) { + EnvoyServerProtoData.UpstreamTlsContext upstreamTlsContext = + CommonTlsContextTestsUtil.buildUpstreamTlsContextForCertProviderInstance( + certInstanceName, + "cert-default", + rootInstanceName, + "root-default", + alpnProtocols, + staticCertValidationContext); + return certProviderClientSslContextProviderFactory.getProvider( + upstreamTlsContext, + bootstrapInfo.getNode().toEnvoyProtoNode(), + bootstrapInfo.getCertProviders()); + } + + @Test + public void testProviderForClient_mtls() throws Exception { + final CertificateProvider.DistributorWatcher[] watcherCaptor = + new CertificateProvider.DistributorWatcher[1]; + TestCertificateProvider.createAndRegisterProviderProvider( + certificateProviderRegistry, watcherCaptor, "testca", 0); + CertProviderClientSslContextProvider provider = + getSslContextProvider( + "gcp_id", + "gcp_id", + CommonCertProviderTestUtils.getTestBootstrapInfo(), + /* alpnProtocols= */ null, + /* staticCertValidationContext= */ null); + + assertThat(provider.savedKey).isNull(); + assertThat(provider.savedCertChain).isNull(); + assertThat(provider.savedTrustedRoots).isNull(); + assertThat(provider.getSslContext()).isNull(); + + // now generate cert update + watcherCaptor[0].updateCertificate( + CommonCertProviderTestUtils.getPrivateKey(CLIENT_KEY_FILE), + ImmutableList.of(getCertFromResourceName(CLIENT_PEM_FILE))); + assertThat(provider.savedKey).isNotNull(); + assertThat(provider.savedCertChain).isNotNull(); + assertThat(provider.getSslContext()).isNull(); + + // now generate root cert update + watcherCaptor[0].updateTrustedRoots(ImmutableList.of(getCertFromResourceName(CA_PEM_FILE))); + assertThat(provider.getSslContext()).isNotNull(); + assertThat(provider.savedKey).isNull(); + assertThat(provider.savedCertChain).isNull(); + assertThat(provider.savedTrustedRoots).isNull(); + + TestCallback testCallback = + CommonTlsContextTestsUtil.getValueThruCallback(provider); + + doChecksOnSslContext(false, testCallback.updatedSslContext, /* expectedApnProtos= */ null); + TestCallback testCallback1 = + CommonTlsContextTestsUtil.getValueThruCallback(provider); + assertThat(testCallback1.updatedSslContext).isSameInstanceAs(testCallback.updatedSslContext); + + // just do root cert update: sslContext should still be the same + watcherCaptor[0].updateTrustedRoots( + ImmutableList.of(getCertFromResourceName(SERVER_0_PEM_FILE))); + assertThat(provider.savedKey).isNull(); + assertThat(provider.savedCertChain).isNull(); + assertThat(provider.savedTrustedRoots).isNotNull(); + testCallback1 = CommonTlsContextTestsUtil.getValueThruCallback(provider); + assertThat(testCallback1.updatedSslContext).isSameInstanceAs(testCallback.updatedSslContext); + + // now update id cert: sslContext should be updated i.e.different from the previous one + watcherCaptor[0].updateCertificate( + CommonCertProviderTestUtils.getPrivateKey(SERVER_1_KEY_FILE), + ImmutableList.of(getCertFromResourceName(SERVER_1_PEM_FILE))); + assertThat(provider.savedKey).isNull(); + assertThat(provider.savedCertChain).isNull(); + assertThat(provider.savedTrustedRoots).isNull(); + assertThat(provider.getSslContext()).isNotNull(); + testCallback1 = CommonTlsContextTestsUtil.getValueThruCallback(provider); + assertThat(testCallback1.updatedSslContext).isNotSameInstanceAs(testCallback.updatedSslContext); + } + + @Test + public void testProviderForClient_queueExecutor() throws Exception { + final CertificateProvider.DistributorWatcher[] watcherCaptor = + new CertificateProvider.DistributorWatcher[1]; + TestCertificateProvider.createAndRegisterProviderProvider( + certificateProviderRegistry, watcherCaptor, "testca", 0); + CertProviderClientSslContextProvider provider = + getSslContextProvider( + "gcp_id", + "gcp_id", + CommonCertProviderTestUtils.getTestBootstrapInfo(), + /* alpnProtocols= */ null, + /* staticCertValidationContext= */ null); + QueuedExecutor queuedExecutor = new QueuedExecutor(); + + TestCallback testCallback = + CommonTlsContextTestsUtil.getValueThruCallback(provider, queuedExecutor); + assertThat(queuedExecutor.runQueue).isEmpty(); + + // now generate cert update + watcherCaptor[0].updateCertificate( + CommonCertProviderTestUtils.getPrivateKey(CLIENT_KEY_FILE), + ImmutableList.of(getCertFromResourceName(CLIENT_PEM_FILE))); + assertThat(queuedExecutor.runQueue).isEmpty(); // still empty + + // now generate root cert update + watcherCaptor[0].updateTrustedRoots(ImmutableList.of(getCertFromResourceName(CA_PEM_FILE))); + assertThat(queuedExecutor.runQueue).hasSize(1); + queuedExecutor.drain(); + + doChecksOnSslContext(false, testCallback.updatedSslContext, /* expectedApnProtos= */ null); + } + + @Test + public void testProviderForClient_tls() throws Exception { + final CertificateProvider.DistributorWatcher[] watcherCaptor = + new CertificateProvider.DistributorWatcher[1]; + TestCertificateProvider.createAndRegisterProviderProvider( + certificateProviderRegistry, watcherCaptor, "testca", 0); + CertProviderClientSslContextProvider provider = + getSslContextProvider( + /* certInstanceName= */ null, + "gcp_id", + CommonCertProviderTestUtils.getTestBootstrapInfo(), + /* alpnProtocols= */ null, + /* staticCertValidationContext= */ null); + + assertThat(provider.savedKey).isNull(); + assertThat(provider.savedCertChain).isNull(); + assertThat(provider.savedTrustedRoots).isNull(); + assertThat(provider.getSslContext()).isNull(); + + // now generate root cert update + watcherCaptor[0].updateTrustedRoots(ImmutableList.of(getCertFromResourceName(CA_PEM_FILE))); + assertThat(provider.getSslContext()).isNotNull(); + assertThat(provider.savedKey).isNull(); + assertThat(provider.savedCertChain).isNull(); + assertThat(provider.savedTrustedRoots).isNull(); + + TestCallback testCallback = + CommonTlsContextTestsUtil.getValueThruCallback(provider); + + doChecksOnSslContext(false, testCallback.updatedSslContext, /* expectedApnProtos= */ null); + } + + @Test + public void testProviderForClient_sslContextException_onError() throws Exception { + CertificateValidationContext staticCertValidationContext = + CertificateValidationContext.newBuilder() + .setTrustedCa(DataSource.newBuilder().setInlineString("foo")) + .build(); + + final CertificateProvider.DistributorWatcher[] watcherCaptor = + new CertificateProvider.DistributorWatcher[1]; + TestCertificateProvider.createAndRegisterProviderProvider( + certificateProviderRegistry, watcherCaptor, "testca", 0); + CertProviderClientSslContextProvider provider = + getSslContextProvider( + /* certInstanceName= */ null, + "gcp_id", + CommonCertProviderTestUtils.getTestBootstrapInfo(), + /* alpnProtocols= */null, + staticCertValidationContext); + + TestCallback testCallback = new TestCallback(MoreExecutors.directExecutor()); + provider.addCallback(testCallback); + try { + watcherCaptor[0].updateTrustedRoots(ImmutableList.of(getCertFromResourceName(CA_PEM_FILE))); + fail("exception expected"); + } catch (RuntimeException expected) { + assertThat(expected) + .hasMessageThat() + .contains("only static certificateValidationContext expected"); + } + assertThat(testCallback.updatedThrowable).isNotNull(); + assertThat(testCallback.updatedThrowable) + .hasCauseThat() + .hasMessageThat() + .contains("only static certificateValidationContext expected"); + } + + @Test + public void testProviderForClient_rootInstanceNull_expectError() throws Exception { + final CertificateProvider.DistributorWatcher[] watcherCaptor = + new CertificateProvider.DistributorWatcher[1]; + TestCertificateProvider.createAndRegisterProviderProvider( + certificateProviderRegistry, watcherCaptor, "testca", 0); + try { + getSslContextProvider( + /* certInstanceName= */ null, + /* rootInstanceName= */ null, + CommonCertProviderTestUtils.getTestBootstrapInfo(), + /* alpnProtocols= */ null, + /* staticCertValidationContext= */ null); + fail("exception expected"); + } catch (NullPointerException expected) { + assertThat(expected).hasMessageThat().contains("Client SSL requires rootCertInstance"); + } + } + + static class QueuedExecutor implements Executor { + /** A list of Runnables to be run in order. */ + private final Queue runQueue = new ConcurrentLinkedQueue<>(); + + @Override + public synchronized void execute(Runnable r) { + runQueue.add(checkNotNull(r, "'r' must not be null.")); + } + + public synchronized void drain() { + Runnable r; + while ((r = runQueue.poll()) != null) { + try { + r.run(); + } catch (RuntimeException e) { + // Log it and keep going. + logger.log(Level.SEVERE, "Exception while executing runnable " + r, e); + } + } + } + } + +} diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/CertificateProviderStoreTest.java b/xds/src/test/java/io/grpc/xds/internal/certprovider/CertificateProviderStoreTest.java index 569d72bf43d..53144c2d48e 100644 --- a/xds/src/test/java/io/grpc/xds/internal/certprovider/CertificateProviderStoreTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/certprovider/CertificateProviderStoreTest.java @@ -49,36 +49,6 @@ public class CertificateProviderStoreTest { private CertificateProviderStore certificateProviderStore; private boolean throwExceptionForCertUpdates; - private class TestCertificateProvider extends CertificateProvider { - Object config; - CertificateProviderProvider certProviderProvider; - int closeCalled = 0; - int startCalled = 0; - - protected TestCertificateProvider( - CertificateProvider.DistributorWatcher watcher, - boolean notifyCertUpdates, - Object config, - CertificateProviderProvider certificateProviderProvider) { - super(watcher, notifyCertUpdates); - if (throwExceptionForCertUpdates && notifyCertUpdates) { - throw new UnsupportedOperationException("Provider does not support Certificate Updates."); - } - this.config = config; - this.certProviderProvider = certificateProviderProvider; - } - - @Override - public void close() { - closeCalled++; - } - - @Override - public void start() { - startCalled++; - } - } - @Before public void setUp() { certificateProviderRegistry = new CertificateProviderRegistry(); @@ -94,7 +64,7 @@ public void pluginNotRegistered_expectException() { "cert-name1", "plugin1", "config", mockWatcher, true); fail("exception expected"); } catch (IllegalArgumentException expected) { - assertThat(expected).hasMessageThat().isEqualTo("Provider not found."); + assertThat(expected).hasMessageThat().isEqualTo("Provider not found for plugin1"); } } @@ -111,7 +81,7 @@ public void pluginUnregistered_expectException() { "cert-name1", "plugin1", "config", mockWatcher, true); fail("exception expected"); } catch (IllegalArgumentException expected) { - assertThat(expected).hasMessageThat().isEqualTo("Provider not found."); + assertThat(expected).hasMessageThat().isEqualTo("Provider not found for plugin1"); } } @@ -369,7 +339,8 @@ public CertificateProvider answer(InvocationOnMock invocation) throws Throwable (CertificateProvider.DistributorWatcher) args[1]; boolean notifyCertUpdates = (Boolean) args[2]; return new TestCertificateProvider( - watcher, notifyCertUpdates, config, certProviderProvider); + watcher, notifyCertUpdates, config, certProviderProvider, + throwExceptionForCertUpdates); } }); certificateProviderRegistry.register(certProviderProvider); diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/CommonCertProviderTestUtils.java b/xds/src/test/java/io/grpc/xds/internal/certprovider/CommonCertProviderTestUtils.java new file mode 100644 index 00000000000..3a056c209fe --- /dev/null +++ b/xds/src/test/java/io/grpc/xds/internal/certprovider/CommonCertProviderTestUtils.java @@ -0,0 +1,177 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds.internal.certprovider; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.io.CharStreams; +import io.grpc.internal.testing.TestUtils; +import io.grpc.xds.Bootstrapper; +import io.grpc.xds.internal.sds.trust.CertificateUtils; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.handler.codec.base64.Base64; +import io.netty.util.CharsetUtil; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Reader; +import java.security.KeyException; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class CommonCertProviderTestUtils { + private static final Logger logger = + Logger.getLogger(CommonCertProviderTestUtils.class.getName()); + + private static final Pattern KEY_PATTERN = Pattern.compile( + "-+BEGIN\\s+.*PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+" + // Header + "([a-z0-9+/=\\r\\n]+)" + // Base64 text + "-+END\\s+.*PRIVATE\\s+KEY[^-]*-+", // Footer + Pattern.CASE_INSENSITIVE); + + static Bootstrapper.BootstrapInfo getTestBootstrapInfo() throws IOException { + String rawData = + "{\n" + + " \"xds_servers\": [],\n" + + " \"certificate_providers\": {\n" + + " \"gcp_id\": {\n" + + " \"plugin_name\": \"testca\",\n" + + " \"config\": {\n" + + " \"server\": {\n" + + " \"api_type\": \"GRPC\",\n" + + " \"grpc_services\": [{\n" + + " \"google_grpc\": {\n" + + " \"target_uri\": \"meshca.com\",\n" + + " \"channel_credentials\": {\"google_default\": {}},\n" + + " \"call_credentials\": [{\n" + + " \"sts_service\": {\n" + + " \"token_exchange_service\": \"securetoken.googleapis.com\",\n" + + " \"subject_token_path\": \"/etc/secret/sajwt.token\"\n" + + " }\n" + + " }]\n" // end call_credentials + + " },\n" // end google_grpc + + " \"time_out\": {\"seconds\": 10}\n" + + " }]\n" // end grpc_services + + " },\n" // end server + + " \"certificate_lifetime\": {\"seconds\": 86400},\n" + + " \"renewal_grace_period\": {\"seconds\": 3600},\n" + + " \"key_type\": \"RSA\",\n" + + " \"key_size\": 2048,\n" + + " \"location\": \"https://container.googleapis.com/v1/project/test-project1/locations/test-zone2/clusters/test-cluster3\"\n" + + " }\n" // end config + + " },\n" // end gcp_id + + " \"file_provider\": {\n" + + " \"plugin_name\": \"file_watcher\",\n" + + " \"config\": {\"path\": \"/etc/secret/certs\"}\n" + + " }\n" + + " }\n" + + "}"; + return Bootstrapper.parseConfig(rawData); + } + + static PrivateKey getPrivateKey(String resourceName) + throws Exception { + InputStream inputStream = TestUtils.class.getResourceAsStream("/certs/" + resourceName); + ByteBuf encodedKeyBuf = readPrivateKey(inputStream); + + byte[] encodedKey = new byte[encodedKeyBuf.readableBytes()]; + encodedKeyBuf.readBytes(encodedKey).release(); + PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(encodedKey); + try { + return KeyFactory.getInstance("RSA").generatePrivate(spec); + } catch (InvalidKeySpecException ignore) { + try { + return KeyFactory.getInstance("DSA").generatePrivate(spec); + } catch (InvalidKeySpecException ignore2) { + try { + return KeyFactory.getInstance("EC").generatePrivate(spec); + } catch (InvalidKeySpecException e) { + throw new InvalidKeySpecException("Neither RSA, DSA nor EC worked", e); + } + } + } + } + + static ByteBuf readPrivateKey(InputStream in) throws KeyException { + String content; + try { + content = readContent(in); + } catch (IOException e) { + throw new KeyException("failed to read key input stream", e); + } + Matcher m = KEY_PATTERN.matcher(content); + if (!m.find()) { + throw new KeyException("could not find a PKCS #8 private key in input stream"); + } + ByteBuf base64 = Unpooled.copiedBuffer(m.group(1), CharsetUtil.US_ASCII); + ByteBuf der = Base64.decode(base64); + base64.release(); + return der; + } + + private static String readContent(InputStream in) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + byte[] buf = new byte[8192]; + for (; ; ) { + int ret = in.read(buf); + if (ret < 0) { + break; + } + out.write(buf, 0, ret); + } + return out.toString(CharsetUtil.US_ASCII.name()); + } finally { + safeClose(out); + } + } + + private static void safeClose(OutputStream out) { + try { + out.close(); + } catch (IOException e) { + logger.log(Level.WARNING, "Failed to close a stream.", e); + } + } + + static X509Certificate getCertFromResourceName(String resourceName) + throws IOException, CertificateException { + return CertificateUtils.toX509Certificate( + new ByteArrayInputStream(getResourceContents(resourceName).getBytes(UTF_8))); + } + + private static String getResourceContents(String resourceName) throws IOException { + InputStream inputStream = TestUtils.class.getResourceAsStream("/certs/" + resourceName); + String text = null; + try (Reader reader = new InputStreamReader(inputStream, UTF_8)) { + text = CharStreams.toString(reader); + } + return text; + } +} diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/TestCertificateProvider.java b/xds/src/test/java/io/grpc/xds/internal/certprovider/TestCertificateProvider.java new file mode 100644 index 00000000000..406ae4f0bb2 --- /dev/null +++ b/xds/src/test/java/io/grpc/xds/internal/certprovider/TestCertificateProvider.java @@ -0,0 +1,84 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds.internal.certprovider; + +public class TestCertificateProvider extends CertificateProvider { + Object config; + CertificateProviderProvider certProviderProvider; + int closeCalled = 0; + int startCalled = 0; + + TestCertificateProvider( + DistributorWatcher watcher, + boolean notifyCertUpdates, + Object config, + CertificateProviderProvider certificateProviderProvider, + boolean throwExceptionForCertUpdates) { + super(watcher, notifyCertUpdates); + if (throwExceptionForCertUpdates && notifyCertUpdates) { + throw new UnsupportedOperationException("Provider does not support Certificate Updates."); + } + this.config = config; + this.certProviderProvider = certificateProviderProvider; + } + + @Override + public void close() { + closeCalled++; + } + + @Override + public void start() { + startCalled++; + } + + static void createAndRegisterProviderProvider( + CertificateProviderRegistry certificateProviderRegistry, + final CertificateProvider.DistributorWatcher[] watcherCaptor, + String testca, + final int index) { + final CertificateProviderProvider mockProviderProviderTestCa = + new TestCertificateProviderProvider(testca, watcherCaptor, index); + certificateProviderRegistry.register(mockProviderProviderTestCa); + } + + private static class TestCertificateProviderProvider implements CertificateProviderProvider { + + private final String testCa; + private final CertificateProvider.DistributorWatcher[] watcherCaptor; + private final int index; + + TestCertificateProviderProvider( + String testCa, CertificateProvider.DistributorWatcher[] watcherCaptor, int index) { + this.testCa = testCa; + this.watcherCaptor = watcherCaptor; + this.index = index; + } + + @Override + public String getName() { + return testCa; + } + + @Override + public CertificateProvider createCertificateProvider( + Object config, DistributorWatcher watcher, boolean notifyCertUpdates) { + watcherCaptor[index] = watcher; + return new TestCertificateProvider(watcher, true, config, this, false); + } + } +} diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/CommonTlsContextTestsUtil.java b/xds/src/test/java/io/grpc/xds/internal/sds/CommonTlsContextTestsUtil.java index afa57f96c46..5f769ae950e 100644 --- a/xds/src/test/java/io/grpc/xds/internal/sds/CommonTlsContextTestsUtil.java +++ b/xds/src/test/java/io/grpc/xds/internal/sds/CommonTlsContextTestsUtil.java @@ -16,10 +16,12 @@ package io.grpc.xds.internal.sds; +import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.base.Strings; import com.google.common.io.CharStreams; +import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.BoolValue; import com.google.protobuf.Struct; import com.google.protobuf.Value; @@ -40,6 +42,7 @@ import io.grpc.internal.testing.TestUtils; import io.grpc.xds.EnvoyServerProtoData; import io.grpc.xds.internal.sds.trust.CertificateUtils; +import io.netty.handler.ssl.SslContext; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -48,6 +51,8 @@ import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Executor; import javax.annotation.Nullable; /** Utility class for client and server ssl provider tests. */ @@ -461,4 +466,122 @@ public static String getResourceContents(String resourceName) throws IOException } return text; } + + private static CommonTlsContext buildCommonTlsContextForCertProviderInstance( + String certInstanceName, + String certName, + String rootInstanceName, + String rootCertName, + Iterable alpnProtocols, + CertificateValidationContext staticCertValidationContext) { + CommonTlsContext.Builder builder = CommonTlsContext.newBuilder(); + if (certInstanceName != null) { + builder = + builder.setTlsCertificateCertificateProviderInstance( + CommonTlsContext.CertificateProviderInstance.newBuilder() + .setInstanceName(certInstanceName) + .setCertificateName(certName)); + } + builder = + addCertificateValidationContext( + builder, rootInstanceName, rootCertName, staticCertValidationContext); + if (alpnProtocols != null) { + builder.addAllAlpnProtocols(alpnProtocols); + } + return builder.build(); + } + + private static CommonTlsContext.Builder addCertificateValidationContext( + CommonTlsContext.Builder builder, + String rootInstanceName, + String rootCertName, + CertificateValidationContext staticCertValidationContext) { + if (rootInstanceName != null) { + CommonTlsContext.CertificateProviderInstance.Builder providerInstanceBuilder = + CommonTlsContext.CertificateProviderInstance.newBuilder() + .setInstanceName(rootInstanceName) + .setCertificateName(rootCertName); + if (staticCertValidationContext != null) { + CombinedCertificateValidationContext combined = + CombinedCertificateValidationContext.newBuilder() + .setDefaultValidationContext(staticCertValidationContext) + .setValidationContextCertificateProviderInstance(providerInstanceBuilder) + .build(); + return builder.setCombinedValidationContext(combined); + } + builder = builder.setValidationContextCertificateProviderInstance(providerInstanceBuilder); + } + return builder; + } + + /** Helper method to build UpstreamTlsContext for CertProvider tests. */ + public static EnvoyServerProtoData.UpstreamTlsContext + buildUpstreamTlsContextForCertProviderInstance( + @Nullable String certInstanceName, + @Nullable String certName, + @Nullable String rootInstanceName, + @Nullable String rootCertName, + Iterable alpnProtocols, + CertificateValidationContext staticCertValidationContext) { + return buildUpstreamTlsContext( + buildCommonTlsContextForCertProviderInstance( + certInstanceName, + certName, + rootInstanceName, + rootCertName, + alpnProtocols, + staticCertValidationContext)); + } + + /** Perform some simple checks on sslContext. */ + public static void doChecksOnSslContext(boolean server, SslContext sslContext, + List expectedApnProtos) { + if (server) { + assertThat(sslContext.isServer()).isTrue(); + } else { + assertThat(sslContext.isClient()).isTrue(); + } + List apnProtos = sslContext.applicationProtocolNegotiator().protocols(); + assertThat(apnProtos).isNotNull(); + if (expectedApnProtos != null) { + assertThat(apnProtos).isEqualTo(expectedApnProtos); + } else { + assertThat(apnProtos).contains("h2"); + } + } + + /** + * Helper method to get the value thru directExecutor callback. Because of directExecutor this is + * a synchronous callback - so need to provide a listener. + */ + public static TestCallback getValueThruCallback(SslContextProvider provider) { + return getValueThruCallback(provider, MoreExecutors.directExecutor()); + } + + /** Helper method to get the value thru callback with a user passed executor. */ + public static TestCallback getValueThruCallback(SslContextProvider provider, Executor executor) { + TestCallback testCallback = new TestCallback(executor); + provider.addCallback(testCallback); + return testCallback; + } + + public static class TestCallback extends SslContextProvider.Callback { + + public SslContext updatedSslContext; + public Throwable updatedThrowable; + + public TestCallback(Executor executor) { + super(executor); + } + + @Override + public void updateSecret(SslContext sslContext) { + updatedSslContext = sslContext; + } + + @Override + public void onException(Throwable throwable) { + updatedThrowable = throwable; + } + } } diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/SdsSslContextProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/sds/SdsSslContextProviderTest.java index 62779ec0a90..6ecb533e280 100644 --- a/xds/src/test/java/io/grpc/xds/internal/sds/SdsSslContextProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/sds/SdsSslContextProviderTest.java @@ -22,9 +22,10 @@ import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_KEY_FILE; import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; +import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.doChecksOnSslContext; +import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.getValueThruCallback; import static io.grpc.xds.internal.sds.SdsClientTest.getOneCertificateValidationContextSecret; import static io.grpc.xds.internal.sds.SdsClientTest.getOneTlsCertSecret; -import static io.grpc.xds.internal.sds.SecretVolumeSslContextProviderTest.doChecksOnSslContext; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -33,6 +34,7 @@ import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; import io.envoyproxy.envoy.type.matcher.v3.StringMatcher; import io.grpc.Status.Code; +import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.TestCallback; import java.io.IOException; import java.util.Arrays; import org.junit.After; @@ -123,8 +125,7 @@ public void testProviderForServer() throws IOException { SdsServerSslContextProvider provider = getSdsServerSslContextProvider("cert1", "valid1", null, null); - SecretVolumeSslContextProviderTest.TestCallback testCallback = - SecretVolumeSslContextProviderTest.getValueThruCallback(provider); + TestCallback testCallback = getValueThruCallback(provider); doChecksOnSslContext(true, testCallback.updatedSslContext, /* expectedApnProtos= */ null); } @@ -142,8 +143,7 @@ public void testProviderForClient() throws IOException { /* validationContextName= */ "valid1", /* matchSubjectAltNames= */ null, /* alpnProtocols= */ null); - SecretVolumeSslContextProviderTest.TestCallback testCallback = - SecretVolumeSslContextProviderTest.getValueThruCallback(provider); + TestCallback testCallback = getValueThruCallback(provider); doChecksOnSslContext(false, testCallback.updatedSslContext, /* expectedApnProtos= */ null); } @@ -159,8 +159,7 @@ public void testProviderForServer_onlyCert() throws IOException { /* validationContextName= */ null, /* matchSubjectAltNames= */ null, /* alpnProtocols= */ null); - SecretVolumeSslContextProviderTest.TestCallback testCallback = - SecretVolumeSslContextProviderTest.getValueThruCallback(provider); + TestCallback testCallback = getValueThruCallback(provider); doChecksOnSslContext(true, testCallback.updatedSslContext, /* expectedApnProtos= */ null); } @@ -176,8 +175,7 @@ public void getProviderForClient_onlyTrust() throws IOException { /* validationContextName= */ "valid1", /* matchSubjectAltNames= */ null, null); - SecretVolumeSslContextProviderTest.TestCallback testCallback = - SecretVolumeSslContextProviderTest.getValueThruCallback(provider); + TestCallback testCallback = getValueThruCallback(provider); doChecksOnSslContext(false, testCallback.updatedSslContext, /* expectedApnProtos= */ null); } @@ -193,8 +191,7 @@ public void getProviderForServer_noCert_throwsException() throws IOException { /* validationContextName= */ "valid1", /* matchSubjectAltNames= */ null, /* alpnProtocols= */ null); - SecretVolumeSslContextProviderTest.TestCallback testCallback = - SecretVolumeSslContextProviderTest.getValueThruCallback(provider); + TestCallback testCallback = getValueThruCallback(provider); assertThat(server.lastNack).isNotNull(); assertThat(server.lastNack.getVersionInfo()).isEmpty(); @@ -222,8 +219,7 @@ public void testProviderForClient_withSubjectAltNames() throws IOException { .build()), /* alpnProtocols= */ null); - SecretVolumeSslContextProviderTest.TestCallback testCallback = - SecretVolumeSslContextProviderTest.getValueThruCallback(provider); + TestCallback testCallback = getValueThruCallback(provider); doChecksOnSslContext(false, testCallback.updatedSslContext, /* expectedApnProtos= */ null); } @@ -240,8 +236,7 @@ public void testProviderForClient_withAlpnProtocols() throws IOException { /* validationContextName= */ "valid1", /* matchSubjectAltNames= */ null, /* alpnProtocols= */ Arrays.asList("managed-mtls", "h2")); - SecretVolumeSslContextProviderTest.TestCallback testCallback = - SecretVolumeSslContextProviderTest.getValueThruCallback(provider); + TestCallback testCallback = getValueThruCallback(provider); doChecksOnSslContext( false, testCallback.updatedSslContext, Arrays.asList("managed-mtls", "h2")); @@ -260,8 +255,7 @@ public void testProviderForServer_withAlpnProtocols() throws IOException { /* validationContextName= */ "valid1", /* matchSubjectAltNames= */ null, /* alpnProtocols= */ Arrays.asList("managed-mtls", "h2")); - SecretVolumeSslContextProviderTest.TestCallback testCallback = - SecretVolumeSslContextProviderTest.getValueThruCallback(provider); + TestCallback testCallback = getValueThruCallback(provider); doChecksOnSslContext( true, testCallback.updatedSslContext, Arrays.asList("managed-mtls", "h2")); diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/SecretVolumeSslContextProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/sds/SecretVolumeSslContextProviderTest.java index 551b8a7f6d7..44a5c461dc7 100644 --- a/xds/src/test/java/io/grpc/xds/internal/sds/SecretVolumeSslContextProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/sds/SecretVolumeSslContextProviderTest.java @@ -22,16 +22,17 @@ import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_KEY_FILE; import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; +import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.doChecksOnSslContext; +import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.getValueThruCallback; -import com.google.common.util.concurrent.MoreExecutors; import io.envoyproxy.envoy.config.core.v3.DataSource; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.TlsCertificate; +import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.TestCallback; import io.netty.handler.ssl.SslContext; import java.io.IOException; import java.security.cert.CertStoreException; import java.security.cert.CertificateException; -import java.util.List; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; @@ -371,22 +372,6 @@ private static void sslContextForEitherWithBothCertAndTrust( doChecksOnSslContext(server, sslContext, /* expectedApnProtos= */ null); } - static void doChecksOnSslContext(boolean server, SslContext sslContext, - List expectedApnProtos) { - if (server) { - assertThat(sslContext.isServer()).isTrue(); - } else { - assertThat(sslContext.isClient()).isTrue(); - } - List apnProtos = sslContext.applicationProtocolNegotiator().protocols(); - assertThat(apnProtos).isNotNull(); - if (expectedApnProtos != null) { - assertThat(apnProtos).isEqualTo(expectedApnProtos); - } else { - assertThat(apnProtos).contains("h2"); - } - } - @Test public void getProviderForServer() throws IOException, CertificateException, CertStoreException { sslContextForEitherWithBothCertAndTrust( @@ -421,32 +406,6 @@ public void getProviderForServer_badFile_throwsException() } } - static class TestCallback implements SslContextProvider.Callback { - - SslContext updatedSslContext; - Throwable updatedThrowable; - - @Override - public void updateSecret(SslContext sslContext) { - updatedSslContext = sslContext; - } - - @Override - public void onException(Throwable throwable) { - updatedThrowable = throwable; - } - } - - /** - * Helper method to get the value thru directExecutor callback. Because of directExecutor this is - * a synchronous callback - so need to provide a listener. - */ - static TestCallback getValueThruCallback(SslContextProvider provider) { - TestCallback testCallback = new TestCallback(); - provider.addCallback(testCallback, MoreExecutors.directExecutor()); - return testCallback; - } - @Test public void getProviderForServer_both_callsback() throws IOException { SecretVolumeServerSslContextProvider provider = diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/trust/SdsTrustManagerFactoryTest.java b/xds/src/test/java/io/grpc/xds/internal/sds/trust/SdsTrustManagerFactoryTest.java index 53ab963eb15..47ac9e6bb42 100644 --- a/xds/src/test/java/io/grpc/xds/internal/sds/trust/SdsTrustManagerFactoryTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/sds/trust/SdsTrustManagerFactoryTest.java @@ -26,6 +26,7 @@ import com.google.protobuf.ByteString; import io.envoyproxy.envoy.config.core.v3.DataSource; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; +import io.envoyproxy.envoy.type.matcher.v3.StringMatcher; import io.grpc.internal.testing.TestUtils; import java.io.IOException; import java.security.cert.CertStoreException; @@ -80,6 +81,100 @@ public void constructor_fromInlineBytes() .isEqualTo(CertificateUtils.toX509Certificates(TestUtils.loadCert(CA_PEM_FILE))[0]); } + @Test + public void constructor_fromRootCert() + throws CertificateException, IOException, CertStoreException { + X509Certificate x509Cert = TestUtils.loadX509Cert(CA_PEM_FILE); + CertificateValidationContext staticValidationContext = buildStaticValidationContext("san1", + "san2"); + SdsTrustManagerFactory factory = + new SdsTrustManagerFactory(new X509Certificate[]{x509Cert}, staticValidationContext); + assertThat(factory).isNotNull(); + TrustManager[] tms = factory.getTrustManagers(); + assertThat(tms).isNotNull(); + assertThat(tms).hasLength(1); + TrustManager myTm = tms[0]; + assertThat(myTm).isInstanceOf(SdsX509TrustManager.class); + SdsX509TrustManager sdsX509TrustManager = (SdsX509TrustManager) myTm; + X509Certificate[] acceptedIssuers = sdsX509TrustManager.getAcceptedIssuers(); + assertThat(acceptedIssuers).isNotNull(); + assertThat(acceptedIssuers).hasLength(1); + X509Certificate caCert = acceptedIssuers[0]; + assertThat(caCert) + .isEqualTo(CertificateUtils.toX509Certificates(TestUtils.loadCert(CA_PEM_FILE))[0]); + } + + @Test + public void constructorRootCert_checkServerTrusted() + throws CertificateException, IOException, CertStoreException { + X509Certificate x509Cert = TestUtils.loadX509Cert(CA_PEM_FILE); + CertificateValidationContext staticValidationContext = buildStaticValidationContext("san1", + "waterzooi.test.google.be"); + SdsTrustManagerFactory factory = + new SdsTrustManagerFactory(new X509Certificate[]{x509Cert}, staticValidationContext); + SdsX509TrustManager sdsX509TrustManager = (SdsX509TrustManager) factory.getTrustManagers()[0]; + X509Certificate[] serverChain = + CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); + sdsX509TrustManager.checkServerTrusted(serverChain, "RSA"); + } + + @Test + public void constructorRootCert_nonStaticContext_throwsException() + throws CertificateException, IOException, CertStoreException { + X509Certificate x509Cert = TestUtils.loadX509Cert(CA_PEM_FILE); + try { + new SdsTrustManagerFactory( + new X509Certificate[] {x509Cert}, getCertContextFromPath(CA_PEM_FILE)); + Assert.fail("no exception thrown"); + } catch (IllegalArgumentException expected) { + assertThat(expected) + .hasMessageThat() + .contains("only static certificateValidationContext expected"); + } + } + + @Test + public void constructorRootCert_checkServerTrusted_throwsException() + throws CertificateException, IOException, CertStoreException { + X509Certificate x509Cert = TestUtils.loadX509Cert(CA_PEM_FILE); + CertificateValidationContext staticValidationContext = buildStaticValidationContext("san1", + "san2"); + SdsTrustManagerFactory factory = + new SdsTrustManagerFactory(new X509Certificate[]{x509Cert}, staticValidationContext); + SdsX509TrustManager sdsX509TrustManager = (SdsX509TrustManager) factory.getTrustManagers()[0]; + X509Certificate[] serverChain = + CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); + try { + sdsX509TrustManager.checkServerTrusted(serverChain, "RSA"); + Assert.fail("no exception thrown"); + } catch (CertificateException expected) { + assertThat(expected) + .hasMessageThat() + .contains("Peer certificate SAN check failed"); + } + } + + @Test + public void constructorRootCert_checkClientTrusted_throwsException() + throws CertificateException, IOException, CertStoreException { + X509Certificate x509Cert = TestUtils.loadX509Cert(CA_PEM_FILE); + CertificateValidationContext staticValidationContext = buildStaticValidationContext("san1", + "san2"); + SdsTrustManagerFactory factory = + new SdsTrustManagerFactory(new X509Certificate[]{x509Cert}, staticValidationContext); + SdsX509TrustManager sdsX509TrustManager = (SdsX509TrustManager) factory.getTrustManagers()[0]; + X509Certificate[] clientChain = + CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE)); + try { + sdsX509TrustManager.checkClientTrusted(clientChain, "RSA"); + Assert.fail("no exception thrown"); + } catch (CertificateException expected) { + assertThat(expected) + .hasMessageThat() + .contains("Peer certificate SAN check failed"); + } + } + @Test public void checkServerTrusted_goodCert() throws CertificateException, IOException, CertStoreException { @@ -156,4 +251,13 @@ private static final CertificateValidationContext getCertContextFromPathAsInline DataSource.newBuilder().setInlineBytes(ByteString.copyFrom(x509Cert.getEncoded()))) .build(); } + + private static final CertificateValidationContext buildStaticValidationContext( + String... verifySans) { + CertificateValidationContext.Builder builder = CertificateValidationContext.newBuilder(); + for (String san : verifySans) { + builder.addMatchSubjectAltNames(StringMatcher.newBuilder().setExact(san)); + } + return builder.build(); + } } From ee9109eceddd57db955ec1bff803c918e8ce7b75 Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Mon, 17 Aug 2020 17:06:29 -0700 Subject: [PATCH 67/88] xds: add CertProviderServerSslContextProvider support (#7331) --- .../CertProviderServerSslContextProvider.java | 126 ++++++++ .../sds/SdsServerSslContextProvider.java | 7 +- .../SecretVolumeServerSslContextProvider.java | 4 +- .../xds/internal/sds/SslContextProvider.java | 7 +- ...tProviderClientSslContextProviderTest.java | 3 +- ...tProviderServerSslContextProviderTest.java | 278 ++++++++++++++++++ .../sds/CommonTlsContextTestsUtil.java | 21 ++ 7 files changed, 439 insertions(+), 7 deletions(-) create mode 100644 xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProvider.java create mode 100644 xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProviderTest.java diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProvider.java new file mode 100644 index 00000000000..5d1a6399b88 --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProvider.java @@ -0,0 +1,126 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds.internal.certprovider; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.VisibleForTesting; +import io.envoyproxy.envoy.config.core.v3.Node; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext.CombinedCertificateValidationContext; +import io.grpc.netty.GrpcSslContexts; +import io.grpc.xds.Bootstrapper.CertificateProviderInfo; +import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; +import io.grpc.xds.internal.sds.trust.SdsTrustManagerFactory; +import io.netty.handler.ssl.SslContextBuilder; + +import java.io.IOException; +import java.security.cert.CertStoreException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Map; + +/** A server SslContext provider using CertificateProviderInstance to fetch secrets. */ +final class CertProviderServerSslContextProvider extends CertProviderSslContextProvider { + + private CertProviderServerSslContextProvider( + Node node, + Map certProviders, + CommonTlsContext.CertificateProviderInstance certInstance, + CommonTlsContext.CertificateProviderInstance rootCertInstance, + CertificateValidationContext staticCertValidationContext, + DownstreamTlsContext downstreamTlsContext, + CertificateProviderStore certificateProviderStore) { + super( + node, + certProviders, + checkNotNull(certInstance, "Server SSL requires certInstance"), + rootCertInstance, + staticCertValidationContext, + downstreamTlsContext, + certificateProviderStore); + } + + @Override + protected final SslContextBuilder getSslContextBuilder( + CertificateValidationContext certificateValidationContextdationContext) + throws CertStoreException, CertificateException, IOException { + SslContextBuilder sslContextBuilder = SslContextBuilder.forServer(savedKey, savedCertChain); + setClientAuthValues( + sslContextBuilder, + isMtls() + ? new SdsTrustManagerFactory( + savedTrustedRoots.toArray(new X509Certificate[0]), + certificateValidationContextdationContext) + : null); + sslContextBuilder = GrpcSslContexts.configure(sslContextBuilder); + return sslContextBuilder; + } + + /** Creates CertProviderServerSslContextProvider. */ + static final class Factory { + private static final Factory DEFAULT_INSTANCE = + new Factory(CertificateProviderStore.getInstance()); + private final CertificateProviderStore certificateProviderStore; + + @VisibleForTesting Factory(CertificateProviderStore certificateProviderStore) { + this.certificateProviderStore = certificateProviderStore; + } + + static Factory getInstance() { + return DEFAULT_INSTANCE; + } + + CertProviderServerSslContextProvider getProvider( + DownstreamTlsContext downstreamTlsContext, + Node node, + Map certProviders) { + checkNotNull(downstreamTlsContext, "downstreamTlsContext"); + CommonTlsContext commonTlsContext = downstreamTlsContext.getCommonTlsContext(); + CommonTlsContext.CertificateProviderInstance rootCertInstance = null; + CertificateValidationContext staticCertValidationContext = null; + if (commonTlsContext.hasCombinedValidationContext()) { + CombinedCertificateValidationContext combinedValidationContext = + commonTlsContext.getCombinedValidationContext(); + if (combinedValidationContext.hasValidationContextCertificateProviderInstance()) { + rootCertInstance = + combinedValidationContext.getValidationContextCertificateProviderInstance(); + } + if (combinedValidationContext.hasDefaultValidationContext()) { + staticCertValidationContext = combinedValidationContext.getDefaultValidationContext(); + } + } else if (commonTlsContext.hasValidationContextCertificateProviderInstance()) { + rootCertInstance = commonTlsContext.getValidationContextCertificateProviderInstance(); + } else if (commonTlsContext.hasValidationContext()) { + staticCertValidationContext = commonTlsContext.getValidationContext(); + } + CommonTlsContext.CertificateProviderInstance certInstance = null; + if (commonTlsContext.hasTlsCertificateCertificateProviderInstance()) { + certInstance = commonTlsContext.getTlsCertificateCertificateProviderInstance(); + } + return new CertProviderServerSslContextProvider( + node, + certProviders, + certInstance, + rootCertInstance, + staticCertValidationContext, + downstreamTlsContext, + certificateProviderStore); + } + } +} diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/SdsServerSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/sds/SdsServerSslContextProvider.java index 27afaa455e3..649309dc677 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/SdsServerSslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/SdsServerSslContextProvider.java @@ -24,6 +24,7 @@ import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.SdsSecretConfig; import io.grpc.netty.GrpcSslContexts; import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; +import io.grpc.xds.internal.sds.trust.SdsTrustManagerFactory; import io.netty.handler.ssl.SslContextBuilder; import java.io.IOException; import java.security.cert.CertStoreException; @@ -85,7 +86,11 @@ protected final SslContextBuilder getSslContextBuilder( tlsCertificate.hasPassword() ? tlsCertificate.getPassword().getInlineString() : null); - setClientAuthValues(sslContextBuilder, localCertValidationContext); + setClientAuthValues( + sslContextBuilder, + localCertValidationContext != null + ? new SdsTrustManagerFactory(localCertValidationContext) + : null); return sslContextBuilder; } } diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/SecretVolumeServerSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/sds/SecretVolumeServerSslContextProvider.java index 3282fc555c3..943b385c6a2 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/SecretVolumeServerSslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/SecretVolumeServerSslContextProvider.java @@ -27,6 +27,7 @@ import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.TlsCertificate; import io.grpc.netty.GrpcSslContexts; import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; +import io.grpc.xds.internal.sds.trust.SdsTrustManagerFactory; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import java.io.File; @@ -108,7 +109,8 @@ SslContext buildSslContextFromSecrets() SslContextBuilder sslContextBuilder = GrpcSslContexts.forServer( new File(certificateChain), new File(privateKey), privateKeyPassword); - setClientAuthValues(sslContextBuilder, certContext); + setClientAuthValues( + sslContextBuilder, certContext != null ? new SdsTrustManagerFactory(certContext) : null); return sslContextBuilder.build(); } } diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/SslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/sds/SslContextProvider.java index 08e93d3a4e6..84060f45abf 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/SslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/SslContextProvider.java @@ -19,7 +19,6 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; -import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; import io.grpc.xds.EnvoyServerProtoData.BaseTlsContext; import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; @@ -70,11 +69,11 @@ protected CommonTlsContext getCommonTlsContext() { } protected void setClientAuthValues( - SslContextBuilder sslContextBuilder, CertificateValidationContext localCertValidationContext) + SslContextBuilder sslContextBuilder, SdsTrustManagerFactory sdsTrustManagerFactory) throws CertificateException, IOException, CertStoreException { DownstreamTlsContext downstreamTlsContext = getDownstreamTlsContext(); - if (localCertValidationContext != null) { - sslContextBuilder.trustManager(new SdsTrustManagerFactory(localCertValidationContext)); + if (sdsTrustManagerFactory != null) { + sslContextBuilder.trustManager(sdsTrustManagerFactory); sslContextBuilder.clientAuth( downstreamTlsContext.isRequireClientCertificate() ? ClientAuth.REQUIRE diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProviderTest.java index 6e9ae15ee5b..f7d8d69bfa1 100644 --- a/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProviderTest.java @@ -28,6 +28,7 @@ import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.doChecksOnSslContext; import static org.junit.Assert.fail; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.MoreExecutors; import io.envoyproxy.envoy.config.core.v3.DataSource; @@ -270,7 +271,7 @@ public void testProviderForClient_rootInstanceNull_expectError() throws Exceptio static class QueuedExecutor implements Executor { /** A list of Runnables to be run in order. */ - private final Queue runQueue = new ConcurrentLinkedQueue<>(); + @VisibleForTesting final Queue runQueue = new ConcurrentLinkedQueue<>(); @Override public synchronized void execute(Runnable r) { diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProviderTest.java new file mode 100644 index 00000000000..3475c6e151a --- /dev/null +++ b/xds/src/test/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProviderTest.java @@ -0,0 +1,278 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds.internal.certprovider; + +import static com.google.common.truth.Truth.assertThat; +import static io.grpc.xds.internal.certprovider.CertProviderClientSslContextProviderTest.QueuedExecutor; +import static io.grpc.xds.internal.certprovider.CommonCertProviderTestUtils.getCertFromResourceName; +import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CA_PEM_FILE; +import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; +import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_0_KEY_FILE; +import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_0_PEM_FILE; +import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_KEY_FILE; +import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; +import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.doChecksOnSslContext; +import static org.junit.Assert.fail; + +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.MoreExecutors; +import io.envoyproxy.envoy.config.core.v3.DataSource; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; +import io.grpc.xds.Bootstrapper; +import io.grpc.xds.EnvoyServerProtoData; +import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil; +import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.TestCallback; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link CertProviderServerSslContextProvider}. */ +@RunWith(JUnit4.class) +public class CertProviderServerSslContextProviderTest { + + CertificateProviderRegistry certificateProviderRegistry; + CertificateProviderStore certificateProviderStore; + private CertProviderServerSslContextProvider.Factory certProviderServerSslContextProviderFactory; + + @Before + public void setUp() throws Exception { + certificateProviderRegistry = new CertificateProviderRegistry(); + certificateProviderStore = new CertificateProviderStore(certificateProviderRegistry); + certProviderServerSslContextProviderFactory = + new CertProviderServerSslContextProvider.Factory(certificateProviderStore); + } + + /** Helper method to build CertProviderServerSslContextProvider. */ + private CertProviderServerSslContextProvider getSslContextProvider( + String certInstanceName, + String rootInstanceName, + Bootstrapper.BootstrapInfo bootstrapInfo, + Iterable alpnProtocols, + CertificateValidationContext staticCertValidationContext, + boolean requireClientCert) { + EnvoyServerProtoData.DownstreamTlsContext downstreamTlsContext = + CommonTlsContextTestsUtil.buildDownstreamTlsContextForCertProviderInstance( + certInstanceName, + "cert-default", + rootInstanceName, + "root-default", + alpnProtocols, + staticCertValidationContext, + requireClientCert); + return certProviderServerSslContextProviderFactory.getProvider( + downstreamTlsContext, + bootstrapInfo.getNode().toEnvoyProtoNode(), + bootstrapInfo.getCertProviders()); + } + + @Test + public void testProviderForServer_mtls() throws Exception { + final CertificateProvider.DistributorWatcher[] watcherCaptor = + new CertificateProvider.DistributorWatcher[1]; + TestCertificateProvider.createAndRegisterProviderProvider( + certificateProviderRegistry, watcherCaptor, "testca", 0); + CertProviderServerSslContextProvider provider = + getSslContextProvider( + "gcp_id", + "gcp_id", + CommonCertProviderTestUtils.getTestBootstrapInfo(), + /* alpnProtocols= */ null, + /* staticCertValidationContext= */ null, + /* requireClientCert= */ true); + + assertThat(provider.savedKey).isNull(); + assertThat(provider.savedCertChain).isNull(); + assertThat(provider.savedTrustedRoots).isNull(); + assertThat(provider.getSslContext()).isNull(); + + // now generate cert update + watcherCaptor[0].updateCertificate( + CommonCertProviderTestUtils.getPrivateKey(SERVER_0_KEY_FILE), + ImmutableList.of(getCertFromResourceName(SERVER_0_PEM_FILE))); + assertThat(provider.savedKey).isNotNull(); + assertThat(provider.savedCertChain).isNotNull(); + assertThat(provider.getSslContext()).isNull(); + + // now generate root cert update + watcherCaptor[0].updateTrustedRoots(ImmutableList.of(getCertFromResourceName(CA_PEM_FILE))); + assertThat(provider.getSslContext()).isNotNull(); + assertThat(provider.savedKey).isNull(); + assertThat(provider.savedCertChain).isNull(); + assertThat(provider.savedTrustedRoots).isNull(); + + TestCallback testCallback = + CommonTlsContextTestsUtil.getValueThruCallback(provider); + + doChecksOnSslContext(true, testCallback.updatedSslContext, /* expectedApnProtos= */ null); + TestCallback testCallback1 = + CommonTlsContextTestsUtil.getValueThruCallback(provider); + assertThat(testCallback1.updatedSslContext).isSameInstanceAs(testCallback.updatedSslContext); + + // just do root cert update: sslContext should still be the same + watcherCaptor[0].updateTrustedRoots( + ImmutableList.of(getCertFromResourceName(CLIENT_PEM_FILE))); + assertThat(provider.savedKey).isNull(); + assertThat(provider.savedCertChain).isNull(); + assertThat(provider.savedTrustedRoots).isNotNull(); + testCallback1 = CommonTlsContextTestsUtil.getValueThruCallback(provider); + assertThat(testCallback1.updatedSslContext).isSameInstanceAs(testCallback.updatedSslContext); + + // now update id cert: sslContext should be updated i.e.different from the previous one + watcherCaptor[0].updateCertificate( + CommonCertProviderTestUtils.getPrivateKey(SERVER_1_KEY_FILE), + ImmutableList.of(getCertFromResourceName(SERVER_1_PEM_FILE))); + assertThat(provider.savedKey).isNull(); + assertThat(provider.savedCertChain).isNull(); + assertThat(provider.savedTrustedRoots).isNull(); + assertThat(provider.getSslContext()).isNotNull(); + testCallback1 = CommonTlsContextTestsUtil.getValueThruCallback(provider); + assertThat(testCallback1.updatedSslContext).isNotSameInstanceAs(testCallback.updatedSslContext); + } + + @Test + public void testProviderForServer_queueExecutor() throws Exception { + final CertificateProvider.DistributorWatcher[] watcherCaptor = + new CertificateProvider.DistributorWatcher[1]; + TestCertificateProvider.createAndRegisterProviderProvider( + certificateProviderRegistry, watcherCaptor, "testca", 0); + CertProviderServerSslContextProvider provider = + getSslContextProvider( + "gcp_id", + "gcp_id", + CommonCertProviderTestUtils.getTestBootstrapInfo(), + /* alpnProtocols= */ null, + /* staticCertValidationContext= */ null, + /* requireClientCert= */ true); + QueuedExecutor queuedExecutor = new QueuedExecutor(); + + TestCallback testCallback = + CommonTlsContextTestsUtil.getValueThruCallback(provider, queuedExecutor); + assertThat(queuedExecutor.runQueue).isEmpty(); + + // now generate cert update + watcherCaptor[0].updateCertificate( + CommonCertProviderTestUtils.getPrivateKey(SERVER_0_KEY_FILE), + ImmutableList.of(getCertFromResourceName(SERVER_0_PEM_FILE))); + assertThat(queuedExecutor.runQueue).isEmpty(); // still empty + + // now generate root cert update + watcherCaptor[0].updateTrustedRoots(ImmutableList.of(getCertFromResourceName(CA_PEM_FILE))); + assertThat(queuedExecutor.runQueue).hasSize(1); + queuedExecutor.drain(); + + doChecksOnSslContext(true, testCallback.updatedSslContext, /* expectedApnProtos= */ null); + } + + @Test + public void testProviderForServer_tls() throws Exception { + final CertificateProvider.DistributorWatcher[] watcherCaptor = + new CertificateProvider.DistributorWatcher[1]; + TestCertificateProvider.createAndRegisterProviderProvider( + certificateProviderRegistry, watcherCaptor, "testca", 0); + CertProviderServerSslContextProvider provider = + getSslContextProvider( + "gcp_id", + /* rootInstanceName= */ null, + CommonCertProviderTestUtils.getTestBootstrapInfo(), + /* alpnProtocols= */ null, + /* staticCertValidationContext= */ null, + /* requireClientCert= */ false); + + assertThat(provider.savedKey).isNull(); + assertThat(provider.savedCertChain).isNull(); + assertThat(provider.savedTrustedRoots).isNull(); + assertThat(provider.getSslContext()).isNull(); + + // now generate cert update + watcherCaptor[0].updateCertificate( + CommonCertProviderTestUtils.getPrivateKey(SERVER_0_KEY_FILE), + ImmutableList.of(getCertFromResourceName(SERVER_0_PEM_FILE))); + + assertThat(provider.getSslContext()).isNotNull(); + assertThat(provider.savedKey).isNull(); + assertThat(provider.savedCertChain).isNull(); + assertThat(provider.savedTrustedRoots).isNull(); + + TestCallback testCallback = + CommonTlsContextTestsUtil.getValueThruCallback(provider); + + doChecksOnSslContext(true, testCallback.updatedSslContext, /* expectedApnProtos= */ null); + } + + @Test + public void testProviderForServer_sslContextException_onError() throws Exception { + CertificateValidationContext staticCertValidationContext = + CertificateValidationContext.newBuilder() + .setTrustedCa(DataSource.newBuilder().setInlineString("foo")) + .build(); + + final CertificateProvider.DistributorWatcher[] watcherCaptor = + new CertificateProvider.DistributorWatcher[1]; + TestCertificateProvider.createAndRegisterProviderProvider( + certificateProviderRegistry, watcherCaptor, "testca", 0); + CertProviderServerSslContextProvider provider = + getSslContextProvider( + /* certInstanceName= */ "gcp_id", + /* rootInstanceName= */ "gcp_id", + CommonCertProviderTestUtils.getTestBootstrapInfo(), + /* alpnProtocols= */null, + staticCertValidationContext, + /* requireClientCert= */ true); + + // now generate cert update + watcherCaptor[0].updateCertificate( + CommonCertProviderTestUtils.getPrivateKey(SERVER_0_KEY_FILE), + ImmutableList.of(getCertFromResourceName(SERVER_0_PEM_FILE))); + + TestCallback testCallback = new TestCallback(MoreExecutors.directExecutor()); + provider.addCallback(testCallback); + try { + watcherCaptor[0].updateTrustedRoots(ImmutableList.of(getCertFromResourceName(CA_PEM_FILE))); + fail("exception expected"); + } catch (RuntimeException expected) { + assertThat(expected) + .hasMessageThat() + .contains("only static certificateValidationContext expected"); + } + assertThat(testCallback.updatedThrowable).isNotNull(); + assertThat(testCallback.updatedThrowable) + .hasCauseThat() + .hasMessageThat() + .contains("only static certificateValidationContext expected"); + } + + @Test + public void testProviderForServer_certInstanceNull_expectError() throws Exception { + final CertificateProvider.DistributorWatcher[] watcherCaptor = + new CertificateProvider.DistributorWatcher[1]; + TestCertificateProvider.createAndRegisterProviderProvider( + certificateProviderRegistry, watcherCaptor, "testca", 0); + try { + getSslContextProvider( + /* certInstanceName= */ null, + /* rootInstanceName= */ null, + CommonCertProviderTestUtils.getTestBootstrapInfo(), + /* alpnProtocols= */ null, + /* staticCertValidationContext= */ null, + /* requireClientCert= */ false); + fail("exception expected"); + } catch (NullPointerException expected) { + assertThat(expected).hasMessageThat().contains("Server SSL requires certInstance"); + } + } +} diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/CommonTlsContextTestsUtil.java b/xds/src/test/java/io/grpc/xds/internal/sds/CommonTlsContextTestsUtil.java index 5f769ae950e..8c150d0ccd0 100644 --- a/xds/src/test/java/io/grpc/xds/internal/sds/CommonTlsContextTestsUtil.java +++ b/xds/src/test/java/io/grpc/xds/internal/sds/CommonTlsContextTestsUtil.java @@ -533,6 +533,27 @@ private static CommonTlsContext.Builder addCertificateValidationContext( staticCertValidationContext)); } + /** Helper method to build DownstreamTlsContext for CertProvider tests. */ + public static EnvoyServerProtoData.DownstreamTlsContext + buildDownstreamTlsContextForCertProviderInstance( + @Nullable String certInstanceName, + @Nullable String certName, + @Nullable String rootInstanceName, + @Nullable String rootCertName, + Iterable alpnProtocols, + CertificateValidationContext staticCertValidationContext, + boolean requireClientCert) { + return buildInternalDownstreamTlsContext( + buildCommonTlsContextForCertProviderInstance( + certInstanceName, + certName, + rootInstanceName, + rootCertName, + alpnProtocols, + staticCertValidationContext), requireClientCert); + } + + /** Perform some simple checks on sslContext. */ public static void doChecksOnSslContext(boolean server, SslContext sslContext, List expectedApnProtos) { From cb07b0fb451e37100094f5c5ea7cf01e786af7fe Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Tue, 18 Aug 2020 12:30:05 -0700 Subject: [PATCH 68/88] xds: add data type for ClusterStats (#7335) In preparation of LRS v3 support. --- .../main/java/io/grpc/xds/EnvoyProtoData.java | 526 +++++++++++++++++- .../java/io/grpc/xds/LoadReportClient.java | 9 +- .../java/io/grpc/xds/LoadStatsManager.java | 4 +- .../java/io/grpc/xds/LoadStatsStoreImpl.java | 28 +- .../java/io/grpc/xds/EnvoyProtoDataTest.java | 79 +++ .../io/grpc/xds/LoadReportClientTest.java | 51 +- .../io/grpc/xds/LoadStatsStoreImplTest.java | 41 +- .../java/io/grpc/xds/LocalityStoreTest.java | 2 +- .../java/io/grpc/xds/LrsLoadBalancerTest.java | 2 +- 9 files changed, 664 insertions(+), 78 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/EnvoyProtoData.java b/xds/src/main/java/io/grpc/xds/EnvoyProtoData.java index cbb73423a16..e1872422da1 100644 --- a/xds/src/main/java/io/grpc/xds/EnvoyProtoData.java +++ b/xds/src/main/java/io/grpc/xds/EnvoyProtoData.java @@ -37,6 +37,7 @@ import io.grpc.xds.RouteMatch.PathMatcher; import java.net.InetSocketAddress; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -558,7 +559,7 @@ public String toString() { /** * See corresponding Envoy proto message {@link - * io.envoyproxy.envoy.api.v2.endpoint.LocalityLbEndpoints}. + * io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints}. */ static final class LocalityLbEndpoints { private final List endpoints; @@ -643,7 +644,8 @@ public String toString() { } /** - * See corresponding Envoy proto message {@link io.envoyproxy.envoy.api.v2.endpoint.LbEndpoint}. + * See corresponding Envoy proto message + * {@link io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint}. */ static final class LbEndpoint { private final EquivalentAddressGroup eag; @@ -735,7 +737,7 @@ public String toString() { /** * See corresponding Envoy proto message {@link - * io.envoyproxy.envoy.api.v2.ClusterLoadAssignment.Policy.DropOverload}. + * io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment.Policy.DropOverload}. */ static final class DropOverload { private final String category; @@ -835,7 +837,7 @@ public String toString() { } } - /** See corresponding Envoy proto message {@link io.envoyproxy.envoy.api.v2.route.Route}. */ + /** See corresponding Envoy proto message {@link io.envoyproxy.envoy.config.route.v3.Route}. */ static final class Route { private final RouteMatch routeMatch; private final RouteAction routeAction; @@ -1068,7 +1070,9 @@ static StructOrError convertEnvoyProtoHeaderMatcher( } } - /** See corresponding Envoy proto message {@link io.envoyproxy.envoy.api.v2.route.RouteAction}. */ + /** + * See corresponding Envoy proto message {@link io.envoyproxy.envoy.config.route.v3.RouteAction}. + */ static final class RouteAction { private final long timeoutNano; // Exactly one of the following fields is non-null. @@ -1176,7 +1180,7 @@ static StructOrError fromEnvoyProtoRouteAction( /** * See corresponding Envoy proto message {@link - * io.envoyproxy.envoy.api.v2.route.WeightedCluster.ClusterWeight}. + * io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight}. */ static final class ClusterWeight { private final String name; @@ -1227,4 +1231,514 @@ static ClusterWeight fromEnvoyProtoClusterWeight( return new ClusterWeight(proto.getName(), proto.getWeight().getValue()); } } + + /** + * See corresponding Envoy proto message {@link + * io.envoyproxy.envoy.config.endpoint.v3.ClusterStats}. + */ + static final class ClusterStats { + private final String clusterName; + @Nullable + private final String clusterServiceName; + private final List upstreamLocalityStatsList; + private final List droppedRequestsList; + private final long totalDroppedRequests; + private final long loadReportIntervalNanos; + + private ClusterStats( + String clusterName, + @Nullable String clusterServiceName, + List upstreamLocalityStatsList, + List droppedRequestsList, + long totalDroppedRequests, + long loadReportIntervalNanos) { + this.clusterName = checkNotNull(clusterName, "clusterName"); + this.clusterServiceName = clusterServiceName; + this.upstreamLocalityStatsList = Collections.unmodifiableList( + checkNotNull(upstreamLocalityStatsList, "upstreamLocalityStatsList")); + this.droppedRequestsList = Collections.unmodifiableList( + checkNotNull(droppedRequestsList, "dropRequestsList")); + this.totalDroppedRequests = totalDroppedRequests; + this.loadReportIntervalNanos = loadReportIntervalNanos; + } + + String getClusterName() { + return clusterName; + } + + @Nullable + String getClusterServiceName() { + return clusterServiceName; + } + + List getUpstreamLocalityStatsList() { + return upstreamLocalityStatsList; + } + + List getDroppedRequestsList() { + return droppedRequestsList; + } + + long getTotalDroppedRequests() { + return totalDroppedRequests; + } + + long getLoadReportIntervalNanos() { + return loadReportIntervalNanos; + } + + io.envoyproxy.envoy.config.endpoint.v3.ClusterStats toEnvoyProtoClusterStats() { + io.envoyproxy.envoy.config.endpoint.v3.ClusterStats.Builder builder = + io.envoyproxy.envoy.config.endpoint.v3.ClusterStats.newBuilder(); + builder.setClusterName(clusterName); + if (clusterServiceName != null) { + builder.setClusterServiceName(clusterServiceName); + } + for (UpstreamLocalityStats upstreamLocalityStats : upstreamLocalityStatsList) { + builder.addUpstreamLocalityStats(upstreamLocalityStats.toEnvoyProtoUpstreamLocalityStats()); + } + for (DroppedRequests droppedRequests : droppedRequestsList) { + builder.addDroppedRequests(droppedRequests.toEnvoyProtoDroppedRequests()); + } + builder.setTotalDroppedRequests(totalDroppedRequests); + builder.setLoadReportInterval(Durations.fromNanos(loadReportIntervalNanos)); + return builder.build(); + } + + io.envoyproxy.envoy.api.v2.endpoint.ClusterStats toEnvoyProtoClusterStatsV2() { + io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.Builder builder = + io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.newBuilder(); + builder.setClusterName(clusterName); + for (UpstreamLocalityStats upstreamLocalityStats : upstreamLocalityStatsList) { + builder.addUpstreamLocalityStats( + upstreamLocalityStats.toEnvoyProtoUpstreamLocalityStatsV2()); + } + for (DroppedRequests droppedRequests : droppedRequestsList) { + builder.addDroppedRequests(droppedRequests.toEnvoyProtoDroppedRequestsV2()); + } + builder.setTotalDroppedRequests(totalDroppedRequests); + builder.setLoadReportInterval(Durations.fromNanos(loadReportIntervalNanos)); + return builder.build(); + } + + @VisibleForTesting + Builder toBuilder() { + Builder builder = new Builder() + .setClusterName(clusterName) + .setTotalDroppedRequests(totalDroppedRequests) + .setLoadReportIntervalNanos(loadReportIntervalNanos); + if (clusterServiceName != null) { + builder.setClusterServiceName(clusterServiceName); + } + for (UpstreamLocalityStats upstreamLocalityStats : upstreamLocalityStatsList) { + builder.addUpstreamLocalityStats(upstreamLocalityStats); + } + for (DroppedRequests droppedRequests : droppedRequestsList) { + builder.addDroppedRequests(droppedRequests); + } + return builder; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ClusterStats that = (ClusterStats) o; + return totalDroppedRequests == that.totalDroppedRequests + && loadReportIntervalNanos == that.loadReportIntervalNanos + && Objects.equals(clusterName, that.clusterName) + && Objects.equals(clusterServiceName, that.clusterServiceName) + && Objects.equals(upstreamLocalityStatsList, that.upstreamLocalityStatsList) + && Objects.equals(droppedRequestsList, that.droppedRequestsList); + } + + @Override + public int hashCode() { + return Objects.hash( + clusterName, clusterServiceName, upstreamLocalityStatsList, droppedRequestsList, + totalDroppedRequests, loadReportIntervalNanos); + } + + static Builder newBuilder() { + return new Builder(); + } + + static final class Builder { + private String clusterName; + private String clusterServiceName; + private final List upstreamLocalityStatsList = new ArrayList<>(); + private final List droppedRequestsList = new ArrayList<>(); + private long totalDroppedRequests; + private long loadReportIntervalNanos; + + private Builder() { + } + + Builder setClusterName(String clusterName) { + this.clusterName = checkNotNull(clusterName, "clusterName"); + return this; + } + + Builder setClusterServiceName(String clusterServiceName) { + this.clusterServiceName = checkNotNull(clusterServiceName, "clusterServiceName"); + return this; + } + + Builder setTotalDroppedRequests(long totalDroppedRequests) { + this.totalDroppedRequests = totalDroppedRequests; + return this; + } + + Builder setLoadReportIntervalNanos(long loadReportIntervalNanos) { + this.loadReportIntervalNanos = loadReportIntervalNanos; + return this; + } + + Builder addUpstreamLocalityStats(UpstreamLocalityStats upstreamLocalityStats) { + upstreamLocalityStatsList.add(checkNotNull(upstreamLocalityStats, "upstreamLocalityStats")); + return this; + } + + Builder addAllUpstreamLocalityStats(Collection upstreamLocalityStats) { + upstreamLocalityStatsList.addAll(upstreamLocalityStats); + return this; + } + + Builder addDroppedRequests(DroppedRequests droppedRequests) { + droppedRequestsList.add(checkNotNull(droppedRequests, "dropRequests")); + return this; + } + + ClusterStats build() { + return new ClusterStats( + clusterName, clusterServiceName,upstreamLocalityStatsList, droppedRequestsList, + totalDroppedRequests, loadReportIntervalNanos); + } + } + + /** + * See corresponding Envoy proto message {@link + * io.envoyproxy.envoy.config.endpoint.v3.ClusterStats.DroppedRequests}. + */ + static final class DroppedRequests { + private final String category; + private final long droppedCount; + + DroppedRequests(String category, long droppedCount) { + this.category = checkNotNull(category, "category"); + this.droppedCount = droppedCount; + } + + String getCategory() { + return category; + } + + long getDroppedCount() { + return droppedCount; + } + + private io.envoyproxy.envoy.config.endpoint.v3.ClusterStats.DroppedRequests + toEnvoyProtoDroppedRequests() { + return io.envoyproxy.envoy.config.endpoint.v3.ClusterStats.DroppedRequests.newBuilder() + .setCategory(category) + .setDroppedCount(droppedCount) + .build(); + } + + private io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.DroppedRequests + toEnvoyProtoDroppedRequestsV2() { + return io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.DroppedRequests.newBuilder() + .setCategory(category) + .setDroppedCount(droppedCount) + .build(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DroppedRequests that = (DroppedRequests) o; + return droppedCount == that.droppedCount && Objects.equals(category, that.category); + } + + @Override + public int hashCode() { + return Objects.hash(category, droppedCount); + } + } + } + + /** + * See corresponding Envoy proto message {@link + * io.envoyproxy.envoy.config.endpoint.v3.UpstreamLocalityStats}. + */ + static final class UpstreamLocalityStats { + private final Locality locality; + private final long totalSuccessfulRequests; + private final long totalErrorRequests; + private final long totalRequestsInProgress; + private final long totalIssuedRequests; + private final List loadMetricStatsList; + + private UpstreamLocalityStats( + Locality locality, + long totalSuccessfulRequests, + long totalErrorRequests, + long totalRequestsInProgress, + long totalIssuedRequests, + List loadMetricStatsList) { + this.locality = checkNotNull(locality, "locality"); + this.totalSuccessfulRequests = totalSuccessfulRequests; + this.totalErrorRequests = totalErrorRequests; + this.totalRequestsInProgress = totalRequestsInProgress; + this.totalIssuedRequests = totalIssuedRequests; + this.loadMetricStatsList = Collections.unmodifiableList( + checkNotNull(loadMetricStatsList, "loadMetricStatsList")); + } + + Locality getLocality() { + return locality; + } + + long getTotalSuccessfulRequests() { + return totalSuccessfulRequests; + } + + long getTotalErrorRequests() { + return totalErrorRequests; + } + + long getTotalRequestsInProgress() { + return totalRequestsInProgress; + } + + long getTotalIssuedRequests() { + return totalIssuedRequests; + } + + List getLoadMetricStatsList() { + return loadMetricStatsList; + } + + private io.envoyproxy.envoy.config.endpoint.v3.UpstreamLocalityStats + toEnvoyProtoUpstreamLocalityStats() { + io.envoyproxy.envoy.config.endpoint.v3.UpstreamLocalityStats.Builder builder + = io.envoyproxy.envoy.config.endpoint.v3.UpstreamLocalityStats.newBuilder(); + builder + .setLocality(locality.toEnvoyProtoLocality()) + .setTotalSuccessfulRequests(totalSuccessfulRequests) + .setTotalErrorRequests(totalErrorRequests) + .setTotalRequestsInProgress(totalRequestsInProgress) + .setTotalIssuedRequests(totalIssuedRequests); + for (EndpointLoadMetricStats endpointLoadMetricStats : loadMetricStatsList) { + builder.addLoadMetricStats(endpointLoadMetricStats.toEnvoyProtoEndpointLoadMetricStats()); + } + return builder.build(); + } + + private io.envoyproxy.envoy.api.v2.endpoint.UpstreamLocalityStats + toEnvoyProtoUpstreamLocalityStatsV2() { + io.envoyproxy.envoy.api.v2.endpoint.UpstreamLocalityStats.Builder builder + = io.envoyproxy.envoy.api.v2.endpoint.UpstreamLocalityStats.newBuilder(); + builder + .setLocality(locality.toEnvoyProtoLocalityV2()) + .setTotalSuccessfulRequests(totalSuccessfulRequests) + .setTotalErrorRequests(totalErrorRequests) + .setTotalRequestsInProgress(totalRequestsInProgress) + .setTotalIssuedRequests(totalIssuedRequests); + for (EndpointLoadMetricStats endpointLoadMetricStats : loadMetricStatsList) { + builder.addLoadMetricStats(endpointLoadMetricStats.toEnvoyProtoEndpointLoadMetricStatsV2()); + } + return builder.build(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + UpstreamLocalityStats that = (UpstreamLocalityStats) o; + return totalSuccessfulRequests == that.totalSuccessfulRequests + && totalErrorRequests == that.totalErrorRequests + && totalRequestsInProgress == that.totalRequestsInProgress + && totalIssuedRequests == that.totalIssuedRequests + && Objects.equals(locality, that.locality) + && Objects.equals(loadMetricStatsList, that.loadMetricStatsList); + } + + @Override + public int hashCode() { + return Objects.hash( + locality, totalSuccessfulRequests, totalErrorRequests, totalRequestsInProgress, + totalIssuedRequests, loadMetricStatsList); + } + + static Builder newBuilder() { + return new Builder(); + } + + static final class Builder { + private Locality locality; + private long totalSuccessfulRequests; + private long totalErrorRequests; + private long totalRequestsInProgress; + private long totalIssuedRequests; + private final List loadMetricStatsList = new ArrayList<>(); + + private Builder() { + } + + Builder setLocality(Locality locality) { + this.locality = checkNotNull(locality, "locality"); + return this; + } + + Builder setTotalSuccessfulRequests(long totalSuccessfulRequests) { + this.totalSuccessfulRequests = totalSuccessfulRequests; + return this; + } + + Builder setTotalErrorRequests(long totalErrorRequests) { + this.totalErrorRequests = totalErrorRequests; + return this; + } + + Builder setTotalRequestsInProgress(long totalRequestsInProgress) { + this.totalRequestsInProgress = totalRequestsInProgress; + return this; + } + + Builder setTotalIssuedRequests(long totalIssuedRequests) { + this.totalIssuedRequests = totalIssuedRequests; + return this; + } + + Builder addLoadMetricStats(EndpointLoadMetricStats endpointLoadMetricStats) { + loadMetricStatsList.add(checkNotNull(endpointLoadMetricStats, "endpointLoadMetricStats")); + return this; + } + + Builder addAllLoadMetricStats(Collection endpointLoadMetricStats) { + loadMetricStatsList.addAll( + checkNotNull(endpointLoadMetricStats, "endpointLoadMetricStats")); + return this; + } + + UpstreamLocalityStats build() { + return new UpstreamLocalityStats( + locality, totalSuccessfulRequests, totalErrorRequests, totalRequestsInProgress, + totalIssuedRequests, loadMetricStatsList); + } + } + } + + /** + * See corresponding Envoy proto message {@link + * io.envoyproxy.envoy.config.endpoint.v3.EndpointLoadMetricStats}. + */ + static final class EndpointLoadMetricStats { + private final String metricName; + private final long numRequestsFinishedWithMetric; + private final double totalMetricValue; + + private EndpointLoadMetricStats(String metricName, long numRequestsFinishedWithMetric, + double totalMetricValue) { + this.metricName = checkNotNull(metricName, "metricName"); + this.numRequestsFinishedWithMetric = numRequestsFinishedWithMetric; + this.totalMetricValue = totalMetricValue; + } + + String getMetricName() { + return metricName; + } + + long getNumRequestsFinishedWithMetric() { + return numRequestsFinishedWithMetric; + } + + double getTotalMetricValue() { + return totalMetricValue; + } + + private io.envoyproxy.envoy.config.endpoint.v3.EndpointLoadMetricStats + toEnvoyProtoEndpointLoadMetricStats() { + return io.envoyproxy.envoy.config.endpoint.v3.EndpointLoadMetricStats.newBuilder() + .setMetricName(metricName) + .setNumRequestsFinishedWithMetric(numRequestsFinishedWithMetric) + .setTotalMetricValue(totalMetricValue) + .build(); + } + + private io.envoyproxy.envoy.api.v2.endpoint.EndpointLoadMetricStats + toEnvoyProtoEndpointLoadMetricStatsV2() { + return io.envoyproxy.envoy.api.v2.endpoint.EndpointLoadMetricStats.newBuilder() + .setMetricName(metricName) + .setNumRequestsFinishedWithMetric(numRequestsFinishedWithMetric) + .setTotalMetricValue(totalMetricValue) + .build(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + EndpointLoadMetricStats that = (EndpointLoadMetricStats) o; + return numRequestsFinishedWithMetric == that.numRequestsFinishedWithMetric + && Double.compare(that.totalMetricValue, totalMetricValue) == 0 + && Objects.equals(metricName, that.metricName); + } + + @Override + public int hashCode() { + return Objects.hash(metricName, numRequestsFinishedWithMetric, totalMetricValue); + } + + static Builder newBuilder() { + return new Builder(); + } + + static final class Builder { + private String metricName; + private long numRequestsFinishedWithMetric; + private double totalMetricValue; + + private Builder() { + } + + Builder setMetricName(String metricName) { + this.metricName = checkNotNull(metricName, "metricName"); + return this; + } + + Builder setNumRequestsFinishedWithMetric(long numRequestsFinishedWithMetric) { + this.numRequestsFinishedWithMetric = numRequestsFinishedWithMetric; + return this; + } + + Builder setTotalMetricValue(double totalMetricValue) { + this.totalMetricValue = totalMetricValue; + return this; + } + + EndpointLoadMetricStats build() { + return new EndpointLoadMetricStats( + metricName, numRequestsFinishedWithMetric, totalMetricValue); + } + } + } } diff --git a/xds/src/main/java/io/grpc/xds/LoadReportClient.java b/xds/src/main/java/io/grpc/xds/LoadReportClient.java index 84d2b340cbd..da526147902 100644 --- a/xds/src/main/java/io/grpc/xds/LoadReportClient.java +++ b/xds/src/main/java/io/grpc/xds/LoadReportClient.java @@ -37,6 +37,7 @@ import io.grpc.SynchronizationContext.ScheduledHandle; import io.grpc.internal.BackoffPolicy; import io.grpc.stub.StreamObserver; +import io.grpc.xds.EnvoyProtoData.ClusterStats; import io.grpc.xds.XdsLogger.XdsLogLevel; import java.util.List; import java.util.concurrent.ScheduledExecutorService; @@ -228,10 +229,14 @@ public void run() { private void sendLoadReport() { LoadStatsRequest.Builder requestBuilder = LoadStatsRequest.newBuilder().setNode(node); if (reportAllClusters) { - requestBuilder.addAllClusterStats(loadStatsManager.getAllLoadReports()); + for (ClusterStats clusterStats : loadStatsManager.getAllLoadReports()) { + requestBuilder.addClusterStats(clusterStats.toEnvoyProtoClusterStatsV2()); + } } else { for (String name : clusterNames) { - requestBuilder.addAllClusterStats(loadStatsManager.getClusterLoadReports(name)); + for (ClusterStats clusterStats : loadStatsManager.getClusterLoadReports(name)) { + requestBuilder.addClusterStats(clusterStats.toEnvoyProtoClusterStatsV2()); + } } } LoadStatsRequest request = requestBuilder.build(); diff --git a/xds/src/main/java/io/grpc/xds/LoadStatsManager.java b/xds/src/main/java/io/grpc/xds/LoadStatsManager.java index 5372d37b0ea..02fd466d3a6 100644 --- a/xds/src/main/java/io/grpc/xds/LoadStatsManager.java +++ b/xds/src/main/java/io/grpc/xds/LoadStatsManager.java @@ -19,7 +19,7 @@ import static com.google.common.base.Preconditions.checkState; import com.google.common.annotations.VisibleForTesting; -import io.envoyproxy.envoy.api.v2.endpoint.ClusterStats; +import io.grpc.xds.EnvoyProtoData.ClusterStats; import io.grpc.xds.EnvoyProtoData.Locality; import java.util.ArrayList; import java.util.HashMap; @@ -90,7 +90,6 @@ void removeLoadStats(String cluster, @Nullable String clusterService) { * the interval between calls of this method or {@link #getAllLoadReports}. A cluster may send * loads to more than one cluster_service, they are included in separate stats reports. */ - // TODO(chengyuanzhang): do not use proto type directly. List getClusterLoadReports(String cluster) { List res = new ArrayList<>(); Map> clusterLoadStatsStores = @@ -109,7 +108,6 @@ List getClusterLoadReports(String cluster) { * interval between calls of this method or {@link #getClusterLoadReports}. Each report * includes stats for one cluster:cluster_service. */ - // TODO(chengyuanzhang): do not use proto type directly. List getAllLoadReports() { List res = new ArrayList<>(); for (Map> clusterLoadStatsStores diff --git a/xds/src/main/java/io/grpc/xds/LoadStatsStoreImpl.java b/xds/src/main/java/io/grpc/xds/LoadStatsStoreImpl.java index 6a8b5447216..afb91c3ecc7 100644 --- a/xds/src/main/java/io/grpc/xds/LoadStatsStoreImpl.java +++ b/xds/src/main/java/io/grpc/xds/LoadStatsStoreImpl.java @@ -20,15 +20,14 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Stopwatch; -import com.google.protobuf.util.Durations; -import io.envoyproxy.envoy.api.v2.endpoint.ClusterStats; -import io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.DroppedRequests; -import io.envoyproxy.envoy.api.v2.endpoint.EndpointLoadMetricStats; -import io.envoyproxy.envoy.api.v2.endpoint.UpstreamLocalityStats; import io.grpc.internal.GrpcUtil; import io.grpc.xds.ClientLoadCounter.ClientLoadSnapshot; import io.grpc.xds.ClientLoadCounter.MetricValue; +import io.grpc.xds.EnvoyProtoData.ClusterStats; +import io.grpc.xds.EnvoyProtoData.ClusterStats.DroppedRequests; +import io.grpc.xds.EnvoyProtoData.EndpointLoadMetricStats; import io.grpc.xds.EnvoyProtoData.Locality; +import io.grpc.xds.EnvoyProtoData.UpstreamLocalityStats; import io.grpc.xds.LoadStatsManager.LoadStatsStore; import io.grpc.xds.LoadStatsManager.LoadStatsStoreFactory; import java.util.Map; @@ -50,7 +49,6 @@ final class LoadStatsStoreImpl implements LoadStatsStore { private final String clusterName; @Nullable - @SuppressWarnings("unused") private final String clusterServiceName; private final ConcurrentMap> localityLoadCounters = new ConcurrentHashMap<>(); @@ -80,12 +78,14 @@ final class LoadStatsStoreImpl implements LoadStatsStore { public ClusterStats generateLoadReport() { ClusterStats.Builder statsBuilder = ClusterStats.newBuilder(); statsBuilder.setClusterName(clusterName); - // TODO(chengyuangzhang): also set cluster_service_name if provided. + if (clusterServiceName != null) { + statsBuilder.setClusterServiceName(clusterServiceName); + } for (Map.Entry> entry : localityLoadCounters.entrySet()) { ClientLoadSnapshot snapshot = entry.getValue().get().snapshot(); UpstreamLocalityStats.Builder localityStatsBuilder = - UpstreamLocalityStats.newBuilder().setLocality(entry.getKey().toEnvoyProtoLocalityV2()); + UpstreamLocalityStats.newBuilder().setLocality(entry.getKey()); localityStatsBuilder .setTotalSuccessfulRequests(snapshot.getCallsSucceeded()) .setTotalErrorRequests(snapshot.getCallsFailed()) @@ -96,9 +96,10 @@ public ClusterStats generateLoadReport() { EndpointLoadMetricStats.newBuilder() .setMetricName(metric.getKey()) .setNumRequestsFinishedWithMetric(metric.getValue().getNumReports()) - .setTotalMetricValue(metric.getValue().getTotalValue())); + .setTotalMetricValue(metric.getValue().getTotalValue()) + .build()); } - statsBuilder.addUpstreamLocalityStats(localityStatsBuilder); + statsBuilder.addUpstreamLocalityStats(localityStatsBuilder.build()); // Discard counters for localities that are no longer exposed by the remote balancer and // no RPCs ongoing. if (entry.getValue().getReferenceCount() == 0 && snapshot.getCallsInProgress() == 0) { @@ -109,13 +110,10 @@ public ClusterStats generateLoadReport() { for (Map.Entry entry : dropCounters.entrySet()) { long drops = entry.getValue().getAndSet(0); totalDrops += drops; - statsBuilder.addDroppedRequests(DroppedRequests.newBuilder() - .setCategory(entry.getKey()) - .setDroppedCount(drops)); + statsBuilder.addDroppedRequests(new DroppedRequests(entry.getKey(),drops)); } statsBuilder.setTotalDroppedRequests(totalDrops); - statsBuilder.setLoadReportInterval( - Durations.fromNanos(stopwatch.elapsed(TimeUnit.NANOSECONDS))); + statsBuilder.setLoadReportIntervalNanos(stopwatch.elapsed(TimeUnit.NANOSECONDS)); stopwatch.reset().start(); return statsBuilder.build(); } diff --git a/xds/src/test/java/io/grpc/xds/EnvoyProtoDataTest.java b/xds/src/test/java/io/grpc/xds/EnvoyProtoDataTest.java index 24e739bf560..2e307d6c7eb 100644 --- a/xds/src/test/java/io/grpc/xds/EnvoyProtoDataTest.java +++ b/xds/src/test/java/io/grpc/xds/EnvoyProtoDataTest.java @@ -34,12 +34,16 @@ import io.envoyproxy.envoy.type.v3.FractionalPercent; import io.envoyproxy.envoy.type.v3.Int64Range; import io.grpc.xds.EnvoyProtoData.Address; +import io.grpc.xds.EnvoyProtoData.ClusterStats; +import io.grpc.xds.EnvoyProtoData.ClusterStats.DroppedRequests; import io.grpc.xds.EnvoyProtoData.ClusterWeight; +import io.grpc.xds.EnvoyProtoData.EndpointLoadMetricStats; import io.grpc.xds.EnvoyProtoData.Locality; import io.grpc.xds.EnvoyProtoData.Node; import io.grpc.xds.EnvoyProtoData.Route; import io.grpc.xds.EnvoyProtoData.RouteAction; import io.grpc.xds.EnvoyProtoData.StructOrError; +import io.grpc.xds.EnvoyProtoData.UpstreamLocalityStats; import io.grpc.xds.RouteMatch.FractionMatcher; import io.grpc.xds.RouteMatch.HeaderMatcher; import io.grpc.xds.RouteMatch.PathMatcher; @@ -564,4 +568,79 @@ public void convertClusterWeight() { assertThat(struct.getName()).isEqualTo("cluster-foo"); assertThat(struct.getWeight()).isEqualTo(30); } + + @Test + public void clusterStats_convertToEnvoyProto() { + ClusterStats clusterStats = + ClusterStats.newBuilder() + .setClusterName("cluster1") + .setLoadReportIntervalNanos(1234) + .setTotalDroppedRequests(123) + .addUpstreamLocalityStats(UpstreamLocalityStats.newBuilder() + .setLocality(new Locality("region1", "zone1", "subzone1")) + .setTotalErrorRequests(1) + .setTotalRequestsInProgress(2) + .setTotalSuccessfulRequests(100) + .setTotalIssuedRequests(103) + .addLoadMetricStats(EndpointLoadMetricStats.newBuilder() + .setMetricName("metric1") + .setNumRequestsFinishedWithMetric(1000) + .setTotalMetricValue(0.5D) + .build()) + .build()) + .addDroppedRequests(new DroppedRequests("category1", 100)) + .build(); + + io.envoyproxy.envoy.config.endpoint.v3.ClusterStats clusterStatsProto = + clusterStats.toEnvoyProtoClusterStats(); + assertThat(clusterStatsProto).isEqualTo( + io.envoyproxy.envoy.config.endpoint.v3.ClusterStats.newBuilder() + .setClusterName("cluster1") + .setLoadReportInterval(Durations.fromNanos(1234)) + .setTotalDroppedRequests(123) + .addUpstreamLocalityStats( + io.envoyproxy.envoy.config.endpoint.v3.UpstreamLocalityStats.newBuilder() + .setLocality( + new Locality("region1", "zone1", "subzone1").toEnvoyProtoLocality()) + .setTotalErrorRequests(1) + .setTotalRequestsInProgress(2) + .setTotalSuccessfulRequests(100) + .setTotalIssuedRequests(103) + .addLoadMetricStats( + io.envoyproxy.envoy.config.endpoint.v3.EndpointLoadMetricStats.newBuilder() + .setMetricName("metric1") + .setNumRequestsFinishedWithMetric(1000) + .setTotalMetricValue(0.5D))) + .addDroppedRequests( + io.envoyproxy.envoy.config.endpoint.v3.ClusterStats.DroppedRequests.newBuilder() + .setCategory("category1") + .setDroppedCount(100)) + .build()); + + io.envoyproxy.envoy.api.v2.endpoint.ClusterStats clusterStatsProtoV2 = + clusterStats.toEnvoyProtoClusterStatsV2(); + assertThat(clusterStatsProtoV2).isEqualTo( + io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.newBuilder() + .setClusterName("cluster1") + .setLoadReportInterval(Durations.fromNanos(1234)) + .setTotalDroppedRequests(123) + .addUpstreamLocalityStats( + io.envoyproxy.envoy.api.v2.endpoint.UpstreamLocalityStats.newBuilder() + .setLocality( + new Locality("region1", "zone1", "subzone1").toEnvoyProtoLocalityV2()) + .setTotalErrorRequests(1) + .setTotalRequestsInProgress(2) + .setTotalSuccessfulRequests(100) + .setTotalIssuedRequests(103) + .addLoadMetricStats( + io.envoyproxy.envoy.api.v2.endpoint.EndpointLoadMetricStats.newBuilder() + .setMetricName("metric1") + .setNumRequestsFinishedWithMetric(1000) + .setTotalMetricValue(0.5D))) + .addDroppedRequests( + io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.DroppedRequests.newBuilder() + .setCategory("category1") + .setDroppedCount(100)) + .build()); + } } diff --git a/xds/src/test/java/io/grpc/xds/LoadReportClientTest.java b/xds/src/test/java/io/grpc/xds/LoadReportClientTest.java index be68f08b110..a63171ef447 100644 --- a/xds/src/test/java/io/grpc/xds/LoadReportClientTest.java +++ b/xds/src/test/java/io/grpc/xds/LoadReportClientTest.java @@ -35,11 +35,7 @@ import com.google.protobuf.Struct; import com.google.protobuf.Value; import com.google.protobuf.util.Durations; -import io.envoyproxy.envoy.api.v2.core.Locality; import io.envoyproxy.envoy.api.v2.core.Node; -import io.envoyproxy.envoy.api.v2.endpoint.ClusterStats; -import io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.DroppedRequests; -import io.envoyproxy.envoy.api.v2.endpoint.UpstreamLocalityStats; import io.envoyproxy.envoy.service.load_stats.v2.LoadReportingServiceGrpc; import io.envoyproxy.envoy.service.load_stats.v2.LoadStatsRequest; import io.envoyproxy.envoy.service.load_stats.v2.LoadStatsResponse; @@ -54,6 +50,10 @@ import io.grpc.internal.FakeClock; import io.grpc.stub.StreamObserver; import io.grpc.testing.GrpcCleanupRule; +import io.grpc.xds.EnvoyProtoData.ClusterStats; +import io.grpc.xds.EnvoyProtoData.ClusterStats.DroppedRequests; +import io.grpc.xds.EnvoyProtoData.Locality; +import io.grpc.xds.EnvoyProtoData.UpstreamLocalityStats; import io.grpc.xds.LoadStatsManager.LoadStatsStore; import io.grpc.xds.LoadStatsManager.LoadStatsStoreFactory; import java.util.ArrayDeque; @@ -225,7 +225,7 @@ public void periodicLoadReporting() { fakeClock.forwardNanos(1); assertThat(loadStatsStore1.reported).hasSize(1); ClusterStats report1 = loadStatsStore1.reported.poll(); - assertThat(Durations.toNanos(report1.getLoadReportInterval())).isEqualTo(1000); + assertThat(report1.getLoadReportIntervalNanos()).isEqualTo(1000); inOrder.verify(requestObserver) .onNext(argThat(new LoadStatsRequestMatcher(Collections.singletonList(report1)))); @@ -233,7 +233,7 @@ public void periodicLoadReporting() { fakeClock.forwardNanos(1000); assertThat(loadStatsStore1.reported).hasSize(1); report1 = loadStatsStore1.reported.poll(); - assertThat(Durations.toNanos(report1.getLoadReportInterval())).isEqualTo(1000); + assertThat(report1.getLoadReportIntervalNanos()).isEqualTo(1000); inOrder.verify(requestObserver) .onNext(argThat(new LoadStatsRequestMatcher(Collections.singletonList(report1)))); @@ -250,7 +250,7 @@ public void periodicLoadReporting() { fakeClock.forwardNanos(1000); assertThat(loadStatsStore1.reported).hasSize(1); report1 = loadStatsStore1.reported.poll(); - assertThat(Durations.toNanos(report1.getLoadReportInterval())).isEqualTo(2000); + assertThat(report1.getLoadReportIntervalNanos()).isEqualTo(2000); assertThat(loadStatsStore2.reported).isEmpty(); inOrder.verify(requestObserver) .onNext(argThat(new LoadStatsRequestMatcher(Collections.singletonList(report1)))); @@ -269,8 +269,8 @@ public void periodicLoadReporting() { report1 = loadStatsStore1.reported.poll(); assertThat(loadStatsStore2.reported).hasSize(1); ClusterStats report2 = loadStatsStore2.reported.poll(); - assertThat(Durations.toNanos(report1.getLoadReportInterval())).isEqualTo(2000); - assertThat(Durations.toNanos(report2.getLoadReportInterval())).isEqualTo(2000 + 2000); + assertThat(report1.getLoadReportIntervalNanos()).isEqualTo(2000); + assertThat(report2.getLoadReportIntervalNanos()).isEqualTo(2000 + 2000); inOrder.verify(requestObserver) .onNext(argThat(new LoadStatsRequestMatcher(Arrays.asList(report1, report2)))); @@ -283,7 +283,7 @@ public void periodicLoadReporting() { assertThat(loadStatsStore1.reported).isEmpty(); assertThat(loadStatsStore2.reported).hasSize(1); report2 = loadStatsStore2.reported.poll(); - assertThat(Durations.toNanos(report2.getLoadReportInterval())).isEqualTo(2000); + assertThat(report2.getLoadReportIntervalNanos()).isEqualTo(2000); inOrder.verify(requestObserver) .onNext(argThat(new LoadStatsRequestMatcher(Collections.singletonList(report2)))); @@ -399,7 +399,7 @@ public void lrsStreamClosedAndRetried() { .onNext(buildLrsResponse(ImmutableList.of(clusterName), 10)); fakeClock.forwardNanos(10); ClusterStats report = Iterables.getOnlyElement(loadStatsStore.reported); - assertThat(Durations.toNanos(report.getLoadReportInterval())) + assertThat(report.getLoadReportIntervalNanos()) .isEqualTo(TimeUnit.SECONDS.toNanos(1 + 10 + 2) + 10); verify(requestObserver) .onNext(argThat(new LoadStatsRequestMatcher(Collections.singletonList(report)))); @@ -500,8 +500,9 @@ public boolean matches(LoadStatsRequest argument) { if (argument.getClusterStatsCount() != expectedStats.size()) { return false; } - for (ClusterStats stats : argument.getClusterStatsList()) { - if (!stats.equals(expectedStats.get(stats.getClusterName()))) { + for (io.envoyproxy.envoy.api.v2.endpoint.ClusterStats stats + : argument.getClusterStatsList()) { + if (!stats.equals(expectedStats.get(stats.getClusterName()).toEnvoyProtoClusterStatsV2())) { return false; } } @@ -528,7 +529,7 @@ private FakeLoadStatsStore(String cluster, String clusterService, Stopwatch stop public ClusterStats generateLoadReport() { ClusterStats report = stats.toBuilder() - .setLoadReportInterval(Durations.fromNanos(stopwatch.elapsed(TimeUnit.NANOSECONDS))) + .setLoadReportIntervalNanos(stopwatch.elapsed(TimeUnit.NANOSECONDS)) .build(); stopwatch.reset().start(); reported.offer(report); @@ -563,25 +564,19 @@ private void refresh() { if (clusterService != null) { clusterStatsBuilder.setClusterServiceName(clusterService); } - clusterStatsBuilder.addUpstreamLocalityStats( - UpstreamLocalityStats.newBuilder() - .setLocality( - Locality.newBuilder() - .setRegion(cluster + "-region-foo") - .setZone(cluster + "-zone-bar") - .setSubZone(cluster + "-subzone-baz")) + clusterStatsBuilder + .addUpstreamLocalityStats(UpstreamLocalityStats.newBuilder() + .setLocality(new Locality( + cluster + "-region-foo", cluster + "-zone-bar", cluster + "-subzone-baz")) .setTotalRequestsInProgress(callsInProgress) .setTotalSuccessfulRequests(callsSucceeded) .setTotalErrorRequests(callsFailed) - .setTotalIssuedRequests(callsIssued)) + .setTotalIssuedRequests(callsIssued) + .build()) .addDroppedRequests( - DroppedRequests.newBuilder() - .setCategory("lb") - .setDroppedCount(numLbDrops)) + new DroppedRequests("lb",numLbDrops)) .addDroppedRequests( - DroppedRequests.newBuilder() - .setCategory("throttle") - .setDroppedCount(numThrottleDrops)) + new DroppedRequests("throttle", numThrottleDrops)) .setTotalDroppedRequests(numLbDrops + numThrottleDrops); stats = clusterStatsBuilder.build(); } diff --git a/xds/src/test/java/io/grpc/xds/LoadStatsStoreImplTest.java b/xds/src/test/java/io/grpc/xds/LoadStatsStoreImplTest.java index 4320ff279fd..127124afc2f 100644 --- a/xds/src/test/java/io/grpc/xds/LoadStatsStoreImplTest.java +++ b/xds/src/test/java/io/grpc/xds/LoadStatsStoreImplTest.java @@ -20,14 +20,13 @@ import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableMap; -import com.google.protobuf.util.Durations; -import io.envoyproxy.envoy.api.v2.endpoint.ClusterStats; -import io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.DroppedRequests; -import io.envoyproxy.envoy.api.v2.endpoint.EndpointLoadMetricStats; -import io.envoyproxy.envoy.api.v2.endpoint.UpstreamLocalityStats; import io.grpc.internal.FakeClock; import io.grpc.xds.ClientLoadCounter.MetricValue; +import io.grpc.xds.EnvoyProtoData.ClusterStats; +import io.grpc.xds.EnvoyProtoData.ClusterStats.DroppedRequests; +import io.grpc.xds.EnvoyProtoData.EndpointLoadMetricStats; import io.grpc.xds.EnvoyProtoData.Locality; +import io.grpc.xds.EnvoyProtoData.UpstreamLocalityStats; import io.grpc.xds.LoadStatsManager.LoadStatsStore; import java.util.ArrayList; import java.util.Arrays; @@ -85,7 +84,7 @@ private static UpstreamLocalityStats buildUpstreamLocalityStats( @Nullable List metrics) { UpstreamLocalityStats.Builder builder = UpstreamLocalityStats.newBuilder() - .setLocality(locality.toEnvoyProtoLocalityV2()) + .setLocality(locality) .setTotalSuccessfulRequests(callsSucceed) .setTotalErrorRequests(callsFailed) .setTotalRequestsInProgress(callsInProgress) @@ -97,10 +96,7 @@ private static UpstreamLocalityStats buildUpstreamLocalityStats( } private static DroppedRequests buildDroppedRequests(String category, long counts) { - return DroppedRequests.newBuilder() - .setCategory(category) - .setDroppedCount(counts) - .build(); + return new DroppedRequests(category, counts); } private static ClusterStats buildClusterStats( @@ -119,15 +115,16 @@ private static ClusterStats buildClusterStats( } clusterStatsBuilder.setTotalDroppedRequests(dropCount); } - clusterStatsBuilder.setLoadReportInterval(Durations.fromNanos(intervalNano)); + clusterStatsBuilder.setLoadReportIntervalNanos(intervalNano); return clusterStatsBuilder.build(); } private static void assertClusterStatsEqual(ClusterStats expected, ClusterStats actual) { assertThat(actual.getClusterName()).isEqualTo(expected.getClusterName()); - assertThat(actual.getLoadReportInterval()).isEqualTo(expected.getLoadReportInterval()); + assertThat(actual.getLoadReportIntervalNanos()) + .isEqualTo(expected.getLoadReportIntervalNanos()); assertThat(actual.getTotalDroppedRequests()).isEqualTo(expected.getTotalDroppedRequests()); - assertThat(actual.getDroppedRequestsCount()).isEqualTo(expected.getDroppedRequestsCount()); + assertThat(actual.getDroppedRequestsList()).hasSize(expected.getDroppedRequestsList().size()); assertThat(new HashSet<>(actual.getDroppedRequestsList())) .isEqualTo(new HashSet<>(expected.getDroppedRequestsList())); assertUpstreamLocalityStatsListsEqual(actual.getUpstreamLocalityStatsList(), @@ -137,7 +134,7 @@ private static void assertClusterStatsEqual(ClusterStats expected, ClusterStats private static void assertUpstreamLocalityStatsListsEqual(List expected, List actual) { assertThat(actual).hasSize(expected.size()); - Map expectedLocalityStats = + Map expectedLocalityStats = new HashMap<>(); for (UpstreamLocalityStats stats : expected) { expectedLocalityStats.put(stats.getLocality(), stats); @@ -164,10 +161,10 @@ private static void assertUpstreamLocalityStatsEqual(UpstreamLocalityStats expec @Test public void removeInactiveCountersAfterGeneratingLoadReport() { loadStatsStore.addLocality(LOCALITY1); - assertThat(loadStatsStore.generateLoadReport().getUpstreamLocalityStatsCount()).isEqualTo(1); + assertThat(loadStatsStore.generateLoadReport().getUpstreamLocalityStatsList()).hasSize(1); loadStatsStore.removeLocality(LOCALITY1); // becomes inactive - assertThat(loadStatsStore.generateLoadReport().getUpstreamLocalityStatsCount()).isEqualTo(1); - assertThat(loadStatsStore.generateLoadReport().getUpstreamLocalityStatsCount()).isEqualTo(0); + assertThat(loadStatsStore.generateLoadReport().getUpstreamLocalityStatsList()).hasSize(1); + assertThat(loadStatsStore.generateLoadReport().getUpstreamLocalityStatsList()).isEmpty(); } @Test @@ -175,12 +172,12 @@ public void localityCountersReferenceCounted() { loadStatsStore.addLocality(LOCALITY1); loadStatsStore.addLocality(LOCALITY1); loadStatsStore.removeLocality(LOCALITY1); - assertThat(loadStatsStore.generateLoadReport().getUpstreamLocalityStatsCount()).isEqualTo(1); - assertThat(loadStatsStore.generateLoadReport().getUpstreamLocalityStatsCount()) - .isEqualTo(1); // still active + assertThat(loadStatsStore.generateLoadReport().getUpstreamLocalityStatsList()).hasSize(1); + assertThat(loadStatsStore.generateLoadReport().getUpstreamLocalityStatsList()) + .hasSize(1); // still active loadStatsStore.removeLocality(LOCALITY1); // becomes inactive - assertThat(loadStatsStore.generateLoadReport().getUpstreamLocalityStatsCount()).isEqualTo(1); - assertThat(loadStatsStore.generateLoadReport().getUpstreamLocalityStatsCount()).isEqualTo(0); + assertThat(loadStatsStore.generateLoadReport().getUpstreamLocalityStatsList()).hasSize(1); + assertThat(loadStatsStore.generateLoadReport().getUpstreamLocalityStatsList()).isEmpty(); } @Test diff --git a/xds/src/test/java/io/grpc/xds/LocalityStoreTest.java b/xds/src/test/java/io/grpc/xds/LocalityStoreTest.java index 6c079b54edb..f514cf1f1d6 100644 --- a/xds/src/test/java/io/grpc/xds/LocalityStoreTest.java +++ b/xds/src/test/java/io/grpc/xds/LocalityStoreTest.java @@ -42,7 +42,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; -import io.envoyproxy.envoy.api.v2.endpoint.ClusterStats; import io.grpc.ChannelLogger; import io.grpc.ClientStreamTracer; import io.grpc.ConnectivityState; @@ -65,6 +64,7 @@ import io.grpc.internal.FakeClock.TaskFilter; import io.grpc.xds.ClientLoadCounter.LoadRecordingStreamTracerFactory; import io.grpc.xds.ClientLoadCounter.MetricsRecordingListener; +import io.grpc.xds.EnvoyProtoData.ClusterStats; import io.grpc.xds.EnvoyProtoData.DropOverload; import io.grpc.xds.EnvoyProtoData.LbEndpoint; import io.grpc.xds.EnvoyProtoData.Locality; diff --git a/xds/src/test/java/io/grpc/xds/LrsLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/LrsLoadBalancerTest.java index bea3a2f2faa..e53ca3048c7 100644 --- a/xds/src/test/java/io/grpc/xds/LrsLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/LrsLoadBalancerTest.java @@ -22,7 +22,6 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import io.envoyproxy.envoy.api.v2.endpoint.ClusterStats; import io.grpc.Attributes; import io.grpc.ClientStreamTracer; import io.grpc.ConnectivityState; @@ -39,6 +38,7 @@ import io.grpc.internal.ServiceConfigUtil.PolicySelection; import io.grpc.xds.ClientLoadCounter.LoadRecordingStreamTracerFactory; import io.grpc.xds.ClientLoadCounter.LoadRecordingSubchannelPicker; +import io.grpc.xds.EnvoyProtoData.ClusterStats; import io.grpc.xds.EnvoyProtoData.Locality; import io.grpc.xds.LoadStatsManager.LoadStatsStore; import io.grpc.xds.LrsLoadBalancerProvider.LrsConfig; From a91acec2d4429c40d3d1fe38745f712f86b0c470 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Tue, 18 Aug 2020 23:12:25 -0700 Subject: [PATCH 69/88] core: fix a bug for hedging with throttling (#7337) Resolves #7222: If a hedging substream fails triggering throttling threshold, the call should be committed. Refactored RetryPlan to two separate classes RetryPlan and HedgingPlan. --- .../io/grpc/internal/RetriableStream.java | 157 ++++++++++-------- .../io/grpc/internal/RetriableStreamTest.java | 34 +++- 2 files changed, 119 insertions(+), 72 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/RetriableStream.java b/core/src/main/java/io/grpc/internal/RetriableStream.java index cf3c9afe48d..44f194c5d0a 100644 --- a/core/src/main/java/io/grpc/internal/RetriableStream.java +++ b/core/src/main/java/io/grpc/internal/RetriableStream.java @@ -783,7 +783,6 @@ public void closed(Status status, RpcProgress rpcProgress, Metadata trailers) { } if (state.winningSubstream == null) { - boolean isFatal = false; if (rpcProgress == RpcProgress.REFUSED && noMoreTransparentRetry.compareAndSet(false, true)) { // transparent retry @@ -837,51 +836,54 @@ public void run() { nextBackoffIntervalNanos = retryPolicy.initialBackoffNanos; } - RetryPlan retryPlan = makeRetryDecision(status, trailers); - if (retryPlan.shouldRetry) { - // The check state.winningSubstream == null, checking if is not already committed, is - // racy, but is still safe b/c the retry will also handle committed/cancellation - FutureCanceller scheduledRetryCopy; + if (isHedging) { + HedgingPlan hedgingPlan = makeHedgingDecision(status, trailers); + if (hedgingPlan.isHedgeable) { + pushbackHedging(hedgingPlan.hedgingPushbackMillis); + } synchronized (lock) { - scheduledRetry = scheduledRetryCopy = new FutureCanceller(lock); + state = state.removeActiveHedge(substream); + // The invariant is whether or not #(Potential Hedge + active hedges) > 0. + // Once hasPotentialHedging(state) is false, it will always be false, and then + // #(state.activeHedges) will be decreasing. This guarantees that even there may be + // multiple concurrent hedges, one of the hedges will end up committed. + if (hedgingPlan.isHedgeable) { + if (hasPotentialHedging(state) || !state.activeHedges.isEmpty()) { + return; + } + // else, no activeHedges, no new hedges possible, try to commit + } // else, isHedgeable is false, try to commit } - scheduledRetryCopy.setFuture(scheduledExecutorService.schedule( - new Runnable() { - @Override - public void run() { - callExecutor.execute(new Runnable() { - @Override - public void run() { - // retry - Substream newSubstream - = createSubstream(substream.previousAttemptCount + 1); - drain(newSubstream); - } - }); - } - }, - retryPlan.backoffNanos, - TimeUnit.NANOSECONDS)); - return; - } - isFatal = retryPlan.isFatal; - pushbackHedging(retryPlan.hedgingPushbackMillis); - } - - if (isHedging) { - synchronized (lock) { - state = state.removeActiveHedge(substream); - - // The invariant is whether or not #(Potential Hedge + active hedges) > 0. - // Once hasPotentialHedging(state) is false, it will always be false, and then - // #(state.activeHedges) will be decreasing. This guarantees that even there may be - // multiple concurrent hedges, one of the hedges will end up committed. - if (!isFatal) { - if (hasPotentialHedging(state) || !state.activeHedges.isEmpty()) { - return; + } else { + RetryPlan retryPlan = makeRetryDecision(status, trailers); + if (retryPlan.shouldRetry) { + // The check state.winningSubstream == null, checking if is not already committed, is + // racy, but is still safe b/c the retry will also handle committed/cancellation + FutureCanceller scheduledRetryCopy; + synchronized (lock) { + scheduledRetry = scheduledRetryCopy = new FutureCanceller(lock); } - // else, no activeHedges, no new hedges possible, try to commit - } // else, fatal, try to commit + scheduledRetryCopy.setFuture( + scheduledExecutorService.schedule( + new Runnable() { + @Override + public void run() { + callExecutor.execute( + new Runnable() { + @Override + public void run() { + // retry + Substream newSubstream = + createSubstream(substream.previousAttemptCount + 1); + drain(newSubstream); + } + }); + } + }, + retryPlan.backoffNanos, + TimeUnit.NANOSECONDS)); + return; + } } } } @@ -901,26 +903,10 @@ private RetryPlan makeRetryDecision(Status status, Metadata trailer) { boolean shouldRetry = false; long backoffNanos = 0L; boolean isRetryableStatusCode = retryPolicy.retryableStatusCodes.contains(status.getCode()); - boolean isNonFatalStatusCode = hedgingPolicy.nonFatalStatusCodes.contains(status.getCode()); - if (isHedging && !isNonFatalStatusCode) { - // isFatal is true, no pushback - return new RetryPlan(/* shouldRetry = */ false, /* isFatal = */ true, 0, null); - } - - String pushbackStr = trailer.get(GRPC_RETRY_PUSHBACK_MS); - Integer pushbackMillis = null; - if (pushbackStr != null) { - try { - pushbackMillis = Integer.valueOf(pushbackStr); - } catch (NumberFormatException e) { - pushbackMillis = -1; - } - } - + Integer pushbackMillis = getPushbackMills(trailer); boolean isThrottled = false; if (throttle != null) { - if (isRetryableStatusCode || isNonFatalStatusCode - || (pushbackMillis != null && pushbackMillis < 0)) { + if (isRetryableStatusCode || (pushbackMillis != null && pushbackMillis < 0)) { isThrottled = !throttle.onQualifiedFailureThenCheckIsAboveThreshold(); } } @@ -933,7 +919,6 @@ private RetryPlan makeRetryDecision(Status status, Metadata trailer) { nextBackoffIntervalNanos = Math.min( (long) (nextBackoffIntervalNanos * retryPolicy.backoffMultiplier), retryPolicy.maxBackoffNanos); - } // else no retry } else if (pushbackMillis >= 0) { shouldRetry = true; @@ -942,8 +927,33 @@ private RetryPlan makeRetryDecision(Status status, Metadata trailer) { } // else no retry } // else no retry - return new RetryPlan( - shouldRetry, /* isFatal = */ false, backoffNanos, isHedging ? pushbackMillis : null); + return new RetryPlan(shouldRetry, backoffNanos); + } + + private HedgingPlan makeHedgingDecision(Status status, Metadata trailer) { + Integer pushbackMillis = getPushbackMills(trailer); + boolean isFatal = !hedgingPolicy.nonFatalStatusCodes.contains(status.getCode()); + boolean isThrottled = false; + if (throttle != null) { + if (!isFatal || (pushbackMillis != null && pushbackMillis < 0)) { + isThrottled = !throttle.onQualifiedFailureThenCheckIsAboveThreshold(); + } + } + return new HedgingPlan(!isFatal && !isThrottled, pushbackMillis); + } + + @Nullable + private Integer getPushbackMills(Metadata trailer) { + String pushbackStr = trailer.get(GRPC_RETRY_PUSHBACK_MS); + Integer pushbackMillis = null; + if (pushbackStr != null) { + try { + pushbackMillis = Integer.valueOf(pushbackStr); + } catch (NumberFormatException e) { + pushbackMillis = -1; + } + } + return pushbackMillis; } @Override @@ -1361,17 +1371,22 @@ public int hashCode() { private static final class RetryPlan { final boolean shouldRetry; - final boolean isFatal; // receiving a status not among the nonFatalStatusCodes final long backoffNanos; - @Nullable - final Integer hedgingPushbackMillis; - RetryPlan( - boolean shouldRetry, boolean isFatal, long backoffNanos, - @Nullable Integer hedgingPushbackMillis) { + RetryPlan(boolean shouldRetry, long backoffNanos) { this.shouldRetry = shouldRetry; - this.isFatal = isFatal; this.backoffNanos = backoffNanos; + } + } + + private static final class HedgingPlan { + final boolean isHedgeable; + @Nullable + final Integer hedgingPushbackMillis; + + public HedgingPlan( + boolean isHedgeable, @Nullable Integer hedgingPushbackMillis) { + this.isHedgeable = isHedgeable; this.hedgingPushbackMillis = hedgingPushbackMillis; } } diff --git a/core/src/test/java/io/grpc/internal/RetriableStreamTest.java b/core/src/test/java/io/grpc/internal/RetriableStreamTest.java index 77c5fe7be89..8a69fe501b2 100644 --- a/core/src/test/java/io/grpc/internal/RetriableStreamTest.java +++ b/core/src/test/java/io/grpc/internal/RetriableStreamTest.java @@ -37,6 +37,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -2284,7 +2285,7 @@ public void hedging_transparentRetryNotAllowed() { } @Test - public void hedging_throttled() { + public void hedging_throttledByOtherCall() { Throttle throttle = new Throttle(4f, 0.8f); RetriableStream hedgingStream = newThrottledHedgingStream(throttle); @@ -2313,6 +2314,37 @@ public void hedging_throttled() { assertEquals(0, fakeClock.numPendingTasks()); } + @Test + public void hedging_throttledByHedgingStreams() { + Throttle throttle = new Throttle(4f, 0.8f); + RetriableStream hedgingStream = newThrottledHedgingStream(throttle); + + ClientStream mockStream1 = mock(ClientStream.class); + ClientStream mockStream2 = mock(ClientStream.class); + ClientStream mockStream3 = mock(ClientStream.class); + when(retriableStreamRecorder.newSubstream(anyInt())) + .thenReturn(mockStream1, mockStream2, mockStream3); + + hedgingStream.start(masterListener); + ArgumentCaptor sublistenerCaptor1 = + ArgumentCaptor.forClass(ClientStreamListener.class); + verify(mockStream1).start(sublistenerCaptor1.capture()); + + fakeClock.forwardTime(HEDGING_DELAY_IN_SECONDS, TimeUnit.SECONDS); + ArgumentCaptor sublistenerCaptor2 = + ArgumentCaptor.forClass(ClientStreamListener.class); + verify(mockStream2).start(sublistenerCaptor2.capture()); + + sublistenerCaptor1.getValue().closed(Status.fromCode(NON_FATAL_STATUS_CODE_1), new Metadata()); + assertTrue(throttle.isAboveThreshold()); // count = 3 + sublistenerCaptor2.getValue().closed(Status.fromCode(NON_FATAL_STATUS_CODE_1), new Metadata()); + assertFalse(throttle.isAboveThreshold()); // count = 2 + + verify(masterListener).closed(any(Status.class), any(Metadata.class)); + verifyNoInteractions(mockStream3); + assertEquals(0, fakeClock.numPendingTasks()); + } + /** * Used to stub a retriable stream as well as to record methods of the retriable stream being * called. From c67dcb3b0884520d105725cad491cb58169ce810 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Wed, 19 Aug 2020 20:01:29 -0700 Subject: [PATCH 70/88] xds: refactor LoadReportClient for supporting LRS v3 --- .../java/io/grpc/xds/LoadReportClient.java | 240 +++++++++++------- .../main/java/io/grpc/xds/XdsClientImpl.java | 21 +- .../io/grpc/xds/LoadReportClientTest.java | 14 +- 3 files changed, 170 insertions(+), 105 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/LoadReportClient.java b/xds/src/main/java/io/grpc/xds/LoadReportClient.java index da526147902..d5126c68917 100644 --- a/xds/src/main/java/io/grpc/xds/LoadReportClient.java +++ b/xds/src/main/java/io/grpc/xds/LoadReportClient.java @@ -23,23 +23,21 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Stopwatch; import com.google.common.base.Supplier; -import com.google.protobuf.Struct; -import com.google.protobuf.Value; import com.google.protobuf.util.Durations; -import io.envoyproxy.envoy.api.v2.core.Node; -import io.envoyproxy.envoy.service.load_stats.v2.LoadReportingServiceGrpc; -import io.envoyproxy.envoy.service.load_stats.v2.LoadStatsRequest; -import io.envoyproxy.envoy.service.load_stats.v2.LoadStatsResponse; import io.grpc.InternalLogId; -import io.grpc.ManagedChannel; import io.grpc.Status; import io.grpc.SynchronizationContext; import io.grpc.SynchronizationContext.ScheduledHandle; import io.grpc.internal.BackoffPolicy; import io.grpc.stub.StreamObserver; import io.grpc.xds.EnvoyProtoData.ClusterStats; +import io.grpc.xds.EnvoyProtoData.Node; +import io.grpc.xds.XdsClient.XdsChannel; import io.grpc.xds.XdsLogger.XdsLogLevel; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; @@ -56,11 +54,10 @@ final class LoadReportClient { private final InternalLogId logId; private final XdsLogger logger; - private final ManagedChannel channel; + private final XdsChannel xdsChannel; private final Node node; private final SynchronizationContext syncContext; private final ScheduledExecutorService timerService; - private final Supplier stopwatchSupplier; private final Stopwatch retryStopwatch; private final BackoffPolicy.Provider backoffPolicyProvider; private final LoadStatsManager loadStatsManager; @@ -77,29 +74,27 @@ final class LoadReportClient { LoadReportClient( String targetName, LoadStatsManager loadStatsManager, - ManagedChannel channel, + XdsChannel xdsChannel, Node node, SynchronizationContext syncContext, ScheduledExecutorService scheduledExecutorService, BackoffPolicy.Provider backoffPolicyProvider, Supplier stopwatchSupplier) { this.loadStatsManager = checkNotNull(loadStatsManager, "loadStatsManager"); - this.channel = checkNotNull(channel, "channel"); + this.xdsChannel = checkNotNull(xdsChannel, "xdsChannel"); this.syncContext = checkNotNull(syncContext, "syncContext"); this.timerService = checkNotNull(scheduledExecutorService, "timeService"); this.backoffPolicyProvider = checkNotNull(backoffPolicyProvider, "backoffPolicyProvider"); - this.stopwatchSupplier = checkNotNull(stopwatchSupplier, "stopwatchSupplier"); + checkNotNull(stopwatchSupplier, "stopwatchSupplier"); this.retryStopwatch = stopwatchSupplier.get(); checkNotNull(targetName, "targetName"); checkNotNull(node, "node"); - Struct metadata = - node.getMetadata() - .toBuilder() - .putFields( - TARGET_NAME_METADATA_KEY, - Value.newBuilder().setStringValue(targetName).build()) - .build(); - this.node = node.toBuilder().setMetadata(metadata).build(); + Map newMetadata = new HashMap<>(); + if (node.getMetadata() != null) { + newMetadata.putAll(node.getMetadata()); + } + newMetadata.put(TARGET_NAME_METADATA_KEY, targetName); + this.node = node.toBuilder().setMetadata(newMetadata).build(); logId = InternalLogId.allocate("lrs-client", targetName); logger = XdsLogger.withLogId(logId); logger.log(XdsLogLevel.INFO, "Created"); @@ -163,17 +158,14 @@ public void run() { private void startLrsRpc() { checkState(lrsStream == null, "previous lbStream has not been cleared yet"); - LoadReportingServiceGrpc.LoadReportingServiceStub stub - = LoadReportingServiceGrpc.newStub(channel); - lrsStream = new LrsStream(stub, stopwatchSupplier.get()); + // TODO(zdapeng): implement LrsStreamV3 and instantiate lrsStream based on value of + // xdsChannel.useProtocolV3 + lrsStream = new LrsStreamV2(); retryStopwatch.reset().start(); lrsStream.start(); } - private class LrsStream implements StreamObserver { - - final LoadReportingServiceGrpc.LoadReportingServiceStub stub; - StreamObserver lrsRequestWriter; + private abstract class LrsStream { boolean initialResponseReceived; boolean closed; long loadReportIntervalNano = -1; @@ -181,32 +173,39 @@ private class LrsStream implements StreamObserver { List clusterNames; // clusters to report loads for, if not report all. ScheduledHandle loadReportTimer; - LrsStream(LoadReportingServiceGrpc.LoadReportingServiceStub stub, Stopwatch stopwatch) { - this.stub = checkNotNull(stub, "stub"); - } + abstract void start(); - void start() { - lrsRequestWriter = stub.withWaitForReady().streamLoadStats(this); - LoadStatsRequest initRequest = - LoadStatsRequest.newBuilder() - .setNode(node) - .build(); - lrsRequestWriter.onNext(initRequest); - logger.log(XdsLogLevel.DEBUG, "Initial LRS request sent:\n{0}", initRequest); - } + abstract void sendLoadStatsRequest(LoadStatsRequestData request); - @Override - public void onNext(final LoadStatsResponse response) { + abstract void sendError(Exception error); + + final void handleResponse(final LoadStatsResponseData response) { syncContext.execute(new Runnable() { @Override public void run() { - handleResponse(response); + if (closed) { + return; + } + if (!initialResponseReceived) { + logger.log(XdsLogLevel.DEBUG, "Initial LRS response received"); + initialResponseReceived = true; + } + reportAllClusters = response.getSendAllClusters(); + if (reportAllClusters) { + logger.log(XdsLogLevel.INFO, "Report loads for all clusters"); + } else { + logger.log(XdsLogLevel.INFO, "Report loads for clusters: ", response.getClustersList()); + clusterNames = response.getClustersList(); + } + long interval = response.getLoadReportingIntervalNanos(); + logger.log(XdsLogLevel.INFO, "Update load reporting interval to {0} ns", interval); + loadReportIntervalNano = interval; + scheduleNextLoadReport(); } }); } - @Override - public void onError(final Throwable t) { + final void handleRpcError(final Throwable t) { syncContext.execute(new Runnable() { @Override public void run() { @@ -215,8 +214,7 @@ public void run() { }); } - @Override - public void onCompleted() { + final void handleRpcComplete() { syncContext.execute(new Runnable() { @Override public void run() { @@ -227,21 +225,17 @@ public void run() { } private void sendLoadReport() { - LoadStatsRequest.Builder requestBuilder = LoadStatsRequest.newBuilder().setNode(node); + List clusterStatsList; if (reportAllClusters) { - for (ClusterStats clusterStats : loadStatsManager.getAllLoadReports()) { - requestBuilder.addClusterStats(clusterStats.toEnvoyProtoClusterStatsV2()); - } + clusterStatsList = loadStatsManager.getAllLoadReports(); } else { + clusterStatsList = new ArrayList<>(); for (String name : clusterNames) { - for (ClusterStats clusterStats : loadStatsManager.getClusterLoadReports(name)) { - requestBuilder.addClusterStats(clusterStats.toEnvoyProtoClusterStatsV2()); - } + clusterStatsList.addAll(loadStatsManager.getClusterLoadReports(name)); } } - LoadStatsRequest request = requestBuilder.build(); - lrsRequestWriter.onNext(request); - logger.log(XdsLogLevel.DEBUG, "Sent LoadStatsRequest\n{0}", request); + LoadStatsRequestData request = new LoadStatsRequestData(node, clusterStatsList); + sendLoadStatsRequest(request); scheduleNextLoadReport(); } @@ -258,29 +252,6 @@ private void scheduleNextLoadReport() { } } - private void handleResponse(LoadStatsResponse response) { - if (closed) { - return; - } - if (!initialResponseReceived) { - logger.log(XdsLogLevel.DEBUG, "Received LRS initial response:\n{0}", response); - initialResponseReceived = true; - } else { - logger.log(XdsLogLevel.DEBUG, "Received LRS response:\n{0}", response); - } - reportAllClusters = response.getSendAllClusters(); - if (reportAllClusters) { - logger.log(XdsLogLevel.INFO, "Report loads for all clusters"); - } else { - logger.log(XdsLogLevel.INFO, "Report loads for clusters: ", response.getClustersList()); - clusterNames = response.getClustersList(); - } - long interval = Durations.toNanos(response.getLoadReportingInterval()); - logger.log(XdsLogLevel.INFO, "Update load reporting interval to {0} ns", interval); - loadReportIntervalNano = interval; - scheduleNextLoadReport(); - } - private void handleStreamClosed(Status status) { checkArgument(!status.isOk(), "unexpected OK status"); if (closed) { @@ -317,17 +288,13 @@ private void handleStreamClosed(Status status) { } } - private void close(@Nullable Exception error) { + private void close(Exception error) { if (closed) { return; } closed = true; cleanUp(); - if (error == null) { - lrsRequestWriter.onCompleted(); - } else { - lrsRequestWriter.onError(error); - } + sendError(error); } private void cleanUp() { @@ -340,4 +307,107 @@ private void cleanUp() { } } } + + private final class LrsStreamV2 extends LrsStream { + StreamObserver lrsRequestWriterV2; + + @Override + void start() { + StreamObserver + lrsResponseReaderV2 = + new StreamObserver() { + @Override + public void onNext( + io.envoyproxy.envoy.service.load_stats.v2.LoadStatsResponse response) { + logger.log(XdsLogLevel.DEBUG, "Received LRS response:\n{0}", response); + handleResponse(LoadStatsResponseData.fromEnvoyProtoV2(response)); + } + + @Override + public void onError(Throwable t) { + handleRpcError(t); + } + + @Override + public void onCompleted() { + handleRpcComplete(); + } + }; + io.envoyproxy.envoy.service.load_stats.v2.LoadReportingServiceGrpc.LoadReportingServiceStub + stubV2 = io.envoyproxy.envoy.service.load_stats.v2.LoadReportingServiceGrpc.newStub( + xdsChannel.getManagedChannel()); + lrsRequestWriterV2 = stubV2.withWaitForReady().streamLoadStats(lrsResponseReaderV2); + logger.log(XdsLogLevel.DEBUG, "Sending initial LRS request"); + sendLoadStatsRequest(new LoadStatsRequestData(node, null)); + } + + @Override + void sendLoadStatsRequest(LoadStatsRequestData request) { + io.envoyproxy.envoy.service.load_stats.v2.LoadStatsRequest requestProto = + request.toEnvoyProtoV2(); + lrsRequestWriterV2.onNext(requestProto); + logger.log(XdsLogLevel.DEBUG, "Sent LoadStatsRequest\n{0}", requestProto); + } + + @Override + void sendError(Exception error) { + lrsRequestWriterV2.onError(error); + } + } + + private static final class LoadStatsRequestData { + final Node node; + @Nullable + final List clusterStatsList; + + LoadStatsRequestData(Node node, @Nullable List clusterStatsList) { + this.node = checkNotNull(node, "node"); + this.clusterStatsList = clusterStatsList; + } + + io.envoyproxy.envoy.service.load_stats.v2.LoadStatsRequest toEnvoyProtoV2() { + io.envoyproxy.envoy.service.load_stats.v2.LoadStatsRequest.Builder builder + = io.envoyproxy.envoy.service.load_stats.v2.LoadStatsRequest.newBuilder(); + builder.setNode(node.toEnvoyProtoNodeV2()); + if (clusterStatsList != null) { + for (ClusterStats stats : clusterStatsList) { + builder.addClusterStats(stats.toEnvoyProtoClusterStatsV2()); + } + } + return builder.build(); + } + } + + private static final class LoadStatsResponseData { + final boolean sendAllClusters; + final List clusters; + final long loadReportingIntervalNanos; + + LoadStatsResponseData( + boolean sendAllClusters, List clusters, long loadReportingIntervalNanos) { + this.sendAllClusters = sendAllClusters; + this.clusters = checkNotNull(clusters, "clusters"); + this.loadReportingIntervalNanos = loadReportingIntervalNanos; + } + + boolean getSendAllClusters() { + return sendAllClusters; + } + + List getClustersList() { + return clusters; + } + + long getLoadReportingIntervalNanos() { + return loadReportingIntervalNanos; + } + + static LoadStatsResponseData fromEnvoyProtoV2( + io.envoyproxy.envoy.service.load_stats.v2.LoadStatsResponse loadStatsResponse) { + return new LoadStatsResponseData( + loadStatsResponse.getSendAllClusters(), + loadStatsResponse.getClustersList(), + Durations.toNanos(loadStatsResponse.getLoadReportingInterval())); + } + } } diff --git a/xds/src/main/java/io/grpc/xds/XdsClientImpl.java b/xds/src/main/java/io/grpc/xds/XdsClientImpl.java index c386daca3c6..82addf72b3e 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClientImpl.java +++ b/xds/src/main/java/io/grpc/xds/XdsClientImpl.java @@ -48,7 +48,6 @@ import io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest; import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse; import io.grpc.InternalLogId; -import io.grpc.ManagedChannel; import io.grpc.Status; import io.grpc.SynchronizationContext; import io.grpc.SynchronizationContext.ScheduledHandle; @@ -117,8 +116,7 @@ final class XdsClientImpl extends XdsClient { private final XdsLogger logger; // Name of the target server this gRPC client is trying to talk to. private final String targetName; - private final ManagedChannel channel; - private final boolean useProtocolV3; + private final XdsChannel xdsChannel; private final SynchronizationContext syncContext; private final ScheduledExecutorService timeService; private final BackoffPolicy.Provider backoffPolicyProvider; @@ -209,8 +207,7 @@ final class XdsClientImpl extends XdsClient { XdsChannel xdsChannel = checkNotNull(channelFactory, "channelFactory") .createChannel(checkNotNull(servers, "servers")); - this.channel = xdsChannel.getManagedChannel(); - this.useProtocolV3 = xdsChannel.isUseProtocolV3(); + this.xdsChannel = xdsChannel; this.node = checkNotNull(node, "node"); this.syncContext = checkNotNull(syncContext, "syncContext"); this.timeService = checkNotNull(timeService, "timeService"); @@ -225,7 +222,7 @@ final class XdsClientImpl extends XdsClient { @Override void shutdown() { logger.log(XdsLogLevel.INFO, "Shutting down"); - channel.shutdown(); + xdsChannel.getManagedChannel().shutdown(); if (adsStream != null) { adsStream.close(Status.CANCELLED.withDescription("shutdown").asException()); } @@ -484,8 +481,8 @@ void reportClientStats() { new LoadReportClient( targetName, loadStatsManager, - channel, - node.toEnvoyProtoNodeV2(), + xdsChannel, + node, syncContext, timeService, backoffPolicyProvider, @@ -529,7 +526,7 @@ public String toString() { */ private void startRpcStream() { checkState(adsStream == null, "Previous adsStream has not been cleared yet"); - if (useProtocolV3) { + if (xdsChannel.isUseProtocolV3()) { adsStream = new AdsStream(); } else { adsStream = new AdsStreamV2(); @@ -1739,8 +1736,8 @@ private final class AdsStreamV2 extends AbstractAdsStream { private StreamObserver requestWriterV2; AdsStreamV2() { - stubV2 = - io.envoyproxy.envoy.service.discovery.v2.AggregatedDiscoveryServiceGrpc.newStub(channel); + stubV2 = io.envoyproxy.envoy.service.discovery.v2.AggregatedDiscoveryServiceGrpc.newStub( + xdsChannel.getManagedChannel()); } @Override @@ -1795,7 +1792,7 @@ private final class AdsStream extends AbstractAdsStream { private StreamObserver requestWriter; AdsStream() { - stub = AggregatedDiscoveryServiceGrpc.newStub(channel); + stub = AggregatedDiscoveryServiceGrpc.newStub(xdsChannel.getManagedChannel()); } @Override diff --git a/xds/src/test/java/io/grpc/xds/LoadReportClientTest.java b/xds/src/test/java/io/grpc/xds/LoadReportClientTest.java index a63171ef447..0a2951ad4dc 100644 --- a/xds/src/test/java/io/grpc/xds/LoadReportClientTest.java +++ b/xds/src/test/java/io/grpc/xds/LoadReportClientTest.java @@ -30,6 +30,7 @@ import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.Struct; @@ -56,6 +57,7 @@ import io.grpc.xds.EnvoyProtoData.UpstreamLocalityStats; import io.grpc.xds.LoadStatsManager.LoadStatsStore; import io.grpc.xds.LoadStatsManager.LoadStatsStoreFactory; +import io.grpc.xds.XdsClient.XdsChannel; import java.util.ArrayDeque; import java.util.Arrays; import java.util.Collection; @@ -87,14 +89,10 @@ public class LoadReportClientTest { private static final String TARGET_NAME = "lrs-test.example.com"; // bootstrap node identifier - private static final Node NODE = - Node.newBuilder() + private static final EnvoyProtoData.Node NODE = + EnvoyProtoData.Node.newBuilder() .setId("LRS test") - .setMetadata( - Struct.newBuilder() - .putFields( - "TRAFFICDIRECTOR_NETWORK_HOSTNAME", - Value.newBuilder().setStringValue("default").build())) + .setMetadata(ImmutableMap.of("TRAFFICDIRECTOR_NETWORK_HOSTNAME", "default")) .build(); private static final String CLUSTER1 = "cluster-foo.googleapis.com"; private static final String CLUSTER2 = "cluster-bar.googleapis.com"; @@ -189,7 +187,7 @@ public void cancelled(Context context) { new LoadReportClient( TARGET_NAME, loadStatsManager, - channel, + new XdsChannel(channel, false), NODE, syncContext, fakeClock.getScheduledExecutorService(), From 00fee4d141163edb358cdd83315c6e91027a6918 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Thu, 20 Aug 2020 10:45:41 -0700 Subject: [PATCH 71/88] xds: have LoadReportClient support LRS v3 As noted in the design doc "The LRS protocol has a transport version, just like the xDS protocol itself does. Initially, we will use the server feature in the bootstrap file to determine the version of the LRS transport protocol. This means that there will not be any way to use a different transport protocol for LRS than for xDS." --- .../java/io/grpc/xds/LoadReportClient.java | 73 ++++++++++++++++++- .../java/io/grpc/xds/XdsClientImplTest.java | 8 +- 2 files changed, 74 insertions(+), 7 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/LoadReportClient.java b/xds/src/main/java/io/grpc/xds/LoadReportClient.java index d5126c68917..c9cff2db6c4 100644 --- a/xds/src/main/java/io/grpc/xds/LoadReportClient.java +++ b/xds/src/main/java/io/grpc/xds/LoadReportClient.java @@ -24,6 +24,10 @@ import com.google.common.base.Stopwatch; import com.google.common.base.Supplier; import com.google.protobuf.util.Durations; +import io.envoyproxy.envoy.service.load_stats.v3.LoadReportingServiceGrpc; +import io.envoyproxy.envoy.service.load_stats.v3.LoadReportingServiceGrpc.LoadReportingServiceStub; +import io.envoyproxy.envoy.service.load_stats.v3.LoadStatsRequest; +import io.envoyproxy.envoy.service.load_stats.v3.LoadStatsResponse; import io.grpc.InternalLogId; import io.grpc.Status; import io.grpc.SynchronizationContext; @@ -158,9 +162,11 @@ public void run() { private void startLrsRpc() { checkState(lrsStream == null, "previous lbStream has not been cleared yet"); - // TODO(zdapeng): implement LrsStreamV3 and instantiate lrsStream based on value of - // xdsChannel.useProtocolV3 - lrsStream = new LrsStreamV2(); + if (xdsChannel.isUseProtocolV3()) { + lrsStream = new LrsStreamV3(); + } else { + lrsStream = new LrsStreamV2(); + } retryStopwatch.reset().start(); lrsStream.start(); } @@ -355,6 +361,49 @@ void sendError(Exception error) { } } + private final class LrsStreamV3 extends LrsStream { + StreamObserver lrsRequestWriterV3; + + @Override + void start() { + StreamObserver lrsResponseReaderV3 = + new StreamObserver() { + @Override + public void onNext(LoadStatsResponse response) { + logger.log(XdsLogLevel.DEBUG, "Received LRS response:\n{0}", response); + handleResponse(LoadStatsResponseData.fromEnvoyProtoV3(response)); + } + + @Override + public void onError(Throwable t) { + handleRpcError(t); + } + + @Override + public void onCompleted() { + handleRpcComplete(); + } + }; + LoadReportingServiceStub stubV3 = + LoadReportingServiceGrpc.newStub(xdsChannel.getManagedChannel()); + lrsRequestWriterV3 = stubV3.withWaitForReady().streamLoadStats(lrsResponseReaderV3); + logger.log(XdsLogLevel.DEBUG, "Sending initial LRS request"); + sendLoadStatsRequest(new LoadStatsRequestData(node, null)); + } + + @Override + void sendLoadStatsRequest(LoadStatsRequestData request) { + LoadStatsRequest requestProto = request.toEnvoyProtoV3(); + lrsRequestWriterV3.onNext(requestProto); + logger.log(XdsLogLevel.DEBUG, "Sent LoadStatsRequest\n{0}", requestProto); + } + + @Override + void sendError(Exception error) { + lrsRequestWriterV3.onError(error); + } + } + private static final class LoadStatsRequestData { final Node node; @Nullable @@ -376,6 +425,17 @@ io.envoyproxy.envoy.service.load_stats.v2.LoadStatsRequest toEnvoyProtoV2() { } return builder.build(); } + + LoadStatsRequest toEnvoyProtoV3() { + LoadStatsRequest.Builder builder = LoadStatsRequest.newBuilder(); + builder.setNode(node.toEnvoyProtoNode()); + if (clusterStatsList != null) { + for (ClusterStats stats : clusterStatsList) { + builder.addClusterStats(stats.toEnvoyProtoClusterStats()); + } + } + return builder.build(); + } } private static final class LoadStatsResponseData { @@ -409,5 +469,12 @@ static LoadStatsResponseData fromEnvoyProtoV2( loadStatsResponse.getClustersList(), Durations.toNanos(loadStatsResponse.getLoadReportingInterval())); } + + static LoadStatsResponseData fromEnvoyProtoV3(LoadStatsResponse loadStatsResponse) { + return new LoadStatsResponseData( + loadStatsResponse.getSendAllClusters(), + loadStatsResponse.getClustersList(), + Durations.toNanos(loadStatsResponse.getLoadReportingInterval())); + } } } diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java index d2cf73ab71c..149dc19cdd0 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java @@ -55,9 +55,9 @@ import io.envoyproxy.envoy.api.v2.core.AggregatedConfigSource; import io.envoyproxy.envoy.api.v2.core.ConfigSource; import io.envoyproxy.envoy.api.v2.core.HealthStatus; -import io.envoyproxy.envoy.api.v2.endpoint.ClusterStats; import io.envoyproxy.envoy.api.v2.route.RedirectAction; import io.envoyproxy.envoy.api.v2.route.WeightedCluster; +import io.envoyproxy.envoy.config.endpoint.v3.ClusterStats; import io.envoyproxy.envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager; import io.envoyproxy.envoy.config.filter.network.http_connection_manager.v2.Rds; import io.envoyproxy.envoy.config.route.v3.QueryParameterMatcher; @@ -70,9 +70,9 @@ import io.envoyproxy.envoy.service.discovery.v3.AggregatedDiscoveryServiceGrpc.AggregatedDiscoveryServiceImplBase; import io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest; import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse; -import io.envoyproxy.envoy.service.load_stats.v2.LoadReportingServiceGrpc.LoadReportingServiceImplBase; -import io.envoyproxy.envoy.service.load_stats.v2.LoadStatsRequest; -import io.envoyproxy.envoy.service.load_stats.v2.LoadStatsResponse; +import io.envoyproxy.envoy.service.load_stats.v3.LoadReportingServiceGrpc.LoadReportingServiceImplBase; +import io.envoyproxy.envoy.service.load_stats.v3.LoadStatsRequest; +import io.envoyproxy.envoy.service.load_stats.v3.LoadStatsResponse; import io.grpc.Context; import io.grpc.Context.CancellationListener; import io.grpc.ManagedChannel; From 39450766dd207f6bcbe5199f1981e2141874500f Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Thu, 20 Aug 2020 13:25:17 -0700 Subject: [PATCH 72/88] xds: refactor XdsClientImplTest to use v3 proto only --- .../java/io/grpc/xds/EdsLoadBalancerTest.java | 130 +++---- .../java/io/grpc/xds/XdsClientImplTest.java | 232 ++++++------ .../xds/XdsClientImplTestForListener.java | 48 +-- .../java/io/grpc/xds/XdsClientImplTestV2.java | 350 +++++++++--------- .../java/io/grpc/xds/XdsClientTestHelper.java | 249 ++++++++++--- .../xds/XdsNameResolverIntegrationTest.java | 24 +- 6 files changed, 595 insertions(+), 438 deletions(-) diff --git a/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java index 17706682bc0..7461159eaa9 100644 --- a/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/EdsLoadBalancerTest.java @@ -21,11 +21,11 @@ import static io.grpc.ConnectivityState.CONNECTING; import static io.grpc.ConnectivityState.READY; import static io.grpc.ConnectivityState.TRANSIENT_FAILURE; -import static io.grpc.xds.XdsClientTestHelper.buildClusterLoadAssignment; +import static io.grpc.xds.XdsClientTestHelper.buildClusterLoadAssignmentV2; import static io.grpc.xds.XdsClientTestHelper.buildDiscoveryResponseV2; -import static io.grpc.xds.XdsClientTestHelper.buildDropOverload; -import static io.grpc.xds.XdsClientTestHelper.buildLbEndpoint; -import static io.grpc.xds.XdsClientTestHelper.buildLocalityLbEndpoints; +import static io.grpc.xds.XdsClientTestHelper.buildDropOverloadV2; +import static io.grpc.xds.XdsClientTestHelper.buildLbEndpointV2; +import static io.grpc.xds.XdsClientTestHelper.buildLocalityLbEndpointsV2; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; @@ -282,13 +282,13 @@ public void handleNameResolutionErrorBeforeAndAfterEdsWorkding() { // Endpoint update received. ClusterLoadAssignment clusterLoadAssignment = - buildClusterLoadAssignment(CLUSTER_NAME, + buildClusterLoadAssignmentV2(CLUSTER_NAME, ImmutableList.of( - buildLocalityLbEndpoints("region1", "zone1", "subzone1", + buildLocalityLbEndpointsV2("region1", "zone1", "subzone1", ImmutableList.of( - buildLbEndpoint("192.168.0.1", 8080, HEALTHY, 2)), + buildLbEndpointV2("192.168.0.1", 8080, HEALTHY, 2)), 1, 0)), - ImmutableList.of(buildDropOverload("throttle", 1000))); + ImmutableList.of(buildDropOverloadV2("throttle", 1000))); deliverClusterLoadAssignments(clusterLoadAssignment); // handleResolutionError() after receiving endpoint update. @@ -304,11 +304,11 @@ public void handleEdsServiceNameChange() { deliverResolvedAddresses("edsServiceName1", null, fakeEndpointPickingPolicy); ClusterLoadAssignment clusterLoadAssignment = - buildClusterLoadAssignment("edsServiceName1", + buildClusterLoadAssignmentV2("edsServiceName1", ImmutableList.of( - buildLocalityLbEndpoints("region1", "zone1", "subzone1", + buildLocalityLbEndpointsV2("region1", "zone1", "subzone1", ImmutableList.of( - buildLbEndpoint("192.168.0.1", 8080, HEALTHY, 2)), + buildLbEndpointV2("192.168.0.1", 8080, HEALTHY, 2)), 1, 0)), ImmutableList.of()); deliverClusterLoadAssignments(clusterLoadAssignment); @@ -327,11 +327,11 @@ public void handleEdsServiceNameChange() { verify(childBalancer1).shutdown(); clusterLoadAssignment = - buildClusterLoadAssignment("edsServiceName2", + buildClusterLoadAssignmentV2("edsServiceName2", ImmutableList.of( - buildLocalityLbEndpoints("region2", "zone2", "subzone2", + buildLocalityLbEndpointsV2("region2", "zone2", "subzone2", ImmutableList.of( - buildLbEndpoint("192.168.0.2", 8080, HEALTHY, 2)), + buildLbEndpointV2("192.168.0.2", 8080, HEALTHY, 2)), 1, 0)), ImmutableList.of()); deliverClusterLoadAssignments(clusterLoadAssignment); @@ -354,11 +354,11 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { // Change edsServiceName to edsServiceName3. deliverResolvedAddresses("edsServiceName3", null, fakeEndpointPickingPolicy); clusterLoadAssignment = - buildClusterLoadAssignment("edsServiceName3", + buildClusterLoadAssignmentV2("edsServiceName3", ImmutableList.of( - buildLocalityLbEndpoints("region3", "zone3", "subzone3", + buildLocalityLbEndpointsV2("region3", "zone3", "subzone3", ImmutableList.of( - buildLbEndpoint("192.168.0.3", 8080, HEALTHY, 2)), + buildLbEndpointV2("192.168.0.3", 8080, HEALTHY, 2)), 1, 0)), ImmutableList.of()); deliverClusterLoadAssignments(clusterLoadAssignment); @@ -382,11 +382,11 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { verify(childBalancer3).shutdown(); clusterLoadAssignment = - buildClusterLoadAssignment("edsServiceName4", + buildClusterLoadAssignmentV2("edsServiceName4", ImmutableList.of( - buildLocalityLbEndpoints("region4", "zone4", "subzone4", + buildLocalityLbEndpointsV2("region4", "zone4", "subzone4", ImmutableList.of( - buildLbEndpoint("192.168.0.4", 8080, HEALTHY, 2)), + buildLbEndpointV2("192.168.0.4", 8080, HEALTHY, 2)), 1, 0)), ImmutableList.of()); deliverClusterLoadAssignments(clusterLoadAssignment); @@ -408,11 +408,11 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { // Change edsServiceName to edsServiceName5. deliverResolvedAddresses("edsServiceName5", null, fakeEndpointPickingPolicy); clusterLoadAssignment = - buildClusterLoadAssignment("edsServiceName5", + buildClusterLoadAssignmentV2("edsServiceName5", ImmutableList.of( - buildLocalityLbEndpoints("region5", "zone5", "subzone5", + buildLocalityLbEndpointsV2("region5", "zone5", "subzone5", ImmutableList.of( - buildLbEndpoint("192.168.0.5", 8080, HEALTHY, 2)), + buildLbEndpointV2("192.168.0.5", 8080, HEALTHY, 2)), 1, 0)), ImmutableList.of()); deliverClusterLoadAssignments(clusterLoadAssignment); @@ -443,12 +443,12 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { public void edsResourceUpdate_allDrop() { deliverResolvedAddresses(null, null, fakeEndpointPickingPolicy); - ClusterLoadAssignment clusterLoadAssignment = buildClusterLoadAssignment( + ClusterLoadAssignment clusterLoadAssignment = buildClusterLoadAssignmentV2( CLUSTER_NAME, ImmutableList.of( - buildLocalityLbEndpoints("region1", "zone1", "subzone1", + buildLocalityLbEndpointsV2("region1", "zone1", "subzone1", ImmutableList.of( - buildLbEndpoint("192.168.0.1", 8080, HEALTHY, 2)), + buildLbEndpointV2("192.168.0.1", 8080, HEALTHY, 2)), 1, 0)), ImmutableList.of()); deliverClusterLoadAssignments(clusterLoadAssignment); @@ -469,17 +469,17 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { childHelper.updateBalancingState(READY, picker); assertLatestSubchannelPicker(subchannel); - clusterLoadAssignment = buildClusterLoadAssignment( + clusterLoadAssignment = buildClusterLoadAssignmentV2( CLUSTER_NAME, ImmutableList.of( - buildLocalityLbEndpoints("region1", "zone1", "subzone1", + buildLocalityLbEndpointsV2("region1", "zone1", "subzone1", ImmutableList.of( - buildLbEndpoint("192.168.0.1", 8080, HEALTHY, 2)), + buildLbEndpointV2("192.168.0.1", 8080, HEALTHY, 2)), 1, 0)), ImmutableList.of( - buildDropOverload("cat_1", 3), - buildDropOverload("cat_2", 1_000_001), - buildDropOverload("cat_3", 4))); + buildDropOverloadV2("cat_1", 3), + buildDropOverloadV2("cat_2", 1_000_001), + buildDropOverloadV2("cat_3", 4))); deliverClusterLoadAssignments(clusterLoadAssignment); verify(helper, atLeastOnce()).updateBalancingState(eq(READY), pickerCaptor.capture()); @@ -492,30 +492,30 @@ public PickResult pickSubchannel(PickSubchannelArgs args) { public void edsResourceUpdate_localityAssignmentChange() { deliverResolvedAddresses(null, null, fakeEndpointPickingPolicy); - LbEndpoint endpoint11 = buildLbEndpoint("addr11.example.com", 8011, HEALTHY, 11); - LbEndpoint endpoint12 = buildLbEndpoint("addr12.example.com", 8012, HEALTHY, 12); - LocalityLbEndpoints localityLbEndpoints1 = buildLocalityLbEndpoints( + LbEndpoint endpoint11 = buildLbEndpointV2("addr11.example.com", 8011, HEALTHY, 11); + LbEndpoint endpoint12 = buildLbEndpointV2("addr12.example.com", 8012, HEALTHY, 12); + LocalityLbEndpoints localityLbEndpoints1 = buildLocalityLbEndpointsV2( "region1", "zone1", "subzone1", ImmutableList.of(endpoint11, endpoint12), 1, 0); - LbEndpoint endpoint21 = buildLbEndpoint("addr21.example.com", 8021, HEALTHY, 21); - LbEndpoint endpoint22 = buildLbEndpoint("addr22.example.com", 8022, HEALTHY, 22); - LocalityLbEndpoints localityLbEndpoints2 = buildLocalityLbEndpoints( + LbEndpoint endpoint21 = buildLbEndpointV2("addr21.example.com", 8021, HEALTHY, 21); + LbEndpoint endpoint22 = buildLbEndpointV2("addr22.example.com", 8022, HEALTHY, 22); + LocalityLbEndpoints localityLbEndpoints2 = buildLocalityLbEndpointsV2( "region2", "zone2", "subzone2", ImmutableList.of(endpoint21, endpoint22), 2, 0); - LbEndpoint endpoint31 = buildLbEndpoint("addr31.example.com", 8031, HEALTHY, 31); - LocalityLbEndpoints localityLbEndpoints3 = buildLocalityLbEndpoints( + LbEndpoint endpoint31 = buildLbEndpointV2("addr31.example.com", 8031, HEALTHY, 31); + LocalityLbEndpoints localityLbEndpoints3 = buildLocalityLbEndpointsV2( "region3", "zone3", "subzone3", ImmutableList.of(endpoint31), 3, 0); - ClusterLoadAssignment clusterLoadAssignment = buildClusterLoadAssignment( + ClusterLoadAssignment clusterLoadAssignment = buildClusterLoadAssignmentV2( CLUSTER_NAME, ImmutableList.of(localityLbEndpoints1, localityLbEndpoints2, localityLbEndpoints3), ImmutableList.of()); @@ -581,32 +581,32 @@ LocalityStore newLocalityStore( assertThat(localityStores).hasSize(1); LocalityStore localityStore = localityStores.peekLast(); - ClusterLoadAssignment clusterLoadAssignment = buildClusterLoadAssignment( + ClusterLoadAssignment clusterLoadAssignment = buildClusterLoadAssignmentV2( "edsServiceName1", ImmutableList.of( - buildLocalityLbEndpoints("region1", "zone1", "subzone1", + buildLocalityLbEndpointsV2("region1", "zone1", "subzone1", ImmutableList.of( - buildLbEndpoint("192.168.0.1", 8080, HEALTHY, 2)), + buildLbEndpointV2("192.168.0.1", 8080, HEALTHY, 2)), 1, 0)), ImmutableList.of( - buildDropOverload("cat_1", 3), - buildDropOverload("cat_2", 456))); + buildDropOverloadV2("cat_1", 3), + buildDropOverloadV2("cat_2", 456))); deliverClusterLoadAssignments(clusterLoadAssignment); EndpointUpdate endpointUpdate = getEndpointUpdateFromClusterAssignmentV2(clusterLoadAssignment); verify(localityStore).updateDropPercentage(endpointUpdate.getDropPolicies()); verify(localityStore).updateLocalityStore(endpointUpdate.getLocalityLbEndpointsMap()); - clusterLoadAssignment = buildClusterLoadAssignment( + clusterLoadAssignment = buildClusterLoadAssignmentV2( "edsServiceName1", ImmutableList.of( - buildLocalityLbEndpoints("region1", "zone1", "subzone1", + buildLocalityLbEndpointsV2("region1", "zone1", "subzone1", ImmutableList.of( - buildLbEndpoint("192.168.0.1", 8080, HEALTHY, 2), - buildLbEndpoint("192.168.0.1", 8088, HEALTHY, 2)), + buildLbEndpointV2("192.168.0.1", 8080, HEALTHY, 2), + buildLbEndpointV2("192.168.0.1", 8088, HEALTHY, 2)), 1, 0)), ImmutableList.of( - buildDropOverload("cat_1", 3), - buildDropOverload("cat_3", 4))); + buildDropOverloadV2("cat_1", 3), + buildDropOverloadV2("cat_3", 4))); deliverClusterLoadAssignments(clusterLoadAssignment); endpointUpdate = getEndpointUpdateFromClusterAssignmentV2(clusterLoadAssignment); @@ -618,17 +618,17 @@ LocalityStore newLocalityStore( assertThat(localityStores).hasSize(2); localityStore = localityStores.peekLast(); - clusterLoadAssignment = buildClusterLoadAssignment( + clusterLoadAssignment = buildClusterLoadAssignmentV2( "edsServiceName2", ImmutableList.of( - buildLocalityLbEndpoints("region2", "zone2", "subzone2", + buildLocalityLbEndpointsV2("region2", "zone2", "subzone2", ImmutableList.of( - buildLbEndpoint("192.168.0.2", 8080, HEALTHY, 2), - buildLbEndpoint("192.168.0.2", 8088, HEALTHY, 2)), + buildLbEndpointV2("192.168.0.2", 8080, HEALTHY, 2), + buildLbEndpointV2("192.168.0.2", 8088, HEALTHY, 2)), 1, 0)), ImmutableList.of( - buildDropOverload("cat_1", 3), - buildDropOverload("cat_3", 4))); + buildDropOverloadV2("cat_1", 3), + buildDropOverloadV2("cat_3", 4))); deliverClusterLoadAssignments(clusterLoadAssignment); endpointUpdate = getEndpointUpdateFromClusterAssignmentV2(clusterLoadAssignment); verify(localityStore).updateDropPercentage(endpointUpdate.getDropPolicies()); @@ -653,11 +653,11 @@ public void edsResourceNotExist() { public void edsResourceRemoved() { deliverResolvedAddresses(null, null, fakeEndpointPickingPolicy); ClusterLoadAssignment clusterLoadAssignment = - buildClusterLoadAssignment(CLUSTER_NAME, + buildClusterLoadAssignmentV2(CLUSTER_NAME, ImmutableList.of( - buildLocalityLbEndpoints("region", "zone", "subzone", + buildLocalityLbEndpointsV2("region", "zone", "subzone", ImmutableList.of( - buildLbEndpoint("192.168.0.1", 8080, HEALTHY, 2)), + buildLbEndpointV2("192.168.0.1", 8080, HEALTHY, 2)), 1, 0)), ImmutableList.of()); deliverClusterLoadAssignments(clusterLoadAssignment); @@ -709,13 +709,13 @@ public void transientError_withPreviousEndpointUpdateReceived() { deliverResolvedAddresses(null, null, fakeEndpointPickingPolicy); // Endpoint update received. ClusterLoadAssignment clusterLoadAssignment = - buildClusterLoadAssignment(CLUSTER_NAME, + buildClusterLoadAssignmentV2(CLUSTER_NAME, ImmutableList.of( - buildLocalityLbEndpoints("region1", "zone1", "subzone1", + buildLocalityLbEndpointsV2("region1", "zone1", "subzone1", ImmutableList.of( - buildLbEndpoint("192.168.0.1", 8080, HEALTHY, 2)), + buildLbEndpointV2("192.168.0.1", 8080, HEALTHY, 2)), 1, 0)), - ImmutableList.of(buildDropOverload("throttle", 1000))); + ImmutableList.of(buildDropOverloadV2("throttle", 1000))); deliverClusterLoadAssignments(clusterLoadAssignment); verify(helper, never()).updateBalancingState( diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java index 149dc19cdd0..c06a6cc5fc4 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTest.java @@ -49,24 +49,23 @@ import com.google.protobuf.BoolValue; import com.google.protobuf.UInt32Value; import com.google.protobuf.util.Durations; -import io.envoyproxy.envoy.api.v2.ClusterLoadAssignment; -import io.envoyproxy.envoy.api.v2.ClusterLoadAssignment.Policy; -import io.envoyproxy.envoy.api.v2.auth.UpstreamTlsContext; -import io.envoyproxy.envoy.api.v2.core.AggregatedConfigSource; -import io.envoyproxy.envoy.api.v2.core.ConfigSource; -import io.envoyproxy.envoy.api.v2.core.HealthStatus; -import io.envoyproxy.envoy.api.v2.route.RedirectAction; -import io.envoyproxy.envoy.api.v2.route.WeightedCluster; +import io.envoyproxy.envoy.config.core.v3.AggregatedConfigSource; +import io.envoyproxy.envoy.config.core.v3.ConfigSource; +import io.envoyproxy.envoy.config.core.v3.HealthStatus; +import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment; import io.envoyproxy.envoy.config.endpoint.v3.ClusterStats; -import io.envoyproxy.envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager; -import io.envoyproxy.envoy.config.filter.network.http_connection_manager.v2.Rds; import io.envoyproxy.envoy.config.route.v3.QueryParameterMatcher; +import io.envoyproxy.envoy.config.route.v3.RedirectAction; import io.envoyproxy.envoy.config.route.v3.Route; import io.envoyproxy.envoy.config.route.v3.RouteAction; import io.envoyproxy.envoy.config.route.v3.RouteConfiguration; import io.envoyproxy.envoy.config.route.v3.RouteMatch; import io.envoyproxy.envoy.config.route.v3.VirtualHost; +import io.envoyproxy.envoy.config.route.v3.WeightedCluster; +import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager; +import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.Rds; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.SdsSecretConfig; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext; import io.envoyproxy.envoy.service.discovery.v3.AggregatedDiscoveryServiceGrpc.AggregatedDiscoveryServiceImplBase; import io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest; import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse; @@ -362,7 +361,7 @@ public void ldsResponseWithoutMatchingResource() { "cluster-baz.googleapis.com")))) .build())))); DiscoveryResponse response = - buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS, "0000"); responseObserver.onNext(response); // Client sends an ACK LDS request. @@ -397,7 +396,7 @@ public void failToFindVirtualHostInLdsResponseInLineRouteConfig() { XdsClientImpl.ADS_TYPE_URL_LDS, ""))); assertThat(fakeClock.getPendingTasks(LDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); - io.envoyproxy.envoy.api.v2.RouteConfiguration routeConfig = + io.envoyproxy.envoy.config.route.v3.RouteConfiguration routeConfig = buildRouteConfiguration( "route.googleapis.com", ImmutableList.of( @@ -410,7 +409,7 @@ public void failToFindVirtualHostInLdsResponseInLineRouteConfig() { Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ Any.pack(HttpConnectionManager.newBuilder().setRouteConfig(routeConfig).build())))); DiscoveryResponse response = - buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS, "0000"); responseObserver.onNext(response); // Client sends an NACK LDS request. @@ -482,7 +481,7 @@ public void resolveVirtualHostInLdsResponse() { "some cluster")))) .build())))); DiscoveryResponse response = - buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS, "0000"); responseObserver.onNext(response); assertThat(ldsRespTimer.isCancelled()).isTrue(); @@ -530,7 +529,7 @@ public void rdsResponseWithoutMatchingResource() { Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) ); DiscoveryResponse response = - buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS, "0000"); responseObserver.onNext(response); // Client sends an ACK LDS request. @@ -562,7 +561,7 @@ public void rdsResponseWithoutMatchingResource() { buildVirtualHost( ImmutableList.of(TARGET_AUTHORITY), "some more whatever cluster"))))); - response = buildDiscoveryResponse("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); + response = buildDiscoveryResponse("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS, "0000"); responseObserver.onNext(response); // Client sends an ACK RDS request. @@ -602,7 +601,7 @@ public void resolveVirtualHostInRdsResponse() { Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) ); DiscoveryResponse response = - buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS, "0000"); responseObserver.onNext(response); // Client sends an ACK LDS request and an RDS request for "route-foo.googleapis.com". (Omitted) @@ -626,7 +625,7 @@ public void resolveVirtualHostInRdsResponse() { ImmutableList.of( buildVirtualHost(ImmutableList.of("foo.googleapis.com"), "some more cluster"))))); - response = buildDiscoveryResponse("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); + response = buildDiscoveryResponse("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS, "0000"); responseObserver.onNext(response); assertThat(fakeClock.getPendingTasks(RDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); @@ -666,7 +665,7 @@ public void resolveVirtualHostWithPathMatchingInRdsResponse() { Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) ); DiscoveryResponse response = - buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS, "0000"); responseObserver.onNext(response); // Client sends an ACK LDS request and an RDS request for "route-foo.googleapis.com". (Omitted) @@ -681,24 +680,24 @@ public void resolveVirtualHostWithPathMatchingInRdsResponse() { buildRouteConfiguration( "route-foo.googleapis.com", ImmutableList.of( - io.envoyproxy.envoy.api.v2.route.VirtualHost.newBuilder() + VirtualHost.newBuilder() .setName("virtualhost00.googleapis.com") // don't care // domains wit a match. .addAllDomains(ImmutableList.of(TARGET_AUTHORITY, "bar.googleapis.com")) .addRoutes( - io.envoyproxy.envoy.api.v2.route.Route.newBuilder() + Route.newBuilder() // path match with cluster route .setRoute( - io.envoyproxy.envoy.api.v2.route.RouteAction.newBuilder() + RouteAction.newBuilder() .setCluster("cl1.googleapis.com")) .setMatch( - io.envoyproxy.envoy.api.v2.route.RouteMatch.newBuilder() + RouteMatch.newBuilder() .setPath("/service1/method1"))) .addRoutes( - io.envoyproxy.envoy.api.v2.route.Route.newBuilder() + Route.newBuilder() // path match with weighted cluster route .setRoute( - io.envoyproxy.envoy.api.v2.route.RouteAction.newBuilder() + RouteAction.newBuilder() .setWeightedClusters( WeightedCluster.newBuilder() .addClusters( @@ -710,28 +709,28 @@ public void resolveVirtualHostWithPathMatchingInRdsResponse() { .setWeight(UInt32Value.of(70)) .setName("cl22.googleapis.com")))) .setMatch( - io.envoyproxy.envoy.api.v2.route.RouteMatch.newBuilder() + RouteMatch.newBuilder() .setPath("/service2/method2"))) .addRoutes( - io.envoyproxy.envoy.api.v2.route.Route.newBuilder() + Route.newBuilder() // prefix match with cluster route .setRoute( - io.envoyproxy.envoy.api.v2.route.RouteAction.newBuilder() + RouteAction.newBuilder() .setCluster("cl1.googleapis.com")) .setMatch( - io.envoyproxy.envoy.api.v2.route.RouteMatch.newBuilder() + RouteMatch.newBuilder() .setPrefix("/service1/"))) .addRoutes( - io.envoyproxy.envoy.api.v2.route.Route.newBuilder() + Route.newBuilder() // default match with cluster route .setRoute( - io.envoyproxy.envoy.api.v2.route.RouteAction.newBuilder() + RouteAction.newBuilder() .setCluster("cluster.googleapis.com")) .setMatch( - io.envoyproxy.envoy.api.v2.route.RouteMatch.newBuilder() + RouteMatch.newBuilder() .setPrefix(""))) .build())))); - response = buildDiscoveryResponse("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); + response = buildDiscoveryResponse("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS, "0000"); responseObserver.onNext(response); assertThat(fakeClock.getPendingTasks(RDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); @@ -809,7 +808,7 @@ public void failToFindVirtualHostInRdsResponse() { Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) ); DiscoveryResponse response = - buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS, "0000"); responseObserver.onNext(response); // Client sends an ACK LDS request and an RDS request for "route-foo.googleapis.com". (Omitted) @@ -832,7 +831,7 @@ public void failToFindVirtualHostInRdsResponse() { ImmutableList.of( buildVirtualHost(ImmutableList.of("one more does not match"), "some more cluster"))))); - response = buildDiscoveryResponse("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); + response = buildDiscoveryResponse("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS, "0000"); responseObserver.onNext(response); // Client sent an NACK RDS request. @@ -875,7 +874,7 @@ public void matchingVirtualHostDoesNotContainRouteAction() { Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) ); DiscoveryResponse response = - buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS, "0000"); responseObserver.onNext(response); // Client sends an ACK LDS request and an RDS request for "route-foo.googleapis.com". (Omitted) @@ -883,12 +882,12 @@ public void matchingVirtualHostDoesNotContainRouteAction() { assertThat(fakeClock.getPendingTasks(RDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); // A VirtualHost with a Route that contains only redirect configuration. - io.envoyproxy.envoy.api.v2.route.VirtualHost virtualHost = - io.envoyproxy.envoy.api.v2.route.VirtualHost.newBuilder() + VirtualHost virtualHost = + VirtualHost.newBuilder() .setName("virtualhost00.googleapis.com") // don't care .addDomains(TARGET_AUTHORITY) .addRoutes( - io.envoyproxy.envoy.api.v2.route.Route.newBuilder() + Route.newBuilder() .setRedirect( RedirectAction.newBuilder() .setHostRedirect("bar.googleapis.com") @@ -899,7 +898,7 @@ public void matchingVirtualHostDoesNotContainRouteAction() { Any.pack( buildRouteConfiguration("route-foo.googleapis.com", ImmutableList.of(virtualHost)))); - response = buildDiscoveryResponse("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); + response = buildDiscoveryResponse("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS, "0000"); responseObserver.onNext(response); // Client sent an NACK RDS request. @@ -934,7 +933,7 @@ public void notifyUpdatedResources() { // Management server sends back an LDS response containing a RouteConfiguration for the // requested Listener directly in-line. - io.envoyproxy.envoy.api.v2.RouteConfiguration routeConfig = + RouteConfiguration routeConfig = buildRouteConfiguration( "route-foo.googleapis.com", // target route configuration ImmutableList.of( @@ -949,7 +948,7 @@ public void notifyUpdatedResources() { Any.pack(HttpConnectionManager.newBuilder().setRouteConfig(routeConfig).build()))) ); DiscoveryResponse response = - buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS, "0000"); responseObserver.onNext(response); // Client sends an ACK LDS request. @@ -978,7 +977,7 @@ public void notifyUpdatedResources() { Any.pack(HttpConnectionManager.newBuilder().setRouteConfig(routeConfig).build()))) ); response = - buildDiscoveryResponse("1", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0001"); + buildDiscoveryResponse("1", listeners, XdsClientImpl.ADS_TYPE_URL_LDS, "0001"); responseObserver.onNext(response); // Client sends an ACK LDS request. @@ -1007,7 +1006,7 @@ public void notifyUpdatedResources() { Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) ); response = - buildDiscoveryResponse("2", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0002"); + buildDiscoveryResponse("2", listeners, XdsClientImpl.ADS_TYPE_URL_LDS, "0002"); responseObserver.onNext(response); // Client sends an ACK LDS request. @@ -1031,7 +1030,7 @@ public void notifyUpdatedResources() { "some cluster"), buildVirtualHost(ImmutableList.of(TARGET_AUTHORITY, "bar.googleapis.com:443"), "some-other-cluster.googleapis.com"))))); - response = buildDiscoveryResponse("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); + response = buildDiscoveryResponse("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS, "0000"); responseObserver.onNext(response); // Client sent an ACK RDS request. @@ -1054,7 +1053,7 @@ public void notifyUpdatedResources() { ImmutableList.of( buildVirtualHost(ImmutableList.of(TARGET_AUTHORITY, "bar.googleapis.com:443"), "an-updated-cluster.googleapis.com"))))); - response = buildDiscoveryResponse("1", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0001"); + response = buildDiscoveryResponse("1", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS, "0001"); responseObserver.onNext(response); // Client sent an ACK RDS request. @@ -1071,7 +1070,7 @@ public void notifyUpdatedResources() { // Management server sends back an LDS response indicating all Listener resources are removed. response = buildDiscoveryResponse("3", ImmutableList.of(), - XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0003"); + XdsClientImpl.ADS_TYPE_URL_LDS, "0003"); responseObserver.onNext(response); verify(configWatcher).onResourceDoesNotExist(TARGET_AUTHORITY); @@ -1112,7 +1111,7 @@ public void waitRdsResponsesForRequestedResource() { Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) ); DiscoveryResponse response = - buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS, "0000"); responseObserver.onNext(response); // Client sends an ACK LDS request. @@ -1142,7 +1141,7 @@ public void waitRdsResponsesForRequestedResource() { buildVirtualHost( ImmutableList.of(TARGET_AUTHORITY), "some more cluster"))))); - response = buildDiscoveryResponse("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); + response = buildDiscoveryResponse("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS, "0000"); responseObserver.onNext(response); // Client sent an ACK RDS request. @@ -1169,7 +1168,7 @@ public void waitRdsResponsesForRequestedResource() { buildVirtualHost( // matching virtual host ImmutableList.of(TARGET_AUTHORITY, "bar.googleapis.com:443"), "another-cluster.googleapis.com"))))); - response = buildDiscoveryResponse("1", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0001"); + response = buildDiscoveryResponse("1", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS, "0001"); responseObserver.onNext(response); // Client sent an ACK RDS request. @@ -1214,7 +1213,7 @@ public void routeConfigurationRemovedNotifiedToWatcher() { Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) ); DiscoveryResponse response = - buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS, "0000"); responseObserver.onNext(response); // Client sends an ACK LDS request. @@ -1236,7 +1235,7 @@ public void routeConfigurationRemovedNotifiedToWatcher() { buildVirtualHost( ImmutableList.of(TARGET_AUTHORITY), // matching virtual host "cluster.googleapis.com"))))); - response = buildDiscoveryResponse("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); + response = buildDiscoveryResponse("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS, "0000"); responseObserver.onNext(response); // Client sent an ACK RDS request. @@ -1254,7 +1253,7 @@ public void routeConfigurationRemovedNotifiedToWatcher() { // in-use by client) removed as the RouteConfiguration it references to is absent. response = buildDiscoveryResponse("1", ImmutableList.of(), // empty - XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0001"); + XdsClientImpl.ADS_TYPE_URL_LDS, "0001"); responseObserver.onNext(response); // Client sent an ACK LDS request. @@ -1289,7 +1288,7 @@ public void updateRdsRequestResourceWhileInitialResourceFetchInProgress() { Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) ); DiscoveryResponse response = - buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS, "0000"); responseObserver.onNext(response); // Client sends an (first) RDS request. @@ -1318,7 +1317,7 @@ public void updateRdsRequestResourceWhileInitialResourceFetchInProgress() { TARGET_AUTHORITY, /* matching resource */ Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) ); - response = buildDiscoveryResponse("1", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0001"); + response = buildDiscoveryResponse("1", listeners, XdsClientImpl.ADS_TYPE_URL_LDS, "0001"); responseObserver.onNext(response); // Client sent a new RDS request with updated resource name. @@ -1341,7 +1340,7 @@ public void updateRdsRequestResourceWhileInitialResourceFetchInProgress() { buildVirtualHost( ImmutableList.of(TARGET_AUTHORITY), // matching virtual host "cluster.googleapis.com"))))); - response = buildDiscoveryResponse("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); + response = buildDiscoveryResponse("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS, "0000"); responseObserver.onNext(response); assertThat(rdsRespTimer.isCancelled()).isTrue(); @@ -1369,7 +1368,7 @@ public void cdsResponseWithoutMatchingResource() { Any.pack(buildCluster("cluster-bar.googleapis.com", null, false)), Any.pack(buildCluster("cluster-baz.googleapis.com", null, false))); DiscoveryResponse response = - buildDiscoveryResponse("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); + buildDiscoveryResponse("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS, "0000"); responseObserver.onNext(response); // Client sent an ACK CDS request. @@ -1409,7 +1408,7 @@ public void cdsResponseWithMatchingResource() { Any.pack(buildCluster("cluster-foo.googleapis.com", null, false)), Any.pack(buildCluster("cluster-baz.googleapis.com", null, false))); DiscoveryResponse response = - buildDiscoveryResponse("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); + buildDiscoveryResponse("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS, "0000"); responseObserver.onNext(response); // Client sent an ACK CDS request. @@ -1433,7 +1432,7 @@ public void cdsResponseWithMatchingResource() { buildCluster("cluster-foo.googleapis.com", "eds-cluster-foo.googleapis.com", true)), Any.pack(buildCluster("cluster-baz.googleapis.com", null, false))); response = - buildDiscoveryResponse("1", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0001"); + buildDiscoveryResponse("1", clusters, XdsClientImpl.ADS_TYPE_URL_CDS, "0001"); responseObserver.onNext(response); // Client sent an ACK CDS request. @@ -1468,7 +1467,7 @@ public void cdsResponseWithUpstreamTlsContext() { "eds-cluster-foo.googleapis.com", true, testUpstreamTlsContext)), Any.pack(buildCluster("cluster-baz.googleapis.com", null, false))); DiscoveryResponse response = - buildDiscoveryResponse("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); + buildDiscoveryResponse("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS, "0000"); responseObserver.onNext(response); // Client sent an ACK CDS request. @@ -1520,7 +1519,7 @@ public void multipleClusterWatchers() { List clusters = ImmutableList.of( Any.pack(buildCluster("cluster-foo.googleapis.com", null, false))); DiscoveryResponse response = - buildDiscoveryResponse("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); + buildDiscoveryResponse("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS, "0000"); responseObserver.onNext(response); assertThat(fakeClock.getPendingTasks(CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); @@ -1568,7 +1567,7 @@ public void multipleClusterWatchers() { buildCluster("cluster-bar.googleapis.com", "eds-cluster-bar.googleapis.com", true))); response = buildDiscoveryResponse("1", clusters, - XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0001"); + XdsClientImpl.ADS_TYPE_URL_CDS, "0001"); responseObserver.onNext(response); // Client sent an ACK CDS request. @@ -1615,7 +1614,7 @@ public void watchClusterAlreadyBeingWatched() { List clusters = ImmutableList.of( Any.pack(buildCluster("cluster-foo.googleapis.com", null, false))); DiscoveryResponse response = - buildDiscoveryResponse("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); + buildDiscoveryResponse("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS, "0000"); responseObserver.onNext(response); // Client sent an ACK CDS request. @@ -1672,7 +1671,7 @@ public void addRemoveClusterWatchers() { List clusters = ImmutableList.of( Any.pack(buildCluster("cluster-foo.googleapis.com", null, false))); DiscoveryResponse response = - buildDiscoveryResponse("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); + buildDiscoveryResponse("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS, "0000"); responseObserver.onNext(response); // Client sent an ACK CDS request. @@ -1707,7 +1706,7 @@ public void addRemoveClusterWatchers() { buildCluster("cluster-bar.googleapis.com", "eds-cluster-bar.googleapis.com", true))); response = buildDiscoveryResponse("1", clusters, - XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0001"); + XdsClientImpl.ADS_TYPE_URL_CDS, "0001"); responseObserver.onNext(response); // Client sent an ACK CDS request for all interested resources. @@ -1754,7 +1753,7 @@ public void addRemoveClusterWatchers() { Any.pack( buildCluster("cluster-bar.googleapis.com", null, false))); response = - buildDiscoveryResponse("2", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0002"); + buildDiscoveryResponse("2", clusters, XdsClientImpl.ADS_TYPE_URL_CDS, "0002"); responseObserver.onNext(response); verify(requestObserver) @@ -1783,7 +1782,7 @@ public void addRemoveClusterWatchers() { Any.pack( buildCluster("cluster-bar.googleapis.com", null, false))); response = - buildDiscoveryResponse("3", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0003"); + buildDiscoveryResponse("3", clusters, XdsClientImpl.ADS_TYPE_URL_CDS, "0003"); responseObserver.onNext(response); // Notified with cached data immediately. @@ -1883,7 +1882,7 @@ public void cdsUpdateForClusterBeingRemoved() { List clusters = ImmutableList.of( Any.pack(buildCluster("cluster-foo.googleapis.com", null, true))); DiscoveryResponse response = - buildDiscoveryResponse("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); + buildDiscoveryResponse("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS, "0000"); responseObserver.onNext(response); // Client sent an ACK CDS request (Omitted). @@ -1900,7 +1899,7 @@ public void cdsUpdateForClusterBeingRemoved() { // No cluster is available. response = buildDiscoveryResponse("1", ImmutableList.of(), - XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0001"); + XdsClientImpl.ADS_TYPE_URL_CDS, "0001"); responseObserver.onNext(response); verify(clusterWatcher).onResourceDoesNotExist("cluster-foo.googleapis.com"); @@ -1945,7 +1944,7 @@ public void edsResponseWithoutMatchingResource() { DiscoveryResponse response = buildDiscoveryResponse("0", clusterLoadAssignments, - XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0000"); + XdsClientImpl.ADS_TYPE_URL_EDS, "0000"); responseObserver.onNext(response); // Client sent an ACK EDS request. @@ -1990,7 +1989,7 @@ public void edsResponseWithMatchingResource() { buildLbEndpoint("192.168.0.1", 8080, HealthStatus.HEALTHY, 2)), 1, 0), buildLocalityLbEndpoints("region3", "zone3", "subzone3", - ImmutableList.of(), + ImmutableList.of(), 2, 1), /* locality with 0 endpoint */ buildLocalityLbEndpoints("region4", "zone4", "subzone4", ImmutableList.of( @@ -2009,7 +2008,7 @@ public void edsResponseWithMatchingResource() { DiscoveryResponse response = buildDiscoveryResponse("0", clusterLoadAssignments, - XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0000"); + XdsClientImpl.ADS_TYPE_URL_EDS, "0000"); responseObserver.onNext(response); assertThat(edsRespTimeoutTask.isCancelled()).isTrue(); @@ -2040,11 +2039,11 @@ public void edsResponseWithMatchingResource() { clusterLoadAssignments = ImmutableList.of( Any.pack(buildClusterLoadAssignment("cluster-foo.googleapis.com", // 0 locality - ImmutableList.of(), + ImmutableList.of(), ImmutableList.of()))); response = buildDiscoveryResponse( - "1", clusterLoadAssignments, XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0001"); + "1", clusterLoadAssignments, XdsClientImpl.ADS_TYPE_URL_EDS, "0001"); responseObserver.onNext(response); // Client sent an ACK EDS request. @@ -2090,11 +2089,11 @@ public void multipleEndpointWatchers() { ImmutableList.of( buildLbEndpoint("192.168.0.1", 8080, HealthStatus.HEALTHY, 2)), 1, 0)), - ImmutableList.of()))); + ImmutableList.of()))); DiscoveryResponse response = buildDiscoveryResponse("0", clusterLoadAssignments, - XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0000"); + XdsClientImpl.ADS_TYPE_URL_EDS, "0000"); responseObserver.onNext(response); assertThat(fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); @@ -2146,7 +2145,7 @@ public void multipleEndpointWatchers() { ImmutableList.of()))); response = buildDiscoveryResponse("1", clusterLoadAssignments, - XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0001"); + XdsClientImpl.ADS_TYPE_URL_EDS, "0001"); responseObserver.onNext(response); // Client sent an ACK EDS request. @@ -2199,11 +2198,11 @@ public void watchEndpointsForClusterAlreadyBeingWatched() { ImmutableList.of( buildLbEndpoint("192.168.0.1", 8080, HealthStatus.HEALTHY, 2)), 1, 0)), - ImmutableList.of()))); + ImmutableList.of()))); DiscoveryResponse response = buildDiscoveryResponse("0", clusterLoadAssignments, - XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0000"); + XdsClientImpl.ADS_TYPE_URL_EDS, "0000"); responseObserver.onNext(response); assertThat(fakeClock.getPendingTasks(EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).isEmpty(); @@ -2276,11 +2275,11 @@ public void addRemoveEndpointWatchers() { buildLbEndpoint("192.168.0.1", 8080, HealthStatus.HEALTHY, 2), buildLbEndpoint("192.132.53.5", 80, HealthStatus.UNHEALTHY, 5)), 1, 0)), - ImmutableList.of()))); + ImmutableList.of()))); DiscoveryResponse response = buildDiscoveryResponse("0", clusterLoadAssignments, - XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0000"); + XdsClientImpl.ADS_TYPE_URL_EDS, "0000"); responseObserver.onNext(response); // Client sent an ACK EDS request. @@ -2322,10 +2321,10 @@ public void addRemoveEndpointWatchers() { ImmutableList.of( buildLbEndpoint("192.168.312.6", 443, HealthStatus.HEALTHY, 1)), 6, 0)), - ImmutableList.of()))); + ImmutableList.of()))); response = buildDiscoveryResponse("1", clusterLoadAssignments, - XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0001"); + XdsClientImpl.ADS_TYPE_URL_EDS, "0001"); responseObserver.onNext(response); // Client sent an ACK EDS request for all interested resources. @@ -2381,17 +2380,17 @@ public void addRemoveEndpointWatchers() { ImmutableList.of( buildLbEndpoint("192.168.432.6", 80, HealthStatus.HEALTHY, 2)), 3, 0)), - ImmutableList.of())), + ImmutableList.of())), Any.pack(buildClusterLoadAssignment("cluster-bar.googleapis.com", ImmutableList.of( buildLocalityLbEndpoints("region4", "zone4", "subzone4", ImmutableList.of( buildLbEndpoint("192.168.75.6", 8888, HealthStatus.HEALTHY, 2)), 3, 0)), - ImmutableList.of()))); + ImmutableList.of()))); response = buildDiscoveryResponse("2", clusterLoadAssignments, - XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0002"); + XdsClientImpl.ADS_TYPE_URL_EDS, "0002"); responseObserver.onNext(response); // Client sent an ACK EDS request. @@ -2427,10 +2426,10 @@ public void addRemoveEndpointWatchers() { ImmutableList.of( buildLbEndpoint("192.168.75.6", 8888, HealthStatus.HEALTHY, 2)), 3, 0)), - ImmutableList.of()))); + ImmutableList.of()))); response = buildDiscoveryResponse("3", clusterLoadAssignments, - XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0003"); + XdsClientImpl.ADS_TYPE_URL_EDS, "0003"); responseObserver.onNext(response); ArgumentCaptor endpointUpdateCaptor3 = ArgumentCaptor.forClass(null); @@ -2528,7 +2527,7 @@ public void cdsUpdateForEdsServiceNameChange() { List clusters = ImmutableList.of( Any.pack(buildCluster("cluster-foo.googleapis.com", "cluster-foo:service-bar", false))); DiscoveryResponse response = - buildDiscoveryResponse("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); + buildDiscoveryResponse("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS, "0000"); responseObserver.onNext(response); xdsClient.watchEndpointData("cluster-foo:service-bar", endpointWatcher); @@ -2541,10 +2540,10 @@ public void cdsUpdateForEdsServiceNameChange() { ImmutableList.of( buildLbEndpoint("192.168.0.1", 8080, HealthStatus.HEALTHY, 2)), 1, 0)), - ImmutableList.of()))); + ImmutableList.of()))); response = buildDiscoveryResponse("0", clusterLoadAssignments, - XdsClientImpl.ADS_TYPE_URL_EDS_V2, "0000"); + XdsClientImpl.ADS_TYPE_URL_EDS, "0000"); responseObserver.onNext(response); ArgumentCaptor endpointUpdateCaptor = ArgumentCaptor.forClass(null); @@ -2565,7 +2564,7 @@ public void cdsUpdateForEdsServiceNameChange() { clusters = ImmutableList.of( Any.pack(buildCluster("cluster-foo.googleapis.com", "cluster-foo:service-blade", false))); response = - buildDiscoveryResponse("1", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0001"); + buildDiscoveryResponse("1", clusters, XdsClientImpl.ADS_TYPE_URL_CDS, "0001"); responseObserver.onNext(response); // Watcher get notification for endpoint resource "cluster-foo:service-bar" being deleted. @@ -2649,7 +2648,7 @@ public void streamClosedAndRetryWhenResolvingConfig() { Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) ); DiscoveryResponse ldsResponse = - buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS, "0000"); responseObserver.onNext(ldsResponse); // Client sent back an ACK LDS request. @@ -2707,7 +2706,7 @@ public void streamClosedAndRetryWhenResolvingConfig() { ImmutableList.of(TARGET_AUTHORITY), // matching virtual host "cluster.googleapis.com"))))); DiscoveryResponse rdsResponse = - buildDiscoveryResponse("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); + buildDiscoveryResponse("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS, "0000"); // Management server sends an RDS response. responseObserver.onNext(rdsResponse); @@ -2858,7 +2857,7 @@ public void streamClosedAndRetry() { List clusters = ImmutableList.of( Any.pack(buildCluster("cluster.googleapis.com", null, false))); DiscoveryResponse cdsResponse = - buildDiscoveryResponse("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); + buildDiscoveryResponse("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS, "0000"); responseObserver.onNext(cdsResponse); // Client sent an CDS ACK request (Omitted). @@ -3009,7 +3008,7 @@ public void streamClosedAndRetryRaceWithAddingAndRemovingWatchers() { List clusters = ImmutableList.of( Any.pack(buildCluster("cluster.googleapis.com", null, false))); DiscoveryResponse cdsResponse = - buildDiscoveryResponse("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); + buildDiscoveryResponse("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS, "0000"); responseObserver.onNext(cdsResponse); // Client sent an CDS ACK request (Omitted). @@ -3107,7 +3106,7 @@ public void streamClosedAndRetryReschedulesAllResourceFetchTimer() { Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) ); DiscoveryResponse response = - buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS, "0000"); responseObserver.onNext(response); // Client sent an RDS request for resource "route-foo.googleapis.com" (Omitted). @@ -3161,7 +3160,7 @@ public void streamClosedAndRetryReschedulesAllResourceFetchTimer() { buildVirtualHost( ImmutableList.of(TARGET_AUTHORITY), // matching virtual host "cluster-foo.googleapis.com"))))); - response = buildDiscoveryResponse("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); + response = buildDiscoveryResponse("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS, "0000"); responseObserver.onNext(response); assertThat(rdsRespTimer.isCancelled()).isTrue(); @@ -3281,7 +3280,7 @@ private void waitUntilConfigResolved(StreamObserver responseO Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) ); DiscoveryResponse ldsResponse = - buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS, "0000"); responseObserver.onNext(ldsResponse); // Client sent an LDS ACK request and an RDS request for resource @@ -3297,7 +3296,7 @@ private void waitUntilConfigResolved(StreamObserver responseO ImmutableList.of(TARGET_AUTHORITY), // matching virtual host "cluster.googleapis.com"))))); DiscoveryResponse rdsResponse = - buildDiscoveryResponse("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); + buildDiscoveryResponse("0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS, "0000"); responseObserver.onNext(rdsResponse); } @@ -3475,12 +3474,12 @@ public void messagePrinter_printLdsResponse() { "cluster.googleapis.com")))) .build())))); DiscoveryResponse response = - buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); + buildDiscoveryResponse("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS, "0000"); String expectedString = "{\n" + " \"versionInfo\": \"0\",\n" + " \"resources\": [{\n" - + " \"@type\": \"type.googleapis.com/envoy.api.v2.Listener\",\n" + + " \"@type\": \"type.googleapis.com/envoy.config.listener.v3.Listener\",\n" + " \"name\": \"foo.googleapis.com:8080\",\n" + " \"address\": {\n" + " },\n" @@ -3488,8 +3487,8 @@ public void messagePrinter_printLdsResponse() { + " }],\n" + " \"apiListener\": {\n" + " \"apiListener\": {\n" - + " \"@type\": \"type.googleapis.com/envoy.config.filter.network" - + ".http_connection_manager.v2.HttpConnectionManager\",\n" + + " \"@type\": \"type.googleapis.com/envoy.extensions.filters.network" + + ".http_connection_manager.v3.HttpConnectionManager\",\n" + " \"routeConfig\": {\n" + " \"name\": \"route-foo.googleapis.com\",\n" + " \"virtualHosts\": [{\n" @@ -3508,7 +3507,7 @@ public void messagePrinter_printLdsResponse() { + " }\n" + " }\n" + " }],\n" - + " \"typeUrl\": \"type.googleapis.com/envoy.api.v2.Listener\",\n" + + " \"typeUrl\": \"type.googleapis.com/envoy.config.listener.v3.Listener\",\n" + " \"nonce\": \"0000\"\n" + "}"; String res = printer.print(response); @@ -3528,12 +3527,12 @@ public void messagePrinter_printRdsResponse() { ImmutableList.of("foo.googleapis.com", "bar.googleapis.com"), "cluster.googleapis.com"))))); DiscoveryResponse response = - buildDiscoveryResponse("213", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0052"); + buildDiscoveryResponse("213", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS, "0052"); String expectedString = "{\n" + " \"versionInfo\": \"213\",\n" + " \"resources\": [{\n" - + " \"@type\": \"type.googleapis.com/envoy.api.v2.RouteConfiguration\",\n" + + " \"@type\": \"type.googleapis.com/envoy.config.route.v3.RouteConfiguration\",\n" + " \"name\": \"route-foo.googleapis.com\",\n" + " \"virtualHosts\": [{\n" + " \"name\": \"virtualhost00.googleapis.com\",\n" @@ -3548,7 +3547,7 @@ public void messagePrinter_printRdsResponse() { + " }]\n" + " }]\n" + " }],\n" - + " \"typeUrl\": \"type.googleapis.com/envoy.api.v2.RouteConfiguration\",\n" + + " \"typeUrl\": \"type.googleapis.com/envoy.config.route.v3.RouteConfiguration\",\n" + " \"nonce\": \"0052\"\n" + "}"; String res = printer.print(response); @@ -3562,12 +3561,12 @@ public void messagePrinter_printCdsResponse() { Any.pack(buildCluster("cluster-bar.googleapis.com", "service-blaze:cluster-bar", true)), Any.pack(buildCluster("cluster-foo.googleapis.com", null, false))); DiscoveryResponse response = - buildDiscoveryResponse("14", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "8"); + buildDiscoveryResponse("14", clusters, XdsClientImpl.ADS_TYPE_URL_CDS, "8"); String expectedString = "{\n" + " \"versionInfo\": \"14\",\n" + " \"resources\": [{\n" - + " \"@type\": \"type.googleapis.com/envoy.api.v2.Cluster\",\n" + + " \"@type\": \"type.googleapis.com/envoy.config.cluster.v3.Cluster\",\n" + " \"name\": \"cluster-bar.googleapis.com\",\n" + " \"type\": \"EDS\",\n" + " \"edsClusterConfig\": {\n" @@ -3582,7 +3581,7 @@ public void messagePrinter_printCdsResponse() { + " }\n" + " }\n" + " }, {\n" - + " \"@type\": \"type.googleapis.com/envoy.api.v2.Cluster\",\n" + + " \"@type\": \"type.googleapis.com/envoy.config.cluster.v3.Cluster\",\n" + " \"name\": \"cluster-foo.googleapis.com\",\n" + " \"type\": \"EDS\",\n" + " \"edsClusterConfig\": {\n" @@ -3592,7 +3591,7 @@ public void messagePrinter_printCdsResponse() { + " }\n" + " }\n" + " }],\n" - + " \"typeUrl\": \"type.googleapis.com/envoy.api.v2.Cluster\",\n" + + " \"typeUrl\": \"type.googleapis.com/envoy.config.cluster.v3.Cluster\",\n" + " \"nonce\": \"8\"\n" + "}"; String res = printer.print(response); @@ -3619,12 +3618,12 @@ public void messagePrinter_printEdsResponse() { DiscoveryResponse response = buildDiscoveryResponse("5", clusterLoadAssignments, - XdsClientImpl.ADS_TYPE_URL_EDS_V2, "004"); + XdsClientImpl.ADS_TYPE_URL_EDS, "004"); String expectedString = "{\n" + " \"versionInfo\": \"5\",\n" + " \"resources\": [{\n" - + " \"@type\": \"type.googleapis.com/envoy.api.v2.ClusterLoadAssignment\",\n" + + " \"@type\": \"type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment\",\n" + " \"clusterName\": \"cluster-foo.googleapis.com\",\n" + " \"endpoints\": [{\n" + " \"locality\": {\n" @@ -3679,11 +3678,10 @@ public void messagePrinter_printEdsResponse() { + " \"numerator\": 1000,\n" + " \"denominator\": \"MILLION\"\n" + " }\n" - + " }],\n" - + " \"disableOverprovisioning\": true\n" + + " }]\n" + " }\n" + " }],\n" - + " \"typeUrl\": \"type.googleapis.com/envoy.api.v2.ClusterLoadAssignment\",\n" + + " \"typeUrl\": \"type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment\",\n" + " \"nonce\": \"004\"\n" + "}"; String res = printer.print(response); diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTestForListener.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTestForListener.java index 6d1aeedaeb8..ba729f04816 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTestForListener.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTestForListener.java @@ -18,9 +18,9 @@ import static com.google.common.truth.Truth.assertThat; import static io.grpc.xds.XdsClientTestHelper.buildDiscoveryResponseV2; -import static io.grpc.xds.XdsClientTestHelper.buildListener; -import static io.grpc.xds.XdsClientTestHelper.buildRouteConfiguration; -import static io.grpc.xds.XdsClientTestHelper.buildVirtualHost; +import static io.grpc.xds.XdsClientTestHelper.buildListenerV2; +import static io.grpc.xds.XdsClientTestHelper.buildRouteConfigurationV2; +import static io.grpc.xds.XdsClientTestHelper.buildVirtualHostV2; import static org.junit.Assert.fail; import static org.mockito.AdditionalAnswers.delegatesTo; import static org.mockito.ArgumentMatchers.any; @@ -311,21 +311,21 @@ public void ldsResponse_nonMatchingFilterChain_notFoundError() { assertThat(fakeClock.getPendingTasks(LISTENER_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); List listeners = ImmutableList.of( - Any.pack(buildListener("bar.googleapis.com", + Any.pack(buildListenerV2("bar.googleapis.com", Any.pack(HttpConnectionManager.newBuilder() .setRouteConfig( - buildRouteConfiguration("route-bar.googleapis.com", + buildRouteConfigurationV2("route-bar.googleapis.com", ImmutableList.of( - buildVirtualHost( + buildVirtualHostV2( ImmutableList.of("bar.googleapis.com"), "cluster-bar.googleapis.com")))) .build()))), - Any.pack(buildListener(LISTENER_NAME, + Any.pack(buildListenerV2(LISTENER_NAME, Any.pack(HttpConnectionManager.newBuilder() .setRouteConfig( - buildRouteConfiguration("route-baz.googleapis.com", + buildRouteConfigurationV2("route-baz.googleapis.com", ImmutableList.of( - buildVirtualHost( + buildVirtualHostV2( ImmutableList.of("baz.googleapis.com"), "cluster-baz.googleapis.com")))) .build())))); @@ -368,12 +368,12 @@ public void ldsResponseWith_listenerAddressPortMismatch() { "ROOTCA"), buildTestFilter("envoy.http_connection_manager")); List listeners = ImmutableList.of( - Any.pack(buildListener("bar.googleapis.com", + Any.pack(buildListenerV2("bar.googleapis.com", Any.pack(HttpConnectionManager.newBuilder() .setRouteConfig( - buildRouteConfiguration("route-bar.googleapis.com", + buildRouteConfigurationV2("route-bar.googleapis.com", ImmutableList.of( - buildVirtualHost( + buildVirtualHostV2( ImmutableList.of("bar.googleapis.com"), "cluster-bar.googleapis.com")))) .build()))), @@ -420,12 +420,12 @@ public void ldsResponseWith_matchingListenerFound() throws InvalidProtocolBuffer "ROOTCA"), buildTestFilter("envoy.http_connection_manager")); List listeners = ImmutableList.of( - Any.pack(buildListener("bar.googleapis.com", + Any.pack(buildListenerV2("bar.googleapis.com", Any.pack(HttpConnectionManager.newBuilder() .setRouteConfig( - buildRouteConfiguration("route-bar.googleapis.com", + buildRouteConfigurationV2("route-bar.googleapis.com", ImmutableList.of( - buildVirtualHost( + buildVirtualHostV2( ImmutableList.of("bar.googleapis.com"), "cluster-bar.googleapis.com")))) .build()))), @@ -495,12 +495,12 @@ public void notifyUpdatedListener() throws InvalidProtocolBufferException { "ROOTCA"), buildTestFilter("envoy.http_connection_manager")); List listeners = ImmutableList.of( - Any.pack(buildListener("bar.googleapis.com", + Any.pack(buildListenerV2("bar.googleapis.com", Any.pack(HttpConnectionManager.newBuilder() .setRouteConfig( - buildRouteConfiguration("route-bar.googleapis.com", + buildRouteConfigurationV2("route-bar.googleapis.com", ImmutableList.of( - buildVirtualHost( + buildVirtualHostV2( ImmutableList.of("bar.googleapis.com"), "cluster-bar.googleapis.com")))) .build()))), @@ -591,12 +591,12 @@ public void ldsResponse_nonMatchingIpAddress() { "ROOTCA"), buildTestFilter("envoy.http_connection_manager")); List listeners = ImmutableList.of( - Any.pack(buildListener("bar.googleapis.com", + Any.pack(buildListenerV2("bar.googleapis.com", Any.pack(HttpConnectionManager.newBuilder() .setRouteConfig( - buildRouteConfiguration("route-bar.googleapis.com", + buildRouteConfigurationV2("route-bar.googleapis.com", ImmutableList.of( - buildVirtualHost( + buildVirtualHostV2( ImmutableList.of("bar.googleapis.com"), "cluster-bar.googleapis.com")))) .build()))), @@ -640,12 +640,12 @@ public void ldsResponse_nonMatchingPort() { "ROOTCA"), buildTestFilter("envoy.http_connection_manager")); List listeners = ImmutableList.of( - Any.pack(buildListener("bar.googleapis.com", + Any.pack(buildListenerV2("bar.googleapis.com", Any.pack(HttpConnectionManager.newBuilder() .setRouteConfig( - buildRouteConfiguration("route-bar.googleapis.com", + buildRouteConfigurationV2("route-bar.googleapis.com", ImmutableList.of( - buildVirtualHost( + buildVirtualHostV2( ImmutableList.of("bar.googleapis.com"), "cluster-bar.googleapis.com")))) .build()))), diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java index 9a9171cfac7..64da95d25b7 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTestV2.java @@ -17,18 +17,18 @@ package io.grpc.xds; import static com.google.common.truth.Truth.assertThat; -import static io.grpc.xds.XdsClientTestHelper.buildCluster; -import static io.grpc.xds.XdsClientTestHelper.buildClusterLoadAssignment; +import static io.grpc.xds.XdsClientTestHelper.buildClusterLoadAssignmentV2; +import static io.grpc.xds.XdsClientTestHelper.buildClusterV2; import static io.grpc.xds.XdsClientTestHelper.buildDiscoveryRequestV2; import static io.grpc.xds.XdsClientTestHelper.buildDiscoveryResponseV2; -import static io.grpc.xds.XdsClientTestHelper.buildDropOverload; -import static io.grpc.xds.XdsClientTestHelper.buildLbEndpoint; -import static io.grpc.xds.XdsClientTestHelper.buildListener; -import static io.grpc.xds.XdsClientTestHelper.buildLocalityLbEndpoints; -import static io.grpc.xds.XdsClientTestHelper.buildRouteConfiguration; -import static io.grpc.xds.XdsClientTestHelper.buildSecureCluster; -import static io.grpc.xds.XdsClientTestHelper.buildUpstreamTlsContext; -import static io.grpc.xds.XdsClientTestHelper.buildVirtualHost; +import static io.grpc.xds.XdsClientTestHelper.buildDropOverloadV2; +import static io.grpc.xds.XdsClientTestHelper.buildLbEndpointV2; +import static io.grpc.xds.XdsClientTestHelper.buildListenerV2; +import static io.grpc.xds.XdsClientTestHelper.buildLocalityLbEndpointsV2; +import static io.grpc.xds.XdsClientTestHelper.buildRouteConfigurationV2; +import static io.grpc.xds.XdsClientTestHelper.buildSecureClusterV2; +import static io.grpc.xds.XdsClientTestHelper.buildUpstreamTlsContextV2; +import static io.grpc.xds.XdsClientTestHelper.buildVirtualHostV2; import static org.mockito.AdditionalAnswers.delegatesTo; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; @@ -341,21 +341,21 @@ public void ldsResponseWithoutMatchingResource() { assertThat(fakeClock.getPendingTasks(LDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); List listeners = ImmutableList.of( - Any.pack(buildListener("bar.googleapis.com", + Any.pack(buildListenerV2("bar.googleapis.com", Any.pack(HttpConnectionManager.newBuilder() .setRouteConfig( - buildRouteConfiguration("route-bar.googleapis.com", + buildRouteConfigurationV2("route-bar.googleapis.com", ImmutableList.of( - buildVirtualHost( + buildVirtualHostV2( ImmutableList.of("bar.googleapis.com"), "cluster-bar.googleapis.com")))) .build()))), - Any.pack(buildListener("baz.googleapis.com", + Any.pack(buildListenerV2("baz.googleapis.com", Any.pack(HttpConnectionManager.newBuilder() .setRouteConfig( - buildRouteConfiguration("route-baz.googleapis.com", + buildRouteConfigurationV2("route-baz.googleapis.com", ImmutableList.of( - buildVirtualHost( + buildVirtualHostV2( ImmutableList.of("baz.googleapis.com"), "cluster-baz.googleapis.com")))) .build())))); @@ -396,16 +396,16 @@ public void failToFindVirtualHostInLdsResponseInLineRouteConfig() { assertThat(fakeClock.getPendingTasks(LDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)).hasSize(1); io.envoyproxy.envoy.api.v2.RouteConfiguration routeConfig = - buildRouteConfiguration( + buildRouteConfigurationV2( "route.googleapis.com", ImmutableList.of( - buildVirtualHost(ImmutableList.of("something does not match"), + buildVirtualHostV2(ImmutableList.of("something does not match"), "some cluster"), - buildVirtualHost(ImmutableList.of("something else does not match"), + buildVirtualHostV2(ImmutableList.of("something else does not match"), "some other cluster"))); List listeners = ImmutableList.of( - Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(buildListenerV2(TARGET_AUTHORITY, /* matching resource */ Any.pack(HttpConnectionManager.newBuilder().setRouteConfig(routeConfig).build())))); DiscoveryResponse response = buildDiscoveryResponseV2("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000"); @@ -448,34 +448,34 @@ public void resolveVirtualHostInLdsResponse() { assertThat(ldsRespTimer.isCancelled()).isFalse(); List listeners = ImmutableList.of( - Any.pack(buildListener("bar.googleapis.com", + Any.pack(buildListenerV2("bar.googleapis.com", Any.pack(HttpConnectionManager.newBuilder() .setRouteConfig( - buildRouteConfiguration("route-bar.googleapis.com", + buildRouteConfigurationV2("route-bar.googleapis.com", ImmutableList.of( - buildVirtualHost( + buildVirtualHostV2( ImmutableList.of("bar.googleapis.com"), "cluster-bar.googleapis.com")))) .build()))), - Any.pack(buildListener("baz.googleapis.com", + Any.pack(buildListenerV2("baz.googleapis.com", Any.pack(HttpConnectionManager.newBuilder() .setRouteConfig( - buildRouteConfiguration("route-baz.googleapis.com", + buildRouteConfigurationV2("route-baz.googleapis.com", ImmutableList.of( - buildVirtualHost( + buildVirtualHostV2( ImmutableList.of("baz.googleapis.com"), "cluster-baz.googleapis.com")))) .build()))), - Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(buildListenerV2(TARGET_AUTHORITY, /* matching resource */ Any.pack( HttpConnectionManager.newBuilder() .setRouteConfig( // target route configuration - buildRouteConfiguration("route-foo.googleapis.com", + buildRouteConfigurationV2("route-foo.googleapis.com", ImmutableList.of( - buildVirtualHost( // matching virtual host + buildVirtualHostV2( // matching virtual host ImmutableList.of(TARGET_AUTHORITY, "bar.googleapis.com"), "cluster.googleapis.com"), - buildVirtualHost( + buildVirtualHostV2( ImmutableList.of("something does not match"), "some cluster")))) .build())))); @@ -524,7 +524,7 @@ public void rdsResponseWithoutMatchingResource() { .setRouteConfigName("route-foo.googleapis.com") .build(); List listeners = ImmutableList.of( - Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(buildListenerV2(TARGET_AUTHORITY, /* matching resource */ Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) ); DiscoveryResponse response = @@ -547,17 +547,17 @@ public void rdsResponseWithoutMatchingResource() { // VirtualHost with domains matching requested hostname. Otherwise, it is invalid data. List routeConfigs = ImmutableList.of( Any.pack( - buildRouteConfiguration( + buildRouteConfigurationV2( "some resource name does not match route-foo.googleapis.com", ImmutableList.of( - buildVirtualHost( + buildVirtualHostV2( ImmutableList.of(TARGET_AUTHORITY), "whatever cluster")))), Any.pack( - buildRouteConfiguration( + buildRouteConfigurationV2( "some other resource name does not match route-foo.googleapis.com", ImmutableList.of( - buildVirtualHost( + buildVirtualHostV2( ImmutableList.of(TARGET_AUTHORITY), "some more whatever cluster"))))); response = buildDiscoveryResponseV2( @@ -597,7 +597,7 @@ public void resolveVirtualHostInRdsResponse() { .build(); List listeners = ImmutableList.of( - Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(buildListenerV2(TARGET_AUTHORITY, /* matching resource */ Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) ); DiscoveryResponse response = @@ -612,18 +612,18 @@ public void resolveVirtualHostInRdsResponse() { // VirtualHost with domains matching requested hostname. Otherwise, it is invalid data. List routeConfigs = ImmutableList.of( Any.pack( - buildRouteConfiguration( + buildRouteConfigurationV2( "route-foo.googleapis.com", // target route configuration ImmutableList.of( - buildVirtualHost(ImmutableList.of("something does not match"), + buildVirtualHostV2(ImmutableList.of("something does not match"), "some cluster"), - buildVirtualHost(ImmutableList.of(TARGET_AUTHORITY, "bar.googleapis.com:443"), + buildVirtualHostV2(ImmutableList.of(TARGET_AUTHORITY, "bar.googleapis.com:443"), "cluster.googleapis.com")))), // matching virtual host Any.pack( - buildRouteConfiguration( + buildRouteConfigurationV2( "some resource name does not match route-foo.googleapis.com", ImmutableList.of( - buildVirtualHost(ImmutableList.of("foo.googleapis.com"), + buildVirtualHostV2(ImmutableList.of("foo.googleapis.com"), "some more cluster"))))); response = buildDiscoveryResponseV2( "0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); @@ -662,7 +662,7 @@ public void resolveVirtualHostWithPathMatchingInRdsResponse() { .build(); List listeners = ImmutableList.of( - Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(buildListenerV2(TARGET_AUTHORITY, /* matching resource */ Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) ); DiscoveryResponse response = @@ -678,7 +678,7 @@ public void resolveVirtualHostWithPathMatchingInRdsResponse() { List routeConfigs = ImmutableList.of( Any.pack( - buildRouteConfiguration( + buildRouteConfigurationV2( "route-foo.googleapis.com", ImmutableList.of( io.envoyproxy.envoy.api.v2.route.VirtualHost.newBuilder() @@ -806,7 +806,7 @@ public void failToFindVirtualHostInRdsResponse() { .build(); List listeners = ImmutableList.of( - Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(buildListenerV2(TARGET_AUTHORITY, /* matching resource */ Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) ); DiscoveryResponse response = @@ -819,19 +819,19 @@ public void failToFindVirtualHostInRdsResponse() { List routeConfigs = ImmutableList.of( Any.pack( - buildRouteConfiguration( + buildRouteConfigurationV2( "route-foo.googleapis.com", ImmutableList.of( - buildVirtualHost(ImmutableList.of("something does not match"), + buildVirtualHostV2(ImmutableList.of("something does not match"), "some cluster"), - buildVirtualHost( + buildVirtualHostV2( ImmutableList.of("something else does not match", "also does not match"), "cluster.googleapis.com")))), Any.pack( - buildRouteConfiguration( + buildRouteConfigurationV2( "some resource name does not match route-foo.googleapis.com", ImmutableList.of( - buildVirtualHost(ImmutableList.of("one more does not match"), + buildVirtualHostV2(ImmutableList.of("one more does not match"), "some more cluster"))))); response = buildDiscoveryResponseV2( "0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); @@ -873,7 +873,7 @@ public void matchingVirtualHostDoesNotContainRouteAction() { .build(); List listeners = ImmutableList.of( - Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(buildListenerV2(TARGET_AUTHORITY, /* matching resource */ Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) ); DiscoveryResponse response = @@ -899,7 +899,7 @@ public void matchingVirtualHostDoesNotContainRouteAction() { List routeConfigs = ImmutableList.of( Any.pack( - buildRouteConfiguration("route-foo.googleapis.com", + buildRouteConfigurationV2("route-foo.googleapis.com", ImmutableList.of(virtualHost)))); response = buildDiscoveryResponseV2( "0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); @@ -938,17 +938,17 @@ public void notifyUpdatedResources() { // Management server sends back an LDS response containing a RouteConfiguration for the // requested Listener directly in-line. io.envoyproxy.envoy.api.v2.RouteConfiguration routeConfig = - buildRouteConfiguration( + buildRouteConfigurationV2( "route-foo.googleapis.com", // target route configuration ImmutableList.of( - buildVirtualHost( // matching virtual host + buildVirtualHostV2( // matching virtual host ImmutableList.of(TARGET_AUTHORITY, "bar.googleapis.com:443"), "cluster.googleapis.com"), - buildVirtualHost(ImmutableList.of("something does not match"), + buildVirtualHostV2(ImmutableList.of("something does not match"), "some cluster"))); List listeners = ImmutableList.of( - Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(buildListenerV2(TARGET_AUTHORITY, /* matching resource */ Any.pack(HttpConnectionManager.newBuilder().setRouteConfig(routeConfig).build()))) ); DiscoveryResponse response = @@ -968,16 +968,16 @@ public void notifyUpdatedResources() { // Management sends back another LDS response containing updates for the requested Listener. routeConfig = - buildRouteConfiguration( + buildRouteConfigurationV2( "another-route-foo.googleapis.com", ImmutableList.of( - buildVirtualHost(ImmutableList.of(TARGET_AUTHORITY, "bar.googleapis.com:443"), + buildVirtualHostV2(ImmutableList.of(TARGET_AUTHORITY, "bar.googleapis.com:443"), "another-cluster.googleapis.com"), - buildVirtualHost(ImmutableList.of("something does not match"), + buildVirtualHostV2(ImmutableList.of("something does not match"), "some cluster"))); listeners = ImmutableList.of( - Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(buildListenerV2(TARGET_AUTHORITY, /* matching resource */ Any.pack(HttpConnectionManager.newBuilder().setRouteConfig(routeConfig).build()))) ); response = @@ -1006,7 +1006,7 @@ public void notifyUpdatedResources() { .build(); listeners = ImmutableList.of( - Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(buildListenerV2(TARGET_AUTHORITY, /* matching resource */ Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) ); response = @@ -1027,12 +1027,12 @@ public void notifyUpdatedResources() { // for the requested resource. List routeConfigs = ImmutableList.of( Any.pack( - buildRouteConfiguration( + buildRouteConfigurationV2( "some-route-to-foo.googleapis.com", ImmutableList.of( - buildVirtualHost(ImmutableList.of("something does not match"), + buildVirtualHostV2(ImmutableList.of("something does not match"), "some cluster"), - buildVirtualHost(ImmutableList.of(TARGET_AUTHORITY, "bar.googleapis.com:443"), + buildVirtualHostV2(ImmutableList.of(TARGET_AUTHORITY, "bar.googleapis.com:443"), "some-other-cluster.googleapis.com"))))); response = buildDiscoveryResponseV2( "0", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0000"); @@ -1053,10 +1053,10 @@ public void notifyUpdatedResources() { // RouteConfiguration currently in-use by client. routeConfigs = ImmutableList.of( Any.pack( - buildRouteConfiguration( + buildRouteConfigurationV2( "some-route-to-foo.googleapis.com", ImmutableList.of( - buildVirtualHost(ImmutableList.of(TARGET_AUTHORITY, "bar.googleapis.com:443"), + buildVirtualHostV2(ImmutableList.of(TARGET_AUTHORITY, "bar.googleapis.com:443"), "an-updated-cluster.googleapis.com"))))); response = buildDiscoveryResponseV2( "1", routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, "0001"); @@ -1113,7 +1113,7 @@ public void waitRdsResponsesForRequestedResource() { .build(); List listeners = ImmutableList.of( - Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(buildListenerV2(TARGET_AUTHORITY, /* matching resource */ Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) ); DiscoveryResponse response = @@ -1141,10 +1141,10 @@ public void waitRdsResponsesForRequestedResource() { // for the requested resource. List routeConfigs = ImmutableList.of( Any.pack( - buildRouteConfiguration( + buildRouteConfigurationV2( "some resource name does not match route-foo.googleapis.com", ImmutableList.of( - buildVirtualHost( + buildVirtualHostV2( ImmutableList.of(TARGET_AUTHORITY), "some more cluster"))))); response = buildDiscoveryResponseV2( @@ -1166,13 +1166,13 @@ public void waitRdsResponsesForRequestedResource() { // for the requested resource. routeConfigs = ImmutableList.of( Any.pack( - buildRouteConfiguration( + buildRouteConfigurationV2( "route-foo.googleapis.com", // target route configuration ImmutableList.of( - buildVirtualHost( + buildVirtualHostV2( ImmutableList.of("something does not match"), "some cluster"), - buildVirtualHost( // matching virtual host + buildVirtualHostV2( // matching virtual host ImmutableList.of(TARGET_AUTHORITY, "bar.googleapis.com:443"), "another-cluster.googleapis.com"))))); response = buildDiscoveryResponseV2( @@ -1217,7 +1217,7 @@ public void routeConfigurationRemovedNotifiedToWatcher() { .build(); List listeners = ImmutableList.of( - Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(buildListenerV2(TARGET_AUTHORITY, /* matching resource */ Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) ); DiscoveryResponse response = @@ -1237,10 +1237,10 @@ public void routeConfigurationRemovedNotifiedToWatcher() { // Management server sends back an RDS response containing RouteConfiguration requested. List routeConfigs = ImmutableList.of( Any.pack( - buildRouteConfiguration( + buildRouteConfigurationV2( "route-foo.googleapis.com", // target route configuration ImmutableList.of( - buildVirtualHost( + buildVirtualHostV2( ImmutableList.of(TARGET_AUTHORITY), // matching virtual host "cluster.googleapis.com"))))); response = buildDiscoveryResponseV2( @@ -1293,7 +1293,7 @@ public void updateRdsRequestResourceWhileInitialResourceFetchInProgress() { .build(); List listeners = ImmutableList.of( - Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(buildListenerV2(TARGET_AUTHORITY, /* matching resource */ Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) ); DiscoveryResponse response = @@ -1322,7 +1322,7 @@ public void updateRdsRequestResourceWhileInitialResourceFetchInProgress() { listeners = ImmutableList.of( Any.pack( - buildListener( + buildListenerV2( TARGET_AUTHORITY, /* matching resource */ Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) ); @@ -1343,10 +1343,10 @@ public void updateRdsRequestResourceWhileInitialResourceFetchInProgress() { // Management server sends back an RDS response containing RouteConfiguration requested. List routeConfigs = ImmutableList.of( Any.pack( - buildRouteConfiguration( + buildRouteConfigurationV2( "route-bar.googleapis.com", // target route configuration ImmutableList.of( - buildVirtualHost( + buildVirtualHostV2( ImmutableList.of(TARGET_AUTHORITY), // matching virtual host "cluster.googleapis.com"))))); response = buildDiscoveryResponseV2( @@ -1375,8 +1375,8 @@ public void cdsResponseWithoutMatchingResource() { // Management server sends back a CDS response without Cluster for the requested resource. List clusters = ImmutableList.of( - Any.pack(buildCluster("cluster-bar.googleapis.com", null, false)), - Any.pack(buildCluster("cluster-baz.googleapis.com", null, false))); + Any.pack(buildClusterV2("cluster-bar.googleapis.com", null, false)), + Any.pack(buildClusterV2("cluster-baz.googleapis.com", null, false))); DiscoveryResponse response = buildDiscoveryResponseV2("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); responseObserver.onNext(response); @@ -1414,9 +1414,9 @@ public void cdsResponseWithMatchingResource() { // Management server sends back a CDS response without Cluster for the requested resource. List clusters = ImmutableList.of( - Any.pack(buildCluster("cluster-bar.googleapis.com", null, false)), - Any.pack(buildCluster("cluster-foo.googleapis.com", null, false)), - Any.pack(buildCluster("cluster-baz.googleapis.com", null, false))); + Any.pack(buildClusterV2("cluster-bar.googleapis.com", null, false)), + Any.pack(buildClusterV2("cluster-foo.googleapis.com", null, false)), + Any.pack(buildClusterV2("cluster-baz.googleapis.com", null, false))); DiscoveryResponse response = buildDiscoveryResponseV2("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); responseObserver.onNext(response); @@ -1437,10 +1437,10 @@ public void cdsResponseWithMatchingResource() { // Management server sends back another CDS response updating the requested Cluster. clusters = ImmutableList.of( - Any.pack(buildCluster("cluster-bar.googleapis.com", null, false)), + Any.pack(buildClusterV2("cluster-bar.googleapis.com", null, false)), Any.pack( - buildCluster("cluster-foo.googleapis.com", "eds-cluster-foo.googleapis.com", true)), - Any.pack(buildCluster("cluster-baz.googleapis.com", null, false))); + buildClusterV2("cluster-foo.googleapis.com", "eds-cluster-foo.googleapis.com", true)), + Any.pack(buildClusterV2("cluster-baz.googleapis.com", null, false))); response = buildDiscoveryResponseV2("1", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0001"); responseObserver.onNext(response); @@ -1470,12 +1470,12 @@ public void cdsResponseWithUpstreamTlsContext() { // Management server sends back CDS response with UpstreamTlsContext. UpstreamTlsContext testUpstreamTlsContext = - buildUpstreamTlsContext("secret1", "unix:/var/uds2"); + buildUpstreamTlsContextV2("secret1", "unix:/var/uds2"); List clusters = ImmutableList.of( - Any.pack(buildCluster("cluster-bar.googleapis.com", null, false)), - Any.pack(buildSecureCluster("cluster-foo.googleapis.com", + Any.pack(buildClusterV2("cluster-bar.googleapis.com", null, false)), + Any.pack(buildSecureClusterV2("cluster-foo.googleapis.com", "eds-cluster-foo.googleapis.com", true, testUpstreamTlsContext)), - Any.pack(buildCluster("cluster-baz.googleapis.com", null, false))); + Any.pack(buildClusterV2("cluster-baz.googleapis.com", null, false))); DiscoveryResponse response = buildDiscoveryResponseV2("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); responseObserver.onNext(response); @@ -1527,7 +1527,7 @@ public void multipleClusterWatchers() { // Management server sends back a CDS response contains Cluster for only one of // requested cluster. List clusters = ImmutableList.of( - Any.pack(buildCluster("cluster-foo.googleapis.com", null, false))); + Any.pack(buildClusterV2("cluster-foo.googleapis.com", null, false))); DiscoveryResponse response = buildDiscoveryResponseV2("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); responseObserver.onNext(response); @@ -1572,9 +1572,9 @@ public void multipleClusterWatchers() { // Management server sends back another CDS response contains Clusters for all // requested clusters. clusters = ImmutableList.of( - Any.pack(buildCluster("cluster-foo.googleapis.com", null, false)), + Any.pack(buildClusterV2("cluster-foo.googleapis.com", null, false)), Any.pack( - buildCluster("cluster-bar.googleapis.com", + buildClusterV2("cluster-bar.googleapis.com", "eds-cluster-bar.googleapis.com", true))); response = buildDiscoveryResponseV2("1", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0001"); @@ -1622,7 +1622,7 @@ public void watchClusterAlreadyBeingWatched() { // Management server sends back an CDS response with Cluster for the requested // cluster. List clusters = ImmutableList.of( - Any.pack(buildCluster("cluster-foo.googleapis.com", null, false))); + Any.pack(buildClusterV2("cluster-foo.googleapis.com", null, false))); DiscoveryResponse response = buildDiscoveryResponseV2("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); responseObserver.onNext(response); @@ -1679,7 +1679,7 @@ public void addRemoveClusterWatchers() { // Management server sends back a CDS response with Cluster for the requested // cluster. List clusters = ImmutableList.of( - Any.pack(buildCluster("cluster-foo.googleapis.com", null, false))); + Any.pack(buildClusterV2("cluster-foo.googleapis.com", null, false))); DiscoveryResponse response = buildDiscoveryResponseV2("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); responseObserver.onNext(response); @@ -1711,9 +1711,9 @@ public void addRemoveClusterWatchers() { // Management server sends back a CDS response with Cluster for all requested cluster. clusters = ImmutableList.of( - Any.pack(buildCluster("cluster-foo.googleapis.com", null, false)), + Any.pack(buildClusterV2("cluster-foo.googleapis.com", null, false)), Any.pack( - buildCluster("cluster-bar.googleapis.com", + buildClusterV2("cluster-bar.googleapis.com", "eds-cluster-bar.googleapis.com", true))); response = buildDiscoveryResponseV2("1", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0001"); @@ -1759,9 +1759,9 @@ public void addRemoveClusterWatchers() { // Management server sends back a new CDS response. clusters = ImmutableList.of( - Any.pack(buildCluster("cluster-foo.googleapis.com", null, true)), + Any.pack(buildClusterV2("cluster-foo.googleapis.com", null, true)), Any.pack( - buildCluster("cluster-bar.googleapis.com", null, false))); + buildClusterV2("cluster-bar.googleapis.com", null, false))); response = buildDiscoveryResponseV2("2", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0002"); responseObserver.onNext(response); @@ -1788,9 +1788,9 @@ public void addRemoveClusterWatchers() { // Management server sends back a new CDS response for at least newly requested resources // (it is required to do so). clusters = ImmutableList.of( - Any.pack(buildCluster("cluster-foo.googleapis.com", null, true)), + Any.pack(buildClusterV2("cluster-foo.googleapis.com", null, true)), Any.pack( - buildCluster("cluster-bar.googleapis.com", null, false))); + buildClusterV2("cluster-bar.googleapis.com", null, false))); response = buildDiscoveryResponseV2("3", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0003"); responseObserver.onNext(response); @@ -1890,7 +1890,7 @@ public void cdsUpdateForClusterBeingRemoved() { // Management server sends back a CDS response containing requested resource. List clusters = ImmutableList.of( - Any.pack(buildCluster("cluster-foo.googleapis.com", null, true))); + Any.pack(buildClusterV2("cluster-foo.googleapis.com", null, true))); DiscoveryResponse response = buildDiscoveryResponseV2("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); responseObserver.onNext(response); @@ -1937,18 +1937,18 @@ public void edsResponseWithoutMatchingResource() { // Management server sends back an EDS response without ClusterLoadAssignment for the requested // cluster. List clusterLoadAssignments = ImmutableList.of( - Any.pack(buildClusterLoadAssignment("cluster-bar.googleapis.com", + Any.pack(buildClusterLoadAssignmentV2("cluster-bar.googleapis.com", ImmutableList.of( - buildLocalityLbEndpoints("region1", "zone1", "subzone1", + buildLocalityLbEndpointsV2("region1", "zone1", "subzone1", ImmutableList.of( - buildLbEndpoint("192.168.0.1", 8080, HealthStatus.HEALTHY, 2)), + buildLbEndpointV2("192.168.0.1", 8080, HealthStatus.HEALTHY, 2)), 1, 0)), ImmutableList.of())), - Any.pack(buildClusterLoadAssignment("cluster-baz.googleapis.com", + Any.pack(buildClusterLoadAssignmentV2("cluster-baz.googleapis.com", ImmutableList.of( - buildLocalityLbEndpoints("region2", "zone2", "subzone2", + buildLocalityLbEndpointsV2("region2", "zone2", "subzone2", ImmutableList.of( - buildLbEndpoint("192.168.234.52", 8888, HealthStatus.UNKNOWN, 5)), + buildLbEndpointV2("192.168.234.52", 8888, HealthStatus.UNKNOWN, 5)), 6, 1)), ImmutableList.of()))); @@ -1992,27 +1992,27 @@ public void edsResponseWithMatchingResource() { // Management server sends back an EDS response with ClusterLoadAssignment for the requested // cluster. List clusterLoadAssignments = ImmutableList.of( - Any.pack(buildClusterLoadAssignment("cluster-foo.googleapis.com", + Any.pack(buildClusterLoadAssignmentV2("cluster-foo.googleapis.com", ImmutableList.of( - buildLocalityLbEndpoints("region1", "zone1", "subzone1", + buildLocalityLbEndpointsV2("region1", "zone1", "subzone1", ImmutableList.of( - buildLbEndpoint("192.168.0.1", 8080, HealthStatus.HEALTHY, 2)), + buildLbEndpointV2("192.168.0.1", 8080, HealthStatus.HEALTHY, 2)), 1, 0), - buildLocalityLbEndpoints("region3", "zone3", "subzone3", + buildLocalityLbEndpointsV2("region3", "zone3", "subzone3", ImmutableList.of(), 2, 1), /* locality with 0 endpoint */ - buildLocalityLbEndpoints("region4", "zone4", "subzone4", + buildLocalityLbEndpointsV2("region4", "zone4", "subzone4", ImmutableList.of( - buildLbEndpoint("192.168.142.5", 80, HealthStatus.UNKNOWN, 5)), + buildLbEndpointV2("192.168.142.5", 80, HealthStatus.UNKNOWN, 5)), 0, 2) /* locality with 0 weight */), ImmutableList.of( - buildDropOverload("lb", 200), - buildDropOverload("throttle", 1000)))), - Any.pack(buildClusterLoadAssignment("cluster-baz.googleapis.com", + buildDropOverloadV2("lb", 200), + buildDropOverloadV2("throttle", 1000)))), + Any.pack(buildClusterLoadAssignmentV2("cluster-baz.googleapis.com", ImmutableList.of( - buildLocalityLbEndpoints("region2", "zone2", "subzone2", + buildLocalityLbEndpointsV2("region2", "zone2", "subzone2", ImmutableList.of( - buildLbEndpoint("192.168.234.52", 8888, HealthStatus.UNKNOWN, 5)), + buildLbEndpointV2("192.168.234.52", 8888, HealthStatus.UNKNOWN, 5)), 6, 1)), ImmutableList.of()))); @@ -2047,7 +2047,7 @@ public void edsResponseWithMatchingResource() { new LocalityLbEndpoints(ImmutableList.of(), 2, 1)); clusterLoadAssignments = ImmutableList.of( - Any.pack(buildClusterLoadAssignment("cluster-foo.googleapis.com", + Any.pack(buildClusterLoadAssignmentV2("cluster-foo.googleapis.com", // 0 locality ImmutableList.of(), ImmutableList.of()))); @@ -2093,11 +2093,11 @@ public void multipleEndpointWatchers() { // Management server sends back an EDS response contains ClusterLoadAssignment for only one of // requested cluster. List clusterLoadAssignments = ImmutableList.of( - Any.pack(buildClusterLoadAssignment("cluster-foo.googleapis.com", + Any.pack(buildClusterLoadAssignmentV2("cluster-foo.googleapis.com", ImmutableList.of( - buildLocalityLbEndpoints("region1", "zone1", "subzone1", + buildLocalityLbEndpointsV2("region1", "zone1", "subzone1", ImmutableList.of( - buildLbEndpoint("192.168.0.1", 8080, HealthStatus.HEALTHY, 2)), + buildLbEndpointV2("192.168.0.1", 8080, HealthStatus.HEALTHY, 2)), 1, 0)), ImmutableList.of()))); @@ -2146,11 +2146,11 @@ public void multipleEndpointWatchers() { // Management server sends back another EDS response contains ClusterLoadAssignment for the // other requested cluster. clusterLoadAssignments = ImmutableList.of( - Any.pack(buildClusterLoadAssignment("cluster-bar.googleapis.com", + Any.pack(buildClusterLoadAssignmentV2("cluster-bar.googleapis.com", ImmutableList.of( - buildLocalityLbEndpoints("region2", "zone2", "subzone2", + buildLocalityLbEndpointsV2("region2", "zone2", "subzone2", ImmutableList.of( - buildLbEndpoint("192.168.234.52", 8888, HealthStatus.UNKNOWN, 5)), + buildLbEndpointV2("192.168.234.52", 8888, HealthStatus.UNKNOWN, 5)), 6, 0)), ImmutableList.of()))); @@ -2202,11 +2202,11 @@ public void watchEndpointsForClusterAlreadyBeingWatched() { // Management server sends back an EDS response containing ClusterLoadAssignments for // some cluster not requested. List clusterLoadAssignments = ImmutableList.of( - Any.pack(buildClusterLoadAssignment("cluster-foo.googleapis.com", + Any.pack(buildClusterLoadAssignmentV2("cluster-foo.googleapis.com", ImmutableList.of( - buildLocalityLbEndpoints("region1", "zone1", "subzone1", + buildLocalityLbEndpointsV2("region1", "zone1", "subzone1", ImmutableList.of( - buildLbEndpoint("192.168.0.1", 8080, HealthStatus.HEALTHY, 2)), + buildLbEndpointV2("192.168.0.1", 8080, HealthStatus.HEALTHY, 2)), 1, 0)), ImmutableList.of()))); @@ -2278,12 +2278,12 @@ public void addRemoveEndpointWatchers() { // Management server sends back an EDS response with ClusterLoadAssignment for the requested // cluster. List clusterLoadAssignments = ImmutableList.of( - Any.pack(buildClusterLoadAssignment("cluster-foo.googleapis.com", + Any.pack(buildClusterLoadAssignmentV2("cluster-foo.googleapis.com", ImmutableList.of( - buildLocalityLbEndpoints("region1", "zone1", "subzone1", + buildLocalityLbEndpointsV2("region1", "zone1", "subzone1", ImmutableList.of( - buildLbEndpoint("192.168.0.1", 8080, HealthStatus.HEALTHY, 2), - buildLbEndpoint("192.132.53.5", 80, HealthStatus.UNHEALTHY, 5)), + buildLbEndpointV2("192.168.0.1", 8080, HealthStatus.HEALTHY, 2), + buildLbEndpointV2("192.132.53.5", 80, HealthStatus.UNHEALTHY, 5)), 1, 0)), ImmutableList.of()))); @@ -2325,11 +2325,11 @@ public void addRemoveEndpointWatchers() { // Management server sends back an EDS response with ClusterLoadAssignment for one of requested // cluster. clusterLoadAssignments = ImmutableList.of( - Any.pack(buildClusterLoadAssignment("cluster-bar.googleapis.com", + Any.pack(buildClusterLoadAssignmentV2("cluster-bar.googleapis.com", ImmutableList.of( - buildLocalityLbEndpoints("region2", "zone2", "subzone2", + buildLocalityLbEndpointsV2("region2", "zone2", "subzone2", ImmutableList.of( - buildLbEndpoint("192.168.312.6", 443, HealthStatus.HEALTHY, 1)), + buildLbEndpointV2("192.168.312.6", 443, HealthStatus.HEALTHY, 1)), 6, 0)), ImmutableList.of()))); @@ -2384,18 +2384,18 @@ public void addRemoveEndpointWatchers() { // Management server sends back an EDS response for updating previously sent resources. clusterLoadAssignments = ImmutableList.of( - Any.pack(buildClusterLoadAssignment("cluster-foo.googleapis.com", + Any.pack(buildClusterLoadAssignmentV2("cluster-foo.googleapis.com", ImmutableList.of( - buildLocalityLbEndpoints("region3", "zone3", "subzone3", + buildLocalityLbEndpointsV2("region3", "zone3", "subzone3", ImmutableList.of( - buildLbEndpoint("192.168.432.6", 80, HealthStatus.HEALTHY, 2)), + buildLbEndpointV2("192.168.432.6", 80, HealthStatus.HEALTHY, 2)), 3, 0)), ImmutableList.of())), - Any.pack(buildClusterLoadAssignment("cluster-bar.googleapis.com", + Any.pack(buildClusterLoadAssignmentV2("cluster-bar.googleapis.com", ImmutableList.of( - buildLocalityLbEndpoints("region4", "zone4", "subzone4", + buildLocalityLbEndpointsV2("region4", "zone4", "subzone4", ImmutableList.of( - buildLbEndpoint("192.168.75.6", 8888, HealthStatus.HEALTHY, 2)), + buildLbEndpointV2("192.168.75.6", 8888, HealthStatus.HEALTHY, 2)), 3, 0)), ImmutableList.of()))); @@ -2430,11 +2430,11 @@ public void addRemoveEndpointWatchers() { // Management server sends back an EDS response for re-subscribed resource. clusterLoadAssignments = ImmutableList.of( - Any.pack(buildClusterLoadAssignment("cluster-bar.googleapis.com", + Any.pack(buildClusterLoadAssignmentV2("cluster-bar.googleapis.com", ImmutableList.of( - buildLocalityLbEndpoints("region4", "zone4", "subzone4", + buildLocalityLbEndpointsV2("region4", "zone4", "subzone4", ImmutableList.of( - buildLbEndpoint("192.168.75.6", 8888, HealthStatus.HEALTHY, 2)), + buildLbEndpointV2("192.168.75.6", 8888, HealthStatus.HEALTHY, 2)), 3, 0)), ImmutableList.of()))); @@ -2535,7 +2535,7 @@ public void cdsUpdateForEdsServiceNameChange() { // Management server sends back a CDS response containing requested resource. List clusters = ImmutableList.of( - Any.pack(buildCluster("cluster-foo.googleapis.com", "cluster-foo:service-bar", false))); + Any.pack(buildClusterV2("cluster-foo.googleapis.com", "cluster-foo:service-bar", false))); DiscoveryResponse response = buildDiscoveryResponseV2("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); responseObserver.onNext(response); @@ -2544,11 +2544,11 @@ public void cdsUpdateForEdsServiceNameChange() { // Management server sends back an EDS response for resource "cluster-foo:service-bar". List clusterLoadAssignments = ImmutableList.of( - Any.pack(buildClusterLoadAssignment("cluster-foo:service-bar", + Any.pack(buildClusterLoadAssignmentV2("cluster-foo:service-bar", ImmutableList.of( - buildLocalityLbEndpoints("region1", "zone1", "subzone1", + buildLocalityLbEndpointsV2("region1", "zone1", "subzone1", ImmutableList.of( - buildLbEndpoint("192.168.0.1", 8080, HealthStatus.HEALTHY, 2)), + buildLbEndpointV2("192.168.0.1", 8080, HealthStatus.HEALTHY, 2)), 1, 0)), ImmutableList.of()))); response = @@ -2572,7 +2572,7 @@ public void cdsUpdateForEdsServiceNameChange() { // Management server sends another CDS response for removing cluster service // "cluster-foo:service-blade" with replacement of "cluster-foo:service-blade". clusters = ImmutableList.of( - Any.pack(buildCluster("cluster-foo.googleapis.com", "cluster-foo:service-blade", false))); + Any.pack(buildClusterV2("cluster-foo.googleapis.com", "cluster-foo:service-blade", false))); response = buildDiscoveryResponseV2("1", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0001"); responseObserver.onNext(response); @@ -2654,7 +2654,7 @@ public void streamClosedAndRetryWhenResolvingConfig() { .build(); List listeners = ImmutableList.of( - Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(buildListenerV2(TARGET_AUTHORITY, /* matching resource */ Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) ); DiscoveryResponse ldsResponse = @@ -2709,10 +2709,10 @@ public void streamClosedAndRetryWhenResolvingConfig() { List routeConfigs = ImmutableList.of( Any.pack( - buildRouteConfiguration( + buildRouteConfigurationV2( "route-foo.googleapis.com", // target route configuration ImmutableList.of( - buildVirtualHost( + buildVirtualHostV2( ImmutableList.of(TARGET_AUTHORITY), // matching virtual host "cluster.googleapis.com"))))); DiscoveryResponse rdsResponse = @@ -2865,7 +2865,7 @@ public void streamClosedAndRetry() { // Management server sends back a CDS response. List clusters = ImmutableList.of( - Any.pack(buildCluster("cluster.googleapis.com", null, false))); + Any.pack(buildClusterV2("cluster.googleapis.com", null, false))); DiscoveryResponse cdsResponse = buildDiscoveryResponseV2("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); responseObserver.onNext(cdsResponse); @@ -3016,7 +3016,7 @@ public void streamClosedAndRetryRaceWithAddingAndRemovingWatchers() { // Management server sends back a CDS response. List clusters = ImmutableList.of( - Any.pack(buildCluster("cluster.googleapis.com", null, false))); + Any.pack(buildClusterV2("cluster.googleapis.com", null, false))); DiscoveryResponse cdsResponse = buildDiscoveryResponseV2("0", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "0000"); responseObserver.onNext(cdsResponse); @@ -3112,7 +3112,7 @@ public void streamClosedAndRetryReschedulesAllResourceFetchTimer() { .build(); List listeners = ImmutableList.of( - Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(buildListenerV2(TARGET_AUTHORITY, /* matching resource */ Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) ); DiscoveryResponse response = @@ -3164,10 +3164,10 @@ public void streamClosedAndRetryReschedulesAllResourceFetchTimer() { // for the requested resource. List routeConfigs = ImmutableList.of( Any.pack( - buildRouteConfiguration( + buildRouteConfigurationV2( "route-foo.googleapis.com", // target route configuration ImmutableList.of( - buildVirtualHost( + buildVirtualHostV2( ImmutableList.of(TARGET_AUTHORITY), // matching virtual host "cluster-foo.googleapis.com"))))); response = buildDiscoveryResponseV2( @@ -3287,7 +3287,7 @@ private void waitUntilConfigResolved(StreamObserver responseO .build(); List listeners = ImmutableList.of( - Any.pack(buildListener(TARGET_AUTHORITY, /* matching resource */ + Any.pack(buildListenerV2(TARGET_AUTHORITY, /* matching resource */ Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build()))) ); DiscoveryResponse ldsResponse = @@ -3300,10 +3300,10 @@ private void waitUntilConfigResolved(StreamObserver responseO // Management server sends an RDS response. List routeConfigs = ImmutableList.of( Any.pack( - buildRouteConfiguration( + buildRouteConfigurationV2( "route-foo.googleapis.com", // target route configuration ImmutableList.of( - buildVirtualHost( + buildVirtualHostV2( ImmutableList.of(TARGET_AUTHORITY), // matching virtual host "cluster.googleapis.com"))))); DiscoveryResponse rdsResponse = @@ -3474,13 +3474,13 @@ public void populateRoutesInVirtualHost_NoUsableRoute() { public void messagePrinter_printLdsResponse() { MessagePrinter printer = new MessagePrinter(); List listeners = ImmutableList.of( - Any.pack(buildListener("foo.googleapis.com:8080", + Any.pack(buildListenerV2("foo.googleapis.com:8080", Any.pack( HttpConnectionManager.newBuilder() .setRouteConfig( - buildRouteConfiguration("route-foo.googleapis.com", + buildRouteConfigurationV2("route-foo.googleapis.com", ImmutableList.of( - buildVirtualHost( + buildVirtualHostV2( ImmutableList.of("foo.googleapis.com", "bar.googleapis.com"), "cluster.googleapis.com")))) .build())))); @@ -3531,10 +3531,10 @@ public void messagePrinter_printRdsResponse() { List routeConfigs = ImmutableList.of( Any.pack( - buildRouteConfiguration( + buildRouteConfigurationV2( "route-foo.googleapis.com", ImmutableList.of( - buildVirtualHost( + buildVirtualHostV2( ImmutableList.of("foo.googleapis.com", "bar.googleapis.com"), "cluster.googleapis.com"))))); DiscoveryResponse response = @@ -3569,8 +3569,8 @@ public void messagePrinter_printRdsResponse() { public void messagePrinter_printCdsResponse() { MessagePrinter printer = new MessagePrinter(); List clusters = ImmutableList.of( - Any.pack(buildCluster("cluster-bar.googleapis.com", "service-blaze:cluster-bar", true)), - Any.pack(buildCluster("cluster-foo.googleapis.com", null, false))); + Any.pack(buildClusterV2("cluster-bar.googleapis.com", "service-blaze:cluster-bar", true)), + Any.pack(buildClusterV2("cluster-foo.googleapis.com", null, false))); DiscoveryResponse response = buildDiscoveryResponseV2("14", clusters, XdsClientImpl.ADS_TYPE_URL_CDS_V2, "8"); @@ -3613,19 +3613,19 @@ public void messagePrinter_printCdsResponse() { public void messagePrinter_printEdsResponse() { MessagePrinter printer = new MessagePrinter(); List clusterLoadAssignments = ImmutableList.of( - Any.pack(buildClusterLoadAssignment("cluster-foo.googleapis.com", + Any.pack(buildClusterLoadAssignmentV2("cluster-foo.googleapis.com", ImmutableList.of( - buildLocalityLbEndpoints("region1", "zone1", "subzone1", + buildLocalityLbEndpointsV2("region1", "zone1", "subzone1", ImmutableList.of( - buildLbEndpoint("192.168.0.1", 8080, HealthStatus.HEALTHY, 2)), + buildLbEndpointV2("192.168.0.1", 8080, HealthStatus.HEALTHY, 2)), 1, 0), - buildLocalityLbEndpoints("region3", "zone3", "subzone3", + buildLocalityLbEndpointsV2("region3", "zone3", "subzone3", ImmutableList.of( - buildLbEndpoint("192.168.142.5", 80, HealthStatus.UNHEALTHY, 5)), + buildLbEndpointV2("192.168.142.5", 80, HealthStatus.UNHEALTHY, 5)), 2, 1)), ImmutableList.of( - buildDropOverload("lb", 200), - buildDropOverload("throttle", 1000))))); + buildDropOverloadV2("lb", 200), + buildDropOverloadV2("throttle", 1000))))); DiscoveryResponse response = buildDiscoveryResponseV2("5", clusterLoadAssignments, diff --git a/xds/src/test/java/io/grpc/xds/XdsClientTestHelper.java b/xds/src/test/java/io/grpc/xds/XdsClientTestHelper.java index ab976e877e1..19bbed263b5 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientTestHelper.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientTestHelper.java @@ -19,37 +19,39 @@ import com.google.common.collect.ImmutableList; import com.google.protobuf.Any; import com.google.protobuf.UInt32Value; -import io.envoyproxy.envoy.api.v2.Cluster; -import io.envoyproxy.envoy.api.v2.Cluster.DiscoveryType; -import io.envoyproxy.envoy.api.v2.Cluster.EdsClusterConfig; -import io.envoyproxy.envoy.api.v2.Cluster.LbPolicy; -import io.envoyproxy.envoy.api.v2.ClusterLoadAssignment; -import io.envoyproxy.envoy.api.v2.ClusterLoadAssignment.Policy; -import io.envoyproxy.envoy.api.v2.Listener; -import io.envoyproxy.envoy.api.v2.RouteConfiguration; -import io.envoyproxy.envoy.api.v2.auth.CommonTlsContext; -import io.envoyproxy.envoy.api.v2.auth.SdsSecretConfig; -import io.envoyproxy.envoy.api.v2.auth.UpstreamTlsContext; -import io.envoyproxy.envoy.api.v2.core.Address; -import io.envoyproxy.envoy.api.v2.core.AggregatedConfigSource; -import io.envoyproxy.envoy.api.v2.core.ApiConfigSource; -import io.envoyproxy.envoy.api.v2.core.ConfigSource; -import io.envoyproxy.envoy.api.v2.core.GrpcService; -import io.envoyproxy.envoy.api.v2.core.GrpcService.GoogleGrpc; -import io.envoyproxy.envoy.api.v2.core.HealthStatus; -import io.envoyproxy.envoy.api.v2.core.SelfConfigSource; -import io.envoyproxy.envoy.api.v2.core.SocketAddress; -import io.envoyproxy.envoy.api.v2.core.TransportSocket; -import io.envoyproxy.envoy.api.v2.listener.FilterChain; -import io.envoyproxy.envoy.api.v2.route.Route; -import io.envoyproxy.envoy.api.v2.route.RouteAction; -import io.envoyproxy.envoy.api.v2.route.RouteMatch; -import io.envoyproxy.envoy.api.v2.route.VirtualHost; -import io.envoyproxy.envoy.config.listener.v2.ApiListener; +import io.envoyproxy.envoy.config.cluster.v3.Cluster; +import io.envoyproxy.envoy.config.cluster.v3.Cluster.DiscoveryType; +import io.envoyproxy.envoy.config.cluster.v3.Cluster.EdsClusterConfig; +import io.envoyproxy.envoy.config.core.v3.Address; +import io.envoyproxy.envoy.config.core.v3.AggregatedConfigSource; +import io.envoyproxy.envoy.config.core.v3.ApiConfigSource; +import io.envoyproxy.envoy.config.core.v3.ConfigSource; +import io.envoyproxy.envoy.config.core.v3.GrpcService; +import io.envoyproxy.envoy.config.core.v3.GrpcService.GoogleGrpc; +import io.envoyproxy.envoy.config.core.v3.HealthStatus; +import io.envoyproxy.envoy.config.core.v3.Locality; +import io.envoyproxy.envoy.config.core.v3.SelfConfigSource; +import io.envoyproxy.envoy.config.core.v3.SocketAddress; +import io.envoyproxy.envoy.config.core.v3.TransportSocket; +import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment; +import io.envoyproxy.envoy.config.endpoint.v3.Endpoint; +import io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint; +import io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints; +import io.envoyproxy.envoy.config.listener.v3.ApiListener; +import io.envoyproxy.envoy.config.listener.v3.FilterChain; +import io.envoyproxy.envoy.config.listener.v3.Listener; +import io.envoyproxy.envoy.config.route.v3.Route; +import io.envoyproxy.envoy.config.route.v3.RouteAction; +import io.envoyproxy.envoy.config.route.v3.RouteConfiguration; +import io.envoyproxy.envoy.config.route.v3.RouteMatch; +import io.envoyproxy.envoy.config.route.v3.VirtualHost; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.SdsSecretConfig; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext; import io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest; import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse; -import io.envoyproxy.envoy.type.FractionalPercent; -import io.envoyproxy.envoy.type.FractionalPercent.DenominatorType; +import io.envoyproxy.envoy.type.v3.FractionalPercent; +import io.envoyproxy.envoy.type.v3.FractionalPercent.DenominatorType; import io.grpc.xds.EnvoyProtoData.Node; import java.util.List; import javax.annotation.Nullable; @@ -125,6 +127,18 @@ static Listener buildListener(String name, com.google.protobuf.Any apiListener) .build(); } + static io.envoyproxy.envoy.api.v2.Listener buildListenerV2( + String name, com.google.protobuf.Any apiListener) { + return + io.envoyproxy.envoy.api.v2.Listener.newBuilder() + .setName(name) + .setAddress(io.envoyproxy.envoy.api.v2.core.Address.getDefaultInstance()) + .addFilterChains(io.envoyproxy.envoy.api.v2.listener.FilterChain.getDefaultInstance()) + .setApiListener(io.envoyproxy.envoy.config.listener.v2.ApiListener.newBuilder() + .setApiListener(apiListener)) + .build(); + } + static RouteConfiguration buildRouteConfiguration(String name, List virtualHosts) { return @@ -134,6 +148,15 @@ static RouteConfiguration buildRouteConfiguration(String name, .build(); } + static io.envoyproxy.envoy.api.v2.RouteConfiguration buildRouteConfigurationV2(String name, + List virtualHosts) { + return + io.envoyproxy.envoy.api.v2.RouteConfiguration.newBuilder() + .setName(name) + .addAllVirtualHosts(virtualHosts) + .build(); + } + static VirtualHost buildVirtualHost(List domains, String clusterName) { return VirtualHost.newBuilder() .setName("virtualhost00.googleapis.com") // don't care @@ -145,27 +168,49 @@ static VirtualHost buildVirtualHost(List domains, String clusterName) { .build(); } + static io.envoyproxy.envoy.api.v2.route.VirtualHost buildVirtualHostV2( + List domains, String clusterName) { + return io.envoyproxy.envoy.api.v2.route.VirtualHost.newBuilder() + .setName("virtualhost00.googleapis.com") // don't care + .addAllDomains(domains) + .addRoutes( + io.envoyproxy.envoy.api.v2.route.Route.newBuilder() + .setRoute( + io.envoyproxy.envoy.api.v2.route.RouteAction.newBuilder() + .setCluster(clusterName)) + .setMatch(io.envoyproxy.envoy.api.v2.route.RouteMatch.newBuilder().setPrefix(""))) + .build(); + } + static Cluster buildCluster(String clusterName, @Nullable String edsServiceName, boolean enableLrs) { return buildSecureCluster(clusterName, edsServiceName, enableLrs, null); } - static Cluster buildSecureCluster(String clusterName, @Nullable String edsServiceName, - boolean enableLrs, @Nullable UpstreamTlsContext upstreamTlsContext) { + static io.envoyproxy.envoy.api.v2.Cluster buildClusterV2( + String clusterName, @Nullable String edsServiceName, boolean enableLrs) { + return buildSecureClusterV2(clusterName, edsServiceName, enableLrs, null); + } + + static Cluster buildSecureCluster( + String clusterName, @Nullable String edsServiceName, boolean enableLrs, + @Nullable UpstreamTlsContext upstreamTlsContext) { Cluster.Builder clusterBuilder = Cluster.newBuilder(); clusterBuilder.setName(clusterName); clusterBuilder.setType(DiscoveryType.EDS); EdsClusterConfig.Builder edsClusterConfigBuilder = EdsClusterConfig.newBuilder(); edsClusterConfigBuilder.setEdsConfig( - ConfigSource.newBuilder().setAds(AggregatedConfigSource.getDefaultInstance())); + ConfigSource.newBuilder() + .setAds(AggregatedConfigSource.getDefaultInstance())); if (edsServiceName != null) { edsClusterConfigBuilder.setServiceName(edsServiceName); } clusterBuilder.setEdsClusterConfig(edsClusterConfigBuilder); - clusterBuilder.setLbPolicy(LbPolicy.ROUND_ROBIN); + clusterBuilder.setLbPolicy(Cluster.LbPolicy.ROUND_ROBIN); if (enableLrs) { clusterBuilder.setLrsServer( - ConfigSource.newBuilder().setSelf(SelfConfigSource.getDefaultInstance())); + ConfigSource.newBuilder() + .setSelf(SelfConfigSource.getDefaultInstance())); } if (upstreamTlsContext != null) { clusterBuilder.setTransportSocket( @@ -174,24 +219,69 @@ static Cluster buildSecureCluster(String clusterName, @Nullable String edsServic return clusterBuilder.build(); } - @SuppressWarnings("deprecation") + static io.envoyproxy.envoy.api.v2.Cluster buildSecureClusterV2( + String clusterName, @Nullable String edsServiceName, boolean enableLrs, + @Nullable io.envoyproxy.envoy.api.v2.auth.UpstreamTlsContext upstreamTlsContext) { + io.envoyproxy.envoy.api.v2.Cluster.Builder clusterBuilder = + io.envoyproxy.envoy.api.v2.Cluster.newBuilder(); + clusterBuilder.setName(clusterName); + clusterBuilder.setType(io.envoyproxy.envoy.api.v2.Cluster.DiscoveryType.EDS); + io.envoyproxy.envoy.api.v2.Cluster.EdsClusterConfig.Builder edsClusterConfigBuilder = + io.envoyproxy.envoy.api.v2.Cluster.EdsClusterConfig.newBuilder(); + edsClusterConfigBuilder.setEdsConfig( + io.envoyproxy.envoy.api.v2.core.ConfigSource.newBuilder() + .setAds(io.envoyproxy.envoy.api.v2.core.AggregatedConfigSource.getDefaultInstance())); + if (edsServiceName != null) { + edsClusterConfigBuilder.setServiceName(edsServiceName); + } + clusterBuilder.setEdsClusterConfig(edsClusterConfigBuilder); + clusterBuilder.setLbPolicy(io.envoyproxy.envoy.api.v2.Cluster.LbPolicy.ROUND_ROBIN); + if (enableLrs) { + clusterBuilder.setLrsServer( + io.envoyproxy.envoy.api.v2.core.ConfigSource.newBuilder() + .setSelf(io.envoyproxy.envoy.api.v2.core.SelfConfigSource.getDefaultInstance())); + } + if (upstreamTlsContext != null) { + clusterBuilder.setTransportSocket( + io.envoyproxy.envoy.api.v2.core.TransportSocket.newBuilder() + .setName("tls").setTypedConfig(Any.pack(upstreamTlsContext))); + } + return clusterBuilder.build(); + } + static ClusterLoadAssignment buildClusterLoadAssignment(String clusterName, - List localityLbEndpoints, - List dropOverloads) { + List localityLbEndpoints, + List dropOverloads) { return ClusterLoadAssignment.newBuilder() .setClusterName(clusterName) .addAllEndpoints(localityLbEndpoints) .setPolicy( - Policy.newBuilder() + ClusterLoadAssignment.Policy.newBuilder() + .addAllDropOverloads(dropOverloads)) + .build(); + } + + @SuppressWarnings("deprecation") + static io.envoyproxy.envoy.api.v2.ClusterLoadAssignment buildClusterLoadAssignmentV2( + String clusterName, + List localityLbEndpoints, + List dropOverloads) { + return + io.envoyproxy.envoy.api.v2.ClusterLoadAssignment.newBuilder() + .setClusterName(clusterName) + .addAllEndpoints(localityLbEndpoints) + .setPolicy( + io.envoyproxy.envoy.api.v2.ClusterLoadAssignment.Policy.newBuilder() .setDisableOverprovisioning(true) .addAllDropOverloads(dropOverloads)) .build(); } - static Policy.DropOverload buildDropOverload(String category, int dropPerMillion) { + static ClusterLoadAssignment.Policy.DropOverload buildDropOverload( + String category, int dropPerMillion) { return - Policy.DropOverload.newBuilder() + ClusterLoadAssignment.Policy.DropOverload.newBuilder() .setCategory(category) .setDropPercentage( FractionalPercent.newBuilder() @@ -200,7 +290,36 @@ static Policy.DropOverload buildDropOverload(String category, int dropPerMillion .build(); } - static io.envoyproxy.envoy.api.v2.endpoint.LocalityLbEndpoints buildLocalityLbEndpoints( + static io.envoyproxy.envoy.api.v2.ClusterLoadAssignment.Policy.DropOverload buildDropOverloadV2( + String category, int dropPerMillion) { + return + io.envoyproxy.envoy.api.v2.ClusterLoadAssignment.Policy.DropOverload.newBuilder() + .setCategory(category) + .setDropPercentage( + io.envoyproxy.envoy.type.FractionalPercent.newBuilder() + .setNumerator(dropPerMillion) + .setDenominator( + io.envoyproxy.envoy.type.FractionalPercent.DenominatorType.MILLION)) + .build(); + } + + static LocalityLbEndpoints buildLocalityLbEndpoints( + String region, String zone, String subZone, List lbEndpoints, + int loadBalancingWeight, int priority) { + return + LocalityLbEndpoints.newBuilder() + .setLocality( + Locality.newBuilder() + .setRegion(region) + .setZone(zone) + .setSubZone(subZone)) + .addAllLbEndpoints(lbEndpoints) + .setLoadBalancingWeight(UInt32Value.of(loadBalancingWeight)) + .setPriority(priority) + .build(); + } + + static io.envoyproxy.envoy.api.v2.endpoint.LocalityLbEndpoints buildLocalityLbEndpointsV2( String region, String zone, String subZone, List lbEndpoints, int loadBalancingWeight, int priority) { @@ -217,12 +336,12 @@ static io.envoyproxy.envoy.api.v2.endpoint.LocalityLbEndpoints buildLocalityLbEn .build(); } - static io.envoyproxy.envoy.api.v2.endpoint.LbEndpoint buildLbEndpoint(String address, - int port, HealthStatus healthStatus, int loadbalancingWeight) { + static LbEndpoint buildLbEndpoint( + String address, int port, HealthStatus healthStatus, int loadbalancingWeight) { return - io.envoyproxy.envoy.api.v2.endpoint.LbEndpoint.newBuilder() + LbEndpoint.newBuilder() .setEndpoint( - io.envoyproxy.envoy.api.v2.endpoint.Endpoint.newBuilder().setAddress( + Endpoint.newBuilder().setAddress( Address.newBuilder().setSocketAddress( SocketAddress.newBuilder().setAddress(address).setPortValue(port)))) .setHealthStatus(healthStatus) @@ -230,6 +349,22 @@ static io.envoyproxy.envoy.api.v2.endpoint.LbEndpoint buildLbEndpoint(String add .build(); } + static io.envoyproxy.envoy.api.v2.endpoint.LbEndpoint buildLbEndpointV2( + String address, int port, io.envoyproxy.envoy.api.v2.core.HealthStatus healthStatus, + int loadbalancingWeight) { + return + io.envoyproxy.envoy.api.v2.endpoint.LbEndpoint.newBuilder() + .setEndpoint( + io.envoyproxy.envoy.api.v2.endpoint.Endpoint.newBuilder().setAddress( + io.envoyproxy.envoy.api.v2.core.Address.newBuilder().setSocketAddress( + io.envoyproxy.envoy.api.v2.core.SocketAddress.newBuilder() + .setAddress(address) + .setPortValue(port)))) + .setHealthStatus(healthStatus) + .setLoadBalancingWeight(UInt32Value.of(loadbalancingWeight)) + .build(); + } + static UpstreamTlsContext buildUpstreamTlsContext(String secretName, String targetUri) { GrpcService grpcService = GrpcService.newBuilder() @@ -250,4 +385,28 @@ static UpstreamTlsContext buildUpstreamTlsContext(String secretName, String targ .setValidationContextSdsSecretConfig(validationContextSdsSecretConfig)) .build(); } + + static io.envoyproxy.envoy.api.v2.auth.UpstreamTlsContext buildUpstreamTlsContextV2( + String secretName, String targetUri) { + io.envoyproxy.envoy.api.v2.core.GrpcService grpcService = + io.envoyproxy.envoy.api.v2.core.GrpcService.newBuilder() + .setGoogleGrpc(io.envoyproxy.envoy.api.v2.core.GrpcService.GoogleGrpc.newBuilder() + .setTargetUri(targetUri)) + .build(); + io.envoyproxy.envoy.api.v2.core.ConfigSource sdsConfig = + io.envoyproxy.envoy.api.v2.core.ConfigSource.newBuilder() + .setApiConfigSource(io.envoyproxy.envoy.api.v2.core.ApiConfigSource.newBuilder() + .addGrpcServices(grpcService)) + .build(); + io.envoyproxy.envoy.api.v2.auth.SdsSecretConfig validationContextSdsSecretConfig = + io.envoyproxy.envoy.api.v2.auth.SdsSecretConfig.newBuilder() + .setName(secretName) + .setSdsConfig(sdsConfig) + .build(); + return io.envoyproxy.envoy.api.v2.auth.UpstreamTlsContext.newBuilder() + .setCommonTlsContext( + io.envoyproxy.envoy.api.v2.auth.CommonTlsContext.newBuilder() + .setValidationContextSdsSecretConfig(validationContextSdsSecretConfig)) + .build(); + } } diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverIntegrationTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverIntegrationTest.java index 3c844e08e99..d911b34ec0b 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverIntegrationTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverIntegrationTest.java @@ -18,9 +18,9 @@ import static com.google.common.truth.Truth.assertThat; import static io.grpc.xds.XdsClientTestHelper.buildDiscoveryResponseV2; -import static io.grpc.xds.XdsClientTestHelper.buildListener; -import static io.grpc.xds.XdsClientTestHelper.buildRouteConfiguration; -import static io.grpc.xds.XdsClientTestHelper.buildVirtualHost; +import static io.grpc.xds.XdsClientTestHelper.buildListenerV2; +import static io.grpc.xds.XdsClientTestHelper.buildRouteConfigurationV2; +import static io.grpc.xds.XdsClientTestHelper.buildVirtualHostV2; import static io.grpc.xds.XdsNameResolverTest.assertCdsPolicy; import static io.grpc.xds.XdsNameResolverTest.assertWeightedTargetConfigClusterWeights; import static io.grpc.xds.XdsNameResolverTest.assertWeightedTargetPolicy; @@ -389,12 +389,12 @@ public void resolve_xdsRoutingLoadBalancing() { HttpConnectionManager httpConnectionManager = HttpConnectionManager.newBuilder() .setRouteConfig( - buildRouteConfiguration( + buildRouteConfigurationV2( "route-foo.googleapis.com", // doesn't matter ImmutableList.of(buildVirtualHostForRoutes(AUTHORITY, protoRoutes)))) .build(); List listeners = - ImmutableList.of(Any.pack(buildListener(AUTHORITY, Any.pack(httpConnectionManager)))); + ImmutableList.of(Any.pack(buildListenerV2(AUTHORITY, Any.pack(httpConnectionManager)))); responseObserver.onNext( buildDiscoveryResponseV2("0", listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, "0000")); @@ -472,7 +472,7 @@ public void resolve_weightedTargetLoadBalancing() { .build(); List routeConfigs = ImmutableList.of( Any.pack( - buildRouteConfiguration( + buildRouteConfigurationV2( routeConfigName, ImmutableList.of( buildVirtualHostForRoutes( @@ -537,13 +537,13 @@ public void resolve_resourceNewlyAdded() { private static DiscoveryResponse buildLdsResponseForCluster( String versionInfo, String host, String clusterName, String nonce) { List listeners = ImmutableList.of( - Any.pack(buildListener(host, // target Listener resource + Any.pack(buildListenerV2(host, // target Listener resource Any.pack( HttpConnectionManager.newBuilder() .setRouteConfig( - buildRouteConfiguration("route-foo.googleapis.com", // doesn't matter + buildRouteConfigurationV2("route-foo.googleapis.com", // doesn't matter ImmutableList.of( - buildVirtualHost( + buildVirtualHostV2( ImmutableList.of(host), // exact match clusterName)))) .build())))); @@ -568,7 +568,7 @@ private static DiscoveryResponse buildLdsResponseForRdsResource( List listeners = ImmutableList.of( Any.pack( - buildListener( + buildListenerV2( host, Any.pack(HttpConnectionManager.newBuilder().setRds(rdsConfig).build())))); return buildDiscoveryResponseV2( versionInfo, listeners, XdsClientImpl.ADS_TYPE_URL_LDS_V2, nonce); @@ -586,10 +586,10 @@ private static DiscoveryResponse buildRdsResponseForCluster( String nonce) { List routeConfigs = ImmutableList.of( Any.pack( - buildRouteConfiguration( + buildRouteConfigurationV2( routeConfigName, ImmutableList.of( - buildVirtualHost(ImmutableList.of(host), clusterName))))); + buildVirtualHostV2(ImmutableList.of(host), clusterName))))); return buildDiscoveryResponseV2( versionInfo, routeConfigs, XdsClientImpl.ADS_TYPE_URL_RDS_V2, nonce); } From f8bd84c755bf7b2ade55a11f050f80d882dfcdeb Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Thu, 20 Aug 2020 21:19:17 -0700 Subject: [PATCH 73/88] xds: replace mock(ScheduledFuture) with a TestScheduledFuture implementation (#7346) --- .../MeshCaCertificateProviderTest.java | 86 +++++++++++++++---- 1 file changed, 69 insertions(+), 17 deletions(-) diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderTest.java index b38ba701a79..3ead9134007 100644 --- a/xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderTest.java @@ -63,14 +63,14 @@ import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Queue; -import java.util.concurrent.ExecutionException; +import java.util.concurrent.Delayed; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import org.bouncycastle.operator.OperatorCreationException; import org.junit.Before; @@ -276,7 +276,7 @@ ManagedChannel createChannel(String serverUri) { @Test public void startAndClose() { - ScheduledFuture scheduledFuture = mock(ScheduledFuture.class); + TestScheduledFuture scheduledFuture = new TestScheduledFuture<>(); doReturn(scheduledFuture) .when(timeService) .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); @@ -304,7 +304,7 @@ public void startAndClose() { @Test public void startTwice_noException() { - ScheduledFuture scheduledFuture = mock(ScheduledFuture.class); + TestScheduledFuture scheduledFuture = new TestScheduledFuture<>(); doReturn(scheduledFuture) .when(timeService) .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); @@ -327,7 +327,7 @@ public void getCertificate() CommonTlsContextTestsUtil.getResourceContents(SERVER_1_PEM_FILE), CommonTlsContextTestsUtil.getResourceContents(CA_PEM_FILE)))); when(timeProvider.currentTimeNanos()).thenReturn(CURRENT_TIME_NANOS); - ScheduledFuture scheduledFuture = mock(ScheduledFuture.class); + TestScheduledFuture scheduledFuture = new TestScheduledFuture<>(); doReturn(scheduledFuture) .when(timeService) .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); @@ -355,7 +355,7 @@ public void getCertificate_withError() oauth2Tokens.offer(TEST_STS_TOKEN + "0"); responsesToSend .offer(new ResponseThrowable(new StatusRuntimeException(Status.FAILED_PRECONDITION))); - ScheduledFuture scheduledFuture = mock(ScheduledFuture.class); + TestScheduledFuture scheduledFuture = new TestScheduledFuture<>(); doReturn(scheduledFuture).when(timeService) .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); provider.refreshCertificate(); @@ -384,7 +384,7 @@ public void getCertificate_withError_withExistingCert() responsesToSend .offer(new ResponseThrowable(new StatusRuntimeException(Status.FAILED_PRECONDITION))); when(timeProvider.currentTimeNanos()).thenReturn(CURRENT_TIME_NANOS); - ScheduledFuture scheduledFuture = mock(ScheduledFuture.class); + TestScheduledFuture scheduledFuture = new TestScheduledFuture<>(); doReturn(scheduledFuture).when(timeService) .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); provider.refreshCertificate(); @@ -414,7 +414,7 @@ public void getCertificate_withError_withExistingExpiredCert() responsesToSend .offer(new ResponseThrowable(new StatusRuntimeException(Status.FAILED_PRECONDITION))); when(timeProvider.currentTimeNanos()).thenReturn(CURRENT_TIME_NANOS); - ScheduledFuture scheduledFuture = mock(ScheduledFuture.class); + TestScheduledFuture scheduledFuture = new TestScheduledFuture<>(); doReturn(scheduledFuture).when(timeService) .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); provider.refreshCertificate(); @@ -432,7 +432,7 @@ public void getCertificate_withError_withExistingExpiredCert() @Test public void getCertificate_retriesWithErrors() throws IOException, CertificateException, OperatorCreationException, - NoSuchAlgorithmException, InterruptedException, ExecutionException, TimeoutException { + NoSuchAlgorithmException { oauth2Tokens.offer(TEST_STS_TOKEN + "0"); oauth2Tokens.offer(TEST_STS_TOKEN + "1"); oauth2Tokens.offer(TEST_STS_TOKEN + "2"); @@ -445,10 +445,10 @@ public void getCertificate_retriesWithErrors() CommonTlsContextTestsUtil.getResourceContents(SERVER_1_PEM_FILE), CommonTlsContextTestsUtil.getResourceContents(CA_PEM_FILE)))); when(timeProvider.currentTimeNanos()).thenReturn(CURRENT_TIME_NANOS); - ScheduledFuture scheduledFuture = mock(ScheduledFuture.class); + TestScheduledFuture scheduledFuture = new TestScheduledFuture<>(); doReturn(scheduledFuture).when(timeService) .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); - ScheduledFuture scheduledFutureSleep = mock(ScheduledFuture.class); + TestScheduledFuture scheduledFutureSleep = new TestScheduledFuture<>(); doReturn(scheduledFutureSleep).when(timeService) .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.NANOSECONDS)); provider.refreshCertificate(); @@ -465,7 +465,7 @@ public void getCertificate_retriesWithErrors() @Test public void getCertificate_retriesWithTimeouts() throws IOException, CertificateException, OperatorCreationException, - NoSuchAlgorithmException, InterruptedException, ExecutionException, TimeoutException { + NoSuchAlgorithmException { oauth2Tokens.offer(TEST_STS_TOKEN + "0"); oauth2Tokens.offer(TEST_STS_TOKEN + "1"); oauth2Tokens.offer(TEST_STS_TOKEN + "2"); @@ -478,10 +478,10 @@ public void getCertificate_retriesWithTimeouts() CommonTlsContextTestsUtil.getResourceContents(SERVER_1_PEM_FILE), CommonTlsContextTestsUtil.getResourceContents(CA_PEM_FILE)))); when(timeProvider.currentTimeNanos()).thenReturn(CURRENT_TIME_NANOS); - ScheduledFuture scheduledFuture = mock(ScheduledFuture.class); + TestScheduledFuture scheduledFuture = new TestScheduledFuture<>(); doReturn(scheduledFuture).when(timeService) .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.SECONDS)); - ScheduledFuture scheduledFutureSleep = mock(ScheduledFuture.class); + TestScheduledFuture scheduledFutureSleep = new TestScheduledFuture<>(); doReturn(scheduledFutureSleep).when(timeService) .schedule(any(Runnable.class), any(Long.TYPE), eq(TimeUnit.NANOSECONDS)); provider.refreshCertificate(); @@ -495,14 +495,15 @@ public void getCertificate_retriesWithTimeouts() verifyReceivedMetadataValues(4); } - private void verifyRetriesWithBackoff(ScheduledFuture scheduledFutureSleep, int numOfRetries) - throws InterruptedException, ExecutionException, TimeoutException { + private void verifyRetriesWithBackoff( + TestScheduledFuture scheduledFutureSleep, int numOfRetries) { for (int i = 0; i < numOfRetries; i++) { long delayValue = DELAY_VALUES[i]; verify(timeService, times(1)).schedule(any(Runnable.class), eq(delayValue), eq(TimeUnit.NANOSECONDS)); - verify(scheduledFutureSleep, times(1)).get(eq(delayValue), eq(TimeUnit.NANOSECONDS)); + assertThat(scheduledFutureSleep.calls.get(i).timeout).isEqualTo(delayValue); + assertThat(scheduledFutureSleep.calls.get(i).unit).isEqualTo(TimeUnit.NANOSECONDS); } } @@ -536,4 +537,55 @@ private void verifyReceivedMetadataValues(int count) { assertThat(receivedZoneValues.poll()).isEqualTo("us-west2-a"); } } + + private static class TestScheduledFuture implements ScheduledFuture { + + static class Record { + long timeout; + TimeUnit unit; + + Record(long timeout, TimeUnit unit) { + this.timeout = timeout; + this.unit = unit; + } + } + + ArrayList calls = new ArrayList<>(); + + @Override + public long getDelay(TimeUnit unit) { + return 0; + } + + @Override + public int compareTo(Delayed o) { + return 0; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return false; + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean isDone() { + return false; + } + + @Override + public V get() { + return null; + } + + @Override + public V get(long timeout, TimeUnit unit) { + calls.add(new Record(timeout, unit)); + return null; + } + } } From b8fe968c88fc50d65581fc1892c00b50b809c19e Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Fri, 21 Aug 2020 11:19:08 -0700 Subject: [PATCH 74/88] xds: not to use insecure DSA crypto Although DSA is only used in tests so it's totally no security concern, it's annoying we need some workaround for internal checks to import. So removing the usage. --- .../certprovider/CommonCertProviderTestUtils.java | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/CommonCertProviderTestUtils.java b/xds/src/test/java/io/grpc/xds/internal/certprovider/CommonCertProviderTestUtils.java index 3a056c209fe..fe584612e27 100644 --- a/xds/src/test/java/io/grpc/xds/internal/certprovider/CommonCertProviderTestUtils.java +++ b/xds/src/test/java/io/grpc/xds/internal/certprovider/CommonCertProviderTestUtils.java @@ -38,7 +38,6 @@ import java.security.PrivateKey; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; -import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.util.logging.Level; import java.util.logging.Logger; @@ -103,19 +102,7 @@ static PrivateKey getPrivateKey(String resourceName) byte[] encodedKey = new byte[encodedKeyBuf.readableBytes()]; encodedKeyBuf.readBytes(encodedKey).release(); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(encodedKey); - try { - return KeyFactory.getInstance("RSA").generatePrivate(spec); - } catch (InvalidKeySpecException ignore) { - try { - return KeyFactory.getInstance("DSA").generatePrivate(spec); - } catch (InvalidKeySpecException ignore2) { - try { - return KeyFactory.getInstance("EC").generatePrivate(spec); - } catch (InvalidKeySpecException e) { - throw new InvalidKeySpecException("Neither RSA, DSA nor EC worked", e); - } - } - } + return KeyFactory.getInstance("RSA").generatePrivate(spec); } static ByteBuf readPrivateKey(InputStream in) throws KeyException { From 50d8be70b05af6fae1d9556bdf26e4292db2de20 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Fri, 21 Aug 2020 12:44:14 -0700 Subject: [PATCH 75/88] core: use DelayedClientCall for awaiting configSelector in ManangedChannelImpl ManagedChannelImpl.newCall() will return a DelayedClientCall until the name resolver updates the configSelector reference. The configSelector follows the same service config error handling rules. Made the following assumption: If there is no service config in resolution result, then there must be no config selector in the resolution result. Actually we ignore any config selector in the resolution result if there is no service config. --- .../java/io/grpc/internal/ClientCallImpl.java | 5 +- .../io/grpc/internal/DelayedClientCall.java | 24 +-- .../io/grpc/internal/ManagedChannelImpl.java | 170 ++++++++++++++++-- .../java/io/grpc/internal/OobChannel.java | 2 +- .../io/grpc/internal/SubchannelChannel.java | 9 +- .../io/grpc/internal/ClientCallImplTest.java | 54 +++--- .../ManagedChannelImplIdlenessTest.java | 37 ++-- .../grpc/internal/ManagedChannelImplTest.java | 60 ++++++- .../ServiceConfigErrorHandlingTest.java | 60 ++++++- 9 files changed, 353 insertions(+), 68 deletions(-) diff --git a/core/src/main/java/io/grpc/internal/ClientCallImpl.java b/core/src/main/java/io/grpc/internal/ClientCallImpl.java index 2c0321b27a5..45029e9e649 100644 --- a/core/src/main/java/io/grpc/internal/ClientCallImpl.java +++ b/core/src/main/java/io/grpc/internal/ClientCallImpl.java @@ -40,6 +40,7 @@ import io.grpc.Context.CancellationListener; import io.grpc.Deadline; import io.grpc.DecompressorRegistry; +import io.grpc.InternalConfigSelector; import io.grpc.InternalDecompressorRegistry; import io.grpc.Metadata; import io.grpc.MethodDescriptor; @@ -101,7 +102,8 @@ final class ClientCallImpl extends ClientCall { MethodDescriptor method, Executor executor, CallOptions callOptions, ClientStreamProvider clientStreamProvider, ScheduledExecutorService deadlineCancellationExecutor, - CallTracer channelCallsTracer) { + CallTracer channelCallsTracer, + InternalConfigSelector configSelector) { this.method = method; // TODO(carl-mastrangelo): consider moving this construction to ManagedChannelImpl. this.tag = PerfMark.createTag(method.getFullMethodName(), System.identityHashCode(this)); @@ -218,6 +220,7 @@ private void startInternal(final Listener observer, Metadata headers) { executeCloseObserverInContext(observer, statusFromCancelled(context)); return; } + // TODO(zdapeng): configSelector.selectConfig() final String compressorName = callOptions.getCompressor(); Compressor compressor; if (compressorName != null) { diff --git a/core/src/main/java/io/grpc/internal/DelayedClientCall.java b/core/src/main/java/io/grpc/internal/DelayedClientCall.java index fd3a4ccac66..c3d3823ed11 100644 --- a/core/src/main/java/io/grpc/internal/DelayedClientCall.java +++ b/core/src/main/java/io/grpc/internal/DelayedClientCall.java @@ -47,7 +47,7 @@ * DelayedCall} may be internally altered by different threads, thus internal synchronization is * necessary. */ -final class DelayedClientCall extends ClientCall { +class DelayedClientCall extends ClientCall { private static final Logger logger = Logger.getLogger(DelayedClientCall.class.getName()); /** * A timer to monitor the initial deadline. The timer must be cancelled on transition to the real @@ -153,7 +153,7 @@ final void setCall(ClientCall call) { } @Override - public void start(Listener listener, final Metadata headers) { + public final void start(Listener listener, final Metadata headers) { checkState(this.listener == null, "already started"); Status savedError; boolean savedPassThrough; @@ -185,7 +185,7 @@ public void run() { // When this method returns, passThrough is guaranteed to be true @Override - public void cancel(@Nullable final String message, @Nullable final Throwable cause) { + public final void cancel(@Nullable final String message, @Nullable final Throwable cause) { Status status = Status.CANCELLED; if (message != null) { status = status.withDescription(message); @@ -231,6 +231,10 @@ public void run() { } drainPendingCalls(); } + callCancelled(); + } + + protected void callCancelled() { } private void delayOrExecute(Runnable runnable) { @@ -302,12 +306,12 @@ private void setRealCall(ClientCall realCall) { } @VisibleForTesting - ClientCall getRealCall() { + final ClientCall getRealCall() { return realCall; } @Override - public void sendMessage(final ReqT message) { + public final void sendMessage(final ReqT message) { if (passThrough) { realCall.sendMessage(message); } else { @@ -321,7 +325,7 @@ public void run() { } @Override - public void setMessageCompression(final boolean enable) { + public final void setMessageCompression(final boolean enable) { if (passThrough) { realCall.setMessageCompression(enable); } else { @@ -335,7 +339,7 @@ public void run() { } @Override - public void request(final int numMessages) { + public final void request(final int numMessages) { if (passThrough) { realCall.request(numMessages); } else { @@ -349,7 +353,7 @@ public void run() { } @Override - public void halfClose() { + public final void halfClose() { delayOrExecute(new Runnable() { @Override public void run() { @@ -359,7 +363,7 @@ public void run() { } @Override - public boolean isReady() { + public final boolean isReady() { if (passThrough) { return realCall.isReady(); } else { @@ -368,7 +372,7 @@ public boolean isReady() { } @Override - public Attributes getAttributes() { + public final Attributes getAttributes() { ClientCall savedRealCall; synchronized (this) { savedRealCall = realCall; diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java index 015b82917df..6d0160b1abd 100644 --- a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java +++ b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java @@ -49,6 +49,7 @@ import io.grpc.InternalChannelz; import io.grpc.InternalChannelz.ChannelStats; import io.grpc.InternalChannelz.ChannelTrace; +import io.grpc.InternalConfigSelector; import io.grpc.InternalInstrumented; import io.grpc.InternalLogId; import io.grpc.InternalWithLogId; @@ -81,6 +82,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -94,6 +96,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; @@ -131,6 +134,13 @@ final class ManagedChannelImpl extends ManagedChannel implements private static final ManagedChannelServiceConfig EMPTY_SERVICE_CONFIG = ManagedChannelServiceConfig.empty(); + private static final InternalConfigSelector INITIAL_PENDING_SELECTOR = + new InternalConfigSelector() { + @Override + public Result selectConfig(PickSubchannelArgs args) { + throw new IllegalStateException("Resolution is pending"); + } + }; private final InternalLogId logId; private final String target; @@ -207,6 +217,11 @@ public void uncaughtException(Thread t, Throwable e) { // switch to a ConcurrentHashMap. private final Set subchannels = new HashSet<>(16, .75f); + // Must be accessed from syncContext + @Nullable + private Collection> pendingCalls; + private final Object pendingCallsInUseObject = new Object(); + // Must be mutated from syncContext private final Set oobChannels = new HashSet<>(1, .75f); @@ -252,6 +267,11 @@ public void uncaughtException(Thread t, Throwable e) { // Must be mutated and read from constructor or syncContext // used for channel tracing when value changed private ManagedChannelServiceConfig lastServiceConfig = EMPTY_SERVICE_CONFIG; + // Reference to null if no config selector is available from resolution result + // Reference must be set() from syncContext + private final AtomicReference configSelector = + new AtomicReference<>(INITIAL_PENDING_SELECTOR); + @Nullable private final ManagedChannelServiceConfig defaultServiceConfig; // Must be mutated and read from constructor or syncContext @@ -688,6 +708,8 @@ public CallTracer create() { // May only be called in constructor or syncContext private void handleServiceConfigUpdate() { serviceConfigUpdated = true; + // TODO(zdapeng): get rid of serviceConfigInterceptor and do handle update together with + // configSelector. serviceConfigInterceptor.handleUpdate(lastServiceConfig); } @@ -736,6 +758,11 @@ static NameResolver getNameResolver(String target, NameResolver.Factory nameReso target, uriSyntaxErrors.length() > 0 ? " (" + uriSyntaxErrors + ")" : "")); } + @VisibleForTesting + InternalConfigSelector getConfigSelector() { + return configSelector.get(); + } + /** * Initiates an orderly shutdown in which preexisting calls continue but new calls are immediately * cancelled. @@ -889,24 +916,114 @@ private RealChannel(String authority) { } @Override - public ClientCall newCall(MethodDescriptor method, - CallOptions callOptions) { + public ClientCall newCall( + MethodDescriptor method, CallOptions callOptions) { + if (configSelector.get() != INITIAL_PENDING_SELECTOR) { + return newClientCall(method, callOptions); + } + syncContext.execute(new Runnable() { + @Override + public void run() { + exitIdleMode(); + } + }); + if (configSelector.get() != INITIAL_PENDING_SELECTOR) { + // This is an optimization for the case (typically with InProcessTransport) when name + // resolution result is immediately available at this point. Otherwise, some users' + // tests might observe slight behavior difference from earlier grpc versions. + return newClientCall(method, callOptions); + } + Context context = Context.current(); + final PendingCall pendingCall = new PendingCall<>(context, method, callOptions); + syncContext.execute(new Runnable() { + @Override + public void run() { + if (configSelector.get() == INITIAL_PENDING_SELECTOR) { + if (pendingCalls == null) { + pendingCalls = new LinkedHashSet<>(); + inUseStateAggregator.updateObjectInUse(pendingCallsInUseObject, true); + } + pendingCalls.add(pendingCall); + } else { + pendingCall.reprocess(); + } + } + }); + return pendingCall; + } + + @Override + public String authority() { + return authority; + } + + private final class PendingCall extends DelayedClientCall { + final Context context; + final MethodDescriptor method; + final CallOptions callOptions; + + PendingCall( + Context context, MethodDescriptor method, CallOptions callOptions) { + super(getCallExecutor(callOptions), scheduledExecutor, callOptions.getDeadline()); + this.context = context; + this.method = method; + this.callOptions = callOptions; + } + + /** Called when it's ready to create a real call and reprocess the pending call. */ + void reprocess() { + getCallExecutor(callOptions).execute( + new Runnable() { + @Override + public void run() { + ClientCall realCall; + Context previous = context.attach(); + try { + realCall = newClientCall(method, callOptions); + } finally { + context.detach(previous); + } + setCall(realCall); + syncContext.execute(new PendingCallRemoval()); + } + } + ); + } + + @Override + protected void callCancelled() { + super.callCancelled(); + syncContext.execute(new PendingCallRemoval()); + } + + final class PendingCallRemoval implements Runnable { + @Override + public void run() { + if (pendingCalls != null) { + pendingCalls.remove(PendingCall.this); + if (pendingCalls.isEmpty()) { + inUseStateAggregator.updateObjectInUse(pendingCallsInUseObject, false); + pendingCalls = null; + } + } + } + } + } + + private ClientCall newClientCall( + MethodDescriptor method, CallOptions callOptions) { return new ClientCallImpl<>( method, getCallExecutor(callOptions), callOptions, transportProvider, terminated ? null : transportFactory.getScheduledExecutorService(), - channelCallTracer) + channelCallTracer, + configSelector.get()) .setFullStreamDecompression(fullStreamDecompression) .setDecompressorRegistry(decompressorRegistry) .setCompressorRegistry(compressorRegistry); } - - @Override - public String authority() { - return authority; - } } /** @@ -1393,9 +1510,11 @@ public void run() { nameResolverBackoffPolicy = null; ConfigOrError configOrError = resolutionResult.getServiceConfig(); + InternalConfigSelector resolvedConfigSelector = + resolutionResult.getAttributes().get(InternalConfigSelector.KEY); ManagedChannelServiceConfig validServiceConfig = configOrError != null && configOrError.getConfig() != null - ? (ManagedChannelServiceConfig) resolutionResult.getServiceConfig().getConfig() + ? (ManagedChannelServiceConfig) configOrError.getConfig() : null; Status serviceConfigError = configOrError != null ? configOrError.getError() : null; @@ -1408,13 +1527,21 @@ public void run() { } effectiveServiceConfig = defaultServiceConfig == null ? EMPTY_SERVICE_CONFIG : defaultServiceConfig; + if (resolvedConfigSelector != null) { + channelLogger.log( + ChannelLogLevel.INFO, + "Config selector from name resolver discarded by channel settings"); + } + configSelector.set(null); } else { // Try to use config if returned from name resolver // Otherwise, try to use the default config if available if (validServiceConfig != null) { effectiveServiceConfig = validServiceConfig; + configSelector.set(resolvedConfigSelector); } else if (defaultServiceConfig != null) { effectiveServiceConfig = defaultServiceConfig; + configSelector.set(null); channelLogger.log( ChannelLogLevel.INFO, "Received no service config, using default service config"); @@ -1431,6 +1558,7 @@ public void run() { } } else { effectiveServiceConfig = EMPTY_SERVICE_CONFIG; + configSelector.set(null); } if (!effectiveServiceConfig.equals(lastServiceConfig)) { channelLogger.log( @@ -1452,14 +1580,17 @@ public void run() { re); } } + drainPendingCalls(); Attributes effectiveAttrs = resolutionResult.getAttributes(); // Call LB only if it's not shutdown. If LB is shutdown, lbHelper won't match. if (NameResolverListener.this.helper == ManagedChannelImpl.this.lbHelper) { + Attributes.Builder attrBuilder = + effectiveAttrs.toBuilder().discard(InternalConfigSelector.KEY); Map healthCheckingConfig = effectiveServiceConfig.getHealthCheckingConfig(); if (healthCheckingConfig != null) { - effectiveAttrs = effectiveAttrs.toBuilder() + attrBuilder .set(LoadBalancer.ATTR_HEALTH_CHECKING_CONFIG, healthCheckingConfig) .build(); } @@ -1467,7 +1598,7 @@ public void run() { Status handleResult = helper.lb.tryHandleResolvedAddresses( ResolvedAddresses.newBuilder() .setAddresses(servers) - .setAttributes(effectiveAttrs) + .setAttributes(attrBuilder.build()) .setLoadBalancingPolicyConfig(effectiveServiceConfig.getLoadBalancingConfig()) .build()); @@ -1497,6 +1628,10 @@ public void run() { private void handleErrorInSyncContext(Status error) { logger.log(Level.WARNING, "[{0}] Failed to resolve name. status={1}", new Object[] {getLogId(), error}); + if (configSelector.get() == INITIAL_PENDING_SELECTOR) { + configSelector.set(null); + drainPendingCalls(); + } if (lastResolutionState != ResolutionState.ERROR) { channelLogger.log(ChannelLogLevel.WARNING, "Failed to resolve name: {0}", error); lastResolutionState = ResolutionState.ERROR; @@ -1511,6 +1646,16 @@ private void handleErrorInSyncContext(Status error) { scheduleExponentialBackOffInSyncContext(); } + // Must run in SynchronizationContext. + private void drainPendingCalls() { + if (pendingCalls == null) { + return; + } + for (RealChannel.PendingCall pendingCall : pendingCalls) { + pendingCall.reprocess(); + } + } + private void scheduleExponentialBackOffInSyncContext() { if (scheduledNameResolverRefresh != null && scheduledNameResolverRefresh.isPending()) { // The name resolver may invoke onError multiple times, but we only want to @@ -1737,7 +1882,8 @@ public Channel asChannel() { return new SubchannelChannel( subchannel, balancerRpcExecutorHolder.getExecutor(), transportFactory.getScheduledExecutorService(), - callTracerFactory.create()); + callTracerFactory.create(), + new AtomicReference(null)); } @Override diff --git a/core/src/main/java/io/grpc/internal/OobChannel.java b/core/src/main/java/io/grpc/internal/OobChannel.java index c746336d58c..5e0dd12b2ae 100644 --- a/core/src/main/java/io/grpc/internal/OobChannel.java +++ b/core/src/main/java/io/grpc/internal/OobChannel.java @@ -202,7 +202,7 @@ public ClientCall newCall( MethodDescriptor methodDescriptor, CallOptions callOptions) { return new ClientCallImpl<>(methodDescriptor, callOptions.getExecutor() == null ? executor : callOptions.getExecutor(), - callOptions, transportProvider, deadlineCancellationExecutor, channelCallsTracer); + callOptions, transportProvider, deadlineCancellationExecutor, channelCallsTracer, null); } @Override diff --git a/core/src/main/java/io/grpc/internal/SubchannelChannel.java b/core/src/main/java/io/grpc/internal/SubchannelChannel.java index d55c403ef03..6c316e4f185 100644 --- a/core/src/main/java/io/grpc/internal/SubchannelChannel.java +++ b/core/src/main/java/io/grpc/internal/SubchannelChannel.java @@ -23,6 +23,7 @@ import io.grpc.Channel; import io.grpc.ClientCall; import io.grpc.Context; +import io.grpc.InternalConfigSelector; import io.grpc.Metadata; import io.grpc.MethodDescriptor; import io.grpc.Status; @@ -30,6 +31,7 @@ import io.grpc.internal.ClientStreamListener.RpcProgress; import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.atomic.AtomicReference; final class SubchannelChannel extends Channel { @VisibleForTesting @@ -45,6 +47,7 @@ final class SubchannelChannel extends Channel { private final Executor executor; private final ScheduledExecutorService deadlineCancellationExecutor; private final CallTracer callsTracer; + private final AtomicReference configSelector; private final ClientStreamProvider transportProvider = new ClientStreamProvider() { @Override @@ -65,12 +68,14 @@ public ClientStream newStream(MethodDescriptor method, SubchannelChannel( InternalSubchannel subchannel, Executor executor, - ScheduledExecutorService deadlineCancellationExecutor, CallTracer callsTracer) { + ScheduledExecutorService deadlineCancellationExecutor, CallTracer callsTracer, + AtomicReference configSelector) { this.subchannel = checkNotNull(subchannel, "subchannel"); this.executor = checkNotNull(executor, "executor"); this.deadlineCancellationExecutor = checkNotNull(deadlineCancellationExecutor, "deadlineCancellationExecutor"); this.callsTracer = checkNotNull(callsTracer, "callsTracer"); + this.configSelector = checkNotNull(configSelector, "configSelector"); } @Override @@ -106,7 +111,7 @@ public void sendMessage(RequestT message) {} return new ClientCallImpl<>(methodDescriptor, effectiveExecutor, callOptions.withOption(GrpcUtil.CALL_OPTIONS_RPC_OWNED_BY_BALANCER, Boolean.TRUE), - transportProvider, deadlineCancellationExecutor, callsTracer); + transportProvider, deadlineCancellationExecutor, callsTracer, configSelector.get()); } @Override diff --git a/core/src/test/java/io/grpc/internal/ClientCallImplTest.java b/core/src/test/java/io/grpc/internal/ClientCallImplTest.java index ac49f9e3c8c..24c7d20db5a 100644 --- a/core/src/test/java/io/grpc/internal/ClientCallImplTest.java +++ b/core/src/test/java/io/grpc/internal/ClientCallImplTest.java @@ -50,6 +50,7 @@ import io.grpc.Deadline; import io.grpc.Decompressor; import io.grpc.DecompressorRegistry; +import io.grpc.InternalConfigSelector; import io.grpc.Metadata; import io.grpc.MethodDescriptor; import io.grpc.MethodDescriptor.MethodType; @@ -118,6 +119,9 @@ public class ClientCallImplTest { @Mock private ClientStream stream; + @Mock + private InternalConfigSelector configSelector; + @Mock private ClientCall.Listener callListener; @@ -165,7 +169,7 @@ public void statusPropagatedFromStreamToCallListener() { baseCallOptions, clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer); + channelCallTracer, configSelector); call.start(callListener, new Metadata()); verify(stream).start(listenerArgumentCaptor.capture()); final ClientStreamListener streamListener = listenerArgumentCaptor.getValue(); @@ -186,7 +190,7 @@ public void exceptionInOnMessageTakesPrecedenceOverServer() { baseCallOptions, clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer); + channelCallTracer, configSelector); call.start(callListener, new Metadata()); verify(stream).start(listenerArgumentCaptor.capture()); final ClientStreamListener streamListener = listenerArgumentCaptor.getValue(); @@ -223,7 +227,7 @@ public void exceptionInOnHeadersTakesPrecedenceOverServer() { baseCallOptions, clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer); + channelCallTracer, configSelector); call.start(callListener, new Metadata()); verify(stream).start(listenerArgumentCaptor.capture()); final ClientStreamListener streamListener = listenerArgumentCaptor.getValue(); @@ -267,7 +271,7 @@ class PointOfNoReturnExecutor implements Executor { baseCallOptions, clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer); + channelCallTracer, configSelector); callListener = new NoopClientCall.NoopClientCallListener() { private final RuntimeException failure = new RuntimeException("bad"); @@ -302,7 +306,7 @@ public void exceptionInOnReadyTakesPrecedenceOverServer() { baseCallOptions, clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer); + channelCallTracer, configSelector); call.start(callListener, new Metadata()); verify(stream).start(listenerArgumentCaptor.capture()); final ClientStreamListener streamListener = listenerArgumentCaptor.getValue(); @@ -336,7 +340,7 @@ public void advertisedEncodingsAreSent() { baseCallOptions, clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer) + channelCallTracer, configSelector) .setDecompressorRegistry(decompressorRegistry); call.start(callListener, new Metadata()); @@ -360,7 +364,7 @@ public void authorityPropagatedToStream() { baseCallOptions.withAuthority("overridden-authority"), clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer) + channelCallTracer, configSelector) .setDecompressorRegistry(decompressorRegistry); call.start(callListener, new Metadata()); @@ -376,7 +380,7 @@ public void callOptionsPropagatedToTransport() { callOptions, clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer) + channelCallTracer, configSelector) .setDecompressorRegistry(decompressorRegistry); final Metadata metadata = new Metadata(); @@ -395,7 +399,7 @@ public void authorityNotPropagatedToStream() { baseCallOptions, clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer) + channelCallTracer, configSelector) .setDecompressorRegistry(decompressorRegistry); call.start(callListener, new Metadata()); @@ -565,7 +569,7 @@ public void callerContextPropagatedToListener() throws Exception { baseCallOptions, clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer) + channelCallTracer, configSelector) .setDecompressorRegistry(decompressorRegistry); context.detach(previous); @@ -642,7 +646,7 @@ public void contextCancellationCancelsStream() throws Exception { baseCallOptions, clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer) + channelCallTracer, configSelector) .setDecompressorRegistry(decompressorRegistry); cancellableContext.detach(previous); @@ -671,7 +675,7 @@ public void contextAlreadyCancelledNotifiesImmediately() throws Exception { baseCallOptions, clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer) + channelCallTracer, configSelector) .setDecompressorRegistry(decompressorRegistry); cancellableContext.detach(previous); @@ -715,7 +719,7 @@ public void deadlineExceededBeforeCallStarted() { callOptions, clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer) + channelCallTracer, configSelector) .setDecompressorRegistry(decompressorRegistry); call.start(callListener, new Metadata()); verify(clientStreamProvider, never()) @@ -743,7 +747,7 @@ public void contextDeadlineShouldBePropagatedToStream() { baseCallOptions, clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer); + channelCallTracer, configSelector); call.start(callListener, new Metadata()); context.detach(origContext); @@ -767,7 +771,7 @@ public void contextDeadlineShouldOverrideLargerCallOptionsDeadline() { callOpts, clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer); + channelCallTracer, configSelector); call.start(callListener, new Metadata()); context.detach(origContext); @@ -791,7 +795,7 @@ public void contextDeadlineShouldNotOverrideSmallerCallOptionsDeadline() { callOpts, clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer); + channelCallTracer, configSelector); call.start(callListener, new Metadata()); context.detach(origContext); @@ -811,7 +815,7 @@ public void callOptionsDeadlineShouldBePropagatedToStream() { callOpts, clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer); + channelCallTracer, configSelector); call.start(callListener, new Metadata()); ArgumentCaptor deadlineCaptor = ArgumentCaptor.forClass(Deadline.class); @@ -828,7 +832,7 @@ public void noDeadlineShouldBePropagatedToStream() { baseCallOptions, clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer); + channelCallTracer, configSelector); call.start(callListener, new Metadata()); verify(stream, never()).setDeadline(any(Deadline.class)); @@ -846,7 +850,7 @@ public void expiredDeadlineCancelsStream_CallOptions() { Deadline.after(1, TimeUnit.SECONDS, fakeClock.getDeadlineTicker())), clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer); + channelCallTracer, configSelector); call.start(callListener, new Metadata()); @@ -884,7 +888,7 @@ public void expiredDeadlineCancelsStream_Context() { baseCallOptions, clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer); + channelCallTracer, configSelector); context.detach(origContext); @@ -914,7 +918,7 @@ public void streamCancelAbortsDeadlineTimer() { baseCallOptions.withDeadline(Deadline.after(1, TimeUnit.SECONDS)), clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer); + channelCallTracer, configSelector); call.start(callListener, new Metadata()); call.cancel("canceled", null); @@ -938,7 +942,7 @@ public void timeoutShouldNotBeSet() { baseCallOptions, clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer); + channelCallTracer, configSelector); Metadata headers = new Metadata(); @@ -955,7 +959,7 @@ public void cancelInOnMessageShouldInvokeStreamCancel() throws Exception { baseCallOptions, clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer); + channelCallTracer, configSelector); final Exception cause = new Exception(); ClientCall.Listener callListener = new ClientCall.Listener() { @@ -992,7 +996,7 @@ public void startAddsMaxSize() { callOptions, clientStreamProvider, deadlineCancellationExecutor, - channelCallTracer) + channelCallTracer, configSelector) .setDecompressorRegistry(decompressorRegistry); call.start(callListener, new Metadata()); @@ -1005,7 +1009,7 @@ public void startAddsMaxSize() { public void getAttributes() { ClientCallImpl call = new ClientCallImpl<>( method, MoreExecutors.directExecutor(), baseCallOptions, clientStreamProvider, - deadlineCancellationExecutor, channelCallTracer); + deadlineCancellationExecutor, channelCallTracer, configSelector); Attributes attrs = Attributes.newBuilder().set( Attributes.Key.create("fake key"), "fake value").build(); when(stream.getAttributes()).thenReturn(attrs); diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java index 4439383c0e0..2295dcb0ff2 100644 --- a/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java +++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java @@ -224,16 +224,7 @@ public void newCallExitsIdleness() throws Exception { call.start(mockCallListener, new Metadata()); verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class)); - - verify(mockNameResolver).start(nameResolverListenerCaptor.capture()); - // Simulate new address resolved to make sure the LoadBalancer is correctly linked to - // the NameResolver. - ResolutionResult resolutionResult = - ResolutionResult.newBuilder() - .setAddresses(servers) - .setAttributes(Attributes.EMPTY) - .build(); - nameResolverListenerCaptor.getValue().onResult(resolutionResult); + deliverResolutionResult(); ArgumentCaptor resolvedAddressCaptor = ArgumentCaptor.forClass(ResolvedAddresses.class); @@ -251,7 +242,9 @@ public void newCallRefreshesIdlenessTimer() throws Exception { // Verify that we have exited the idle mode verify(mockLoadBalancerProvider).newLoadBalancer(any(Helper.class)); + deliverResolutionResult(); assertFalse(channel.inUseStateAggregator.isInUse()); + verify(mockCallListener).onClose(any(Status.class), any(Metadata.class)); // Move closer to idleness, but not yet. timer.forwardTime(IDLE_TIMEOUT_SECONDS - 1, TimeUnit.SECONDS); @@ -274,9 +267,7 @@ public void newCallRefreshesIdlenessTimer() throws Exception { verify(mockLoadBalancer).shutdown(); assertFalse(channel.inUseStateAggregator.isInUse()); - // Drain the app executor, which runs the call listeners - verify(mockCallListener, never()).onClose(any(Status.class), any(Metadata.class)); - assertEquals(2, executor.runDueTasks()); + executor.runDueTasks(); verify(mockCallListener, times(2)).onClose(any(Status.class), any(Metadata.class)); } @@ -284,6 +275,7 @@ public void newCallRefreshesIdlenessTimer() throws Exception { public void delayedTransportHoldsOffIdleness() throws Exception { ClientCall call = channel.newCall(method, CallOptions.DEFAULT); call.start(mockCallListener, new Metadata()); + deliverResolutionResult(); assertTrue(channel.inUseStateAggregator.isInUse()); // As long as the delayed transport is in-use (by the pending RPC), the channel won't go idle. @@ -313,6 +305,7 @@ public void realTransportsHoldsOffIdleness() throws Exception { // Verify that we have exited the idle mode ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(null); verify(mockLoadBalancerProvider).newLoadBalancer(helperCaptor.capture()); + deliverResolutionResult(); Helper helper = helperCaptor.getValue(); assertTrue(channel.inUseStateAggregator.isInUse()); @@ -355,6 +348,7 @@ public void updateSubchannelAddresses_newAddressConnects() { call.start(mockCallListener, new Metadata()); // Create LB ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(null); verify(mockLoadBalancerProvider).newLoadBalancer(helperCaptor.capture()); + deliverResolutionResult(); Helper helper = helperCaptor.getValue(); Subchannel subchannel = createSubchannelSafely(helper, servers.get(0), Attributes.EMPTY); @@ -378,6 +372,7 @@ public void updateSubchannelAddresses_existingAddressDoesNotConnect() { call.start(mockCallListener, new Metadata()); // Create LB ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(null); verify(mockLoadBalancerProvider).newLoadBalancer(helperCaptor.capture()); + deliverResolutionResult(); Helper helper = helperCaptor.getValue(); Subchannel subchannel = createSubchannelSafely(helper, servers.get(0), Attributes.EMPTY); @@ -403,6 +398,7 @@ public void oobTransportDoesNotAffectIdleness() { ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(null); verify(mockLoadBalancerProvider).newLoadBalancer(helperCaptor.capture()); Helper helper = helperCaptor.getValue(); + deliverResolutionResult(); // Fail the RPC SubchannelPicker failingPicker = mock(SubchannelPicker.class); @@ -450,6 +446,7 @@ public void updateOobChannelAddresses_newAddressConnects() { call.start(mockCallListener, new Metadata()); // Create LB ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(null); verify(mockLoadBalancerProvider).newLoadBalancer(helperCaptor.capture()); + deliverResolutionResult(); Helper helper = helperCaptor.getValue(); ManagedChannel oobChannel = helper.createOobChannel(servers.get(0), "localhost"); @@ -474,6 +471,7 @@ public void updateOobChannelAddresses_existingAddressDoesNotConnect() { ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(null); verify(mockLoadBalancerProvider).newLoadBalancer(helperCaptor.capture()); Helper helper = helperCaptor.getValue(); + deliverResolutionResult(); ManagedChannel oobChannel = helper.createOobChannel(servers.get(0), "localhost"); oobChannel.newCall(method, CallOptions.DEFAULT).start(mockCallListener, new Metadata()); @@ -532,6 +530,19 @@ public void run() { return resultCapture.get(); } + private void deliverResolutionResult() { + verify(mockNameResolver).start(nameResolverListenerCaptor.capture()); + // Simulate new address resolved to make sure the LoadBalancer is correctly linked to + // the NameResolver. + ResolutionResult resolutionResult = + ResolutionResult.newBuilder() + .setAddresses(servers) + .setAttributes(Attributes.EMPTY) + .build(); + nameResolverListenerCaptor.getValue().onResult(resolutionResult); + executor.runDueTasks(); + } + private static void requestConnectionSafely(Helper helper, final Subchannel subchannel) { helper.getSynchronizationContext().execute( new Runnable() { diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java index 110392117ef..3650911f142 100644 --- a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java +++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java @@ -76,6 +76,7 @@ import io.grpc.InternalChannelz; import io.grpc.InternalChannelz.ChannelStats; import io.grpc.InternalChannelz.ChannelTrace; +import io.grpc.InternalConfigSelector; import io.grpc.InternalInstrumented; import io.grpc.LoadBalancer; import io.grpc.LoadBalancer.CreateSubchannelArgs; @@ -482,6 +483,51 @@ public void immediateDeadlineExceeded() { assertSame(Status.DEADLINE_EXCEEDED.getCode(), status.getCode()); } + @Test + public void startCallBeforeNameResolution() { + channel = new ManagedChannelImpl( + channelBuilder, mockTransportFactory, new FakeBackoffPolicyProvider(), + balancerRpcExecutorPool, timer.getStopwatchSupplier(), + Collections.emptyList(), timer.getTimeProvider()); + Metadata headers = new Metadata(); + ClientStream mockStream = mock(ClientStream.class); + ClientCall call = channel.newCall(method, CallOptions.DEFAULT); + call.start(mockCallListener, headers); + + ArgumentCaptor helperCaptor = ArgumentCaptor.forClass(null); + verify(mockLoadBalancerProvider).newLoadBalancer(helperCaptor.capture()); + helper = helperCaptor.getValue(); + // Make the transport available + Subchannel subchannel = + createSubchannelSafely(helper, addressGroup, Attributes.EMPTY, subchannelStateListener); + verify(mockTransportFactory, never()) + .newClientTransport( + any(SocketAddress.class), any(ClientTransportOptions.class), any(ChannelLogger.class)); + requestConnectionSafely(helper, subchannel); + verify(mockTransportFactory) + .newClientTransport( + any(SocketAddress.class), any(ClientTransportOptions.class), any(ChannelLogger.class)); + MockClientTransportInfo transportInfo = transports.poll(); + ConnectionClientTransport mockTransport = transportInfo.transport; + ManagedClientTransport.Listener transportListener = transportInfo.listener; + when(mockTransport.newStream(same(method), same(headers), any(CallOptions.class))) + .thenReturn(mockStream); + transportListener.transportReady(); + when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))) + .thenReturn(PickResult.withSubchannel(subchannel)); + updateBalancingStateSafely(helper, READY, mockPicker); + executor.runDueTasks(); + + // TODO(zdapeng): verify CallOptions updated by config selector + verify(mockTransport).newStream(same(method), same(headers), any(CallOptions.class)); + verify(mockStream).start(streamListenerCaptor.capture()); + + // Clean up as much as possible to allow the channel to terminate. + shutdownSafely(helper, subchannel); + timer.forwardNanos( + TimeUnit.SECONDS.toNanos(ManagedChannelImpl.SUBCHANNEL_SHUTDOWN_DELAY_SECONDS)); + } + @Test public void shutdownWithNoTransportsEverCreated() { channelBuilder.nameResolverFactory( @@ -3687,6 +3733,10 @@ public void disableServiceConfigLookUp_noDefaultConfig() throws Exception { createManagedChannelServiceConfig(rawServiceConfig, null); nameResolverFactory.nextConfigOrError.set( ConfigOrError.fromConfig(managedChannelServiceConfig)); + nameResolverFactory.nextAttributes.set( + Attributes.newBuilder() + .set(InternalConfigSelector.KEY, mock(InternalConfigSelector.class)) + .build()); createChannel(); @@ -3694,6 +3744,7 @@ public void disableServiceConfigLookUp_noDefaultConfig() throws Exception { ArgumentCaptor.forClass(ResolvedAddresses.class); verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup); + assertThat(resultCaptor.getValue().getAttributes().get(InternalConfigSelector.KEY)).isNull(); verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); } finally { LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider); @@ -3720,6 +3771,10 @@ public void disableServiceConfigLookUp_withDefaultConfig() throws Exception { createManagedChannelServiceConfig(rawServiceConfig, null); nameResolverFactory.nextConfigOrError.set( ConfigOrError.fromConfig(managedChannelServiceConfig)); + nameResolverFactory.nextAttributes.set( + Attributes.newBuilder() + .set(InternalConfigSelector.KEY, mock(InternalConfigSelector.class)) + .build()); createChannel(); @@ -3727,6 +3782,7 @@ public void disableServiceConfigLookUp_withDefaultConfig() throws Exception { ArgumentCaptor.forClass(ResolvedAddresses.class); verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); assertThat(resultCaptor.getValue().getAddresses()).containsExactly(addressGroup); + assertThat(resultCaptor.getValue().getAttributes().get(InternalConfigSelector.KEY)).isNull(); verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); } finally { LoadBalancerRegistry.getDefaultRegistry().deregister(mockLoadBalancerProvider); @@ -4006,6 +4062,7 @@ private static final class FakeNameResolverFactory extends NameResolver.Factory final Status error; final ArrayList resolvers = new ArrayList<>(); final AtomicReference nextConfigOrError = new AtomicReference<>(); + final AtomicReference nextAttributes = new AtomicReference<>(Attributes.EMPTY); FakeNameResolverFactory( List expectedUris, @@ -4076,7 +4133,8 @@ void resolved() { } ResolutionResult.Builder builder = ResolutionResult.newBuilder() - .setAddresses(servers); + .setAddresses(servers) + .setAttributes(nextAttributes.get()); ConfigOrError configOrError = nextConfigOrError.get(); if (configOrError != null) { builder.setServiceConfig(configOrError); diff --git a/core/src/test/java/io/grpc/internal/ServiceConfigErrorHandlingTest.java b/core/src/test/java/io/grpc/internal/ServiceConfigErrorHandlingTest.java index 7e61213f861..b8790ad34dc 100644 --- a/core/src/test/java/io/grpc/internal/ServiceConfigErrorHandlingTest.java +++ b/core/src/test/java/io/grpc/internal/ServiceConfigErrorHandlingTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -36,6 +37,7 @@ import io.grpc.ConnectivityState; import io.grpc.EquivalentAddressGroup; import io.grpc.InternalChannelz; +import io.grpc.InternalConfigSelector; import io.grpc.LoadBalancer; import io.grpc.LoadBalancer.Helper; import io.grpc.LoadBalancer.ResolvedAddresses; @@ -449,6 +451,11 @@ public void invalidConfig_2ndResolution() throws Exception { Map rawServiceConfig = parseJson("{\"loadBalancingConfig\": [{\"mock_lb\": {\"check\": \"1st raw config\"}}]}"); nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig); + InternalConfigSelector configSelector = mock(InternalConfigSelector.class); + nameResolverFactory.nextAttributes.set( + Attributes.newBuilder() + .set(InternalConfigSelector.KEY, configSelector) + .build()); createChannel(); @@ -458,6 +465,7 @@ public void invalidConfig_2ndResolution() throws Exception { ResolvedAddresses resolvedAddresses = resultCaptor.getValue(); assertThat(resolvedAddresses.getAddresses()).containsExactly(addressGroup); assertThat(resolvedAddresses.getLoadBalancingPolicyConfig()).isEqualTo("1st raw config"); + assertThat(channel.getConfigSelector()).isSameInstanceAs(configSelector); verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); assertThat(channel.getState(false)).isNotEqualTo(ConnectivityState.TRANSIENT_FAILURE); @@ -471,12 +479,56 @@ public void invalidConfig_2ndResolution() throws Exception { verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); ResolvedAddresses newResolvedAddress = resultCaptor.getValue(); // should use previous service config because new service config is invalid. - assertThat(resolvedAddresses.getLoadBalancingPolicyConfig()).isEqualTo("1st raw config"); - assertThat(newResolvedAddress.getAttributes()).isNotEqualTo(Attributes.EMPTY); + assertThat(newResolvedAddress.getLoadBalancingPolicyConfig()).isEqualTo("1st raw config"); + assertThat(channel.getConfigSelector()).isSameInstanceAs(configSelector); verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); assertThat(channel.getState(false)).isEqualTo(ConnectivityState.IDLE); } + @Test + public void validConfig_thenNoConfig_withDefaultConfig() throws Exception { + FakeNameResolverFactory nameResolverFactory = + new FakeNameResolverFactory.Builder(expectedUri) + .setServers(ImmutableList.of(addressGroup)) + .build(); + channelBuilder.nameResolverFactory(nameResolverFactory); + Map defaultServiceConfig = + parseJson("{\"loadBalancingConfig\": [{\"mock_lb\": {\"check\": \"mate\"}}]}"); + channelBuilder.defaultServiceConfig(defaultServiceConfig); + + Map rawServiceConfig = + parseJson("{\"loadBalancingConfig\": [{\"mock_lb\": {\"check\": \"1st raw config\"}}]}"); + nameResolverFactory.nextRawServiceConfig.set(rawServiceConfig); + InternalConfigSelector configSelector = mock(InternalConfigSelector.class); + nameResolverFactory.nextAttributes.set( + Attributes.newBuilder() + .set(InternalConfigSelector.KEY, configSelector) + .build()); + + createChannel(); + + ArgumentCaptor resultCaptor = + ArgumentCaptor.forClass(ResolvedAddresses.class); + verify(mockLoadBalancer).handleResolvedAddresses(resultCaptor.capture()); + ResolvedAddresses resolvedAddresses = resultCaptor.getValue(); + assertThat(resolvedAddresses.getAddresses()).containsExactly(addressGroup); + // should use previous service config because new resolution result is no config. + assertThat(resolvedAddresses.getLoadBalancingPolicyConfig()).isEqualTo("1st raw config"); + assertThat(channel.getConfigSelector()).isSameInstanceAs(configSelector); + verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); + + // 2nd resolution lbConfig is no config + nameResolverFactory.nextRawServiceConfig.set(null); + nameResolverFactory.allResolved(); + + verify(mockLoadBalancer, times(2)).handleResolvedAddresses(resultCaptor.capture()); + ResolvedAddresses newResolvedAddress = resultCaptor.getValue(); + assertThat(newResolvedAddress.getLoadBalancingPolicyConfig()).isEqualTo("mate"); + assertThat(newResolvedAddress.getAttributes().get(InternalConfigSelector.KEY)) + .isNull(); + verify(mockLoadBalancer, never()).handleNameResolutionError(any(Status.class)); + } + private static final class ChannelBuilder extends AbstractManagedChannelImplBuilder { @@ -513,6 +565,7 @@ private static final class FakeNameResolverFactory extends NameResolver.Factory final boolean resolvedAtStart; final ArrayList resolvers = new ArrayList<>(); final AtomicReference> nextRawServiceConfig = new AtomicReference<>(); + final AtomicReference nextAttributes = new AtomicReference<>(Attributes.EMPTY); FakeNameResolverFactory( URI expectedUri, @@ -574,7 +627,8 @@ final class FakeNameResolver extends NameResolver { void resolved() { Map rawServiceConfig = nextRawServiceConfig.get(); - ResolutionResult.Builder builder = ResolutionResult.newBuilder().setAddresses(servers); + ResolutionResult.Builder builder = + ResolutionResult.newBuilder().setAddresses(servers).setAttributes(nextAttributes.get()); if (rawServiceConfig != null) { builder .setServiceConfig(serviceConfigParser.parseServiceConfig(rawServiceConfig)); From 80480e69efa0fcfb20244a871d13460d43176f19 Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Fri, 21 Aug 2020 12:52:51 -0700 Subject: [PATCH 76/88] xds: implement cert-provider bootstrap config as per design (#7333) --- .../MeshCaCertificateProviderProvider.java | 138 ++++++++----- .../CommonCertProviderTestUtils.java | 191 ++++++++++++++++++ ...MeshCaCertificateProviderProviderTest.java | 104 ++++++---- 3 files changed, 341 insertions(+), 92 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderProvider.java index 669b0f28591..c29170b104d 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderProvider.java @@ -19,13 +19,16 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static io.grpc.internal.JsonUtil.getObject; import com.google.common.annotations.VisibleForTesting; import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.grpc.internal.BackoffPolicy; import io.grpc.internal.ExponentialBackoffPolicy; +import io.grpc.internal.JsonUtil; import io.grpc.internal.TimeProvider; import io.grpc.xds.internal.sts.StsCredentials; +import java.util.List; import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -39,17 +42,17 @@ */ final class MeshCaCertificateProviderProvider implements CertificateProviderProvider { - private static final String MESHCA_URL_KEY = "meshCaUrl"; - private static final String RPC_TIMEOUT_SECONDS_KEY = "rpcTimeoutSeconds"; - private static final String GKECLUSTER_URL_KEY = "gkeClusterUrl"; - private static final String CERT_VALIDITY_SECONDS_KEY = "certValiditySeconds"; - private static final String RENEWAL_GRACE_PERIOD_SECONDS_KEY = "renewalGracePeriodSeconds"; - private static final String KEY_ALGO_KEY = "keyAlgo"; // aka keyType - private static final String KEY_SIZE_KEY = "keySize"; - private static final String SIGNATURE_ALGO_KEY = "signatureAlgo"; - private static final String MAX_RETRY_ATTEMPTS_KEY = "maxRetryAttempts"; - private static final String STS_URL_KEY = "stsUrl"; - private static final String GKE_SA_JWT_LOCATION_KEY = "gkeSaJwtLocation"; + private static final String SERVER_CONFIG_KEY = "server"; + private static final String MESHCA_URL_KEY = "target_uri"; + private static final String RPC_TIMEOUT_SECONDS_KEY = "time_out"; + private static final String GKECLUSTER_URL_KEY = "location"; + private static final String CERT_VALIDITY_SECONDS_KEY = "certificate_lifetime"; + private static final String RENEWAL_GRACE_PERIOD_SECONDS_KEY = "renewal_grace_period"; + private static final String KEY_ALGO_KEY = "key_type"; // aka keyType + private static final String KEY_SIZE_KEY = "key_size"; + private static final String STS_SERVICE_KEY = "sts_service"; + private static final String TOKEN_EXCHANGE_SERVICE_KEY = "token_exchange_service"; + private static final String GKE_SA_JWT_LOCATION_KEY = "subject_token_path"; @VisibleForTesting static final String MESHCA_URL_DEFAULT = "meshca.googleapis.com"; @VisibleForTesting static final long RPC_TIMEOUT_SECONDS_DEFAULT = 5L; @@ -146,56 +149,93 @@ public CertificateProvider createCertificateProvider( private static Config validateAndTranslateConfig(Object config) { // TODO(sanjaypujare): add support for string, struct proto etc checkArgument(config instanceof Map, "Only Map supported for config"); - @SuppressWarnings("unchecked") Map map = (Map)config; + @SuppressWarnings("unchecked") Map map = (Map)config; Config configObj = new Config(); - configObj.meshCaUrl = mapGetOrDefault(map, MESHCA_URL_KEY, MESHCA_URL_DEFAULT); - configObj.rpcTimeoutSeconds = - mapGetOrDefault(map, RPC_TIMEOUT_SECONDS_KEY, RPC_TIMEOUT_SECONDS_DEFAULT); - configObj.gkeClusterUrl = - checkNotNull( - map.get(GKECLUSTER_URL_KEY), GKECLUSTER_URL_KEY + " is required in the config"); + extractMeshCaServerConfig(configObj, getObject(map, SERVER_CONFIG_KEY)); configObj.certValiditySeconds = - mapGetOrDefault(map, CERT_VALIDITY_SECONDS_KEY, CERT_VALIDITY_SECONDS_DEFAULT); + getSeconds( + JsonUtil.getObject(map, CERT_VALIDITY_SECONDS_KEY), CERT_VALIDITY_SECONDS_DEFAULT); configObj.renewalGracePeriodSeconds = - mapGetOrDefault( - map, RENEWAL_GRACE_PERIOD_SECONDS_KEY, RENEWAL_GRACE_PERIOD_SECONDS_DEFAULT); - configObj.keyAlgo = mapGetOrDefault(map, KEY_ALGO_KEY, KEY_ALGO_DEFAULT); - configObj.keySize = mapGetOrDefault(map, KEY_SIZE_KEY, KEY_SIZE_DEFAULT); - configObj.signatureAlgo = mapGetOrDefault(map, SIGNATURE_ALGO_KEY, SIGNATURE_ALGO_DEFAULT); - configObj.maxRetryAttempts = - mapGetOrDefault(map, MAX_RETRY_ATTEMPTS_KEY, MAX_RETRY_ATTEMPTS_DEFAULT); - configObj.stsUrl = mapGetOrDefault(map, STS_URL_KEY, STS_URL_DEFAULT); - configObj.gkeSaJwtLocation = - checkNotNull( - map.get(GKE_SA_JWT_LOCATION_KEY), - GKE_SA_JWT_LOCATION_KEY + " is required in the config"); + getSeconds( + JsonUtil.getObject(map, RENEWAL_GRACE_PERIOD_SECONDS_KEY), + RENEWAL_GRACE_PERIOD_SECONDS_DEFAULT); + String keyType = JsonUtil.getString(map, KEY_ALGO_KEY); + checkArgument( + keyType == null || keyType.equals(KEY_ALGO_DEFAULT), "key_type can only be null or 'RSA'"); + // TODO: remove signatureAlgo, keyType (or keyAlgo), maxRetryAttempts + configObj.maxRetryAttempts = MAX_RETRY_ATTEMPTS_DEFAULT; + configObj.keyAlgo = KEY_ALGO_DEFAULT; + configObj.signatureAlgo = SIGNATURE_ALGO_DEFAULT; + configObj.keySize = JsonUtil.getNumberAsInteger(map, KEY_SIZE_KEY); + if (configObj.keySize == null) { + configObj.keySize = KEY_SIZE_DEFAULT; + } + configObj.gkeClusterUrl = + checkNotNull(JsonUtil.getString(map, GKECLUSTER_URL_KEY), + "'location' is required in the config"); parseProjectAndZone(configObj.gkeClusterUrl, configObj); return configObj; } - private static String mapGetOrDefault(Map map, String key, String defaultVal) { - String value = map.get(key); - if (value == null) { - return defaultVal; - } - return value; - } - - private static Long mapGetOrDefault(Map map, String key, long defaultVal) { - String value = map.get(key); - if (value == null) { - return defaultVal; + private static void extractMeshCaServerConfig(Config configObj, Map serverConfig) { + // init with defaults + configObj.meshCaUrl = MESHCA_URL_DEFAULT; + configObj.rpcTimeoutSeconds = RPC_TIMEOUT_SECONDS_DEFAULT; + configObj.stsUrl = STS_URL_DEFAULT; + if (serverConfig != null) { + checkArgument( + "GRPC".equals(JsonUtil.getString(serverConfig, "api_type")), + "Only GRPC api_type supported"); + List> grpcServices = + checkNotNull( + JsonUtil.getListOfObjects(serverConfig, "grpc_services"), "grpc_services not found"); + for (Map grpcService : grpcServices) { + Map googleGrpcConfig = JsonUtil.getObject(grpcService, "google_grpc"); + if (googleGrpcConfig != null) { + String value = JsonUtil.getString(googleGrpcConfig, MESHCA_URL_KEY); + if (value != null) { + configObj.meshCaUrl = value; + } + Map channelCreds = + JsonUtil.getObject(googleGrpcConfig, "channel_credentials"); + if (channelCreds != null) { + Map googleDefaultChannelCreds = + checkNotNull( + JsonUtil.getObject(channelCreds, "google_default"), + "channel_credentials need to be google_default!"); + checkArgument( + googleDefaultChannelCreds.isEmpty(), + "google_default credentials contain illegal value"); + } + List> callCreds = + JsonUtil.getListOfObjects(googleGrpcConfig, "call_credentials"); + for (Map callCred : callCreds) { + Map stsCreds = JsonUtil.getObject(callCred, STS_SERVICE_KEY); + if (stsCreds != null) { + value = JsonUtil.getString(stsCreds, TOKEN_EXCHANGE_SERVICE_KEY); + if (value != null) { + configObj.stsUrl = value; + } + configObj.gkeSaJwtLocation = JsonUtil.getString(stsCreds, GKE_SA_JWT_LOCATION_KEY); + } + } + configObj.rpcTimeoutSeconds = + getSeconds( + JsonUtil.getObject(grpcService, RPC_TIMEOUT_SECONDS_KEY), + RPC_TIMEOUT_SECONDS_DEFAULT); + } + } } - return Long.parseLong(value); + // check required value(s) + checkNotNull(configObj.gkeSaJwtLocation, "'subject_token_path' is required in the config"); } - private static Integer mapGetOrDefault(Map map, String key, int defaultVal) { - String value = map.get(key); - if (value == null) { - return defaultVal; + private static Long getSeconds(Map duration, long defaultValue) { + if (duration != null) { + return JsonUtil.getNumberAsLong(duration, "seconds"); } - return Integer.parseInt(value); + return defaultValue; } private static void parseProjectAndZone(String gkeClusterUrl, Config configObj) { diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/CommonCertProviderTestUtils.java b/xds/src/test/java/io/grpc/xds/internal/certprovider/CommonCertProviderTestUtils.java index fe584612e27..34347b68e2d 100644 --- a/xds/src/test/java/io/grpc/xds/internal/certprovider/CommonCertProviderTestUtils.java +++ b/xds/src/test/java/io/grpc/xds/internal/certprovider/CommonCertProviderTestUtils.java @@ -94,6 +94,197 @@ static Bootstrapper.BootstrapInfo getTestBootstrapInfo() throws IOException { return Bootstrapper.parseConfig(rawData); } + static Bootstrapper.BootstrapInfo getNonDefaultTestBootstrapInfo() throws IOException { + String rawData = + "{\n" + + " \"xds_servers\": [],\n" + + " \"certificate_providers\": {\n" + + " \"gcp_id\": {\n" + + " \"plugin_name\": \"testca\",\n" + + " \"config\": {\n" + + " \"server\": {\n" + + " \"api_type\": \"GRPC\",\n" + + " \"grpc_services\": [{\n" + + " \"google_grpc\": {\n" + + " \"target_uri\": \"nonDefaultMeshCaUrl\",\n" + + " \"channel_credentials\": {\"google_default\": {}},\n" + + " \"call_credentials\": [{\n" + + " \"sts_service\": {\n" + + " \"token_exchange_service\": \"test.sts.com\",\n" + + " \"subject_token_path\": \"/tmp/path4\"\n" + + " }\n" + + " }]\n" // end call_credentials + + " },\n" // end google_grpc + + " \"time_out\": {\"seconds\": 12}\n" + + " }]\n" // end grpc_services + + " },\n" // end server + + " \"certificate_lifetime\": {\"seconds\": 234567},\n" + + " \"renewal_grace_period\": {\"seconds\": 4321},\n" + + " \"key_type\": \"RSA\",\n" + + " \"key_size\": 512,\n" + + " \"location\": \"https://container.googleapis.com/v1/projects/test-project1/locations/test-zone2/clusters/test-cluster3\"\n" + + " }\n" // end config + + " },\n" // end gcp_id + + " \"file_provider\": {\n" + + " \"plugin_name\": \"file_watcher\",\n" + + " \"config\": {\"path\": \"/etc/secret/certs\"}\n" + + " }\n" + + " }\n" + + "}"; + return Bootstrapper.parseConfig(rawData); + } + + static Bootstrapper.BootstrapInfo getMinimalBootstrapInfo() throws IOException { + String rawData = + "{\n" + + " \"xds_servers\": [],\n" + + " \"certificate_providers\": {\n" + + " \"gcp_id\": {\n" + + " \"plugin_name\": \"testca\",\n" + + " \"config\": {\n" + + " \"server\": {\n" + + " \"api_type\": \"GRPC\",\n" + + " \"grpc_services\": [{\n" + + " \"google_grpc\": {\n" + + " \"call_credentials\": [{\n" + + " \"sts_service\": {\n" + + " \"subject_token_path\": \"/tmp/path5\"\n" + + " }\n" + + " }]\n" // end call_credentials + + " }\n" // end google_grpc + + " }]\n" // end grpc_services + + " },\n" // end server + + " \"location\": \"https://container.googleapis.com/v1/projects/test-project1/locations/test-zone2/clusters/test-cluster3\"\n" + + " }\n" // end config + + " }\n" // end gcp_id + + " }\n" + + "}"; + return Bootstrapper.parseConfig(rawData); + } + + static Bootstrapper.BootstrapInfo getMinimalAndBadClusterUrlBootstrapInfo() throws IOException { + String rawData = + "{\n" + + " \"xds_servers\": [],\n" + + " \"certificate_providers\": {\n" + + " \"gcp_id\": {\n" + + " \"plugin_name\": \"testca\",\n" + + " \"config\": {\n" + + " \"server\": {\n" + + " \"api_type\": \"GRPC\",\n" + + " \"grpc_services\": [{\n" + + " \"google_grpc\": {\n" + + " \"call_credentials\": [{\n" + + " \"sts_service\": {\n" + + " \"subject_token_path\": \"/tmp/path5\"\n" + + " }\n" + + " }]\n" // end call_credentials + + " }\n" // end google_grpc + + " }]\n" // end grpc_services + + " },\n" // end server + + " \"location\": \"https://container.googleapis.com/v1/project/test-project1/locations/test-zone2/clusters/test-cluster3\"\n" + + " }\n" // end config + + " }\n" // end gcp_id + + " }\n" + + "}"; + return Bootstrapper.parseConfig(rawData); + } + + static Bootstrapper.BootstrapInfo getMissingSaJwtLocation() throws IOException { + String rawData = + "{\n" + + " \"xds_servers\": [],\n" + + " \"certificate_providers\": {\n" + + " \"gcp_id\": {\n" + + " \"plugin_name\": \"testca\",\n" + + " \"config\": {\n" + + " \"server\": {\n" + + " \"api_type\": \"GRPC\",\n" + + " \"grpc_services\": [{\n" + + " \"google_grpc\": {\n" + + " \"call_credentials\": [{\n" + + " \"sts_service\": {\n" + + " }\n" + + " }]\n" // end call_credentials + + " }\n" // end google_grpc + + " }]\n" // end grpc_services + + " },\n" // end server + + " \"location\": \"https://container.googleapis.com/v1/projects/test-project1/locations/test-zone2/clusters/test-cluster3\"\n" + + " }\n" // end config + + " }\n" // end gcp_id + + " }\n" + + "}"; + return Bootstrapper.parseConfig(rawData); + } + + static Bootstrapper.BootstrapInfo getMissingGkeClusterUrl() throws IOException { + String rawData = + "{\n" + + " \"xds_servers\": [],\n" + + " \"certificate_providers\": {\n" + + " \"gcp_id\": {\n" + + " \"plugin_name\": \"testca\",\n" + + " \"config\": {\n" + + " \"server\": {\n" + + " \"api_type\": \"GRPC\",\n" + + " \"grpc_services\": [{\n" + + " \"google_grpc\": {\n" + + " \"target_uri\": \"meshca.com\",\n" + + " \"channel_credentials\": {\"google_default\": {}},\n" + + " \"call_credentials\": [{\n" + + " \"sts_service\": {\n" + + " \"token_exchange_service\": \"securetoken.googleapis.com\",\n" + + " \"subject_token_path\": \"/etc/secret/sajwt.token\"\n" + + " }\n" + + " }]\n" // end call_credentials + + " },\n" // end google_grpc + + " \"time_out\": {\"seconds\": 10}\n" + + " }]\n" // end grpc_services + + " },\n" // end server + + " \"certificate_lifetime\": {\"seconds\": 86400},\n" + + " \"renewal_grace_period\": {\"seconds\": 3600},\n" + + " \"key_type\": \"RSA\",\n" + + " \"key_size\": 2048\n" + + " }\n" // end config + + " },\n" // end gcp_id + + " \"file_provider\": {\n" + + " \"plugin_name\": \"file_watcher\",\n" + + " \"config\": {\"path\": \"/etc/secret/certs\"}\n" + + " }\n" + + " }\n" + + "}"; + return Bootstrapper.parseConfig(rawData); + } + + static Bootstrapper.BootstrapInfo getBadChannelCredsConfig() throws IOException { + String rawData = + "{\n" + + " \"xds_servers\": [],\n" + + " \"certificate_providers\": {\n" + + " \"gcp_id\": {\n" + + " \"plugin_name\": \"testca\",\n" + + " \"config\": {\n" + + " \"server\": {\n" + + " \"api_type\": \"GRPC\",\n" + + " \"grpc_services\": [{\n" + + " \"google_grpc\": {\n" + + " \"channel_credentials\": {\"mtls\": \"true\"},\n" + + " \"call_credentials\": [{\n" + + " \"sts_service\": {\n" + + " \"subject_token_path\": \"/tmp/path5\"\n" + + " }\n" + + " }]\n" // end call_credentials + + " }\n" // end google_grpc + + " }]\n" // end grpc_services + + " },\n" // end server + + " \"location\": \"https://container.googleapis.com/v1/projects/test-project1/locations/test-zone2/clusters/test-cluster3\"\n" + + " }\n" // end config + + " }\n" // end gcp_id + + " }\n" + + "}"; + return Bootstrapper.parseConfig(rawData); + } + static PrivateKey getPrivateKey(String resourceName) throws Exception { InputStream inputStream = TestUtils.class.getResourceAsStream("/certs/" + resourceName); diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderProviderTest.java index b28bd4980b8..5da5b34d030 100644 --- a/xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/certprovider/MeshCaCertificateProviderProviderTest.java @@ -30,9 +30,9 @@ import io.grpc.internal.BackoffPolicy; import io.grpc.internal.ExponentialBackoffPolicy; import io.grpc.internal.TimeProvider; +import io.grpc.xds.Bootstrapper; import io.grpc.xds.internal.sts.StsCredentials; import java.io.IOException; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -106,10 +106,10 @@ public void providerRegisteredName() { } @Test - public void createProvider_minimalConfig() { + public void createProvider_minimalConfig() throws IOException { CertificateProvider.DistributorWatcher distWatcher = new CertificateProvider.DistributorWatcher(); - Map map = buildMinimalMap(); + Map map = buildMinimalConfig(); ScheduledExecutorService mockService = mock(ScheduledExecutorService.class); when(scheduledExecutorServiceFactory.create( eq(MeshCaCertificateProviderProvider.MESHCA_URL_DEFAULT))) @@ -119,7 +119,7 @@ public void createProvider_minimalConfig() { .create( eq(MeshCaCertificateProviderProvider.STS_URL_DEFAULT), eq(EXPECTED_AUDIENCE), - eq(TMP_PATH_4)); + eq("/tmp/path5")); verify(meshCaCertificateProviderFactory, times(1)) .create( eq(distWatcher), @@ -141,41 +141,36 @@ public void createProvider_minimalConfig() { } @Test - public void createProvider_missingGkeUrl_expectException() { + public void createProvider_missingGkeUrl_expectException() throws IOException { CertificateProvider.DistributorWatcher distWatcher = new CertificateProvider.DistributorWatcher(); - Map map = buildMinimalMap(); - map.remove("gkeClusterUrl"); + Map map = buildMissingGkeClusterUrlConfig(); try { provider.createCertificateProvider(map, distWatcher, true); fail("exception expected"); } catch (NullPointerException npe) { - assertThat(npe).hasMessageThat().isEqualTo("gkeClusterUrl is required in the config"); + assertThat(npe).hasMessageThat().isEqualTo("'location' is required in the config"); } } @Test - public void createProvider_missingGkeSaJwtLocation_expectException() { + public void createProvider_missingGkeSaJwtLocation_expectException() throws IOException { CertificateProvider.DistributorWatcher distWatcher = new CertificateProvider.DistributorWatcher(); - Map map = buildMinimalMap(); - map.remove("gkeSaJwtLocation"); + Map map = buildMissingSaJwtLocationConfig(); try { provider.createCertificateProvider(map, distWatcher, true); fail("exception expected"); } catch (NullPointerException npe) { - assertThat(npe).hasMessageThat().isEqualTo("gkeSaJwtLocation is required in the config"); + assertThat(npe).hasMessageThat().isEqualTo("'subject_token_path' is required in the config"); } } @Test - public void createProvider_missingProject_expectException() { + public void createProvider_missingProject_expectException() throws IOException { CertificateProvider.DistributorWatcher distWatcher = new CertificateProvider.DistributorWatcher(); - Map map = buildMinimalMap(); - map.put( - "gkeClusterUrl", - "https://container.googleapis.com/v1/project/test-project1/locations/test-zone2/clusters/test-cluster3"); + Map map = buildBadClusterUrlConfig(); try { provider.createCertificateProvider(map, distWatcher, true); fail("exception expected"); @@ -185,17 +180,30 @@ public void createProvider_missingProject_expectException() { } @Test - public void createProvider_nonDefaultFullConfig() { + public void createProvider_badChannelCreds_expectException() throws IOException { CertificateProvider.DistributorWatcher distWatcher = new CertificateProvider.DistributorWatcher(); - Map map = buildFullMap(); + Map map = buildBadChannelCredsConfig(); + try { + provider.createCertificateProvider(map, distWatcher, true); + fail("exception expected"); + } catch (NullPointerException ex) { + assertThat(ex).hasMessageThat().isEqualTo("channel_credentials need to be google_default!"); + } + } + + @Test + public void createProvider_nonDefaultFullConfig() throws IOException { + CertificateProvider.DistributorWatcher distWatcher = + new CertificateProvider.DistributorWatcher(); + Map map = buildFullConfig(); ScheduledExecutorService mockService = mock(ScheduledExecutorService.class); when(scheduledExecutorServiceFactory.create(eq(NON_DEFAULT_MESH_CA_URL))) .thenReturn(mockService); provider.createCertificateProvider(map, distWatcher, true); verify(stsCredentialsFactory, times(1)) .create( - eq("nonDefaultStsUrl"), + eq("test.sts.com"), eq(EXPECTED_AUDIENCE), eq(TMP_PATH_4)); verify(meshCaCertificateProviderFactory, times(1)) @@ -205,39 +213,49 @@ public void createProvider_nonDefaultFullConfig() { eq(NON_DEFAULT_MESH_CA_URL), eq("test-zone2"), eq(234567L), - eq(4096), - eq("KEY-ALGO1"), - eq("SIG-ALGO2"), + eq(512), + eq("RSA"), + eq("SHA256withRSA"), eq(meshCaChannelFactory), eq(backoffPolicyProvider), eq(4321L), - eq(9), + eq(3), (GoogleCredentials) isNull(), eq(mockService), eq(timeProvider), eq(TimeUnit.SECONDS.toMillis(RPC_TIMEOUT_SECONDS))); } - private Map buildFullMap() { - Map map = new HashMap<>(); - map.put("gkeClusterUrl", GKE_CLUSTER_URL); - map.put("gkeSaJwtLocation", TMP_PATH_4); - map.put("meshCaUrl", NON_DEFAULT_MESH_CA_URL); - map.put("rpcTimeoutSeconds", "123"); - map.put("certValiditySeconds", "234567"); - map.put("renewalGracePeriodSeconds", "4321"); - map.put("keyAlgo", "KEY-ALGO1"); - map.put("keySize", "4096"); - map.put("signatureAlgo", "SIG-ALGO2"); - map.put("maxRetryAttempts", "9"); - map.put("stsUrl", "nonDefaultStsUrl"); - return map; + private Map buildFullConfig() throws IOException { + return getCertProviderConfig(CommonCertProviderTestUtils.getNonDefaultTestBootstrapInfo()); + } + + private Map buildMinimalConfig() throws IOException { + return getCertProviderConfig(CommonCertProviderTestUtils.getMinimalBootstrapInfo()); + } + + private Map buildBadClusterUrlConfig() throws IOException { + return getCertProviderConfig( + CommonCertProviderTestUtils.getMinimalAndBadClusterUrlBootstrapInfo()); + } + + private Map buildMissingSaJwtLocationConfig() throws IOException { + return getCertProviderConfig(CommonCertProviderTestUtils.getMissingSaJwtLocation()); + } + + private Map buildMissingGkeClusterUrlConfig() throws IOException { + return getCertProviderConfig(CommonCertProviderTestUtils.getMissingGkeClusterUrl()); + } + + private Map buildBadChannelCredsConfig() throws IOException { + return getCertProviderConfig(CommonCertProviderTestUtils.getBadChannelCredsConfig()); } - private Map buildMinimalMap() { - Map map = new HashMap<>(); - map.put("gkeClusterUrl", GKE_CLUSTER_URL); - map.put("gkeSaJwtLocation", TMP_PATH_4); - return map; + private Map getCertProviderConfig(Bootstrapper.BootstrapInfo bootstrapInfo) { + Map certProviders = + bootstrapInfo.getCertProviders(); + Bootstrapper.CertificateProviderInfo gcpIdInfo = + certProviders.get("gcp_id"); + return gcpIdInfo.getConfig(); } } From e6ab16733421c33e52f0d5d8755d41ec249ca6e4 Mon Sep 17 00:00:00 2001 From: sanjaypujare Date: Fri, 21 Aug 2020 14:08:39 -0700 Subject: [PATCH 77/88] xds: Add CertProviderSslContextProviders to Client&Server SslContextProviderFactories (#7338) --- .../io/grpc/xds/EnvoyServerProtoData.java | 5 +- .../CertProviderClientSslContextProvider.java | 14 +- .../CertProviderServerSslContextProvider.java | 14 +- .../certprovider/CertificateProvider.java | 7 +- .../CertificateProviderProvider.java | 4 +- .../CertificateProviderRegistry.java | 2 +- .../CertificateProviderStore.java | 2 +- .../sds/ClientSslContextProviderFactory.java | 28 ++- .../internal/sds/CommonTlsContextUtil.java | 30 ++- .../SecretVolumeClientSslContextProvider.java | 4 + .../SecretVolumeServerSslContextProvider.java | 4 + .../sds/ServerSslContextProviderFactory.java | 28 ++- .../CommonCertProviderTestUtils.java | 3 +- .../certprovider/TestCertificateProvider.java | 13 +- .../ClientSslContextProviderFactoryTest.java | 227 +++++++++++++++++- .../ServerSslContextProviderFactoryTest.java | 191 ++++++++++++++- 16 files changed, 525 insertions(+), 51 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java b/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java index c0ab9083fb9..9911f011eda 100644 --- a/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java +++ b/xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java @@ -72,7 +72,7 @@ public int hashCode() { public static final class UpstreamTlsContext extends BaseTlsContext { @VisibleForTesting - UpstreamTlsContext(CommonTlsContext commonTlsContext) { + public UpstreamTlsContext(CommonTlsContext commonTlsContext) { super(commonTlsContext); } @@ -93,7 +93,8 @@ public static final class DownstreamTlsContext extends BaseTlsContext { private final boolean requireClientCertificate; @VisibleForTesting - DownstreamTlsContext(CommonTlsContext commonTlsContext, boolean requireClientCertificate) { + public DownstreamTlsContext( + CommonTlsContext commonTlsContext, boolean requireClientCertificate) { super(commonTlsContext); this.requireClientCertificate = requireClientCertificate; } diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProvider.java index 562f27431c7..1dc7be1be33 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderClientSslContextProvider.java @@ -23,6 +23,7 @@ import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext.CombinedCertificateValidationContext; +import io.grpc.Internal; import io.grpc.netty.GrpcSslContexts; import io.grpc.xds.Bootstrapper.CertificateProviderInfo; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; @@ -33,7 +34,8 @@ import java.util.Map; /** A client SslContext provider using CertificateProviderInstance to fetch secrets. */ -final class CertProviderClientSslContextProvider extends CertProviderSslContextProvider { +@Internal +public final class CertProviderClientSslContextProvider extends CertProviderSslContextProvider { private CertProviderClientSslContextProvider( Node node, @@ -70,20 +72,22 @@ protected final SslContextBuilder getSslContextBuilder( } /** Creates CertProviderClientSslContextProvider. */ - static final class Factory { + @Internal + public static final class Factory { private static final Factory DEFAULT_INSTANCE = new Factory(CertificateProviderStore.getInstance()); private final CertificateProviderStore certificateProviderStore; - @VisibleForTesting Factory(CertificateProviderStore certificateProviderStore) { + @VisibleForTesting public Factory(CertificateProviderStore certificateProviderStore) { this.certificateProviderStore = certificateProviderStore; } - static Factory getInstance() { + public static Factory getInstance() { return DEFAULT_INSTANCE; } - CertProviderClientSslContextProvider getProvider( + /** Creates a {@link CertProviderClientSslContextProvider}. */ + public CertProviderClientSslContextProvider getProvider( UpstreamTlsContext upstreamTlsContext, Node node, Map certProviders) { diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProvider.java index 5d1a6399b88..78e825f60fd 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertProviderServerSslContextProvider.java @@ -23,6 +23,7 @@ import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext.CombinedCertificateValidationContext; +import io.grpc.Internal; import io.grpc.netty.GrpcSslContexts; import io.grpc.xds.Bootstrapper.CertificateProviderInfo; import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; @@ -36,7 +37,8 @@ import java.util.Map; /** A server SslContext provider using CertificateProviderInstance to fetch secrets. */ -final class CertProviderServerSslContextProvider extends CertProviderSslContextProvider { +@Internal +public final class CertProviderServerSslContextProvider extends CertProviderSslContextProvider { private CertProviderServerSslContextProvider( Node node, @@ -73,20 +75,22 @@ protected final SslContextBuilder getSslContextBuilder( } /** Creates CertProviderServerSslContextProvider. */ - static final class Factory { + @Internal + public static final class Factory { private static final Factory DEFAULT_INSTANCE = new Factory(CertificateProviderStore.getInstance()); private final CertificateProviderStore certificateProviderStore; - @VisibleForTesting Factory(CertificateProviderStore certificateProviderStore) { + @VisibleForTesting public Factory(CertificateProviderStore certificateProviderStore) { this.certificateProviderStore = certificateProviderStore; } - static Factory getInstance() { + public static Factory getInstance() { return DEFAULT_INSTANCE; } - CertProviderServerSslContextProvider getProvider( + /** Creates a {@link CertProviderServerSslContextProvider}. */ + public CertProviderServerSslContextProvider getProvider( DownstreamTlsContext downstreamTlsContext, Node node, Map certProviders) { diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProvider.java index b5d149777ed..04ed997fa58 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProvider.java @@ -23,6 +23,7 @@ import io.grpc.xds.internal.sds.Closeable; import java.security.PrivateKey; import java.security.cert.X509Certificate; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -48,7 +49,7 @@ public interface Watcher { } @VisibleForTesting - static final class DistributorWatcher implements Watcher { + public static final class DistributorWatcher implements Watcher { private PrivateKey privateKey; private List certChain; private List trustedRoots; @@ -70,6 +71,10 @@ synchronized void removeWatcher(Watcher watcher) { downstreamWatchers.remove(watcher); } + @VisibleForTesting public Set getDownstreamWatchers() { + return Collections.unmodifiableSet(downstreamWatchers); + } + private void sendLastCertificateUpdate(Watcher watcher) { watcher.updateCertificate(privateKey, certChain); } diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderProvider.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderProvider.java index 92b2d4d6aa1..a426542eea0 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderProvider.java @@ -16,13 +16,15 @@ package io.grpc.xds.internal.certprovider; +import io.grpc.Internal; import io.grpc.xds.internal.certprovider.CertificateProvider.Watcher; /** * Provider of {@link CertificateProvider}s. Implemented by the implementer of the plugin. We may * move this out of the internal package and make this an official API in the future. */ -interface CertificateProviderProvider { +@Internal +public interface CertificateProviderProvider { /** Returns the unique name of the {@link CertificateProvider} plugin. */ String getName(); diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderRegistry.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderRegistry.java index 36db37e5db7..04a8f73f261 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderRegistry.java +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderRegistry.java @@ -31,7 +31,7 @@ public final class CertificateProviderRegistry { new LinkedHashMap<>(); @VisibleForTesting - CertificateProviderRegistry() { + public CertificateProviderRegistry() { } /** Returns the singleton registry. */ diff --git a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderStore.java b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderStore.java index 18f8a11ec2e..43143ebb3ae 100644 --- a/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderStore.java +++ b/xds/src/main/java/io/grpc/xds/internal/certprovider/CertificateProviderStore.java @@ -139,7 +139,7 @@ public CertificateProvider create(CertProviderKey key) { } @VisibleForTesting - CertificateProviderStore(CertificateProviderRegistry certificateProviderRegistry) { + public CertificateProviderStore(CertificateProviderRegistry certificateProviderRegistry) { this.certificateProviderRegistry = certificateProviderRegistry; certProviderMap = new ReferenceCountingMap<>(new CertProviderFactory()); } diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/ClientSslContextProviderFactory.java b/xds/src/main/java/io/grpc/xds/internal/sds/ClientSslContextProviderFactory.java index 84b2f8284aa..8e593fd560e 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/ClientSslContextProviderFactory.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/ClientSslContextProviderFactory.java @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.grpc.xds.Bootstrapper; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; +import io.grpc.xds.internal.certprovider.CertProviderClientSslContextProvider; import io.grpc.xds.internal.sds.ReferenceCountingMap.ValueFactory; import java.io.IOException; import java.util.concurrent.Executors; @@ -29,6 +30,20 @@ final class ClientSslContextProviderFactory implements ValueFactory { + private final Bootstrapper bootstrapper; + private final CertProviderClientSslContextProvider.Factory + certProviderClientSslContextProviderFactory; + + ClientSslContextProviderFactory() { + this(Bootstrapper.getInstance(), CertProviderClientSslContextProvider.Factory.getInstance()); + } + + ClientSslContextProviderFactory( + Bootstrapper bootstrapper, CertProviderClientSslContextProvider.Factory factory) { + this.bootstrapper = bootstrapper; + this.certProviderClientSslContextProviderFactory = factory; + } + /** Creates an SslContextProvider from the given UpstreamTlsContext. */ @Override public SslContextProvider create(UpstreamTlsContext upstreamTlsContext) { @@ -52,8 +67,17 @@ public SslContextProvider create(UpstreamTlsContext upstreamTlsContext) { } catch (IOException ioe) { throw new RuntimeException(ioe); } + } else if (CommonTlsContextUtil.hasCertProviderInstance( + upstreamTlsContext.getCommonTlsContext())) { + try { + return certProviderClientSslContextProviderFactory.getProvider( + upstreamTlsContext, + bootstrapper.readBootstrap().getNode().toEnvoyProtoNode(), + bootstrapper.readBootstrap().getCertProviders()); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } } - throw new UnsupportedOperationException( - "UpstreamTlsContext to have all filenames or all SdsConfig"); + throw new UnsupportedOperationException("Unsupported configurations in UpstreamTlsContext!"); } } diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/CommonTlsContextUtil.java b/xds/src/main/java/io/grpc/xds/internal/sds/CommonTlsContextUtil.java index 54dd0e71171..5ffb356e187 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/CommonTlsContextUtil.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/CommonTlsContextUtil.java @@ -23,6 +23,7 @@ import io.envoyproxy.envoy.config.core.v3.DataSource.SpecifierCase; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext.CombinedCertificateValidationContext; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext.ValidationContextTypeCase; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.TlsCertificate; import javax.annotation.Nullable; @@ -34,18 +35,31 @@ private CommonTlsContextUtil() {} /** Returns true only if given CommonTlsContext uses no SdsSecretConfigs. */ static boolean hasAllSecretsUsingFilename(CommonTlsContext commonTlsContext) { - checkNotNull(commonTlsContext, "commonTlsContext"); - // return true if it has no SdsSecretConfig(s) - return (commonTlsContext.getTlsCertificateSdsSecretConfigsCount() == 0) - && !commonTlsContext.hasValidationContextSdsSecretConfig(); + return commonTlsContext != null + && (commonTlsContext.getTlsCertificatesCount() > 0 + || commonTlsContext.hasValidationContext()); } /** Returns true only if given CommonTlsContext uses only SdsSecretConfigs. */ static boolean hasAllSecretsUsingSds(CommonTlsContext commonTlsContext) { - checkNotNull(commonTlsContext, "commonTlsContext"); - // return true if it has only SdsSecretConfig(s) - return (commonTlsContext.getTlsCertificatesCount() == 0) - && !commonTlsContext.hasValidationContext(); + return commonTlsContext != null + && (commonTlsContext.getTlsCertificateSdsSecretConfigsCount() > 0 + || commonTlsContext.hasValidationContextSdsSecretConfig()); + } + + static boolean hasCertProviderInstance(CommonTlsContext commonTlsContext) { + return commonTlsContext != null + && (commonTlsContext.hasTlsCertificateCertificateProviderInstance() + || hasCertProviderValidationContext(commonTlsContext)); + } + + private static boolean hasCertProviderValidationContext(CommonTlsContext commonTlsContext) { + if (commonTlsContext.hasCombinedValidationContext()) { + CombinedCertificateValidationContext combinedCertificateValidationContext = + commonTlsContext.getCombinedValidationContext(); + return combinedCertificateValidationContext.hasValidationContextCertificateProviderInstance(); + } + return commonTlsContext.hasValidationContextCertificateProviderInstance(); } @Nullable diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/SecretVolumeClientSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/sds/SecretVolumeClientSslContextProvider.java index fc3befd5f1c..8698c6cc099 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/SecretVolumeClientSslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/SecretVolumeClientSslContextProvider.java @@ -16,6 +16,7 @@ package io.grpc.xds.internal.sds; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static io.grpc.xds.internal.sds.CommonTlsContextUtil.getCertificateValidationContext; import static io.grpc.xds.internal.sds.CommonTlsContextUtil.validateCertificateContext; @@ -60,6 +61,9 @@ private SecretVolumeClientSslContextProvider( static SecretVolumeClientSslContextProvider getProvider(UpstreamTlsContext upstreamTlsContext) { checkNotNull(upstreamTlsContext, "upstreamTlsContext"); CommonTlsContext commonTlsContext = upstreamTlsContext.getCommonTlsContext(); + checkArgument( + commonTlsContext.getTlsCertificateSdsSecretConfigsCount() == 0, + "unexpected TlsCertificateSdsSecretConfigs"); CertificateValidationContext certificateValidationContext = getCertificateValidationContext(commonTlsContext); // first validate diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/SecretVolumeServerSslContextProvider.java b/xds/src/main/java/io/grpc/xds/internal/sds/SecretVolumeServerSslContextProvider.java index 943b385c6a2..f1bb93a6513 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/SecretVolumeServerSslContextProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/SecretVolumeServerSslContextProvider.java @@ -16,6 +16,7 @@ package io.grpc.xds.internal.sds; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static io.grpc.xds.internal.sds.CommonTlsContextUtil.getCertificateValidationContext; import static io.grpc.xds.internal.sds.CommonTlsContextUtil.validateCertificateContext; @@ -61,6 +62,9 @@ static SecretVolumeServerSslContextProvider getProvider( DownstreamTlsContext downstreamTlsContext) { checkNotNull(downstreamTlsContext, "downstreamTlsContext"); CommonTlsContext commonTlsContext = downstreamTlsContext.getCommonTlsContext(); + checkArgument( + commonTlsContext.getTlsCertificateSdsSecretConfigsCount() == 0, + "unexpected TlsCertificateSdsSecretConfigs"); TlsCertificate tlsCertificate = null; if (commonTlsContext.getTlsCertificatesCount() > 0) { tlsCertificate = commonTlsContext.getTlsCertificates(0); diff --git a/xds/src/main/java/io/grpc/xds/internal/sds/ServerSslContextProviderFactory.java b/xds/src/main/java/io/grpc/xds/internal/sds/ServerSslContextProviderFactory.java index 1cd2cfa8e9b..6ae5308930e 100644 --- a/xds/src/main/java/io/grpc/xds/internal/sds/ServerSslContextProviderFactory.java +++ b/xds/src/main/java/io/grpc/xds/internal/sds/ServerSslContextProviderFactory.java @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.grpc.xds.Bootstrapper; import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; +import io.grpc.xds.internal.certprovider.CertProviderServerSslContextProvider; import io.grpc.xds.internal.sds.ReferenceCountingMap.ValueFactory; import java.io.IOException; import java.util.concurrent.Executors; @@ -29,6 +30,20 @@ final class ServerSslContextProviderFactory implements ValueFactory { + private final Bootstrapper bootstrapper; + private final CertProviderServerSslContextProvider.Factory + certProviderServerSslContextProviderFactory; + + ServerSslContextProviderFactory() { + this(Bootstrapper.getInstance(), CertProviderServerSslContextProvider.Factory.getInstance()); + } + + ServerSslContextProviderFactory( + Bootstrapper bootstrapper, CertProviderServerSslContextProvider.Factory factory) { + this.bootstrapper = bootstrapper; + this.certProviderServerSslContextProviderFactory = factory; + } + /** Creates a SslContextProvider from the given DownstreamTlsContext. */ @Override public SslContextProvider create( @@ -54,8 +69,17 @@ public SslContextProvider create( } catch (IOException ioe) { throw new RuntimeException(ioe); } + } else if (CommonTlsContextUtil.hasCertProviderInstance( + downstreamTlsContext.getCommonTlsContext())) { + try { + return certProviderServerSslContextProviderFactory.getProvider( + downstreamTlsContext, + bootstrapper.readBootstrap().getNode().toEnvoyProtoNode(), + bootstrapper.readBootstrap().getCertProviders()); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } } - throw new UnsupportedOperationException( - "DownstreamTlsContext to have all filenames or all SdsConfig"); + throw new UnsupportedOperationException("Unsupported configurations in DownstreamTlsContext!"); } } diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/CommonCertProviderTestUtils.java b/xds/src/test/java/io/grpc/xds/internal/certprovider/CommonCertProviderTestUtils.java index 34347b68e2d..ce494e5efa9 100644 --- a/xds/src/test/java/io/grpc/xds/internal/certprovider/CommonCertProviderTestUtils.java +++ b/xds/src/test/java/io/grpc/xds/internal/certprovider/CommonCertProviderTestUtils.java @@ -54,7 +54,8 @@ public class CommonCertProviderTestUtils { "-+END\\s+.*PRIVATE\\s+KEY[^-]*-+", // Footer Pattern.CASE_INSENSITIVE); - static Bootstrapper.BootstrapInfo getTestBootstrapInfo() throws IOException { + /** Creates a test bootstrap info object. */ + public static Bootstrapper.BootstrapInfo getTestBootstrapInfo() throws IOException { String rawData = "{\n" + " \"xds_servers\": [],\n" diff --git a/xds/src/test/java/io/grpc/xds/internal/certprovider/TestCertificateProvider.java b/xds/src/test/java/io/grpc/xds/internal/certprovider/TestCertificateProvider.java index 406ae4f0bb2..9253d071fba 100644 --- a/xds/src/test/java/io/grpc/xds/internal/certprovider/TestCertificateProvider.java +++ b/xds/src/test/java/io/grpc/xds/internal/certprovider/TestCertificateProvider.java @@ -22,12 +22,13 @@ public class TestCertificateProvider extends CertificateProvider { int closeCalled = 0; int startCalled = 0; - TestCertificateProvider( - DistributorWatcher watcher, - boolean notifyCertUpdates, - Object config, - CertificateProviderProvider certificateProviderProvider, - boolean throwExceptionForCertUpdates) { + /** Creates a TestCertificateProvider instance. */ + public TestCertificateProvider( + DistributorWatcher watcher, + boolean notifyCertUpdates, + Object config, + CertificateProviderProvider certificateProviderProvider, + boolean throwExceptionForCertUpdates) { super(watcher, notifyCertUpdates); if (throwExceptionForCertUpdates && notifyCertUpdates) { throw new UnsupportedOperationException("Provider does not support Certificate Updates."); diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/ClientSslContextProviderFactoryTest.java b/xds/src/test/java/io/grpc/xds/internal/sds/ClientSslContextProviderFactoryTest.java index 8fe011f0eed..c1c2b30e68b 100644 --- a/xds/src/test/java/io/grpc/xds/internal/sds/ClientSslContextProviderFactoryTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/sds/ClientSslContextProviderFactoryTest.java @@ -20,20 +20,54 @@ import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CA_PEM_FILE; import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_KEY_FILE; import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_PEM_FILE; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import com.google.common.collect.ImmutableSet; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; +import io.envoyproxy.envoy.type.matcher.v3.StringMatcher; +import io.grpc.xds.Bootstrapper; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; +import io.grpc.xds.internal.certprovider.CertProviderClientSslContextProvider; +import io.grpc.xds.internal.certprovider.CertificateProvider; +import io.grpc.xds.internal.certprovider.CertificateProviderProvider; +import io.grpc.xds.internal.certprovider.CertificateProviderRegistry; +import io.grpc.xds.internal.certprovider.CertificateProviderStore; +import io.grpc.xds.internal.certprovider.CommonCertProviderTestUtils; +import io.grpc.xds.internal.certprovider.TestCertificateProvider; +import java.io.IOException; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; /** Unit tests for {@link ClientSslContextProviderFactory}. */ @RunWith(JUnit4.class) public class ClientSslContextProviderFactoryTest { - ClientSslContextProviderFactory clientSslContextProviderFactory = - new ClientSslContextProviderFactory(); + Bootstrapper bootstrapper; + CertificateProviderRegistry certificateProviderRegistry; + CertificateProviderStore certificateProviderStore; + CertProviderClientSslContextProvider.Factory certProviderClientSslContextProviderFactory; + ClientSslContextProviderFactory clientSslContextProviderFactory; + + @Before + public void setUp() { + bootstrapper = mock(Bootstrapper.class); + certificateProviderRegistry = new CertificateProviderRegistry(); + certificateProviderStore = new CertificateProviderStore(certificateProviderRegistry); + certProviderClientSslContextProviderFactory = + new CertProviderClientSslContextProvider.Factory(certificateProviderStore); + clientSslContextProviderFactory = + new ClientSslContextProviderFactory( + bootstrapper, certProviderClientSslContextProviderFactory); + } @Test public void createSslContextProvider_allFilenames() { @@ -55,13 +89,10 @@ public void createSslContextProvider_sdsConfigForTlsCert_expectException() { CommonTlsContextTestsUtil.buildUpstreamTlsContext(commonTlsContext); try { - SslContextProvider unused = - clientSslContextProviderFactory.create(upstreamTlsContext); + clientSslContextProviderFactory.create(upstreamTlsContext); Assert.fail("no exception thrown"); - } catch (UnsupportedOperationException expected) { - assertThat(expected) - .hasMessageThat() - .isEqualTo("UpstreamTlsContext to have all filenames or all SdsConfig"); + } catch (IllegalArgumentException expected) { + assertThat(expected).hasMessageThat().isEqualTo("unexpected TlsCertificateSdsSecretConfigs"); } } @@ -80,10 +111,188 @@ public void createSslContextProvider_sdsConfigForCertValidationContext_expectExc SslContextProvider unused = clientSslContextProviderFactory.create(upstreamTlsContext); Assert.fail("no exception thrown"); + } catch (IllegalStateException expected) { + assertThat(expected).hasMessageThat().isEqualTo("incorrect ValidationContextTypeCase"); + } + } + + @Test + public void createCertProviderClientSslContextProvider() throws IOException { + final CertificateProvider.DistributorWatcher[] watcherCaptor = + new CertificateProvider.DistributorWatcher[1]; + createAndRegisterProviderProvider(certificateProviderRegistry, watcherCaptor, "testca", 0); + UpstreamTlsContext upstreamTlsContext = + CommonTlsContextTestsUtil.buildUpstreamTlsContextForCertProviderInstance( + "gcp_id", + "cert-default", + "gcp_id", + "root-default", + /* alpnProtocols= */ null, + /* staticCertValidationContext= */ null); + + Bootstrapper.BootstrapInfo bootstrapInfo = CommonCertProviderTestUtils.getTestBootstrapInfo(); + when(bootstrapper.readBootstrap()).thenReturn(bootstrapInfo); + SslContextProvider sslContextProvider = + clientSslContextProviderFactory.create(upstreamTlsContext); + assertThat(sslContextProvider).isInstanceOf(CertProviderClientSslContextProvider.class); + verifyWatcher(sslContextProvider, watcherCaptor[0]); + } + + @Test + public void createCertProviderClientSslContextProvider_onlyRootCert() throws IOException { + final CertificateProvider.DistributorWatcher[] watcherCaptor = + new CertificateProvider.DistributorWatcher[1]; + createAndRegisterProviderProvider(certificateProviderRegistry, watcherCaptor, "testca", 0); + UpstreamTlsContext upstreamTlsContext = + CommonTlsContextTestsUtil.buildUpstreamTlsContextForCertProviderInstance( + /* certInstanceName= */ null, + /* certName= */ null, + "gcp_id", + "root-default", + /* alpnProtocols= */ null, + /* staticCertValidationContext= */ null); + + Bootstrapper.BootstrapInfo bootstrapInfo = CommonCertProviderTestUtils.getTestBootstrapInfo(); + when(bootstrapper.readBootstrap()).thenReturn(bootstrapInfo); + SslContextProvider sslContextProvider = + clientSslContextProviderFactory.create(upstreamTlsContext); + assertThat(sslContextProvider).isInstanceOf(CertProviderClientSslContextProvider.class); + verifyWatcher(sslContextProvider, watcherCaptor[0]); + } + + @Test + public void createCertProviderClientSslContextProvider_withStaticContext() throws IOException { + final CertificateProvider.DistributorWatcher[] watcherCaptor = + new CertificateProvider.DistributorWatcher[1]; + createAndRegisterProviderProvider(certificateProviderRegistry, watcherCaptor, "testca", 0); + CertificateValidationContext staticCertValidationContext = + CertificateValidationContext.newBuilder() + .addAllMatchSubjectAltNames( + ImmutableSet.of( + StringMatcher.newBuilder().setExact("foo").build(), + StringMatcher.newBuilder().setExact("bar").build())) + .build(); + UpstreamTlsContext upstreamTlsContext = + CommonTlsContextTestsUtil.buildUpstreamTlsContextForCertProviderInstance( + /* certInstanceName= */ null, + /* certName= */ null, + "gcp_id", + "root-default", + /* alpnProtocols= */ null, + staticCertValidationContext); + + Bootstrapper.BootstrapInfo bootstrapInfo = CommonCertProviderTestUtils.getTestBootstrapInfo(); + when(bootstrapper.readBootstrap()).thenReturn(bootstrapInfo); + SslContextProvider sslContextProvider = + clientSslContextProviderFactory.create(upstreamTlsContext); + assertThat(sslContextProvider).isInstanceOf(CertProviderClientSslContextProvider.class); + verifyWatcher(sslContextProvider, watcherCaptor[0]); + } + + @Test + public void createCertProviderClientSslContextProvider_2providers() throws IOException { + final CertificateProvider.DistributorWatcher[] watcherCaptor = + new CertificateProvider.DistributorWatcher[2]; + createAndRegisterProviderProvider(certificateProviderRegistry, watcherCaptor, "testca", 0); + + createAndRegisterProviderProvider( + certificateProviderRegistry, watcherCaptor, "file_watcher", 1); + + UpstreamTlsContext upstreamTlsContext = + CommonTlsContextTestsUtil.buildUpstreamTlsContextForCertProviderInstance( + "gcp_id", + "cert-default", + "file_provider", + "root-default", + /* alpnProtocols= */ null, + /* staticCertValidationContext= */ null); + + Bootstrapper.BootstrapInfo bootstrapInfo = CommonCertProviderTestUtils.getTestBootstrapInfo(); + when(bootstrapper.readBootstrap()).thenReturn(bootstrapInfo); + SslContextProvider sslContextProvider = + clientSslContextProviderFactory.create(upstreamTlsContext); + assertThat(sslContextProvider).isInstanceOf(CertProviderClientSslContextProvider.class); + verifyWatcher(sslContextProvider, watcherCaptor[0]); + verifyWatcher(sslContextProvider, watcherCaptor[1]); + } + + @Test + public void createCertProviderClientSslContextProvider_ioException() throws IOException { + UpstreamTlsContext upstreamTlsContext = + CommonTlsContextTestsUtil.buildUpstreamTlsContextForCertProviderInstance( + "gcp_id", + "cert-default", + "gcp_id", + "root-default", + /* alpnProtocols= */ null, + /* staticCertValidationContext= */ null); + when(bootstrapper.readBootstrap()).thenThrow(new IOException("test IOException")); + try { + clientSslContextProviderFactory.create(upstreamTlsContext); + Assert.fail("no exception thrown"); + } catch (RuntimeException expected) { + assertThat(expected).hasMessageThat().isEqualTo("java.io.IOException: test IOException"); + } + } + + @Test + public void createEmptyCommonTlsContext_exception() throws IOException { + UpstreamTlsContext upstreamTlsContext = + CommonTlsContextTestsUtil.buildUpstreamTlsContextFromFilenames(null, null, null); + try { + clientSslContextProviderFactory.create(upstreamTlsContext); + Assert.fail("no exception thrown"); } catch (UnsupportedOperationException expected) { assertThat(expected) .hasMessageThat() - .isEqualTo("UpstreamTlsContext to have all filenames or all SdsConfig"); + .isEqualTo("Unsupported configurations in UpstreamTlsContext!"); } } + + @Test + public void createNullCommonTlsContext_exception() throws IOException { + UpstreamTlsContext upstreamTlsContext = new UpstreamTlsContext(null); + try { + clientSslContextProviderFactory.create(upstreamTlsContext); + Assert.fail("no exception thrown"); + } catch (NullPointerException expected) { + assertThat(expected) + .hasMessageThat() + .isEqualTo("upstreamTlsContext should have CommonTlsContext"); + } + } + + static void createAndRegisterProviderProvider( + CertificateProviderRegistry certificateProviderRegistry, + final CertificateProvider.DistributorWatcher[] watcherCaptor, + String testca, + final int i) { + final CertificateProviderProvider mockProviderProviderTestCa = + mock(CertificateProviderProvider.class); + when(mockProviderProviderTestCa.getName()).thenReturn(testca); + + when(mockProviderProviderTestCa.createCertificateProvider( + any(Object.class), any(CertificateProvider.DistributorWatcher.class), eq(true))) + .thenAnswer( + new Answer() { + @Override + public CertificateProvider answer(InvocationOnMock invocation) throws Throwable { + Object[] args = invocation.getArguments(); + CertificateProvider.DistributorWatcher watcher = + (CertificateProvider.DistributorWatcher) args[1]; + watcherCaptor[i] = watcher; + return new TestCertificateProvider( + watcher, true, args[0], mockProviderProviderTestCa, false); + } + }); + certificateProviderRegistry.register(mockProviderProviderTestCa); + } + + static void verifyWatcher( + SslContextProvider sslContextProvider, CertificateProvider.DistributorWatcher watcherCaptor) { + assertThat(watcherCaptor).isNotNull(); + assertThat(watcherCaptor.getDownstreamWatchers()).hasSize(1); + assertThat(watcherCaptor.getDownstreamWatchers().iterator().next()) + .isSameInstanceAs(sslContextProvider); + } } diff --git a/xds/src/test/java/io/grpc/xds/internal/sds/ServerSslContextProviderFactoryTest.java b/xds/src/test/java/io/grpc/xds/internal/sds/ServerSslContextProviderFactoryTest.java index 1c2b58f7b25..4ab957bdb43 100644 --- a/xds/src/test/java/io/grpc/xds/internal/sds/ServerSslContextProviderFactoryTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/sds/ServerSslContextProviderFactoryTest.java @@ -17,13 +17,28 @@ package io.grpc.xds.internal.sds; import static com.google.common.truth.Truth.assertThat; +import static io.grpc.xds.internal.sds.ClientSslContextProviderFactoryTest.createAndRegisterProviderProvider; +import static io.grpc.xds.internal.sds.ClientSslContextProviderFactoryTest.verifyWatcher; import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CA_PEM_FILE; import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_KEY_FILE; import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import com.google.common.collect.ImmutableSet; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; +import io.envoyproxy.envoy.type.matcher.v3.StringMatcher; +import io.grpc.xds.Bootstrapper; import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; +import io.grpc.xds.internal.certprovider.CertProviderServerSslContextProvider; +import io.grpc.xds.internal.certprovider.CertificateProvider; +import io.grpc.xds.internal.certprovider.CertificateProviderRegistry; +import io.grpc.xds.internal.certprovider.CertificateProviderStore; +import io.grpc.xds.internal.certprovider.CommonCertProviderTestUtils; +import java.io.IOException; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -32,8 +47,23 @@ @RunWith(JUnit4.class) public class ServerSslContextProviderFactoryTest { - ServerSslContextProviderFactory serverSslContextProviderFactory = - new ServerSslContextProviderFactory(); + Bootstrapper bootstrapper; + CertificateProviderRegistry certificateProviderRegistry; + CertificateProviderStore certificateProviderStore; + CertProviderServerSslContextProvider.Factory certProviderServerSslContextProviderFactory; + ServerSslContextProviderFactory serverSslContextProviderFactory; + + @Before + public void setUp() { + bootstrapper = mock(Bootstrapper.class); + certificateProviderRegistry = new CertificateProviderRegistry(); + certificateProviderStore = new CertificateProviderStore(certificateProviderRegistry); + certProviderServerSslContextProviderFactory = + new CertProviderServerSslContextProvider.Factory(certificateProviderStore); + serverSslContextProviderFactory = + new ServerSslContextProviderFactory( + bootstrapper, certProviderServerSslContextProviderFactory); + } @Test public void createSslContextProvider_allFilenames() { @@ -59,10 +89,8 @@ public void createSslContextProvider_sdsConfigForTlsCert_expectException() { SslContextProvider unused = serverSslContextProviderFactory.create(downstreamTlsContext); Assert.fail("no exception thrown"); - } catch (UnsupportedOperationException expected) { - assertThat(expected) - .hasMessageThat() - .isEqualTo("DownstreamTlsContext to have all filenames or all SdsConfig"); + } catch (IllegalArgumentException expected) { + assertThat(expected).hasMessageThat().isEqualTo("unexpected TlsCertificateSdsSecretConfigs"); } } @@ -79,10 +107,159 @@ public void createSslContextProvider_sdsConfigForCertValidationContext_expectExc SslContextProvider unused = serverSslContextProviderFactory.create(downstreamTlsContext); Assert.fail("no exception thrown"); + } catch (IllegalStateException expected) { + assertThat(expected).hasMessageThat().isEqualTo("incorrect ValidationContextTypeCase"); + } + } + + @Test + public void createCertProviderServerSslContextProvider() throws IOException { + final CertificateProvider.DistributorWatcher[] watcherCaptor = + new CertificateProvider.DistributorWatcher[1]; + createAndRegisterProviderProvider(certificateProviderRegistry, watcherCaptor, "testca", 0); + DownstreamTlsContext downstreamTlsContext = + CommonTlsContextTestsUtil.buildDownstreamTlsContextForCertProviderInstance( + "gcp_id", + "cert-default", + "gcp_id", + "root-default", + /* alpnProtocols= */ null, + /* staticCertValidationContext= */ null, + /* requireClientCert= */ true); + + Bootstrapper.BootstrapInfo bootstrapInfo = CommonCertProviderTestUtils.getTestBootstrapInfo(); + when(bootstrapper.readBootstrap()).thenReturn(bootstrapInfo); + SslContextProvider sslContextProvider = + serverSslContextProviderFactory.create(downstreamTlsContext); + assertThat(sslContextProvider).isInstanceOf(CertProviderServerSslContextProvider.class); + verifyWatcher(sslContextProvider, watcherCaptor[0]); + } + + @Test + public void createCertProviderServerSslContextProvider_onlyCertInstance() throws IOException { + final CertificateProvider.DistributorWatcher[] watcherCaptor = + new CertificateProvider.DistributorWatcher[1]; + createAndRegisterProviderProvider(certificateProviderRegistry, watcherCaptor, "testca", 0); + DownstreamTlsContext downstreamTlsContext = + CommonTlsContextTestsUtil.buildDownstreamTlsContextForCertProviderInstance( + "gcp_id", + "cert-default", + /* rootInstanceName= */ null, + /* rootCertName= */ null, + /* alpnProtocols= */ null, + /* staticCertValidationContext= */ null, + /* requireClientCert= */ true); + + Bootstrapper.BootstrapInfo bootstrapInfo = CommonCertProviderTestUtils.getTestBootstrapInfo(); + when(bootstrapper.readBootstrap()).thenReturn(bootstrapInfo); + SslContextProvider sslContextProvider = + serverSslContextProviderFactory.create(downstreamTlsContext); + assertThat(sslContextProvider).isInstanceOf(CertProviderServerSslContextProvider.class); + verifyWatcher(sslContextProvider, watcherCaptor[0]); + } + + @Test + public void createCertProviderServerSslContextProvider_withStaticContext() throws IOException { + final CertificateProvider.DistributorWatcher[] watcherCaptor = + new CertificateProvider.DistributorWatcher[1]; + createAndRegisterProviderProvider(certificateProviderRegistry, watcherCaptor, "testca", 0); + CertificateValidationContext staticCertValidationContext = + CertificateValidationContext.newBuilder() + .addAllMatchSubjectAltNames( + ImmutableSet.of( + StringMatcher.newBuilder().setExact("foo").build(), + StringMatcher.newBuilder().setExact("bar").build())) + .build(); + DownstreamTlsContext downstreamTlsContext = + CommonTlsContextTestsUtil.buildDownstreamTlsContextForCertProviderInstance( + "gcp_id", + "cert-default", + "gcp_id", + "root-default", + /* alpnProtocols= */ null, + staticCertValidationContext, + /* requireClientCert= */ true); + + Bootstrapper.BootstrapInfo bootstrapInfo = CommonCertProviderTestUtils.getTestBootstrapInfo(); + when(bootstrapper.readBootstrap()).thenReturn(bootstrapInfo); + SslContextProvider sslContextProvider = + serverSslContextProviderFactory.create(downstreamTlsContext); + assertThat(sslContextProvider).isInstanceOf(CertProviderServerSslContextProvider.class); + verifyWatcher(sslContextProvider, watcherCaptor[0]); + } + + @Test + public void createCertProviderServerSslContextProvider_2providers() throws IOException { + final CertificateProvider.DistributorWatcher[] watcherCaptor = + new CertificateProvider.DistributorWatcher[2]; + createAndRegisterProviderProvider(certificateProviderRegistry, watcherCaptor, "testca", 0); + + createAndRegisterProviderProvider( + certificateProviderRegistry, watcherCaptor, "file_watcher", 1); + + DownstreamTlsContext downstreamTlsContext = + CommonTlsContextTestsUtil.buildDownstreamTlsContextForCertProviderInstance( + "gcp_id", + "cert-default", + "file_provider", + "root-default", + /* alpnProtocols= */ null, + /* staticCertValidationContext= */ null, + /* requireClientCert= */ true); + + Bootstrapper.BootstrapInfo bootstrapInfo = CommonCertProviderTestUtils.getTestBootstrapInfo(); + when(bootstrapper.readBootstrap()).thenReturn(bootstrapInfo); + SslContextProvider sslContextProvider = + serverSslContextProviderFactory.create(downstreamTlsContext); + assertThat(sslContextProvider).isInstanceOf(CertProviderServerSslContextProvider.class); + verifyWatcher(sslContextProvider, watcherCaptor[0]); + verifyWatcher(sslContextProvider, watcherCaptor[1]); + } + + @Test + public void createCertProviderServerSslContextProvider_ioException() throws IOException { + DownstreamTlsContext downstreamTlsContext = + CommonTlsContextTestsUtil.buildDownstreamTlsContextForCertProviderInstance( + "gcp_id", + "cert-default", + "gcp_id", + "root-default", + /* alpnProtocols= */ null, + /* staticCertValidationContext= */ null, + /* requireClientCert= */ true); + when(bootstrapper.readBootstrap()).thenThrow(new IOException("test IOException")); + try { + serverSslContextProviderFactory.create(downstreamTlsContext); + Assert.fail("no exception thrown"); + } catch (RuntimeException expected) { + assertThat(expected).hasMessageThat().isEqualTo("java.io.IOException: test IOException"); + } + } + + @Test + public void createEmptyCommonTlsContext_exception() throws IOException { + DownstreamTlsContext downstreamTlsContext = + CommonTlsContextTestsUtil.buildDownstreamTlsContextFromFilenames(null, null, null); + try { + serverSslContextProviderFactory.create(downstreamTlsContext); + Assert.fail("no exception thrown"); } catch (UnsupportedOperationException expected) { assertThat(expected) .hasMessageThat() - .isEqualTo("DownstreamTlsContext to have all filenames or all SdsConfig"); + .isEqualTo("Unsupported configurations in DownstreamTlsContext!"); + } + } + + @Test + public void createNullCommonTlsContext_exception() throws IOException { + DownstreamTlsContext downstreamTlsContext = new DownstreamTlsContext(null, true); + try { + serverSslContextProviderFactory.create(downstreamTlsContext); + Assert.fail("no exception thrown"); + } catch (NullPointerException expected) { + assertThat(expected) + .hasMessageThat() + .isEqualTo("downstreamTlsContext should have CommonTlsContext"); } } } From f36f0e90462dcb86279b232fb4c3cbca8f6bd873 Mon Sep 17 00:00:00 2001 From: ZHANG Dapeng Date: Mon, 24 Aug 2020 10:13:14 -0700 Subject: [PATCH 78/88] core: disable usage of PendingCall due to a bug #7259 failed internally and there seems to be a bug. Temporarily disable PendingCall. --- core/src/main/java/io/grpc/internal/ManagedChannelImpl.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java index 6d0160b1abd..9af74d046bf 100644 --- a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java +++ b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java @@ -918,6 +918,9 @@ private RealChannel(String authority) { @Override public ClientCall newCall( MethodDescriptor method, CallOptions callOptions) { + if (true) { // FIXME(zdapeng): there is a bug for using PendingCall. Temporarily disable it. + return newClientCall(method, callOptions); + } if (configSelector.get() != INITIAL_PENDING_SELECTOR) { return newClientCall(method, callOptions); } From 03f83bbac25b08ff89d435428101143d0a17d000 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Mon, 24 Aug 2020 07:26:21 -0700 Subject: [PATCH 79/88] api: Fix NameResolver.ConfigOrError javadoc It appears getAttributes() javadoc was accidentally copied when ConfigOrError was moved in commit 0244418d2. This restores the documentation from before the move. --- api/src/main/java/io/grpc/NameResolver.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/io/grpc/NameResolver.java b/api/src/main/java/io/grpc/NameResolver.java index 8d4b6472e0f..eb70eae31fd 100644 --- a/api/src/main/java/io/grpc/NameResolver.java +++ b/api/src/main/java/io/grpc/NameResolver.java @@ -842,12 +842,12 @@ public ResolutionResult build() { } } } - + /** - * Gets the attributes associated with the addresses resolved by name resolution. If there are - * no attributes, {@link Attributes#EMPTY} will be returned. + * Represents either a successfully parsed service config, containing all necessary parts to be + * later applied by the channel, or a Status containing the error encountered while parsing. * - * @since 1.21.0 + * @since 1.20.0 */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770") public static final class ConfigOrError { From 292f3b954af6af2f6097f729c9794aa2d4ffca15 Mon Sep 17 00:00:00 2001 From: Chengyuan Zhang Date: Mon, 24 Aug 2020 15:42:18 -0700 Subject: [PATCH 80/88] xds: implement routing in xDS resolver with config selector API (#7275) Name resolver implementation for performing xDS request routing before the call is made: the resolver emits a config selector to the Channel to let calls make routing decision before delegating to the corresponding cluster load balancer's picker. This is a branched xDS name resolver implementation. It will replace the existing xDS resolver once the Channel's integration for using config selector is done. --- .../grpc/xds/ClusterManagerLoadBalancer.java | 7 +- .../main/java/io/grpc/xds/EnvoyProtoData.java | 3 + xds/src/main/java/io/grpc/xds/RouteMatch.java | 1 + xds/src/main/java/io/grpc/xds/XdsClient.java | 6 + .../java/io/grpc/xds/XdsNameResolver2.java | 374 ++++++++++++ .../io/grpc/xds/XdsNameResolverProvider2.java | 132 +++++ .../xds/ClusterManagerLoadBalancerTest.java | 2 +- .../io/grpc/xds/XdsNameResolver2Test.java | 554 ++++++++++++++++++ 8 files changed, 1073 insertions(+), 6 deletions(-) create mode 100644 xds/src/main/java/io/grpc/xds/XdsNameResolver2.java create mode 100644 xds/src/main/java/io/grpc/xds/XdsNameResolverProvider2.java create mode 100644 xds/src/test/java/io/grpc/xds/XdsNameResolver2Test.java diff --git a/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java index 589a42e04c9..f8901fb0ebe 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java @@ -24,7 +24,6 @@ import static io.grpc.xds.XdsSubchannelPickers.BUFFER_PICKER; import com.google.common.annotations.VisibleForTesting; -import io.grpc.CallOptions; import io.grpc.ConnectivityState; import io.grpc.InternalLogId; import io.grpc.LoadBalancer; @@ -51,9 +50,6 @@ class ClusterManagerLoadBalancer extends LoadBalancer { @VisibleForTesting static final int DELAYED_CHILD_DELETION_TIME_MINUTES = 15; - @VisibleForTesting - static final CallOptions.Key ROUTING_CLUSTER_NAME_KEY = - CallOptions.Key.create("io.grpc.xds.ROUTING_CLUSTER_NAME_KEY"); private final Map childLbStates = new HashMap<>(); private final Helper helper; @@ -148,7 +144,8 @@ private void updateOverallBalancingState() { SubchannelPicker picker = new SubchannelPicker() { @Override public PickResult pickSubchannel(PickSubchannelArgs args) { - String clusterName = args.getCallOptions().getOption(ROUTING_CLUSTER_NAME_KEY); + String clusterName = + args.getCallOptions().getOption(XdsNameResolver2.CLUSTER_SELECTION_KEY); SubchannelPicker delegate = childPickers.get(clusterName); if (delegate == null) { return diff --git a/xds/src/main/java/io/grpc/xds/EnvoyProtoData.java b/xds/src/main/java/io/grpc/xds/EnvoyProtoData.java index e1872422da1..dc96aa0f69b 100644 --- a/xds/src/main/java/io/grpc/xds/EnvoyProtoData.java +++ b/xds/src/main/java/io/grpc/xds/EnvoyProtoData.java @@ -1153,6 +1153,9 @@ static StructOrError fromEnvoyProtoRouteAction( case WEIGHTED_CLUSTERS: List clusterWeights = proto.getWeightedClusters().getClustersList(); + if (clusterWeights.isEmpty()) { + return StructOrError.fromError("No cluster found in weighted cluster list"); + } weightedClusters = new ArrayList<>(); for (io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight clusterWeight : clusterWeights) { diff --git a/xds/src/main/java/io/grpc/xds/RouteMatch.java b/xds/src/main/java/io/grpc/xds/RouteMatch.java index 4fdbe7c29a7..dc5ff221de1 100644 --- a/xds/src/main/java/io/grpc/xds/RouteMatch.java +++ b/xds/src/main/java/io/grpc/xds/RouteMatch.java @@ -45,6 +45,7 @@ final class RouteMatch { this.headerMatchers = headerMatchers; } + @VisibleForTesting RouteMatch(@Nullable String pathPrefixMatch, @Nullable String pathExactMatch) { this( new PathMatcher(pathExactMatch, pathPrefixMatch, null), diff --git a/xds/src/main/java/io/grpc/xds/XdsClient.java b/xds/src/main/java/io/grpc/xds/XdsClient.java index 69a12843003..7efb2a2937e 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClient.java +++ b/xds/src/main/java/io/grpc/xds/XdsClient.java @@ -29,6 +29,7 @@ import io.grpc.Status; import io.grpc.alts.GoogleDefaultChannelBuilder; import io.grpc.internal.ObjectPool; +import io.grpc.xds.Bootstrapper.BootstrapInfo; import io.grpc.xds.Bootstrapper.ChannelCreds; import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.EnvoyProtoData.DropOverload; @@ -549,6 +550,7 @@ void removeClientStats(String clusterName, @Nullable String clusterServiceName) throw new UnsupportedOperationException(); } + // TODO(chengyuanzhang): eliminate this factory abstract static class XdsClientFactory { abstract XdsClient createXdsClient(); } @@ -685,4 +687,8 @@ boolean isUseProtocolV3() { return useProtocolV3; } } + + interface XdsClientPoolFactory { + ObjectPool newXdsClientObjectPool(BootstrapInfo bootstrapInfo); + } } diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolver2.java b/xds/src/main/java/io/grpc/xds/XdsNameResolver2.java new file mode 100644 index 00000000000..123ddd53f48 --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/XdsNameResolver2.java @@ -0,0 +1,374 @@ +/* + * Copyright 2019 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Sets; +import com.google.gson.Gson; +import io.grpc.Attributes; +import io.grpc.CallOptions; +import io.grpc.InternalConfigSelector; +import io.grpc.InternalLogId; +import io.grpc.LoadBalancer.PickSubchannelArgs; +import io.grpc.Metadata; +import io.grpc.NameResolver; +import io.grpc.Status; +import io.grpc.SynchronizationContext; +import io.grpc.internal.GrpcUtil; +import io.grpc.internal.ObjectPool; +import io.grpc.xds.Bootstrapper.BootstrapInfo; +import io.grpc.xds.EnvoyProtoData.ClusterWeight; +import io.grpc.xds.EnvoyProtoData.Route; +import io.grpc.xds.EnvoyProtoData.RouteAction; +import io.grpc.xds.ThreadSafeRandom.ThreadSafeRandomImpl; +import io.grpc.xds.XdsClient.ConfigUpdate; +import io.grpc.xds.XdsClient.ConfigWatcher; +import io.grpc.xds.XdsClient.XdsClientPoolFactory; +import io.grpc.xds.XdsLogger.XdsLogLevel; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * A {@link NameResolver} for resolving gRPC target names with "xds:" scheme. + * + *

Resolving a gRPC target involves contacting the control plane management server via xDS + * protocol to retrieve service information and produce a service config to the caller. + * + * @see XdsNameResolverProvider2 + */ +final class XdsNameResolver2 extends NameResolver { + + static final CallOptions.Key CLUSTER_SELECTION_KEY = + CallOptions.Key.create("io.grpc.xds.CLUSTER_SELECTION_KEY"); + + private final XdsLogger logger; + private final String authority; + private final ServiceConfigParser serviceConfigParser; + private final SynchronizationContext syncContext; + private final Bootstrapper bootstrapper; + private final XdsClientPoolFactory xdsClientPoolFactory; + private final ThreadSafeRandom random; + private final ConcurrentMap clusterRefs = new ConcurrentHashMap<>(); + private final ConfigSelector configSelector = new ConfigSelector(); + + private volatile List routes = Collections.emptyList(); + private Listener2 listener; + private ObjectPool xdsClientPool; + private XdsClient xdsClient; + + XdsNameResolver2(String name, + ServiceConfigParser serviceConfigParser, + SynchronizationContext syncContext, + XdsClientPoolFactory xdsClientPoolFactory) { + this(name, serviceConfigParser, syncContext, Bootstrapper.getInstance(), + xdsClientPoolFactory, ThreadSafeRandomImpl.instance); + } + + XdsNameResolver2( + String name, + ServiceConfigParser serviceConfigParser, + SynchronizationContext syncContext, + Bootstrapper bootstrapper, + XdsClientPoolFactory xdsClientPoolFactory, + ThreadSafeRandom random) { + authority = GrpcUtil.checkAuthority(checkNotNull(name, "name")); + this.serviceConfigParser = checkNotNull(serviceConfigParser, "serviceConfigParser"); + this.syncContext = checkNotNull(syncContext, "syncContext"); + this.bootstrapper = checkNotNull(bootstrapper, "bootstrapper"); + this.xdsClientPoolFactory = checkNotNull(xdsClientPoolFactory, "xdsClientPoolFactory"); + this.random = checkNotNull(random, "random"); + logger = XdsLogger.withLogId(InternalLogId.allocate("xds-resolver", name)); + logger.log(XdsLogLevel.INFO, "Created resolver for {0}", name); + } + + @Override + public String getServiceAuthority() { + return authority; + } + + @Override + public void start(Listener2 listener) { + this.listener = checkNotNull(listener, "listener"); + BootstrapInfo bootstrapInfo; + try { + bootstrapInfo = bootstrapper.readBootstrap(); + } catch (Exception e) { + listener.onError( + Status.UNAVAILABLE.withDescription("Failed to load xDS bootstrap").withCause(e)); + return; + } + xdsClientPool = xdsClientPoolFactory.newXdsClientObjectPool(bootstrapInfo); + xdsClient = xdsClientPool.getObject(); + xdsClient.watchConfigData(authority, new ConfigWatcherImpl()); + } + + @Override + public void shutdown() { + logger.log(XdsLogLevel.INFO, "Shutdown"); + if (xdsClient != null) { + xdsClient = xdsClientPool.returnObject(xdsClient); + } + } + + @VisibleForTesting + static Map generateServiceConfigWithMethodTimeoutConfig(long timeoutNano) { + String timeout = timeoutNano / 1_000_000_000.0 + "s"; + Map methodConfig = new HashMap<>(); + methodConfig.put( + "name", Collections.singletonList(Collections.emptyMap())); + methodConfig.put("timeout", timeout); + return Collections.singletonMap( + "methodConfig", Collections.singletonList(Collections.unmodifiableMap(methodConfig))); + } + + @VisibleForTesting + static Map generateServiceConfigWithLoadBalancingConfig(Collection clusters) { + Map childPolicy = new HashMap<>(); + for (String cluster : clusters) { + List>> lbPolicy = + Collections.singletonList( + Collections.singletonMap( + "cds_experimental", Collections.singletonMap("cluster", cluster))); + childPolicy.put(cluster, Collections.singletonMap("lbPolicy", lbPolicy)); + } + return Collections.singletonMap("loadBalancingConfig", + Collections.singletonList( + Collections.singletonMap( + "cluster_manager_experimental", Collections.singletonMap( + "childPolicy", Collections.unmodifiableMap(childPolicy))))); + } + + @VisibleForTesting + XdsClient getXdsClient() { + return xdsClient; + } + + private void updateResolutionResult() { + Map rawServiceConfig = + generateServiceConfigWithLoadBalancingConfig(clusterRefs.keySet()); + if (logger.isLoggable(XdsLogLevel.INFO)) { + logger.log( + XdsLogLevel.INFO, "Generated service config:\n{0}", new Gson().toJson(rawServiceConfig)); + } + ConfigOrError parsedServiceConfig = serviceConfigParser.parseServiceConfig(rawServiceConfig); + Attributes attrs = + Attributes.newBuilder() + .set(XdsAttributes.XDS_CLIENT_POOL, xdsClientPool) + .set(InternalConfigSelector.KEY, configSelector) + .build(); + ResolutionResult result = + ResolutionResult.newBuilder() + .setAttributes(attrs) + .setServiceConfig(parsedServiceConfig) + .build(); + listener.onResult(result); + } + + private final class ConfigSelector extends InternalConfigSelector { + @Override + public Result selectConfig(PickSubchannelArgs args) { + // Index ASCII headers by keys. + Map> asciiHeaders = new HashMap<>(); + Metadata headers = args.getHeaders(); + for (String headerName : headers.keys()) { + if (headerName.endsWith(Metadata.BINARY_HEADER_SUFFIX)) { + continue; + } + Metadata.Key key = Metadata.Key.of(headerName, Metadata.ASCII_STRING_MARSHALLER); + asciiHeaders.put(headerName, headers.getAll(key)); + } + String cluster = null; + Route selectedRoute = null; + do { + for (Route route : routes) { + if (route.getRouteMatch().matches( + "/" + args.getMethodDescriptor().getFullMethodName(), asciiHeaders)) { + selectedRoute = route; + break; + } + } + if (selectedRoute == null) { + return Result.forError( + Status.UNAVAILABLE.withDescription("Could not find xDS route matching RPC")); + } + RouteAction action = selectedRoute.getRouteAction(); + if (action.getCluster() != null) { + cluster = action.getCluster(); + } else if (action.getWeightedCluster() != null) { + int totalWeight = 0; + for (ClusterWeight weightedCluster : action.getWeightedCluster()) { + totalWeight += weightedCluster.getWeight(); + } + int select = random.nextInt(totalWeight); + int accumulator = 0; + for (ClusterWeight weightedCluster : action.getWeightedCluster()) { + accumulator += weightedCluster.getWeight(); + if (select < accumulator) { + cluster = weightedCluster.getName(); + break; + } + } + } + } while (!retainCluster(cluster)); + // TODO(chengyuanzhang): avoid service config generation and parsing for each call. + Map rawServiceConfig = + generateServiceConfigWithMethodTimeoutConfig( + selectedRoute.getRouteAction().getTimeoutNano()); + if (logger.isLoggable(XdsLogLevel.INFO)) { + logger.log(XdsLogLevel.INFO, + "Generated service config (method config):\n{0}", new Gson().toJson(rawServiceConfig)); + } + ConfigOrError parsedServiceConfig = serviceConfigParser.parseServiceConfig(rawServiceConfig); + Object config = parsedServiceConfig.getConfig(); + if (config == null) { + releaseCluster(cluster); + return Result.forError( + parsedServiceConfig.getError().augmentDescription( + "Failed to parse service config (method config)")); + } + final String finalCluster = cluster; + class SelectionCompleted implements Runnable { + @Override + public void run() { + releaseCluster(finalCluster); + } + } + + return + Result.newBuilder() + .setCallOptions(args.getCallOptions().withOption(CLUSTER_SELECTION_KEY, cluster)) + .setConfig(config) + .setCommittedCallback(new SelectionCompleted()) + .build(); + } + + private boolean retainCluster(String cluster) { + AtomicInteger refCount = clusterRefs.get(cluster); + if (refCount == null) { + return false; + } + int count; + do { + count = refCount.get(); + if (count == 0) { + return false; + } + } while (!refCount.compareAndSet(count, count + 1)); + return true; + } + + private void releaseCluster(final String cluster) { + int count = clusterRefs.get(cluster).decrementAndGet(); + if (count == 0) { + syncContext.execute(new Runnable() { + @Override + public void run() { + if (clusterRefs.get(cluster).get() == 0) { + clusterRefs.remove(cluster); + updateResolutionResult(); + } + } + }); + } + } + } + + // https://github.com/google/error-prone/issues/1767 + @SuppressWarnings("ModifyCollectionInEnhancedForLoop") + private class ConfigWatcherImpl implements ConfigWatcher { + private Set existingClusters; + + @Override + public void onConfigChanged(ConfigUpdate update) { + Set clusters = new HashSet<>(); + for (Route route : update.getRoutes()) { + RouteAction action = route.getRouteAction(); + if (action.getCluster() != null) { + clusters.add(action.getCluster()); + } else if (action.getWeightedCluster() != null) { + for (ClusterWeight weighedCluster : action.getWeightedCluster()) { + clusters.add(weighedCluster.getName()); + } + } + } + Set addedClusters = + existingClusters == null ? clusters : Sets.difference(clusters, existingClusters); + Set deletedClusters = + existingClusters == null + ? Collections.emptySet() : Sets.difference(existingClusters, clusters); + existingClusters = clusters; + boolean shouldUpdateResult = false; + for (String cluster : addedClusters) { + if (clusterRefs.containsKey(cluster)) { + clusterRefs.get(cluster).incrementAndGet(); + } else { + clusterRefs.put(cluster, new AtomicInteger(1)); + shouldUpdateResult = true; + } + } + // Update service config to include newly added clusters. + if (shouldUpdateResult) { + updateResolutionResult(); + } + // Make newly added clusters selectable by config selector and deleted clusters no longer + // selectable. + routes = update.getRoutes(); + shouldUpdateResult = false; + for (String cluster : deletedClusters) { + int count = clusterRefs.get(cluster).decrementAndGet(); + if (count == 0) { + clusterRefs.remove(cluster); + shouldUpdateResult = true; + } + } + if (shouldUpdateResult) { + updateResolutionResult(); + } + } + + @Override + public void onResourceDoesNotExist(String resourceName) { + logger.log(XdsLogLevel.INFO, "Resource {0} is unavailable", resourceName); + ConfigOrError parsedServiceConfig = + serviceConfigParser.parseServiceConfig(Collections.emptyMap()); + ResolutionResult result = + ResolutionResult.newBuilder() + .setServiceConfig(parsedServiceConfig) + // let channel take action for no config selector + .build(); + listener.onResult(result); + } + + @Override + public void onError(Status error) { + logger.log( + XdsLogLevel.WARNING, + "Received error from xDS client {0}: {1}", xdsClient, error.getDescription()); + listener.onError(error); + } + } +} diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolverProvider2.java b/xds/src/main/java/io/grpc/xds/XdsNameResolverProvider2.java new file mode 100644 index 00000000000..44c97ef85d3 --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/XdsNameResolverProvider2.java @@ -0,0 +1,132 @@ +/* + * Copyright 2019 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Preconditions; +import com.google.common.base.Stopwatch; +import com.google.common.base.Supplier; +import io.grpc.Internal; +import io.grpc.NameResolver.Args; +import io.grpc.NameResolverProvider; +import io.grpc.SynchronizationContext; +import io.grpc.internal.BackoffPolicy; +import io.grpc.internal.ExponentialBackoffPolicy; +import io.grpc.internal.GrpcUtil; +import io.grpc.internal.ObjectPool; +import io.grpc.xds.Bootstrapper.BootstrapInfo; +import io.grpc.xds.XdsClient.RefCountedXdsClientObjectPool; +import io.grpc.xds.XdsClient.XdsChannelFactory; +import io.grpc.xds.XdsClient.XdsClientFactory; +import io.grpc.xds.XdsClient.XdsClientPoolFactory; +import java.net.URI; +import java.util.concurrent.ScheduledExecutorService; + +/** + * A provider for {@link XdsNameResolver2}. + * + *

It resolves a target URI whose scheme is {@code "xds"}. The authority of the + * target URI is never used for current release. The path of the target URI, excluding the leading + * slash {@code '/'}, will indicate the name to use in the VHDS query. + * + *

This class should not be directly referenced in code. The resolver should be accessed + * through {@link io.grpc.NameResolverRegistry} with the URI scheme "xds". + */ +@Internal +public final class XdsNameResolverProvider2 extends NameResolverProvider { + + private static final String SCHEME = "xds"; + + @Override + public XdsNameResolver2 newNameResolver(URI targetUri, Args args) { + if (SCHEME.equals(targetUri.getScheme())) { + String targetPath = checkNotNull(targetUri.getPath(), "targetPath"); + Preconditions.checkArgument( + targetPath.startsWith("/"), + "the path component (%s) of the target (%s) must start with '/'", + targetPath, + targetUri); + String name = targetPath.substring(1); + XdsClientPoolFactory xdsClientPoolFactory = + new RefCountedXdsClientPoolFactory( + name, + XdsChannelFactory.getInstance(), + args.getSynchronizationContext(), args.getScheduledExecutorService(), + new ExponentialBackoffPolicy.Provider(), + GrpcUtil.STOPWATCH_SUPPLIER); + return new XdsNameResolver2( + name, args.getServiceConfigParser(), + args.getSynchronizationContext(), xdsClientPoolFactory); + } + return null; + } + + @Override + public String getDefaultScheme() { + return SCHEME; + } + + @Override + protected boolean isAvailable() { + return true; + } + + @Override + protected int priority() { + // Set priority value to be < 5 as we still want DNS resolver to be the primary default + // resolver. + return 4; + } + + static class RefCountedXdsClientPoolFactory implements XdsClientPoolFactory { + private final String serviceName; + private final XdsChannelFactory channelFactory; + private final SynchronizationContext syncContext; + private final ScheduledExecutorService timeService; + private final BackoffPolicy.Provider backoffPolicyProvider; + private final Supplier stopwatchSupplier; + + RefCountedXdsClientPoolFactory( + String serviceName, + XdsChannelFactory channelFactory, + SynchronizationContext syncContext, + ScheduledExecutorService timeService, + BackoffPolicy.Provider backoffPolicyProvider, + Supplier stopwatchSupplier) { + this.serviceName = checkNotNull(serviceName, "serviceName"); + this.channelFactory = checkNotNull(channelFactory, "channelFactory"); + this.syncContext = checkNotNull(syncContext, "syncContext"); + this.timeService = checkNotNull(timeService, "timeService"); + this.backoffPolicyProvider = checkNotNull(backoffPolicyProvider, "backoffPolicyProvider"); + this.stopwatchSupplier = checkNotNull(stopwatchSupplier, "stopwatchSupplier"); + } + + @Override + public ObjectPool newXdsClientObjectPool(final BootstrapInfo bootstrapInfo) { + XdsClientFactory xdsClientFactory = new XdsClientFactory() { + @Override + XdsClient createXdsClient() { + return new XdsClientImpl( + serviceName, bootstrapInfo.getServers(), channelFactory, bootstrapInfo.getNode(), + syncContext, timeService, backoffPolicyProvider, stopwatchSupplier); + } + }; + return new RefCountedXdsClientObjectPool(xdsClientFactory); + } + } +} diff --git a/xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerTest.java index 257ee3e1810..3e89a8b0231 100644 --- a/xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/ClusterManagerLoadBalancerTest.java @@ -276,7 +276,7 @@ private static PickResult pickSubchannel(SubchannelPicker picker, String name) { .build(), new Metadata(), CallOptions.DEFAULT.withOption( - ClusterManagerLoadBalancer.ROUTING_CLUSTER_NAME_KEY, name)); + XdsNameResolver2.CLUSTER_SELECTION_KEY, name)); return picker.pickSubchannel(args); } diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolver2Test.java b/xds/src/test/java/io/grpc/xds/XdsNameResolver2Test.java new file mode 100644 index 00000000000..31872a385ee --- /dev/null +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolver2Test.java @@ -0,0 +1,554 @@ +/* + * Copyright 2020 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import io.grpc.CallOptions; +import io.grpc.InternalConfigSelector; +import io.grpc.InternalConfigSelector.Result; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.MethodDescriptor.MethodType; +import io.grpc.NameResolver; +import io.grpc.NameResolver.ConfigOrError; +import io.grpc.NameResolver.ResolutionResult; +import io.grpc.NameResolver.ServiceConfigParser; +import io.grpc.Status; +import io.grpc.Status.Code; +import io.grpc.SynchronizationContext; +import io.grpc.internal.JsonParser; +import io.grpc.internal.JsonUtil; +import io.grpc.internal.ObjectPool; +import io.grpc.internal.PickSubchannelArgsImpl; +import io.grpc.testing.TestMethodDescriptors; +import io.grpc.xds.Bootstrapper.BootstrapInfo; +import io.grpc.xds.EnvoyProtoData.ClusterWeight; +import io.grpc.xds.EnvoyProtoData.Node; +import io.grpc.xds.EnvoyProtoData.Route; +import io.grpc.xds.EnvoyProtoData.RouteAction; +import io.grpc.xds.XdsClient.XdsClientPoolFactory; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +/** Unit tests for {@link XdsNameResolver2}. */ +// TODO(chengyuanzhang): should do tests with ManagedChannelImpl. +@RunWith(JUnit4.class) +public class XdsNameResolver2Test { + private static final String AUTHORITY = "foo.googleapis.com:80"; + @Rule + public final MockitoRule mocks = MockitoJUnit.rule(); + private final SynchronizationContext syncContext = new SynchronizationContext( + new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + throw new AssertionError(e); + } + }); + private final ServiceConfigParser serviceConfigParser = new ServiceConfigParser() { + @Override + public ConfigOrError parseServiceConfig(Map rawServiceConfig) { + return ConfigOrError.fromConfig(rawServiceConfig); + } + }; + private final FakeXdsClientPoolFactory xdsClientPoolFactory = new FakeXdsClientPoolFactory(); + private final String cluster1 = "cluster-foo.googleapis.com"; + private final String cluster2 = "cluster-bar.googleapis.com"; + private final CallInfo call1 = new CallInfo("HelloService", "hi"); + private final CallInfo call2 = new CallInfo("GreetService", "bye"); + + @Mock + private ThreadSafeRandom mockRandom; + @Mock + private NameResolver.Listener2 mockListener; + @Captor + private ArgumentCaptor resolutionResultCaptor; + @Captor + ArgumentCaptor errorCaptor; + private XdsNameResolver2 resolver; + + @Before + public void setUp() { + Bootstrapper bootstrapper = new Bootstrapper() { + @Override + public BootstrapInfo readBootstrap() { + return new BootstrapInfo( + ImmutableList.of( + new ServerInfo( + "trafficdirector.googleapis.com", + ImmutableList.of(), ImmutableList.of())), + Node.newBuilder().build(), + null); + } + }; + resolver = new XdsNameResolver2(AUTHORITY, serviceConfigParser, syncContext, bootstrapper, + xdsClientPoolFactory, mockRandom); + } + + @Test + public void resolve_failToBootstrap() { + Bootstrapper bootstrapper = new Bootstrapper() { + @Override + public BootstrapInfo readBootstrap() throws IOException { + throw new IOException("Fail to read bootstrap file"); + } + }; + resolver = new XdsNameResolver2(AUTHORITY, serviceConfigParser, syncContext, bootstrapper, + xdsClientPoolFactory, mockRandom); + resolver.start(mockListener); + verify(mockListener).onError(errorCaptor.capture()); + Status error = errorCaptor.getValue(); + assertThat(error.getCode()).isEqualTo(Code.UNAVAILABLE); + assertThat(error.getDescription()).isEqualTo("Failed to load xDS bootstrap"); + assertThat(error.getCause()).hasMessageThat().isEqualTo("Fail to read bootstrap file"); + } + + @SuppressWarnings("unchecked") + @Test + public void resolve_resourceNotFound() { + resolver.start(mockListener); + FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); + xdsClient.deliverResourceNotFound(); + verify(mockListener).onResult(resolutionResultCaptor.capture()); + ResolutionResult result = resolutionResultCaptor.getValue(); + assertThat(result.getAddresses()).isEmpty(); + assertThat((Map) result.getServiceConfig().getConfig()).isEmpty(); + } + + @Test + public void resolve_encounterError() { + resolver.start(mockListener); + FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); + xdsClient.deliverError(Status.UNAVAILABLE.withDescription("server unreachable")); + verify(mockListener).onError(errorCaptor.capture()); + Status error = errorCaptor.getValue(); + assertThat(error.getCode()).isEqualTo(Code.UNAVAILABLE); + assertThat(error.getDescription()).isEqualTo("server unreachable"); + } + + @Test + public void resolve_simpleCallSucceeds() { + InternalConfigSelector configSelector = resolveToClusters(); + Result selectResult = assertCallSelectResult(call1, configSelector, cluster1, 15.0); + selectResult.getCommittedCallback().run(); + verifyNoMoreInteractions(mockListener); + } + + @Test + public void resolve_simpleCallFailedToRoute() { + InternalConfigSelector configSelector = resolveToClusters(); + CallInfo call = new CallInfo("FooService", "barMethod"); + Result selectResult = configSelector.selectConfig( + new PickSubchannelArgsImpl(call.methodDescriptor, new Metadata(), CallOptions.DEFAULT)); + Status status = selectResult.getStatus(); + assertThat(status.isOk()).isFalse(); + assertThat(status.getCode()).isEqualTo(Code.UNAVAILABLE); + assertThat(status.getDescription()).isEqualTo("Could not find xDS route matching RPC"); + verifyNoMoreInteractions(mockListener); + } + + @SuppressWarnings("unchecked") + @Test + public void resolve_resourceUpdateAfterCallStarted() { + InternalConfigSelector configSelector = resolveToClusters(); + Result selectResult = assertCallSelectResult(call1, configSelector, cluster1, 15.0); + + reset(mockListener); + FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); + xdsClient.deliverRoutes( + Arrays.asList( + new Route( + new RouteMatch(null, call1.getFullMethodNameForPath()), + new RouteAction(TimeUnit.SECONDS.toNanos(20L), "another-cluster", null)), + new Route( + new RouteMatch(null, call2.getFullMethodNameForPath()), + new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster2, null)))); + verify(mockListener).onResult(resolutionResultCaptor.capture()); + ResolutionResult result = resolutionResultCaptor.getValue(); + // Updated service config still contains cluster1 while it is removed resource. New calls no + // longer routed to cluster1. + assertServiceConfigForLoadBalancingConfig( + Arrays.asList(cluster1, cluster2, "another-cluster"), + (Map) result.getServiceConfig().getConfig()); + assertThat(result.getAttributes().get(InternalConfigSelector.KEY)) + .isSameInstanceAs(configSelector); + assertCallSelectResult(call1, configSelector, "another-cluster", 20.0); + + selectResult.getCommittedCallback().run(); // completes previous call + verify(mockListener, times(2)).onResult(resolutionResultCaptor.capture()); + result = resolutionResultCaptor.getValue(); + assertServiceConfigForLoadBalancingConfig( + Arrays.asList(cluster2, "another-cluster"), + (Map) result.getServiceConfig().getConfig()); + verifyNoMoreInteractions(mockListener); + } + + @SuppressWarnings("unchecked") + @Test + public void resolve_resourceUpdatedBeforeCallStarted() { + InternalConfigSelector configSelector = resolveToClusters(); + reset(mockListener); + FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); + xdsClient.deliverRoutes( + Arrays.asList( + new Route( + new RouteMatch(null, call1.getFullMethodNameForPath()), + new RouteAction(TimeUnit.SECONDS.toNanos(20L), "another-cluster", null)), + new Route( + new RouteMatch(null, call2.getFullMethodNameForPath()), + new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster2, null)))); + // Two consecutive service config updates: one for removing clcuster1, + // one for adding "another=cluster". + verify(mockListener, times(2)).onResult(resolutionResultCaptor.capture()); + ResolutionResult result = resolutionResultCaptor.getValue(); + assertServiceConfigForLoadBalancingConfig( + Arrays.asList(cluster2, "another-cluster"), + (Map) result.getServiceConfig().getConfig()); + assertThat(result.getAttributes().get(InternalConfigSelector.KEY)) + .isSameInstanceAs(configSelector); + assertCallSelectResult(call1, configSelector, "another-cluster", 20.0); + + verifyNoMoreInteractions(mockListener); + } + + @SuppressWarnings("unchecked") + @Test + public void resolve_raceBetweenCallAndRepeatedResourceUpdate() { + InternalConfigSelector configSelector = resolveToClusters(); + assertCallSelectResult(call1, configSelector, cluster1, 15.0); + + reset(mockListener); + FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); + xdsClient.deliverRoutes( + Arrays.asList( + new Route( + new RouteMatch(null, call1.getFullMethodNameForPath()), + new RouteAction(TimeUnit.SECONDS.toNanos(20L), "another-cluster", null)), + new Route( + new RouteMatch(null, call2.getFullMethodNameForPath()), + new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster2, null)))); + + verify(mockListener).onResult(resolutionResultCaptor.capture()); + ResolutionResult result = resolutionResultCaptor.getValue(); + assertServiceConfigForLoadBalancingConfig( + Arrays.asList(cluster1, cluster2, "another-cluster"), + (Map) result.getServiceConfig().getConfig()); + + xdsClient.deliverRoutes( + Arrays.asList( + new Route( + new RouteMatch(null, call1.getFullMethodNameForPath()), + new RouteAction(TimeUnit.SECONDS.toNanos(15L), "another-cluster", null)), + new Route( + new RouteMatch(null, call2.getFullMethodNameForPath()), + new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster2, null)))); + verifyNoMoreInteractions(mockListener); // no cluster added/deleted + assertCallSelectResult(call1, configSelector, "another-cluster", 15.0); + } + + @Test + public void resolve_raceBetweenClusterReleasedAndResourceUpdateAddBackAgain() { + InternalConfigSelector configSelector = resolveToClusters(); + Result result = assertCallSelectResult(call1, configSelector, cluster1, 15.0); + FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); + xdsClient.deliverRoutes( + Collections.singletonList( + new Route( + new RouteMatch(null, call2.getFullMethodNameForPath()), + new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster2, null)))); + xdsClient.deliverRoutes( + Arrays.asList( + new Route( + new RouteMatch(null, call1.getFullMethodNameForPath()), + new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster1, null)), + new Route( + new RouteMatch(null, call2.getFullMethodNameForPath()), + new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster2, null)))); + result.getCommittedCallback().run(); + verifyNoMoreInteractions(mockListener); + } + + @SuppressWarnings("unchecked") + @Test + public void resolve_simpleCallSucceeds_routeToWeightedCluster() { + when(mockRandom.nextInt(anyInt())).thenReturn(90, 10); + resolver.start(mockListener); + FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); + xdsClient.deliverRoutes( + Arrays.asList( + new Route( + new RouteMatch(null, call1.getFullMethodNameForPath()), + new RouteAction( + TimeUnit.SECONDS.toNanos(20L), null, + Arrays.asList( + new ClusterWeight(cluster1, 20), new ClusterWeight(cluster2, 80)))))); + verify(mockListener).onResult(resolutionResultCaptor.capture()); + ResolutionResult result = resolutionResultCaptor.getValue(); + assertThat(result.getAddresses()).isEmpty(); + assertServiceConfigForLoadBalancingConfig( + Arrays.asList(cluster1, cluster2), (Map) result.getServiceConfig().getConfig()); + assertThat(result.getAttributes().get(XdsAttributes.XDS_CLIENT_POOL)).isNotNull(); + InternalConfigSelector configSelector = result.getAttributes().get(InternalConfigSelector.KEY); + Result selectResult = configSelector.selectConfig( + new PickSubchannelArgsImpl(call1.methodDescriptor, new Metadata(), CallOptions.DEFAULT)); + assertThat(selectResult.getStatus().isOk()).isTrue(); + assertThat(selectResult.getCallOptions().getOption(XdsNameResolver2.CLUSTER_SELECTION_KEY)) + .isEqualTo(cluster2); + assertServiceConfigForMethodConfig(20.0, (Map) selectResult.getConfig()); + + selectResult = configSelector.selectConfig( + new PickSubchannelArgsImpl(call1.methodDescriptor, new Metadata(), CallOptions.DEFAULT)); + assertThat(selectResult.getStatus().isOk()).isTrue(); + assertThat(selectResult.getCallOptions().getOption(XdsNameResolver2.CLUSTER_SELECTION_KEY)) + .isEqualTo(cluster1); + assertServiceConfigForMethodConfig(20.0, (Map) selectResult.getConfig()); + } + + @SuppressWarnings("unchecked") + private static Result assertCallSelectResult( + CallInfo call, InternalConfigSelector configSelector, String expectedCluster, + double expectedTimeoutSec) { + Result result = configSelector.selectConfig( + new PickSubchannelArgsImpl(call.methodDescriptor, new Metadata(), CallOptions.DEFAULT)); + assertThat(result.getStatus().isOk()).isTrue(); + assertThat(result.getCallOptions().getOption(XdsNameResolver2.CLUSTER_SELECTION_KEY)) + .isEqualTo(expectedCluster); + assertServiceConfigForMethodConfig(expectedTimeoutSec, (Map) result.getConfig()); + return result; + } + + @SuppressWarnings("unchecked") + private InternalConfigSelector resolveToClusters() { + resolver.start(mockListener); + FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); + xdsClient.deliverRoutes( + Arrays.asList( + new Route( + new RouteMatch(null, call1.getFullMethodNameForPath()), + new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster1, null)), + new Route( + new RouteMatch(null, call2.getFullMethodNameForPath()), + new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster2, null)))); + verify(mockListener).onResult(resolutionResultCaptor.capture()); + ResolutionResult result = resolutionResultCaptor.getValue(); + assertThat(result.getAddresses()).isEmpty(); + assertServiceConfigForLoadBalancingConfig( + Arrays.asList(cluster1, cluster2), (Map) result.getServiceConfig().getConfig()); + assertThat(result.getAttributes().get(XdsAttributes.XDS_CLIENT_POOL)).isNotNull(); + return result.getAttributes().get(InternalConfigSelector.KEY); + } + + /** + * Verifies the raw service config contains a single method config for method with the + * specified timeout. + */ + private static void assertServiceConfigForMethodConfig( + double timeoutSec, Map actualServiceConfig) { + List> rawMethodConfigs = + JsonUtil.getListOfObjects(actualServiceConfig, "methodConfig"); + Map methodConfig = Iterables.getOnlyElement(rawMethodConfigs); + List> methods = JsonUtil.getListOfObjects(methodConfig, "name"); + assertThat(Iterables.getOnlyElement(methods)).isEmpty(); + assertThat(JsonUtil.getString(methodConfig, "timeout")).isEqualTo(timeoutSec + "s"); + } + + /** + * Verifies the raw service config contains an xDS load balancing config for the given clusters. + */ + private static void assertServiceConfigForLoadBalancingConfig( + List clusters, Map actualServiceConfig) { + List> rawLbConfigs = + JsonUtil.getListOfObjects(actualServiceConfig, "loadBalancingConfig"); + Map lbConfig = Iterables.getOnlyElement(rawLbConfigs); + assertThat(lbConfig.keySet()).containsExactly("cluster_manager_experimental"); + Map clusterManagerLbConfig = + JsonUtil.getObject(lbConfig, "cluster_manager_experimental"); + Map clusterManagerChildLbPolicies = + JsonUtil.getObject(clusterManagerLbConfig, "childPolicy"); + assertThat(clusterManagerChildLbPolicies.keySet()).containsExactlyElementsIn(clusters); + for (String cluster : clusters) { + Map childLbConfig = JsonUtil.getObject(clusterManagerChildLbPolicies, cluster); + assertThat(childLbConfig.keySet()).containsExactly("lbPolicy"); + List> childLbConfigValues = + JsonUtil.getListOfObjects(childLbConfig, "lbPolicy"); + Map cdsLbPolicy = Iterables.getOnlyElement(childLbConfigValues); + assertThat(cdsLbPolicy.keySet()).containsExactly("cds_experimental"); + assertThat(JsonUtil.getObject(cdsLbPolicy, "cds_experimental")) + .containsExactly("cluster", cluster); + } + } + + @SuppressWarnings("unchecked") + @Test + public void generateServiceConfig_forLoadBalancingConfig() throws IOException { + List clusters = Arrays.asList("cluster-foo", "cluster-bar", "cluster-baz"); + String expectedServiceConfigJson = "{\n" + + " \"loadBalancingConfig\": [{\n" + + " \"cluster_manager_experimental\": {\n" + + " \"childPolicy\": {\n" + + " \"cluster-foo\": {\n" + + " \"lbPolicy\": [{\n" + + " \"cds_experimental\": {\n" + + " \"cluster\": \"cluster-foo\"\n" + + " }\n" + + " }]\n" + + " },\n" + + " \"cluster-bar\": {\n" + + " \"lbPolicy\": [{\n" + + " \"cds_experimental\": {\n" + + " \"cluster\": \"cluster-bar\"\n" + + " }\n" + + " }]\n" + + " },\n" + + " \"cluster-baz\": {\n" + + " \"lbPolicy\": [{\n" + + " \"cds_experimental\": {\n" + + " \"cluster\": \"cluster-baz\"\n" + + " }\n" + + " }]\n" + + " }\n" + + " }\n" + + " }\n" + + " }]\n" + + "}"; + Map expectedServiceConfig = + (Map) JsonParser.parse(expectedServiceConfigJson); + assertThat(XdsNameResolver2.generateServiceConfigWithLoadBalancingConfig(clusters)) + .isEqualTo(expectedServiceConfig); + } + + @SuppressWarnings("unchecked") + @Test + public void generateServiceConfig_forMethodTimeoutConfig() throws IOException { + long timeoutNano = TimeUnit.SECONDS.toNanos(1L) + 1L; // 1.0000000001s + String expectedServiceConfigJson = "{\n" + + " \"methodConfig\": [{\n" + + " \"name\": [ {} ],\n" + + " \"timeout\": \"1.000000001s\"\n" + + " }]\n" + + "}"; + Map expectedServiceConfig = + (Map) JsonParser.parse(expectedServiceConfigJson); + assertThat(XdsNameResolver2.generateServiceConfigWithMethodTimeoutConfig(timeoutNano)) + .isEqualTo(expectedServiceConfig); + } + + private final class FakeXdsClientPoolFactory implements XdsClientPoolFactory { + @Override + public ObjectPool newXdsClientObjectPool(BootstrapInfo bootstrapInfo) { + return new ObjectPool() { + @Override + public XdsClient getObject() { + return new FakeXdsClient(); + } + + @Override + public XdsClient returnObject(Object object) { + return null; + } + }; + } + } + + private class FakeXdsClient extends XdsClient { + private String resource; + private ConfigWatcher watcher; + + @Override + void watchConfigData(String targetAuthority, ConfigWatcher watcher) { + resource = targetAuthority; + this.watcher = watcher; + } + + @Override + void shutdown() { + // no-op + } + + void deliverRoutes(final List routes) { + syncContext.execute(new Runnable() { + @Override + public void run() { + watcher.onConfigChanged(ConfigUpdate.newBuilder().addRoutes(routes).build()); + } + }); + } + + void deliverError(final Status error) { + syncContext.execute(new Runnable() { + @Override + public void run() { + watcher.onError(error); + } + }); + } + + void deliverResourceNotFound() { + Preconditions.checkState(resource != null, "no resource subscribed"); + syncContext.execute(new Runnable() { + @Override + public void run() { + watcher.onResourceDoesNotExist(resource); + } + }); + } + } + + private static final class CallInfo { + private final String service; + private final String method; + private final MethodDescriptor methodDescriptor; + + private CallInfo(String service, String method) { + this.service = service; + this.method = method; + methodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodType.UNARY).setFullMethodName(service + "/" + method) + .setRequestMarshaller(TestMethodDescriptors.voidMarshaller()) + .setResponseMarshaller(TestMethodDescriptors.voidMarshaller()) + .build(); + } + + private String getFullMethodNameForPath() { + return "/" + service + "/" + method; + } + } +} From 720df64fd25df8b1a3ee9b2e8244129fd8cb8a6b Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Tue, 25 Aug 2020 14:24:38 -0700 Subject: [PATCH 81/88] Upgrade to Netty 4.1.51 and tcnative 2.0.31 --- SECURITY.md | 3 +- build.gradle | 4 +- examples/example-tls/build.gradle | 2 +- examples/example-tls/pom.xml | 2 +- repositories.bzl | 72 +++++++++++++++---------------- 5 files changed, 42 insertions(+), 41 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 45d75542bb4..5aeff135349 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -403,7 +403,8 @@ grpc-netty version | netty-handler version | netty-tcnative-boringssl-static ver 1.23.x-1.24.x | 4.1.38.Final | 2.0.25.Final 1.25.x-1.27.x | 4.1.42.Final | 2.0.26.Final 1.28.x | 4.1.45.Final | 2.0.28.Final -1.29.x- | 4.1.48.Final | 2.0.30.Final +1.29.x-1.31.x | 4.1.48.Final | 2.0.30.Final +1.32.x- | 4.1.51.Final | 2.0.31.Final _(grpc-netty-shaded avoids issues with keeping these versions in sync.)_ diff --git a/build.gradle b/build.gradle index f541539599d..fcb2ddd6678 100644 --- a/build.gradle +++ b/build.gradle @@ -54,7 +54,7 @@ subprojects { protocPluginBaseName = 'protoc-gen-grpc-java' javaPluginPath = "$rootDir/compiler/build/exe/java_plugin/$protocPluginBaseName$exeSuffix" - nettyVersion = '4.1.48.Final' + nettyVersion = '4.1.51.Final' guavaVersion = '29.0-android' googleauthVersion = '0.20.0' protobufVersion = '3.12.0' @@ -172,7 +172,7 @@ subprojects { // SECURITY.md (multiple occurrences) // examples/example-tls/build.gradle // examples/example-tls/pom.xml - netty_tcnative: 'io.netty:netty-tcnative-boringssl-static:2.0.30.Final', + netty_tcnative: 'io.netty:netty-tcnative-boringssl-static:2.0.31.Final', conscrypt: 'org.conscrypt:conscrypt-openjdk-uber:2.2.1', re2j: 'com.google.re2j:re2j:1.2', diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index 0ae0df47c92..9b420626e71 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -24,7 +24,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. def grpcVersion = '1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION -def nettyTcNativeVersion = '2.0.30.Final' +def nettyTcNativeVersion = '2.0.31.Final' def protocVersion = '3.12.0' dependencies { diff --git a/examples/example-tls/pom.xml b/examples/example-tls/pom.xml index 7efb6aa3e94..e51be823719 100644 --- a/examples/example-tls/pom.xml +++ b/examples/example-tls/pom.xml @@ -14,7 +14,7 @@ UTF-8 1.32.0-SNAPSHOT 3.12.0 - 2.0.30.Final + 2.0.31.Final 1.7 1.7 diff --git a/repositories.bzl b/repositories.bzl index 62c9279c5d2..9c4ab886bac 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -24,18 +24,18 @@ IO_GRPC_GRPC_JAVA_ARTIFACTS = [ "com.google.truth:truth:1.0.1", "com.squareup.okhttp:okhttp:2.7.4", "com.squareup.okio:okio:1.13.0", - "io.netty:netty-buffer:4.1.48.Final", - "io.netty:netty-codec-http2:4.1.48.Final", - "io.netty:netty-codec-http:4.1.48.Final", - "io.netty:netty-codec-socks:4.1.48.Final", - "io.netty:netty-codec:4.1.48.Final", - "io.netty:netty-common:4.1.48.Final", - "io.netty:netty-handler-proxy:4.1.48.Final", - "io.netty:netty-handler:4.1.48.Final", - "io.netty:netty-resolver:4.1.48.Final", - "io.netty:netty-tcnative-boringssl-static:2.0.30.Final", - "io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.48.Final", - "io.netty:netty-transport:4.1.48.Final", + "io.netty:netty-buffer:4.1.51.Final", + "io.netty:netty-codec-http2:4.1.51.Final", + "io.netty:netty-codec-http:4.1.51.Final", + "io.netty:netty-codec-socks:4.1.51.Final", + "io.netty:netty-codec:4.1.51.Final", + "io.netty:netty-common:4.1.51.Final", + "io.netty:netty-handler-proxy:4.1.51.Final", + "io.netty:netty-handler:4.1.51.Final", + "io.netty:netty-resolver:4.1.51.Final", + "io.netty:netty-tcnative-boringssl-static:2.0.31.Final", + "io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.51.Final", + "io.netty:netty-transport:4.1.51.Final", "io.opencensus:opencensus-api:0.24.0", "io.opencensus:opencensus-contrib-grpc-metrics:0.24.0", "io.perfmark:perfmark-api:0.19.0", @@ -318,108 +318,108 @@ def io_grpc_grpc_proto(): def io_netty_netty_buffer(): jvm_maven_import_external( name = "io_netty_netty_buffer", - artifact = "io.netty:netty-buffer:4.1.48.Final", + artifact = "io.netty:netty-buffer:4.1.51.Final", server_urls = ["https://repo.maven.apache.org/maven2/"], - artifact_sha256 = "7efc8f98224c703ef09a409e5ddffbe14f5b4b6f527d3836c1647b4d9eff8cec", + artifact_sha256 = "c3c3b710e1b5a8df3d60cd4602e0a743481d5e609e4aa852fa2629e4e412d245", licenses = ["notice"], # Apache 2.0 ) def io_netty_netty_codec(): jvm_maven_import_external( name = "io_netty_netty_codec", - artifact = "io.netty:netty-codec:4.1.48.Final", + artifact = "io.netty:netty-codec:4.1.51.Final", server_urls = ["https://repo.maven.apache.org/maven2/"], - artifact_sha256 = "81b4c316163a591b4f74fd2dc23a3ea45359cb817d0a9c4fc7f37dc9edfdbea8", + artifact_sha256 = "ff741aaa35f7048a6be7c700aa4851bf643917648ea5b7c0cbada2f3848c2bee", licenses = ["notice"], # Apache 2.0 ) def io_netty_netty_codec_http(): jvm_maven_import_external( name = "io_netty_netty_codec_http", - artifact = "io.netty:netty-codec-http:4.1.48.Final", + artifact = "io.netty:netty-codec-http:4.1.51.Final", server_urls = ["https://repo.maven.apache.org/maven2/"], - artifact_sha256 = "aa4b18070e7fc105f0c94a077605687bec48091274c8acc121116692c335edd0", + artifact_sha256 = "e2fc9d846b77160d30df733bf9e88c6bcc589ab4a54719ac6c9195dd82865bea", licenses = ["notice"], # Apache 2.0 ) def io_netty_netty_codec_http2(): jvm_maven_import_external( name = "io_netty_netty_codec_http2", - artifact = "io.netty:netty-codec-http2:4.1.48.Final", + artifact = "io.netty:netty-codec-http2:4.1.51.Final", server_urls = ["https://repo.maven.apache.org/maven2/"], - artifact_sha256 = "359548f53cf8697ebdfa13a4700f1b9a5585573c64f2d3ed135a3197ebd51579", + artifact_sha256 = "48b0102de286e1f5528a17aed5b2a4fb35615b358fdde1b4d8702484f29cf87d", licenses = ["notice"], # Apache 2.0 ) def io_netty_netty_codec_socks(): jvm_maven_import_external( name = "io_netty_netty_codec_socks", - artifact = "io.netty:netty-codec-socks:4.1.48.Final", + artifact = "io.netty:netty-codec-socks:4.1.51.Final", server_urls = ["https://repo.maven.apache.org/maven2/"], - artifact_sha256 = "d0dd35f9ac6892a03bb0d38ea32e683993c4308a02de5756bb5a23ecb929f917", + artifact_sha256 = "bd4d6f8917059a178eb4f94801f7dbfbde0f9f09accfdcf1addccd72081cf9a2", licenses = ["notice"], # Apache 2.0 ) def io_netty_netty_common(): jvm_maven_import_external( name = "io_netty_netty_common", - artifact = "io.netty:netty-common:4.1.48.Final", + artifact = "io.netty:netty-common:4.1.51.Final", server_urls = ["https://repo.maven.apache.org/maven2/"], - artifact_sha256 = "e44a2369566fd1fa8a0f30b12e2801de8fb405b9d1fa3894a58b6262065a9916", + artifact_sha256 = "110e06515f43913a2bbac23e1aa78b7f59ae09d466b00af5fcf399a4f9af1b6b", licenses = ["notice"], # Apache 2.0 ) def io_netty_netty_handler(): jvm_maven_import_external( name = "io_netty_netty_handler", - artifact = "io.netty:netty-handler:4.1.48.Final", + artifact = "io.netty:netty-handler:4.1.51.Final", server_urls = ["https://repo.maven.apache.org/maven2/"], - artifact_sha256 = "757f83c7891ad2ebad209f02d8dbca0121e03f7062c2d4ec9d00eba1a0d403d5", + artifact_sha256 = "4461970f04f4d5eb9112ad94255ce1987394ce64de6c3c87690bf0865c936258", licenses = ["notice"], # Apache 2.0 ) def io_netty_netty_handler_proxy(): jvm_maven_import_external( name = "io_netty_netty_handler_proxy", - artifact = "io.netty:netty-handler-proxy:4.1.48.Final", + artifact = "io.netty:netty-handler-proxy:4.1.51.Final", server_urls = ["https://repo.maven.apache.org/maven2/"], - artifact_sha256 = "f784f331bdb05834390c132d1534724e5371c1a19c7a62217e5f192963a9a92c", + artifact_sha256 = "8ed8f70c5c9591e9f168b7ae6b315944409ef404839204d972906e8e1de171dd", licenses = ["notice"], # Apache 2.0 ) def io_netty_netty_resolver(): jvm_maven_import_external( name = "io_netty_netty_resolver", - artifact = "io.netty:netty-resolver:4.1.48.Final", + artifact = "io.netty:netty-resolver:4.1.51.Final", server_urls = ["https://repo.maven.apache.org/maven2/"], - artifact_sha256 = "fb125914398ebef821def3dbb1642f9f360f39d182f00149ef3db845ebf06ad2", + artifact_sha256 = "c8a77765e481fbf5906c596eb441de49096b354bcae0356b7404ac5e96399350", licenses = ["notice"], # Apache 2.0 ) def io_netty_netty_tcnative_boringssl_static(): jvm_maven_import_external( name = "io_netty_netty_tcnative_boringssl_static", - artifact = "io.netty:netty-tcnative-boringssl-static:2.0.30.Final", + artifact = "io.netty:netty-tcnative-boringssl-static:2.0.31.Final", server_urls = ["https://repo.maven.apache.org/maven2/"], - artifact_sha256 = "61934ca753be47973fe427d1f483a1b2fbcaf56eefc71519bf35fddb036ee111", + artifact_sha256 = "308e7e1f5faea3ff86bf689f6309b8605090cdd5186586a52e418bf48af93d68", licenses = ["notice"], # Apache 2.0 ) def io_netty_netty_transport(): jvm_maven_import_external( name = "io_netty_netty_transport", - artifact = "io.netty:netty-transport:4.1.48.Final", + artifact = "io.netty:netty-transport:4.1.51.Final", server_urls = ["https://repo.maven.apache.org/maven2/"], - artifact_sha256 = "6b4ba9e09a8e060bad2540845491b5fa1ca73614d157860e657f4027c91e72fd", + artifact_sha256 = "e5be259f35a246bf504ad93ea8f5df31872b5abebfb751380eab95d5dc840d44", licenses = ["notice"], # Apache 2.0 ) def io_netty_netty_transport_native_epoll_linux_x86_64(): jvm_maven_import_external( name = "io_netty_netty_transport_native_epoll_linux_x86_64", - artifact = "io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.48.Final", + artifact = "io.netty:netty-transport-native-epoll:jar:linux-x86_64:4.1.51.Final", server_urls = ["https://repo.maven.apache.org/maven2/"], - artifact_sha256 = "7436ecfb442b299af6ecff7ae6a8d3f00fb56e081d20e82b467dad2e6ee8848f", + artifact_sha256 = "8058a306f5b4d511f8d31e6700121359a659dd90fe3a0902d2b32e70bdc21a57", licenses = ["notice"], # Apache 2.0 ) From c30505df041359305230fa94139deebf0aae5b71 Mon Sep 17 00:00:00 2001 From: Chengyuan Zhang Date: Tue, 25 Aug 2020 17:21:34 -0700 Subject: [PATCH 82/88] core, alts, cronet: fix ByteBuffer covariant method usages (#7349) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Java 9 introduces overridden methods with covariant return types for the following methods in java.nio.ByteBuffer: - position​(int newPosition) - limit​(int newLimit) - flip​() - clear​() - mark​() - reset​() - rewind​() In Java 9 they all now return ByteBuffer, whereas the methods they override return Buffer, resulting in exceptions like this when executing on Java 8 and lower: java.lang.NoSuchMethodError: java.nio.ByteBuffer.limit(I)Ljava/nio/ByteBuffer This is because the generated byte code includes the static return type of the method, which is not found on Java 8 and lower because the overloaded methods with covariant return types don't exist (the issue appears even with source and target 8 or lower in compilation parameters). The solution is to cast ByteBuffer instances to Buffer before calling the method. --- .../io/grpc/alts/internal/AltsFraming.java | 29 +++++++-------- .../alts/internal/AltsHandshakerClient.java | 5 +-- .../grpc/alts/internal/AltsTsiHandshaker.java | 5 +-- .../grpc/alts/internal/AltsFramingTest.java | 11 +++--- .../internal/AltsHandshakerClientTest.java | 3 +- .../alts/internal/AltsTsiHandshakerTest.java | 7 ++-- .../io/grpc/alts/internal/FakeTsiTest.java | 35 ++++++++++--------- .../alts/internal/MockAltsHandshakerResp.java | 3 +- .../java/io/grpc/alts/internal/TsiTest.java | 7 ++-- .../internal/CompositeReadableBuffer.java | 8 ++--- .../io/grpc/internal/ReadableBuffers.java | 23 ++++++------ .../internal/CompositeReadableBufferTest.java | 7 ++-- .../grpc/internal/ReadableBufferTestBase.java | 5 +-- .../io/grpc/cronet/CronetClientStream.java | 5 +-- .../grpc/cronet/CronetClientStreamTest.java | 5 +-- 15 files changed, 84 insertions(+), 74 deletions(-) diff --git a/alts/src/main/java/io/grpc/alts/internal/AltsFraming.java b/alts/src/main/java/io/grpc/alts/internal/AltsFraming.java index d243d95f4ab..2571f937225 100644 --- a/alts/src/main/java/io/grpc/alts/internal/AltsFraming.java +++ b/alts/src/main/java/io/grpc/alts/internal/AltsFraming.java @@ -17,6 +17,7 @@ package io.grpc.alts.internal; import com.google.common.base.Preconditions; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.security.GeneralSecurityException; @@ -63,10 +64,10 @@ static ByteBuffer toFrame(ByteBuffer input, int dataSize) throws GeneralSecurity } Producer producer = new Producer(); ByteBuffer inputAlias = input.duplicate(); - inputAlias.limit(input.position() + dataSize); + ((Buffer) inputAlias).limit(input.position() + dataSize); producer.readBytes(inputAlias); producer.flush(); - input.position(inputAlias.position()); + ((Buffer) input).position(inputAlias.position()); ByteBuffer output = producer.getRawFrame(); return output; } @@ -166,10 +167,10 @@ void flush() throws GeneralSecurityException { int frameLength = buffer.position() + getFrameSuffixLength(); // Set the limit and move to the start. - buffer.flip(); + ((Buffer) buffer).flip(); // Advance the limit to allow a crypto suffix. - buffer.limit(buffer.limit() + getFrameSuffixLength()); + ((Buffer) buffer).limit(buffer.limit() + getFrameSuffixLength()); // Write the data length and the message type. int dataLength = frameLength - FRAME_LENGTH_HEADER_SIZE; @@ -178,17 +179,17 @@ void flush() throws GeneralSecurityException { buffer.putInt(MESSAGE_TYPE); // Move the position back to 0, the frame is ready. - buffer.position(0); + ((Buffer) buffer).position(0); isComplete = true; } /** Resets the state, preparing to construct a new frame. Must be called between frames. */ private void reset() { - buffer.clear(); + ((Buffer) buffer).clear(); // Save some space for framing, we'll fill that in later. - buffer.position(getFramePrefixLength()); - buffer.limit(buffer.limit() - getFrameSuffixLength()); + ((Buffer) buffer).position(getFramePrefixLength()); + ((Buffer) buffer).limit(buffer.limit() - getFrameSuffixLength()); isComplete = false; } @@ -279,7 +280,7 @@ public boolean readBytes(ByteBuffer input) throws GeneralSecurityException { // internal buffer is large enough. if (buffer.position() == FRAME_LENGTH_HEADER_SIZE && input.hasRemaining()) { ByteBuffer bufferAlias = buffer.duplicate(); - bufferAlias.flip(); + ((Buffer) bufferAlias).flip(); bufferAlias.order(ByteOrder.LITTLE_ENDIAN); int dataLength = bufferAlias.getInt(); if (dataLength < FRAME_MESSAGE_TYPE_HEADER_SIZE || dataLength > MAX_DATA_LENGTH) { @@ -292,7 +293,7 @@ public boolean readBytes(ByteBuffer input) throws GeneralSecurityException { buffer.order(ByteOrder.LITTLE_ENDIAN); buffer.putInt(dataLength); } - buffer.limit(frameLength); + ((Buffer) buffer).limit(frameLength); } // TODO: Similarly extract and check message type. @@ -300,7 +301,7 @@ public boolean readBytes(ByteBuffer input) throws GeneralSecurityException { // Read the remaining data into the internal buffer. copy(buffer, input); if (!buffer.hasRemaining()) { - buffer.flip(); + ((Buffer) buffer).flip(); isComplete = true; } return isComplete; @@ -323,7 +324,7 @@ public boolean isComplete() { /** Resets the state, preparing to parse a new frame. Must be called between frames. */ private void reset() { - buffer.clear(); + ((Buffer) buffer).clear(); isComplete = false; } @@ -356,9 +357,9 @@ private static void copy(ByteBuffer dst, ByteBuffer src) { } else { int count = Math.min(dst.remaining(), src.remaining()); ByteBuffer slice = src.slice(); - slice.limit(count); + ((Buffer) slice).limit(count); dst.put(slice); - src.position(src.position() + count); + ((Buffer) src).position(src.position() + count); } } } diff --git a/alts/src/main/java/io/grpc/alts/internal/AltsHandshakerClient.java b/alts/src/main/java/io/grpc/alts/internal/AltsHandshakerClient.java index 083ad056789..3b6a0c69616 100644 --- a/alts/src/main/java/io/grpc/alts/internal/AltsHandshakerClient.java +++ b/alts/src/main/java/io/grpc/alts/internal/AltsHandshakerClient.java @@ -23,6 +23,7 @@ import io.grpc.Status; import io.grpc.alts.internal.HandshakerServiceGrpc.HandshakerServiceStub; import java.io.IOException; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.security.GeneralSecurityException; import java.util.logging.Level; @@ -199,7 +200,7 @@ public ByteBuffer startServerHandshake(ByteBuffer inBytes) throws GeneralSecurit throw new GeneralSecurityException(e); } handleResponse(resp); - inBytes.position(inBytes.position() + resp.getBytesConsumed()); + ((Buffer) inBytes).position(inBytes.position() + resp.getBytesConsumed()); return resp.getOutFrames().asReadOnlyByteBuffer(); } @@ -227,7 +228,7 @@ public ByteBuffer next(ByteBuffer inBytes) throws GeneralSecurityException { throw new GeneralSecurityException(e); } handleResponse(resp); - inBytes.position(inBytes.position() + resp.getBytesConsumed()); + ((Buffer) inBytes).position(inBytes.position() + resp.getBytesConsumed()); return resp.getOutFrames().asReadOnlyByteBuffer(); } diff --git a/alts/src/main/java/io/grpc/alts/internal/AltsTsiHandshaker.java b/alts/src/main/java/io/grpc/alts/internal/AltsTsiHandshaker.java index 844e1038746..a371b8f165a 100644 --- a/alts/src/main/java/io/grpc/alts/internal/AltsTsiHandshaker.java +++ b/alts/src/main/java/io/grpc/alts/internal/AltsTsiHandshaker.java @@ -22,6 +22,7 @@ import com.google.common.base.Preconditions; import io.grpc.alts.internal.HandshakerServiceGrpc.HandshakerServiceStub; import io.netty.buffer.ByteBufAllocator; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.security.GeneralSecurityException; import java.util.ArrayList; @@ -151,10 +152,10 @@ public void getBytesToSendToPeer(ByteBuffer bytes) throws GeneralSecurityExcepti ByteBuffer outputFrameAlias = outputFrame; if (outputFrame.remaining() > bytes.remaining()) { outputFrameAlias = outputFrame.duplicate(); - outputFrameAlias.limit(outputFrameAlias.position() + bytes.remaining()); + ((Buffer) outputFrameAlias).limit(outputFrameAlias.position() + bytes.remaining()); } bytes.put(outputFrameAlias); - outputFrame.position(outputFrameAlias.position()); + ((Buffer) outputFrame).position(outputFrameAlias.position()); } /** diff --git a/alts/src/test/java/io/grpc/alts/internal/AltsFramingTest.java b/alts/src/test/java/io/grpc/alts/internal/AltsFramingTest.java index 5ad250e0621..a4703e052ee 100644 --- a/alts/src/test/java/io/grpc/alts/internal/AltsFramingTest.java +++ b/alts/src/test/java/io/grpc/alts/internal/AltsFramingTest.java @@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.security.GeneralSecurityException; @@ -38,7 +39,7 @@ public void parserFrameLengthNegativeFails() throws GeneralSecurityException { buffer.order(ByteOrder.LITTLE_ENDIAN); buffer.putInt(-1); // write invalid length buffer.put((byte) 0); // write some byte - buffer.flip(); + ((Buffer) buffer).flip(); try { parser.readBytes(buffer); @@ -56,7 +57,7 @@ public void parserFrameLengthSmallerMessageTypeFails() throws GeneralSecurityExc buffer.order(ByteOrder.LITTLE_ENDIAN); buffer.putInt(AltsFraming.getFrameMessageTypeHeaderSize() - 1); // write invalid length buffer.put((byte) 0); // write some byte - buffer.flip(); + ((Buffer) buffer).flip(); try { parser.readBytes(buffer); @@ -74,7 +75,7 @@ public void parserFrameLengthTooLargeFails() throws GeneralSecurityException { buffer.order(ByteOrder.LITTLE_ENDIAN); buffer.putInt(AltsFraming.getMaxDataLength() + 1); // write invalid length buffer.put((byte) 0); // write some byte - buffer.flip(); + ((Buffer) buffer).flip(); try { parser.readBytes(buffer); @@ -97,7 +98,7 @@ public void parserFrameLengthMaxOk() throws GeneralSecurityException { buffer.putInt(6); // default message type buffer.put(new byte[dataLength - AltsFraming.getFrameMessageTypeHeaderSize()]); // write data buffer.put((byte) 0); - buffer.flip(); + ((Buffer) buffer).flip(); parser.readBytes(buffer); @@ -116,7 +117,7 @@ public void parserFrameLengthZeroOk() throws GeneralSecurityException { buffer.putInt(dataLength); // write invalid length buffer.putInt(6); // default message type buffer.put((byte) 0); - buffer.flip(); + ((Buffer) buffer).flip(); parser.readBytes(buffer); diff --git a/alts/src/test/java/io/grpc/alts/internal/AltsHandshakerClientTest.java b/alts/src/test/java/io/grpc/alts/internal/AltsHandshakerClientTest.java index d1bd5ffea08..27ad16ee2d3 100644 --- a/alts/src/test/java/io/grpc/alts/internal/AltsHandshakerClientTest.java +++ b/alts/src/test/java/io/grpc/alts/internal/AltsHandshakerClientTest.java @@ -29,6 +29,7 @@ import com.google.common.collect.ImmutableList; import com.google.protobuf.ByteString; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.security.GeneralSecurityException; import org.junit.Before; @@ -178,7 +179,7 @@ public void startServerHandshakeWithPrefixBuffer() throws Exception { .thenReturn(MockAltsHandshakerResp.getOkResponse(BYTES_CONSUMED)); ByteBuffer inBytes = ByteBuffer.allocate(IN_BYTES_SIZE); - inBytes.position(PREFIX_POSITION); + ((Buffer) inBytes).position(PREFIX_POSITION); ByteBuffer outFrame = handshaker.startServerHandshake(inBytes); assertEquals(ByteString.copyFrom(outFrame), MockAltsHandshakerResp.getOutFrame()); diff --git a/alts/src/test/java/io/grpc/alts/internal/AltsTsiHandshakerTest.java b/alts/src/test/java/io/grpc/alts/internal/AltsTsiHandshakerTest.java index f474586b6ad..b5164a36f8a 100644 --- a/alts/src/test/java/io/grpc/alts/internal/AltsTsiHandshakerTest.java +++ b/alts/src/test/java/io/grpc/alts/internal/AltsTsiHandshakerTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.when; import com.google.protobuf.ByteString; +import java.nio.Buffer; import java.nio.ByteBuffer; import org.junit.Before; import org.junit.Test; @@ -112,7 +113,7 @@ public void processBytesFromPeerStartServer() throws Exception { verify(mockServer, never()).startClientHandshake(); verify(mockServer, never()).next(ArgumentMatchers.any()); // Mock transport buffer all consumed by processBytesFromPeer and there is an output frame. - transportBuffer.position(transportBuffer.limit()); + ((Buffer) transportBuffer).position(transportBuffer.limit()); when(mockServer.startServerHandshake(transportBuffer)).thenReturn(outputFrame); when(mockServer.isFinished()).thenReturn(false); @@ -127,7 +128,7 @@ public void processBytesFromPeerStartServerEmptyOutput() throws Exception { verify(mockServer, never()).next(ArgumentMatchers.any()); // Mock transport buffer all consumed by processBytesFromPeer and output frame is empty. // Expect processBytesFromPeer return False, because more data are needed from the peer. - transportBuffer.position(transportBuffer.limit()); + ((Buffer) transportBuffer).position(transportBuffer.limit()); when(mockServer.startServerHandshake(transportBuffer)).thenReturn(emptyOutputFrame); when(mockServer.isFinished()).thenReturn(false); @@ -174,7 +175,7 @@ public void processBytesFromPeerClientNext() throws Exception { when(mockClient.isFinished()).thenReturn(false); handshakerClient.getBytesToSendToPeer(transportBuffer); - transportBuffer.position(transportBuffer.limit()); + ((Buffer) transportBuffer).position(transportBuffer.limit()); assertFalse(handshakerClient.processBytesFromPeer(transportBuffer)); } diff --git a/alts/src/test/java/io/grpc/alts/internal/FakeTsiTest.java b/alts/src/test/java/io/grpc/alts/internal/FakeTsiTest.java index cf7c3133e7b..f908160f958 100644 --- a/alts/src/test/java/io/grpc/alts/internal/FakeTsiTest.java +++ b/alts/src/test/java/io/grpc/alts/internal/FakeTsiTest.java @@ -27,6 +27,7 @@ import io.netty.util.ReferenceCounted; import io.netty.util.ResourceLeakDetector; import io.netty.util.ResourceLeakDetector.Level; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.security.GeneralSecurityException; import java.util.ArrayList; @@ -86,11 +87,11 @@ public void handshakeStateOrderTest() { byte[] transportBufferBytes = new byte[TsiTest.getDefaultTransportBufferSize()]; ByteBuffer transportBuffer = ByteBuffer.wrap(transportBufferBytes); - transportBuffer.limit(0); // Start off with an empty buffer + ((Buffer) transportBuffer).limit(0); // Start off with an empty buffer - transportBuffer.clear(); + ((Buffer) transportBuffer).clear(); clientHandshaker.getBytesToSendToPeer(transportBuffer); - transportBuffer.flip(); + ((Buffer) transportBuffer).flip(); assertEquals( FakeTsiHandshaker.State.CLIENT_INIT.toString().trim(), new String(transportBufferBytes, 4, transportBuffer.remaining(), UTF_8).trim()); @@ -99,14 +100,14 @@ public void handshakeStateOrderTest() { assertFalse(transportBuffer.hasRemaining()); // client shouldn't offer any more bytes - transportBuffer.clear(); + ((Buffer) transportBuffer).clear(); clientHandshaker.getBytesToSendToPeer(transportBuffer); - transportBuffer.flip(); + ((Buffer) transportBuffer).flip(); assertFalse(transportBuffer.hasRemaining()); - transportBuffer.clear(); + ((Buffer) transportBuffer).clear(); serverHandshaker.getBytesToSendToPeer(transportBuffer); - transportBuffer.flip(); + ((Buffer) transportBuffer).flip(); assertEquals( FakeTsiHandshaker.State.SERVER_INIT.toString().trim(), new String(transportBufferBytes, 4, transportBuffer.remaining(), UTF_8).trim()); @@ -115,14 +116,14 @@ public void handshakeStateOrderTest() { assertFalse(transportBuffer.hasRemaining()); // server shouldn't offer any more bytes - transportBuffer.clear(); + ((Buffer) transportBuffer).clear(); serverHandshaker.getBytesToSendToPeer(transportBuffer); - transportBuffer.flip(); + ((Buffer) transportBuffer).flip(); assertFalse(transportBuffer.hasRemaining()); - transportBuffer.clear(); + ((Buffer) transportBuffer).clear(); clientHandshaker.getBytesToSendToPeer(transportBuffer); - transportBuffer.flip(); + ((Buffer) transportBuffer).flip(); assertEquals( FakeTsiHandshaker.State.CLIENT_FINISHED.toString().trim(), new String(transportBufferBytes, 4, transportBuffer.remaining(), UTF_8).trim()); @@ -131,14 +132,14 @@ public void handshakeStateOrderTest() { assertFalse(transportBuffer.hasRemaining()); // client shouldn't offer any more bytes - transportBuffer.clear(); + ((Buffer) transportBuffer).clear(); clientHandshaker.getBytesToSendToPeer(transportBuffer); - transportBuffer.flip(); + ((Buffer) transportBuffer).flip(); assertFalse(transportBuffer.hasRemaining()); - transportBuffer.clear(); + ((Buffer) transportBuffer).clear(); serverHandshaker.getBytesToSendToPeer(transportBuffer); - transportBuffer.flip(); + ((Buffer) transportBuffer).flip(); assertEquals( FakeTsiHandshaker.State.SERVER_FINISHED.toString().trim(), new String(transportBufferBytes, 4, transportBuffer.remaining(), UTF_8).trim()); @@ -147,9 +148,9 @@ public void handshakeStateOrderTest() { assertFalse(transportBuffer.hasRemaining()); // server shouldn't offer any more bytes - transportBuffer.clear(); + ((Buffer) transportBuffer).clear(); serverHandshaker.getBytesToSendToPeer(transportBuffer); - transportBuffer.flip(); + ((Buffer) transportBuffer).flip(); assertFalse(transportBuffer.hasRemaining()); } catch (GeneralSecurityException e) { throw new AssertionError(e); diff --git a/alts/src/test/java/io/grpc/alts/internal/MockAltsHandshakerResp.java b/alts/src/test/java/io/grpc/alts/internal/MockAltsHandshakerResp.java index 7843d681208..a21082be41f 100644 --- a/alts/src/test/java/io/grpc/alts/internal/MockAltsHandshakerResp.java +++ b/alts/src/test/java/io/grpc/alts/internal/MockAltsHandshakerResp.java @@ -20,6 +20,7 @@ import com.google.protobuf.ByteString; import io.grpc.Status; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.security.SecureRandom; @@ -62,7 +63,7 @@ static ByteString getOutFrame() { buffer.order(ByteOrder.LITTLE_ENDIAN); buffer.putInt(frameSize); buffer.put(TEST_OUT_FRAME.getBytes(UTF_8)); - buffer.flip(); + ((Buffer) buffer).flip(); return ByteString.copyFrom(buffer); } diff --git a/alts/src/test/java/io/grpc/alts/internal/TsiTest.java b/alts/src/test/java/io/grpc/alts/internal/TsiTest.java index 5182ecde028..f677a44a754 100644 --- a/alts/src/test/java/io/grpc/alts/internal/TsiTest.java +++ b/alts/src/test/java/io/grpc/alts/internal/TsiTest.java @@ -26,6 +26,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.buffer.UnpooledByteBufAllocator; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.security.GeneralSecurityException; import java.util.ArrayList; @@ -83,7 +84,7 @@ static void performHandshake(int transportBufferSize, Handshakers handshakers) byte[] transportBufferBytes = new byte[transportBufferSize]; ByteBuffer transportBuffer = ByteBuffer.wrap(transportBufferBytes); - transportBuffer.limit(0); // Start off with an empty buffer + ((Buffer) transportBuffer).limit(0); // Start off with an empty buffer while (clientHandshaker.isInProgress() || serverHandshaker.isInProgress()) { for (TsiHandshaker handshaker : new TsiHandshaker[] {clientHandshaker, serverHandshaker}) { @@ -94,9 +95,9 @@ static void performHandshake(int transportBufferSize, Handshakers handshakers) } // Put new bytes on the wire, if needed. if (handshaker.isInProgress()) { - transportBuffer.clear(); + ((Buffer) transportBuffer).clear(); handshaker.getBytesToSendToPeer(transportBuffer); - transportBuffer.flip(); + ((Buffer) transportBuffer).flip(); } } } diff --git a/core/src/main/java/io/grpc/internal/CompositeReadableBuffer.java b/core/src/main/java/io/grpc/internal/CompositeReadableBuffer.java index 9a9bf5c9266..93dda7cdbc8 100644 --- a/core/src/main/java/io/grpc/internal/CompositeReadableBuffer.java +++ b/core/src/main/java/io/grpc/internal/CompositeReadableBuffer.java @@ -102,18 +102,16 @@ public int readInternal(ReadableBuffer buffer, int length) { @Override public void readBytes(final ByteBuffer dest) { - // Use Buffer instead of ByteBuffer for JDK 9+ compatibility. - final Buffer destAsBuffer = dest; execute(new ReadOperation() { @Override public int readInternal(ReadableBuffer buffer, int length) { // Change the limit so that only lengthToCopy bytes are available. - int prevLimit = destAsBuffer.limit(); - destAsBuffer.limit(destAsBuffer.position() + length); + int prevLimit = dest.limit(); + ((Buffer) dest).limit(dest.position() + length); // Write the bytes and restore the original limit. buffer.readBytes(dest); - destAsBuffer.limit(prevLimit); + ((Buffer) dest).limit(prevLimit); return 0; } }, dest.remaining()); diff --git a/core/src/main/java/io/grpc/internal/ReadableBuffers.java b/core/src/main/java/io/grpc/internal/ReadableBuffers.java index 34805420fa5..cfe5542a573 100644 --- a/core/src/main/java/io/grpc/internal/ReadableBuffers.java +++ b/core/src/main/java/io/grpc/internal/ReadableBuffers.java @@ -210,8 +210,7 @@ public int arrayOffset() { * A {@link ReadableBuffer} that is backed by a {@link ByteBuffer}. */ private static class ByteReadableBufferWrapper extends AbstractReadableBuffer { - // Use Buffer instead of ByteBuffer for JDK 9+ compatibility. - final Buffer bytes; + final ByteBuffer bytes; ByteReadableBufferWrapper(ByteBuffer bytes) { this.bytes = Preconditions.checkNotNull(bytes, "bytes"); @@ -225,19 +224,19 @@ public int readableBytes() { @Override public int readUnsignedByte() { checkReadable(1); - return ((ByteBuffer) bytes).get() & 0xFF; + return bytes.get() & 0xFF; } @Override public void skipBytes(int length) { checkReadable(length); - bytes.position(bytes.position() + length); + ((Buffer) bytes).position(bytes.position() + length); } @Override public void readBytes(byte[] dest, int destOffset, int length) { checkReadable(length); - ((ByteBuffer) bytes).get(dest, destOffset, length); + bytes.get(dest, destOffset, length); } @Override @@ -248,10 +247,10 @@ public void readBytes(ByteBuffer dest) { // Change the limit so that only length bytes are available. int prevLimit = bytes.limit(); - bytes.limit(bytes.position() + length); + ((Buffer) bytes).limit(bytes.position() + length); // Write the bytes and restore the original limit. - dest.put((ByteBuffer) bytes); + dest.put(bytes); bytes.limit(prevLimit); } @@ -260,11 +259,11 @@ public void readBytes(OutputStream dest, int length) throws IOException { checkReadable(length); if (hasArray()) { dest.write(array(), arrayOffset(), length); - bytes.position(bytes.position() + length); + ((Buffer) bytes).position(bytes.position() + length); } else { // The buffer doesn't support array(). Copy the data to an intermediate buffer. byte[] array = new byte[length]; - ((ByteBuffer) bytes).get(array); + bytes.get(array); dest.write(array); } } @@ -272,9 +271,9 @@ public void readBytes(OutputStream dest, int length) throws IOException { @Override public ByteReadableBufferWrapper readBytes(int length) { checkReadable(length); - ByteBuffer buffer = ((ByteBuffer) bytes).duplicate(); + ByteBuffer buffer = bytes.duplicate(); ((Buffer) buffer).limit(bytes.position() + length); - bytes.position(bytes.position() + length); + ((Buffer) bytes).position(bytes.position() + length); return new ByteReadableBufferWrapper(buffer); } @@ -285,7 +284,7 @@ public boolean hasArray() { @Override public byte[] array() { - return ((ByteBuffer) bytes).array(); + return bytes.array(); } @Override diff --git a/core/src/test/java/io/grpc/internal/CompositeReadableBufferTest.java b/core/src/test/java/io/grpc/internal/CompositeReadableBufferTest.java index a73df727dd7..660aa116317 100644 --- a/core/src/test/java/io/grpc/internal/CompositeReadableBufferTest.java +++ b/core/src/test/java/io/grpc/internal/CompositeReadableBufferTest.java @@ -23,6 +23,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.nio.Buffer; import java.nio.ByteBuffer; import org.junit.After; import org.junit.Before; @@ -119,17 +120,17 @@ public void readByteBufferShouldSucceed() { ByteBuffer byteBuffer = ByteBuffer.allocate(EXPECTED_VALUE.length()); int remaining = EXPECTED_VALUE.length(); - byteBuffer.limit(1); + ((Buffer) byteBuffer).limit(1); composite.readBytes(byteBuffer); remaining--; assertEquals(remaining, composite.readableBytes()); - byteBuffer.limit(byteBuffer.limit() + 5); + ((Buffer) byteBuffer).limit(byteBuffer.limit() + 5); composite.readBytes(byteBuffer); remaining -= 5; assertEquals(remaining, composite.readableBytes()); - byteBuffer.limit(byteBuffer.limit() + remaining); + ((Buffer) byteBuffer).limit(byteBuffer.limit() + remaining); composite.readBytes(byteBuffer); assertEquals(0, composite.readableBytes()); assertEquals(EXPECTED_VALUE, new String(byteBuffer.array(), UTF_8)); diff --git a/core/src/test/java/io/grpc/internal/ReadableBufferTestBase.java b/core/src/test/java/io/grpc/internal/ReadableBufferTestBase.java index c53b89109cd..e469b807d51 100644 --- a/core/src/test/java/io/grpc/internal/ReadableBufferTestBase.java +++ b/core/src/test/java/io/grpc/internal/ReadableBufferTestBase.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertEquals; import java.io.ByteArrayOutputStream; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.util.Arrays; import org.junit.Test; @@ -86,7 +87,7 @@ public void readToByteBufferShouldSucceed() { ReadableBuffer buffer = buffer(); ByteBuffer byteBuffer = ByteBuffer.allocate(msg.length()); buffer.readBytes(byteBuffer); - byteBuffer.flip(); + ((Buffer) byteBuffer).flip(); byte[] array = new byte[msg.length()]; byteBuffer.get(array); assertArrayEquals(msg.getBytes(UTF_8), array); @@ -98,7 +99,7 @@ public void partialReadToByteBufferShouldSucceed() { ReadableBuffer buffer = buffer(); ByteBuffer byteBuffer = ByteBuffer.allocate(2); buffer.readBytes(byteBuffer); - byteBuffer.flip(); + ((Buffer) byteBuffer).flip(); byte[] array = new byte[2]; byteBuffer.get(array); assertArrayEquals(new byte[]{'h', 'e'}, array); diff --git a/cronet/src/main/java/io/grpc/cronet/CronetClientStream.java b/cronet/src/main/java/io/grpc/cronet/CronetClientStream.java index 000828b5a36..d44b716146e 100644 --- a/cronet/src/main/java/io/grpc/cronet/CronetClientStream.java +++ b/cronet/src/main/java/io/grpc/cronet/CronetClientStream.java @@ -42,6 +42,7 @@ import io.grpc.internal.WritableBuffer; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.ArrayList; @@ -218,7 +219,7 @@ public void writeFrame( ByteBuffer byteBuffer; if (buffer != null) { byteBuffer = ((CronetWritableBuffer) buffer).buffer(); - byteBuffer.flip(); + ((Buffer) byteBuffer).flip(); } else { byteBuffer = EMPTY_BUFFER; } @@ -471,7 +472,7 @@ public void onResponseHeadersReceived(BidirectionalStream stream, UrlResponseInf @Override public void onReadCompleted(BidirectionalStream stream, UrlResponseInfo info, ByteBuffer buffer, boolean endOfStream) { - buffer.flip(); + ((Buffer) buffer).flip(); if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) { Log.v(LOG_TAG, "onReadCompleted. Size=" + buffer.remaining()); } diff --git a/cronet/src/test/java/io/grpc/cronet/CronetClientStreamTest.java b/cronet/src/test/java/io/grpc/cronet/CronetClientStreamTest.java index 34a8b601dbe..2983c14044b 100644 --- a/cronet/src/test/java/io/grpc/cronet/CronetClientStreamTest.java +++ b/cronet/src/test/java/io/grpc/cronet/CronetClientStreamTest.java @@ -43,6 +43,7 @@ import io.grpc.internal.WritableBuffer; import io.grpc.testing.TestMethodDescriptors; import java.io.ByteArrayInputStream; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.ArrayList; @@ -176,7 +177,7 @@ public void write() { // 5 writes are called. verify(cronetStream, times(5)).write(isA(ByteBuffer.class), eq(false)); ByteBuffer fakeBuffer = ByteBuffer.allocateDirect(8); - fakeBuffer.position(8); + ((Buffer) fakeBuffer).position(8); verify(cronetStream, times(2)).flush(); // 5 onWriteCompleted callbacks for previous writes. @@ -294,7 +295,7 @@ public void streamSucceeded() { ArgumentCaptor bufferCaptor = ArgumentCaptor.forClass(ByteBuffer.class); verify(cronetStream, times(1)).write(bufferCaptor.capture(), isA(Boolean.class)); ByteBuffer buffer = bufferCaptor.getValue(); - buffer.position(request.length()); + ((Buffer) buffer).position(request.length()); verify(cronetStream, times(1)).flush(); // Receive response header From db5f82c4dcce7f9659c63237bd92ff597b7f5819 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Tue, 8 Sep 2020 18:24:20 -0700 Subject: [PATCH 83/88] Update README etc to reference 1.32.0 --- README.md | 28 ++++++++++++------------ cronet/README.md | 2 +- documentation/android-channel-builder.md | 4 ++-- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 164145d8c01..6a785fa2fed 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,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.31.1/examples) and the -[Android example](https://github.com/grpc/grpc-java/tree/v1.31.1/examples/android) +The [examples](https://github.com/grpc/grpc-java/tree/v1.32.0/examples) and the +[Android example](https://github.com/grpc/grpc-java/tree/v1.32.0/examples/android) are standalone projects that showcase the usage of gRPC. Download @@ -42,17 +42,17 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`: io.grpc grpc-netty-shaded - 1.31.1 + 1.32.0 io.grpc grpc-protobuf - 1.31.1 + 1.32.0 io.grpc grpc-stub - 1.31.1 + 1.32.0 org.apache.tomcat @@ -64,23 +64,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 -implementation 'io.grpc:grpc-netty-shaded:1.31.1' -implementation 'io.grpc:grpc-protobuf:1.31.1' -implementation 'io.grpc:grpc-stub:1.31.1' +implementation 'io.grpc:grpc-netty-shaded:1.32.0' +implementation 'io.grpc:grpc-protobuf:1.32.0' +implementation 'io.grpc:grpc-stub:1.32.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.31.1' -implementation 'io.grpc:grpc-protobuf-lite:1.31.1' -implementation 'io.grpc:grpc-stub:1.31.1' +implementation 'io.grpc:grpc-okhttp:1.32.0' +implementation 'io.grpc:grpc-protobuf-lite:1.32.0' +implementation 'io.grpc:grpc-stub:1.32.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.31.1 +https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.32.0 Development snapshots are available in [Sonatypes's snapshot repository](https://oss.sonatype.org/content/repositories/snapshots/). @@ -112,7 +112,7 @@ For protobuf-based codegen integrated with the Maven build system, you can use com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier} grpc-java - io.grpc:protoc-gen-grpc-java:1.31.1:exe:${os.detected.classifier} + io.grpc:protoc-gen-grpc-java:1.32.0:exe:${os.detected.classifier} @@ -142,7 +142,7 @@ protobuf { } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.31.1' + artifact = 'io.grpc:protoc-gen-grpc-java:1.32.0' } } generateProtoTasks { diff --git a/cronet/README.md b/cronet/README.md index 5cfe8858bd6..60258543958 100644 --- a/cronet/README.md +++ b/cronet/README.md @@ -26,7 +26,7 @@ In your app module's `build.gradle` file, include a dependency on both `grpc-cro Google Play Services Client Library for Cronet ``` -implementation 'io.grpc:grpc-cronet:1.31.1' +implementation 'io.grpc:grpc-cronet:1.32.0' implementation 'com.google.android.gms:play-services-cronet:16.0.0' ``` diff --git a/documentation/android-channel-builder.md b/documentation/android-channel-builder.md index 9e6e233806f..8a7ff3c5b61 100644 --- a/documentation/android-channel-builder.md +++ b/documentation/android-channel-builder.md @@ -36,8 +36,8 @@ In your `build.gradle` file, include a dependency on both `grpc-android` and `grpc-okhttp`: ``` -implementation 'io.grpc:grpc-android:1.31.1' -implementation 'io.grpc:grpc-okhttp:1.31.1' +implementation 'io.grpc:grpc-android:1.32.0' +implementation 'io.grpc:grpc-okhttp:1.32.0' ``` You also need permission to access the device's network state in your From a093a69fec50966a5b0b802407f846ccf9855d2e Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Tue, 8 Sep 2020 18:29:01 -0700 Subject: [PATCH 84/88] Bump version to 1.32.0 --- build.gradle | 2 +- .../src/test/golden/TestDeprecatedService.java.txt | 2 +- compiler/src/test/golden/TestService.java.txt | 2 +- .../src/testLite/golden/TestDeprecatedService.java.txt | 2 +- compiler/src/testLite/golden/TestService.java.txt | 2 +- core/src/main/java/io/grpc/internal/GrpcUtil.java | 2 +- examples/android/clientcache/app/build.gradle | 10 +++++----- examples/android/helloworld/app/build.gradle | 8 ++++---- examples/android/routeguide/app/build.gradle | 8 ++++---- examples/android/strictmode/app/build.gradle | 8 ++++---- examples/build.gradle | 2 +- examples/example-alts/build.gradle | 2 +- examples/example-gauth/build.gradle | 2 +- examples/example-gauth/pom.xml | 4 ++-- examples/example-hostname/build.gradle | 2 +- examples/example-hostname/pom.xml | 4 ++-- examples/example-jwt-auth/build.gradle | 2 +- examples/example-jwt-auth/pom.xml | 4 ++-- examples/example-tls/build.gradle | 2 +- examples/example-tls/pom.xml | 4 ++-- examples/example-xds/build.gradle | 2 +- examples/pom.xml | 4 ++-- 22 files changed, 40 insertions(+), 40 deletions(-) diff --git a/build.gradle b/build.gradle index fcb2ddd6678..a66ebbad048 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,7 @@ subprojects { apply plugin: "net.ltgt.errorprone" group = "io.grpc" - version = "1.32.0-SNAPSHOT" // CURRENT_GRPC_VERSION + version = "1.32.0" // CURRENT_GRPC_VERSION repositories { maven { // The google mirror is less flaky than mavenCentral() diff --git a/compiler/src/test/golden/TestDeprecatedService.java.txt b/compiler/src/test/golden/TestDeprecatedService.java.txt index 1374659889b..45800e4864e 100644 --- a/compiler/src/test/golden/TestDeprecatedService.java.txt +++ b/compiler/src/test/golden/TestDeprecatedService.java.txt @@ -21,7 +21,7 @@ import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.32.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.32.0)", comments = "Source: grpc/testing/compiler/test.proto") @java.lang.Deprecated public final class TestDeprecatedServiceGrpc { diff --git a/compiler/src/test/golden/TestService.java.txt b/compiler/src/test/golden/TestService.java.txt index f6fd217cbdd..6618e84c545 100644 --- a/compiler/src/test/golden/TestService.java.txt +++ b/compiler/src/test/golden/TestService.java.txt @@ -21,7 +21,7 @@ import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.32.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.32.0)", comments = "Source: grpc/testing/compiler/test.proto") public final class TestServiceGrpc { diff --git a/compiler/src/testLite/golden/TestDeprecatedService.java.txt b/compiler/src/testLite/golden/TestDeprecatedService.java.txt index 694bab3b3d8..e0049f3bf69 100644 --- a/compiler/src/testLite/golden/TestDeprecatedService.java.txt +++ b/compiler/src/testLite/golden/TestDeprecatedService.java.txt @@ -21,7 +21,7 @@ import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.32.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.32.0)", comments = "Source: grpc/testing/compiler/test.proto") @java.lang.Deprecated public final class TestDeprecatedServiceGrpc { diff --git a/compiler/src/testLite/golden/TestService.java.txt b/compiler/src/testLite/golden/TestService.java.txt index 50fc255cd4a..92f8744d1de 100644 --- a/compiler/src/testLite/golden/TestService.java.txt +++ b/compiler/src/testLite/golden/TestService.java.txt @@ -21,7 +21,7 @@ import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.32.0-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.32.0)", comments = "Source: grpc/testing/compiler/test.proto") public final class TestServiceGrpc { diff --git a/core/src/main/java/io/grpc/internal/GrpcUtil.java b/core/src/main/java/io/grpc/internal/GrpcUtil.java index 3e210961541..e4c79733a64 100644 --- a/core/src/main/java/io/grpc/internal/GrpcUtil.java +++ b/core/src/main/java/io/grpc/internal/GrpcUtil.java @@ -197,7 +197,7 @@ public byte[] parseAsciiString(byte[] serialized) { public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults(); - private static final String IMPLEMENTATION_VERSION = "1.32.0-SNAPSHOT"; // CURRENT_GRPC_VERSION + private static final String IMPLEMENTATION_VERSION = "1.32.0"; // CURRENT_GRPC_VERSION /** * The default timeout in nanos for a keepalive ping request. diff --git a/examples/android/clientcache/app/build.gradle b/examples/android/clientcache/app/build.gradle index 96e05eba65f..0124a13e27c 100644 --- a/examples/android/clientcache/app/build.gradle +++ b/examples/android/clientcache/app/build.gradle @@ -30,7 +30,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.12.0' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.0' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -50,12 +50,12 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.0.2' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.32.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.32.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.32.0' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' testImplementation 'junit:junit:4.12' testImplementation 'com.google.truth:truth:1.0.1' - testImplementation 'io.grpc:grpc-testing:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION + testImplementation 'io.grpc:grpc-testing:1.32.0' // CURRENT_GRPC_VERSION } diff --git a/examples/android/helloworld/app/build.gradle b/examples/android/helloworld/app/build.gradle index 6f0b4da88a9..b131eb3580a 100644 --- a/examples/android/helloworld/app/build.gradle +++ b/examples/android/helloworld/app/build.gradle @@ -28,7 +28,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.12.0' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.0' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -48,8 +48,8 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.0.2' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.32.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.32.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.32.0' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/android/routeguide/app/build.gradle b/examples/android/routeguide/app/build.gradle index 3c32ad0f46f..3275494356f 100644 --- a/examples/android/routeguide/app/build.gradle +++ b/examples/android/routeguide/app/build.gradle @@ -28,7 +28,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.12.0' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.0' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -48,8 +48,8 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.0.2' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.32.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.32.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.32.0' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/android/strictmode/app/build.gradle b/examples/android/strictmode/app/build.gradle index 03d923d5a80..4c66aa64702 100644 --- a/examples/android/strictmode/app/build.gradle +++ b/examples/android/strictmode/app/build.gradle @@ -29,7 +29,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.12.0' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.0' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -49,8 +49,8 @@ dependencies { implementation 'com.android.support:appcompat-v7:28.0.0' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.32.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.32.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.32.0' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/build.gradle b/examples/build.gradle index 770e9e28d9e..7054ddeafc6 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -22,7 +22,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.0' // CURRENT_GRPC_VERSION def protobufVersion = '3.12.0' def protocVersion = protobufVersion diff --git a/examples/example-alts/build.gradle b/examples/example-alts/build.gradle index 7f3d202f01f..ff1958180a8 100644 --- a/examples/example-alts/build.gradle +++ b/examples/example-alts/build.gradle @@ -23,7 +23,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.0' // CURRENT_GRPC_VERSION def protocVersion = '3.12.0' dependencies { diff --git a/examples/example-gauth/build.gradle b/examples/example-gauth/build.gradle index 76a1563b167..ee847c8dd64 100644 --- a/examples/example-gauth/build.gradle +++ b/examples/example-gauth/build.gradle @@ -23,7 +23,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.0' // CURRENT_GRPC_VERSION def protobufVersion = '3.12.0' def protocVersion = protobufVersion diff --git a/examples/example-gauth/pom.xml b/examples/example-gauth/pom.xml index 779f91a0250..a08533649eb 100644 --- a/examples/example-gauth/pom.xml +++ b/examples/example-gauth/pom.xml @@ -6,13 +6,13 @@ jar - 1.32.0-SNAPSHOT + 1.32.0 example-gauth https://github.com/grpc/grpc-java UTF-8 - 1.32.0-SNAPSHOT + 1.32.0 3.12.0 1.7 diff --git a/examples/example-hostname/build.gradle b/examples/example-hostname/build.gradle index b7624483aac..412ed5c1ab3 100644 --- a/examples/example-hostname/build.gradle +++ b/examples/example-hostname/build.gradle @@ -21,7 +21,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.0' // CURRENT_GRPC_VERSION def protobufVersion = '3.12.0' dependencies { diff --git a/examples/example-hostname/pom.xml b/examples/example-hostname/pom.xml index fea6ee83fd0..081d11a0d89 100644 --- a/examples/example-hostname/pom.xml +++ b/examples/example-hostname/pom.xml @@ -6,13 +6,13 @@ jar - 1.32.0-SNAPSHOT + 1.32.0 example-hostname https://github.com/grpc/grpc-java UTF-8 - 1.32.0-SNAPSHOT + 1.32.0 3.12.0 1.7 diff --git a/examples/example-jwt-auth/build.gradle b/examples/example-jwt-auth/build.gradle index a597a7e4beb..28524c0479d 100644 --- a/examples/example-jwt-auth/build.gradle +++ b/examples/example-jwt-auth/build.gradle @@ -22,7 +22,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.0' // CURRENT_GRPC_VERSION def protobufVersion = '3.12.0' def protocVersion = protobufVersion diff --git a/examples/example-jwt-auth/pom.xml b/examples/example-jwt-auth/pom.xml index cc813513bec..6adcff91d06 100644 --- a/examples/example-jwt-auth/pom.xml +++ b/examples/example-jwt-auth/pom.xml @@ -7,13 +7,13 @@ jar - 1.32.0-SNAPSHOT + 1.32.0 example-jwt-auth https://github.com/grpc/grpc-java UTF-8 - 1.32.0-SNAPSHOT + 1.32.0 3.12.0 3.12.0 diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index 9b420626e71..b08a2e6a69a 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -23,7 +23,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.0' // CURRENT_GRPC_VERSION def nettyTcNativeVersion = '2.0.31.Final' def protocVersion = '3.12.0' diff --git a/examples/example-tls/pom.xml b/examples/example-tls/pom.xml index e51be823719..ec6e88fe596 100644 --- a/examples/example-tls/pom.xml +++ b/examples/example-tls/pom.xml @@ -6,13 +6,13 @@ jar - 1.32.0-SNAPSHOT + 1.32.0 example-tls https://github.com/grpc/grpc-java UTF-8 - 1.32.0-SNAPSHOT + 1.32.0 3.12.0 2.0.31.Final diff --git a/examples/example-xds/build.gradle b/examples/example-xds/build.gradle index 4142790c764..d83d4856960 100644 --- a/examples/example-xds/build.gradle +++ b/examples/example-xds/build.gradle @@ -18,7 +18,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.32.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.0' // CURRENT_GRPC_VERSION dependencies { // This example's client is the same as the helloworld client. We depend on the helloworld diff --git a/examples/pom.xml b/examples/pom.xml index d6aa9b6aabc..e5df7bb7d29 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -6,13 +6,13 @@ jar - 1.32.0-SNAPSHOT + 1.32.0 examples https://github.com/grpc/grpc-java UTF-8 - 1.32.0-SNAPSHOT + 1.32.0 3.12.0 3.12.0 From 01bf056fda4e675f6274da2cfa9e456ea2658829 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Tue, 8 Sep 2020 18:32:14 -0700 Subject: [PATCH 85/88] Bump version to 1.32.1-SNAPSHOT --- build.gradle | 2 +- .../src/test/golden/TestDeprecatedService.java.txt | 2 +- compiler/src/test/golden/TestService.java.txt | 2 +- .../src/testLite/golden/TestDeprecatedService.java.txt | 2 +- compiler/src/testLite/golden/TestService.java.txt | 2 +- core/src/main/java/io/grpc/internal/GrpcUtil.java | 2 +- examples/android/clientcache/app/build.gradle | 10 +++++----- examples/android/helloworld/app/build.gradle | 8 ++++---- examples/android/routeguide/app/build.gradle | 8 ++++---- examples/android/strictmode/app/build.gradle | 8 ++++---- examples/build.gradle | 2 +- examples/example-alts/build.gradle | 2 +- examples/example-gauth/build.gradle | 2 +- examples/example-gauth/pom.xml | 4 ++-- examples/example-hostname/build.gradle | 2 +- examples/example-hostname/pom.xml | 4 ++-- examples/example-jwt-auth/build.gradle | 2 +- examples/example-jwt-auth/pom.xml | 4 ++-- examples/example-tls/build.gradle | 2 +- examples/example-tls/pom.xml | 4 ++-- examples/example-xds/build.gradle | 2 +- examples/pom.xml | 4 ++-- 22 files changed, 40 insertions(+), 40 deletions(-) diff --git a/build.gradle b/build.gradle index a66ebbad048..f56a70a46b5 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,7 @@ subprojects { apply plugin: "net.ltgt.errorprone" group = "io.grpc" - version = "1.32.0" // CURRENT_GRPC_VERSION + version = "1.32.1-SNAPSHOT" // CURRENT_GRPC_VERSION repositories { maven { // The google mirror is less flaky than mavenCentral() diff --git a/compiler/src/test/golden/TestDeprecatedService.java.txt b/compiler/src/test/golden/TestDeprecatedService.java.txt index 45800e4864e..f909f8ba497 100644 --- a/compiler/src/test/golden/TestDeprecatedService.java.txt +++ b/compiler/src/test/golden/TestDeprecatedService.java.txt @@ -21,7 +21,7 @@ import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.32.0)", + value = "by gRPC proto compiler (version 1.32.1-SNAPSHOT)", comments = "Source: grpc/testing/compiler/test.proto") @java.lang.Deprecated public final class TestDeprecatedServiceGrpc { diff --git a/compiler/src/test/golden/TestService.java.txt b/compiler/src/test/golden/TestService.java.txt index 6618e84c545..d9f37bc74f7 100644 --- a/compiler/src/test/golden/TestService.java.txt +++ b/compiler/src/test/golden/TestService.java.txt @@ -21,7 +21,7 @@ import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.32.0)", + value = "by gRPC proto compiler (version 1.32.1-SNAPSHOT)", comments = "Source: grpc/testing/compiler/test.proto") public final class TestServiceGrpc { diff --git a/compiler/src/testLite/golden/TestDeprecatedService.java.txt b/compiler/src/testLite/golden/TestDeprecatedService.java.txt index e0049f3bf69..65bb115decf 100644 --- a/compiler/src/testLite/golden/TestDeprecatedService.java.txt +++ b/compiler/src/testLite/golden/TestDeprecatedService.java.txt @@ -21,7 +21,7 @@ import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.32.0)", + value = "by gRPC proto compiler (version 1.32.1-SNAPSHOT)", comments = "Source: grpc/testing/compiler/test.proto") @java.lang.Deprecated public final class TestDeprecatedServiceGrpc { diff --git a/compiler/src/testLite/golden/TestService.java.txt b/compiler/src/testLite/golden/TestService.java.txt index 92f8744d1de..7d1d77bbc90 100644 --- a/compiler/src/testLite/golden/TestService.java.txt +++ b/compiler/src/testLite/golden/TestService.java.txt @@ -21,7 +21,7 @@ import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.32.0)", + value = "by gRPC proto compiler (version 1.32.1-SNAPSHOT)", comments = "Source: grpc/testing/compiler/test.proto") public final class TestServiceGrpc { diff --git a/core/src/main/java/io/grpc/internal/GrpcUtil.java b/core/src/main/java/io/grpc/internal/GrpcUtil.java index e4c79733a64..9310db2becd 100644 --- a/core/src/main/java/io/grpc/internal/GrpcUtil.java +++ b/core/src/main/java/io/grpc/internal/GrpcUtil.java @@ -197,7 +197,7 @@ public byte[] parseAsciiString(byte[] serialized) { public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults(); - private static final String IMPLEMENTATION_VERSION = "1.32.0"; // CURRENT_GRPC_VERSION + private static final String IMPLEMENTATION_VERSION = "1.32.1-SNAPSHOT"; // CURRENT_GRPC_VERSION /** * The default timeout in nanos for a keepalive ping request. diff --git a/examples/android/clientcache/app/build.gradle b/examples/android/clientcache/app/build.gradle index 0124a13e27c..237bef81176 100644 --- a/examples/android/clientcache/app/build.gradle +++ b/examples/android/clientcache/app/build.gradle @@ -30,7 +30,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.12.0' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.0' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -50,12 +50,12 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.0.2' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.32.0' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.32.0' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.32.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' testImplementation 'junit:junit:4.12' testImplementation 'com.google.truth:truth:1.0.1' - testImplementation 'io.grpc:grpc-testing:1.32.0' // CURRENT_GRPC_VERSION + testImplementation 'io.grpc:grpc-testing:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION } diff --git a/examples/android/helloworld/app/build.gradle b/examples/android/helloworld/app/build.gradle index b131eb3580a..fd574908510 100644 --- a/examples/android/helloworld/app/build.gradle +++ b/examples/android/helloworld/app/build.gradle @@ -28,7 +28,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.12.0' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.0' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -48,8 +48,8 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.0.2' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.32.0' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.32.0' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.32.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/android/routeguide/app/build.gradle b/examples/android/routeguide/app/build.gradle index 3275494356f..d5476a0e844 100644 --- a/examples/android/routeguide/app/build.gradle +++ b/examples/android/routeguide/app/build.gradle @@ -28,7 +28,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.12.0' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.0' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -48,8 +48,8 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.0.2' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.32.0' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.32.0' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.32.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/android/strictmode/app/build.gradle b/examples/android/strictmode/app/build.gradle index 4c66aa64702..e93ebfd2463 100644 --- a/examples/android/strictmode/app/build.gradle +++ b/examples/android/strictmode/app/build.gradle @@ -29,7 +29,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.12.0' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.0' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -49,8 +49,8 @@ dependencies { implementation 'com.android.support:appcompat-v7:28.0.0' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.32.0' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.32.0' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.32.0' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/build.gradle b/examples/build.gradle index 7054ddeafc6..f47cc996115 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -22,7 +22,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.32.0' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.12.0' def protocVersion = protobufVersion diff --git a/examples/example-alts/build.gradle b/examples/example-alts/build.gradle index ff1958180a8..d513f2bc627 100644 --- a/examples/example-alts/build.gradle +++ b/examples/example-alts/build.gradle @@ -23,7 +23,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.32.0' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION def protocVersion = '3.12.0' dependencies { diff --git a/examples/example-gauth/build.gradle b/examples/example-gauth/build.gradle index ee847c8dd64..e389d3c035e 100644 --- a/examples/example-gauth/build.gradle +++ b/examples/example-gauth/build.gradle @@ -23,7 +23,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.32.0' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.12.0' def protocVersion = protobufVersion diff --git a/examples/example-gauth/pom.xml b/examples/example-gauth/pom.xml index a08533649eb..3017c607243 100644 --- a/examples/example-gauth/pom.xml +++ b/examples/example-gauth/pom.xml @@ -6,13 +6,13 @@ jar - 1.32.0 + 1.32.1-SNAPSHOT example-gauth https://github.com/grpc/grpc-java UTF-8 - 1.32.0 + 1.32.1-SNAPSHOT 3.12.0 1.7 diff --git a/examples/example-hostname/build.gradle b/examples/example-hostname/build.gradle index 412ed5c1ab3..e09030367c5 100644 --- a/examples/example-hostname/build.gradle +++ b/examples/example-hostname/build.gradle @@ -21,7 +21,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.32.0' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.12.0' dependencies { diff --git a/examples/example-hostname/pom.xml b/examples/example-hostname/pom.xml index 081d11a0d89..1a8652ec014 100644 --- a/examples/example-hostname/pom.xml +++ b/examples/example-hostname/pom.xml @@ -6,13 +6,13 @@ jar - 1.32.0 + 1.32.1-SNAPSHOT example-hostname https://github.com/grpc/grpc-java UTF-8 - 1.32.0 + 1.32.1-SNAPSHOT 3.12.0 1.7 diff --git a/examples/example-jwt-auth/build.gradle b/examples/example-jwt-auth/build.gradle index 28524c0479d..0c569f7f5a8 100644 --- a/examples/example-jwt-auth/build.gradle +++ b/examples/example-jwt-auth/build.gradle @@ -22,7 +22,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.32.0' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION def protobufVersion = '3.12.0' def protocVersion = protobufVersion diff --git a/examples/example-jwt-auth/pom.xml b/examples/example-jwt-auth/pom.xml index 6adcff91d06..5b578fca4a6 100644 --- a/examples/example-jwt-auth/pom.xml +++ b/examples/example-jwt-auth/pom.xml @@ -7,13 +7,13 @@ jar - 1.32.0 + 1.32.1-SNAPSHOT example-jwt-auth https://github.com/grpc/grpc-java UTF-8 - 1.32.0 + 1.32.1-SNAPSHOT 3.12.0 3.12.0 diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index b08a2e6a69a..994154bd6ab 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -23,7 +23,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.32.0' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION def nettyTcNativeVersion = '2.0.31.Final' def protocVersion = '3.12.0' diff --git a/examples/example-tls/pom.xml b/examples/example-tls/pom.xml index ec6e88fe596..29bf6a37f66 100644 --- a/examples/example-tls/pom.xml +++ b/examples/example-tls/pom.xml @@ -6,13 +6,13 @@ jar - 1.32.0 + 1.32.1-SNAPSHOT example-tls https://github.com/grpc/grpc-java UTF-8 - 1.32.0 + 1.32.1-SNAPSHOT 3.12.0 2.0.31.Final diff --git a/examples/example-xds/build.gradle b/examples/example-xds/build.gradle index d83d4856960..fd9c43d0c78 100644 --- a/examples/example-xds/build.gradle +++ b/examples/example-xds/build.gradle @@ -18,7 +18,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.32.0' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION dependencies { // This example's client is the same as the helloworld client. We depend on the helloworld diff --git a/examples/pom.xml b/examples/pom.xml index e5df7bb7d29..3c7e09800c8 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -6,13 +6,13 @@ jar - 1.32.0 + 1.32.1-SNAPSHOT examples https://github.com/grpc/grpc-java UTF-8 - 1.32.0 + 1.32.1-SNAPSHOT 3.12.0 3.12.0 From b33bec09be53c8f252fad29925a032418e24d44f Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Wed, 9 Sep 2020 10:07:48 -0700 Subject: [PATCH 86/88] netty-shaded: Fix publish regression for javadoc and sources 96ad6338 accidentally caused the javadoc and sources jars to no longer be published for grpc-netty-shaded. It would appear to be due to the jars being empty. This commit causes them to be published again. --- netty/shaded/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/netty/shaded/build.gradle b/netty/shaded/build.gradle index 904bccd44df..5226fb60ab2 100644 --- a/netty/shaded/build.gradle +++ b/netty/shaded/build.gradle @@ -47,6 +47,9 @@ publishing { maven(MavenPublication) { // Ideally swap to project.shadow.component(it) when it isn't broken for project deps artifact shadowJar + // Empty jars are not published via withJavadocJar() and withSourcesJar() + artifact javadocJar + artifact sourcesJar pom.withXml { def dependencies = asNode().appendNode('dependencies') From 0e12c89bee3904d278245f3e1badcac9b99ac8cd Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Wed, 9 Sep 2020 15:47:50 -0700 Subject: [PATCH 87/88] Update README etc to reference 1.32.1 --- README.md | 28 ++++++++++++------------ cronet/README.md | 2 +- documentation/android-channel-builder.md | 4 ++-- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 6a785fa2fed..b1a66311d75 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,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.32.0/examples) and the -[Android example](https://github.com/grpc/grpc-java/tree/v1.32.0/examples/android) +The [examples](https://github.com/grpc/grpc-java/tree/v1.32.1/examples) and the +[Android example](https://github.com/grpc/grpc-java/tree/v1.32.1/examples/android) are standalone projects that showcase the usage of gRPC. Download @@ -42,17 +42,17 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`: io.grpc grpc-netty-shaded - 1.32.0 + 1.32.1 io.grpc grpc-protobuf - 1.32.0 + 1.32.1 io.grpc grpc-stub - 1.32.0 + 1.32.1 org.apache.tomcat @@ -64,23 +64,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 -implementation 'io.grpc:grpc-netty-shaded:1.32.0' -implementation 'io.grpc:grpc-protobuf:1.32.0' -implementation 'io.grpc:grpc-stub:1.32.0' +implementation 'io.grpc:grpc-netty-shaded:1.32.1' +implementation 'io.grpc:grpc-protobuf:1.32.1' +implementation 'io.grpc:grpc-stub:1.32.1' compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+ ``` For Android client, use `grpc-okhttp` instead of `grpc-netty-shaded` and `grpc-protobuf-lite` instead of `grpc-protobuf`: ```gradle -implementation 'io.grpc:grpc-okhttp:1.32.0' -implementation 'io.grpc:grpc-protobuf-lite:1.32.0' -implementation 'io.grpc:grpc-stub:1.32.0' +implementation 'io.grpc:grpc-okhttp:1.32.1' +implementation 'io.grpc:grpc-protobuf-lite:1.32.1' +implementation 'io.grpc:grpc-stub:1.32.1' compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+ ``` [the JARs]: -https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.32.0 +https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.32.1 Development snapshots are available in [Sonatypes's snapshot repository](https://oss.sonatype.org/content/repositories/snapshots/). @@ -112,7 +112,7 @@ For protobuf-based codegen integrated with the Maven build system, you can use com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier} grpc-java - io.grpc:protoc-gen-grpc-java:1.32.0:exe:${os.detected.classifier} + io.grpc:protoc-gen-grpc-java:1.32.1:exe:${os.detected.classifier} @@ -142,7 +142,7 @@ protobuf { } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.32.0' + artifact = 'io.grpc:protoc-gen-grpc-java:1.32.1' } } generateProtoTasks { diff --git a/cronet/README.md b/cronet/README.md index 60258543958..b752082c42f 100644 --- a/cronet/README.md +++ b/cronet/README.md @@ -26,7 +26,7 @@ In your app module's `build.gradle` file, include a dependency on both `grpc-cro Google Play Services Client Library for Cronet ``` -implementation 'io.grpc:grpc-cronet:1.32.0' +implementation 'io.grpc:grpc-cronet:1.32.1' implementation 'com.google.android.gms:play-services-cronet:16.0.0' ``` diff --git a/documentation/android-channel-builder.md b/documentation/android-channel-builder.md index 8a7ff3c5b61..579f4f13210 100644 --- a/documentation/android-channel-builder.md +++ b/documentation/android-channel-builder.md @@ -36,8 +36,8 @@ In your `build.gradle` file, include a dependency on both `grpc-android` and `grpc-okhttp`: ``` -implementation 'io.grpc:grpc-android:1.32.0' -implementation 'io.grpc:grpc-okhttp:1.32.0' +implementation 'io.grpc:grpc-android:1.32.1' +implementation 'io.grpc:grpc-okhttp:1.32.1' ``` You also need permission to access the device's network state in your From cb8b0679add2d389cfd15cd34e11c3577e3de2cb Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Wed, 9 Sep 2020 15:51:00 -0700 Subject: [PATCH 88/88] Bump version to 1.32.1 --- build.gradle | 2 +- .../src/test/golden/TestDeprecatedService.java.txt | 2 +- compiler/src/test/golden/TestService.java.txt | 2 +- .../src/testLite/golden/TestDeprecatedService.java.txt | 2 +- compiler/src/testLite/golden/TestService.java.txt | 2 +- core/src/main/java/io/grpc/internal/GrpcUtil.java | 2 +- examples/android/clientcache/app/build.gradle | 10 +++++----- examples/android/helloworld/app/build.gradle | 8 ++++---- examples/android/routeguide/app/build.gradle | 8 ++++---- examples/android/strictmode/app/build.gradle | 8 ++++---- examples/build.gradle | 2 +- examples/example-alts/build.gradle | 2 +- examples/example-gauth/build.gradle | 2 +- examples/example-gauth/pom.xml | 4 ++-- examples/example-hostname/build.gradle | 2 +- examples/example-hostname/pom.xml | 4 ++-- examples/example-jwt-auth/build.gradle | 2 +- examples/example-jwt-auth/pom.xml | 4 ++-- examples/example-tls/build.gradle | 2 +- examples/example-tls/pom.xml | 4 ++-- examples/example-xds/build.gradle | 2 +- examples/pom.xml | 4 ++-- 22 files changed, 40 insertions(+), 40 deletions(-) diff --git a/build.gradle b/build.gradle index f56a70a46b5..52deaf5c8b0 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,7 @@ subprojects { apply plugin: "net.ltgt.errorprone" group = "io.grpc" - version = "1.32.1-SNAPSHOT" // CURRENT_GRPC_VERSION + version = "1.32.1" // CURRENT_GRPC_VERSION repositories { maven { // The google mirror is less flaky than mavenCentral() diff --git a/compiler/src/test/golden/TestDeprecatedService.java.txt b/compiler/src/test/golden/TestDeprecatedService.java.txt index f909f8ba497..60531c7d4c2 100644 --- a/compiler/src/test/golden/TestDeprecatedService.java.txt +++ b/compiler/src/test/golden/TestDeprecatedService.java.txt @@ -21,7 +21,7 @@ import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.32.1-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.32.1)", comments = "Source: grpc/testing/compiler/test.proto") @java.lang.Deprecated public final class TestDeprecatedServiceGrpc { diff --git a/compiler/src/test/golden/TestService.java.txt b/compiler/src/test/golden/TestService.java.txt index d9f37bc74f7..958064f5ccf 100644 --- a/compiler/src/test/golden/TestService.java.txt +++ b/compiler/src/test/golden/TestService.java.txt @@ -21,7 +21,7 @@ import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.32.1-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.32.1)", comments = "Source: grpc/testing/compiler/test.proto") public final class TestServiceGrpc { diff --git a/compiler/src/testLite/golden/TestDeprecatedService.java.txt b/compiler/src/testLite/golden/TestDeprecatedService.java.txt index 65bb115decf..059ec619d79 100644 --- a/compiler/src/testLite/golden/TestDeprecatedService.java.txt +++ b/compiler/src/testLite/golden/TestDeprecatedService.java.txt @@ -21,7 +21,7 @@ import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.32.1-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.32.1)", comments = "Source: grpc/testing/compiler/test.proto") @java.lang.Deprecated public final class TestDeprecatedServiceGrpc { diff --git a/compiler/src/testLite/golden/TestService.java.txt b/compiler/src/testLite/golden/TestService.java.txt index 7d1d77bbc90..e6f9d8f8824 100644 --- a/compiler/src/testLite/golden/TestService.java.txt +++ b/compiler/src/testLite/golden/TestService.java.txt @@ -21,7 +21,7 @@ import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall; * */ @javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.32.1-SNAPSHOT)", + value = "by gRPC proto compiler (version 1.32.1)", comments = "Source: grpc/testing/compiler/test.proto") public final class TestServiceGrpc { diff --git a/core/src/main/java/io/grpc/internal/GrpcUtil.java b/core/src/main/java/io/grpc/internal/GrpcUtil.java index 9310db2becd..ee21940aff4 100644 --- a/core/src/main/java/io/grpc/internal/GrpcUtil.java +++ b/core/src/main/java/io/grpc/internal/GrpcUtil.java @@ -197,7 +197,7 @@ public byte[] parseAsciiString(byte[] serialized) { public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults(); - private static final String IMPLEMENTATION_VERSION = "1.32.1-SNAPSHOT"; // CURRENT_GRPC_VERSION + private static final String IMPLEMENTATION_VERSION = "1.32.1"; // CURRENT_GRPC_VERSION /** * The default timeout in nanos for a keepalive ping request. diff --git a/examples/android/clientcache/app/build.gradle b/examples/android/clientcache/app/build.gradle index 237bef81176..38c83681f34 100644 --- a/examples/android/clientcache/app/build.gradle +++ b/examples/android/clientcache/app/build.gradle @@ -30,7 +30,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.12.0' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.1' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -50,12 +50,12 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.0.2' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.32.1' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.32.1' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.32.1' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' testImplementation 'junit:junit:4.12' testImplementation 'com.google.truth:truth:1.0.1' - testImplementation 'io.grpc:grpc-testing:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION + testImplementation 'io.grpc:grpc-testing:1.32.1' // CURRENT_GRPC_VERSION } diff --git a/examples/android/helloworld/app/build.gradle b/examples/android/helloworld/app/build.gradle index fd574908510..ff6eb2659c8 100644 --- a/examples/android/helloworld/app/build.gradle +++ b/examples/android/helloworld/app/build.gradle @@ -28,7 +28,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.12.0' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.1' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -48,8 +48,8 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.0.2' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.32.1' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.32.1' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.32.1' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/android/routeguide/app/build.gradle b/examples/android/routeguide/app/build.gradle index d5476a0e844..b7a005e0599 100644 --- a/examples/android/routeguide/app/build.gradle +++ b/examples/android/routeguide/app/build.gradle @@ -28,7 +28,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.12.0' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.1' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -48,8 +48,8 @@ dependencies { implementation 'com.android.support:appcompat-v7:27.0.2' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.32.1' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.32.1' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.32.1' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/android/strictmode/app/build.gradle b/examples/android/strictmode/app/build.gradle index e93ebfd2463..0cfedbae970 100644 --- a/examples/android/strictmode/app/build.gradle +++ b/examples/android/strictmode/app/build.gradle @@ -29,7 +29,7 @@ android { protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.12.0' } plugins { - grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION + grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.32.1' // CURRENT_GRPC_VERSION } } generateProtoTasks { @@ -49,8 +49,8 @@ dependencies { implementation 'com.android.support:appcompat-v7:28.0.0' // You need to build grpc-java to obtain these libraries below. - implementation 'io.grpc:grpc-okhttp:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-protobuf-lite:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION - implementation 'io.grpc:grpc-stub:1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-okhttp:1.32.1' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-protobuf-lite:1.32.1' // CURRENT_GRPC_VERSION + implementation 'io.grpc:grpc-stub:1.32.1' // CURRENT_GRPC_VERSION implementation 'org.apache.tomcat:annotations-api:6.0.53' } diff --git a/examples/build.gradle b/examples/build.gradle index f47cc996115..a998073f0d6 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -22,7 +22,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.1' // CURRENT_GRPC_VERSION def protobufVersion = '3.12.0' def protocVersion = protobufVersion diff --git a/examples/example-alts/build.gradle b/examples/example-alts/build.gradle index d513f2bc627..202fb33a051 100644 --- a/examples/example-alts/build.gradle +++ b/examples/example-alts/build.gradle @@ -23,7 +23,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.1' // CURRENT_GRPC_VERSION def protocVersion = '3.12.0' dependencies { diff --git a/examples/example-gauth/build.gradle b/examples/example-gauth/build.gradle index e389d3c035e..71002c31f8a 100644 --- a/examples/example-gauth/build.gradle +++ b/examples/example-gauth/build.gradle @@ -23,7 +23,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.1' // CURRENT_GRPC_VERSION def protobufVersion = '3.12.0' def protocVersion = protobufVersion diff --git a/examples/example-gauth/pom.xml b/examples/example-gauth/pom.xml index 3017c607243..85716fa9352 100644 --- a/examples/example-gauth/pom.xml +++ b/examples/example-gauth/pom.xml @@ -6,13 +6,13 @@ jar - 1.32.1-SNAPSHOT + 1.32.1 example-gauth https://github.com/grpc/grpc-java UTF-8 - 1.32.1-SNAPSHOT + 1.32.1 3.12.0 1.7 diff --git a/examples/example-hostname/build.gradle b/examples/example-hostname/build.gradle index e09030367c5..ff8c9b58fb3 100644 --- a/examples/example-hostname/build.gradle +++ b/examples/example-hostname/build.gradle @@ -21,7 +21,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.1' // CURRENT_GRPC_VERSION def protobufVersion = '3.12.0' dependencies { diff --git a/examples/example-hostname/pom.xml b/examples/example-hostname/pom.xml index 1a8652ec014..b3175d7fd9c 100644 --- a/examples/example-hostname/pom.xml +++ b/examples/example-hostname/pom.xml @@ -6,13 +6,13 @@ jar - 1.32.1-SNAPSHOT + 1.32.1 example-hostname https://github.com/grpc/grpc-java UTF-8 - 1.32.1-SNAPSHOT + 1.32.1 3.12.0 1.7 diff --git a/examples/example-jwt-auth/build.gradle b/examples/example-jwt-auth/build.gradle index 0c569f7f5a8..c8974938e8d 100644 --- a/examples/example-jwt-auth/build.gradle +++ b/examples/example-jwt-auth/build.gradle @@ -22,7 +22,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.1' // CURRENT_GRPC_VERSION def protobufVersion = '3.12.0' def protocVersion = protobufVersion diff --git a/examples/example-jwt-auth/pom.xml b/examples/example-jwt-auth/pom.xml index 5b578fca4a6..7de7e8c3d83 100644 --- a/examples/example-jwt-auth/pom.xml +++ b/examples/example-jwt-auth/pom.xml @@ -7,13 +7,13 @@ jar - 1.32.1-SNAPSHOT + 1.32.1 example-jwt-auth https://github.com/grpc/grpc-java UTF-8 - 1.32.1-SNAPSHOT + 1.32.1 3.12.0 3.12.0 diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle index 994154bd6ab..3966eb1021f 100644 --- a/examples/example-tls/build.gradle +++ b/examples/example-tls/build.gradle @@ -23,7 +23,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.1' // CURRENT_GRPC_VERSION def nettyTcNativeVersion = '2.0.31.Final' def protocVersion = '3.12.0' diff --git a/examples/example-tls/pom.xml b/examples/example-tls/pom.xml index 29bf6a37f66..129fa1c10d6 100644 --- a/examples/example-tls/pom.xml +++ b/examples/example-tls/pom.xml @@ -6,13 +6,13 @@ jar - 1.32.1-SNAPSHOT + 1.32.1 example-tls https://github.com/grpc/grpc-java UTF-8 - 1.32.1-SNAPSHOT + 1.32.1 3.12.0 2.0.31.Final diff --git a/examples/example-xds/build.gradle b/examples/example-xds/build.gradle index fd9c43d0c78..894e4b39156 100644 --- a/examples/example-xds/build.gradle +++ b/examples/example-xds/build.gradle @@ -18,7 +18,7 @@ targetCompatibility = 1.7 // Feel free to delete the comment at the next line. It is just for safely // updating the version in our release process. -def grpcVersion = '1.32.1-SNAPSHOT' // CURRENT_GRPC_VERSION +def grpcVersion = '1.32.1' // CURRENT_GRPC_VERSION dependencies { // This example's client is the same as the helloworld client. We depend on the helloworld diff --git a/examples/pom.xml b/examples/pom.xml index 3c7e09800c8..91643236356 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -6,13 +6,13 @@ jar - 1.32.1-SNAPSHOT + 1.32.1 examples https://github.com/grpc/grpc-java UTF-8 - 1.32.1-SNAPSHOT + 1.32.1 3.12.0 3.12.0