From f499ced28a562cbb3ea49f14a4fa16eb6a8173cc Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Tue, 2 Jan 2024 17:29:17 +0000 Subject: [PATCH] feat: Full Endpoint Resolution from EndpointContext (#2313) * feat: Full endpoint resolution * chore: Fix lint issues * chore: Address PR comments * chore: Do not set resolved universe domain for gdch * chore: Move helper methods to the Builder class * chore: Address PR comments * chore: Add javadocs for universe domain --- .../InstantiatingGrpcChannelProvider.java | 13 +- .../InstantiatingGrpcChannelProviderTest.java | 19 ++ gax-java/gax/clirr-ignored-differences.xml | 6 + .../com/google/api/gax/rpc/ClientContext.java | 35 ++-- .../google/api/gax/rpc/EndpointContext.java | 191 ++++++++++++----- .../com/google/api/gax/rpc/StubSettings.java | 17 ++ .../google/api/gax/rpc/ClientContextTest.java | 97 ++++++++- .../api/gax/rpc/EndpointContextTest.java | 195 ++++++++++++++++-- .../api/gax/rpc/testing/FakeStubSettings.java | 5 + 9 files changed, 489 insertions(+), 89 deletions(-) diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java index b92f9ca7a4..2703d13486 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java @@ -295,6 +295,10 @@ private void logDirectPathMisconfig() { Level.WARNING, "DirectPath is misconfigured. DirectPath is only available in a GCE environment."); } + if (!canUseDirectPathWithUniverseDomain()) { + LOG.log( + Level.WARNING, "DirectPath will only work in the the googleapis.com Universe Domain"); + } } } } @@ -325,6 +329,10 @@ static boolean isOnComputeEngine() { return false; } + private boolean canUseDirectPathWithUniverseDomain() { + return endpoint.contains("googleapis.com"); + } + @VisibleForTesting ChannelCredentials createMtlsChannelCredentials() throws IOException, GeneralSecurityException { if (mtlsProvider.useMtlsClientCertificate()) { @@ -356,7 +364,10 @@ private ManagedChannel createSingleChannel() throws IOException { // Check DirectPath traffic. boolean useDirectPathXds = false; - if (isDirectPathEnabled() && isNonDefaultServiceAccountAllowed() && isOnComputeEngine()) { + if (isDirectPathEnabled() + && isNonDefaultServiceAccountAllowed() + && isOnComputeEngine() + && canUseDirectPathWithUniverseDomain()) { CallCredentials callCreds = MoreCallCredentials.from(credentials); ChannelCredentials channelCreds = GoogleDefaultChannelCredentials.newBuilder().callCredentials(callCreds).build(); diff --git a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java index edd7a73768..9fdea5e398 100644 --- a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java +++ b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java @@ -290,6 +290,7 @@ public void testDirectPathXdsEnabled() throws IOException { InstantiatingGrpcChannelProvider.newBuilder() .setAttemptDirectPath(true) .setAttemptDirectPathXds() + .setEndpoint("test.googleapis.com:443") .build(); assertThat(provider.isDirectPathXdsEnabled()).isTrue(); @@ -528,6 +529,7 @@ public void testLogDirectPathMisconfigWrongCredential() { InstantiatingGrpcChannelProvider.newBuilder() .setAttemptDirectPathXds() .setAttemptDirectPath(true) + .setEndpoint("test.googleapis.com:443") .build(); assertThat(logHandler.getAllMessages()) .contains( @@ -545,6 +547,7 @@ public void testLogDirectPathMisconfigNotOnGCE() { .setAttemptDirectPathXds() .setAttemptDirectPath(true) .setAllowNonDefaultServiceAccount(true) + .setEndpoint("test.googleapis.com:443") .build(); if (!InstantiatingGrpcChannelProvider.isOnComputeEngine()) { assertThat(logHandler.getAllMessages()) @@ -554,6 +557,22 @@ public void testLogDirectPathMisconfigNotOnGCE() { InstantiatingGrpcChannelProvider.LOG.removeHandler(logHandler); } + @Test + public void testLogDirectPathMisconfigNotInGDU() { + FakeLogHandler logHandler = new FakeLogHandler(); + InstantiatingGrpcChannelProvider.LOG.addHandler(logHandler); + InstantiatingGrpcChannelProvider provider = + InstantiatingGrpcChannelProvider.newBuilder() + .setAttemptDirectPathXds() + .setAttemptDirectPath(true) + .setAllowNonDefaultServiceAccount(true) + .setEndpoint("test.random.endpoint.com:443") + .build(); + assertThat(logHandler.getAllMessages()) + .contains("DirectPath will only work in the the googleapis.com Universe Domain"); + InstantiatingGrpcChannelProvider.LOG.removeHandler(logHandler); + } + private static class FakeLogHandler extends Handler { List records = new ArrayList<>(); diff --git a/gax-java/gax/clirr-ignored-differences.xml b/gax-java/gax/clirr-ignored-differences.xml index 690b74ee71..1c4dd28a3c 100644 --- a/gax-java/gax/clirr-ignored-differences.xml +++ b/gax-java/gax/clirr-ignored-differences.xml @@ -25,4 +25,10 @@ com/google/api/gax/rpc/TransportChannelProvider * getEndpoint() + + + 7013 + com/google/api/gax/rpc/ClientContext* + * *UniverseDomain*(*) + diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java index 49c43c4060..e7fac9d0c6 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java @@ -104,6 +104,9 @@ public abstract class ClientContext { @Nullable abstract String getServiceName(); + @Nullable + public abstract String getUniverseDomain(); + @Nullable public abstract String getEndpoint(); @@ -157,15 +160,28 @@ public static ClientContext create(StubSettings settings) throws IOException { final ScheduledExecutorService backgroundExecutor = backgroundExecutorProvider.getExecutor(); Credentials credentials = settings.getCredentialsProvider().getCredentials(); + boolean usingGDCH = credentials instanceof GdchCredentials; + EndpointContext endpointContext = + EndpointContext.newBuilder() + .setServiceName(settings.getServiceName()) + .setUniverseDomain(settings.getUniverseDomain()) + .setClientSettingsEndpoint(settings.getEndpoint()) + .setTransportChannelProviderEndpoint( + settings.getTransportChannelProvider().getEndpoint()) + .setMtlsEndpoint(settings.getMtlsEndpoint()) + .setSwitchToMtlsEndpointAllowed(settings.getSwitchToMtlsEndpointAllowed()) + .setUsingGDCH(usingGDCH) + .build(); + String endpoint = endpointContext.resolvedEndpoint(); String settingsGdchApiAudience = settings.getGdchApiAudience(); - if (credentials instanceof GdchCredentials) { + if (usingGDCH) { // We recompute the GdchCredentials with the audience String audienceString; if (!Strings.isNullOrEmpty(settingsGdchApiAudience)) { audienceString = settingsGdchApiAudience; - } else if (!Strings.isNullOrEmpty(settings.getEndpoint())) { - audienceString = settings.getEndpoint(); + } else if (!Strings.isNullOrEmpty(endpoint)) { + audienceString = endpoint; } else { throw new IllegalArgumentException("Could not infer GDCH api audience from settings"); } @@ -204,16 +220,6 @@ public static ClientContext create(StubSettings settings) throws IOException { if (transportChannelProvider.needsCredentials() && credentials != null) { transportChannelProvider = transportChannelProvider.withCredentials(credentials); } - EndpointContext endpointContext = - EndpointContext.newBuilder() - .setServiceName(settings.getServiceName()) - .setClientSettingsEndpoint(settings.getEndpoint()) - .setTransportChannelProviderEndpoint( - settings.getTransportChannelProvider().getEndpoint()) - .setMtlsEndpoint(settings.getMtlsEndpoint()) - .setSwitchToMtlsEndpointAllowed(settings.getSwitchToMtlsEndpointAllowed()) - .build(); - String endpoint = endpointContext.getResolvedEndpoint(); if (transportChannelProvider.needsEndpoint()) { transportChannelProvider = transportChannelProvider.withEndpoint(endpoint); } @@ -264,6 +270,7 @@ public static ClientContext create(StubSettings settings) throws IOException { .setClock(clock) .setDefaultCallContext(defaultCallContext) .setServiceName(settings.getServiceName()) + .setUniverseDomain(settings.getUniverseDomain()) .setEndpoint(settings.getEndpoint()) .setQuotaProjectId(settings.getQuotaProjectId()) .setStreamWatchdog(watchdog) @@ -332,6 +339,8 @@ public abstract static class Builder { // Package-Private scope for internal use only. Shared between StubSettings and ClientContext abstract Builder setServiceName(String serviceName); + public abstract Builder setUniverseDomain(String universeDomain); + public abstract Builder setEndpoint(String endpoint); public abstract Builder setQuotaProjectId(String QuotaProjectId); diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java index 8f7a6eecaf..89100a06df 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java @@ -33,13 +33,16 @@ import com.google.api.gax.rpc.mtls.MtlsProvider; import com.google.auto.value.AutoValue; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; import java.io.IOException; import javax.annotation.Nullable; -/** Contains the fields required to resolve the endpoint */ +/** Contains the fields required to resolve the endpoint and Universe Domain */ @InternalApi @AutoValue public abstract class EndpointContext { + static final String GOOGLE_DEFAULT_UNIVERSE = "googleapis.com"; + /** * ServiceName is host URI for Google Cloud Services. It follows the format of * `{ServiceName}.googleapis.com`. For example, speech.googleapis.com would have a ServiceName of @@ -48,6 +51,15 @@ public abstract class EndpointContext { @Nullable public abstract String serviceName(); + /** + * Universe Domain is the domain for Google Cloud Services. It follows the format of + * `{ServiceName}.{UniverseDomain}`. For example, speech.googleapis.com would have a Universe + * Domain value of `googleapis.com` and cloudasset.test.com would have a Universe Domain of + * `test.com`. If this value is not set, this will default to `googleapis.com`. + */ + @Nullable + public abstract String universeDomain(); + /** * ClientSettingsEndpoint is the endpoint value set via the ClientSettings/StubSettings classes. */ @@ -69,58 +81,18 @@ public abstract class EndpointContext { @Nullable public abstract MtlsProvider mtlsProvider(); - public abstract Builder toBuilder(); + public abstract boolean usingGDCH(); - private String resolvedEndpoint; + abstract String resolvedUniverseDomain(); - public static Builder newBuilder() { - return new AutoValue_EndpointContext.Builder().setSwitchToMtlsEndpointAllowed(false); - } - - @VisibleForTesting - void determineEndpoint() throws IOException { - MtlsProvider mtlsProvider = mtlsProvider() == null ? new MtlsProvider() : mtlsProvider(); - String customEndpoint = - transportChannelProviderEndpoint() == null - ? clientSettingsEndpoint() - : transportChannelProviderEndpoint(); - resolvedEndpoint = - mtlsEndpointResolver( - customEndpoint, mtlsEndpoint(), switchToMtlsEndpointAllowed(), mtlsProvider); - } + public abstract String resolvedEndpoint(); - // This takes in parameters because determineEndpoint()'s logic will be updated - // to pass custom values in. - // Follows https://google.aip.dev/auth/4114 for resolving the endpoint - @VisibleForTesting - String mtlsEndpointResolver( - String endpoint, - String mtlsEndpoint, - boolean switchToMtlsEndpointAllowed, - MtlsProvider mtlsProvider) - throws IOException { - if (switchToMtlsEndpointAllowed && mtlsProvider != null) { - switch (mtlsProvider.getMtlsEndpointUsagePolicy()) { - case ALWAYS: - return mtlsEndpoint; - case NEVER: - return endpoint; - default: - if (mtlsProvider.useMtlsClientCertificate() && mtlsProvider.getKeyStore() != null) { - return mtlsEndpoint; - } - return endpoint; - } - } - return endpoint; - } + public abstract Builder toBuilder(); - /** - * The resolved endpoint is the computed endpoint after accounting for the custom endpoints and - * mTLS configurations. - */ - public String getResolvedEndpoint() { - return resolvedEndpoint; + public static Builder newBuilder() { + return new AutoValue_EndpointContext.Builder() + .setSwitchToMtlsEndpointAllowed(false) + .setUsingGDCH(false); } @AutoValue.Builder @@ -132,6 +104,14 @@ public abstract static class Builder { */ public abstract Builder setServiceName(String serviceName); + /** + * Universe Domain is the domain for Google Cloud Services. It follows the format of + * `{ServiceName}.{UniverseDomain}`. For example, speech.googleapis.com would have a Universe + * Domain value of `googleapis.com` and cloudasset.test.com would have a Universe Domain of + * `test.com`. If this value is not set, this will default to `googleapis.com`. + */ + public abstract Builder setUniverseDomain(String universeDomain); + /** * ClientSettingsEndpoint is the endpoint value set via the ClientSettings/StubSettings classes. */ @@ -149,12 +129,119 @@ public abstract static class Builder { public abstract Builder setMtlsProvider(MtlsProvider mtlsProvider); + public abstract Builder setUsingGDCH(boolean usingGDCH); + + public abstract Builder setResolvedEndpoint(String resolvedEndpoint); + + public abstract Builder setResolvedUniverseDomain(String resolvedUniverseDomain); + + abstract String serviceName(); + + abstract String universeDomain(); + + abstract String clientSettingsEndpoint(); + + abstract String transportChannelProviderEndpoint(); + + abstract String mtlsEndpoint(); + + abstract boolean switchToMtlsEndpointAllowed(); + + abstract MtlsProvider mtlsProvider(); + + abstract boolean usingGDCH(); + + abstract String resolvedUniverseDomain(); + abstract EndpointContext autoBuild(); + private String determineUniverseDomain() { + if (usingGDCH()) { + // GDC-H has no concept of Universe Domain. User should not set a custom value + if (universeDomain() != null) { + throw new IllegalArgumentException( + "Universe domain configuration is incompatible with GDC-H"); + } + return GOOGLE_DEFAULT_UNIVERSE; + } + // Check for "" (empty string) + if (universeDomain() != null && universeDomain().isEmpty()) { + throw new IllegalArgumentException("The universe domain value cannot be empty."); + } + // Override with user set universe domain if provided + return universeDomain() != null ? universeDomain() : GOOGLE_DEFAULT_UNIVERSE; + } + + /** Determines the fully resolved endpoint and universe domain values */ + private String determineEndpoint() throws IOException { + MtlsProvider mtlsProvider = mtlsProvider() == null ? new MtlsProvider() : mtlsProvider(); + // TransportChannelProvider's endpoint will override the ClientSettings' endpoint + String customEndpoint = + transportChannelProviderEndpoint() == null + ? clientSettingsEndpoint() + : transportChannelProviderEndpoint(); + + // GDC-H has a separate flow + if (usingGDCH()) { + if (customEndpoint == null) { + return buildEndpointTemplate(serviceName(), resolvedUniverseDomain()); + } + return customEndpoint; + } + + // If user does not provide a custom endpoint, build one with the universe domain + if (Strings.isNullOrEmpty(customEndpoint)) { + customEndpoint = buildEndpointTemplate(serviceName(), resolvedUniverseDomain()); + } + + String endpoint = + mtlsEndpointResolver( + customEndpoint, mtlsEndpoint(), switchToMtlsEndpointAllowed(), mtlsProvider); + + // Check if mTLS is configured with non-GDU + if (endpoint.equals(mtlsEndpoint()) + && !resolvedUniverseDomain().equals(GOOGLE_DEFAULT_UNIVERSE)) { + throw new IllegalArgumentException( + "mTLS is not supported in any universe other than googleapis.com"); + } + + return endpoint; + } + + // Default to port 443 for HTTPS. Using HTTP requires explicitly setting the endpoint + private String buildEndpointTemplate(String serviceName, String resolvedUniverseDomain) { + return serviceName + "." + resolvedUniverseDomain + ":443"; + } + + // Follows https://google.aip.dev/auth/4114 for resolving the endpoint + @VisibleForTesting + String mtlsEndpointResolver( + String endpoint, + String mtlsEndpoint, + boolean switchToMtlsEndpointAllowed, + MtlsProvider mtlsProvider) + throws IOException { + if (switchToMtlsEndpointAllowed && mtlsProvider != null) { + switch (mtlsProvider.getMtlsEndpointUsagePolicy()) { + case ALWAYS: + return mtlsEndpoint; + case NEVER: + return endpoint; + default: + if (mtlsProvider.useMtlsClientCertificate() && mtlsProvider.getKeyStore() != null) { + return mtlsEndpoint; + } + return endpoint; + } + } + return endpoint; + } + public EndpointContext build() throws IOException { - EndpointContext endpointContext = autoBuild(); - endpointContext.determineEndpoint(); - return endpointContext; + // The Universe Domain is used to resolve the Endpoint. It should be resolved first + setResolvedUniverseDomain(determineUniverseDomain()); + setResolvedEndpoint(determineEndpoint()); + return autoBuild(); } } } diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/StubSettings.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/StubSettings.java index f534dc07e1..f446fd3d11 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/StubSettings.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/StubSettings.java @@ -81,6 +81,7 @@ public abstract class StubSettings> { @Nonnull private final ApiTracerFactory tracerFactory; // Track if deprecated setExecutorProvider is called private boolean deprecatedExecutorProviderSet; + private final String universeDomain; /** * Indicate when creating transport whether it is allowed to use mTLS endpoint instead of the @@ -108,6 +109,7 @@ protected StubSettings(Builder builder) { this.tracerFactory = builder.tracerFactory; this.deprecatedExecutorProviderSet = builder.deprecatedExecutorProviderSet; this.gdchApiAudience = builder.gdchApiAudience; + this.universeDomain = builder.universeDomain; } /** @deprecated Please use {@link #getBackgroundExecutorProvider()}. */ @@ -147,6 +149,10 @@ public String getServiceName() { return ""; } + public final String getUniverseDomain() { + return universeDomain; + } + public final String getEndpoint() { return endpoint; } @@ -199,6 +205,7 @@ public String toString() { .add("headerProvider", headerProvider) .add("internalHeaderProvider", internalHeaderProvider) .add("clock", clock) + .add("universeDomain", universeDomain) .add("endpoint", endpoint) .add("mtlsEndpoint", mtlsEndpoint) .add("switchToMtlsEndpointAllowed", switchToMtlsEndpointAllowed) @@ -230,6 +237,7 @@ public abstract static class Builder< @Nonnull private Duration streamWatchdogCheckInterval; @Nonnull private ApiTracerFactory tracerFactory; private boolean deprecatedExecutorProviderSet; + private String universeDomain; /** * Indicate when creating transport whether it is allowed to use mTLS endpoint instead of the @@ -257,6 +265,7 @@ protected Builder(StubSettings settings) { this.tracerFactory = settings.tracerFactory; this.deprecatedExecutorProviderSet = settings.deprecatedExecutorProviderSet; this.gdchApiAudience = settings.gdchApiAudience; + this.universeDomain = settings.universeDomain; } /** Get Quota Project ID from Client Context * */ @@ -293,6 +302,7 @@ protected Builder(ClientContext clientContext) { this.tracerFactory = BaseApiTracerFactory.getInstance(); this.deprecatedExecutorProviderSet = false; this.gdchApiAudience = null; + this.universeDomain = null; } else { ExecutorProvider fixedExecutorProvider = FixedExecutorProvider.create(clientContext.getExecutor()); @@ -316,6 +326,7 @@ protected Builder(ClientContext clientContext) { this.tracerFactory = clientContext.getTracerFactory(); this.quotaProjectId = getQuotaProjectIdFromClientContext(clientContext); this.gdchApiAudience = clientContext.getGdchApiAudience(); + this.universeDomain = clientContext.getUniverseDomain(); } } @@ -428,6 +439,11 @@ public B setClock(ApiClock clock) { return self(); } + public B setUniverseDomain(String universeDomain) { + this.universeDomain = universeDomain; + return self(); + } + public B setEndpoint(String endpoint) { this.endpoint = endpoint; this.switchToMtlsEndpointAllowed = false; @@ -577,6 +593,7 @@ public String toString() { .add("headerProvider", headerProvider) .add("internalHeaderProvider", internalHeaderProvider) .add("clock", clock) + .add("universeDomain", universeDomain) .add("endpoint", endpoint) .add("mtlsEndpoint", mtlsEndpoint) .add("switchToMtlsEndpointAllowed", switchToMtlsEndpointAllowed) diff --git a/gax-java/gax/src/test/java/com/google/api/gax/rpc/ClientContextTest.java b/gax-java/gax/src/test/java/com/google/api/gax/rpc/ClientContextTest.java index 096349971c..cad4a7869a 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/rpc/ClientContextTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/rpc/ClientContextTest.java @@ -73,6 +73,7 @@ @RunWith(JUnit4.class) public class ClientContextTest { private static final String DEFAULT_ENDPOINT = "test.googleapis.com"; + private static final String DEFAULT_UNIVERSE_DOMAIN = "googleapis.com"; private static class InterceptingExecutor extends ScheduledThreadPoolExecutor { boolean shutdownCalled = false; @@ -788,10 +789,12 @@ private TransportChannelProvider getFakeTransportChannelProvider() { FakeTransportChannel.create(new FakeChannel()), null, true, null, null, DEFAULT_ENDPOINT); } + // EndpointContext will construct a valid endpoint if nothing is provided @Test - public void testCreateClientContext_withGdchCredentialNoAudienceNoEndpoint_throws() - throws IOException { - TransportChannelProvider transportChannelProvider = getFakeTransportChannelProvider(); + public void testCreateClientContext_withGdchCredentialNoAudienceNoEndpoint() throws IOException { + TransportChannelProvider transportChannelProvider = + new FakeTransportProvider( + FakeTransportChannel.create(new FakeChannel()), null, true, null, null, null); Credentials creds = getMockGdchCredentials(); CredentialsProvider provider = FixedCredentialsProvider.create(creds); @@ -800,6 +803,34 @@ public void testCreateClientContext_withGdchCredentialNoAudienceNoEndpoint_throw clientSettingsBuilder.setCredentialsProvider(provider); clientSettingsBuilder.setTransportChannelProvider(transportChannelProvider); + ClientContext context = ClientContext.create(clientSettingsBuilder.build()); + + Credentials fromContext = context.getCredentials(); + Credentials fromProvider = provider.getCredentials(); + assertNotNull(fromProvider); + assertNotNull(fromContext); + assertThat(fromContext).isInstanceOf(GdchCredentials.class); + assertThat(fromProvider).isInstanceOf(GdchCredentials.class); + assertNotSame(fromContext, fromProvider); + verify((GdchCredentials) fromProvider, times(1)) + .createWithGdchAudience(URI.create("test.googleapis.com:443")); + } + + @Test + public void testCreateClientContext_withGdchCredentialNoAudienceEmptyEndpoint_throws() + throws IOException { + TransportChannelProvider transportChannelProvider = + new FakeTransportProvider( + FakeTransportChannel.create(new FakeChannel()), null, true, null, null, null); + Credentials creds = getMockGdchCredentials(); + + CredentialsProvider provider = FixedCredentialsProvider.create(creds); + StubSettings settings = + new FakeStubSettings.Builder().setGdchApiAudience(null).setEndpoint("").build(); + FakeClientSettings.Builder clientSettingsBuilder = new FakeClientSettings.Builder(settings); + clientSettingsBuilder.setCredentialsProvider(provider); + clientSettingsBuilder.setTransportChannelProvider(transportChannelProvider); + // should throw IllegalArgumentException ex = assertThrows( @@ -811,7 +842,9 @@ public void testCreateClientContext_withGdchCredentialNoAudienceNoEndpoint_throw @Test public void testCreateClientContext_withGdchCredentialWithoutAudienceWithEndpoint_correct() throws IOException { - TransportChannelProvider transportChannelProvider = getFakeTransportChannelProvider(); + TransportChannelProvider transportChannelProvider = + new FakeTransportProvider( + FakeTransportChannel.create(new FakeChannel()), null, true, null, null, null); Credentials creds = getMockGdchCredentials(); // it should correctly create a client context with gdch creds and null audience @@ -926,7 +959,11 @@ public void testCreateClientContext_SetEndpointViaClientSettings() throws IOExce TransportChannelProvider transportChannelProvider = new FakeTransportProvider( FakeTransportChannel.create(new FakeChannel()), null, true, null, null, null); - StubSettings settings = new FakeStubSettings.Builder().setEndpoint(DEFAULT_ENDPOINT).build(); + StubSettings settings = + new FakeStubSettings.Builder() + .setEndpoint(DEFAULT_ENDPOINT) + .setUniverseDomain(DEFAULT_UNIVERSE_DOMAIN) + .build(); ClientSettings.Builder clientSettingsBuilder = new FakeClientSettings.Builder(settings); clientSettingsBuilder.setTransportChannelProvider(transportChannelProvider); clientSettingsBuilder.setCredentialsProvider( @@ -934,6 +971,7 @@ public void testCreateClientContext_SetEndpointViaClientSettings() throws IOExce ClientSettings clientSettings = clientSettingsBuilder.build(); ClientContext clientContext = ClientContext.create(clientSettings); assertThat(clientContext.getEndpoint()).isEqualTo(DEFAULT_ENDPOINT); + assertThat(clientContext.getUniverseDomain()).isEqualTo(DEFAULT_UNIVERSE_DOMAIN); } @Test @@ -946,7 +984,11 @@ public void testCreateClientContext_SetEndpointViaTransportChannelProvider() thr null, null, DEFAULT_ENDPOINT); - StubSettings settings = new FakeStubSettings.Builder().setEndpoint(null).build(); + StubSettings settings = + new FakeStubSettings.Builder() + .setEndpoint(null) + .setUniverseDomain(DEFAULT_UNIVERSE_DOMAIN) + .build(); ClientSettings.Builder clientSettingsBuilder = new FakeClientSettings.Builder(settings); clientSettingsBuilder.setTransportChannelProvider(transportChannelProvider); clientSettingsBuilder.setCredentialsProvider( @@ -955,6 +997,7 @@ public void testCreateClientContext_SetEndpointViaTransportChannelProvider() thr ClientContext clientContext = ClientContext.create(clientSettings); // ClientContext.getEndpoint() currently always refers to the ClientSettingsEndpoint value assertThat(clientContext.getEndpoint()).isEqualTo(null); + assertThat(clientContext.getUniverseDomain()).isEqualTo(DEFAULT_UNIVERSE_DOMAIN); } @Test @@ -971,7 +1014,10 @@ public void testCreateClientContext_SetEndpointViaClientSettingsAndTransportChan null, transportChannelProviderEndpoint); StubSettings settings = - new FakeStubSettings.Builder().setEndpoint(clientSettingsEndpoint).build(); + new FakeStubSettings.Builder() + .setEndpoint(clientSettingsEndpoint) + .setUniverseDomain(DEFAULT_UNIVERSE_DOMAIN) + .build(); ClientSettings.Builder clientSettingsBuilder = new FakeClientSettings.Builder(settings); clientSettingsBuilder.setTransportChannelProvider(transportChannelProvider); clientSettingsBuilder.setCredentialsProvider( @@ -980,5 +1026,42 @@ public void testCreateClientContext_SetEndpointViaClientSettingsAndTransportChan ClientContext clientContext = ClientContext.create(clientSettings); // ClientContext.getEndpoint() currently always refers to the ClientSettingsEndpoint value assertThat(clientContext.getEndpoint()).isEqualTo(clientSettingsEndpoint); + assertThat(clientContext.getUniverseDomain()).isEqualTo(DEFAULT_UNIVERSE_DOMAIN); + } + + @Test + public void testCreateClientContext_doNotSetUniverseDomain() throws IOException { + TransportChannelProvider transportChannelProvider = + new FakeTransportProvider( + FakeTransportChannel.create(new FakeChannel()), null, true, null, null, null); + StubSettings settings = + new FakeStubSettings.Builder() + .setEndpoint(null) + .setUniverseDomain(DEFAULT_UNIVERSE_DOMAIN) + .build(); + ClientSettings.Builder clientSettingsBuilder = new FakeClientSettings.Builder(settings); + clientSettingsBuilder.setTransportChannelProvider(transportChannelProvider); + clientSettingsBuilder.setCredentialsProvider( + FixedCredentialsProvider.create(Mockito.mock(Credentials.class))); + ClientSettings clientSettings = clientSettingsBuilder.build(); + ClientContext clientContext = ClientContext.create(clientSettings); + assertThat(clientContext.getUniverseDomain()).isEqualTo(DEFAULT_UNIVERSE_DOMAIN); + } + + @Test + public void testCreateClientContext_setUniverseDomain() throws IOException { + TransportChannelProvider transportChannelProvider = + new FakeTransportProvider( + FakeTransportChannel.create(new FakeChannel()), null, true, null, null, null); + String universeDomain = "testdomain.com"; + StubSettings settings = + new FakeStubSettings.Builder().setEndpoint(null).setUniverseDomain(universeDomain).build(); + ClientSettings.Builder clientSettingsBuilder = new FakeClientSettings.Builder(settings); + clientSettingsBuilder.setTransportChannelProvider(transportChannelProvider); + clientSettingsBuilder.setCredentialsProvider( + FixedCredentialsProvider.create(Mockito.mock(Credentials.class))); + ClientSettings clientSettings = clientSettingsBuilder.build(); + ClientContext clientContext = ClientContext.create(clientSettings); + assertThat(clientContext.getUniverseDomain()).isEqualTo(universeDomain); } } diff --git a/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java b/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java index bb403d0971..d040450786 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java @@ -35,24 +35,25 @@ import com.google.api.gax.rpc.testing.FakeMtlsProvider; import com.google.common.truth.Truth; import java.io.IOException; -import org.junit.BeforeClass; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class EndpointContextTest { - private static final String DEFAULT_ENDPOINT = "test.googleapis.com"; - private static final String DEFAULT_MTLS_ENDPOINT = "test.mtls.googleapis.com"; - private static EndpointContext defaultEndpointContext; + private static final String DEFAULT_ENDPOINT = "test.googleapis.com:443"; + private static final String DEFAULT_MTLS_ENDPOINT = "test.mtls.googleapis.com:443"; + private EndpointContext.Builder defaultEndpointContextBuilder; - @BeforeClass - public static void setUp() throws IOException { - defaultEndpointContext = + @Before + public void setUp() throws IOException { + defaultEndpointContextBuilder = EndpointContext.newBuilder() + .setServiceName("test") + .setUniverseDomain(EndpointContext.GOOGLE_DEFAULT_UNIVERSE) .setClientSettingsEndpoint(DEFAULT_ENDPOINT) - .setMtlsEndpoint(DEFAULT_MTLS_ENDPOINT) - .build(); + .setMtlsEndpoint(DEFAULT_MTLS_ENDPOINT); } @Test @@ -68,7 +69,7 @@ public void mtlsEndpointResolver_switchToMtlsAllowedIsFalse() throws IOException throwExceptionForGetKeyStore); boolean switchToMtlsEndpointAllowed = false; Truth.assertThat( - defaultEndpointContext.mtlsEndpointResolver( + defaultEndpointContextBuilder.mtlsEndpointResolver( DEFAULT_ENDPOINT, DEFAULT_MTLS_ENDPOINT, switchToMtlsEndpointAllowed, mtlsProvider)) .isEqualTo(DEFAULT_ENDPOINT); } @@ -86,7 +87,7 @@ public void mtlsEndpointResolver_switchToMtlsAllowedIsTrue_mtlsUsageAuto() throw throwExceptionForGetKeyStore); boolean switchToMtlsEndpointAllowed = true; Truth.assertThat( - defaultEndpointContext.mtlsEndpointResolver( + defaultEndpointContextBuilder.mtlsEndpointResolver( DEFAULT_ENDPOINT, DEFAULT_MTLS_ENDPOINT, switchToMtlsEndpointAllowed, mtlsProvider)) .isEqualTo(DEFAULT_MTLS_ENDPOINT); } @@ -104,7 +105,7 @@ public void mtlsEndpointResolver_switchToMtlsAllowedIsTrue_mtlsUsageAlways() thr throwExceptionForGetKeyStore); boolean switchToMtlsEndpointAllowed = true; Truth.assertThat( - defaultEndpointContext.mtlsEndpointResolver( + defaultEndpointContextBuilder.mtlsEndpointResolver( DEFAULT_ENDPOINT, DEFAULT_MTLS_ENDPOINT, switchToMtlsEndpointAllowed, mtlsProvider)) .isEqualTo(DEFAULT_MTLS_ENDPOINT); } @@ -122,7 +123,7 @@ public void mtlsEndpointResolver_switchToMtlsAllowedIsTrue_mtlsUsageNever() thro throwExceptionForGetKeyStore); boolean switchToMtlsEndpointAllowed = true; Truth.assertThat( - defaultEndpointContext.mtlsEndpointResolver( + defaultEndpointContextBuilder.mtlsEndpointResolver( DEFAULT_ENDPOINT, DEFAULT_MTLS_ENDPOINT, switchToMtlsEndpointAllowed, mtlsProvider)) .isEqualTo(DEFAULT_ENDPOINT); } @@ -142,13 +143,13 @@ public void mtlsEndpointResolver_switchToMtlsAllowedIsTrue_mtlsUsageNever() thro throwExceptionForGetKeyStore); boolean switchToMtlsEndpointAllowed = true; Truth.assertThat( - defaultEndpointContext.mtlsEndpointResolver( + defaultEndpointContextBuilder.mtlsEndpointResolver( DEFAULT_ENDPOINT, DEFAULT_MTLS_ENDPOINT, switchToMtlsEndpointAllowed, mtlsProvider)) .isEqualTo(DEFAULT_ENDPOINT); } @Test - public void mtlsEndpointResolver_getKeyStore_throwsIOException() { + public void mtlsEndpointResolver_getKeyStore_throwsIOException() throws IOException { boolean useClientCertificate = true; boolean throwExceptionForGetKeyStore = true; MtlsProvider mtlsProvider = @@ -162,10 +163,172 @@ public void mtlsEndpointResolver_getKeyStore_throwsIOException() { assertThrows( IOException.class, () -> - defaultEndpointContext.mtlsEndpointResolver( + defaultEndpointContextBuilder.mtlsEndpointResolver( DEFAULT_ENDPOINT, DEFAULT_MTLS_ENDPOINT, switchToMtlsEndpointAllowed, mtlsProvider)); } + + @Test + public void endpointContextBuild_noUniverseDomain_usesClientSettingsEndpoint() + throws IOException { + EndpointContext endpointContext = + defaultEndpointContextBuilder.setClientSettingsEndpoint(DEFAULT_ENDPOINT).build(); + Truth.assertThat(endpointContext.resolvedEndpoint()).isEqualTo(DEFAULT_ENDPOINT); + Truth.assertThat(endpointContext.resolvedUniverseDomain()) + .isEqualTo(EndpointContext.GOOGLE_DEFAULT_UNIVERSE); + } + + @Test + public void endpointContextBuild_noUniverseDomain_usesTransportChannelProviderEndpoint() + throws IOException { + String transportChannelProviderEndpoint = "random.endpoint.com:443"; + EndpointContext endpointContext = + defaultEndpointContextBuilder + .setClientSettingsEndpoint(null) + .setTransportChannelProviderEndpoint(transportChannelProviderEndpoint) + .build(); + Truth.assertThat(endpointContext.resolvedEndpoint()) + .isEqualTo(transportChannelProviderEndpoint); + Truth.assertThat(endpointContext.resolvedUniverseDomain()) + .isEqualTo(EndpointContext.GOOGLE_DEFAULT_UNIVERSE); + } + + @Test + public void endpointContextBuild_noUniverseDomain_overrideUsesTransportChannelProviderEndpoint() + throws IOException { + String transportChannelProviderEndpoint = "random.endpoint.com"; + EndpointContext endpointContext = + defaultEndpointContextBuilder + .setClientSettingsEndpoint(DEFAULT_ENDPOINT) + .setTransportChannelProviderEndpoint(transportChannelProviderEndpoint) + .build(); + Truth.assertThat(endpointContext.resolvedEndpoint()) + .isEqualTo(transportChannelProviderEndpoint); + Truth.assertThat(endpointContext.resolvedUniverseDomain()) + .isEqualTo(EndpointContext.GOOGLE_DEFAULT_UNIVERSE); + } + + @Test + public void endpointContextBuild_emptyStringUniverseDomain_throwsIllegalArgumentException() { + EndpointContext.Builder endpointContextBuilder = + defaultEndpointContextBuilder.setUniverseDomain(""); + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, endpointContextBuilder::build); + Truth.assertThat(exception.getMessage()) + .isEqualTo("The universe domain value cannot be empty."); + } + + @Test + public void endpointContextBuild_GDUUniverseDomain() throws IOException { + EndpointContext endpointContext = defaultEndpointContextBuilder.build(); + Truth.assertThat(endpointContext.resolvedEndpoint()).isEqualTo(DEFAULT_ENDPOINT); + Truth.assertThat(endpointContext.resolvedUniverseDomain()) + .isEqualTo(EndpointContext.GOOGLE_DEFAULT_UNIVERSE); + } + + @Test + public void endpointContextBuild_nonGDUUniverseDomain() throws IOException { + String universeDomain = "random.com"; + EndpointContext endpointContext = + defaultEndpointContextBuilder.setUniverseDomain(universeDomain).build(); + Truth.assertThat(endpointContext.resolvedEndpoint()).isEqualTo(DEFAULT_ENDPOINT); + Truth.assertThat(endpointContext.resolvedUniverseDomain()).isEqualTo(universeDomain); + } + + @Test + public void endpointContextBuild_noUniverseDomain_noEndpoints() throws IOException { + String expectedEndpoint = "random.googleapis.com:443"; + EndpointContext endpointContext = + defaultEndpointContextBuilder + .setServiceName("random") + .setClientSettingsEndpoint(null) + .setTransportChannelProviderEndpoint(null) + .build(); + Truth.assertThat(endpointContext.resolvedEndpoint()).isEqualTo(expectedEndpoint); + Truth.assertThat(endpointContext.resolvedUniverseDomain()) + .isEqualTo(EndpointContext.GOOGLE_DEFAULT_UNIVERSE); + } + + @Test + public void endpointContextBuild_mtlsConfigured_GDU() throws IOException { + MtlsProvider mtlsProvider = + new FakeMtlsProvider( + true, + MtlsProvider.MtlsEndpointUsagePolicy.ALWAYS, + FakeMtlsProvider.createTestMtlsKeyStore(), + "", + false); + EndpointContext endpointContext = + defaultEndpointContextBuilder + .setClientSettingsEndpoint(null) + .setTransportChannelProviderEndpoint(null) + .setSwitchToMtlsEndpointAllowed(true) + .setMtlsProvider(mtlsProvider) + .build(); + Truth.assertThat(endpointContext.resolvedEndpoint()).isEqualTo(DEFAULT_MTLS_ENDPOINT); + Truth.assertThat(endpointContext.resolvedUniverseDomain()) + .isEqualTo(EndpointContext.GOOGLE_DEFAULT_UNIVERSE); + } + + @Test + public void endpointContextBuild_mtlsConfigured_nonGDU_throwsIllegalArgumentException() + throws IOException { + MtlsProvider mtlsProvider = + new FakeMtlsProvider( + true, + MtlsProvider.MtlsEndpointUsagePolicy.ALWAYS, + FakeMtlsProvider.createTestMtlsKeyStore(), + "", + false); + EndpointContext.Builder endpointContextBuilder = + defaultEndpointContextBuilder + .setUniverseDomain("random.com") + .setClientSettingsEndpoint(null) + .setTransportChannelProviderEndpoint(null) + .setSwitchToMtlsEndpointAllowed(true) + .setMtlsProvider(mtlsProvider); + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, endpointContextBuilder::build); + Truth.assertThat(exception.getMessage()) + .isEqualTo("mTLS is not supported in any universe other than googleapis.com"); + } + + @Test + public void endpointContextBuild_gdchFlow_setUniverseDomain() throws IOException { + EndpointContext.Builder endpointContextBuilder = + defaultEndpointContextBuilder.setUsingGDCH(true); + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, endpointContextBuilder::build); + Truth.assertThat(exception.getMessage()) + .isEqualTo("Universe domain configuration is incompatible with GDC-H"); + } + + @Test + public void endpointContextBuild_gdchFlow_noUniverseDomain_noCustomEndpoint() throws IOException { + EndpointContext endpointContext = + defaultEndpointContextBuilder + .setUniverseDomain(null) + .setUsingGDCH(true) + .setClientSettingsEndpoint(null) + .build(); + Truth.assertThat(endpointContext.resolvedEndpoint()).isEqualTo(DEFAULT_ENDPOINT); + Truth.assertThat(endpointContext.resolvedUniverseDomain()) + .isEqualTo(EndpointContext.GOOGLE_DEFAULT_UNIVERSE); + } + + @Test + public void endpointContextBuild_gdchFlow_noUniverseDomain_customEndpoint() throws IOException { + String clientSettingsEndpoint = "random.endpoint.com:443"; + EndpointContext endpointContext = + defaultEndpointContextBuilder + .setUniverseDomain(null) + .setUsingGDCH(true) + .setClientSettingsEndpoint(clientSettingsEndpoint) + .build(); + Truth.assertThat(endpointContext.resolvedEndpoint()).isEqualTo(clientSettingsEndpoint); + Truth.assertThat(endpointContext.resolvedUniverseDomain()) + .isEqualTo(EndpointContext.GOOGLE_DEFAULT_UNIVERSE); + } } diff --git a/gax-java/gax/src/test/java/com/google/api/gax/rpc/testing/FakeStubSettings.java b/gax-java/gax/src/test/java/com/google/api/gax/rpc/testing/FakeStubSettings.java index 2c41d75a47..c5f0cc81f4 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/rpc/testing/FakeStubSettings.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/rpc/testing/FakeStubSettings.java @@ -41,6 +41,11 @@ private FakeStubSettings(Builder builder) throws IOException { super(builder); } + @Override + public String getServiceName() { + return "test"; + } + @Override public StubSettings.Builder toBuilder() { return new Builder(this);