diff --git a/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/AlloyDBAdminClientFactory.java b/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/AlloyDBAdminClientFactory.java index e5d1ad39..85031408 100644 --- a/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/AlloyDBAdminClientFactory.java +++ b/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/AlloyDBAdminClientFactory.java @@ -32,7 +32,8 @@ class AlloyDBAdminClientFactory { private static final String DEFAULT_ENDPOINT = "alloydb.googleapis.com:443"; static AlloyDBAdminClient create( - FixedCredentialsProvider credentialsProvider, ConnectorConfig config) throws IOException { + FixedCredentialsProvider credentialsProvider, ConnectorConfig config, String userAgents) + throws IOException { AlloyDBAdminSettings.Builder settingsBuilder = AlloyDBAdminSettings.newBuilder(); String endpoint = config.getAdminServiceEndpoint(); @@ -41,9 +42,7 @@ static AlloyDBAdminClient create( } Map headers = - ImmutableMap.builder() - .put("user-agent", "alloydb-java-connector/" + Version.VERSION) - .build(); + ImmutableMap.builder().put("user-agent", userAgents).build(); settingsBuilder .setEndpoint(endpoint) diff --git a/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/ConnectionInfoRepositoryFactory.java b/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/ConnectionInfoRepositoryFactory.java index c15921f3..811db332 100644 --- a/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/ConnectionInfoRepositoryFactory.java +++ b/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/ConnectionInfoRepositoryFactory.java @@ -16,7 +16,7 @@ package com.google.cloud.alloydb; -/** Factory interface for creating SQLAdmin clients to interact with AlloyDB Admin API. */ +/** Factory interface for creating AlloyDBAdminClient to interact with AlloyDB Admin API. */ public interface ConnectionInfoRepositoryFactory { ConnectionInfoRepository create(CredentialFactory credentialFactory, ConnectorConfig config); diff --git a/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/ConnectionSocket.java b/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/ConnectionSocket.java index bb53c944..129a95c6 100644 --- a/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/ConnectionSocket.java +++ b/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/ConnectionSocket.java @@ -60,23 +60,25 @@ class ConnectionSocket { private static final String X_509 = "X.509"; private static final String ROOT_CA_CERT = "rootCaCert"; private static final String CLIENT_CERT = "clientCert"; - private static final String USER_AGENT = "alloydb-java-connector/" + Version.VERSION; private static final int IO_TIMEOUT_MS = 30000; private static final int SERVER_SIDE_PROXY_PORT = 5433; private final ConnectionInfo connectionInfo; private final ConnectionConfig connectionConfig; private final KeyPair clientConnectorKeyPair; private final AccessTokenSupplier accessTokenSupplier; + private final String userAgents; ConnectionSocket( ConnectionInfo connectionInfo, ConnectionConfig connectionConfig, KeyPair clientConnectorKeyPair, - AccessTokenSupplier accessTokenSupplier) { + AccessTokenSupplier accessTokenSupplier, + String userAgents) { this.connectionInfo = connectionInfo; this.connectionConfig = connectionConfig; this.clientConnectorKeyPair = clientConnectorKeyPair; this.accessTokenSupplier = accessTokenSupplier; + this.userAgents = userAgents; } Socket connect() throws IOException { @@ -234,7 +236,7 @@ private void metadataExchange(SSLSocket socket) throws IOException { MetadataExchangeRequest.newBuilder() .setAuthType(authType) .setOauth2Token(tokenValue) - .setUserAgent(USER_AGENT) + .setUserAgent(userAgents) .build(); // Write data to the server. diff --git a/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/Connector.java b/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/Connector.java index 8cceb5b4..09e6bd97 100644 --- a/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/Connector.java +++ b/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/Connector.java @@ -36,6 +36,7 @@ class Connector { private final ConcurrentHashMap instances; private final ConnectorConfig config; private final AccessTokenSupplier accessTokenSupplier; + private final String userAgents; Connector( ConnectorConfig config, @@ -44,7 +45,8 @@ class Connector { KeyPair clientConnectorKeyPair, ConnectionInfoCacheFactory connectionInfoCacheFactory, ConcurrentHashMap instances, - AccessTokenSupplier accessTokenSupplier) { + AccessTokenSupplier accessTokenSupplier, + String userAgents) { this.config = config; this.executor = executor; this.connectionInfoRepo = connectionInfoRepo; @@ -52,6 +54,7 @@ class Connector { this.connectionInfoCacheFactory = connectionInfoCacheFactory; this.instances = instances; this.accessTokenSupplier = accessTokenSupplier; + this.userAgents = userAgents; } public ConnectorConfig getConfig() { @@ -70,7 +73,8 @@ Socket connect(ConnectionConfig config) throws IOException { try { ConnectionSocket socket = - new ConnectionSocket(connectionInfo, config, clientConnectorKeyPair, accessTokenSupplier); + new ConnectionSocket( + connectionInfo, config, clientConnectorKeyPair, accessTokenSupplier, userAgents); return socket.connect(); } catch (IOException e) { logger.debug( @@ -124,7 +128,8 @@ public boolean equals(Object o) { && Objects.equal(clientConnectorKeyPair, that.clientConnectorKeyPair) && Objects.equal(connectionInfoCacheFactory, that.connectionInfoCacheFactory) && Objects.equal(instances, that.instances) - && Objects.equal(accessTokenSupplier, that.accessTokenSupplier); + && Objects.equal(accessTokenSupplier, that.accessTokenSupplier) + && Objects.equal(userAgents, that.userAgents); } @Override @@ -136,6 +141,7 @@ public int hashCode() { clientConnectorKeyPair, connectionInfoCacheFactory, instances, - accessTokenSupplier); + accessTokenSupplier, + userAgents); } } diff --git a/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/ConnectorRegistry.java b/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/ConnectorRegistry.java index 62acb890..b2fc526f 100644 --- a/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/ConnectorRegistry.java +++ b/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/ConnectorRegistry.java @@ -53,4 +53,14 @@ public static void reset() { public static void shutdown() { InternalConnectorRegistry.INSTANCE.shutdownInstance(); } + + /** + * Adds an external application name to the user agent string for tracking. This is known to be + * used by the spring-cloud-gcp project. + * + * @throws IllegalStateException if the AlloyDB Admin client has already been initialized + */ + public static void addArtifactId(String artifactId) { + InternalConnectorRegistry.INSTANCE.addArtifactId(artifactId); + } } diff --git a/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/DefaultConnectionInfoRepositoryFactory.java b/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/DefaultConnectionInfoRepositoryFactory.java index 823aa503..8dec31ca 100644 --- a/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/DefaultConnectionInfoRepositoryFactory.java +++ b/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/DefaultConnectionInfoRepositoryFactory.java @@ -20,12 +20,15 @@ import com.google.common.util.concurrent.ListeningScheduledExecutorService; import java.io.IOException; -/** Factory for creating a SQLAdmin client that interacts with the real AlloyDB Admin API. */ +/** Factory for creating a AlloyDBAdminClient that interacts with the real AlloyDB Admin API. */ class DefaultConnectionInfoRepositoryFactory implements ConnectionInfoRepositoryFactory { private final ListeningScheduledExecutorService executor; + private final String userAgents; - DefaultConnectionInfoRepositoryFactory(ListeningScheduledExecutorService executor) { + DefaultConnectionInfoRepositoryFactory( + ListeningScheduledExecutorService executor, String userAgents) { this.executor = executor; + this.userAgents = userAgents; } @Override @@ -34,7 +37,8 @@ public DefaultConnectionInfoRepository create( AlloyDBAdminClient alloyDBAdminClient; try { - alloyDBAdminClient = AlloyDBAdminClientFactory.create(credentialFactory.create(), config); + alloyDBAdminClient = + AlloyDBAdminClientFactory.create(credentialFactory.create(), config, userAgents); return new DefaultConnectionInfoRepository(executor, alloyDBAdminClient); } catch (IOException e) { throw new RuntimeException(e); diff --git a/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/InternalConnectorRegistry.java b/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/InternalConnectorRegistry.java index 47498f5e..72df3c4b 100644 --- a/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/InternalConnectorRegistry.java +++ b/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/InternalConnectorRegistry.java @@ -23,6 +23,8 @@ import java.io.Closeable; import java.io.IOException; import java.net.Socket; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; @@ -53,6 +55,11 @@ enum InternalConnectorRegistry implements Closeable { @GuardedBy("shutdownGuard") private boolean shutdown = false; + @SuppressWarnings("ImmutableEnumChecker") + private List userAgents = new ArrayList<>(); + + private static final String USER_AGENT = "alloydb-java-connector/" + Version.VERSION; + InternalConnectorRegistry() { // During refresh, each instance consumes 2 threads from the thread pool. By using 8 threads, // there should be enough free threads so that there will not be a deadlock. Most users @@ -62,6 +69,7 @@ enum InternalConnectorRegistry implements Closeable { this.unnamedConnectors = new ConcurrentHashMap<>(); this.namedConnectors = new ConcurrentHashMap<>(); this.credentialFactoryProvider = new CredentialFactoryProvider(); + this.addArtifactId(USER_AGENT); } /** Test use only: Set a new CredentialFactoryProvider */ @@ -154,6 +162,23 @@ public void shutdownInstance() { } } + /** + * Sets the default string which is appended to the AlloyDB Admin API client User-Agent header. + */ + public void addArtifactId(String artifactId) { + if (!userAgents.contains(artifactId)) { + userAgents.add(artifactId); + } + } + + /** + * Returns the default string which is appended to the AlloyDB Admin API client User-Agent header. + */ + @VisibleForTesting + String getUserAgents() { + return String.join(" ", userAgents); + } + private Connector getConnector(ConnectionConfig config) { return unnamedConnectors.computeIfAbsent( config.getConnectorConfig(), k -> createConnector(config.getConnectorConfig())); @@ -163,7 +188,7 @@ private Connector createConnector(ConnectorConfig config) { CredentialFactory instanceCredentialFactory = credentialFactoryProvider.getInstanceCredentialFactory(config); DefaultConnectionInfoRepositoryFactory connectionInfoRepositoryFactory = - new DefaultConnectionInfoRepositoryFactory(executor); + new DefaultConnectionInfoRepositoryFactory(executor, getUserAgents()); DefaultConnectionInfoRepository connectionInfoRepository = connectionInfoRepositoryFactory.create(instanceCredentialFactory, config); AccessTokenSupplier accessTokenSupplier = @@ -176,7 +201,8 @@ private Connector createConnector(ConnectorConfig config) { RsaKeyPairGenerator.generateKeyPair(), new DefaultConnectionInfoCacheFactory(), new ConcurrentHashMap<>(), - accessTokenSupplier); + accessTokenSupplier, + getUserAgents()); } private Connector getNamedConnector(String name) { diff --git a/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/ConnectorTest.java b/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/ConnectorTest.java index 4a086c60..8d895126 100644 --- a/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/ConnectorTest.java +++ b/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/ConnectorTest.java @@ -47,6 +47,7 @@ public class ConnectorTest { private static final String ERROR_MESSAGE_PERMISSION_DENIED = "Location not found or access is unauthorized."; private static final String ERROR_MESSAGE_INTERNAL = "Internal Error"; + private static final String USER_AGENT = "unit tests"; ListeningScheduledExecutorService defaultExecutor; @@ -160,7 +161,8 @@ private Connector newConnector(ConnectorConfig config, MockAlloyDBAdminGrpc mock TestCertificates.INSTANCE.getClientKey(), new DefaultConnectionInfoCacheFactory(), new ConcurrentHashMap<>(), - accessTokenSupplier); + accessTokenSupplier, + USER_AGENT); } private String readLine(Socket socket) throws IOException { diff --git a/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/ITConnectorTest.java b/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/ITConnectorTest.java index a757ae81..19e50802 100644 --- a/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/ITConnectorTest.java +++ b/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/ITConnectorTest.java @@ -44,6 +44,7 @@ public class ITConnectorTest { private ConnectionInfoRepository connectionInfoRepo; private CredentialFactoryProvider credentialFactoryProvider; private AccessTokenSupplier accessTokenSupplier; + private static final String USER_AGENT = "integration tests"; @Before public void setUp() throws IOException { @@ -54,7 +55,8 @@ public void setUp() throws IOException { credentialFactoryProvider = new CredentialFactoryProvider(); CredentialFactory instanceCredentialFactory = credentialFactoryProvider.getInstanceCredentialFactory(config); - connectionInfoRepositoryFactory = new DefaultConnectionInfoRepositoryFactory(executor); + connectionInfoRepositoryFactory = + new DefaultConnectionInfoRepositoryFactory(executor, USER_AGENT); connectionInfoRepo = connectionInfoRepositoryFactory.create(instanceCredentialFactory, config); accessTokenSupplier = new DefaultAccessTokenSupplier(instanceCredentialFactory); } @@ -87,7 +89,8 @@ public void testConnect_createsSocketConnection() throws IOException { RsaKeyPairGenerator.generateKeyPair(), new DefaultConnectionInfoCacheFactory(), new ConcurrentHashMap<>(), - accessTokenSupplier); + accessTokenSupplier, + USER_AGENT); socket = (SSLSocket) connector.connect(config); @@ -132,7 +135,8 @@ public void testConnect_whenTlsHandshakeFails() clientConnectorKeyPair, connectionInfoCacheFactory, new ConcurrentHashMap<>(), - accessTokenSupplier); + accessTokenSupplier, + USER_AGENT); socket = (SSLSocket) connector.connect(config); } catch (ConnectException ignore) { // The socket connect will fail because it's trying to connect to localhost with TLS certs. @@ -174,7 +178,8 @@ public void testEquals() { clientConnectorKeyPair, connectionInfoCacheFactory, new ConcurrentHashMap<>(), - accessTokenSupplier); + accessTokenSupplier, + USER_AGENT); assertThat(a) .isNotEqualTo( @@ -185,7 +190,8 @@ public void testEquals() { clientConnectorKeyPair, connectionInfoCacheFactory, new ConcurrentHashMap<>(), - accessTokenSupplier)); + accessTokenSupplier, + USER_AGENT)); assertThat(a) .isNotEqualTo( @@ -196,7 +202,8 @@ public void testEquals() { clientConnectorKeyPair, connectionInfoCacheFactory, new ConcurrentHashMap<>(), - accessTokenSupplier)); + accessTokenSupplier, + USER_AGENT)); assertThat(a) .isNotEqualTo( @@ -207,7 +214,8 @@ public void testEquals() { clientConnectorKeyPair, connectionInfoCacheFactory, new ConcurrentHashMap<>(), - accessTokenSupplier)); + accessTokenSupplier, + USER_AGENT)); assertThat(a) .isNotEqualTo( @@ -218,7 +226,8 @@ public void testEquals() { RsaKeyPairGenerator.generateKeyPair(), // Different connectionInfoCacheFactory, new ConcurrentHashMap<>(), - accessTokenSupplier)); + accessTokenSupplier, + USER_AGENT)); assertThat(a) .isNotEqualTo( @@ -229,7 +238,8 @@ public void testEquals() { clientConnectorKeyPair, new DefaultConnectionInfoCacheFactory(), // Different new ConcurrentHashMap<>(), - accessTokenSupplier)); + accessTokenSupplier, + USER_AGENT)); assertThat(a) .isNotEqualTo( @@ -240,6 +250,19 @@ public void testEquals() { clientConnectorKeyPair, connectionInfoCacheFactory, new ConcurrentHashMap<>(), + null, // Different + USER_AGENT)); + + assertThat(a) + .isNotEqualTo( + new Connector( + config, + executor, + connectionInfoRepo, + clientConnectorKeyPair, + connectionInfoCacheFactory, + new ConcurrentHashMap<>(), + accessTokenSupplier, null)); // Different } @@ -259,7 +282,8 @@ public void testHashCode() { clientConnectorKeyPair, connectionInfoCacheFactory, instances, - accessTokenSupplier); + accessTokenSupplier, + USER_AGENT); assertThat(a.hashCode()) .isEqualTo( @@ -270,6 +294,7 @@ public void testHashCode() { clientConnectorKeyPair, connectionInfoCacheFactory, instances, - accessTokenSupplier)); + accessTokenSupplier, + USER_AGENT)); } } diff --git a/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/ITDefaultConnectionInfoRepositoryTest.java b/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/ITDefaultConnectionInfoRepositoryTest.java index fcc73e00..3072fe36 100644 --- a/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/ITDefaultConnectionInfoRepositoryTest.java +++ b/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/ITDefaultConnectionInfoRepositoryTest.java @@ -39,6 +39,7 @@ public class ITDefaultConnectionInfoRepositoryTest { private KeyPair keyPair; private String instanceUri; private ListeningScheduledExecutorService executor; + private static final String USER_AGENT = "integration tests"; @Before public void setUp() throws Exception { @@ -61,7 +62,7 @@ public void setUp() throws Exception { CredentialFactory instanceCredentialFactory = credentialFactoryProvider.getInstanceCredentialFactory(config); ConnectionInfoRepositoryFactory connectionInfoRepositoryFactory = - new DefaultConnectionInfoRepositoryFactory(executor); + new DefaultConnectionInfoRepositoryFactory(executor, USER_AGENT); defaultConnectionInfoRepository = connectionInfoRepositoryFactory.create(instanceCredentialFactory, config); } diff --git a/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/InternalConnectorRegistryTest.java b/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/InternalConnectorRegistryTest.java index b3c0c368..32a59579 100644 --- a/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/InternalConnectorRegistryTest.java +++ b/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/InternalConnectorRegistryTest.java @@ -187,4 +187,12 @@ public void connect_failOnNamedConnectionAfterResetInstance() throws Interrupted .hasMessageThat() .contains(String.format("Named connection %s does not exist.", namedConnector)); } + + @Test + public void addArtifactId_checkUserAgents() { + String artifactId = "unit testing"; + InternalConnectorRegistry.INSTANCE.addArtifactId(artifactId); + + assertThat(InternalConnectorRegistry.INSTANCE.getUserAgents()).contains(artifactId); + } }