From 2cb0f9e542c6f074372bbaae0ab8b1f2f2598bc1 Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Fri, 8 Dec 2023 02:07:54 +0000 Subject: [PATCH] refactor: Add EndpointContext (#2275) * chore: Add EndpointContext * chore: Update constants scope to private * chore: Rename tests * chore: Fix lint issues * chore: Clean up EndpointContext * chore: Add uses for the EndpointContext in ClientContext * chore: Replace ClientContext getEndpoint() logic * chore: Remove stubsettings code * chore: Clean up EndpointContext test case * chore: Fix lint issues * chore: Add variables for test params * feat: Remove TransportChannelProvider logic * feat: Add javadocs for clientSettingsEndpoint --- .../com/google/api/gax/rpc/ClientContext.java | 37 +--- .../google/api/gax/rpc/EndpointContext.java | 128 +++++++++++++ .../google/api/gax/rpc/ClientContextTest.java | 91 ---------- .../api/gax/rpc/EndpointContextTest.java | 171 ++++++++++++++++++ 4 files changed, 306 insertions(+), 121 deletions(-) create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java create mode 100644 gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java 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 c26d4a0f4d..8bb14ebea9 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 @@ -37,7 +37,6 @@ import com.google.api.gax.core.ExecutorAsBackgroundResource; import com.google.api.gax.core.ExecutorProvider; import com.google.api.gax.rpc.internal.QuotaProjectIdHidingCredentials; -import com.google.api.gax.rpc.mtls.MtlsProvider; import com.google.api.gax.tracing.ApiTracerFactory; import com.google.api.gax.tracing.BaseApiTracerFactory; import com.google.auth.Credentials; @@ -143,29 +142,6 @@ public static ClientContext create(ClientSettings settings) throws IOException { return create(settings.getStubSettings()); } - /** Returns the endpoint that should be used. See https://google.aip.dev/auth/4114. */ - static String getEndpoint( - String endpoint, - String mtlsEndpoint, - boolean switchToMtlsEndpointAllowed, - MtlsProvider mtlsProvider) - throws IOException { - if (switchToMtlsEndpointAllowed) { - switch (mtlsProvider.getMtlsEndpointUsagePolicy()) { - case ALWAYS: - return mtlsEndpoint; - case NEVER: - return endpoint; - default: - if (mtlsProvider.useMtlsClientCertificate() && mtlsProvider.getKeyStore() != null) { - return mtlsEndpoint; - } - return endpoint; - } - } - return endpoint; - } - /** * Instantiates the executor, credentials, and transport context based on the given client * settings. @@ -224,12 +200,13 @@ public static ClientContext create(StubSettings settings) throws IOException { if (transportChannelProvider.needsCredentials() && credentials != null) { transportChannelProvider = transportChannelProvider.withCredentials(credentials); } - String endpoint = - getEndpoint( - settings.getEndpoint(), - settings.getMtlsEndpoint(), - settings.getSwitchToMtlsEndpointAllowed(), - new MtlsProvider()); + EndpointContext endpointContext = + EndpointContext.newBuilder() + .setClientSettingsEndpoint(settings.getEndpoint()) + .setMtlsEndpoint(settings.getMtlsEndpoint()) + .setSwitchToMtlsEndpointAllowed(settings.getSwitchToMtlsEndpointAllowed()) + .build(); + String endpoint = endpointContext.getResolvedEndpoint(); if (transportChannelProvider.needsEndpoint()) { transportChannelProvider = transportChannelProvider.withEndpoint(endpoint); } 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 new file mode 100644 index 0000000000..3ecb53c3f8 --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java @@ -0,0 +1,128 @@ +/* + * Copyright 2023 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.rpc; + +import com.google.api.core.InternalApi; +import com.google.api.gax.rpc.mtls.MtlsProvider; +import com.google.auto.value.AutoValue; +import com.google.common.annotations.VisibleForTesting; +import java.io.IOException; +import javax.annotation.Nullable; + +/** Contains the fields required to resolve the endpoint */ +@InternalApi +@AutoValue +public abstract class EndpointContext { + /** + * ClientSettingsEndpoint is the endpoint value set via the ClientSettings/StubSettings classes. + */ + @Nullable + public abstract String clientSettingsEndpoint(); + + @Nullable + public abstract String mtlsEndpoint(); + + public abstract boolean switchToMtlsEndpointAllowed(); + + @Nullable + public abstract MtlsProvider mtlsProvider(); + + public abstract Builder toBuilder(); + + private String resolvedEndpoint; + + public static Builder newBuilder() { + return new AutoValue_EndpointContext.Builder().setSwitchToMtlsEndpointAllowed(false); + } + + @VisibleForTesting + void determineEndpoint() throws IOException { + MtlsProvider mtlsProvider = mtlsProvider() == null ? new MtlsProvider() : mtlsProvider(); + resolvedEndpoint = + mtlsEndpointResolver( + clientSettingsEndpoint(), mtlsEndpoint(), switchToMtlsEndpointAllowed(), mtlsProvider); + } + + // 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; + } + + /** + * The resolved endpoint is the computed endpoint after accounting for the custom endpoints and + * mTLS configurations. + */ + public String getResolvedEndpoint() { + return resolvedEndpoint; + } + + @AutoValue.Builder + public abstract static class Builder { + /** + * ClientSettingsEndpoint is the endpoint value set via the ClientSettings/StubSettings classes. + */ + public abstract Builder setClientSettingsEndpoint(String clientSettingsEndpoint); + + public abstract Builder setMtlsEndpoint(String mtlsEndpoint); + + public abstract Builder setSwitchToMtlsEndpointAllowed(boolean switchToMtlsEndpointAllowed); + + public abstract Builder setMtlsProvider(MtlsProvider mtlsProvider); + + abstract EndpointContext autoBuild(); + + public EndpointContext build() throws IOException { + EndpointContext endpointContext = autoBuild(); + endpointContext.determineEndpoint(); + return endpointContext; + } + } +} 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 ebe7a66712..c1f6a43e50 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 @@ -36,7 +36,6 @@ import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -47,11 +46,8 @@ import com.google.api.gax.core.FixedCredentialsProvider; import com.google.api.gax.core.FixedExecutorProvider; import com.google.api.gax.core.NoCredentialsProvider; -import com.google.api.gax.rpc.mtls.MtlsProvider; -import com.google.api.gax.rpc.mtls.MtlsProvider.MtlsEndpointUsagePolicy; import com.google.api.gax.rpc.testing.FakeChannel; import com.google.api.gax.rpc.testing.FakeClientSettings; -import com.google.api.gax.rpc.testing.FakeMtlsProvider; import com.google.api.gax.rpc.testing.FakeStubSettings; import com.google.api.gax.rpc.testing.FakeTransportChannel; import com.google.auth.Credentials; @@ -636,93 +632,6 @@ public void testUserAgentConcat() throws Exception { private static String endpoint = "https://foo.googleapis.com"; private static String mtlsEndpoint = "https://foo.mtls.googleapis.com"; - @Test - public void testAutoUseMtlsEndpoint() throws IOException { - // Test the case client certificate exists and mTLS endpoint is selected. - boolean switchToMtlsEndpointAllowed = true; - MtlsProvider provider = - new FakeMtlsProvider( - true, - MtlsEndpointUsagePolicy.AUTO, - FakeMtlsProvider.createTestMtlsKeyStore(), - "", - false); - String endpointSelected = - ClientContext.getEndpoint(endpoint, mtlsEndpoint, switchToMtlsEndpointAllowed, provider); - assertEquals(mtlsEndpoint, endpointSelected); - } - - @Test - public void testEndpointNotOverridable() throws IOException { - // Test the case that switching to mTLS endpoint is not allowed so the original endpoint is - // selected. - boolean switchToMtlsEndpointAllowed = false; - MtlsProvider provider = - new FakeMtlsProvider( - true, - MtlsEndpointUsagePolicy.AUTO, - FakeMtlsProvider.createTestMtlsKeyStore(), - "", - false); - String endpointSelected = - ClientContext.getEndpoint(endpoint, mtlsEndpoint, switchToMtlsEndpointAllowed, provider); - assertEquals(endpoint, endpointSelected); - } - - @Test - public void testNoClientCertificate() throws IOException { - // Test the case that client certificates doesn't exists so the original endpoint is selected. - boolean switchToMtlsEndpointAllowed = true; - MtlsProvider provider = - new FakeMtlsProvider(true, MtlsEndpointUsagePolicy.AUTO, null, "", false); - String endpointSelected = - ClientContext.getEndpoint(endpoint, mtlsEndpoint, switchToMtlsEndpointAllowed, provider); - assertEquals(endpoint, endpointSelected); - } - - @Test - public void testAlwaysUseMtlsEndpoint() throws IOException { - // Test the case that mTLS endpoint is always used. - boolean switchToMtlsEndpointAllowed = true; - MtlsProvider provider = - new FakeMtlsProvider(false, MtlsEndpointUsagePolicy.ALWAYS, null, "", false); - String endpointSelected = - ClientContext.getEndpoint(endpoint, mtlsEndpoint, switchToMtlsEndpointAllowed, provider); - assertEquals(mtlsEndpoint, endpointSelected); - } - - @Test - public void testNeverUseMtlsEndpoint() throws IOException { - // Test the case that mTLS endpoint is never used. - boolean switchToMtlsEndpointAllowed = true; - MtlsProvider provider = - new FakeMtlsProvider( - true, - MtlsEndpointUsagePolicy.NEVER, - FakeMtlsProvider.createTestMtlsKeyStore(), - "", - false); - String endpointSelected = - ClientContext.getEndpoint(endpoint, mtlsEndpoint, switchToMtlsEndpointAllowed, provider); - assertEquals(endpoint, endpointSelected); - } - - @Test - public void testGetKeyStoreThrows() throws IOException { - // Test the case that getKeyStore throws exceptions. - try { - boolean switchToMtlsEndpointAllowed = true; - MtlsProvider provider = - new FakeMtlsProvider(true, MtlsEndpointUsagePolicy.AUTO, null, "", true); - ClientContext.getEndpoint(endpoint, mtlsEndpoint, switchToMtlsEndpointAllowed, provider); - fail("should throw an exception"); - } catch (IOException e) { - assertTrue( - "expected getKeyStore to throw an exception", - e.getMessage().contains("getKeyStore throws exception")); - } - } - @Test public void testSwitchToMtlsEndpointAllowed() throws IOException { StubSettings settings = new FakeStubSettings.Builder().setEndpoint(endpoint).build(); 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 new file mode 100644 index 0000000000..bb403d0971 --- /dev/null +++ b/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java @@ -0,0 +1,171 @@ +/* + * Copyright 2023 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.rpc; + +import static org.junit.Assert.assertThrows; + +import com.google.api.gax.rpc.mtls.MtlsProvider; +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.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; + + @BeforeClass + public static void setUp() throws IOException { + defaultEndpointContext = + EndpointContext.newBuilder() + .setClientSettingsEndpoint(DEFAULT_ENDPOINT) + .setMtlsEndpoint(DEFAULT_MTLS_ENDPOINT) + .build(); + } + + @Test + public void mtlsEndpointResolver_switchToMtlsAllowedIsFalse() throws IOException { + boolean useClientCertificate = true; + boolean throwExceptionForGetKeyStore = false; + MtlsProvider mtlsProvider = + new FakeMtlsProvider( + useClientCertificate, + MtlsProvider.MtlsEndpointUsagePolicy.AUTO, + FakeMtlsProvider.createTestMtlsKeyStore(), + "", + throwExceptionForGetKeyStore); + boolean switchToMtlsEndpointAllowed = false; + Truth.assertThat( + defaultEndpointContext.mtlsEndpointResolver( + DEFAULT_ENDPOINT, DEFAULT_MTLS_ENDPOINT, switchToMtlsEndpointAllowed, mtlsProvider)) + .isEqualTo(DEFAULT_ENDPOINT); + } + + @Test + public void mtlsEndpointResolver_switchToMtlsAllowedIsTrue_mtlsUsageAuto() throws IOException { + boolean useClientCertificate = true; + boolean throwExceptionForGetKeyStore = false; + MtlsProvider mtlsProvider = + new FakeMtlsProvider( + useClientCertificate, + MtlsProvider.MtlsEndpointUsagePolicy.AUTO, + FakeMtlsProvider.createTestMtlsKeyStore(), + "", + throwExceptionForGetKeyStore); + boolean switchToMtlsEndpointAllowed = true; + Truth.assertThat( + defaultEndpointContext.mtlsEndpointResolver( + DEFAULT_ENDPOINT, DEFAULT_MTLS_ENDPOINT, switchToMtlsEndpointAllowed, mtlsProvider)) + .isEqualTo(DEFAULT_MTLS_ENDPOINT); + } + + @Test + public void mtlsEndpointResolver_switchToMtlsAllowedIsTrue_mtlsUsageAlways() throws IOException { + boolean useClientCertificate = true; + boolean throwExceptionForGetKeyStore = false; + MtlsProvider mtlsProvider = + new FakeMtlsProvider( + useClientCertificate, + MtlsProvider.MtlsEndpointUsagePolicy.ALWAYS, + FakeMtlsProvider.createTestMtlsKeyStore(), + "", + throwExceptionForGetKeyStore); + boolean switchToMtlsEndpointAllowed = true; + Truth.assertThat( + defaultEndpointContext.mtlsEndpointResolver( + DEFAULT_ENDPOINT, DEFAULT_MTLS_ENDPOINT, switchToMtlsEndpointAllowed, mtlsProvider)) + .isEqualTo(DEFAULT_MTLS_ENDPOINT); + } + + @Test + public void mtlsEndpointResolver_switchToMtlsAllowedIsTrue_mtlsUsageNever() throws IOException { + boolean useClientCertificate = true; + boolean throwExceptionForGetKeyStore = false; + MtlsProvider mtlsProvider = + new FakeMtlsProvider( + useClientCertificate, + MtlsProvider.MtlsEndpointUsagePolicy.NEVER, + FakeMtlsProvider.createTestMtlsKeyStore(), + "", + throwExceptionForGetKeyStore); + boolean switchToMtlsEndpointAllowed = true; + Truth.assertThat( + defaultEndpointContext.mtlsEndpointResolver( + DEFAULT_ENDPOINT, DEFAULT_MTLS_ENDPOINT, switchToMtlsEndpointAllowed, mtlsProvider)) + .isEqualTo(DEFAULT_ENDPOINT); + } + + @Test + public void + mtlsEndpointResolver_switchToMtlsAllowedIsTrue_useCertificateIsFalse_nullMtlsKeystore() + throws IOException { + boolean useClientCertificate = false; + boolean throwExceptionForGetKeyStore = false; + MtlsProvider mtlsProvider = + new FakeMtlsProvider( + useClientCertificate, + MtlsProvider.MtlsEndpointUsagePolicy.AUTO, + null, + "", + throwExceptionForGetKeyStore); + boolean switchToMtlsEndpointAllowed = true; + Truth.assertThat( + defaultEndpointContext.mtlsEndpointResolver( + DEFAULT_ENDPOINT, DEFAULT_MTLS_ENDPOINT, switchToMtlsEndpointAllowed, mtlsProvider)) + .isEqualTo(DEFAULT_ENDPOINT); + } + + @Test + public void mtlsEndpointResolver_getKeyStore_throwsIOException() { + boolean useClientCertificate = true; + boolean throwExceptionForGetKeyStore = true; + MtlsProvider mtlsProvider = + new FakeMtlsProvider( + useClientCertificate, + MtlsProvider.MtlsEndpointUsagePolicy.AUTO, + null, + "", + throwExceptionForGetKeyStore); + boolean switchToMtlsEndpointAllowed = true; + assertThrows( + IOException.class, + () -> + defaultEndpointContext.mtlsEndpointResolver( + DEFAULT_ENDPOINT, + DEFAULT_MTLS_ENDPOINT, + switchToMtlsEndpointAllowed, + mtlsProvider)); + } +}