diff --git a/MODULE.bazel b/MODULE.bazel
index 88f3a524060..e62ec70dda0 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -2,7 +2,7 @@ module(
name = "grpc-java",
compatibility_level = 0,
repo_name = "io_grpc_grpc_java",
- version = "1.72.0-SNAPSHOT", # CURRENT_GRPC_VERSION
+ version = "1.72.1-SNAPSHOT", # CURRENT_GRPC_VERSION
)
# GRPC_DEPS_START
diff --git a/README.md b/README.md
index 756519d0ad0..d1b4d516f74 100644
--- a/README.md
+++ b/README.md
@@ -44,8 +44,8 @@ For a guided tour, take a look at the [quick start
guide](https://grpc.io/docs/languages/java/quickstart) or the more explanatory [gRPC
basics](https://grpc.io/docs/languages/java/basics).
-The [examples](https://github.com/grpc/grpc-java/tree/v1.71.0/examples) and the
-[Android example](https://github.com/grpc/grpc-java/tree/v1.71.0/examples/android)
+The [examples](https://github.com/grpc/grpc-java/tree/v1.72.0/examples) and the
+[Android example](https://github.com/grpc/grpc-java/tree/v1.72.0/examples/android)
are standalone projects that showcase the usage of gRPC.
Download
@@ -56,18 +56,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`:
io.grpc
grpc-netty-shaded
- 1.71.0
+ 1.72.0
runtime
io.grpc
grpc-protobuf
- 1.71.0
+ 1.72.0
io.grpc
grpc-stub
- 1.71.0
+ 1.72.0
org.apache.tomcat
@@ -79,18 +79,18 @@ Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`:
Or for Gradle with non-Android, add to your dependencies:
```gradle
-runtimeOnly 'io.grpc:grpc-netty-shaded:1.71.0'
-implementation 'io.grpc:grpc-protobuf:1.71.0'
-implementation 'io.grpc:grpc-stub:1.71.0'
+runtimeOnly 'io.grpc:grpc-netty-shaded:1.72.0'
+implementation 'io.grpc:grpc-protobuf:1.72.0'
+implementation 'io.grpc:grpc-stub:1.72.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.71.0'
-implementation 'io.grpc:grpc-protobuf-lite:1.71.0'
-implementation 'io.grpc:grpc-stub:1.71.0'
+implementation 'io.grpc:grpc-okhttp:1.72.0'
+implementation 'io.grpc:grpc-protobuf-lite:1.72.0'
+implementation 'io.grpc:grpc-stub:1.72.0'
compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+
```
@@ -99,7 +99,7 @@ For [Bazel](https://bazel.build), you can either
(with the GAVs from above), or use `@io_grpc_grpc_java//api` et al (see below).
[the JARs]:
-https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.71.0
+https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.72.0
Development snapshots are available in [Sonatypes's snapshot
repository](https://oss.sonatype.org/content/repositories/snapshots/).
@@ -131,7 +131,7 @@ For protobuf-based codegen integrated with the Maven build system, you can use
com.google.protobuf:protoc:3.25.5:exe:${os.detected.classifier}
grpc-java
- io.grpc:protoc-gen-grpc-java:1.71.0:exe:${os.detected.classifier}
+ io.grpc:protoc-gen-grpc-java:1.72.0:exe:${os.detected.classifier}
@@ -161,7 +161,7 @@ protobuf {
}
plugins {
grpc {
- artifact = 'io.grpc:protoc-gen-grpc-java:1.71.0'
+ artifact = 'io.grpc:protoc-gen-grpc-java:1.72.0'
}
}
generateProtoTasks {
@@ -194,7 +194,7 @@ protobuf {
}
plugins {
grpc {
- artifact = 'io.grpc:protoc-gen-grpc-java:1.71.0'
+ artifact = 'io.grpc:protoc-gen-grpc-java:1.72.0'
}
}
generateProtoTasks {
diff --git a/build.gradle b/build.gradle
index 93ce60054bc..e63e03d22d2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -21,7 +21,7 @@ subprojects {
apply plugin: "net.ltgt.errorprone"
group = "io.grpc"
- version = "1.72.0-SNAPSHOT" // CURRENT_GRPC_VERSION
+ version = "1.72.1-SNAPSHOT" // CURRENT_GRPC_VERSION
repositories {
maven { // The google mirror is less flaky than mavenCentral()
diff --git a/buildscripts/grpc-java-artifacts/Dockerfile b/buildscripts/grpc-java-artifacts/Dockerfile
index 736babe9d8e..bf71a710d74 100644
--- a/buildscripts/grpc-java-artifacts/Dockerfile
+++ b/buildscripts/grpc-java-artifacts/Dockerfile
@@ -28,6 +28,6 @@ RUN mkdir -p "$ANDROID_HOME/cmdline-tools" && \
yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --licenses
# Install Maven
-RUN curl -Ls https://dlcdn.apache.org/maven/maven-3/3.8.8/binaries/apache-maven-3.8.8-bin.tar.gz | \
+RUN curl -Ls https://archive.apache.org/dist/maven/maven-3/3.8.8/binaries/apache-maven-3.8.8-bin.tar.gz | \
tar xz -C /var/local
ENV PATH /var/local/apache-maven-3.8.8/bin:$PATH
diff --git a/buildscripts/kokoro/psm-fallback.cfg b/buildscripts/kokoro/psm-light.cfg
similarity index 94%
rename from buildscripts/kokoro/psm-fallback.cfg
rename to buildscripts/kokoro/psm-light.cfg
index 7335d1d9fd9..decd179efa3 100644
--- a/buildscripts/kokoro/psm-fallback.cfg
+++ b/buildscripts/kokoro/psm-light.cfg
@@ -13,5 +13,5 @@ action {
}
env_vars {
key: "PSM_TEST_SUITE"
- value: "fallback"
+ value: "light"
}
diff --git a/buildscripts/kokoro/psm-spiffe.cfg b/buildscripts/kokoro/psm-spiffe.cfg
new file mode 100644
index 00000000000..b04d715fca1
--- /dev/null
+++ b/buildscripts/kokoro/psm-spiffe.cfg
@@ -0,0 +1,17 @@
+# Config file for internal CI
+
+# Location of the continuous shell script in repository.
+build_file: "grpc-java/buildscripts/kokoro/psm-interop-test-java.sh"
+timeout_mins: 240
+
+action {
+ define_artifacts {
+ regex: "artifacts/**/*sponge_log.xml"
+ regex: "artifacts/**/*.log"
+ strip_prefix: "artifacts"
+ }
+}
+env_vars {
+ key: "PSM_TEST_SUITE"
+ value: "spiffe"
+}
diff --git a/compiler/src/test/golden/TestDeprecatedService.java.txt b/compiler/src/test/golden/TestDeprecatedService.java.txt
index 82fe6a81d18..44d4dc93175 100644
--- a/compiler/src/test/golden/TestDeprecatedService.java.txt
+++ b/compiler/src/test/golden/TestDeprecatedService.java.txt
@@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
*
*/
@javax.annotation.Generated(
- value = "by gRPC proto compiler (version 1.72.0-SNAPSHOT)",
+ value = "by gRPC proto compiler (version 1.72.1-SNAPSHOT)",
comments = "Source: grpc/testing/compiler/test.proto")
@io.grpc.stub.annotations.GrpcGenerated
@java.lang.Deprecated
diff --git a/compiler/src/test/golden/TestService.java.txt b/compiler/src/test/golden/TestService.java.txt
index 912bd50da12..085ba3abe46 100644
--- a/compiler/src/test/golden/TestService.java.txt
+++ b/compiler/src/test/golden/TestService.java.txt
@@ -8,7 +8,7 @@ import static io.grpc.MethodDescriptor.generateFullMethodName;
*
*/
@javax.annotation.Generated(
- value = "by gRPC proto compiler (version 1.72.0-SNAPSHOT)",
+ value = "by gRPC proto compiler (version 1.72.1-SNAPSHOT)",
comments = "Source: grpc/testing/compiler/test.proto")
@io.grpc.stub.annotations.GrpcGenerated
public final class TestServiceGrpc {
diff --git a/core/src/main/java/io/grpc/internal/DelayedClientTransport.java b/core/src/main/java/io/grpc/internal/DelayedClientTransport.java
index 8ff755af3eb..eccd8fadc8c 100644
--- a/core/src/main/java/io/grpc/internal/DelayedClientTransport.java
+++ b/core/src/main/java/io/grpc/internal/DelayedClientTransport.java
@@ -325,7 +325,11 @@ final void reprocess(@Nullable SubchannelPicker picker) {
if (!hasPendingStreams()) {
return;
}
- pendingStreams.removeAll(toRemove);
+ // Avoid pendingStreams.removeAll() as it can degrade to calling toRemove.contains() for each
+ // element in pendingStreams.
+ for (PendingStream stream : toRemove) {
+ pendingStreams.remove(stream);
+ }
// Because delayed transport is long-lived, we take this opportunity to down-size the
// hashmap.
if (pendingStreams.isEmpty()) {
diff --git a/core/src/main/java/io/grpc/internal/GrpcUtil.java b/core/src/main/java/io/grpc/internal/GrpcUtil.java
index 937854ac3ff..fef4b638941 100644
--- a/core/src/main/java/io/grpc/internal/GrpcUtil.java
+++ b/core/src/main/java/io/grpc/internal/GrpcUtil.java
@@ -219,7 +219,7 @@ public byte[] parseAsciiString(byte[] serialized) {
public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults();
- public static final String IMPLEMENTATION_VERSION = "1.72.0-SNAPSHOT"; // CURRENT_GRPC_VERSION
+ public static final String IMPLEMENTATION_VERSION = "1.72.1-SNAPSHOT"; // CURRENT_GRPC_VERSION
/**
* The default timeout in nanos for a keepalive ping request.
diff --git a/examples/MODULE.bazel b/examples/MODULE.bazel
index 4d72ae9c395..3612ec2640c 100644
--- a/examples/MODULE.bazel
+++ b/examples/MODULE.bazel
@@ -1,5 +1,5 @@
bazel_dep(name = "googleapis", repo_name = "com_google_googleapis", version = "0.0.0-20240326-1c8d509c5")
-bazel_dep(name = "grpc-java", repo_name = "io_grpc_grpc_java", version = "1.72.0-SNAPSHOT") # CURRENT_GRPC_VERSION
+bazel_dep(name = "grpc-java", repo_name = "io_grpc_grpc_java", version = "1.72.1-SNAPSHOT") # CURRENT_GRPC_VERSION
bazel_dep(name = "grpc-proto", repo_name = "io_grpc_grpc_proto", version = "0.0.0-20240627-ec30f58")
bazel_dep(name = "protobuf", repo_name = "com_google_protobuf", version = "23.1")
bazel_dep(name = "rules_jvm_external", version = "6.0")
diff --git a/examples/android/clientcache/app/build.gradle b/examples/android/clientcache/app/build.gradle
index 1f2a17ae6bb..f68e4b52061 100644
--- a/examples/android/clientcache/app/build.gradle
+++ b/examples/android/clientcache/app/build.gradle
@@ -34,7 +34,7 @@ android {
protobuf {
protoc { artifact = 'com.google.protobuf:protoc:3.25.1' }
plugins {
- grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+ grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
}
}
generateProtoTasks {
@@ -54,12 +54,12 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.0.0'
// You need to build grpc-java to obtain these libraries below.
- implementation 'io.grpc:grpc-okhttp:1.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
- implementation 'io.grpc:grpc-protobuf-lite:1.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
- implementation 'io.grpc:grpc-stub:1.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+ implementation 'io.grpc:grpc-okhttp:1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
+ implementation 'io.grpc:grpc-protobuf-lite:1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
+ implementation 'io.grpc:grpc-stub:1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
implementation 'org.apache.tomcat:annotations-api:6.0.53'
testImplementation 'junit:junit:4.13.2'
testImplementation 'com.google.truth:truth:1.1.5'
- testImplementation 'io.grpc:grpc-testing:1.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+ testImplementation 'io.grpc:grpc-testing:1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
}
diff --git a/examples/android/helloworld/app/build.gradle b/examples/android/helloworld/app/build.gradle
index 09b994a4954..5ff4fa926b9 100644
--- a/examples/android/helloworld/app/build.gradle
+++ b/examples/android/helloworld/app/build.gradle
@@ -32,7 +32,7 @@ android {
protobuf {
protoc { artifact = 'com.google.protobuf:protoc:3.25.1' }
plugins {
- grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+ grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
}
}
generateProtoTasks {
@@ -52,8 +52,8 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.0.0'
// You need to build grpc-java to obtain these libraries below.
- implementation 'io.grpc:grpc-okhttp:1.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
- implementation 'io.grpc:grpc-protobuf-lite:1.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
- implementation 'io.grpc:grpc-stub:1.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+ implementation 'io.grpc:grpc-okhttp:1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
+ implementation 'io.grpc:grpc-protobuf-lite:1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
+ implementation 'io.grpc:grpc-stub:1.72.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 bdad129845b..1ae9ca28e08 100644
--- a/examples/android/routeguide/app/build.gradle
+++ b/examples/android/routeguide/app/build.gradle
@@ -32,7 +32,7 @@ android {
protobuf {
protoc { artifact = 'com.google.protobuf:protoc:3.25.1' }
plugins {
- grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+ grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
}
}
generateProtoTasks {
@@ -52,8 +52,8 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.0.0'
// You need to build grpc-java to obtain these libraries below.
- implementation 'io.grpc:grpc-okhttp:1.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
- implementation 'io.grpc:grpc-protobuf-lite:1.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
- implementation 'io.grpc:grpc-stub:1.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+ implementation 'io.grpc:grpc-okhttp:1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
+ implementation 'io.grpc:grpc-protobuf-lite:1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
+ implementation 'io.grpc:grpc-stub:1.72.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 f38110a741b..0e4a057675e 100644
--- a/examples/android/strictmode/app/build.gradle
+++ b/examples/android/strictmode/app/build.gradle
@@ -33,7 +33,7 @@ android {
protobuf {
protoc { artifact = 'com.google.protobuf:protoc:3.25.1' }
plugins {
- grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+ grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
}
}
generateProtoTasks {
@@ -53,8 +53,8 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.0.0'
// You need to build grpc-java to obtain these libraries below.
- implementation 'io.grpc:grpc-okhttp:1.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
- implementation 'io.grpc:grpc-protobuf-lite:1.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
- implementation 'io.grpc:grpc-stub:1.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+ implementation 'io.grpc:grpc-okhttp:1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
+ implementation 'io.grpc:grpc-protobuf-lite:1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
+ implementation 'io.grpc:grpc-stub:1.72.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 e807d09f407..8dc8cb39716 100644
--- a/examples/build.gradle
+++ b/examples/build.gradle
@@ -21,7 +21,7 @@ java {
// 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.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def grpcVersion = '1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
def protobufVersion = '3.25.5'
def protocVersion = protobufVersion
diff --git a/examples/example-alts/build.gradle b/examples/example-alts/build.gradle
index e7142f3fb5a..369f7b212da 100644
--- a/examples/example-alts/build.gradle
+++ b/examples/example-alts/build.gradle
@@ -21,7 +21,7 @@ java {
// 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.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def grpcVersion = '1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
def protocVersion = '3.25.5'
dependencies {
diff --git a/examples/example-debug/build.gradle b/examples/example-debug/build.gradle
index 1d07a7cb8ec..aa13f55d7b7 100644
--- a/examples/example-debug/build.gradle
+++ b/examples/example-debug/build.gradle
@@ -23,7 +23,7 @@ java {
// 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.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def grpcVersion = '1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
def protobufVersion = '3.25.5'
dependencies {
diff --git a/examples/example-debug/pom.xml b/examples/example-debug/pom.xml
index 00fdecdb6c4..6807df43771 100644
--- a/examples/example-debug/pom.xml
+++ b/examples/example-debug/pom.xml
@@ -6,13 +6,13 @@
jar
- 1.72.0-SNAPSHOT
+ 1.72.1-SNAPSHOT
example-debug
https://github.com/grpc/grpc-java
UTF-8
- 1.72.0-SNAPSHOT
+ 1.72.1-SNAPSHOT
3.25.5
1.8
diff --git a/examples/example-dualstack/build.gradle b/examples/example-dualstack/build.gradle
index b73902095a1..b8e8ec5acc4 100644
--- a/examples/example-dualstack/build.gradle
+++ b/examples/example-dualstack/build.gradle
@@ -23,7 +23,7 @@ java {
// 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.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def grpcVersion = '1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
def protobufVersion = '3.25.5'
dependencies {
diff --git a/examples/example-dualstack/pom.xml b/examples/example-dualstack/pom.xml
index 28539851934..8ed41f27256 100644
--- a/examples/example-dualstack/pom.xml
+++ b/examples/example-dualstack/pom.xml
@@ -6,13 +6,13 @@
jar
- 1.72.0-SNAPSHOT
+ 1.72.1-SNAPSHOT
example-dualstack
https://github.com/grpc/grpc-java
UTF-8
- 1.72.0-SNAPSHOT
+ 1.72.1-SNAPSHOT
3.25.5
1.8
diff --git a/examples/example-gauth/build.gradle b/examples/example-gauth/build.gradle
index 0ef0dcaefe2..f4dcf3fb8ce 100644
--- a/examples/example-gauth/build.gradle
+++ b/examples/example-gauth/build.gradle
@@ -21,7 +21,7 @@ java {
// 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.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def grpcVersion = '1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
def protobufVersion = '3.25.5'
def protocVersion = protobufVersion
diff --git a/examples/example-gauth/pom.xml b/examples/example-gauth/pom.xml
index d2a32578550..527916af157 100644
--- a/examples/example-gauth/pom.xml
+++ b/examples/example-gauth/pom.xml
@@ -6,13 +6,13 @@
jar
- 1.72.0-SNAPSHOT
+ 1.72.1-SNAPSHOT
example-gauth
https://github.com/grpc/grpc-java
UTF-8
- 1.72.0-SNAPSHOT
+ 1.72.1-SNAPSHOT
3.25.5
1.8
diff --git a/examples/example-gcp-csm-observability/build.gradle b/examples/example-gcp-csm-observability/build.gradle
index e16e32e3bc1..c0062d69efd 100644
--- a/examples/example-gcp-csm-observability/build.gradle
+++ b/examples/example-gcp-csm-observability/build.gradle
@@ -22,7 +22,7 @@ java {
// 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.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def grpcVersion = '1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
def protocVersion = '3.25.5'
def openTelemetryVersion = '1.40.0'
def openTelemetryPrometheusVersion = '1.40.0-alpha'
diff --git a/examples/example-gcp-observability/build.gradle b/examples/example-gcp-observability/build.gradle
index 1cad10bbb87..85703711527 100644
--- a/examples/example-gcp-observability/build.gradle
+++ b/examples/example-gcp-observability/build.gradle
@@ -22,7 +22,7 @@ java {
// 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.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def grpcVersion = '1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
def protocVersion = '3.25.5'
dependencies {
diff --git a/examples/example-hostname/build.gradle b/examples/example-hostname/build.gradle
index 86aa42f8ed0..3676c3ea850 100644
--- a/examples/example-hostname/build.gradle
+++ b/examples/example-hostname/build.gradle
@@ -21,7 +21,7 @@ java {
// 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.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def grpcVersion = '1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
def protobufVersion = '3.25.5'
dependencies {
diff --git a/examples/example-hostname/pom.xml b/examples/example-hostname/pom.xml
index 6993c40a1ac..46644f3ee7c 100644
--- a/examples/example-hostname/pom.xml
+++ b/examples/example-hostname/pom.xml
@@ -6,13 +6,13 @@
jar
- 1.72.0-SNAPSHOT
+ 1.72.1-SNAPSHOT
example-hostname
https://github.com/grpc/grpc-java
UTF-8
- 1.72.0-SNAPSHOT
+ 1.72.1-SNAPSHOT
3.25.5
1.8
diff --git a/examples/example-jwt-auth/build.gradle b/examples/example-jwt-auth/build.gradle
index ca12c3f7872..2382365fd82 100644
--- a/examples/example-jwt-auth/build.gradle
+++ b/examples/example-jwt-auth/build.gradle
@@ -21,7 +21,7 @@ java {
// 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.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def grpcVersion = '1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
def protobufVersion = '3.25.5'
def protocVersion = protobufVersion
diff --git a/examples/example-jwt-auth/pom.xml b/examples/example-jwt-auth/pom.xml
index 7e9a915bfbd..a92e1c96dd2 100644
--- a/examples/example-jwt-auth/pom.xml
+++ b/examples/example-jwt-auth/pom.xml
@@ -7,13 +7,13 @@
jar
- 1.72.0-SNAPSHOT
+ 1.72.1-SNAPSHOT
example-jwt-auth
https://github.com/grpc/grpc-java
UTF-8
- 1.72.0-SNAPSHOT
+ 1.72.1-SNAPSHOT
3.25.5
3.25.5
diff --git a/examples/example-oauth/build.gradle b/examples/example-oauth/build.gradle
index 6d06f097ccb..d2e1654f0f8 100644
--- a/examples/example-oauth/build.gradle
+++ b/examples/example-oauth/build.gradle
@@ -21,7 +21,7 @@ java {
// 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.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def grpcVersion = '1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
def protobufVersion = '3.25.5'
def protocVersion = protobufVersion
diff --git a/examples/example-oauth/pom.xml b/examples/example-oauth/pom.xml
index a9fea928a34..17514f95116 100644
--- a/examples/example-oauth/pom.xml
+++ b/examples/example-oauth/pom.xml
@@ -7,13 +7,13 @@
jar
- 1.72.0-SNAPSHOT
+ 1.72.1-SNAPSHOT
example-oauth
https://github.com/grpc/grpc-java
UTF-8
- 1.72.0-SNAPSHOT
+ 1.72.1-SNAPSHOT
3.25.5
3.25.5
diff --git a/examples/example-opentelemetry/build.gradle b/examples/example-opentelemetry/build.gradle
index f575d24d19b..b4b78461812 100644
--- a/examples/example-opentelemetry/build.gradle
+++ b/examples/example-opentelemetry/build.gradle
@@ -21,7 +21,7 @@ java {
// 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.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def grpcVersion = '1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
def protocVersion = '3.25.5'
def openTelemetryVersion = '1.40.0'
def openTelemetryPrometheusVersion = '1.40.0-alpha'
diff --git a/examples/example-orca/build.gradle b/examples/example-orca/build.gradle
index 45235fa1e08..3944e451403 100644
--- a/examples/example-orca/build.gradle
+++ b/examples/example-orca/build.gradle
@@ -16,7 +16,7 @@ java {
targetCompatibility = JavaVersion.VERSION_1_8
}
-def grpcVersion = '1.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def grpcVersion = '1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
def protocVersion = '3.25.5'
dependencies {
diff --git a/examples/example-reflection/build.gradle b/examples/example-reflection/build.gradle
index ad68e891436..9dcbbd6c354 100644
--- a/examples/example-reflection/build.gradle
+++ b/examples/example-reflection/build.gradle
@@ -16,7 +16,7 @@ java {
targetCompatibility = JavaVersion.VERSION_1_8
}
-def grpcVersion = '1.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def grpcVersion = '1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
def protocVersion = '3.25.5'
dependencies {
diff --git a/examples/example-servlet/build.gradle b/examples/example-servlet/build.gradle
index 2176df5afc5..da4a8744da1 100644
--- a/examples/example-servlet/build.gradle
+++ b/examples/example-servlet/build.gradle
@@ -15,7 +15,7 @@ java {
targetCompatibility = JavaVersion.VERSION_1_8
}
-def grpcVersion = '1.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def grpcVersion = '1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
def protocVersion = '3.25.5'
dependencies {
diff --git a/examples/example-tls/build.gradle b/examples/example-tls/build.gradle
index e741bfe1c3f..49f0383b49a 100644
--- a/examples/example-tls/build.gradle
+++ b/examples/example-tls/build.gradle
@@ -21,7 +21,7 @@ java {
// 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.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def grpcVersion = '1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
def protocVersion = '3.25.5'
dependencies {
diff --git a/examples/example-tls/pom.xml b/examples/example-tls/pom.xml
index de9298cf0e1..93095aa01d9 100644
--- a/examples/example-tls/pom.xml
+++ b/examples/example-tls/pom.xml
@@ -6,13 +6,13 @@
jar
- 1.72.0-SNAPSHOT
+ 1.72.1-SNAPSHOT
example-tls
https://github.com/grpc/grpc-java
UTF-8
- 1.72.0-SNAPSHOT
+ 1.72.1-SNAPSHOT
3.25.5
1.8
diff --git a/examples/example-xds/build.gradle b/examples/example-xds/build.gradle
index c0159dce258..16a85758c9d 100644
--- a/examples/example-xds/build.gradle
+++ b/examples/example-xds/build.gradle
@@ -21,7 +21,7 @@ java {
// 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.72.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def grpcVersion = '1.72.1-SNAPSHOT' // CURRENT_GRPC_VERSION
def protocVersion = '3.25.5'
dependencies {
diff --git a/examples/pom.xml b/examples/pom.xml
index edc9c4cda14..ac60626c578 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -6,13 +6,13 @@
jar
- 1.72.0-SNAPSHOT
+ 1.72.1-SNAPSHOT
examples
https://github.com/grpc/grpc-java
UTF-8
- 1.72.0-SNAPSHOT
+ 1.72.1-SNAPSHOT
3.25.5
3.25.5
diff --git a/stub/src/main/java/io/grpc/stub/ServerCalls.java b/stub/src/main/java/io/grpc/stub/ServerCalls.java
index 7990a5b34c0..9f0063713cc 100644
--- a/stub/src/main/java/io/grpc/stub/ServerCalls.java
+++ b/stub/src/main/java/io/grpc/stub/ServerCalls.java
@@ -382,9 +382,10 @@ public void onNext(RespT response) {
@Override
public void onError(Throwable t) {
- Metadata metadata = Status.trailersFromThrowable(t);
- if (metadata == null) {
- metadata = new Metadata();
+ Metadata metadata = new Metadata();
+ Metadata trailers = Status.trailersFromThrowable(t);
+ if (trailers != null) {
+ metadata.merge(trailers);
}
call.close(Status.fromThrowable(t), metadata);
aborted = true;
diff --git a/stub/src/main/java/io/grpc/stub/StreamObservers.java b/stub/src/main/java/io/grpc/stub/StreamObservers.java
index 2cc53ea0aa2..a421d3eca2f 100644
--- a/stub/src/main/java/io/grpc/stub/StreamObservers.java
+++ b/stub/src/main/java/io/grpc/stub/StreamObservers.java
@@ -23,12 +23,21 @@
/**
* Utility functions for working with {@link StreamObserver} and it's common subclasses like
* {@link CallStreamObserver}.
- *
- * @deprecated Of questionable utility and generally not used.
*/
-@Deprecated
-@ExperimentalApi("https://github.com/grpc/grpc-java/issues/4694")
public final class StreamObservers {
+ // Prevent instantiation
+ private StreamObservers() { }
+
+ /**
+ * Utility method to call {@link StreamObserver#onNext(Object)} and
+ * {@link StreamObserver#onCompleted()} on the specified responseObserver.
+ */
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/10957")
+ public static void nextAndComplete(StreamObserver responseObserver, T response) {
+ responseObserver.onNext(response);
+ responseObserver.onCompleted();
+ }
+
/**
* Copy the values of an {@link Iterator} to the target {@link CallStreamObserver} while properly
* accounting for outbound flow-control. After calling this method, {@code target} should no
@@ -40,7 +49,10 @@ public final class StreamObservers {
*
* @param source of values expressed as an {@link Iterator}.
* @param target {@link CallStreamObserver} which accepts values from the source.
+ * @deprecated Of questionable utility and generally not used.
*/
+ @Deprecated
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4694")
public static void copyWithFlowControl(final Iterator source,
final CallStreamObserver target) {
Preconditions.checkNotNull(source, "source");
@@ -80,7 +92,10 @@ public void run() {
*
* @param source of values expressed as an {@link Iterable}.
* @param target {@link CallStreamObserver} which accepts values from the source.
+ * @deprecated Of questionable utility and generally not used.
*/
+ @Deprecated
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4694")
public static void copyWithFlowControl(final Iterable source,
CallStreamObserver target) {
Preconditions.checkNotNull(source, "source");
diff --git a/stub/src/test/java/io/grpc/stub/ServerCallsTest.java b/stub/src/test/java/io/grpc/stub/ServerCallsTest.java
index 1e51ac10110..6f458facc5e 100644
--- a/stub/src/test/java/io/grpc/stub/ServerCallsTest.java
+++ b/stub/src/test/java/io/grpc/stub/ServerCallsTest.java
@@ -555,6 +555,35 @@ public void invoke(Integer req, StreamObserver responseObserver) {
listener.onHalfClose();
}
+ @Test
+ public void clientSendsOne_serverOnErrorWithTrailers_serverStreaming() {
+ Metadata trailers = new Metadata();
+ Metadata.Key key = Metadata.Key.of("trailers-test-key1",
+ Metadata.ASCII_STRING_MARSHALLER);
+ trailers.put(key, "trailers-test-value1");
+
+ ServerCallRecorder serverCall = new ServerCallRecorder(SERVER_STREAMING_METHOD);
+ ServerCallHandler callHandler = ServerCalls.asyncServerStreamingCall(
+ new ServerCalls.ServerStreamingMethod() {
+ @Override
+ public void invoke(Integer req, StreamObserver responseObserver) {
+ responseObserver.onError(
+ Status.fromCode(Status.Code.INTERNAL)
+ .asRuntimeException(trailers)
+ );
+ }
+ });
+ ServerCall.Listener listener = callHandler.startCall(serverCall, new Metadata());
+ serverCall.isReady = true;
+ serverCall.isCancelled = false;
+ listener.onReady();
+ listener.onMessage(1);
+ listener.onHalfClose();
+ // verify trailers key is set
+ assertTrue(serverCall.trailers.containsKey(key));
+ assertTrue(serverCall.status.equals(Status.INTERNAL));
+ }
+
@Test
public void inprocessTransportManualFlow() throws Exception {
final Semaphore semaphore = new Semaphore(1);
@@ -652,6 +681,7 @@ private static class ServerCallRecorder extends ServerCall {
private boolean isCancelled;
private boolean isReady;
private int onReadyThreshold;
+ private Metadata trailers;
public ServerCallRecorder(MethodDescriptor methodDescriptor) {
this.methodDescriptor = methodDescriptor;
@@ -674,6 +704,7 @@ public void sendMessage(Integer message) {
@Override
public void close(Status status, Metadata trailers) {
this.status = status;
+ this.trailers = trailers;
}
@Override
diff --git a/stub/src/test/java/io/grpc/stub/StreamObserversTest.java b/stub/src/test/java/io/grpc/stub/StreamObserversTest.java
new file mode 100644
index 00000000000..86a1aba2e76
--- /dev/null
+++ b/stub/src/test/java/io/grpc/stub/StreamObserversTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2025 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.stub;
+
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+
+public class StreamObserversTest {
+
+ @Test
+ public void nextAndComplete() {
+ @SuppressWarnings("unchecked")
+ StreamObserver observer = Mockito.mock(StreamObserver.class);
+ InOrder inOrder = Mockito.inOrder(observer);
+ StreamObservers.nextAndComplete(observer, "TEST");
+ inOrder.verify(observer).onNext("TEST");
+ inOrder.verify(observer).onCompleted();
+ inOrder.verifyNoMoreInteractions();
+ }
+}
diff --git a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java
index c92f592ebc8..0fb7cf15909 100644
--- a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java
+++ b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java
@@ -33,6 +33,7 @@
import io.grpc.NameResolver;
import io.grpc.NameResolver.ResolutionResult;
import io.grpc.Status;
+import io.grpc.StatusOr;
import io.grpc.SynchronizationContext;
import io.grpc.SynchronizationContext.ScheduledHandle;
import io.grpc.internal.BackoffPolicy;
@@ -657,79 +658,84 @@ private class NameResolverListener extends NameResolver.Listener2 {
@Override
public void onResult(final ResolutionResult resolutionResult) {
- class NameResolved implements Runnable {
- @Override
- public void run() {
- if (shutdown) {
- return;
- }
- backoffPolicy = null; // reset backoff sequence if succeeded
- // Arbitrary priority notation for all DNS-resolved endpoints.
- String priorityName = priorityName(name, 0); // value doesn't matter
- List addresses = new ArrayList<>();
- for (EquivalentAddressGroup eag : resolutionResult.getAddresses()) {
- // No weight attribute is attached, all endpoint-level LB policy should be able
- // to handle such it.
- String localityName = localityName(LOGICAL_DNS_CLUSTER_LOCALITY);
- Attributes attr = eag.getAttributes().toBuilder()
- .set(XdsAttributes.ATTR_LOCALITY, LOGICAL_DNS_CLUSTER_LOCALITY)
- .set(XdsAttributes.ATTR_LOCALITY_NAME, localityName)
- .set(XdsAttributes.ATTR_ADDRESS_NAME, dnsHostName)
- .build();
- eag = new EquivalentAddressGroup(eag.getAddresses(), attr);
- eag = AddressFilter.setPathFilter(eag, Arrays.asList(priorityName, localityName));
- addresses.add(eag);
- }
- PriorityChildConfig priorityChildConfig = generateDnsBasedPriorityChildConfig(
- name, lrsServerInfo, maxConcurrentRequests, tlsContext, filterMetadata,
- lbRegistry, Collections.emptyList());
- status = Status.OK;
- resolved = true;
- result = new ClusterResolutionResult(addresses, priorityName, priorityChildConfig);
- handleEndpointResourceUpdate();
+ syncContext.execute(() -> onResult2(resolutionResult));
+ }
+
+ @Override
+ public Status onResult2(final ResolutionResult resolutionResult) {
+ if (shutdown) {
+ return Status.OK;
+ }
+ // Arbitrary priority notation for all DNS-resolved endpoints.
+ String priorityName = priorityName(name, 0); // value doesn't matter
+ List addresses = new ArrayList<>();
+ StatusOr> addressesOrError =
+ resolutionResult.getAddressesOrError();
+ if (addressesOrError.hasValue()) {
+ backoffPolicy = null; // reset backoff sequence if succeeded
+ for (EquivalentAddressGroup eag : resolutionResult.getAddresses()) {
+ // No weight attribute is attached, all endpoint-level LB policy should be able
+ // to handle such it.
+ String localityName = localityName(LOGICAL_DNS_CLUSTER_LOCALITY);
+ Attributes attr = eag.getAttributes().toBuilder()
+ .set(XdsAttributes.ATTR_LOCALITY, LOGICAL_DNS_CLUSTER_LOCALITY)
+ .set(XdsAttributes.ATTR_LOCALITY_NAME, localityName)
+ .set(XdsAttributes.ATTR_ADDRESS_NAME, dnsHostName)
+ .build();
+ eag = new EquivalentAddressGroup(eag.getAddresses(), attr);
+ eag = AddressFilter.setPathFilter(eag, Arrays.asList(priorityName, localityName));
+ addresses.add(eag);
}
+ PriorityChildConfig priorityChildConfig = generateDnsBasedPriorityChildConfig(
+ name, lrsServerInfo, maxConcurrentRequests, tlsContext, filterMetadata,
+ lbRegistry, Collections.emptyList());
+ status = Status.OK;
+ resolved = true;
+ result = new ClusterResolutionResult(addresses, priorityName, priorityChildConfig);
+ handleEndpointResourceUpdate();
+ return Status.OK;
+ } else {
+ handleErrorInSyncContext(addressesOrError.getStatus());
+ return addressesOrError.getStatus();
}
-
- syncContext.execute(new NameResolved());
}
@Override
public void onError(final Status error) {
- syncContext.execute(new Runnable() {
- @Override
- public void run() {
- if (shutdown) {
- return;
- }
- status = error;
- // NameResolver.Listener API cannot distinguish between address-not-found and
- // transient errors. If the error occurs in the first resolution, treat it as
- // address not found. Otherwise, either there is previously resolved addresses
- // previously encountered error, propagate the error to downstream/upstream and
- // let downstream/upstream handle it.
- if (!resolved) {
- resolved = true;
- handleEndpointResourceUpdate();
- } else {
- handleEndpointResolutionError();
- }
- if (scheduledRefresh != null && scheduledRefresh.isPending()) {
- return;
- }
- if (backoffPolicy == null) {
- backoffPolicy = backoffPolicyProvider.get();
- }
- long delayNanos = backoffPolicy.nextBackoffNanos();
- logger.log(XdsLogLevel.DEBUG,
+ syncContext.execute(() -> handleErrorInSyncContext(error));
+ }
+
+ private void handleErrorInSyncContext(final Status error) {
+ if (shutdown) {
+ return;
+ }
+ status = error;
+ // NameResolver.Listener API cannot distinguish between address-not-found and
+ // transient errors. If the error occurs in the first resolution, treat it as
+ // address not found. Otherwise, either there is previously resolved addresses
+ // previously encountered error, propagate the error to downstream/upstream and
+ // let downstream/upstream handle it.
+ if (!resolved) {
+ resolved = true;
+ handleEndpointResourceUpdate();
+ } else {
+ handleEndpointResolutionError();
+ }
+ if (scheduledRefresh != null && scheduledRefresh.isPending()) {
+ return;
+ }
+ if (backoffPolicy == null) {
+ backoffPolicy = backoffPolicyProvider.get();
+ }
+ long delayNanos = backoffPolicy.nextBackoffNanos();
+ logger.log(XdsLogLevel.DEBUG,
"Logical DNS resolver for cluster {0} encountered name resolution "
- + "error: {1}, scheduling DNS resolution backoff for {2} ns",
+ + "error: {1}, scheduling DNS resolution backoff for {2} ns",
name, error, delayNanos);
- scheduledRefresh =
+ scheduledRefresh =
syncContext.schedule(
- new DelayedNameResolverRefresh(), delayNanos, TimeUnit.NANOSECONDS,
- timeService);
- }
- });
+ new DelayedNameResolverRefresh(), delayNanos, TimeUnit.NANOSECONDS,
+ timeService);
}
}
}
diff --git a/xds/src/main/java/io/grpc/xds/FilterRegistry.java b/xds/src/main/java/io/grpc/xds/FilterRegistry.java
index 426c6d1b3f6..1fbccea8000 100644
--- a/xds/src/main/java/io/grpc/xds/FilterRegistry.java
+++ b/xds/src/main/java/io/grpc/xds/FilterRegistry.java
@@ -17,6 +17,7 @@
package io.grpc.xds;
import com.google.common.annotations.VisibleForTesting;
+import io.grpc.internal.GrpcUtil;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
@@ -32,12 +33,18 @@ final class FilterRegistry {
private FilterRegistry() {}
+ static boolean isEnabledGcpAuthnFilter =
+ GrpcUtil.getFlag("GRPC_EXPERIMENTAL_XDS_GCP_AUTHENTICATION_FILTER", false);
+
static synchronized FilterRegistry getDefaultRegistry() {
if (instance == null) {
instance = newRegistry().register(
new FaultFilter.Provider(),
new RouterFilter.Provider(),
new RbacFilter.Provider());
+ if (isEnabledGcpAuthnFilter) {
+ instance.register(new GcpAuthenticationFilter.Provider());
+ }
}
return instance;
}
diff --git a/xds/src/main/java/io/grpc/xds/GcpAuthenticationFilter.java b/xds/src/main/java/io/grpc/xds/GcpAuthenticationFilter.java
index add885c6416..dc133eaaf1a 100644
--- a/xds/src/main/java/io/grpc/xds/GcpAuthenticationFilter.java
+++ b/xds/src/main/java/io/grpc/xds/GcpAuthenticationFilter.java
@@ -16,8 +16,14 @@
package io.grpc.xds;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static io.grpc.xds.FilterRegistry.isEnabledGcpAuthnFilter;
+import static io.grpc.xds.XdsNameResolver.CLUSTER_SELECTION_KEY;
+import static io.grpc.xds.XdsNameResolver.XDS_CONFIG_CALL_OPTION_KEY;
+
import com.google.auth.oauth2.ComputeEngineCredentials;
import com.google.auth.oauth2.IdTokenCredentials;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.primitives.UnsignedLongs;
import com.google.protobuf.Any;
import com.google.protobuf.InvalidProtocolBufferException;
@@ -34,8 +40,11 @@
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import io.grpc.Status;
+import io.grpc.StatusOr;
import io.grpc.auth.MoreCallCredentials;
+import io.grpc.xds.GcpAuthenticationFilter.AudienceMetadataParser.AudienceWrapper;
import io.grpc.xds.MetadataRegistry.MetadataValueParser;
+import io.grpc.xds.XdsConfig.XdsClusterConfig;
import io.grpc.xds.client.XdsResourceType.ResourceInvalidException;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -51,8 +60,17 @@ final class GcpAuthenticationFilter implements Filter {
static final String TYPE_URL =
"type.googleapis.com/envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig";
+ private final LruCache callCredentialsCache;
+ final String filterInstanceName;
+
+ GcpAuthenticationFilter(String name, int cacheSize) {
+ filterInstanceName = checkNotNull(name, "name");
+ this.callCredentialsCache = new LruCache<>(cacheSize);
+ }
static final class Provider implements Filter.Provider {
+ private final int cacheSize = 10;
+
@Override
public String[] typeUrls() {
return new String[]{TYPE_URL};
@@ -65,7 +83,7 @@ public boolean isClientFilter() {
@Override
public GcpAuthenticationFilter newInstance(String name) {
- return new GcpAuthenticationFilter();
+ return new GcpAuthenticationFilter(name, cacheSize);
}
@Override
@@ -86,11 +104,14 @@ public ConfigOrError parseFilterConfig(Message rawProto
// Validate cache_config
if (gcpAuthnProto.hasCacheConfig()) {
TokenCacheConfig cacheConfig = gcpAuthnProto.getCacheConfig();
- cacheSize = cacheConfig.getCacheSize().getValue();
- if (cacheSize == 0) {
- return ConfigOrError.fromError(
- "cache_config.cache_size must be greater than zero");
+ if (cacheConfig.hasCacheSize()) {
+ cacheSize = cacheConfig.getCacheSize().getValue();
+ if (cacheSize == 0) {
+ return ConfigOrError.fromError(
+ "cache_config.cache_size must be greater than zero");
+ }
}
+
// LruCache's size is an int and briefly exceeds its maximum size before evicting entries
cacheSize = UnsignedLongs.min(cacheSize, Integer.MAX_VALUE - 1);
}
@@ -112,41 +133,65 @@ public ClientInterceptor buildClientInterceptor(FilterConfig config,
@Nullable FilterConfig overrideConfig, ScheduledExecutorService scheduler) {
ComputeEngineCredentials credentials = ComputeEngineCredentials.create();
- LruCache callCredentialsCache =
- new LruCache<>(((GcpAuthenticationConfig) config).getCacheSize());
+ synchronized (callCredentialsCache) {
+ callCredentialsCache.resizeCache(((GcpAuthenticationConfig) config).getCacheSize());
+ }
return new ClientInterceptor() {
@Override
public ClientCall interceptCall(
MethodDescriptor method, CallOptions callOptions, Channel next) {
- /*String clusterName = callOptions.getOption(XdsAttributes.ATTR_CLUSTER_NAME);
+ String clusterName = callOptions.getOption(CLUSTER_SELECTION_KEY);
if (clusterName == null) {
+ return new FailingClientCall<>(
+ Status.UNAVAILABLE.withDescription(
+ String.format(
+ "GCP Authn for %s does not contain cluster resource", filterInstanceName)));
+ }
+
+ if (!clusterName.startsWith("cluster:")) {
return next.newCall(method, callOptions);
- }*/
-
- // TODO: Fetch the CDS resource for the cluster.
- // If the CDS resource is not available, fail the RPC with Status.UNAVAILABLE.
-
- // TODO: Extract the audience from the CDS resource metadata.
- // If the audience is not found or is in the wrong format, fail the RPC.
- String audience = "TEST_AUDIENCE";
-
- try {
- CallCredentials existingCallCredentials = callOptions.getCredentials();
- CallCredentials newCallCredentials =
- getCallCredentials(callCredentialsCache, audience, credentials);
- if (existingCallCredentials != null) {
- callOptions = callOptions.withCallCredentials(
- new CompositeCallCredentials(existingCallCredentials, newCallCredentials));
- } else {
- callOptions = callOptions.withCallCredentials(newCallCredentials);
- }
}
- catch (Exception e) {
- // If we fail to attach CallCredentials due to any reason, return a FailingClientCall
- return new FailingClientCall<>(Status.UNAUTHENTICATED
- .withDescription("Failed to attach CallCredentials.")
- .withCause(e));
+ XdsConfig xdsConfig = callOptions.getOption(XDS_CONFIG_CALL_OPTION_KEY);
+ if (xdsConfig == null) {
+ return new FailingClientCall<>(
+ Status.UNAVAILABLE.withDescription(
+ String.format(
+ "GCP Authn for %s with %s does not contain xds configuration",
+ filterInstanceName, clusterName)));
+ }
+ StatusOr xdsCluster =
+ xdsConfig.getClusters().get(clusterName.substring("cluster:".length()));
+ if (xdsCluster == null) {
+ return new FailingClientCall<>(
+ Status.UNAVAILABLE.withDescription(
+ String.format(
+ "GCP Authn for %s with %s - xds cluster config does not contain xds cluster",
+ filterInstanceName, clusterName)));
+ }
+ if (!xdsCluster.hasValue()) {
+ return new FailingClientCall<>(xdsCluster.getStatus());
+ }
+ Object audienceObj =
+ xdsCluster.getValue().getClusterResource().parsedMetadata().get(filterInstanceName);
+ if (audienceObj == null) {
+ return next.newCall(method, callOptions);
+ }
+ if (!(audienceObj instanceof AudienceWrapper)) {
+ return new FailingClientCall<>(
+ Status.UNAVAILABLE.withDescription(
+ String.format("GCP Authn found wrong type in %s metadata: %s=%s",
+ clusterName, filterInstanceName, audienceObj.getClass())));
+ }
+ AudienceWrapper audience = (AudienceWrapper) audienceObj;
+ CallCredentials existingCallCredentials = callOptions.getCredentials();
+ CallCredentials newCallCredentials =
+ getCallCredentials(callCredentialsCache, audience.audience, credentials);
+ if (existingCallCredentials != null) {
+ callOptions = callOptions.withCallCredentials(
+ new CompositeCallCredentials(existingCallCredentials, newCallCredentials));
+ } else {
+ callOptions = callOptions.withCallCredentials(newCallCredentials);
}
return next.newCall(method, callOptions);
}
@@ -186,9 +231,11 @@ public String typeUrl() {
}
/** An implementation of {@link ClientCall} that fails when started. */
- private static final class FailingClientCall extends ClientCall {
+ @VisibleForTesting
+ static final class FailingClientCall extends ClientCall {
- private final Status error;
+ @VisibleForTesting
+ final Status error;
public FailingClientCall(Status error) {
this.error = error;
@@ -214,36 +261,62 @@ public void sendMessage(ReqT message) {}
private static final class LruCache {
- private final Map cache;
+ private Map cache;
+ private int maxSize;
LruCache(int maxSize) {
- this.cache = new LinkedHashMap(
- maxSize,
- 0.75f,
- true) {
- @Override
- protected boolean removeEldestEntry(Map.Entry eldest) {
- return size() > maxSize;
- }
- };
+ this.maxSize = maxSize;
+ this.cache = createEvictingMap(maxSize);
}
V getOrInsert(K key, Function create) {
return cache.computeIfAbsent(key, create);
}
+
+ private void resizeCache(int newSize) {
+ if (newSize >= maxSize) {
+ maxSize = newSize;
+ return;
+ }
+ Map newCache = createEvictingMap(newSize);
+ maxSize = newSize;
+ newCache.putAll(cache);
+ cache = newCache;
+ }
+
+ private Map createEvictingMap(int size) {
+ return new LinkedHashMap(size, 0.75f, true) {
+ @Override
+ protected boolean removeEldestEntry(Map.Entry eldest) {
+ return size() > LruCache.this.maxSize;
+ }
+ };
+ }
}
static class AudienceMetadataParser implements MetadataValueParser {
+ static final class AudienceWrapper {
+ final String audience;
+
+ AudienceWrapper(String audience) {
+ this.audience = checkNotNull(audience);
+ }
+ }
+
@Override
public String getTypeUrl() {
return "type.googleapis.com/envoy.extensions.filters.http.gcp_authn.v3.Audience";
}
@Override
- public String parse(Any any) throws ResourceInvalidException {
+ public AudienceWrapper parse(Any any) throws ResourceInvalidException {
Audience audience;
try {
+ if (!isEnabledGcpAuthnFilter) {
+ throw new InvalidProtocolBufferException("Environment variable for GCP Authentication "
+ + "Filter is Not Set");
+ }
audience = any.unpack(Audience.class);
} catch (InvalidProtocolBufferException ex) {
throw new ResourceInvalidException("Invalid Resource in address proto", ex);
@@ -253,7 +326,7 @@ public String parse(Any any) throws ResourceInvalidException {
throw new ResourceInvalidException(
"Audience URL is empty. Metadata value must contain a valid URL.");
}
- return url;
+ return new AudienceWrapper(url);
}
}
}
diff --git a/xds/src/main/java/io/grpc/xds/XdsDependencyManager.java b/xds/src/main/java/io/grpc/xds/XdsDependencyManager.java
index 8cd3119727d..d804954ecf9 100644
--- a/xds/src/main/java/io/grpc/xds/XdsDependencyManager.java
+++ b/xds/src/main/java/io/grpc/xds/XdsDependencyManager.java
@@ -199,6 +199,7 @@ private void shutdownWatchersForType(TypeWatchers
for (Map.Entry> watcherEntry : watchers.watchers.entrySet()) {
xdsClient.cancelXdsResourceWatch(watchers.resourceType, watcherEntry.getKey(),
watcherEntry.getValue());
+ watcherEntry.getValue().cancelled = true;
}
}
@@ -591,6 +592,9 @@ private XdsWatcherBase(XdsResourceType type, String resourceName) {
@Override
public void onError(Status error) {
checkNotNull(error, "error");
+ if (cancelled) {
+ return;
+ }
// Don't update configuration on error, if we've already received configuration
if (!hasDataValue()) {
setDataAsStatus(Status.UNAVAILABLE.withDescription(
@@ -659,6 +663,9 @@ private LdsWatcher(String resourceName) {
@Override
public void onChanged(XdsListenerResource.LdsUpdate update) {
checkNotNull(update, "update");
+ if (cancelled) {
+ return;
+ }
HttpConnectionManager httpConnectionManager = update.httpConnectionManager();
List virtualHosts;
@@ -787,6 +794,9 @@ public RdsWatcher(String resourceName) {
@Override
public void onChanged(RdsUpdate update) {
checkNotNull(update, "update");
+ if (cancelled) {
+ return;
+ }
List oldVirtualHosts = hasDataValue()
? getData().getValue().virtualHosts
: Collections.emptyList();
@@ -815,6 +825,9 @@ private class CdsWatcher extends XdsWatcherBase {
@Override
public void onChanged(XdsClusterResource.CdsUpdate update) {
checkNotNull(update, "update");
+ if (cancelled) {
+ return;
+ }
switch (update.clusterType()) {
case EDS:
setData(update);
@@ -895,6 +908,9 @@ private EdsWatcher(String resourceName, CdsWatcher parentContext) {
@Override
public void onChanged(XdsEndpointResource.EdsUpdate update) {
+ if (cancelled) {
+ return;
+ }
setData(checkNotNull(update, "update"));
maybePublishConfig();
}
diff --git a/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java
index d0176d7aa38..d701f281c01 100644
--- a/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java
+++ b/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java
@@ -200,6 +200,7 @@ public XdsClient returnObject(Object object) {
private ArgumentCaptor pickerCaptor;
private int xdsClientRefs;
private ClusterResolverLoadBalancer loadBalancer;
+ private NameResolverProvider fakeNameResolverProvider;
@Before
public void setUp() throws URISyntaxException {
@@ -216,7 +217,8 @@ public void setUp() throws URISyntaxException {
.setServiceConfigParser(mock(ServiceConfigParser.class))
.setChannelLogger(mock(ChannelLogger.class))
.build();
- nsRegistry.register(new FakeNameResolverProvider());
+ fakeNameResolverProvider = new FakeNameResolverProvider(false);
+ nsRegistry.register(fakeNameResolverProvider);
when(helper.getNameResolverRegistry()).thenReturn(nsRegistry);
when(helper.getNameResolverArgs()).thenReturn(args);
when(helper.getSynchronizationContext()).thenReturn(syncContext);
@@ -826,6 +828,17 @@ public void handleEdsResource_noHealthyEndpoint() {
@Test
public void onlyLogicalDnsCluster_endpointsResolved() {
+ do_onlyLogicalDnsCluster_endpointsResolved();
+ }
+
+ @Test
+ public void oldListenerCallback_onlyLogicalDnsCluster_endpointsResolved() {
+ nsRegistry.deregister(fakeNameResolverProvider);
+ nsRegistry.register(new FakeNameResolverProvider(true));
+ do_onlyLogicalDnsCluster_endpointsResolved();
+ }
+
+ void do_onlyLogicalDnsCluster_endpointsResolved() {
ClusterResolverConfig config = new ClusterResolverConfig(
Collections.singletonList(logicalDnsDiscoveryMechanism), roundRobin, false);
deliverLbConfig(config);
@@ -854,7 +867,6 @@ public void onlyLogicalDnsCluster_endpointsResolved() {
.get(XdsAttributes.ATTR_ADDRESS_NAME)).isEqualTo(DNS_HOST_NAME);
assertThat(childBalancer.addresses.get(1).getAttributes()
.get(XdsAttributes.ATTR_ADDRESS_NAME)).isEqualTo(DNS_HOST_NAME);
-
}
@Test
@@ -874,37 +886,48 @@ public void onlyLogicalDnsCluster_handleRefreshNameResolution() {
}
@Test
- public void onlyLogicalDnsCluster_resolutionError_backoffAndRefresh() {
+ public void resolutionError_backoffAndRefresh() {
+ do_onlyLogicalDnsCluster_resolutionError_backoffAndRefresh();
+ }
+
+ @Test
+ public void oldListenerCallback_resolutionError_backoffAndRefresh() {
+ nsRegistry.deregister(fakeNameResolverProvider);
+ nsRegistry.register(new FakeNameResolverProvider(true));
+ do_onlyLogicalDnsCluster_resolutionError_backoffAndRefresh();
+ }
+
+ void do_onlyLogicalDnsCluster_resolutionError_backoffAndRefresh() {
InOrder inOrder = Mockito.inOrder(helper, backoffPolicyProvider,
- backoffPolicy1, backoffPolicy2);
+ backoffPolicy1, backoffPolicy2);
ClusterResolverConfig config = new ClusterResolverConfig(
- Collections.singletonList(logicalDnsDiscoveryMechanism), roundRobin, false);
+ Collections.singletonList(logicalDnsDiscoveryMechanism), roundRobin, false);
deliverLbConfig(config);
FakeNameResolver resolver = assertResolverCreated("/" + DNS_HOST_NAME);
assertThat(childBalancers).isEmpty();
Status error = Status.UNAVAILABLE.withDescription("cannot reach DNS server");
resolver.deliverError(error);
inOrder.verify(helper).updateBalancingState(
- eq(ConnectivityState.TRANSIENT_FAILURE), pickerCaptor.capture());
+ eq(ConnectivityState.TRANSIENT_FAILURE), pickerCaptor.capture());
assertPicker(pickerCaptor.getValue(), error, null);
assertThat(resolver.refreshCount).isEqualTo(0);
inOrder.verify(backoffPolicyProvider).get();
inOrder.verify(backoffPolicy1).nextBackoffNanos();
assertThat(fakeClock.getPendingTasks()).hasSize(1);
assertThat(Iterables.getOnlyElement(fakeClock.getPendingTasks()).getDelay(TimeUnit.SECONDS))
- .isEqualTo(1L);
+ .isEqualTo(1L);
fakeClock.forwardTime(1L, TimeUnit.SECONDS);
assertThat(resolver.refreshCount).isEqualTo(1);
error = Status.UNKNOWN.withDescription("I am lost");
resolver.deliverError(error);
inOrder.verify(helper).updateBalancingState(
- eq(ConnectivityState.TRANSIENT_FAILURE), pickerCaptor.capture());
+ eq(ConnectivityState.TRANSIENT_FAILURE), pickerCaptor.capture());
inOrder.verify(backoffPolicy1).nextBackoffNanos();
assertPicker(pickerCaptor.getValue(), error, null);
assertThat(fakeClock.getPendingTasks()).hasSize(1);
assertThat(Iterables.getOnlyElement(fakeClock.getPendingTasks()).getDelay(TimeUnit.SECONDS))
- .isEqualTo(10L);
+ .isEqualTo(10L);
fakeClock.forwardTime(10L, TimeUnit.SECONDS);
assertThat(resolver.refreshCount).isEqualTo(2);
@@ -914,7 +937,7 @@ public void onlyLogicalDnsCluster_resolutionError_backoffAndRefresh() {
resolver.deliverEndpointAddresses(Arrays.asList(endpoint1, endpoint2));
assertThat(childBalancers).hasSize(1);
assertAddressesEqual(Arrays.asList(endpoint1, endpoint2),
- Iterables.getOnlyElement(childBalancers).addresses);
+ Iterables.getOnlyElement(childBalancers).addresses);
assertThat(fakeClock.getPendingTasks()).isEmpty();
inOrder.verifyNoMoreInteractions();
@@ -1319,10 +1342,18 @@ void deliverError(Status error) {
}
private class FakeNameResolverProvider extends NameResolverProvider {
+ private final boolean useOldListenerCallback;
+
+ private FakeNameResolverProvider(boolean useOldListenerCallback) {
+ this.useOldListenerCallback = useOldListenerCallback;
+ }
+
@Override
public NameResolver newNameResolver(URI targetUri, NameResolver.Args args) {
assertThat(targetUri.getScheme()).isEqualTo("dns");
- FakeNameResolver resolver = new FakeNameResolver(targetUri);
+ FakeNameResolver resolver = useOldListenerCallback
+ ? new FakeNameResolverUsingOldListenerCallback(targetUri)
+ : new FakeNameResolver(targetUri);
resolvers.add(resolver);
return resolver;
}
@@ -1343,9 +1374,10 @@ protected int priority() {
}
}
+
private class FakeNameResolver extends NameResolver {
private final URI targetUri;
- private Listener2 listener;
+ protected Listener2 listener;
private int refreshCount;
private FakeNameResolver(URI targetUri) {
@@ -1372,12 +1404,33 @@ public void shutdown() {
resolvers.remove(this);
}
- private void deliverEndpointAddresses(List addresses) {
+ protected void deliverEndpointAddresses(List addresses) {
+ syncContext.execute(() -> {
+ Status ret = listener.onResult2(ResolutionResult.newBuilder()
+ .setAddressesOrError(StatusOr.fromValue(addresses)).build());
+ assertThat(ret.getCode()).isEqualTo(Status.Code.OK);
+ });
+ }
+
+ protected void deliverError(Status error) {
+ syncContext.execute(() -> listener.onResult2(ResolutionResult.newBuilder()
+ .setAddressesOrError(StatusOr.fromStatus(error)).build()));
+ }
+ }
+
+ private class FakeNameResolverUsingOldListenerCallback extends FakeNameResolver {
+ private FakeNameResolverUsingOldListenerCallback(URI targetUri) {
+ super(targetUri);
+ }
+
+ @Override
+ protected void deliverEndpointAddresses(List addresses) {
listener.onResult(ResolutionResult.newBuilder()
- .setAddressesOrError(StatusOr.fromValue(addresses)).build());
+ .setAddressesOrError(StatusOr.fromValue(addresses)).build());
}
- private void deliverError(Status error) {
+ @Override
+ protected void deliverError(Status error) {
listener.onError(error);
}
}
diff --git a/xds/src/test/java/io/grpc/xds/GcpAuthenticationFilterTest.java b/xds/src/test/java/io/grpc/xds/GcpAuthenticationFilterTest.java
index 52efaf9bd7b..cebf739d417 100644
--- a/xds/src/test/java/io/grpc/xds/GcpAuthenticationFilterTest.java
+++ b/xds/src/test/java/io/grpc/xds/GcpAuthenticationFilterTest.java
@@ -17,25 +17,63 @@
package io.grpc.xds;
import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.xds.XdsNameResolver.CLUSTER_SELECTION_KEY;
+import static io.grpc.xds.XdsNameResolver.XDS_CONFIG_CALL_OPTION_KEY;
+import static io.grpc.xds.XdsTestUtils.CLUSTER_NAME;
+import static io.grpc.xds.XdsTestUtils.EDS_NAME;
+import static io.grpc.xds.XdsTestUtils.ENDPOINT_HOSTNAME;
+import static io.grpc.xds.XdsTestUtils.ENDPOINT_PORT;
+import static io.grpc.xds.XdsTestUtils.RDS_NAME;
+import static io.grpc.xds.XdsTestUtils.buildRouteConfiguration;
+import static io.grpc.xds.XdsTestUtils.getWrrLbConfigAsMap;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.protobuf.Any;
import com.google.protobuf.Empty;
import com.google.protobuf.Message;
import com.google.protobuf.UInt64Value;
+import io.envoyproxy.envoy.config.route.v3.RouteConfiguration;
import io.envoyproxy.envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig;
import io.envoyproxy.envoy.extensions.filters.http.gcp_authn.v3.TokenCacheConfig;
import io.grpc.CallOptions;
import io.grpc.Channel;
+import io.grpc.ClientCall;
import io.grpc.ClientInterceptor;
import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import io.grpc.StatusOr;
+import io.grpc.inprocess.InProcessServerBuilder;
import io.grpc.testing.TestMethodDescriptors;
+import io.grpc.xds.Endpoints.LbEndpoint;
+import io.grpc.xds.Endpoints.LocalityLbEndpoints;
+import io.grpc.xds.GcpAuthenticationFilter.AudienceMetadataParser.AudienceWrapper;
+import io.grpc.xds.GcpAuthenticationFilter.FailingClientCall;
import io.grpc.xds.GcpAuthenticationFilter.GcpAuthenticationConfig;
+import io.grpc.xds.XdsClusterResource.CdsUpdate;
+import io.grpc.xds.XdsConfig.XdsClusterConfig;
+import io.grpc.xds.XdsConfig.XdsClusterConfig.EndpointConfig;
+import io.grpc.xds.XdsEndpointResource.EdsUpdate;
+import io.grpc.xds.XdsListenerResource.LdsUpdate;
+import io.grpc.xds.XdsRouteConfigureResource.RdsUpdate;
+import io.grpc.xds.client.Locality;
+import io.grpc.xds.client.XdsResourceType;
+import io.grpc.xds.client.XdsResourceType.ResourceInvalidException;
+import java.io.IOException;
+import java.util.Collections;
+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;
@@ -46,6 +84,22 @@
public class GcpAuthenticationFilterTest {
private static final GcpAuthenticationFilter.Provider FILTER_PROVIDER =
new GcpAuthenticationFilter.Provider();
+ private static final String serverName = InProcessServerBuilder.generateName();
+ private static final LdsUpdate ldsUpdate = getLdsUpdate();
+ private static final EdsUpdate edsUpdate = getEdsUpdate();
+ private static final RdsUpdate rdsUpdate = getRdsUpdate();
+ private static final CdsUpdate cdsUpdate = getCdsUpdate();
+
+ @Before
+ public void setUp() {
+ System.setProperty("GRPC_EXPERIMENTAL_XDS_GCP_AUTHENTICATION_FILTER", "true");
+ }
+
+ @Test
+ public void testNewFilterInstancesPerFilterName() {
+ assertThat(new GcpAuthenticationFilter("FILTER_INSTANCE_NAME1", 10))
+ .isNotEqualTo(new GcpAuthenticationFilter("FILTER_INSTANCE_NAME1", 10));
+ }
@Test
public void filterType_clientOnly() {
@@ -92,35 +146,380 @@ public void testParseFilterConfig_withInvalidMessageType() {
}
@Test
- public void testClientInterceptor_createsAndReusesCachedCredentials() {
+ public void testClientInterceptor_success() throws IOException, ResourceInvalidException {
+ XdsConfig.XdsClusterConfig clusterConfig = new XdsConfig.XdsClusterConfig(
+ CLUSTER_NAME,
+ cdsUpdate,
+ new EndpointConfig(StatusOr.fromValue(edsUpdate)));
+ XdsConfig defaultXdsConfig = new XdsConfig.XdsConfigBuilder()
+ .setListener(ldsUpdate)
+ .setRoute(rdsUpdate)
+ .setVirtualHost(rdsUpdate.virtualHosts.get(0))
+ .addCluster(CLUSTER_NAME, StatusOr.fromValue(clusterConfig)).build();
+ CallOptions callOptionsWithXds = CallOptions.DEFAULT
+ .withOption(CLUSTER_SELECTION_KEY, "cluster:cluster0")
+ .withOption(XDS_CONFIG_CALL_OPTION_KEY, defaultXdsConfig);
GcpAuthenticationConfig config = new GcpAuthenticationConfig(10);
- GcpAuthenticationFilter filter = new GcpAuthenticationFilter();
-
- // Create interceptor
+ GcpAuthenticationFilter filter = new GcpAuthenticationFilter("FILTER_INSTANCE_NAME", 10);
ClientInterceptor interceptor = filter.buildClientInterceptor(config, null, null);
MethodDescriptor methodDescriptor = TestMethodDescriptors.voidMethod();
+ Channel mockChannel = Mockito.mock(Channel.class);
+ ArgumentCaptor callOptionsCaptor = ArgumentCaptor.forClass(CallOptions.class);
+
+ interceptor.interceptCall(methodDescriptor, callOptionsWithXds, mockChannel);
+
+ verify(mockChannel).newCall(eq(methodDescriptor), callOptionsCaptor.capture());
+ CallOptions capturedOptions = callOptionsCaptor.getAllValues().get(0);
+ assertNotNull(capturedOptions.getCredentials());
+ }
- // Mock channel and capture CallOptions
+ @Test
+ public void testClientInterceptor_createsAndReusesCachedCredentials()
+ throws IOException, ResourceInvalidException {
+ XdsConfig.XdsClusterConfig clusterConfig = new XdsConfig.XdsClusterConfig(
+ CLUSTER_NAME,
+ cdsUpdate,
+ new EndpointConfig(StatusOr.fromValue(edsUpdate)));
+ XdsConfig defaultXdsConfig = new XdsConfig.XdsConfigBuilder()
+ .setListener(ldsUpdate)
+ .setRoute(rdsUpdate)
+ .setVirtualHost(rdsUpdate.virtualHosts.get(0))
+ .addCluster(CLUSTER_NAME, StatusOr.fromValue(clusterConfig)).build();
+ CallOptions callOptionsWithXds = CallOptions.DEFAULT
+ .withOption(CLUSTER_SELECTION_KEY, "cluster:cluster0")
+ .withOption(XDS_CONFIG_CALL_OPTION_KEY, defaultXdsConfig);
+ GcpAuthenticationConfig config = new GcpAuthenticationConfig(10);
+ GcpAuthenticationFilter filter = new GcpAuthenticationFilter("FILTER_INSTANCE_NAME", 10);
+ ClientInterceptor interceptor = filter.buildClientInterceptor(config, null, null);
+ MethodDescriptor methodDescriptor = TestMethodDescriptors.voidMethod();
Channel mockChannel = Mockito.mock(Channel.class);
ArgumentCaptor callOptionsCaptor = ArgumentCaptor.forClass(CallOptions.class);
- // Execute interception twice to check caching
- interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT, mockChannel);
- interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT, mockChannel);
+ interceptor.interceptCall(methodDescriptor, callOptionsWithXds, mockChannel);
+ interceptor.interceptCall(methodDescriptor, callOptionsWithXds, mockChannel);
- // Capture and verify CallOptions for CallCredentials presence
- Mockito.verify(mockChannel, Mockito.times(2))
+ verify(mockChannel, times(2))
.newCall(eq(methodDescriptor), callOptionsCaptor.capture());
-
- // Retrieve the CallOptions captured from both calls
CallOptions firstCapturedOptions = callOptionsCaptor.getAllValues().get(0);
CallOptions secondCapturedOptions = callOptionsCaptor.getAllValues().get(1);
-
- // Ensure that CallCredentials was added
assertNotNull(firstCapturedOptions.getCredentials());
assertNotNull(secondCapturedOptions.getCredentials());
-
- // Ensure that the CallCredentials from both calls are the same, indicating caching
assertSame(firstCapturedOptions.getCredentials(), secondCapturedOptions.getCredentials());
}
+
+ @Test
+ public void testClientInterceptor_withoutClusterSelectionKey() throws Exception {
+ GcpAuthenticationConfig config = new GcpAuthenticationConfig(10);
+ GcpAuthenticationFilter filter = new GcpAuthenticationFilter("FILTER_INSTANCE_NAME", 10);
+ ClientInterceptor interceptor = filter.buildClientInterceptor(config, null, null);
+ MethodDescriptor methodDescriptor = TestMethodDescriptors.voidMethod();
+ Channel mockChannel = mock(Channel.class);
+ CallOptions callOptionsWithXds = CallOptions.DEFAULT;
+
+ ClientCall call = interceptor.interceptCall(
+ methodDescriptor, callOptionsWithXds, mockChannel);
+
+ assertTrue(call instanceof FailingClientCall);
+ FailingClientCall clientCall = (FailingClientCall) call;
+ assertThat(clientCall.error.getDescription()).contains("does not contain cluster resource");
+ }
+
+ @Test
+ public void testClientInterceptor_clusterSelectionKeyWithoutPrefix() throws Exception {
+ XdsConfig.XdsClusterConfig clusterConfig = new XdsConfig.XdsClusterConfig(
+ CLUSTER_NAME,
+ cdsUpdate,
+ new EndpointConfig(StatusOr.fromValue(edsUpdate)));
+ XdsConfig defaultXdsConfig = new XdsConfig.XdsConfigBuilder()
+ .setListener(ldsUpdate)
+ .setRoute(rdsUpdate)
+ .setVirtualHost(rdsUpdate.virtualHosts.get(0))
+ .addCluster(CLUSTER_NAME, StatusOr.fromValue(clusterConfig)).build();
+ CallOptions callOptionsWithXds = CallOptions.DEFAULT
+ .withOption(CLUSTER_SELECTION_KEY, "cluster0")
+ .withOption(XDS_CONFIG_CALL_OPTION_KEY, defaultXdsConfig);
+ Channel mockChannel = mock(Channel.class);
+
+ GcpAuthenticationConfig config = new GcpAuthenticationConfig(10);
+ GcpAuthenticationFilter filter = new GcpAuthenticationFilter("FILTER_INSTANCE_NAME", 10);
+ ClientInterceptor interceptor = filter.buildClientInterceptor(config, null, null);
+ MethodDescriptor methodDescriptor = TestMethodDescriptors.voidMethod();
+ interceptor.interceptCall(methodDescriptor, callOptionsWithXds, mockChannel);
+
+ verify(mockChannel).newCall(methodDescriptor, callOptionsWithXds);
+ }
+
+ @Test
+ public void testClientInterceptor_xdsConfigDoesNotExist() throws Exception {
+ GcpAuthenticationConfig config = new GcpAuthenticationConfig(10);
+ GcpAuthenticationFilter filter = new GcpAuthenticationFilter("FILTER_INSTANCE_NAME", 10);
+ ClientInterceptor interceptor = filter.buildClientInterceptor(config, null, null);
+ MethodDescriptor methodDescriptor = TestMethodDescriptors.voidMethod();
+ Channel mockChannel = mock(Channel.class);
+ CallOptions callOptionsWithXds = CallOptions.DEFAULT
+ .withOption(CLUSTER_SELECTION_KEY, "cluster:cluster0");
+
+ ClientCall call =
+ interceptor.interceptCall(methodDescriptor, callOptionsWithXds, mockChannel);
+
+ assertTrue(call instanceof FailingClientCall);
+ FailingClientCall clientCall = (FailingClientCall) call;
+ assertThat(clientCall.error.getDescription()).contains("does not contain xds configuration");
+ }
+
+ @Test
+ public void testClientInterceptor_incorrectClusterName() throws Exception {
+ XdsConfig.XdsClusterConfig clusterConfig = new XdsConfig.XdsClusterConfig(
+ CLUSTER_NAME,
+ cdsUpdate,
+ new EndpointConfig(StatusOr.fromValue(edsUpdate)));
+ XdsConfig defaultXdsConfig = new XdsConfig.XdsConfigBuilder()
+ .setListener(ldsUpdate)
+ .setRoute(rdsUpdate)
+ .setVirtualHost(rdsUpdate.virtualHosts.get(0))
+ .addCluster("custer0", StatusOr.fromValue(clusterConfig)).build();
+ CallOptions callOptionsWithXds = CallOptions.DEFAULT
+ .withOption(CLUSTER_SELECTION_KEY, "cluster:cluster")
+ .withOption(XDS_CONFIG_CALL_OPTION_KEY, defaultXdsConfig);
+ GcpAuthenticationConfig config = new GcpAuthenticationConfig(10);
+ GcpAuthenticationFilter filter = new GcpAuthenticationFilter("FILTER_INSTANCE_NAME", 10);
+ ClientInterceptor interceptor = filter.buildClientInterceptor(config, null, null);
+ MethodDescriptor methodDescriptor = TestMethodDescriptors.voidMethod();
+ Channel mockChannel = mock(Channel.class);
+
+ ClientCall call =
+ interceptor.interceptCall(methodDescriptor, callOptionsWithXds, mockChannel);
+
+ assertTrue(call instanceof FailingClientCall);
+ FailingClientCall clientCall = (FailingClientCall) call;
+ assertThat(clientCall.error.getDescription()).contains("does not contain xds cluster");
+ }
+
+ @Test
+ public void testClientInterceptor_statusOrError() throws Exception {
+ StatusOr errorCluster =
+ StatusOr.fromStatus(Status.NOT_FOUND.withDescription("Cluster resource not found"));
+ XdsConfig defaultXdsConfig = new XdsConfig.XdsConfigBuilder()
+ .setListener(ldsUpdate)
+ .setRoute(rdsUpdate)
+ .setVirtualHost(rdsUpdate.virtualHosts.get(0))
+ .addCluster(CLUSTER_NAME, errorCluster).build();
+ CallOptions callOptionsWithXds = CallOptions.DEFAULT
+ .withOption(CLUSTER_SELECTION_KEY, "cluster:cluster0")
+ .withOption(XDS_CONFIG_CALL_OPTION_KEY, defaultXdsConfig);
+ GcpAuthenticationConfig config = new GcpAuthenticationConfig(10);
+ GcpAuthenticationFilter filter = new GcpAuthenticationFilter("FILTER_INSTANCE_NAME", 10);
+ ClientInterceptor interceptor = filter.buildClientInterceptor(config, null, null);
+ MethodDescriptor methodDescriptor = TestMethodDescriptors.voidMethod();
+ Channel mockChannel = mock(Channel.class);
+
+ ClientCall call =
+ interceptor.interceptCall(methodDescriptor, callOptionsWithXds, mockChannel);
+
+ assertTrue(call instanceof FailingClientCall);
+ FailingClientCall clientCall = (FailingClientCall) call;
+ assertThat(clientCall.error.getDescription()).contains("Cluster resource not found");
+ }
+
+ @Test
+ public void testClientInterceptor_notAudienceWrapper()
+ throws IOException, ResourceInvalidException {
+ XdsConfig.XdsClusterConfig clusterConfig = new XdsConfig.XdsClusterConfig(
+ CLUSTER_NAME,
+ getCdsUpdateWithIncorrectAudienceWrapper(),
+ new EndpointConfig(StatusOr.fromValue(edsUpdate)));
+ XdsConfig defaultXdsConfig = new XdsConfig.XdsConfigBuilder()
+ .setListener(ldsUpdate)
+ .setRoute(rdsUpdate)
+ .setVirtualHost(rdsUpdate.virtualHosts.get(0))
+ .addCluster(CLUSTER_NAME, StatusOr.fromValue(clusterConfig)).build();
+ CallOptions callOptionsWithXds = CallOptions.DEFAULT
+ .withOption(CLUSTER_SELECTION_KEY, "cluster:cluster0")
+ .withOption(XDS_CONFIG_CALL_OPTION_KEY, defaultXdsConfig);
+ GcpAuthenticationConfig config = new GcpAuthenticationConfig(10);
+ GcpAuthenticationFilter filter = new GcpAuthenticationFilter("FILTER_INSTANCE_NAME", 10);
+ ClientInterceptor interceptor = filter.buildClientInterceptor(config, null, null);
+ MethodDescriptor methodDescriptor = TestMethodDescriptors.voidMethod();
+ Channel mockChannel = Mockito.mock(Channel.class);
+
+ ClientCall call =
+ interceptor.interceptCall(methodDescriptor, callOptionsWithXds, mockChannel);
+
+ assertTrue(call instanceof FailingClientCall);
+ FailingClientCall clientCall = (FailingClientCall) call;
+ assertThat(clientCall.error.getDescription()).contains("GCP Authn found wrong type");
+ }
+
+ @Test
+ public void testLruCacheAcrossInterceptors() throws IOException, ResourceInvalidException {
+ XdsConfig.XdsClusterConfig clusterConfig = new XdsConfig.XdsClusterConfig(
+ CLUSTER_NAME, cdsUpdate, new EndpointConfig(StatusOr.fromValue(edsUpdate)));
+ XdsConfig defaultXdsConfig = new XdsConfig.XdsConfigBuilder()
+ .setListener(ldsUpdate)
+ .setRoute(rdsUpdate)
+ .setVirtualHost(rdsUpdate.virtualHosts.get(0))
+ .addCluster(CLUSTER_NAME, StatusOr.fromValue(clusterConfig)).build();
+ CallOptions callOptionsWithXds = CallOptions.DEFAULT
+ .withOption(CLUSTER_SELECTION_KEY, "cluster:cluster0")
+ .withOption(XDS_CONFIG_CALL_OPTION_KEY, defaultXdsConfig);
+ GcpAuthenticationFilter filter = new GcpAuthenticationFilter("FILTER_INSTANCE_NAME", 2);
+ ClientInterceptor interceptor1
+ = filter.buildClientInterceptor(new GcpAuthenticationConfig(2), null, null);
+ MethodDescriptor methodDescriptor = TestMethodDescriptors.voidMethod();
+ Channel mockChannel = Mockito.mock(Channel.class);
+ ArgumentCaptor callOptionsCaptor = ArgumentCaptor.forClass(CallOptions.class);
+
+ interceptor1.interceptCall(methodDescriptor, callOptionsWithXds, mockChannel);
+ verify(mockChannel).newCall(eq(methodDescriptor), callOptionsCaptor.capture());
+ CallOptions capturedOptions1 = callOptionsCaptor.getAllValues().get(0);
+ assertNotNull(capturedOptions1.getCredentials());
+ ClientInterceptor interceptor2
+ = filter.buildClientInterceptor(new GcpAuthenticationConfig(1), null, null);
+ interceptor2.interceptCall(methodDescriptor, callOptionsWithXds, mockChannel);
+ verify(mockChannel, times(2))
+ .newCall(eq(methodDescriptor), callOptionsCaptor.capture());
+ CallOptions capturedOptions2 = callOptionsCaptor.getAllValues().get(1);
+ assertNotNull(capturedOptions2.getCredentials());
+
+ assertSame(capturedOptions1.getCredentials(), capturedOptions2.getCredentials());
+ }
+
+ @Test
+ public void testLruCacheEvictionOnResize() throws IOException, ResourceInvalidException {
+ XdsConfig.XdsClusterConfig clusterConfig = new XdsConfig.XdsClusterConfig(
+ CLUSTER_NAME, cdsUpdate, new EndpointConfig(StatusOr.fromValue(edsUpdate)));
+ XdsConfig defaultXdsConfig = new XdsConfig.XdsConfigBuilder()
+ .setListener(ldsUpdate)
+ .setRoute(rdsUpdate)
+ .setVirtualHost(rdsUpdate.virtualHosts.get(0))
+ .addCluster(CLUSTER_NAME, StatusOr.fromValue(clusterConfig)).build();
+ CallOptions callOptionsWithXds = CallOptions.DEFAULT
+ .withOption(CLUSTER_SELECTION_KEY, "cluster:cluster0")
+ .withOption(XDS_CONFIG_CALL_OPTION_KEY, defaultXdsConfig);
+ GcpAuthenticationFilter filter = new GcpAuthenticationFilter("FILTER_INSTANCE_NAME", 2);
+ MethodDescriptor methodDescriptor = TestMethodDescriptors.voidMethod();
+
+ ClientInterceptor interceptor1 =
+ filter.buildClientInterceptor(new GcpAuthenticationConfig(2), null, null);
+ Channel mockChannel1 = Mockito.mock(Channel.class);
+ ArgumentCaptor captor = ArgumentCaptor.forClass(CallOptions.class);
+ interceptor1.interceptCall(methodDescriptor, callOptionsWithXds, mockChannel1);
+ verify(mockChannel1).newCall(eq(methodDescriptor), captor.capture());
+ CallOptions options1 = captor.getValue();
+ // This will recreate the cache with max size of 1 and copy the credential for audience1.
+ ClientInterceptor interceptor2 =
+ filter.buildClientInterceptor(new GcpAuthenticationConfig(1), null, null);
+ Channel mockChannel2 = Mockito.mock(Channel.class);
+ interceptor2.interceptCall(methodDescriptor, callOptionsWithXds, mockChannel2);
+ verify(mockChannel2).newCall(eq(methodDescriptor), captor.capture());
+ CallOptions options2 = captor.getValue();
+
+ assertSame(options1.getCredentials(), options2.getCredentials());
+
+ clusterConfig = new XdsConfig.XdsClusterConfig(
+ CLUSTER_NAME, getCdsUpdate2(), new EndpointConfig(StatusOr.fromValue(edsUpdate)));
+ defaultXdsConfig = new XdsConfig.XdsConfigBuilder()
+ .setListener(ldsUpdate)
+ .setRoute(rdsUpdate)
+ .setVirtualHost(rdsUpdate.virtualHosts.get(0))
+ .addCluster(CLUSTER_NAME, StatusOr.fromValue(clusterConfig)).build();
+ callOptionsWithXds = CallOptions.DEFAULT
+ .withOption(CLUSTER_SELECTION_KEY, "cluster:cluster0")
+ .withOption(XDS_CONFIG_CALL_OPTION_KEY, defaultXdsConfig);
+
+ // This will evict the credential for audience1 and add new credential for audience2
+ ClientInterceptor interceptor3 =
+ filter.buildClientInterceptor(new GcpAuthenticationConfig(1), null, null);
+ Channel mockChannel3 = Mockito.mock(Channel.class);
+ interceptor3.interceptCall(methodDescriptor, callOptionsWithXds, mockChannel3);
+ verify(mockChannel3).newCall(eq(methodDescriptor), captor.capture());
+ CallOptions options3 = captor.getValue();
+
+ assertNotSame(options1.getCredentials(), options3.getCredentials());
+
+ clusterConfig = new XdsConfig.XdsClusterConfig(
+ CLUSTER_NAME, cdsUpdate, new EndpointConfig(StatusOr.fromValue(edsUpdate)));
+ defaultXdsConfig = new XdsConfig.XdsConfigBuilder()
+ .setListener(ldsUpdate)
+ .setRoute(rdsUpdate)
+ .setVirtualHost(rdsUpdate.virtualHosts.get(0))
+ .addCluster(CLUSTER_NAME, StatusOr.fromValue(clusterConfig)).build();
+ callOptionsWithXds = CallOptions.DEFAULT
+ .withOption(CLUSTER_SELECTION_KEY, "cluster:cluster0")
+ .withOption(XDS_CONFIG_CALL_OPTION_KEY, defaultXdsConfig);
+
+ // This will create new credential for audience1 because it has been evicted
+ ClientInterceptor interceptor4 =
+ filter.buildClientInterceptor(new GcpAuthenticationConfig(1), null, null);
+ Channel mockChannel4 = Mockito.mock(Channel.class);
+ interceptor4.interceptCall(methodDescriptor, callOptionsWithXds, mockChannel4);
+ verify(mockChannel4).newCall(eq(methodDescriptor), captor.capture());
+ CallOptions options4 = captor.getValue();
+
+ assertNotSame(options1.getCredentials(), options4.getCredentials());
+ }
+
+ private static LdsUpdate getLdsUpdate() {
+ Filter.NamedFilterConfig routerFilterConfig = new Filter.NamedFilterConfig(
+ serverName, RouterFilter.ROUTER_CONFIG);
+ HttpConnectionManager httpConnectionManager = HttpConnectionManager.forRdsName(
+ 0L, RDS_NAME, Collections.singletonList(routerFilterConfig));
+ return XdsListenerResource.LdsUpdate.forApiListener(httpConnectionManager);
+ }
+
+ private static RdsUpdate getRdsUpdate() {
+ RouteConfiguration routeConfiguration =
+ buildRouteConfiguration(serverName, RDS_NAME, CLUSTER_NAME);
+ XdsResourceType.Args args = new XdsResourceType.Args(null, "0", "0", null, null, null);
+ try {
+ return XdsRouteConfigureResource.getInstance().doParse(args, routeConfiguration);
+ } catch (ResourceInvalidException ex) {
+ return null;
+ }
+ }
+
+ private static EdsUpdate getEdsUpdate() {
+ Map lbEndpointsMap = new HashMap<>();
+ LbEndpoint lbEndpoint = LbEndpoint.create(
+ serverName, ENDPOINT_PORT, 0, true, ENDPOINT_HOSTNAME, ImmutableMap.of());
+ lbEndpointsMap.put(
+ Locality.create("", "", ""),
+ LocalityLbEndpoints.create(ImmutableList.of(lbEndpoint), 10, 0, ImmutableMap.of()));
+ return new XdsEndpointResource.EdsUpdate(EDS_NAME, lbEndpointsMap, Collections.emptyList());
+ }
+
+ private static CdsUpdate getCdsUpdate() {
+ ImmutableMap.Builder parsedMetadata = ImmutableMap.builder();
+ parsedMetadata.put("FILTER_INSTANCE_NAME", new AudienceWrapper("TEST_AUDIENCE"));
+ try {
+ CdsUpdate.Builder cdsUpdate = CdsUpdate.forEds(
+ CLUSTER_NAME, EDS_NAME, null, null, null, null, false)
+ .lbPolicyConfig(getWrrLbConfigAsMap());
+ return cdsUpdate.parsedMetadata(parsedMetadata.build()).build();
+ } catch (IOException ex) {
+ return null;
+ }
+ }
+
+ private static CdsUpdate getCdsUpdate2() {
+ ImmutableMap.Builder parsedMetadata = ImmutableMap.builder();
+ parsedMetadata.put("FILTER_INSTANCE_NAME", new AudienceWrapper("NEW_TEST_AUDIENCE"));
+ try {
+ CdsUpdate.Builder cdsUpdate = CdsUpdate.forEds(
+ CLUSTER_NAME, EDS_NAME, null, null, null, null, false)
+ .lbPolicyConfig(getWrrLbConfigAsMap());
+ return cdsUpdate.parsedMetadata(parsedMetadata.build()).build();
+ } catch (IOException ex) {
+ return null;
+ }
+ }
+
+ private static CdsUpdate getCdsUpdateWithIncorrectAudienceWrapper() throws IOException {
+ ImmutableMap.Builder parsedMetadata = ImmutableMap.builder();
+ parsedMetadata.put("FILTER_INSTANCE_NAME", "TEST_AUDIENCE");
+ CdsUpdate.Builder cdsUpdate = CdsUpdate.forEds(
+ CLUSTER_NAME, EDS_NAME, null, null, null, null, false)
+ .lbPolicyConfig(getWrrLbConfigAsMap());
+ return cdsUpdate.parsedMetadata(parsedMetadata.build()).build();
+ }
}
diff --git a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplDataTest.java b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplDataTest.java
index bfaa17245cf..d6f711ee20c 100644
--- a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplDataTest.java
+++ b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplDataTest.java
@@ -129,6 +129,7 @@
import io.grpc.xds.Endpoints.LbEndpoint;
import io.grpc.xds.Endpoints.LocalityLbEndpoints;
import io.grpc.xds.Filter.FilterConfig;
+import io.grpc.xds.GcpAuthenticationFilter.AudienceMetadataParser.AudienceWrapper;
import io.grpc.xds.MetadataRegistry.MetadataValueParser;
import io.grpc.xds.RouteLookupServiceClusterSpecifierPlugin.RlsPluginConfig;
import io.grpc.xds.VirtualHost.Route;
@@ -2417,8 +2418,8 @@ public Object parse(Any value) {
}
@Test
- public void processCluster_parsesAudienceMetadata()
- throws ResourceInvalidException, InvalidProtocolBufferException {
+ public void processCluster_parsesAudienceMetadata() throws Exception {
+ FilterRegistry.isEnabledGcpAuthnFilter = true;
MetadataRegistry.getInstance();
Audience audience = Audience.newBuilder()
@@ -2462,7 +2463,14 @@ public void processCluster_parsesAudienceMetadata()
"FILTER_METADATA", ImmutableMap.of(
"key1", "value1",
"key2", 42.0));
- assertThat(update.parsedMetadata()).isEqualTo(expectedParsedMetadata);
+ try {
+ assertThat(update.parsedMetadata().get("FILTER_METADATA"))
+ .isEqualTo(expectedParsedMetadata.get("FILTER_METADATA"));
+ assertThat(update.parsedMetadata().get("AUDIENCE_METADATA"))
+ .isInstanceOf(AudienceWrapper.class);
+ } finally {
+ FilterRegistry.isEnabledGcpAuthnFilter = false;
+ }
}
@Test
@@ -2519,8 +2527,7 @@ public void processCluster_parsesAddressMetadata() throws Exception {
}
@Test
- public void processCluster_metadataKeyCollision_resolvesToTypedMetadata()
- throws ResourceInvalidException, InvalidProtocolBufferException {
+ public void processCluster_metadataKeyCollision_resolvesToTypedMetadata() throws Exception {
MetadataRegistry metadataRegistry = MetadataRegistry.getInstance();
MetadataValueParser testParser =
@@ -2575,8 +2582,7 @@ public Object parse(Any value) {
}
@Test
- public void parseNonAggregateCluster_withHttp11ProxyTransportSocket()
- throws ResourceInvalidException, InvalidProtocolBufferException {
+ public void parseNonAggregateCluster_withHttp11ProxyTransportSocket() throws Exception {
XdsClusterResource.isEnabledXdsHttpConnect = true;
Http11ProxyUpstreamTransport http11ProxyUpstreamTransport =
diff --git a/xds/src/test/java/io/grpc/xds/XdsDependencyManagerTest.java b/xds/src/test/java/io/grpc/xds/XdsDependencyManagerTest.java
index 2af04a3aedf..1f3d8511ecc 100644
--- a/xds/src/test/java/io/grpc/xds/XdsDependencyManagerTest.java
+++ b/xds/src/test/java/io/grpc/xds/XdsDependencyManagerTest.java
@@ -41,6 +41,7 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import com.google.common.util.concurrent.MoreExecutors;
import com.google.protobuf.Message;
import io.envoyproxy.envoy.config.cluster.v3.Cluster;
import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment;
@@ -65,7 +66,7 @@
import io.grpc.xds.XdsConfig.XdsClusterConfig;
import io.grpc.xds.XdsEndpointResource.EdsUpdate;
import io.grpc.xds.client.CommonBootstrapperTestUtils;
-import io.grpc.xds.client.XdsClientImpl;
+import io.grpc.xds.client.XdsClient;
import io.grpc.xds.client.XdsClientMetricReporter;
import io.grpc.xds.client.XdsTransportFactory;
import java.io.Closeable;
@@ -115,7 +116,7 @@ public class XdsDependencyManagerTest {
});
private ManagedChannel channel;
- private XdsClientImpl xdsClient;
+ private XdsClient xdsClient;
private XdsDependencyManager xdsDependencyManager;
private TestWatcher xdsConfigWatcher;
private Server xdsServer;
@@ -715,6 +716,138 @@ public void testCdsError() throws IOException {
assertThat(status.getDescription()).contains(XdsTestUtils.CLUSTER_NAME);
}
+ @Test
+ public void ldsUpdateAfterShutdown() {
+ XdsTestUtils.setAdsConfig(controlPlaneService, serverName, "RDS", "CDS", "EDS",
+ ENDPOINT_HOSTNAME, ENDPOINT_PORT);
+
+ xdsDependencyManager = new XdsDependencyManager(xdsClient, xdsConfigWatcher, syncContext,
+ serverName, serverName, nameResolverArgs, scheduler);
+
+ verify(xdsConfigWatcher, timeout(1000)).onUpdate(any());
+
+ @SuppressWarnings("unchecked")
+ XdsClient.ResourceWatcher resourceWatcher =
+ mock(XdsClient.ResourceWatcher.class);
+ xdsClient.watchXdsResource(
+ XdsListenerResource.getInstance(),
+ serverName,
+ resourceWatcher,
+ MoreExecutors.directExecutor());
+ verify(resourceWatcher, timeout(5000)).onChanged(any());
+
+ syncContext.execute(() -> {
+ // Shutdown before any updates. This will unsubscribe from XdsClient, but only after this
+ // Runnable returns
+ xdsDependencyManager.shutdown();
+
+ XdsTestUtils.setAdsConfig(controlPlaneService, serverName, "RDS2", "CDS", "EDS",
+ ENDPOINT_HOSTNAME, ENDPOINT_PORT);
+ verify(resourceWatcher, timeout(5000).times(2)).onChanged(any());
+ xdsClient.cancelXdsResourceWatch(
+ XdsListenerResource.getInstance(), serverName, resourceWatcher);
+ });
+ }
+
+ @Test
+ public void rdsUpdateAfterShutdown() {
+ XdsTestUtils.setAdsConfig(controlPlaneService, serverName, "RDS", "CDS", "EDS",
+ ENDPOINT_HOSTNAME, ENDPOINT_PORT);
+
+ xdsDependencyManager = new XdsDependencyManager(xdsClient, xdsConfigWatcher, syncContext,
+ serverName, serverName, nameResolverArgs, scheduler);
+
+ verify(xdsConfigWatcher, timeout(1000)).onUpdate(any());
+
+ @SuppressWarnings("unchecked")
+ XdsClient.ResourceWatcher resourceWatcher =
+ mock(XdsClient.ResourceWatcher.class);
+ xdsClient.watchXdsResource(
+ XdsRouteConfigureResource.getInstance(),
+ "RDS",
+ resourceWatcher,
+ MoreExecutors.directExecutor());
+ verify(resourceWatcher, timeout(5000)).onChanged(any());
+
+ syncContext.execute(() -> {
+ // Shutdown before any updates. This will unsubscribe from XdsClient, but only after this
+ // Runnable returns
+ xdsDependencyManager.shutdown();
+
+ XdsTestUtils.setAdsConfig(controlPlaneService, serverName, "RDS", "CDS2", "EDS",
+ ENDPOINT_HOSTNAME, ENDPOINT_PORT);
+ verify(resourceWatcher, timeout(5000).times(2)).onChanged(any());
+ xdsClient.cancelXdsResourceWatch(
+ XdsRouteConfigureResource.getInstance(), serverName, resourceWatcher);
+ });
+ }
+
+ @Test
+ public void cdsUpdateAfterShutdown() {
+ XdsTestUtils.setAdsConfig(controlPlaneService, serverName, "RDS", "CDS", "EDS",
+ ENDPOINT_HOSTNAME, ENDPOINT_PORT);
+
+ xdsDependencyManager = new XdsDependencyManager(xdsClient, xdsConfigWatcher, syncContext,
+ serverName, serverName, nameResolverArgs, scheduler);
+
+ verify(xdsConfigWatcher, timeout(1000)).onUpdate(any());
+
+ @SuppressWarnings("unchecked")
+ XdsClient.ResourceWatcher resourceWatcher =
+ mock(XdsClient.ResourceWatcher.class);
+ xdsClient.watchXdsResource(
+ XdsClusterResource.getInstance(),
+ "CDS",
+ resourceWatcher,
+ MoreExecutors.directExecutor());
+ verify(resourceWatcher, timeout(5000)).onChanged(any());
+
+ syncContext.execute(() -> {
+ // Shutdown before any updates. This will unsubscribe from XdsClient, but only after this
+ // Runnable returns
+ xdsDependencyManager.shutdown();
+
+ XdsTestUtils.setAdsConfig(controlPlaneService, serverName, "RDS", "CDS", "EDS2",
+ ENDPOINT_HOSTNAME, ENDPOINT_PORT);
+ verify(resourceWatcher, timeout(5000).times(2)).onChanged(any());
+ xdsClient.cancelXdsResourceWatch(
+ XdsClusterResource.getInstance(), serverName, resourceWatcher);
+ });
+ }
+
+ @Test
+ public void edsUpdateAfterShutdown() {
+ XdsTestUtils.setAdsConfig(controlPlaneService, serverName, "RDS", "CDS", "EDS",
+ ENDPOINT_HOSTNAME, ENDPOINT_PORT);
+
+ xdsDependencyManager = new XdsDependencyManager(xdsClient, xdsConfigWatcher, syncContext,
+ serverName, serverName, nameResolverArgs, scheduler);
+
+ verify(xdsConfigWatcher, timeout(1000)).onUpdate(any());
+
+ @SuppressWarnings("unchecked")
+ XdsClient.ResourceWatcher resourceWatcher =
+ mock(XdsClient.ResourceWatcher.class);
+ xdsClient.watchXdsResource(
+ XdsEndpointResource.getInstance(),
+ "EDS",
+ resourceWatcher,
+ MoreExecutors.directExecutor());
+ verify(resourceWatcher, timeout(5000)).onChanged(any());
+
+ syncContext.execute(() -> {
+ // Shutdown before any updates. This will unsubscribe from XdsClient, but only after this
+ // Runnable returns
+ xdsDependencyManager.shutdown();
+
+ XdsTestUtils.setAdsConfig(controlPlaneService, serverName, "RDS", "CDS", "EDS",
+ ENDPOINT_HOSTNAME + "2", ENDPOINT_PORT);
+ verify(resourceWatcher, timeout(5000).times(2)).onChanged(any());
+ xdsClient.cancelXdsResourceWatch(
+ XdsEndpointResource.getInstance(), serverName, resourceWatcher);
+ });
+ }
+
private Listener buildInlineClientListener(String rdsName, String clusterName) {
return XdsTestUtils.buildInlineClientListener(rdsName, clusterName, serverName);
}
diff --git a/xds/src/test/java/io/grpc/xds/XdsTestUtils.java b/xds/src/test/java/io/grpc/xds/XdsTestUtils.java
index 9f90777be3d..52953ef5407 100644
--- a/xds/src/test/java/io/grpc/xds/XdsTestUtils.java
+++ b/xds/src/test/java/io/grpc/xds/XdsTestUtils.java
@@ -291,7 +291,7 @@ static Map createMinimalLbEndpointsMap(String ser
}
@SuppressWarnings("unchecked")
- private static ImmutableMap getWrrLbConfigAsMap() throws IOException {
+ static ImmutableMap getWrrLbConfigAsMap() throws IOException {
String lbConfigStr = "{\"wrr_locality_experimental\" : "
+ "{ \"childPolicy\" : [{\"round_robin\" : {}}]}}";