From 0f14a55d416b359f52a2c59945809e3a9d2ad17d Mon Sep 17 00:00:00 2001 From: Kannan J Date: Wed, 26 Aug 2026 08:27:48 +0000 Subject: [PATCH 1/2] xds: Move JwtTokenFileCallCredentials to xds.client package and make package-private Move `JwtTokenFileCallCredentials` from `io.grpc.auth` to `io.grpc.xds.client` and make it package-private as it is only used in xds. To support this move without breaking tests: - Moved `JwtTokenFileCallCredentialsTest` to the same package (`io.grpc.xds.client`). - Created `BootstrapperImplTest` in `io.grpc.xds.client` to test the base `BootstrapperImpl` parsing logic. - Moved `call_creds` parsing tests (which reference `JwtTokenFileCallCredentials`) from `GrpcBootstrapperImplTest` to the new `BootstrapperImplTest`. --- .../io/grpc/xds/client/BootstrapperImpl.java | 1 - .../client}/JwtTokenFileCallCredentials.java | 6 +- .../io/grpc/xds/GrpcBootstrapperImplTest.java | 255 ------------- .../grpc/xds/client/BootstrapperImplTest.java | 341 ++++++++++++++++++ .../JwtTokenFileCallCredentialsTest.java | 2 +- 5 files changed, 345 insertions(+), 260 deletions(-) rename {auth/src/main/java/io/grpc/auth => xds/src/main/java/io/grpc/xds/client}/JwtTokenFileCallCredentials.java (98%) create mode 100644 xds/src/test/java/io/grpc/xds/client/BootstrapperImplTest.java rename {auth/src/test/java/io/grpc/auth => xds/src/test/java/io/grpc/xds/client}/JwtTokenFileCallCredentialsTest.java (99%) diff --git a/xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java b/xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java index 8d5d3471b9d..cdbb1fd6c2d 100644 --- a/xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java +++ b/xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java @@ -23,7 +23,6 @@ import io.grpc.CompositeCallCredentials; import io.grpc.Internal; import io.grpc.InternalLogId; -import io.grpc.auth.JwtTokenFileCallCredentials; import io.grpc.internal.GrpcUtil; import io.grpc.internal.GrpcUtil.GrpcBuildVersion; import io.grpc.internal.JsonParser; diff --git a/auth/src/main/java/io/grpc/auth/JwtTokenFileCallCredentials.java b/xds/src/main/java/io/grpc/xds/client/JwtTokenFileCallCredentials.java similarity index 98% rename from auth/src/main/java/io/grpc/auth/JwtTokenFileCallCredentials.java rename to xds/src/main/java/io/grpc/xds/client/JwtTokenFileCallCredentials.java index eda4a142358..bb58ef85c01 100644 --- a/auth/src/main/java/io/grpc/auth/JwtTokenFileCallCredentials.java +++ b/xds/src/main/java/io/grpc/xds/client/JwtTokenFileCallCredentials.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.auth; +package io.grpc.xds.client; import static com.google.common.base.Preconditions.checkNotNull; @@ -45,7 +45,7 @@ * A {@link CallCredentials} implementation that loads a JWT token from a file, * parses it to extract its expiration time, and caches/refreshes it. */ -public final class JwtTokenFileCallCredentials extends CallCredentials { +final class JwtTokenFileCallCredentials extends CallCredentials { private static final int MAX_FILE_SIZE_BYTES = 1048576; private static final Logger log = Logger.getLogger(JwtTokenFileCallCredentials.class.getName()); @@ -87,7 +87,7 @@ public long currentTimeMillis() { } }; - public JwtTokenFileCallCredentials(String filePath) { + JwtTokenFileCallCredentials(String filePath) { this(filePath, SYSTEM_TIME_PROVIDER); } diff --git a/xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java b/xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java index c5cbd1c5442..eaef5a424d6 100644 --- a/xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java +++ b/xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java @@ -24,10 +24,8 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; -import io.grpc.CallCredentials; import io.grpc.InsecureChannelCredentials; import io.grpc.TlsChannelCredentials; -import io.grpc.auth.JwtTokenFileCallCredentials; import io.grpc.internal.GrpcUtil; import io.grpc.internal.GrpcUtil.GrpcBuildVersion; import io.grpc.xds.client.AllowedGrpcServices; @@ -1061,257 +1059,4 @@ private static Node.Builder getNodeBuilder() { .addClientFeatures(GrpcBootstrapperImpl.CLIENT_FEATURE_RESOURCE_IN_SOTW); } - private static void setEnableXdsBootstrapCallCreds(boolean enable) { - io.grpc.xds.client.BootstrapperImpl.enableXdsBootstrapCallCreds = enable; - } - - private static String getFilePath(JwtTokenFileCallCredentials credentials) { - try { - java.lang.reflect.Field field = - JwtTokenFileCallCredentials.class.getDeclaredField("filePath"); - field.setAccessible(true); - return (String) field.get(credentials); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Test - public void parseBootstrap_callCreds_flagDisabled() throws Exception { - setEnableXdsBootstrapCallCreds(false); - try { - String rawData = "{\n" - + " \"xds_servers\": [\n" - + " {\n" - + " \"server_uri\": \"" + SERVER_URI + "\",\n" - + " \"channel_creds\": [{\"type\": \"insecure\"}],\n" - + " \"call_creds\": [\n" - + " {\n" - + " \"type\": \"jwt_token_file\",\n" - + " \"config\": {\n" - + " \"jwt_token_file\": \"/var/run/secrets/token\"\n" - + " }\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + "}"; - bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); - BootstrapInfo info = bootstrapper.bootstrap(); - assertThat(info.servers()).hasSize(1); - ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); - assertThat(serverInfo.callCredentials()).isNull(); - } finally { - setEnableXdsBootstrapCallCreds(false); - } - } - - @Test - public void parseBootstrap_xdsServers_jwtTokenFileCallCreds() throws Exception { - setEnableXdsBootstrapCallCreds(true); - try { - String rawData = "{\n" - + " \"xds_servers\": [\n" - + " {\n" - + " \"server_uri\": \"" + SERVER_URI + "\",\n" - + " \"channel_creds\": [{\"type\": \"insecure\"}],\n" - + " \"call_creds\": [\n" - + " {\n" - + " \"type\": \"jwt_token_file\",\n" - + " \"config\": {\n" - + " \"jwt_token_file\": \"/var/run/secrets/token\"\n" - + " }\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + "}"; - bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); - BootstrapInfo info = bootstrapper.bootstrap(); - assertThat(info.servers()).hasSize(1); - ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); - assertThat(serverInfo.callCredentials()) - .isInstanceOf(JwtTokenFileCallCredentials.class); - assertThat(getFilePath((JwtTokenFileCallCredentials) serverInfo.callCredentials())) - .isEqualTo("/var/run/secrets/token"); - } finally { - setEnableXdsBootstrapCallCreds(false); - } - } - - @Test - public void parseBootstrap_authorities_jwtTokenFileCallCreds() throws Exception { - setEnableXdsBootstrapCallCreds(true); - try { - String rawData = "{\n" - + " \"authorities\": {\n" - + " \"a.com\": {\n" - + " \"xds_servers\": [\n" - + " {\n" - + " \"server_uri\": \"td2.googleapis.com:443\",\n" - + " \"channel_creds\": [\n" - + " {\"type\": \"insecure\"}\n" - + " ],\n" - + " \"call_creds\": [\n" - + " {\n" - + " \"type\": \"jwt_token_file\",\n" - + " \"config\": {\n" - + " \"jwt_token_file\": \"/var/run/secrets/authority_token\"\n" - + " }\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + " }\n" - + " },\n" - + " \"xds_servers\": [\n" - + " {\n" - + " \"server_uri\": \"" + SERVER_URI + "\",\n" - + " \"channel_creds\": [\n" - + " {\"type\": \"insecure\"}\n" - + " ]\n" - + " }\n" - + " ]\n" - + "}"; - bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); - BootstrapInfo info = bootstrapper.bootstrap(); - assertThat(info.authorities()).hasSize(1); - AuthorityInfo authorityInfo = info.authorities().get("a.com"); - assertThat(authorityInfo.xdsServers()).hasSize(1); - ServerInfo serverInfo = authorityInfo.xdsServers().get(0); - assertThat(serverInfo.callCredentials()) - .isInstanceOf(JwtTokenFileCallCredentials.class); - assertThat(getFilePath((JwtTokenFileCallCredentials) serverInfo.callCredentials())) - .isEqualTo("/var/run/secrets/authority_token"); - } finally { - setEnableXdsBootstrapCallCreds(false); - } - } - - @Test - public void parseBootstrap_unsupportedCallCredsType_ignored() throws Exception { - setEnableXdsBootstrapCallCreds(true); - try { - String rawData = "{\n" - + " \"xds_servers\": [\n" - + " {\n" - + " \"server_uri\": \"" + SERVER_URI + "\",\n" - + " \"channel_creds\": [{\"type\": \"insecure\"}],\n" - + " \"call_creds\": [\n" - + " {\n" - + " \"type\": \"unsupported_type\",\n" - + " \"config\": {\n" - + " \"some_field\": \"some_val\"\n" - + " }\n" - + " },\n" - + " {\n" - + " \"type\": \"jwt_token_file\",\n" - + " \"config\": {\n" - + " \"jwt_token_file\": \"/var/run/secrets/token\"\n" - + " }\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + "}"; - bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); - BootstrapInfo info = bootstrapper.bootstrap(); - assertThat(info.servers()).hasSize(1); - ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); - assertThat(serverInfo.callCredentials()) - .isInstanceOf(JwtTokenFileCallCredentials.class); - assertThat(getFilePath((JwtTokenFileCallCredentials) serverInfo.callCredentials())) - .isEqualTo("/var/run/secrets/token"); - } finally { - setEnableXdsBootstrapCallCreds(false); - } - } - - @Test - public void parseBootstrap_malformedCallCreds_throws() throws Exception { - setEnableXdsBootstrapCallCreds(true); - try { - String rawData = "{\n" - + " \"xds_servers\": [\n" - + " {\n" - + " \"server_uri\": \"" + SERVER_URI + "\",\n" - + " \"channel_creds\": [{\"type\": \"insecure\"}],\n" - + " \"call_creds\": [\n" - + " {\n" - + " \"type\": \"jwt_token_file\",\n" - + " \"config\": {}\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + "}"; - bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); - XdsInitializationException e = assertThrows(XdsInitializationException.class, - bootstrapper::bootstrap); - assertThat(e).hasMessageThat().contains("jwt_token_file' jwt_token_file missing or empty"); - } finally { - setEnableXdsBootstrapCallCreds(false); - } - } - - @Test - public void parseBootstrap_xdsServers_multipleValidCallCreds() throws Exception { - setEnableXdsBootstrapCallCreds(true); - try { - String rawData = "{\n" - + " \"xds_servers\": [\n" - + " {\n" - + " \"server_uri\": \"" + SERVER_URI + "\",\n" - + " \"channel_creds\": [{\"type\": \"insecure\"}],\n" - + " \"call_creds\": [\n" - + " {\n" - + " \"type\": \"jwt_token_file\",\n" - + " \"config\": { \"jwt_token_file\": \"/var/run/secrets/token1\" }\n" - + " },\n" - + " {\n" - + " \"type\": \"jwt_token_file\",\n" - + " \"config\": { \"jwt_token_file\": \"/var/run/secrets/token2\" }\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + "}"; - bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); - BootstrapInfo info = bootstrapper.bootstrap(); - assertThat(info.servers()).hasSize(1); - ServerInfo serverInfo = info.servers().get(0); - CallCredentials creds = serverInfo.callCredentials(); - assertThat(creds).isNotNull(); - assertThat(creds).isInstanceOf(io.grpc.CompositeCallCredentials.class); - } finally { - setEnableXdsBootstrapCallCreds(false); - } - } - - @Test - public void parseBootstrap_xdsServers_missingTypeCallCreds() throws Exception { - setEnableXdsBootstrapCallCreds(true); - try { - String rawData = "{\n" - + " \"xds_servers\": [\n" - + " {\n" - + " \"server_uri\": \"" + SERVER_URI + "\",\n" - + " \"channel_creds\": [{\"type\": \"insecure\"}],\n" - + " \"call_creds\": [\n" - + " {\n" - + " \"config\": { \"jwt_token_file\": \"/var/run/secrets/token\" }\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + "}"; - bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); - bootstrapper.bootstrap(); - fail("Expected exception"); - } catch (XdsInitializationException e) { - assertThat(e).hasMessageThat().contains("with 'call_creds' type unspecified"); - } finally { - setEnableXdsBootstrapCallCreds(false); - } - } } diff --git a/xds/src/test/java/io/grpc/xds/client/BootstrapperImplTest.java b/xds/src/test/java/io/grpc/xds/client/BootstrapperImplTest.java new file mode 100644 index 00000000000..bda036a4be1 --- /dev/null +++ b/xds/src/test/java/io/grpc/xds/client/BootstrapperImplTest.java @@ -0,0 +1,341 @@ +/* + * Copyright 2026 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.client; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.fail; + +import com.google.common.collect.Iterables; +import io.grpc.CallCredentials; +import io.grpc.CompositeCallCredentials; +import io.grpc.xds.client.Bootstrapper.AuthorityInfo; +import io.grpc.xds.client.Bootstrapper.BootstrapInfo; +import io.grpc.xds.client.Bootstrapper.ServerInfo; +import java.io.IOException; +import java.util.Map; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link BootstrapperImpl}. */ +@RunWith(JUnit4.class) +public class BootstrapperImplTest { + + private static final String BOOTSTRAP_FILE_PATH = "/fake/fs/path/bootstrap.json"; + private static final String SERVER_URI = "trafficdirector.googleapis.com:443"; + + private TestBootstrapperImpl bootstrapper; + private boolean originalExperimentalXdsFallbackFlag; + + @Before + public void setUp() { + originalExperimentalXdsFallbackFlag = BootstrapperImpl.enableXdsFallback; + BootstrapperImpl.enableXdsFallback = true; + } + + @After + public void tearDown() { + BootstrapperImpl.enableXdsFallback = originalExperimentalXdsFallbackFlag; + } + + private static class TestBootstrapperImpl extends BootstrapperImpl { + private final String jsonContent; + + TestBootstrapperImpl(String jsonContent) { + this.jsonContent = jsonContent; + } + + @Override + protected String getJsonContent() { + return jsonContent; + } + + @Override + protected Object getImplSpecificConfig(Map serverConfig, String serverUri) { + return "dummy-config"; + } + } + + private static String getFilePath(JwtTokenFileCallCredentials credentials) { + try { + java.lang.reflect.Field field = + JwtTokenFileCallCredentials.class.getDeclaredField("filePath"); + field.setAccessible(true); + return (String) field.get(credentials); + } catch (Exception e) { + throw new AssertionError(e); + } + } + + private static BootstrapperImpl.FileReader createFileReader( + final String expectedPath, final String rawData) { + return new BootstrapperImpl.FileReader() { + @Override + public String readFile(String path) throws IOException { + assertThat(path).isEqualTo(expectedPath); + return rawData; + } + }; + } + + @Test + public void parseBootstrap_callCreds_flagDisabled() throws Exception { + BootstrapperImpl.enableXdsBootstrapCallCreds = false; + try { + String rawData = "{\n" + + " \"xds_servers\": [\n" + + " {\n" + + " \"server_uri\": \"" + SERVER_URI + "\",\n" + + " \"channel_creds\": [{\"type\": \"insecure\"}],\n" + + " \"call_creds\": [\n" + + " {\n" + + " \"type\": \"jwt_token_file\",\n" + + " \"config\": {\n" + + " \"jwt_token_file\": \"/var/run/secrets/token\"\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + bootstrapper = new TestBootstrapperImpl(rawData); + bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); + BootstrapInfo info = bootstrapper.bootstrap(); + assertThat(info.servers()).hasSize(1); + ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); + assertThat(serverInfo.callCredentials()).isNull(); + } finally { + BootstrapperImpl.enableXdsBootstrapCallCreds = false; + } + } + + @Test + public void parseBootstrap_xdsServers_jwtTokenFileCallCreds() throws Exception { + BootstrapperImpl.enableXdsBootstrapCallCreds = true; + try { + String rawData = "{\n" + + " \"xds_servers\": [\n" + + " {\n" + + " \"server_uri\": \"" + SERVER_URI + "\",\n" + + " \"channel_creds\": [{\"type\": \"insecure\"}],\n" + + " \"call_creds\": [\n" + + " {\n" + + " \"type\": \"jwt_token_file\",\n" + + " \"config\": {\n" + + " \"jwt_token_file\": \"/var/run/secrets/token\"\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + bootstrapper = new TestBootstrapperImpl(rawData); + bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); + BootstrapInfo info = bootstrapper.bootstrap(); + assertThat(info.servers()).hasSize(1); + ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); + assertThat(serverInfo.callCredentials()) + .isInstanceOf(JwtTokenFileCallCredentials.class); + assertThat(getFilePath((JwtTokenFileCallCredentials) serverInfo.callCredentials())) + .isEqualTo("/var/run/secrets/token"); + } finally { + BootstrapperImpl.enableXdsBootstrapCallCreds = false; + } + } + + @Test + public void parseBootstrap_authorities_jwtTokenFileCallCreds() throws Exception { + BootstrapperImpl.enableXdsBootstrapCallCreds = true; + try { + String rawData = "{\n" + + " \"authorities\": {\n" + + " \"a.com\": {\n" + + " \"xds_servers\": [\n" + + " {\n" + + " \"server_uri\": \"td2.googleapis.com:443\",\n" + + " \"channel_creds\": [\n" + + " {\"type\": \"insecure\"}\n" + + " ],\n" + + " \"call_creds\": [\n" + + " {\n" + + " \"type\": \"jwt_token_file\",\n" + + " \"config\": {\n" + + " \"jwt_token_file\": \"/var/run/secrets/authority_token\"\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + " }\n" + + " },\n" + + " \"xds_servers\": [\n" + + " {\n" + + " \"server_uri\": \"" + SERVER_URI + "\",\n" + + " \"channel_creds\": [\n" + + " {\"type\": \"insecure\"}\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + bootstrapper = new TestBootstrapperImpl(rawData); + bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); + BootstrapInfo info = bootstrapper.bootstrap(); + assertThat(info.authorities()).hasSize(1); + AuthorityInfo authorityInfo = info.authorities().get("a.com"); + assertThat(authorityInfo.xdsServers()).hasSize(1); + ServerInfo serverInfo = authorityInfo.xdsServers().get(0); + assertThat(serverInfo.callCredentials()) + .isInstanceOf(JwtTokenFileCallCredentials.class); + assertThat(getFilePath((JwtTokenFileCallCredentials) serverInfo.callCredentials())) + .isEqualTo("/var/run/secrets/authority_token"); + } finally { + BootstrapperImpl.enableXdsBootstrapCallCreds = false; + } + } + + @Test + public void parseBootstrap_unsupportedCallCredsType_ignored() throws Exception { + BootstrapperImpl.enableXdsBootstrapCallCreds = true; + try { + String rawData = "{\n" + + " \"xds_servers\": [\n" + + " {\n" + + " \"server_uri\": \"" + SERVER_URI + "\",\n" + + " \"channel_creds\": [{\"type\": \"insecure\"}],\n" + + " \"call_creds\": [\n" + + " {\n" + + " \"type\": \"unsupported_type\",\n" + + " \"config\": {\n" + + " \"some_field\": \"some_val\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"type\": \"jwt_token_file\",\n" + + " \"config\": {\n" + + " \"jwt_token_file\": \"/var/run/secrets/token\"\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + bootstrapper = new TestBootstrapperImpl(rawData); + bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); + BootstrapInfo info = bootstrapper.bootstrap(); + assertThat(info.servers()).hasSize(1); + ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); + assertThat(serverInfo.callCredentials()) + .isInstanceOf(JwtTokenFileCallCredentials.class); + assertThat(getFilePath((JwtTokenFileCallCredentials) serverInfo.callCredentials())) + .isEqualTo("/var/run/secrets/token"); + } finally { + BootstrapperImpl.enableXdsBootstrapCallCreds = false; + } + } + + @Test + public void parseBootstrap_malformedCallCreds_throws() throws Exception { + BootstrapperImpl.enableXdsBootstrapCallCreds = true; + try { + String rawData = "{\n" + + " \"xds_servers\": [\n" + + " {\n" + + " \"server_uri\": \"" + SERVER_URI + "\",\n" + + " \"channel_creds\": [{\"type\": \"insecure\"}],\n" + + " \"call_creds\": [\n" + + " {\n" + + " \"type\": \"jwt_token_file\",\n" + + " \"config\": {}\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + bootstrapper = new TestBootstrapperImpl(rawData); + bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); + assertThrows(XdsInitializationException.class, bootstrapper::bootstrap); + } finally { + BootstrapperImpl.enableXdsBootstrapCallCreds = false; + } + } + + @Test + public void parseBootstrap_xdsServers_multipleValidCallCreds() throws Exception { + BootstrapperImpl.enableXdsBootstrapCallCreds = true; + try { + String rawData = "{\n" + + " \"xds_servers\": [\n" + + " {\n" + + " \"server_uri\": \"" + SERVER_URI + "\",\n" + + " \"channel_creds\": [{\"type\": \"insecure\"}],\n" + + " \"call_creds\": [\n" + + " {\n" + + " \"type\": \"jwt_token_file\",\n" + + " \"config\": { \"jwt_token_file\": \"/var/run/secrets/token1\" }\n" + + " },\n" + + " {\n" + + " \"type\": \"jwt_token_file\",\n" + + " \"config\": { \"jwt_token_file\": \"/var/run/secrets/token2\" }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + bootstrapper = new TestBootstrapperImpl(rawData); + bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); + BootstrapInfo info = bootstrapper.bootstrap(); + assertThat(info.servers()).hasSize(1); + ServerInfo serverInfo = info.servers().get(0); + CallCredentials creds = serverInfo.callCredentials(); + assertThat(creds).isNotNull(); + assertThat(creds).isInstanceOf(CompositeCallCredentials.class); + } finally { + BootstrapperImpl.enableXdsBootstrapCallCreds = false; + } + } + + @Test + public void parseBootstrap_xdsServers_missingTypeCallCreds() throws Exception { + BootstrapperImpl.enableXdsBootstrapCallCreds = true; + try { + String rawData = "{\n" + + " \"xds_servers\": [\n" + + " {\n" + + " \"server_uri\": \"" + SERVER_URI + "\",\n" + + " \"channel_creds\": [{\"type\": \"insecure\"}],\n" + + " \"call_creds\": [\n" + + " {\n" + + " \"config\": { \"jwt_token_file\": \"/var/run/secrets/token\" }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + bootstrapper = new TestBootstrapperImpl(rawData); + bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); + bootstrapper.bootstrap(); + fail("Expected exception"); + } catch (XdsInitializationException e) { + assertThat(e).hasMessageThat().contains("with 'call_creds' type unspecified"); + } finally { + BootstrapperImpl.enableXdsBootstrapCallCreds = false; + } + } +} diff --git a/auth/src/test/java/io/grpc/auth/JwtTokenFileCallCredentialsTest.java b/xds/src/test/java/io/grpc/xds/client/JwtTokenFileCallCredentialsTest.java similarity index 99% rename from auth/src/test/java/io/grpc/auth/JwtTokenFileCallCredentialsTest.java rename to xds/src/test/java/io/grpc/xds/client/JwtTokenFileCallCredentialsTest.java index a5aa70e32cb..1575618613c 100644 --- a/auth/src/test/java/io/grpc/auth/JwtTokenFileCallCredentialsTest.java +++ b/xds/src/test/java/io/grpc/xds/client/JwtTokenFileCallCredentialsTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.auth; +package io.grpc.xds.client; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; From ab692459140159d6fd53c7e0c279c7ee0f5820da Mon Sep 17 00:00:00 2001 From: Kannan J Date: Mon, 31 Aug 2026 05:42:00 +0000 Subject: [PATCH 2/2] Refactor xDS bootstrapper to improve role separation between io.grpc.xds/io.grpc (grpc specific) and io.grpc.xds.client (generic) packages. - Remove call credentials from BootstrapperImpl.ServerInfo. - Store call credentials in implSpecificConfig (Object) wrapped in a Map. - Move JwtTokenFileCallCredentials to io.grpc.xds package. - Remove ChannelCredentials fallback from GrpcXdsTransportFactory. --- xds/build.gradle | 1 - .../io/grpc/xds/GrpcBootstrapperImpl.java | 79 +++- .../io/grpc/xds/GrpcXdsTransportFactory.java | 18 +- .../JwtTokenFileCallCredentials.java | 2 +- .../java/io/grpc/xds/client/Bootstrapper.java | 10 +- .../io/grpc/xds/client/BootstrapperImpl.java | 51 +-- .../io/grpc/xds/ExtAuthzConfigParserTest.java | 2 +- ...xternalProcessorClientInterceptorTest.java | 2 +- .../grpc/xds/ExternalProcessorFilterTest.java | 2 +- .../java/io/grpc/xds/FaultFilterTest.java | 2 +- .../grpc/xds/GcpAuthenticationFilterTest.java | 2 +- .../io/grpc/xds/GrpcBootstrapperImplTest.java | 291 ++++++++++++++- .../grpc/xds/GrpcServiceConfigParserTest.java | 2 +- .../grpc/xds/GrpcXdsClientImplDataTest.java | 7 +- .../grpc/xds/GrpcXdsClientImplTestBase.java | 79 ++-- .../grpc/xds/GrpcXdsTransportFactoryTest.java | 34 +- .../JwtTokenFileCallCredentialsTest.java | 2 +- .../test/java/io/grpc/xds/RbacFilterTest.java | 2 +- .../xds/SharedXdsClientPoolProviderTest.java | 14 +- .../io/grpc/xds/XdsClientFallbackTest.java | 10 +- .../xds/XdsJwtCallCredsIntegrationTest.java | 2 +- .../java/io/grpc/xds/XdsNameResolverTest.java | 32 +- .../test/java/io/grpc/xds/XdsTestUtils.java | 4 +- .../grpc/xds/client/BootstrapperImplTest.java | 341 ------------------ .../client/CommonBootstrapperTestUtils.java | 10 +- 25 files changed, 493 insertions(+), 508 deletions(-) rename xds/src/main/java/io/grpc/xds/{client => }/JwtTokenFileCallCredentials.java (99%) rename xds/src/test/java/io/grpc/xds/{client => }/JwtTokenFileCallCredentialsTest.java (99%) delete mode 100644 xds/src/test/java/io/grpc/xds/client/BootstrapperImplTest.java diff --git a/xds/build.gradle b/xds/build.gradle index c6325f7fc2d..79e424a42ec 100644 --- a/xds/build.gradle +++ b/xds/build.gradle @@ -49,7 +49,6 @@ dependencies { project(':grpc-core'), project(':grpc-util'), project(':grpc-services'), - project(':grpc-auth'), project(path: ':grpc-alts', configuration: 'shadow'), libraries.guava, libraries.gson, diff --git a/xds/src/main/java/io/grpc/xds/GrpcBootstrapperImpl.java b/xds/src/main/java/io/grpc/xds/GrpcBootstrapperImpl.java index 00a2e0d48d6..1670a95dbb3 100644 --- a/xds/src/main/java/io/grpc/xds/GrpcBootstrapperImpl.java +++ b/xds/src/main/java/io/grpc/xds/GrpcBootstrapperImpl.java @@ -21,6 +21,8 @@ import com.google.errorprone.annotations.concurrent.GuardedBy; import io.grpc.CallCredentials; import io.grpc.ChannelCredentials; +import io.grpc.CompositeCallCredentials; +import io.grpc.internal.GrpcUtil; import io.grpc.internal.JsonUtil; import io.grpc.xds.client.AllowedGrpcServices; import io.grpc.xds.client.AllowedGrpcServices.AllowedGrpcService; @@ -30,12 +32,17 @@ import io.grpc.xds.client.XdsInitializationException; import io.grpc.xds.client.XdsLogger; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; import javax.annotation.Nullable; class GrpcBootstrapperImpl extends BootstrapperImpl { + @VisibleForTesting + public static boolean enableXdsBootstrapCallCreds = GrpcUtil.getFlag( + "GRPC_EXPERIMENTAL_XDS_BOOTSTRAP_CALL_CREDS", false); + private static final String BOOTSTRAP_PATH_SYS_ENV_VAR = "GRPC_XDS_BOOTSTRAP"; private static final String BOOTSTRAP_PATH_SYS_PROPERTY = "io.grpc.xds.bootstrap"; private static final String BOOTSTRAP_CONFIG_SYS_ENV_VAR = "GRPC_XDS_BOOTSTRAP_CONFIG"; @@ -104,7 +111,62 @@ protected String getJsonContent() throws XdsInitializationException, IOException protected Object getImplSpecificConfig(Map serverConfig, String serverUri) throws XdsInitializationException { ConfiguredChannelCredentials configuredChannel = getChannelCredentials(serverConfig, serverUri); - return configuredChannel != null ? configuredChannel.channelCredentials() : null; + ChannelCredentials channelCredentials = configuredChannel != null + ? configuredChannel.channelCredentials() : null; + + CallCredentials callCredentials = null; + List rawCallCreds = JsonUtil.getList(serverConfig, "call_creds"); + if (enableXdsBootstrapCallCreds && rawCallCreds != null) { + List> callCredsList = JsonUtil.checkObjectList(rawCallCreds); + callCredentials = parseCallCredentials(callCredsList, serverUri); + } + + ImmutableMap.Builder builder = ImmutableMap.builder(); + if (channelCredentials != null) { + builder.put("grpc.channel_credentials", channelCredentials); + } + if (callCredentials != null) { + builder.put("grpc.call_credentials", callCredentials); + } + return builder.buildOrThrow(); + } + + @Nullable + private CallCredentials parseCallCredentials(List> jsonList, String serverUri) + throws XdsInitializationException { + List parsedCreds = new ArrayList<>(); + for (Map credJson : jsonList) { + String type = JsonUtil.getString(credJson, "type"); + if (type == null) { + throw new XdsInitializationException( + "Invalid bootstrap: server " + serverUri + " with 'call_creds' type unspecified"); + } + if ("jwt_token_file".equals(type)) { + Map config = JsonUtil.getObject(credJson, "config"); + if (config == null) { + throw new XdsInitializationException( + "Invalid bootstrap: server " + serverUri + " with 'jwt_token_file' config missing"); + } + String jwtTokenFile = JsonUtil.getString(config, "jwt_token_file"); + if (jwtTokenFile == null || jwtTokenFile.isEmpty()) { + throw new XdsInitializationException( + "Invalid bootstrap: server " + serverUri + + " with 'jwt_token_file' jwt_token_file missing or empty"); + } + parsedCreds.add(new JwtTokenFileCallCredentials(jwtTokenFile)); + } else { + logger.log(XdsLogger.XdsLogLevel.INFO, + "Skipping unsupported call credential type: {0}", type); + } + } + if (parsedCreds.isEmpty()) { + return null; + } + CallCredentials combined = parsedCreds.get(0); + for (int i = 1; i < parsedCreds.size(); i++) { + combined = new CompositeCallCredentials(combined, parsedCreds.get(i)); + } + return combined; } @GuardedBy("GrpcBootstrapperImpl.class") @@ -194,8 +256,8 @@ protected Optional parseImplSpecificObject( Optional callCredentials = Optional.empty(); List rawCallCredsList = JsonUtil.getList(serviceConfig, "call_creds"); if (rawCallCredsList != null && !rawCallCredsList.isEmpty()) { - callCredentials = - parseCallCredentials(JsonUtil.checkObjectList(rawCallCredsList), targetUri); + callCredentials = Optional.ofNullable( + parseCallCredentials(JsonUtil.checkObjectList(rawCallCredsList), targetUri)); } AllowedGrpcService.Builder b = AllowedGrpcService.builder() @@ -208,16 +270,7 @@ protected Optional parseImplSpecificObject( return Optional.of(customConfig); } - @SuppressWarnings("unused") - private static Optional parseCallCredentials(List> jsonList, - String targetUri) - throws XdsInitializationException { - // TODO(sauravzg): Currently no xDS call credentials providers are implemented (no - // XdsCallCredentialsRegistry). - // As per A102/A97, we should just ignore unsupported call credentials types - // without throwing an exception. - return Optional.empty(); - } + private static final class JsonChannelCredsConfig implements ChannelCredsConfig { private final String type; diff --git a/xds/src/main/java/io/grpc/xds/GrpcXdsTransportFactory.java b/xds/src/main/java/io/grpc/xds/GrpcXdsTransportFactory.java index 13ff70432ed..ec6e1460d65 100644 --- a/xds/src/main/java/io/grpc/xds/GrpcXdsTransportFactory.java +++ b/xds/src/main/java/io/grpc/xds/GrpcXdsTransportFactory.java @@ -34,6 +34,7 @@ import io.grpc.Status; import io.grpc.xds.client.Bootstrapper; import io.grpc.xds.client.XdsTransportFactory; +import java.util.Map; import java.util.concurrent.TimeUnit; final class GrpcXdsTransportFactory implements XdsTransportFactory { @@ -80,7 +81,14 @@ public GrpcXdsTransport(Bootstrapper.ServerInfo serverInfo, CallCredentials callCredentials, ChannelConfigurator channelConfigurator) { String target = serverInfo.target(); - ChannelCredentials channelCredentials = (ChannelCredentials) serverInfo.implSpecificConfig(); + Object implConfig = serverInfo.implSpecificConfig(); + ChannelCredentials channelCredentials = null; + CallCredentials serverCallCredentials = null; + if (implConfig instanceof Map) { + Map configMap = (Map) implConfig; + channelCredentials = (ChannelCredentials) configMap.get("grpc.channel_credentials"); + serverCallCredentials = (CallCredentials) configMap.get("grpc.call_credentials"); + } ManagedChannelBuilder channelBuilder = Grpc.newChannelBuilder(target, channelCredentials) .keepAliveTime(5, TimeUnit.MINUTES); if (channelConfigurator != null) { @@ -88,11 +96,11 @@ public GrpcXdsTransport(Bootstrapper.ServerInfo serverInfo, channelBuilder.childChannelConfigurator(channelConfigurator); } this.channel = channelBuilder.build(); - if (callCredentials != null && serverInfo.callCredentials() != null) { + if (callCredentials != null && serverCallCredentials != null) { this.callCredentials = new CompositeCallCredentials( - callCredentials, serverInfo.callCredentials()); - } else if (serverInfo.callCredentials() != null) { - this.callCredentials = serverInfo.callCredentials(); + callCredentials, serverCallCredentials); + } else if (serverCallCredentials != null) { + this.callCredentials = serverCallCredentials; } else { this.callCredentials = callCredentials; } diff --git a/xds/src/main/java/io/grpc/xds/client/JwtTokenFileCallCredentials.java b/xds/src/main/java/io/grpc/xds/JwtTokenFileCallCredentials.java similarity index 99% rename from xds/src/main/java/io/grpc/xds/client/JwtTokenFileCallCredentials.java rename to xds/src/main/java/io/grpc/xds/JwtTokenFileCallCredentials.java index bb58ef85c01..beaa8b44d09 100644 --- a/xds/src/main/java/io/grpc/xds/client/JwtTokenFileCallCredentials.java +++ b/xds/src/main/java/io/grpc/xds/JwtTokenFileCallCredentials.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.client; +package io.grpc.xds; import static com.google.common.base.Preconditions.checkNotNull; diff --git a/xds/src/main/java/io/grpc/xds/client/Bootstrapper.java b/xds/src/main/java/io/grpc/xds/client/Bootstrapper.java index 843c6f3f3c5..b8d6444e3b3 100644 --- a/xds/src/main/java/io/grpc/xds/client/Bootstrapper.java +++ b/xds/src/main/java/io/grpc/xds/client/Bootstrapper.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.grpc.CallCredentials; import io.grpc.Internal; import io.grpc.xds.client.EnvoyProtoData.Node; import java.util.List; @@ -69,23 +68,20 @@ public abstract static class ServerInfo { public abstract boolean failOnDataErrors(); - @Nullable public abstract CallCredentials callCredentials(); - @VisibleForTesting public static ServerInfo create(String target, @Nullable Object implSpecificConfig) { return new AutoValue_Bootstrapper_ServerInfo(target, implSpecificConfig, - false, false, false, false, null); + false, false, false, false); } @VisibleForTesting public static ServerInfo create( String target, Object implSpecificConfig, boolean ignoreResourceDeletion, boolean isTrustedXdsServer, - boolean resourceTimerIsTransientError, boolean failOnDataErrors, - @Nullable CallCredentials callCredentials) { + boolean resourceTimerIsTransientError, boolean failOnDataErrors) { return new AutoValue_Bootstrapper_ServerInfo(target, implSpecificConfig, ignoreResourceDeletion, isTrustedXdsServer, - resourceTimerIsTransientError, failOnDataErrors, callCredentials); + resourceTimerIsTransientError, failOnDataErrors); } } diff --git a/xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java b/xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java index cdbb1fd6c2d..f78eae70452 100644 --- a/xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java +++ b/xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java @@ -19,8 +19,6 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import io.grpc.CallCredentials; -import io.grpc.CompositeCallCredentials; import io.grpc.Internal; import io.grpc.InternalLogId; import io.grpc.internal.GrpcUtil; @@ -33,7 +31,6 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -68,9 +65,6 @@ public abstract class BootstrapperImpl extends Bootstrapper { @VisibleForTesting static boolean enableXdsFallback = GrpcUtil.getFlag(GRPC_EXPERIMENTAL_XDS_FALLBACK, true); - @VisibleForTesting - public static boolean enableXdsBootstrapCallCreds = GrpcUtil.getFlag( - "GRPC_EXPERIMENTAL_XDS_BOOTSTRAP_CALL_CREDS", false); @VisibleForTesting public static boolean xdsDataErrorHandlingEnabled @@ -290,58 +284,15 @@ private List parseServerInfos(List rawServerConfigs, XdsLogger lo failOnDataErrors = xdsDataErrorHandlingEnabled && serverFeatures.contains(SERVER_FEATURE_FAIL_ON_DATA_ERRORS); } - CallCredentials callCredentials = null; - List rawCallCreds = JsonUtil.getList(serverConfig, "call_creds"); - if (enableXdsBootstrapCallCreds && rawCallCreds != null) { - List> callCredsList = JsonUtil.checkObjectList(rawCallCreds); - callCredentials = parseCallCredentials(callCredsList, serverUri); - } servers.add( ServerInfo.create(serverUri, implSpecificConfig, ignoreResourceDeletion, serverFeatures != null && serverFeatures.contains(SERVER_FEATURE_TRUSTED_XDS_SERVER), - resourceTimerIsTransientError, failOnDataErrors, callCredentials)); + resourceTimerIsTransientError, failOnDataErrors)); } return servers.build(); } - @Nullable - private CallCredentials parseCallCredentials(List> jsonList, String serverUri) - throws XdsInitializationException { - List parsedCreds = new ArrayList<>(); - for (Map credJson : jsonList) { - String type = JsonUtil.getString(credJson, "type"); - if (type == null) { - throw new XdsInitializationException( - "Invalid bootstrap: server " + serverUri + " with 'call_creds' type unspecified"); - } - if ("jwt_token_file".equals(type)) { - Map config = JsonUtil.getObject(credJson, "config"); - if (config == null) { - throw new XdsInitializationException( - "Invalid bootstrap: server " + serverUri + " with 'jwt_token_file' config missing"); - } - String jwtTokenFile = JsonUtil.getString(config, "jwt_token_file"); - if (jwtTokenFile == null || jwtTokenFile.isEmpty()) { - throw new XdsInitializationException( - "Invalid bootstrap: server " + serverUri - + " with 'jwt_token_file' jwt_token_file missing or empty"); - } - parsedCreds.add(new JwtTokenFileCallCredentials(jwtTokenFile)); - } else { - logger.log(XdsLogLevel.INFO, "Skipping unsupported call credential type: {0}", type); - } - } - if (parsedCreds.isEmpty()) { - return null; - } - CallCredentials combined = parsedCreds.get(0); - for (int i = 1; i < parsedCreds.size(); i++) { - combined = new CompositeCallCredentials(combined, parsedCreds.get(i)); - } - return combined; - } - @VisibleForTesting public void setFileReader(FileReader reader) { this.reader = reader; diff --git a/xds/src/test/java/io/grpc/xds/ExtAuthzConfigParserTest.java b/xds/src/test/java/io/grpc/xds/ExtAuthzConfigParserTest.java index 3340586e3ea..6072ee66bbe 100644 --- a/xds/src/test/java/io/grpc/xds/ExtAuthzConfigParserTest.java +++ b/xds/src/test/java/io/grpc/xds/ExtAuthzConfigParserTest.java @@ -65,7 +65,7 @@ private static BootstrapInfo dummyBootstrapInfo() { private static ServerInfo dummyServerInfo() { return ServerInfo.create( - "test_target", Collections.emptyMap(), false, true, false, false, null); + "test_target", Collections.emptyMap(), false, true, false, false); } private ExtAuthz.Builder extAuthzBuilder; diff --git a/xds/src/test/java/io/grpc/xds/ExternalProcessorClientInterceptorTest.java b/xds/src/test/java/io/grpc/xds/ExternalProcessorClientInterceptorTest.java index cd6de138a48..2e8761214a3 100644 --- a/xds/src/test/java/io/grpc/xds/ExternalProcessorClientInterceptorTest.java +++ b/xds/src/test/java/io/grpc/xds/ExternalProcessorClientInterceptorTest.java @@ -246,7 +246,7 @@ public void setUp() throws Exception { serverInfo = Bootstrapper.ServerInfo.create( - "test_target", Collections.emptyMap(), false, true, false, false, null); + "test_target", Collections.emptyMap(), false, true, false, false); filterContext = Filter.FilterConfigParseContext.builder() .bootstrapInfo(bootstrapInfo) diff --git a/xds/src/test/java/io/grpc/xds/ExternalProcessorFilterTest.java b/xds/src/test/java/io/grpc/xds/ExternalProcessorFilterTest.java index db42217d08c..74853f1edd0 100644 --- a/xds/src/test/java/io/grpc/xds/ExternalProcessorFilterTest.java +++ b/xds/src/test/java/io/grpc/xds/ExternalProcessorFilterTest.java @@ -77,7 +77,7 @@ public void setUp() throws Exception { serverInfo = Bootstrapper.ServerInfo.create( - "test_target", Collections.emptyMap(), false, true, false, false, null); + "test_target", Collections.emptyMap(), false, true, false, false); filterContext = Filter.FilterConfigParseContext.builder() .bootstrapInfo(bootstrapInfo) diff --git a/xds/src/test/java/io/grpc/xds/FaultFilterTest.java b/xds/src/test/java/io/grpc/xds/FaultFilterTest.java index 630a8f8bd2b..9033d1e636e 100644 --- a/xds/src/test/java/io/grpc/xds/FaultFilterTest.java +++ b/xds/src/test/java/io/grpc/xds/FaultFilterTest.java @@ -114,7 +114,7 @@ private static Filter.FilterConfigParseContext getFilterContext() { .node(Node.newBuilder().build()) .build()) .serverInfo(ServerInfo.create( - "test_target", Collections.emptyMap(), false, true, false, false, null)) + "test_target", Collections.emptyMap(), false, true, false, false)) .build(); } } diff --git a/xds/src/test/java/io/grpc/xds/GcpAuthenticationFilterTest.java b/xds/src/test/java/io/grpc/xds/GcpAuthenticationFilterTest.java index a53335a7c9b..11745c01fc2 100644 --- a/xds/src/test/java/io/grpc/xds/GcpAuthenticationFilterTest.java +++ b/xds/src/test/java/io/grpc/xds/GcpAuthenticationFilterTest.java @@ -525,7 +525,7 @@ private static Filter.FilterConfigParseContext getFilterContext() { .node(Node.newBuilder().build()) .build()) .serverInfo(ServerInfo.create( - "test_target", Collections.emptyMap(), false, true, false, false, null)) + "test_target", Collections.emptyMap(), false, true, false, false)) .build(); } } diff --git a/xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java b/xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java index eaef5a424d6..bb5d51eb472 100644 --- a/xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java +++ b/xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java @@ -24,6 +24,9 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; +import io.grpc.CallCredentials; +import io.grpc.ChannelCredentials; +import io.grpc.CompositeCallCredentials; import io.grpc.InsecureChannelCredentials; import io.grpc.TlsChannelCredentials; import io.grpc.internal.GrpcUtil; @@ -183,7 +186,7 @@ public void parseBootstrap_singleXdsServer() throws XdsInitializationException { assertThat(info.servers()).hasSize(1); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); + assertThat(getChannelCredentials(serverInfo)).isInstanceOf(InsecureChannelCredentials.class); assertThat(info.node()).isEqualTo( getNodeBuilder() .setId("ENVOY_NODE_ID") @@ -236,11 +239,11 @@ public void parseBootstrap_multipleXdsServers() throws XdsInitializationExceptio List serverInfoList = info.servers(); assertThat(serverInfoList.get(0).target()) .isEqualTo("trafficdirector-foo.googleapis.com:443"); - assertThat(serverInfoList.get(0).implSpecificConfig()) + assertThat(getChannelCredentials(serverInfoList.get(0))) .isInstanceOf(TlsChannelCredentials.class); assertThat(serverInfoList.get(1).target()) .isEqualTo("trafficdirector-bar.googleapis.com:443"); - assertThat(serverInfoList.get(1).implSpecificConfig()) + assertThat(getChannelCredentials(serverInfoList.get(1))) .isInstanceOf(InsecureChannelCredentials.class); assertThat(info.node()).isEqualTo( getNodeBuilder() @@ -285,7 +288,7 @@ public void parseBootstrap_IgnoreIrrelevantFields() throws XdsInitializationExce assertThat(info.servers()).hasSize(1); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); + assertThat(getChannelCredentials(serverInfo)).isInstanceOf(InsecureChannelCredentials.class); assertThat(info.node()).isEqualTo( getNodeBuilder() .setId("ENVOY_NODE_ID") @@ -356,7 +359,7 @@ public void parseBootstrap_useFirstSupportedChannelCredentials() assertThat(info.servers()).hasSize(1); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); + assertThat(getChannelCredentials(serverInfo)).isInstanceOf(InsecureChannelCredentials.class); assertThat(info.node()).isEqualTo(getNodeBuilder().build()); } @@ -664,7 +667,7 @@ public void useV2ProtocolByDefault() throws XdsInitializationException { BootstrapInfo info = bootstrapper.bootstrap(); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); + assertThat(getChannelCredentials(serverInfo)).isInstanceOf(InsecureChannelCredentials.class); assertThat(serverInfo.ignoreResourceDeletion()).isFalse(); } @@ -686,7 +689,7 @@ public void useV3ProtocolIfV3FeaturePresent() throws XdsInitializationException BootstrapInfo info = bootstrapper.bootstrap(); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); + assertThat(getChannelCredentials(serverInfo)).isInstanceOf(InsecureChannelCredentials.class); assertThat(serverInfo.ignoreResourceDeletion()).isFalse(); } @@ -708,7 +711,7 @@ public void serverFeatureIgnoreResourceDeletion() throws XdsInitializationExcept BootstrapInfo info = bootstrapper.bootstrap(); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); + assertThat(getChannelCredentials(serverInfo)).isInstanceOf(InsecureChannelCredentials.class); // Only ignore_resource_deletion feature enabled: confirm it's on, and xds_v3 is off. assertThat(serverInfo.ignoreResourceDeletion()).isTrue(); } @@ -731,7 +734,7 @@ public void serverFeatureTrustedXdsServer() throws XdsInitializationException { BootstrapInfo info = bootstrapper.bootstrap(); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); + assertThat(getChannelCredentials(serverInfo)).isInstanceOf(InsecureChannelCredentials.class); assertThat(serverInfo.isTrustedXdsServer()).isTrue(); } @@ -753,7 +756,7 @@ public void serverFeatureIgnoreResourceDeletion_xdsV3() throws XdsInitialization BootstrapInfo info = bootstrapper.bootstrap(); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); + assertThat(getChannelCredentials(serverInfo)).isInstanceOf(InsecureChannelCredentials.class); // ignore_resource_deletion features enabled: confirm both are on. assertThat(serverInfo.ignoreResourceDeletion()).isTrue(); } @@ -797,7 +800,7 @@ public void serverFeature_failOnDataErrors() throws XdsInitializationException { BootstrapInfo info = bootstrapper.bootstrap(); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); + assertThat(getChannelCredentials(serverInfo)).isInstanceOf(InsecureChannelCredentials.class); assertThat(serverInfo.failOnDataErrors()).isTrue(); BootstrapperImpl.xdsDataErrorHandlingEnabled = false; } @@ -1059,4 +1062,270 @@ private static Node.Builder getNodeBuilder() { .addClientFeatures(GrpcBootstrapperImpl.CLIENT_FEATURE_RESOURCE_IN_SOTW); } + private static String getFilePath(JwtTokenFileCallCredentials credentials) { + try { + java.lang.reflect.Field field = + JwtTokenFileCallCredentials.class.getDeclaredField("filePath"); + field.setAccessible(true); + return (String) field.get(credentials); + } catch (Exception e) { + throw new AssertionError(e); + } + } + + @Test + public void parseBootstrap_callCreds_flagDisabled() throws Exception { + GrpcBootstrapperImpl.enableXdsBootstrapCallCreds = false; + try { + String rawData = "{\n" + + " \"xds_servers\": [\n" + + " {\n" + + " \"server_uri\": \"" + SERVER_URI + "\",\n" + + " \"channel_creds\": [{\"type\": \"insecure\"}],\n" + + " \"call_creds\": [\n" + + " {\n" + + " \"type\": \"jwt_token_file\",\n" + + " \"config\": {\n" + + " \"jwt_token_file\": \"/var/run/secrets/token\"\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); + BootstrapInfo info = bootstrapper.bootstrap(); + assertThat(info.servers()).hasSize(1); + ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); + assertThat(getCallCredentials(serverInfo)).isNull(); + } finally { + GrpcBootstrapperImpl.enableXdsBootstrapCallCreds = false; + } + } + + @Test + public void parseBootstrap_xdsServers_jwtTokenFileCallCreds() throws Exception { + GrpcBootstrapperImpl.enableXdsBootstrapCallCreds = true; + try { + String rawData = "{\n" + + " \"xds_servers\": [\n" + + " {\n" + + " \"server_uri\": \"" + SERVER_URI + "\",\n" + + " \"channel_creds\": [{\"type\": \"insecure\"}],\n" + + " \"call_creds\": [\n" + + " {\n" + + " \"type\": \"jwt_token_file\",\n" + + " \"config\": {\n" + + " \"jwt_token_file\": \"/var/run/secrets/token\"\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); + BootstrapInfo info = bootstrapper.bootstrap(); + assertThat(info.servers()).hasSize(1); + ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); + assertThat(getCallCredentials(serverInfo)) + .isInstanceOf(JwtTokenFileCallCredentials.class); + assertThat(getFilePath((JwtTokenFileCallCredentials) getCallCredentials(serverInfo))) + .isEqualTo("/var/run/secrets/token"); + } finally { + GrpcBootstrapperImpl.enableXdsBootstrapCallCreds = false; + } + } + + @Test + public void parseBootstrap_authorities_jwtTokenFileCallCreds() throws Exception { + GrpcBootstrapperImpl.enableXdsBootstrapCallCreds = true; + try { + String rawData = "{\n" + + " \"authorities\": {\n" + + " \"a.com\": {\n" + + " \"xds_servers\": [\n" + + " {\n" + + " \"server_uri\": \"td2.googleapis.com:443\",\n" + + " \"channel_creds\": [\n" + + " {\"type\": \"insecure\"}\n" + + " ],\n" + + " \"call_creds\": [\n" + + " {\n" + + " \"type\": \"jwt_token_file\",\n" + + " \"config\": {\n" + + " \"jwt_token_file\": \"/var/run/secrets/authority_token\"\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + " }\n" + + " },\n" + + " \"xds_servers\": [\n" + + " {\n" + + " \"server_uri\": \"" + SERVER_URI + "\",\n" + + " \"channel_creds\": [\n" + + " {\"type\": \"insecure\"}\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); + BootstrapInfo info = bootstrapper.bootstrap(); + assertThat(info.authorities()).hasSize(1); + AuthorityInfo authorityInfo = info.authorities().get("a.com"); + assertThat(authorityInfo.xdsServers()).hasSize(1); + ServerInfo serverInfo = authorityInfo.xdsServers().get(0); + assertThat(getCallCredentials(serverInfo)) + .isInstanceOf(JwtTokenFileCallCredentials.class); + assertThat(getFilePath((JwtTokenFileCallCredentials) getCallCredentials(serverInfo))) + .isEqualTo("/var/run/secrets/authority_token"); + } finally { + GrpcBootstrapperImpl.enableXdsBootstrapCallCreds = false; + } + } + + @Test + public void parseBootstrap_unsupportedCallCredsType_ignored() throws Exception { + GrpcBootstrapperImpl.enableXdsBootstrapCallCreds = true; + try { + String rawData = "{\n" + + " \"xds_servers\": [\n" + + " {\n" + + " \"server_uri\": \"" + SERVER_URI + "\",\n" + + " \"channel_creds\": [{\"type\": \"insecure\"}],\n" + + " \"call_creds\": [\n" + + " {\n" + + " \"type\": \"unsupported_type\",\n" + + " \"config\": {\n" + + " \"some_field\": \"some_val\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"type\": \"jwt_token_file\",\n" + + " \"config\": {\n" + + " \"jwt_token_file\": \"/var/run/secrets/token\"\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); + BootstrapInfo info = bootstrapper.bootstrap(); + assertThat(info.servers()).hasSize(1); + ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); + assertThat(getCallCredentials(serverInfo)) + .isInstanceOf(JwtTokenFileCallCredentials.class); + assertThat(getFilePath((JwtTokenFileCallCredentials) getCallCredentials(serverInfo))) + .isEqualTo("/var/run/secrets/token"); + } finally { + GrpcBootstrapperImpl.enableXdsBootstrapCallCreds = false; + } + } + + @Test + public void parseBootstrap_malformedCallCreds_throws() throws Exception { + GrpcBootstrapperImpl.enableXdsBootstrapCallCreds = true; + try { + String rawData = "{\n" + + " \"xds_servers\": [\n" + + " {\n" + + " \"server_uri\": \"" + SERVER_URI + "\",\n" + + " \"channel_creds\": [{\"type\": \"insecure\"}],\n" + + " \"call_creds\": [\n" + + " {\n" + + " \"type\": \"jwt_token_file\",\n" + + " \"config\": {}\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); + Assert.assertThrows(XdsInitializationException.class, bootstrapper::bootstrap); + } finally { + GrpcBootstrapperImpl.enableXdsBootstrapCallCreds = false; + } + } + + @Test + public void parseBootstrap_xdsServers_multipleValidCallCreds() throws Exception { + GrpcBootstrapperImpl.enableXdsBootstrapCallCreds = true; + try { + String rawData = "{\n" + + " \"xds_servers\": [\n" + + " {\n" + + " \"server_uri\": \"" + SERVER_URI + "\",\n" + + " \"channel_creds\": [{\"type\": \"insecure\"}],\n" + + " \"call_creds\": [\n" + + " {\n" + + " \"type\": \"jwt_token_file\",\n" + + " \"config\": { \"jwt_token_file\": \"/var/run/secrets/token1\" }\n" + + " },\n" + + " {\n" + + " \"type\": \"jwt_token_file\",\n" + + " \"config\": { \"jwt_token_file\": \"/var/run/secrets/token2\" }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); + BootstrapInfo info = bootstrapper.bootstrap(); + assertThat(info.servers()).hasSize(1); + ServerInfo serverInfo = info.servers().get(0); + CallCredentials creds = getCallCredentials(serverInfo); + assertThat(creds).isNotNull(); + assertThat(creds).isInstanceOf(CompositeCallCredentials.class); + } finally { + GrpcBootstrapperImpl.enableXdsBootstrapCallCreds = false; + } + } + + @Test + public void parseBootstrap_xdsServers_missingTypeCallCreds() throws Exception { + GrpcBootstrapperImpl.enableXdsBootstrapCallCreds = true; + try { + String rawData = "{\n" + + " \"xds_servers\": [\n" + + " {\n" + + " \"server_uri\": \"" + SERVER_URI + "\",\n" + + " \"channel_creds\": [{\"type\": \"insecure\"}],\n" + + " \"call_creds\": [\n" + + " {\n" + + " \"config\": { \"jwt_token_file\": \"/var/run/secrets/token\" }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); + try { + bootstrapper.bootstrap(); + fail("Expected exception"); + } catch (XdsInitializationException e) { + assertThat(e).hasMessageThat().contains("with 'call_creds' type unspecified"); + } + } finally { + GrpcBootstrapperImpl.enableXdsBootstrapCallCreds = false; + } + } + + private static ChannelCredentials getChannelCredentials(ServerInfo serverInfo) { + Object config = serverInfo.implSpecificConfig(); + if (config instanceof Map) { + return (ChannelCredentials) ((Map) config).get("grpc.channel_credentials"); + } + return (ChannelCredentials) config; + } + + private static CallCredentials getCallCredentials(ServerInfo serverInfo) { + Object config = serverInfo.implSpecificConfig(); + if (config instanceof Map) { + return (CallCredentials) ((Map) config).get("grpc.call_credentials"); + } + return null; + } + } diff --git a/xds/src/test/java/io/grpc/xds/GrpcServiceConfigParserTest.java b/xds/src/test/java/io/grpc/xds/GrpcServiceConfigParserTest.java index 56b153502f1..7dd4bb7cb67 100644 --- a/xds/src/test/java/io/grpc/xds/GrpcServiceConfigParserTest.java +++ b/xds/src/test/java/io/grpc/xds/GrpcServiceConfigParserTest.java @@ -80,7 +80,7 @@ private static ServerInfo dummyServerInfo() { private static ServerInfo dummyServerInfo(boolean isTrusted) { return ServerInfo.create("test_target", Collections.emptyMap(), false, isTrusted, false, - false, null); + false); } private static GrpcServiceConfig parse( diff --git a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplDataTest.java b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplDataTest.java index bb13d155843..46f2c0f1d65 100644 --- a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplDataTest.java +++ b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplDataTest.java @@ -169,7 +169,8 @@ public class GrpcXdsClientImplDataTest { private static final RouterFilter.Provider ROUTER_FILTER_PROVIDER = new RouterFilter.Provider(); private static final ServerInfo LRS_SERVER_INFO = - ServerInfo.create("lrs.googleapis.com", InsecureChannelCredentials.create()); + ServerInfo.create("lrs.googleapis.com", + ImmutableMap.of("grpc.channel_credentials", InsecureChannelCredentials.create())); private static final String GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE = "GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE"; @@ -3628,7 +3629,9 @@ private static Filter buildHttpConnectionManagerFilter(HttpFilter... httpFilters private XdsResourceType.Args getXdsResourceTypeArgs(boolean isTrustedServer) { return new XdsResourceType.Args( - ServerInfo.create("http://td", "", false, isTrustedServer, false, false, null), "1.0", null, XdsTestUtils.EMPTY_BOOTSTRAP, null, null + ServerInfo.create("http://td", Collections.emptyMap(), false, isTrustedServer, + false, false), + "1.0", null, XdsTestUtils.EMPTY_BOOTSTRAP, null, null ); } } diff --git a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java index 9381aab2e76..64bb994609f 100644 --- a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java +++ b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java @@ -365,8 +365,8 @@ public void setUp() throws IOException { channel = cleanupRule.register(InProcessChannelBuilder.forName(serverName).directExecutor().build()); - xdsServerInfo = ServerInfo.create(SERVER_URI, CHANNEL_CREDENTIALS, ignoreResourceDeletion(), - true, false, false, null); + xdsServerInfo = createServerInfo(SERVER_URI, CHANNEL_CREDENTIALS, ignoreResourceDeletion(), + true, false, false); BootstrapInfo bootstrapInfo = Bootstrapper.BootstrapInfo.builder() .servers(Collections.singletonList(xdsServerInfo)) @@ -375,12 +375,12 @@ public void setUp() throws IOException { "authority.xds.com", AuthorityInfo.create( "xdstp://authority.xds.com/envoy.config.listener.v3.Listener/%s", - ImmutableList.of(Bootstrapper.ServerInfo.create( + ImmutableList.of(createServerInfo( SERVER_URI_CUSTOM_AUTHORITY, CHANNEL_CREDENTIALS))), "", AuthorityInfo.create( "xdstp:///envoy.config.listener.v3.Listener/%s", - ImmutableList.of(Bootstrapper.ServerInfo.create( + ImmutableList.of(createServerInfo( SERVER_URI_EMPTY_AUTHORITY, CHANNEL_CREDENTIALS))))) .certProviders(ImmutableMap.of("cert-instance-name", CertificateProviderInfo.create("file-watcher", ImmutableMap.of()))) @@ -1556,8 +1556,8 @@ public void ldsResourceDeleted_ignoreResourceDeletion() { @Test public void ldsResourceDeleted_failOnDataErrors_true() { BootstrapperImpl.xdsDataErrorHandlingEnabled = true; - xdsServerInfo = ServerInfo.create(SERVER_URI, CHANNEL_CREDENTIALS, false, - true, false, true, null); + xdsServerInfo = createServerInfo(SERVER_URI, CHANNEL_CREDENTIALS, false, + true, false, true); BootstrapInfo bootstrapInfo = Bootstrapper.BootstrapInfo.builder() .servers(Collections.singletonList(xdsServerInfo)) @@ -1566,7 +1566,7 @@ public void ldsResourceDeleted_failOnDataErrors_true() { "", AuthorityInfo.create( "xdstp:///envoy.config.listener.v3.Listener/%s", - ImmutableList.of(Bootstrapper.ServerInfo.create( + ImmutableList.of(createServerInfo( SERVER_URI_EMPTY_AUTHORITY, CHANNEL_CREDENTIALS))))) .certProviders(ImmutableMap.of()) .build(); @@ -1617,8 +1617,8 @@ public void ldsResourceDeleted_failOnDataErrors_true() { public void ldsResourceDeleted_failOnDataErrors_false() { BootstrapperImpl.xdsDataErrorHandlingEnabled = true; - xdsServerInfo = ServerInfo.create(SERVER_URI, CHANNEL_CREDENTIALS, false, - true, false, false, null); + xdsServerInfo = createServerInfo(SERVER_URI, CHANNEL_CREDENTIALS, false, + true, false, false); BootstrapInfo bootstrapInfo = Bootstrapper.BootstrapInfo.builder() .servers(Collections.singletonList(xdsServerInfo)) @@ -1627,7 +1627,7 @@ public void ldsResourceDeleted_failOnDataErrors_false() { "", AuthorityInfo.create( "xdstp:///envoy.config.listener.v3.Listener/%s", - ImmutableList.of(Bootstrapper.ServerInfo.create( + ImmutableList.of(createServerInfo( SERVER_URI_EMPTY_AUTHORITY, CHANNEL_CREDENTIALS))))) .certProviders(ImmutableMap.of()) .build(); @@ -1680,8 +1680,8 @@ public void ldsResourceDeleted_failOnDataErrors_false() { public void ldsResourceDeleted_failOnDataErrorsIgnoredWithoutEnvVar() { BootstrapperImpl.xdsDataErrorHandlingEnabled = false; - xdsServerInfo = ServerInfo.create(SERVER_URI, CHANNEL_CREDENTIALS, false, - true, false, true, null); + xdsServerInfo = createServerInfo(SERVER_URI, CHANNEL_CREDENTIALS, false, + true, false, true); BootstrapInfo bootstrapInfo = Bootstrapper.BootstrapInfo.builder() .servers(Collections.singletonList(xdsServerInfo)) @@ -1690,7 +1690,7 @@ public void ldsResourceDeleted_failOnDataErrorsIgnoredWithoutEnvVar() { "", AuthorityInfo.create( "xdstp:///envoy.config.listener.v3.Listener/%s", - ImmutableList.of(Bootstrapper.ServerInfo.create( + ImmutableList.of(createServerInfo( SERVER_URI_EMPTY_AUTHORITY, CHANNEL_CREDENTIALS))))) .certProviders(ImmutableMap.of()) .build(); @@ -3243,8 +3243,8 @@ public void cdsResourceDeleted_ignoreResourceDeletion() { @Test public void cdsResourceDeleted_failOnDataErrors_true() { BootstrapperImpl.xdsDataErrorHandlingEnabled = true; - xdsServerInfo = ServerInfo.create(SERVER_URI, CHANNEL_CREDENTIALS, false, - true, false, true, null); + xdsServerInfo = createServerInfo(SERVER_URI, CHANNEL_CREDENTIALS, false, + true, false, true); BootstrapInfo bootstrapInfo = Bootstrapper.BootstrapInfo.builder() .servers(Collections.singletonList(xdsServerInfo)) @@ -3253,7 +3253,7 @@ public void cdsResourceDeleted_failOnDataErrors_true() { "", AuthorityInfo.create( "xdstp:///envoy.config.listener.v3.Listener/%s", - ImmutableList.of(Bootstrapper.ServerInfo.create( + ImmutableList.of(createServerInfo( SERVER_URI_EMPTY_AUTHORITY, CHANNEL_CREDENTIALS))))) .certProviders(ImmutableMap.of()) .build(); @@ -3302,8 +3302,8 @@ public void cdsResourceDeleted_failOnDataErrors_true() { public void cdsResourceDeleted_failOnDataErrors_false() { BootstrapperImpl.xdsDataErrorHandlingEnabled = true; // Set failOnDataErrors to false for this test case. - xdsServerInfo = ServerInfo.create(SERVER_URI, CHANNEL_CREDENTIALS, false, - true, false, false, null); + xdsServerInfo = createServerInfo(SERVER_URI, CHANNEL_CREDENTIALS, false, + true, false, false); BootstrapInfo bootstrapInfo = Bootstrapper.BootstrapInfo.builder() .servers(Collections.singletonList(xdsServerInfo)) @@ -3312,7 +3312,7 @@ public void cdsResourceDeleted_failOnDataErrors_false() { "", AuthorityInfo.create( "xdstp:///envoy.config.listener.v3.Listener/%s", - ImmutableList.of(Bootstrapper.ServerInfo.create( + ImmutableList.of(createServerInfo( SERVER_URI_EMPTY_AUTHORITY, CHANNEL_CREDENTIALS))))) .certProviders(ImmutableMap.of()) .build(); @@ -3369,8 +3369,8 @@ public void cdsResourceDeleted_failOnDataErrors_false() { @Test public void ldsResourceNacked_withFailOnDataErrors_dropsResource() { BootstrapperImpl.xdsDataErrorHandlingEnabled = true; - xdsServerInfo = ServerInfo.create(SERVER_URI, CHANNEL_CREDENTIALS, false, - true, false, true, null); + xdsServerInfo = createServerInfo(SERVER_URI, CHANNEL_CREDENTIALS, false, + true, false, true); BootstrapInfo bootstrapInfo = Bootstrapper.BootstrapInfo.builder() .servers(Collections.singletonList(xdsServerInfo)) @@ -3418,8 +3418,8 @@ public void ldsResourceNacked_withFailOnDataErrors_dropsResource() { @Test public void ldsResourceNacked_withFailOnDataErrorsDisabled_isAmbientError() { BootstrapperImpl.xdsDataErrorHandlingEnabled = true; - xdsServerInfo = ServerInfo.create(SERVER_URI, CHANNEL_CREDENTIALS, false, - true, false, false, null); + xdsServerInfo = createServerInfo(SERVER_URI, CHANNEL_CREDENTIALS, false, + true, false, false); BootstrapInfo bootstrapInfo = Bootstrapper.BootstrapInfo.builder() .servers(Collections.singletonList(xdsServerInfo)) @@ -3851,8 +3851,8 @@ public void flowControlAbsent() throws Exception { @Test public void resourceTimerIsTransientError_schedulesExtendedTimeout() { BootstrapperImpl.xdsDataErrorHandlingEnabled = true; - ServerInfo serverInfo = ServerInfo.create(SERVER_URI, CHANNEL_CREDENTIALS, - false, true, true, false, null); + ServerInfo serverInfo = createServerInfo(SERVER_URI, CHANNEL_CREDENTIALS, + false, true, true, false); BootstrapInfo bootstrapInfo = Bootstrapper.BootstrapInfo.builder() .servers(Collections.singletonList(serverInfo)) @@ -3861,7 +3861,7 @@ public void resourceTimerIsTransientError_schedulesExtendedTimeout() { "", AuthorityInfo.create( "xdstp:///envoy.config.listener.v3.Listener/%s", - ImmutableList.of(Bootstrapper.ServerInfo.create( + ImmutableList.of(createServerInfo( SERVER_URI_EMPTY_AUTHORITY, CHANNEL_CREDENTIALS))))) .certProviders(ImmutableMap.of()) .build(); @@ -3896,8 +3896,8 @@ public void resourceTimerIsTransientError_schedulesExtendedTimeout() { @Test public void resourceTimerIsTransientError_callsOnErrorUnavailable() { BootstrapperImpl.xdsDataErrorHandlingEnabled = true; - xdsServerInfo = ServerInfo.create(SERVER_URI, CHANNEL_CREDENTIALS, ignoreResourceDeletion(), - true, true, false, null); + xdsServerInfo = createServerInfo(SERVER_URI, CHANNEL_CREDENTIALS, ignoreResourceDeletion(), + true, true, false); BootstrapInfo bootstrapInfo = Bootstrapper.BootstrapInfo.builder() .servers(Collections.singletonList(xdsServerInfo)) @@ -3906,12 +3906,12 @@ public void resourceTimerIsTransientError_callsOnErrorUnavailable() { "authority.xds.com", AuthorityInfo.create( "xdstp://authority.xds.com/envoy.config.listener.v3.Listener/%s", - ImmutableList.of(Bootstrapper.ServerInfo.create( + ImmutableList.of(createServerInfo( SERVER_URI_CUSTOM_AUTHORITY, CHANNEL_CREDENTIALS))), "", AuthorityInfo.create( "xdstp:///envoy.config.listener.v3.Listener/%s", - ImmutableList.of(Bootstrapper.ServerInfo.create( + ImmutableList.of(createServerInfo( SERVER_URI_EMPTY_AUTHORITY, CHANNEL_CREDENTIALS))))) .certProviders(ImmutableMap.of("cert-instance-name", CertificateProviderInfo.create("file-watcher", ImmutableMap.of()))) @@ -5126,8 +5126,8 @@ private XdsClientImpl createXdsClient(String serverUri) { private BootstrapInfo buildBootStrap(String serverUri) { - ServerInfo xdsServerInfo = ServerInfo.create(serverUri, CHANNEL_CREDENTIALS, - ignoreResourceDeletion(), true, false, false, null); + ServerInfo xdsServerInfo = createServerInfo(serverUri, CHANNEL_CREDENTIALS, + ignoreResourceDeletion(), true, false, false); return Bootstrapper.BootstrapInfo.builder() .servers(Collections.singletonList(xdsServerInfo)) @@ -5136,12 +5136,12 @@ private BootstrapInfo buildBootStrap(String serverUri) { "authority.xds.com", AuthorityInfo.create( "xdstp://authority.xds.com/envoy.config.listener.v3.Listener/%s", - ImmutableList.of(Bootstrapper.ServerInfo.create( + ImmutableList.of(createServerInfo( SERVER_URI_CUSTOM_AUTHORITY, CHANNEL_CREDENTIALS))), "", AuthorityInfo.create( "xdstp:///envoy.config.listener.v3.Listener/%s", - ImmutableList.of(Bootstrapper.ServerInfo.create( + ImmutableList.of(createServerInfo( SERVER_URI_EMPTY_AUTHORITY, CHANNEL_CREDENTIALS))))) .certProviders(ImmutableMap.of("cert-instance-name", CertificateProviderInfo.create("file-watcher", ImmutableMap.of()))) @@ -5429,4 +5429,15 @@ public int hashCode() { return Objects.hash(value); } } + + private static ServerInfo createServerInfo(String target, ChannelCredentials credentials) { + return ServerInfo.create(target, ImmutableMap.of("grpc.channel_credentials", credentials)); + } + + private static ServerInfo createServerInfo(String target, ChannelCredentials credentials, + boolean ignoreResourceDeletion, boolean useModernBootstrap, boolean enableFallback, + boolean failoverToNextServer) { + return ServerInfo.create(target, ImmutableMap.of("grpc.channel_credentials", credentials), + ignoreResourceDeletion, useModernBootstrap, enableFallback, failoverToNextServer); + } } diff --git a/xds/src/test/java/io/grpc/xds/GrpcXdsTransportFactoryTest.java b/xds/src/test/java/io/grpc/xds/GrpcXdsTransportFactoryTest.java index 484b081915b..7155565130e 100644 --- a/xds/src/test/java/io/grpc/xds/GrpcXdsTransportFactoryTest.java +++ b/xds/src/test/java/io/grpc/xds/GrpcXdsTransportFactoryTest.java @@ -22,6 +22,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.SettableFuture; import io.envoyproxy.envoy.service.discovery.v3.AggregatedDiscoveryServiceGrpc; import io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest; @@ -31,6 +32,7 @@ import io.grpc.CallOptions; import io.grpc.Channel; import io.grpc.ChannelConfigurator; +import io.grpc.ChannelCredentials; import io.grpc.ClientCall; import io.grpc.ClientInterceptor; import io.grpc.CompositeCallCredentials; @@ -113,7 +115,7 @@ public void callApis() throws Exception { XdsTransportFactory.XdsTransport xdsTransport = new GrpcXdsTransportFactory(null, null) .create( - Bootstrapper.ServerInfo.create( + createServerInfo( "localhost:" + server.getPort(), InsecureChannelCredentials.create())); MethodDescriptor methodDescriptor = AggregatedDiscoveryServiceGrpc.getStreamAggregatedResourcesMethod(); @@ -188,7 +190,7 @@ public void configureChannelBuilder(ManagedChannelBuilder builder) { // Create Transport XdsTransportFactory.XdsTransport transport = factory.create( - Bootstrapper.ServerInfo.create("localhost:8080", InsecureChannelCredentials.create())); + createServerInfo("localhost:8080", InsecureChannelCredentials.create())); // Create a Call to trigger interceptors MethodDescriptor method = MethodDescriptor.newBuilder() @@ -224,7 +226,7 @@ public void configureChannelBuilder(ManagedChannelBuilder builder) { // Create Transport (triggers channel creation) XdsTransportFactory.XdsTransport transport = factory.create( - Bootstrapper.ServerInfo.create("localhost:8080", InsecureChannelCredentials.create())); + createServerInfo("localhost:8080", InsecureChannelCredentials.create())); // Verify Configurer was accessed and applied assertThat(called[0]).isTrue(); @@ -267,7 +269,7 @@ protected int priority() { }; GrpcXdsTransportFactory factory = new GrpcXdsTransportFactory(null, configurer); XdsTransportFactory.XdsTransport transport = factory.create( - Bootstrapper.ServerInfo.create( + createServerInfo( "test-xds-transport://localhost:8080", InsecureChannelCredentials.create())); assertNotNull(capturedArgs.get()); ChannelConfigurator childConfigurator = capturedArgs.get().getChildChannelConfigurator(); @@ -295,7 +297,7 @@ public void configureChannelBuilder(ManagedChannelBuilder builder) { try { factory.create( - Bootstrapper.ServerInfo.create("localhost:8080", InsecureChannelCredentials.create())); + createServerInfo("localhost:8080", InsecureChannelCredentials.create())); org.junit.Assert.fail("Expected RuntimeException"); } catch (RuntimeException e) { assertThat(e).isSameInstanceAs(testException); @@ -375,8 +377,11 @@ public void createTransport_combinesCallCredentials() throws Exception { // 1. Both factory and server callCredentials are non-null GrpcXdsTransportFactory factoryBoth = new GrpcXdsTransportFactory(factoryCreds, null); Bootstrapper.ServerInfo serverInfoBoth = Bootstrapper.ServerInfo.create( - "localhost:8080", InsecureChannelCredentials.create(), - false, false, false, false, serverCreds); + "localhost:8080", + ImmutableMap.of( + "grpc.channel_credentials", InsecureChannelCredentials.create(), + "grpc.call_credentials", serverCreds), + false, false, false, false); XdsTransportFactory.XdsTransport transportBoth = factoryBoth.create(serverInfoBoth); CallCredentials combined = getCallCredentials(transportBoth); assertThat(combined).isInstanceOf(CompositeCallCredentials.class); @@ -387,7 +392,7 @@ public void createTransport_combinesCallCredentials() throws Exception { // 2. Server credentials are null -> resolves to factory credentials GrpcXdsTransportFactory factoryOnly = new GrpcXdsTransportFactory(factoryCreds, null); - Bootstrapper.ServerInfo serverInfoNoCreds = Bootstrapper.ServerInfo.create( + Bootstrapper.ServerInfo serverInfoNoCreds = createServerInfo( "localhost:8080", InsecureChannelCredentials.create()); XdsTransportFactory.XdsTransport transportFactoryOnly = factoryOnly.create(serverInfoNoCreds); assertThat(getCallCredentials(transportFactoryOnly)).isSameInstanceAs(factoryCreds); @@ -396,11 +401,20 @@ public void createTransport_combinesCallCredentials() throws Exception { // 3. Factory credentials are null -> resolves to server credentials GrpcXdsTransportFactory factoryNone = new GrpcXdsTransportFactory(null, null); Bootstrapper.ServerInfo serverInfoWithCreds = Bootstrapper.ServerInfo.create( - "localhost:8080", InsecureChannelCredentials.create(), - false, false, false, false, serverCreds); + "localhost:8080", + ImmutableMap.of( + "grpc.channel_credentials", InsecureChannelCredentials.create(), + "grpc.call_credentials", serverCreds), + false, false, false, false); XdsTransportFactory.XdsTransport transportServerOnly = factoryNone.create(serverInfoWithCreds); assertThat(getCallCredentials(transportServerOnly)).isSameInstanceAs(serverCreds); transportServerOnly.shutdown(); } + + private static Bootstrapper.ServerInfo createServerInfo( + String target, ChannelCredentials credentials) { + return Bootstrapper.ServerInfo.create( + target, ImmutableMap.of("grpc.channel_credentials", credentials)); + } } diff --git a/xds/src/test/java/io/grpc/xds/client/JwtTokenFileCallCredentialsTest.java b/xds/src/test/java/io/grpc/xds/JwtTokenFileCallCredentialsTest.java similarity index 99% rename from xds/src/test/java/io/grpc/xds/client/JwtTokenFileCallCredentialsTest.java rename to xds/src/test/java/io/grpc/xds/JwtTokenFileCallCredentialsTest.java index 1575618613c..c01ee420b47 100644 --- a/xds/src/test/java/io/grpc/xds/client/JwtTokenFileCallCredentialsTest.java +++ b/xds/src/test/java/io/grpc/xds/JwtTokenFileCallCredentialsTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds.client; +package io.grpc.xds; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; diff --git a/xds/src/test/java/io/grpc/xds/RbacFilterTest.java b/xds/src/test/java/io/grpc/xds/RbacFilterTest.java index 18074dfabf8..4680f570327 100644 --- a/xds/src/test/java/io/grpc/xds/RbacFilterTest.java +++ b/xds/src/test/java/io/grpc/xds/RbacFilterTest.java @@ -483,7 +483,7 @@ private Filter.FilterConfigParseContext getFilterContext() { .node(Node.newBuilder().build()) .build()) .serverInfo(ServerInfo.create( - "test_target", Collections.emptyMap(), false, true, false, false, null)) + "test_target", Collections.emptyMap(), false, true, false, false)) .build(); } } diff --git a/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java b/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java index 15887ff3d26..7e192d3709d 100644 --- a/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java @@ -29,6 +29,7 @@ import com.google.common.util.concurrent.SettableFuture; import io.grpc.CallCredentials; import io.grpc.ChannelConfigurator; +import io.grpc.ChannelCredentials; import io.grpc.ClientInterceptor; import io.grpc.Grpc; import io.grpc.InsecureChannelCredentials; @@ -80,7 +81,7 @@ public class SharedXdsClientPoolProviderTest { @Deprecated @Test public void sharedXdsClientObjectPool_deprecated() throws XdsInitializationException { - ServerInfo server = ServerInfo.create(SERVER_URI, InsecureChannelCredentials.create()); + ServerInfo server = createServerInfo(SERVER_URI, InsecureChannelCredentials.create()); BootstrapInfo bootstrapInfo = BootstrapInfo.builder().servers(Collections.singletonList(server)).node(node).build(); when(bootstrapper.bootstrap()).thenReturn(bootstrapInfo); @@ -152,7 +153,7 @@ public void refCountedXdsClientObjectPool_refCounted() { @Test public void refCountedXdsClientObjectPool_getObjectCreatesNewInstanceIfAlreadyShutdown() { - ServerInfo server = ServerInfo.create(SERVER_URI, InsecureChannelCredentials.create()); + ServerInfo server = createServerInfo(SERVER_URI, InsecureChannelCredentials.create()); BootstrapInfo bootstrapInfo = BootstrapInfo.builder().servers(Collections.singletonList(server)).node(node).build(); SharedXdsClientPoolProvider provider = new SharedXdsClientPoolProvider(); @@ -198,7 +199,7 @@ public void xdsClient_usesCallCredentials() throws Exception { String xdsServerUri = "localhost:" + xdsServer.getPort(); // Set up bootstrap & xDS client pool provider - ServerInfo server = ServerInfo.create(xdsServerUri, InsecureChannelCredentials.create()); + ServerInfo server = createServerInfo(xdsServerUri, InsecureChannelCredentials.create()); BootstrapInfo bootstrapInfo = BootstrapInfo.builder().servers(Collections.singletonList(server)).node(node).build(); SharedXdsClientPoolProvider provider = new SharedXdsClientPoolProvider(); @@ -239,7 +240,7 @@ public void xdsClient_usesChannelConfigurator() throws Exception { String xdsServerUri = "localhost:" + xdsServer.getPort(); // Set up bootstrap & xDS client pool provider - ServerInfo server = ServerInfo.create(xdsServerUri, InsecureChannelCredentials.create()); + ServerInfo server = createServerInfo(xdsServerUri, InsecureChannelCredentials.create()); BootstrapInfo bootstrapInfo = BootstrapInfo.builder().servers(Collections.singletonList(server)).node(node).build(); SharedXdsClientPoolProvider provider = new SharedXdsClientPoolProvider(); @@ -285,4 +286,9 @@ public void configureChannelBuilder(ManagedChannelBuilder builder) { xdsClientPool.returnObject(xdsClient); xdsServer.shutdownNow(); } + + private static ServerInfo createServerInfo(String target, ChannelCredentials credentials) { + return ServerInfo.create( + target, Collections.singletonMap("grpc.channel_credentials", credentials)); + } } diff --git a/xds/src/test/java/io/grpc/xds/XdsClientFallbackTest.java b/xds/src/test/java/io/grpc/xds/XdsClientFallbackTest.java index 4d5e7d09ad4..d54ff5e215e 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientFallbackTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientFallbackTest.java @@ -381,8 +381,14 @@ public void connect_then_mainServerDown_fallbackServerUp() throws Exception { XdsTransportFactory xdsTransportFactory = new XdsTransportFactory() { @Override public XdsTransport create(Bootstrapper.ServerInfo serverInfo) { - ChannelCredentials channelCredentials = - (ChannelCredentials) serverInfo.implSpecificConfig(); + Object config = serverInfo.implSpecificConfig(); + ChannelCredentials channelCredentials; + if (config instanceof Map) { + channelCredentials = (ChannelCredentials) + ((Map) config).get("grpc.channel_credentials"); + } else { + channelCredentials = (ChannelCredentials) config; + } return new GrpcXdsTransportFactory.GrpcXdsTransport( Grpc.newChannelBuilder(serverInfo.target(), channelCredentials) .executor(executor) diff --git a/xds/src/test/java/io/grpc/xds/XdsJwtCallCredsIntegrationTest.java b/xds/src/test/java/io/grpc/xds/XdsJwtCallCredsIntegrationTest.java index 1c3ab37c7c0..3d990cd053e 100644 --- a/xds/src/test/java/io/grpc/xds/XdsJwtCallCredsIntegrationTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsJwtCallCredsIntegrationTest.java @@ -203,7 +203,7 @@ public void configureChannelBuilder(ManagedChannelBuilder builder) { } private static void setEnableXdsBootstrapCallCreds(boolean enable) { - io.grpc.xds.client.BootstrapperImpl.enableXdsBootstrapCallCreds = enable; + GrpcBootstrapperImpl.enableXdsBootstrapCallCreds = enable; } private static Path generateTrustStore() throws Exception { diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java index 1a92598aaf3..fb54e516e72 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java @@ -48,6 +48,7 @@ import io.grpc.CallOptions; import io.grpc.Channel; import io.grpc.ChannelConfigurator; +import io.grpc.ChannelCredentials; import io.grpc.ChannelLogger; import io.grpc.ClientCall; import io.grpc.ClientInterceptor; @@ -198,8 +199,8 @@ public ConfigOrError parseServiceConfig(Map rawServiceConfig) { )); private BootstrapInfo bootstrapInfo = BootstrapInfo.builder() - .servers(ImmutableList.of(ServerInfo.create( - "td.googleapis.com", InsecureChannelCredentials.create()))) + .servers(ImmutableList.of( + createServerInfo("td.googleapis.com", InsecureChannelCredentials.create()))) .node(Node.newBuilder().build()) .build(); private String expectedLdsResourceName = AUTHORITY; @@ -297,8 +298,8 @@ public void resolving_withTargetAuthorityNotFound() { @Test public void resolving_noTargetAuthority_templateWithoutXdstp() { bootstrapInfo = BootstrapInfo.builder() - .servers(ImmutableList.of(ServerInfo.create( - "td.googleapis.com", InsecureChannelCredentials.create()))) + .servers(ImmutableList.of( + createServerInfo("td.googleapis.com", InsecureChannelCredentials.create()))) .node(Node.newBuilder().build()) .clientDefaultListenerResourceNameTemplate("%s/id=1") .build(); @@ -319,7 +320,7 @@ public void resolving_emptyTargetAuthority_templateWithXdstp() { BootstrapInfo.builder() .servers( ImmutableList.of( - ServerInfo.create("td.googleapis.com", InsecureChannelCredentials.create()))) + createServerInfo("td.googleapis.com", InsecureChannelCredentials.create()))) .node(Node.newBuilder().build()) .clientDefaultListenerResourceNameTemplate( "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/%s?id=1") @@ -350,8 +351,8 @@ public void resolving_emptyTargetAuthority_templateWithXdstp() { @Test public void resolving_noTargetAuthority_templateWithXdstp() { bootstrapInfo = BootstrapInfo.builder() - .servers(ImmutableList.of(ServerInfo.create( - "td.googleapis.com", InsecureChannelCredentials.create()))) + .servers(ImmutableList.of( + createServerInfo("td.googleapis.com", InsecureChannelCredentials.create()))) .node(Node.newBuilder().build()) .clientDefaultListenerResourceNameTemplate( "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/%s?id=1") @@ -371,8 +372,8 @@ public void resolving_noTargetAuthority_templateWithXdstp() { @Test public void resolving_noTargetAuthority_xdstpWithMultipleSlashes() { bootstrapInfo = BootstrapInfo.builder() - .servers(ImmutableList.of(ServerInfo.create( - "td.googleapis.com", InsecureChannelCredentials.create()))) + .servers(ImmutableList.of( + createServerInfo("td.googleapis.com", InsecureChannelCredentials.create()))) .node(Node.newBuilder().build()) .clientDefaultListenerResourceNameTemplate( "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/%s?id=1") @@ -400,15 +401,14 @@ public void resolving_targetAuthorityInAuthoritiesMap() { String serviceAuthority = "[::FFFF:129.144.52.38]:80"; bootstrapInfo = BootstrapInfo.builder() .servers(ImmutableList.of(ServerInfo.create( - "td.googleapis.com", InsecureChannelCredentials.create(), true, true, false, false, - null))) + "td.googleapis.com", InsecureChannelCredentials.create(), true, true, false, false))) .node(Node.newBuilder().build()) .authorities( ImmutableMap.of(targetAuthority, AuthorityInfo.create( "xdstp://" + targetAuthority + "/envoy.config.listener.v3.Listener/%s?foo=1&bar=2", ImmutableList.of(ServerInfo.create( "td.googleapis.com", InsecureChannelCredentials.create(), - true, true, false, false, null))))) + true, true, false, false))))) .build(); expectedLdsResourceName = "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/" + "%5B::FFFF:129.144.52.38%5D:80?bar=2&foo=1"; // query param canonified @@ -440,8 +440,8 @@ public void resolving_ldsResourceUpdateRdsName() { cluster2, Collections.emptyList(), TimeUnit.SECONDS.toNanos(20L), null, false), ImmutableMap.of()); bootstrapInfo = BootstrapInfo.builder() - .servers(ImmutableList.of(ServerInfo.create( - "td.googleapis.com", InsecureChannelCredentials.create()))) + .servers(ImmutableList.of( + createServerInfo("td.googleapis.com", InsecureChannelCredentials.create()))) .clientDefaultListenerResourceNameTemplate("test-%s") .node(Node.newBuilder().build()) .build(); @@ -3098,4 +3098,8 @@ public void onMessage(String message) { channel, METHOD_SAY_HELLO, CallOptions.DEFAULT, "World"); assertThat(response).isEqualTo("Hello World"); } + + private static ServerInfo createServerInfo(String target, ChannelCredentials credentials) { + return ServerInfo.create(target, ImmutableMap.of("grpc.channel_credentials", credentials)); + } } diff --git a/xds/src/test/java/io/grpc/xds/XdsTestUtils.java b/xds/src/test/java/io/grpc/xds/XdsTestUtils.java index 8229c5fef94..290f4d4cfc9 100644 --- a/xds/src/test/java/io/grpc/xds/XdsTestUtils.java +++ b/xds/src/test/java/io/grpc/xds/XdsTestUtils.java @@ -86,7 +86,9 @@ public class XdsTestUtils { + ".HttpConnectionManager"; static final Bootstrapper.ServerInfo EMPTY_BOOTSTRAPPER_SERVER_INFO = Bootstrapper.ServerInfo.create( - "td.googleapis.com", InsecureChannelCredentials.create(), false, true, false, false, null); + "td.googleapis.com", + ImmutableMap.of("grpc.channel_credentials", InsecureChannelCredentials.create()), + false, true, false, false); static final Bootstrapper.BootstrapInfo EMPTY_BOOTSTRAP = Bootstrapper.BootstrapInfo.builder() .servers(com.google.common.collect.ImmutableList.of(EMPTY_BOOTSTRAPPER_SERVER_INFO)) diff --git a/xds/src/test/java/io/grpc/xds/client/BootstrapperImplTest.java b/xds/src/test/java/io/grpc/xds/client/BootstrapperImplTest.java deleted file mode 100644 index bda036a4be1..00000000000 --- a/xds/src/test/java/io/grpc/xds/client/BootstrapperImplTest.java +++ /dev/null @@ -1,341 +0,0 @@ -/* - * Copyright 2026 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.client; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertThrows; -import static org.junit.Assert.fail; - -import com.google.common.collect.Iterables; -import io.grpc.CallCredentials; -import io.grpc.CompositeCallCredentials; -import io.grpc.xds.client.Bootstrapper.AuthorityInfo; -import io.grpc.xds.client.Bootstrapper.BootstrapInfo; -import io.grpc.xds.client.Bootstrapper.ServerInfo; -import java.io.IOException; -import java.util.Map; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -/** Unit tests for {@link BootstrapperImpl}. */ -@RunWith(JUnit4.class) -public class BootstrapperImplTest { - - private static final String BOOTSTRAP_FILE_PATH = "/fake/fs/path/bootstrap.json"; - private static final String SERVER_URI = "trafficdirector.googleapis.com:443"; - - private TestBootstrapperImpl bootstrapper; - private boolean originalExperimentalXdsFallbackFlag; - - @Before - public void setUp() { - originalExperimentalXdsFallbackFlag = BootstrapperImpl.enableXdsFallback; - BootstrapperImpl.enableXdsFallback = true; - } - - @After - public void tearDown() { - BootstrapperImpl.enableXdsFallback = originalExperimentalXdsFallbackFlag; - } - - private static class TestBootstrapperImpl extends BootstrapperImpl { - private final String jsonContent; - - TestBootstrapperImpl(String jsonContent) { - this.jsonContent = jsonContent; - } - - @Override - protected String getJsonContent() { - return jsonContent; - } - - @Override - protected Object getImplSpecificConfig(Map serverConfig, String serverUri) { - return "dummy-config"; - } - } - - private static String getFilePath(JwtTokenFileCallCredentials credentials) { - try { - java.lang.reflect.Field field = - JwtTokenFileCallCredentials.class.getDeclaredField("filePath"); - field.setAccessible(true); - return (String) field.get(credentials); - } catch (Exception e) { - throw new AssertionError(e); - } - } - - private static BootstrapperImpl.FileReader createFileReader( - final String expectedPath, final String rawData) { - return new BootstrapperImpl.FileReader() { - @Override - public String readFile(String path) throws IOException { - assertThat(path).isEqualTo(expectedPath); - return rawData; - } - }; - } - - @Test - public void parseBootstrap_callCreds_flagDisabled() throws Exception { - BootstrapperImpl.enableXdsBootstrapCallCreds = false; - try { - String rawData = "{\n" - + " \"xds_servers\": [\n" - + " {\n" - + " \"server_uri\": \"" + SERVER_URI + "\",\n" - + " \"channel_creds\": [{\"type\": \"insecure\"}],\n" - + " \"call_creds\": [\n" - + " {\n" - + " \"type\": \"jwt_token_file\",\n" - + " \"config\": {\n" - + " \"jwt_token_file\": \"/var/run/secrets/token\"\n" - + " }\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + "}"; - bootstrapper = new TestBootstrapperImpl(rawData); - bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); - BootstrapInfo info = bootstrapper.bootstrap(); - assertThat(info.servers()).hasSize(1); - ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); - assertThat(serverInfo.callCredentials()).isNull(); - } finally { - BootstrapperImpl.enableXdsBootstrapCallCreds = false; - } - } - - @Test - public void parseBootstrap_xdsServers_jwtTokenFileCallCreds() throws Exception { - BootstrapperImpl.enableXdsBootstrapCallCreds = true; - try { - String rawData = "{\n" - + " \"xds_servers\": [\n" - + " {\n" - + " \"server_uri\": \"" + SERVER_URI + "\",\n" - + " \"channel_creds\": [{\"type\": \"insecure\"}],\n" - + " \"call_creds\": [\n" - + " {\n" - + " \"type\": \"jwt_token_file\",\n" - + " \"config\": {\n" - + " \"jwt_token_file\": \"/var/run/secrets/token\"\n" - + " }\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + "}"; - bootstrapper = new TestBootstrapperImpl(rawData); - bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); - BootstrapInfo info = bootstrapper.bootstrap(); - assertThat(info.servers()).hasSize(1); - ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); - assertThat(serverInfo.callCredentials()) - .isInstanceOf(JwtTokenFileCallCredentials.class); - assertThat(getFilePath((JwtTokenFileCallCredentials) serverInfo.callCredentials())) - .isEqualTo("/var/run/secrets/token"); - } finally { - BootstrapperImpl.enableXdsBootstrapCallCreds = false; - } - } - - @Test - public void parseBootstrap_authorities_jwtTokenFileCallCreds() throws Exception { - BootstrapperImpl.enableXdsBootstrapCallCreds = true; - try { - String rawData = "{\n" - + " \"authorities\": {\n" - + " \"a.com\": {\n" - + " \"xds_servers\": [\n" - + " {\n" - + " \"server_uri\": \"td2.googleapis.com:443\",\n" - + " \"channel_creds\": [\n" - + " {\"type\": \"insecure\"}\n" - + " ],\n" - + " \"call_creds\": [\n" - + " {\n" - + " \"type\": \"jwt_token_file\",\n" - + " \"config\": {\n" - + " \"jwt_token_file\": \"/var/run/secrets/authority_token\"\n" - + " }\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + " }\n" - + " },\n" - + " \"xds_servers\": [\n" - + " {\n" - + " \"server_uri\": \"" + SERVER_URI + "\",\n" - + " \"channel_creds\": [\n" - + " {\"type\": \"insecure\"}\n" - + " ]\n" - + " }\n" - + " ]\n" - + "}"; - bootstrapper = new TestBootstrapperImpl(rawData); - bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); - BootstrapInfo info = bootstrapper.bootstrap(); - assertThat(info.authorities()).hasSize(1); - AuthorityInfo authorityInfo = info.authorities().get("a.com"); - assertThat(authorityInfo.xdsServers()).hasSize(1); - ServerInfo serverInfo = authorityInfo.xdsServers().get(0); - assertThat(serverInfo.callCredentials()) - .isInstanceOf(JwtTokenFileCallCredentials.class); - assertThat(getFilePath((JwtTokenFileCallCredentials) serverInfo.callCredentials())) - .isEqualTo("/var/run/secrets/authority_token"); - } finally { - BootstrapperImpl.enableXdsBootstrapCallCreds = false; - } - } - - @Test - public void parseBootstrap_unsupportedCallCredsType_ignored() throws Exception { - BootstrapperImpl.enableXdsBootstrapCallCreds = true; - try { - String rawData = "{\n" - + " \"xds_servers\": [\n" - + " {\n" - + " \"server_uri\": \"" + SERVER_URI + "\",\n" - + " \"channel_creds\": [{\"type\": \"insecure\"}],\n" - + " \"call_creds\": [\n" - + " {\n" - + " \"type\": \"unsupported_type\",\n" - + " \"config\": {\n" - + " \"some_field\": \"some_val\"\n" - + " }\n" - + " },\n" - + " {\n" - + " \"type\": \"jwt_token_file\",\n" - + " \"config\": {\n" - + " \"jwt_token_file\": \"/var/run/secrets/token\"\n" - + " }\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + "}"; - bootstrapper = new TestBootstrapperImpl(rawData); - bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); - BootstrapInfo info = bootstrapper.bootstrap(); - assertThat(info.servers()).hasSize(1); - ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); - assertThat(serverInfo.callCredentials()) - .isInstanceOf(JwtTokenFileCallCredentials.class); - assertThat(getFilePath((JwtTokenFileCallCredentials) serverInfo.callCredentials())) - .isEqualTo("/var/run/secrets/token"); - } finally { - BootstrapperImpl.enableXdsBootstrapCallCreds = false; - } - } - - @Test - public void parseBootstrap_malformedCallCreds_throws() throws Exception { - BootstrapperImpl.enableXdsBootstrapCallCreds = true; - try { - String rawData = "{\n" - + " \"xds_servers\": [\n" - + " {\n" - + " \"server_uri\": \"" + SERVER_URI + "\",\n" - + " \"channel_creds\": [{\"type\": \"insecure\"}],\n" - + " \"call_creds\": [\n" - + " {\n" - + " \"type\": \"jwt_token_file\",\n" - + " \"config\": {}\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + "}"; - bootstrapper = new TestBootstrapperImpl(rawData); - bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); - assertThrows(XdsInitializationException.class, bootstrapper::bootstrap); - } finally { - BootstrapperImpl.enableXdsBootstrapCallCreds = false; - } - } - - @Test - public void parseBootstrap_xdsServers_multipleValidCallCreds() throws Exception { - BootstrapperImpl.enableXdsBootstrapCallCreds = true; - try { - String rawData = "{\n" - + " \"xds_servers\": [\n" - + " {\n" - + " \"server_uri\": \"" + SERVER_URI + "\",\n" - + " \"channel_creds\": [{\"type\": \"insecure\"}],\n" - + " \"call_creds\": [\n" - + " {\n" - + " \"type\": \"jwt_token_file\",\n" - + " \"config\": { \"jwt_token_file\": \"/var/run/secrets/token1\" }\n" - + " },\n" - + " {\n" - + " \"type\": \"jwt_token_file\",\n" - + " \"config\": { \"jwt_token_file\": \"/var/run/secrets/token2\" }\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + "}"; - bootstrapper = new TestBootstrapperImpl(rawData); - bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); - BootstrapInfo info = bootstrapper.bootstrap(); - assertThat(info.servers()).hasSize(1); - ServerInfo serverInfo = info.servers().get(0); - CallCredentials creds = serverInfo.callCredentials(); - assertThat(creds).isNotNull(); - assertThat(creds).isInstanceOf(CompositeCallCredentials.class); - } finally { - BootstrapperImpl.enableXdsBootstrapCallCreds = false; - } - } - - @Test - public void parseBootstrap_xdsServers_missingTypeCallCreds() throws Exception { - BootstrapperImpl.enableXdsBootstrapCallCreds = true; - try { - String rawData = "{\n" - + " \"xds_servers\": [\n" - + " {\n" - + " \"server_uri\": \"" + SERVER_URI + "\",\n" - + " \"channel_creds\": [{\"type\": \"insecure\"}],\n" - + " \"call_creds\": [\n" - + " {\n" - + " \"config\": { \"jwt_token_file\": \"/var/run/secrets/token\" }\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + "}"; - bootstrapper = new TestBootstrapperImpl(rawData); - bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); - bootstrapper.bootstrap(); - fail("Expected exception"); - } catch (XdsInitializationException e) { - assertThat(e).hasMessageThat().contains("with 'call_creds' type unspecified"); - } finally { - BootstrapperImpl.enableXdsBootstrapCallCreds = false; - } - } -} diff --git a/xds/src/test/java/io/grpc/xds/client/CommonBootstrapperTestUtils.java b/xds/src/test/java/io/grpc/xds/client/CommonBootstrapperTestUtils.java index 38df2ec74cb..26b4b58e32b 100644 --- a/xds/src/test/java/io/grpc/xds/client/CommonBootstrapperTestUtils.java +++ b/xds/src/test/java/io/grpc/xds/client/CommonBootstrapperTestUtils.java @@ -203,7 +203,9 @@ public static Bootstrapper.BootstrapInfo buildBootStrap(List serverUris) List serverInfos = new ArrayList<>(); for (String uri : serverUris) { - serverInfos.add(ServerInfo.create(uri, CHANNEL_CREDENTIALS, false, true, false, false, null)); + serverInfos.add(ServerInfo.create(uri, + ImmutableMap.of("grpc.channel_credentials", CHANNEL_CREDENTIALS), + false, true, false, false)); } EnvoyProtoData.Node node = EnvoyProtoData.Node.newBuilder().setId("node-id").build(); @@ -215,12 +217,14 @@ public static Bootstrapper.BootstrapInfo buildBootStrap(List serverUris) Bootstrapper.AuthorityInfo.create( "xdstp://authority.xds.com/envoy.config.listener.v3.Listener/%s", ImmutableList.of(Bootstrapper.ServerInfo.create( - SERVER_URI_CUSTOM_AUTHORITY, CHANNEL_CREDENTIALS))), + SERVER_URI_CUSTOM_AUTHORITY, + ImmutableMap.of("grpc.channel_credentials", CHANNEL_CREDENTIALS)))), "", Bootstrapper.AuthorityInfo.create( "xdstp:///envoy.config.listener.v3.Listener/%s", ImmutableList.of(Bootstrapper.ServerInfo.create( - SERVER_URI_EMPTY_AUTHORITY, CHANNEL_CREDENTIALS))))) + SERVER_URI_EMPTY_AUTHORITY, + ImmutableMap.of("grpc.channel_credentials", CHANNEL_CREDENTIALS)))))) .certProviders(ImmutableMap.of("cert-instance-name", Bootstrapper.CertificateProviderInfo.create("file-watcher", ImmutableMap.of()))) .build();