From af37fb3c05dd486862d25a3aaa012307da535a22 Mon Sep 17 00:00:00 2001 From: "Jonathan Hess (he/him)" <103529393+hessjcg@users.noreply.github.com> Date: Mon, 30 Oct 2023 15:03:24 -0600 Subject: [PATCH] chore: Extract internal connector registry implementation (#1645) Move the connector registry implementation out of CoreSocketFactory and into InternalConnectorRegistry. CoreSocketFactory is deprecated and contains only the static public methods used informally by other projects. It delegates to the InternalConnectorRegistry. --- .../cloud/sql/core/CoreSocketFactory.java | 337 +---------------- .../sql/core/InternalConnectorRegistry.java | 358 ++++++++++++++++++ .../google/cloud/sql/core/package-info.java | 6 +- ...ultConnectionInfoCacheConcurrencyTest.java | 2 +- ...ava => InternalConnectorRegistryTest.java} | 95 ++--- .../cloud/sql/mariadb/SocketFactory.java | 8 +- .../google/cloud/sql/mysql/SocketFactory.java | 8 +- .../cloud/sql/postgres/SocketFactory.java | 8 +- .../cloud/sql/sqlserver/SocketFactory.java | 8 +- .../sql/core/CloudSqlConnectionFactory.java | 4 +- .../core/GcpConnectionFactoryProvider.java | 4 +- .../GcpConnectionFactoryProviderTest.java | 10 +- .../GcpConnectionFactoryProviderMysql.java | 2 +- .../GcpConnectionFactoryProviderPostgres.java | 2 +- .../GcpConnectionFactoryProviderMssql.java | 2 +- 15 files changed, 448 insertions(+), 406 deletions(-) create mode 100644 core/src/main/java/com/google/cloud/sql/core/InternalConnectorRegistry.java rename core/src/test/java/com/google/cloud/sql/core/{CoreSocketFactoryTest.java => InternalConnectorRegistryTest.java} (82%) diff --git a/core/src/main/java/com/google/cloud/sql/core/CoreSocketFactory.java b/core/src/main/java/com/google/cloud/sql/core/CoreSocketFactory.java index 96800a8ba..9a594cb8d 100644 --- a/core/src/main/java/com/google/cloud/sql/core/CoreSocketFactory.java +++ b/core/src/main/java/com/google/cloud/sql/core/CoreSocketFactory.java @@ -16,42 +16,15 @@ package com.google.cloud.sql.core; -import com.google.api.client.http.HttpRequestInitializer; import com.google.cloud.sql.ConnectionConfig; -import com.google.cloud.sql.CredentialFactory; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.ListeningScheduledExecutorService; -import com.google.common.util.concurrent.MoreExecutors; -import java.io.File; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.logging.Logger; -import javax.net.ssl.SSLSocket; -import jnr.unixsocket.UnixSocketAddress; -import jnr.unixsocket.UnixSocketChannel; /** - * Factory responsible for obtaining an ephemeral certificate, if necessary, and establishing a - * secure connecting to a Cloud SQL instance. + * Implementation of informally used Java API to preserve compatibility with older code that uses + * CoreSocketFactory. * - *

This class should not be used directly, but only through the JDBC driver specific {@code - * SocketFactory} implementations. - * - *

The API of this class is subject to change without notice. + * @deprecated This will soon be replaced. */ +@Deprecated public final class CoreSocketFactory { /** @@ -98,303 +71,11 @@ public final class CoreSocketFactory { * @deprecated Use {@link #setApplicationName(String)} to set the application name * programmatically. */ - @Deprecated public static final String USER_TOKEN_PROPERTY_NAME = "_CLOUD_SQL_USER_TOKEN"; - - static final long DEFAULT_MAX_REFRESH_MS = 30000; - private static final Logger logger = Logger.getLogger(CoreSocketFactory.class.getName()); - - private static final int DEFAULT_SERVER_PROXY_PORT = 3307; - private static final int RSA_KEY_SIZE = 2048; - private static final List userAgents = new ArrayList<>(); - private static final String version = getVersion(); - private static final long MIN_REFRESH_DELAY_MS = 30000; // Minimum 30 seconds between refresh. - private static CoreSocketFactory coreSocketFactory; - private final ListenableFuture localKeyPair; - private final ConcurrentHashMap connectionInfoCaches = - new ConcurrentHashMap<>(); - private final ListeningScheduledExecutorService executor; - private final CredentialFactory credentialFactory; - private final int serverProxyPort; - private final long refreshTimeoutMs; - private final ConnectionInfoRepositoryFactory connectionInfoRepositoryFactory; - - @VisibleForTesting - CoreSocketFactory( - ListenableFuture localKeyPair, - ConnectionInfoRepositoryFactory connectionInfoRepositoryFactory, - CredentialFactory credentialFactory, - int serverProxyPort, - long refreshTimeoutMs, - ListeningScheduledExecutorService executor) { - this.connectionInfoRepositoryFactory = connectionInfoRepositoryFactory; - this.credentialFactory = credentialFactory; - this.serverProxyPort = serverProxyPort; - this.executor = executor; - this.localKeyPair = localKeyPair; - this.refreshTimeoutMs = refreshTimeoutMs; - } - - /** Returns the {@link CoreSocketFactory} singleton. */ - public static synchronized CoreSocketFactory getInstance() { - if (coreSocketFactory == null) { - logger.info("First Cloud SQL connection, generating RSA key pair."); - - CredentialFactory credentialFactory = CredentialFactoryProvider.getCredentialFactory(); - - ListeningScheduledExecutorService executor = getDefaultExecutor(); - - coreSocketFactory = - new CoreSocketFactory( - executor.submit(CoreSocketFactory::generateRsaKeyPair), - new DefaultConnectionInfoRepositoryFactory(getUserAgents()), - credentialFactory, - DEFAULT_SERVER_PROXY_PORT, - CoreSocketFactory.DEFAULT_MAX_REFRESH_MS, - executor); - } - return coreSocketFactory; - } - - // TODO(kvg): Figure out better executor to use for testing - @VisibleForTesting - // Returns a listenable, scheduled executor that exits upon shutdown. - static ListeningScheduledExecutorService getDefaultExecutor() { - - // 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 - // configure 3 or fewer instances, requiring 6 threads during refresh. By setting - // this to 8, it's enough threads for most users, plus a safety factor of 2. - - ScheduledThreadPoolExecutor executor = - (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(8); - - executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); - return MoreExecutors.listeningDecorator( - MoreExecutors.getExitingScheduledExecutorService(executor)); - } - - /** Extracts the Unix socket argument from specified properties object. If unset, returns null. */ - private static String getUnixSocketArg(ConnectionConfig config) { - String unixSocketPath = config.getUnixSocketPath(); - if (unixSocketPath != null) { - // Get the Unix socket file path from the properties object - return unixSocketPath; - } else if (System.getenv("CLOUD_SQL_FORCE_UNIX_SOCKET") != null) { - // If the deprecated env var is set, warn and use `/cloudsql/INSTANCE_CONNECTION_NAME` - // A socket factory is provided at this path for GAE, GCF, and Cloud Run - logger.warning( - String.format( - "\"CLOUD_SQL_FORCE_UNIX_SOCKET\" env var has been deprecated. Please use" - + " '%s=\"/cloudsql/INSTANCE_CONNECTION_NAME\"' property in your JDBC url" - + " instead.", - ConnectionConfig.UNIX_SOCKET_PROPERTY)); - return "/cloudsql/" + config.getCloudSqlInstance(); - } - return null; // if unset, default to null - } - - /** Creates a socket representing a connection to a Cloud SQL instance. */ - public static Socket connect(Properties props) throws IOException, InterruptedException { - return connect(props, null); - } - - /** - * Creates a socket representing a connection to a Cloud SQL instance. - * - *

Depending on the given properties, it may return either a SSL Socket or a Unix Socket. - * - * @param props Properties used to configure the connection. - * @param unixPathSuffix suffix to add the the Unix socket path. Unused if null. - * @return the newly created Socket. - * @throws IOException if error occurs during socket creation. - */ - public static Socket connect(Properties props, String unixPathSuffix) - throws IOException, InterruptedException { - // Gather parameters - - ConnectionConfig config = ConnectionConfig.fromConnectionProperties(props); - - // Validate parameters - Preconditions.checkArgument( - config.getCloudSqlInstance() != null, - "cloudSqlInstance property not set. Please specify this property in the JDBC URL or the " - + "connection Properties with value in form \"project:region:instance\""); - - // Connect using the specified Unix socket - String unixSocket = getUnixSocketArg(config); - if (unixSocket != null) { - // Verify it ends with the correct suffix - if (unixPathSuffix != null && !unixSocket.endsWith(unixPathSuffix)) { - unixSocket = unixSocket + unixPathSuffix; - } - logger.info( - String.format( - "Connecting to Cloud SQL instance [%s] via unix socket at %s.", - config.getCloudSqlInstance(), unixSocket)); - UnixSocketAddress socketAddress = new UnixSocketAddress(new File(unixSocket)); - return UnixSocketChannel.open(socketAddress).socket(); - } - - return getInstance().createSslSocket(config); - } - - /** Returns data that can be used to establish Cloud SQL SSL connection. */ - public static SslData getSslData(ConnectionConfig config) throws IOException { - CoreSocketFactory instance = getInstance(); - return instance.getConnectionInfoCache(config).getSslData(instance.refreshTimeoutMs); - } - - /** Returns preferred ip address that can be used to establish Cloud SQL connection. */ - public static String getHostIp(ConnectionConfig config) throws IOException { - CoreSocketFactory instance = getInstance(); - return instance - .getConnectionInfoCache(config) - .getPreferredIp(config.getIpTypes(), instance.refreshTimeoutMs); - } - - private static KeyPair generateRsaKeyPair() { - KeyPairGenerator generator; - try { - generator = KeyPairGenerator.getInstance("RSA"); - } catch (NoSuchAlgorithmException err) { - throw new RuntimeException( - "Unable to initialize Cloud SQL socket factory because no RSA implementation is " - + "available."); - } - generator.initialize(RSA_KEY_SIZE); - return generator.generateKeyPair(); - } - - private static String getVersion() { - try { - Properties packageInfo = new Properties(); - packageInfo.load( - CoreSocketFactory.class - .getClassLoader() - .getResourceAsStream("com.google.cloud.sql/project.properties")); - return packageInfo.getProperty("version", "unknown"); - } catch (IOException e) { - return "unknown"; - } - } - - /** - * Internal use only: Sets the default string which is appended to the SQLAdmin API client - * User-Agent header. - * - *

This is used by the specific database connector socket factory implementations to append - * their database name to the user agent. - */ - public static void addArtifactId(String artifactId) { - String userAgent = artifactId + "/" + version; - if (!userAgents.contains(userAgent)) { - userAgents.add(userAgent); - } - } - - /** Resets the values of User Agent fields for unit tests. */ - @VisibleForTesting - static void resetUserAgent() { - coreSocketFactory = null; - userAgents.clear(); - setApplicationName(""); - } - - /** Returns the default string which is appended to the SQLAdmin API client User-Agent header. */ - static String getUserAgents() { - String ua = String.join(" ", userAgents); - String appName = getApplicationName(); - if (!Strings.isNullOrEmpty(appName)) { - ua = ua + " " + appName; - } - return ua; - } - - /** Returns the current User-Agent header set for the underlying SQLAdmin API client. */ - private static String getApplicationName() { - return System.getProperty(USER_TOKEN_PROPERTY_NAME, ""); - } - - /** - * 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 SQLAdmin client has already been initialized - */ - public static void setApplicationName(String applicationName) { - if (coreSocketFactory != null) { - throw new IllegalStateException( - "Unable to set ApplicationName - SQLAdmin client already initialized."); - } - System.setProperty(USER_TOKEN_PROPERTY_NAME, applicationName); - } - - /** - * Creates a secure socket representing a connection to a Cloud SQL instance. - * - * @return the newly created Socket. - * @throws IOException if error occurs during socket creation. - */ - // TODO(berezv): separate creating socket and performing connection to make it easier to test - @VisibleForTesting - Socket createSslSocket(ConnectionConfig config) throws IOException, InterruptedException { - DefaultConnectionInfoCache connectionInfoCache = getConnectionInfoCache(config); - - try { - SSLSocket socket = connectionInfoCache.createSslSocket(this.refreshTimeoutMs); - - // TODO(kvg): Support all socket related options listed here: - // https://dev.mysql.com/doc/connector-j/en/connector-j-reference-configuration-properties.html - socket.setKeepAlive(true); - socket.setTcpNoDelay(true); - - String instanceIp = connectionInfoCache.getPreferredIp(config.getIpTypes(), refreshTimeoutMs); - - socket.connect(new InetSocketAddress(instanceIp, serverProxyPort)); - socket.startHandshake(); - - return socket; - } catch (Exception ex) { - // TODO(kvg): Let user know about the rate limit - connectionInfoCache.forceRefresh(); - throw ex; - } - } - - DefaultConnectionInfoCache getConnectionInfoCache(ConnectionConfig config) { - return connectionInfoCaches.computeIfAbsent( - config.getCloudSqlInstance(), k -> newConnectionInfoCache(config)); - } - - private DefaultConnectionInfoCache newConnectionInfoCache(ConnectionConfig config) { - - final CredentialFactory instanceCredentialFactory; - if (config.getTargetPrincipal() != null && !config.getTargetPrincipal().isEmpty()) { - instanceCredentialFactory = - new ServiceAccountImpersonatingCredentialFactory( - credentialFactory, config.getTargetPrincipal(), config.getDelegates()); - } else { - if (config.getDelegates() != null && !config.getDelegates().isEmpty()) { - throw new IllegalArgumentException( - String.format( - "Connection property %s must be when %s is set.", - ConnectionConfig.CLOUD_SQL_TARGET_PRINCIPAL_PROPERTY, - ConnectionConfig.CLOUD_SQL_DELEGATES_PROPERTY)); - } - instanceCredentialFactory = credentialFactory; - } - - HttpRequestInitializer credential = instanceCredentialFactory.create(); - DefaultConnectionInfoRepository adminApi = - connectionInfoRepositoryFactory.create(credential, config); + @Deprecated + public static final String USER_TOKEN_PROPERTY_NAME = + InternalConnectorRegistry.USER_TOKEN_PROPERTY_NAME; - return new DefaultConnectionInfoCache( - config.getCloudSqlInstance(), - adminApi, - config.getAuthType(), - instanceCredentialFactory, - executor, - localKeyPair, - MIN_REFRESH_DELAY_MS); + static void setApplicationName(String artifactId) { + InternalConnectorRegistry.setApplicationName(artifactId); } } diff --git a/core/src/main/java/com/google/cloud/sql/core/InternalConnectorRegistry.java b/core/src/main/java/com/google/cloud/sql/core/InternalConnectorRegistry.java new file mode 100644 index 000000000..dc251a387 --- /dev/null +++ b/core/src/main/java/com/google/cloud/sql/core/InternalConnectorRegistry.java @@ -0,0 +1,358 @@ +/* + * Copyright 2023 Google LLC + * + * 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 com.google.cloud.sql.core; + +import com.google.api.client.http.HttpRequestInitializer; +import com.google.cloud.sql.ConnectionConfig; +import com.google.cloud.sql.CredentialFactory; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningScheduledExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import java.io.File; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.logging.Logger; +import javax.net.ssl.SSLSocket; +import jnr.unixsocket.UnixSocketAddress; +import jnr.unixsocket.UnixSocketChannel; + +/** + * InternalConnectorRegistry keeps track of connectors. This class should not be used directly, but + * only through the JDBC driver specific {@code SocketFactory} implementations. + * + *

WARNING: This is an internal class. The API is subject to change without notice. + */ +public final class InternalConnectorRegistry { + static final long DEFAULT_MAX_REFRESH_MS = 30000; + private static final Logger logger = Logger.getLogger(InternalConnectorRegistry.class.getName()); + + private static final int DEFAULT_SERVER_PROXY_PORT = 3307; + private static final int RSA_KEY_SIZE = 2048; + private static final List userAgents = new ArrayList<>(); + private static final String version = getVersion(); + private static final long MIN_REFRESH_DELAY_MS = 30000; // Minimum 30 seconds between refresh. + private static InternalConnectorRegistry internalConnectorRegistry; + private final ListenableFuture localKeyPair; + private final ConcurrentHashMap connectionInfoCaches = + new ConcurrentHashMap<>(); + private final ListeningScheduledExecutorService executor; + private final CredentialFactory credentialFactory; + private final int serverProxyPort; + private final long refreshTimeoutMs; + private final ConnectionInfoRepositoryFactory connectionInfoRepositoryFactory; + + /** + * Property used to set the application name for the underlying SQLAdmin client. + * + * @deprecated Use {@link #setApplicationName(String)} to set the application name + * programmatically. + */ + static final String USER_TOKEN_PROPERTY_NAME = "_CLOUD_SQL_USER_TOKEN"; + + @VisibleForTesting + InternalConnectorRegistry( + ListenableFuture localKeyPair, + ConnectionInfoRepositoryFactory connectionInfoRepositoryFactory, + CredentialFactory credentialFactory, + int serverProxyPort, + long refreshTimeoutMs, + ListeningScheduledExecutorService executor) { + this.connectionInfoRepositoryFactory = connectionInfoRepositoryFactory; + this.credentialFactory = credentialFactory; + this.serverProxyPort = serverProxyPort; + this.executor = executor; + this.localKeyPair = localKeyPair; + this.refreshTimeoutMs = refreshTimeoutMs; + } + + /** Returns the {@link InternalConnectorRegistry} singleton. */ + public static synchronized InternalConnectorRegistry getInstance() { + if (internalConnectorRegistry == null) { + logger.info("First Cloud SQL connection, generating RSA key pair."); + + CredentialFactory credentialFactory = CredentialFactoryProvider.getCredentialFactory(); + + ListeningScheduledExecutorService executor = getDefaultExecutor(); + + internalConnectorRegistry = + new InternalConnectorRegistry( + executor.submit(InternalConnectorRegistry::generateRsaKeyPair), + new DefaultConnectionInfoRepositoryFactory(getUserAgents()), + credentialFactory, + DEFAULT_SERVER_PROXY_PORT, + InternalConnectorRegistry.DEFAULT_MAX_REFRESH_MS, + executor); + } + return internalConnectorRegistry; + } + + // TODO(kvg): Figure out better executor to use for testing + @VisibleForTesting + // Returns a listenable, scheduled executor that exits upon shutdown. + static ListeningScheduledExecutorService getDefaultExecutor() { + + // 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 + // configure 3 or fewer instances, requiring 6 threads during refresh. By setting + // this to 8, it's enough threads for most users, plus a safety factor of 2. + + ScheduledThreadPoolExecutor executor = + (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(8); + + executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); + return MoreExecutors.listeningDecorator( + MoreExecutors.getExitingScheduledExecutorService(executor)); + } + + /** Extracts the Unix socket argument from specified properties object. If unset, returns null. */ + private static String getUnixSocketArg(ConnectionConfig config) { + String unixSocketPath = config.getUnixSocketPath(); + if (unixSocketPath != null) { + // Get the Unix socket file path from the properties object + return unixSocketPath; + } else if (System.getenv("CLOUD_SQL_FORCE_UNIX_SOCKET") != null) { + // If the deprecated env var is set, warn and use `/cloudsql/INSTANCE_CONNECTION_NAME` + // A socket factory is provided at this path for GAE, GCF, and Cloud Run + logger.warning( + String.format( + "\"CLOUD_SQL_FORCE_UNIX_SOCKET\" env var has been deprecated. Please use" + + " '%s=\"/cloudsql/INSTANCE_CONNECTION_NAME\"' property in your JDBC url" + + " instead.", + ConnectionConfig.UNIX_SOCKET_PROPERTY)); + return "/cloudsql/" + config.getCloudSqlInstance(); + } + return null; // if unset, default to null + } + + /** Creates a socket representing a connection to a Cloud SQL instance. */ + public static Socket connect(Properties props) throws IOException, InterruptedException { + return connect(props, null); + } + + /** + * Creates a socket representing a connection to a Cloud SQL instance. + * + *

Depending on the given properties, it may return either a SSL Socket or a Unix Socket. + * + * @param props Properties used to configure the connection. + * @param unixPathSuffix suffix to add the the Unix socket path. Unused if null. + * @return the newly created Socket. + * @throws IOException if error occurs during socket creation. + */ + public static Socket connect(Properties props, String unixPathSuffix) + throws IOException, InterruptedException { + // Gather parameters + + ConnectionConfig config = ConnectionConfig.fromConnectionProperties(props); + + // Validate parameters + Preconditions.checkArgument( + config.getCloudSqlInstance() != null, + "cloudSqlInstance property not set. Please specify this property in the JDBC URL or the " + + "connection Properties with value in form \"project:region:instance\""); + + // Connect using the specified Unix socket + String unixSocket = getUnixSocketArg(config); + if (unixSocket != null) { + // Verify it ends with the correct suffix + if (unixPathSuffix != null && !unixSocket.endsWith(unixPathSuffix)) { + unixSocket = unixSocket + unixPathSuffix; + } + logger.info( + String.format( + "Connecting to Cloud SQL instance [%s] via unix socket at %s.", + config.getCloudSqlInstance(), unixSocket)); + UnixSocketAddress socketAddress = new UnixSocketAddress(new File(unixSocket)); + return UnixSocketChannel.open(socketAddress).socket(); + } + + return getInstance().createSslSocket(config); + } + + /** Returns data that can be used to establish Cloud SQL SSL connection. */ + public static SslData getSslData(ConnectionConfig config) throws IOException { + InternalConnectorRegistry instance = getInstance(); + return instance.getConnectionInfoCache(config).getSslData(instance.refreshTimeoutMs); + } + + /** Returns preferred ip address that can be used to establish Cloud SQL connection. */ + public static String getHostIp(ConnectionConfig config) throws IOException { + InternalConnectorRegistry instance = getInstance(); + return instance + .getConnectionInfoCache(config) + .getPreferredIp(config.getIpTypes(), instance.refreshTimeoutMs); + } + + private static KeyPair generateRsaKeyPair() { + KeyPairGenerator generator; + try { + generator = KeyPairGenerator.getInstance("RSA"); + } catch (NoSuchAlgorithmException err) { + throw new RuntimeException( + "Unable to initialize Cloud SQL socket factory because no RSA implementation is " + + "available."); + } + generator.initialize(RSA_KEY_SIZE); + return generator.generateKeyPair(); + } + + private static String getVersion() { + try { + Properties packageInfo = new Properties(); + packageInfo.load( + InternalConnectorRegistry.class + .getClassLoader() + .getResourceAsStream("com.google.cloud.sql/project.properties")); + return packageInfo.getProperty("version", "unknown"); + } catch (IOException e) { + return "unknown"; + } + } + + /** + * Internal use only: Sets the default string which is appended to the SQLAdmin API client + * User-Agent header. + * + *

This is used by the specific database connector socket factory implementations to append + * their database name to the user agent. + */ + public static void addArtifactId(String artifactId) { + String userAgent = artifactId + "/" + version; + if (!userAgents.contains(userAgent)) { + userAgents.add(userAgent); + } + } + + /** Resets the values of User Agent fields for unit tests. */ + @VisibleForTesting + static void resetUserAgent() { + internalConnectorRegistry = null; + userAgents.clear(); + setApplicationName(""); + } + + /** Returns the default string which is appended to the SQLAdmin API client User-Agent header. */ + static String getUserAgents() { + String ua = String.join(" ", userAgents); + String appName = getApplicationName(); + if (!Strings.isNullOrEmpty(appName)) { + ua = ua + " " + appName; + } + return ua; + } + + /** Returns the current User-Agent header set for the underlying SQLAdmin API client. */ + private static String getApplicationName() { + return System.getProperty(USER_TOKEN_PROPERTY_NAME, ""); + } + + /** + * 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 SQLAdmin client has already been initialized + */ + public static void setApplicationName(String applicationName) { + if (internalConnectorRegistry != null) { + throw new IllegalStateException( + "Unable to set ApplicationName - SQLAdmin client already initialized."); + } + System.setProperty(USER_TOKEN_PROPERTY_NAME, applicationName); + } + + /** + * Creates a secure socket representing a connection to a Cloud SQL instance. + * + * @return the newly created Socket. + * @throws IOException if error occurs during socket creation. + */ + // TODO(berezv): separate creating socket and performing connection to make it easier to test + @VisibleForTesting + Socket createSslSocket(ConnectionConfig config) throws IOException, InterruptedException { + DefaultConnectionInfoCache connectionInfoCache = getConnectionInfoCache(config); + + try { + SSLSocket socket = connectionInfoCache.createSslSocket(this.refreshTimeoutMs); + + // TODO(kvg): Support all socket related options listed here: + // https://dev.mysql.com/doc/connector-j/en/connector-j-reference-configuration-properties.html + socket.setKeepAlive(true); + socket.setTcpNoDelay(true); + + String instanceIp = connectionInfoCache.getPreferredIp(config.getIpTypes(), refreshTimeoutMs); + + socket.connect(new InetSocketAddress(instanceIp, serverProxyPort)); + socket.startHandshake(); + + return socket; + } catch (Exception ex) { + // TODO(kvg): Let user know about the rate limit + connectionInfoCache.forceRefresh(); + throw ex; + } + } + + DefaultConnectionInfoCache getConnectionInfoCache(ConnectionConfig config) { + return connectionInfoCaches.computeIfAbsent( + config.getCloudSqlInstance(), k -> apiFetcher(config)); + } + + private DefaultConnectionInfoCache apiFetcher(ConnectionConfig config) { + + final CredentialFactory instanceCredentialFactory; + if (config.getTargetPrincipal() != null && !config.getTargetPrincipal().isEmpty()) { + instanceCredentialFactory = + new ServiceAccountImpersonatingCredentialFactory( + credentialFactory, config.getTargetPrincipal(), config.getDelegates()); + } else { + if (config.getDelegates() != null && !config.getDelegates().isEmpty()) { + throw new IllegalArgumentException( + String.format( + "Connection property %s must be when %s is set.", + ConnectionConfig.CLOUD_SQL_TARGET_PRINCIPAL_PROPERTY, + ConnectionConfig.CLOUD_SQL_DELEGATES_PROPERTY)); + } + instanceCredentialFactory = credentialFactory; + } + + HttpRequestInitializer credential = instanceCredentialFactory.create(); + DefaultConnectionInfoRepository adminApi = + connectionInfoRepositoryFactory.create(credential, config); + + return new DefaultConnectionInfoCache( + config.getCloudSqlInstance(), + adminApi, + config.getAuthType(), + instanceCredentialFactory, + executor, + localKeyPair, + MIN_REFRESH_DELAY_MS); + } +} diff --git a/core/src/main/java/com/google/cloud/sql/core/package-info.java b/core/src/main/java/com/google/cloud/sql/core/package-info.java index 8643db86f..f483c6271 100644 --- a/core/src/main/java/com/google/cloud/sql/core/package-info.java +++ b/core/src/main/java/com/google/cloud/sql/core/package-info.java @@ -15,8 +15,10 @@ */ /** - * Package com.google.cloud.sql.core holds internal shared packages that implement logic to create a - * socket to a Cloud SQL database. Classes in this package are considered internal and subject to + * WARNING: This package does not contain any stable, public Java API. The class definitions may * change without notice. + * + *

Package com.google.cloud.sql.core holds internal shared packages that implement logic to + * create a socket to a Cloud SQL database. */ package com.google.cloud.sql.core; diff --git a/core/src/test/java/com/google/cloud/sql/core/DefaultConnectionInfoCacheConcurrencyTest.java b/core/src/test/java/com/google/cloud/sql/core/DefaultConnectionInfoCacheConcurrencyTest.java index 05a3baa43..1f966c351 100644 --- a/core/src/test/java/com/google/cloud/sql/core/DefaultConnectionInfoCacheConcurrencyTest.java +++ b/core/src/test/java/com/google/cloud/sql/core/DefaultConnectionInfoCacheConcurrencyTest.java @@ -59,7 +59,7 @@ public void testForceRefreshDoesNotCauseADeadlockOrBrokenRefreshLoop() throws Ex MockAdminApi mockAdminApi = new MockAdminApi(); ListenableFuture keyPairFuture = Futures.immediateFuture(mockAdminApi.getClientKeyPair()); - ListeningScheduledExecutorService executor = CoreSocketFactory.getDefaultExecutor(); + ListeningScheduledExecutorService executor = InternalConnectorRegistry.getDefaultExecutor(); TestDataSupplier supplier = new TestDataSupplier(false); List caches = new ArrayList<>(); diff --git a/core/src/test/java/com/google/cloud/sql/core/CoreSocketFactoryTest.java b/core/src/test/java/com/google/cloud/sql/core/InternalConnectorRegistryTest.java similarity index 82% rename from core/src/test/java/com/google/cloud/sql/core/CoreSocketFactoryTest.java rename to core/src/test/java/com/google/cloud/sql/core/InternalConnectorRegistryTest.java index 7afb26098..73eb2f634 100644 --- a/core/src/test/java/com/google/cloud/sql/core/CoreSocketFactoryTest.java +++ b/core/src/test/java/com/google/cloud/sql/core/InternalConnectorRegistryTest.java @@ -1,11 +1,11 @@ /* - * Copyright 2016 Google Inc. + * Copyright 2023 Google LLC * * 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 + * 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, @@ -42,7 +42,7 @@ // TODO(berezv): add multithreaded test @RunWith(JUnit4.class) -public class CoreSocketFactoryTest extends CloudSqlCoreTestingBase { +public class InternalConnectorRegistryTest extends CloudSqlCoreTestingBase { private final long TEST_MAX_REFRESH_MS = 5000L; ListeningScheduledExecutorService defaultExecutor; @@ -50,7 +50,7 @@ public class CoreSocketFactoryTest extends CloudSqlCoreTestingBase { @Before public void setUp() throws Exception { super.setup(); - defaultExecutor = CoreSocketFactory.getDefaultExecutor(); + defaultExecutor = InternalConnectorRegistry.getDefaultExecutor(); } @After @@ -62,11 +62,11 @@ public void tearDown() throws Exception { public void create_throwsErrorForInvalidInstanceName() throws IOException { ConnectionInfoRepositoryFactory factory = new StubConnectionInfoRepositoryFactory(fakeSuccessHttpTransport(Duration.ofSeconds(0))); - CoreSocketFactory coreSocketFactory = - new CoreSocketFactory( + InternalConnectorRegistry internalConnectorRegistry = + new InternalConnectorRegistry( clientKeyPair, factory, credentialFactory, 3307, TEST_MAX_REFRESH_MS, defaultExecutor); try { - coreSocketFactory.createSslSocket( + internalConnectorRegistry.createSslSocket( new ConnectionConfig.Builder() .withCloudSqlInstance("myProject") .withIpTypes("PRIMARY") @@ -77,7 +77,7 @@ public void create_throwsErrorForInvalidInstanceName() throws IOException { } try { - coreSocketFactory.createSslSocket( + internalConnectorRegistry.createSslSocket( new ConnectionConfig.Builder() .withCloudSqlInstance("myProject:myRegion") .withIpTypes("PRIMARY") @@ -94,11 +94,11 @@ public void create_throwsErrorForInvalidInstanceName() throws IOException { public void create_throwsErrorForInvalidInstanceRegion() throws IOException { ConnectionInfoRepositoryFactory factory = new StubConnectionInfoRepositoryFactory(fakeSuccessHttpTransport(Duration.ofSeconds(0))); - CoreSocketFactory coreSocketFactory = - new CoreSocketFactory( + InternalConnectorRegistry internalConnectorRegistry = + new InternalConnectorRegistry( clientKeyPair, factory, credentialFactory, 3307, TEST_MAX_REFRESH_MS, defaultExecutor); try { - coreSocketFactory.createSslSocket( + internalConnectorRegistry.createSslSocket( new ConnectionConfig.Builder() .withCloudSqlInstance("myProject:notMyRegion:myInstance") .withIpTypes("PRIMARY") @@ -124,11 +124,11 @@ public void create_successfulPrivateConnection() throws IOException, Interrupted ConnectionInfoRepositoryFactory factory = new StubConnectionInfoRepositoryFactory(fakeSuccessHttpTransport(Duration.ofSeconds(0))); - CoreSocketFactory coreSocketFactory = - new CoreSocketFactory( + InternalConnectorRegistry internalConnectorRegistry = + new InternalConnectorRegistry( clientKeyPair, factory, credentialFactory, port, TEST_MAX_REFRESH_MS, defaultExecutor); Socket socket = - coreSocketFactory.createSslSocket( + internalConnectorRegistry.createSslSocket( new ConnectionConfig.Builder() .withCloudSqlInstance("myProject:myRegion:myInstance") .withIpTypes("PRIVATE") @@ -144,12 +144,12 @@ public void create_failOnEmptyTargetPrincipal() throws IOException, InterruptedE ConnectionInfoRepositoryFactory factory = new StubConnectionInfoRepositoryFactory(fakeSuccessHttpTransport(Duration.ofSeconds(0))); - CoreSocketFactory coreSocketFactory = - new CoreSocketFactory( + InternalConnectorRegistry internalConnectorRegistry = + new InternalConnectorRegistry( clientKeyPair, factory, credentialFactory, port, TEST_MAX_REFRESH_MS, defaultExecutor); try { - coreSocketFactory.createSslSocket( + internalConnectorRegistry.createSslSocket( new ConnectionConfig.Builder() .withCloudSqlInstance("myProject:myRegion:myInstance") .withIpTypes("PRIMARY") @@ -168,12 +168,12 @@ public void create_successfulConnection() throws IOException, InterruptedExcepti ConnectionInfoRepositoryFactory factory = new StubConnectionInfoRepositoryFactory(fakeSuccessHttpTransport(Duration.ofSeconds(0))); - CoreSocketFactory coreSocketFactory = - new CoreSocketFactory( + InternalConnectorRegistry internalConnectorRegistry = + new InternalConnectorRegistry( clientKeyPair, factory, credentialFactory, port, TEST_MAX_REFRESH_MS, defaultExecutor); Socket socket = - coreSocketFactory.createSslSocket( + internalConnectorRegistry.createSslSocket( new ConnectionConfig.Builder() .withCloudSqlInstance("myProject:myRegion:myInstance") .withIpTypes("PRIMARY") @@ -189,11 +189,11 @@ public void create_successfulDomainScopedConnection() throws IOException, Interr ConnectionInfoRepositoryFactory factory = new StubConnectionInfoRepositoryFactory(fakeSuccessHttpTransport(Duration.ofSeconds(0))); - CoreSocketFactory coreSocketFactory = - new CoreSocketFactory( + InternalConnectorRegistry internalConnectorRegistry = + new InternalConnectorRegistry( clientKeyPair, factory, credentialFactory, port, TEST_MAX_REFRESH_MS, defaultExecutor); Socket socket = - coreSocketFactory.createSslSocket( + internalConnectorRegistry.createSslSocket( new ConnectionConfig.Builder() .withCloudSqlInstance("example.com:myProject:myRegion:myInstance") .withIpTypes("PRIMARY") @@ -205,12 +205,12 @@ public void create_successfulDomainScopedConnection() throws IOException, Interr public void create_adminApiNotEnabled() throws IOException { ConnectionInfoRepositoryFactory factory = new StubConnectionInfoRepositoryFactory(fakeNotConfiguredException()); - CoreSocketFactory coreSocketFactory = - new CoreSocketFactory( + InternalConnectorRegistry internalConnectorRegistry = + new InternalConnectorRegistry( clientKeyPair, factory, credentialFactory, 3307, TEST_MAX_REFRESH_MS, defaultExecutor); try { // Use a different project to get Api Not Enabled Error. - coreSocketFactory.createSslSocket( + internalConnectorRegistry.createSslSocket( new ConnectionConfig.Builder() .withCloudSqlInstance("NotMyProject:myRegion:myInstance") .withIpTypes("PRIMARY") @@ -230,12 +230,12 @@ public void create_adminApiNotEnabled() throws IOException { public void create_notAuthorized() throws IOException { ConnectionInfoRepositoryFactory factory = new StubConnectionInfoRepositoryFactory(fakeNotAuthorizedException()); - CoreSocketFactory coreSocketFactory = - new CoreSocketFactory( + InternalConnectorRegistry internalConnectorRegistry = + new InternalConnectorRegistry( clientKeyPair, factory, credentialFactory, 3307, TEST_MAX_REFRESH_MS, defaultExecutor); try { // Use a different instance to simulate incorrect permissions. - coreSocketFactory.createSslSocket( + internalConnectorRegistry.createSslSocket( new ConnectionConfig.Builder() .withCloudSqlInstance("myProject:myRegion:NotMyInstance") .withIpTypes("PRIMARY") @@ -264,8 +264,8 @@ public void supportsCustomCredentialFactoryWithIAM() throws InterruptedException ConnectionInfoRepositoryFactory factory = new StubConnectionInfoRepositoryFactory(fakeSuccessHttpTransport(Duration.ofSeconds(0))); - CoreSocketFactory coreSocketFactory = - new CoreSocketFactory( + InternalConnectorRegistry internalConnectorRegistry = + new InternalConnectorRegistry( clientKeyPair, factory, stubCredentialFactory, @@ -273,7 +273,7 @@ public void supportsCustomCredentialFactoryWithIAM() throws InterruptedException TEST_MAX_REFRESH_MS, defaultExecutor); Socket socket = - coreSocketFactory.createSslSocket( + internalConnectorRegistry.createSslSocket( new ConnectionConfig.Builder() .withCloudSqlInstance("myProject:myRegion:myInstance") .withIpTypes("PRIMARY") @@ -293,8 +293,8 @@ public void supportsCustomCredentialFactoryWithNoExpirationTime() ConnectionInfoRepositoryFactory factory = new StubConnectionInfoRepositoryFactory(fakeSuccessHttpTransport(Duration.ofSeconds(0))); - CoreSocketFactory coreSocketFactory = - new CoreSocketFactory( + InternalConnectorRegistry internalConnectorRegistry = + new InternalConnectorRegistry( clientKeyPair, factory, stubCredentialFactory, @@ -302,7 +302,7 @@ public void supportsCustomCredentialFactoryWithNoExpirationTime() TEST_MAX_REFRESH_MS, defaultExecutor); Socket socket = - coreSocketFactory.createSslSocket( + internalConnectorRegistry.createSslSocket( new ConnectionConfig.Builder() .withCloudSqlInstance("myProject:myRegion:myInstance") .withIpTypes("PRIMARY") @@ -329,8 +329,8 @@ public HttpRequestInitializer create() { ConnectionInfoRepositoryFactory factory = new StubConnectionInfoRepositoryFactory(fakeSuccessHttpTransport(Duration.ofSeconds(0))); - CoreSocketFactory coreSocketFactory = - new CoreSocketFactory( + InternalConnectorRegistry internalConnectorRegistry = + new InternalConnectorRegistry( clientKeyPair, factory, stubCredentialFactory, @@ -340,7 +340,7 @@ public HttpRequestInitializer create() { assertThrows( RuntimeException.class, () -> - coreSocketFactory.createSslSocket( + internalConnectorRegistry.createSslSocket( new ConnectionConfig.Builder() .withCloudSqlInstance("myProject:myRegion:myInstance") .withIpTypes("PRIMARY") @@ -350,20 +350,21 @@ public HttpRequestInitializer create() { @Test public void testGetApplicationNameWithApplicationName() { - CoreSocketFactory.resetUserAgent(); - CoreSocketFactory.setApplicationName("sample-app"); - CoreSocketFactory.addArtifactId("unit-test"); - CoreSocketFactory.getInstance(); - assertThat(CoreSocketFactory.getUserAgents()).startsWith("unit-test/"); - assertThat(CoreSocketFactory.getUserAgents()).endsWith(" sample-app"); + InternalConnectorRegistry.resetUserAgent(); + InternalConnectorRegistry.setApplicationName("sample-app"); + InternalConnectorRegistry.addArtifactId("unit-test"); + InternalConnectorRegistry.getInstance(); + assertThat(InternalConnectorRegistry.getUserAgents()).startsWith("unit-test/"); + assertThat(InternalConnectorRegistry.getUserAgents()).endsWith(" sample-app"); } @Test public void testGetApplicationNameFailsAfterInitialization() { - CoreSocketFactory.resetUserAgent(); - CoreSocketFactory.getInstance(); + InternalConnectorRegistry.resetUserAgent(); + InternalConnectorRegistry.getInstance(); assertThrows( - IllegalStateException.class, () -> CoreSocketFactory.setApplicationName("sample-app")); + IllegalStateException.class, + () -> InternalConnectorRegistry.setApplicationName("sample-app")); } private String readLine(Socket socket) throws IOException { diff --git a/jdbc/mariadb/src/main/java/com/google/cloud/sql/mariadb/SocketFactory.java b/jdbc/mariadb/src/main/java/com/google/cloud/sql/mariadb/SocketFactory.java index a0fedc01a..1ca01f600 100644 --- a/jdbc/mariadb/src/main/java/com/google/cloud/sql/mariadb/SocketFactory.java +++ b/jdbc/mariadb/src/main/java/com/google/cloud/sql/mariadb/SocketFactory.java @@ -16,7 +16,7 @@ package com.google.cloud.sql.mariadb; -import com.google.cloud.sql.core.CoreSocketFactory; +import com.google.cloud.sql.core.InternalConnectorRegistry; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; @@ -27,12 +27,12 @@ * A MariaDB {@link SocketFactory} that establishes a secure connection to a Cloud SQL instance * using ephemeral certificates. * - *

The heavy lifting is done by the singleton {@link CoreSocketFactory} class. + *

The heavy lifting is done by the singleton {@link InternalConnectorRegistry} class. */ public class SocketFactory extends ConfigurableSocketFactory { static { - CoreSocketFactory.addArtifactId("mariadb-socket-factory"); + InternalConnectorRegistry.addArtifactId("mariadb-socket-factory"); } private Configuration conf; @@ -48,7 +48,7 @@ public void setConfiguration(Configuration conf, String host) { @Override public Socket createSocket() throws IOException { try { - return CoreSocketFactory.connect(conf.nonMappedOptions()); + return InternalConnectorRegistry.connect(conf.nonMappedOptions()); } catch (InterruptedException e) { throw new RuntimeException(e); } diff --git a/jdbc/mysql-j-8/src/main/java/com/google/cloud/sql/mysql/SocketFactory.java b/jdbc/mysql-j-8/src/main/java/com/google/cloud/sql/mysql/SocketFactory.java index 623d13dc3..05e52c4bb 100644 --- a/jdbc/mysql-j-8/src/main/java/com/google/cloud/sql/mysql/SocketFactory.java +++ b/jdbc/mysql-j-8/src/main/java/com/google/cloud/sql/mysql/SocketFactory.java @@ -16,7 +16,7 @@ package com.google.cloud.sql.mysql; -import com.google.cloud.sql.core.CoreSocketFactory; +import com.google.cloud.sql.core.InternalConnectorRegistry; import com.mysql.cj.conf.PropertySet; import com.mysql.cj.protocol.ServerSession; import com.mysql.cj.protocol.SocketConnection; @@ -28,12 +28,12 @@ * A MySQL {@link SocketFactory} that establishes a secure connection to a Cloud SQL instance using * ephemeral certificates. * - *

The heavy lifting is done by the singleton {@link CoreSocketFactory} class. + *

The heavy lifting is done by the singleton {@link InternalConnectorRegistry} class. */ public class SocketFactory implements com.mysql.cj.protocol.SocketFactory { static { - CoreSocketFactory.addArtifactId("mysql-socket-factory-connector-j-8"); + InternalConnectorRegistry.addArtifactId("mysql-socket-factory-connector-j-8"); } @Override @@ -56,7 +56,7 @@ public T connect( String host, int portNumber, Properties props, int loginTimeout) throws IOException, InterruptedException { @SuppressWarnings("unchecked") - T socket = (T) CoreSocketFactory.connect(props); + T socket = (T) InternalConnectorRegistry.connect(props); return socket; } diff --git a/jdbc/postgres/src/main/java/com/google/cloud/sql/postgres/SocketFactory.java b/jdbc/postgres/src/main/java/com/google/cloud/sql/postgres/SocketFactory.java index 52c97181b..9425bc10e 100644 --- a/jdbc/postgres/src/main/java/com/google/cloud/sql/postgres/SocketFactory.java +++ b/jdbc/postgres/src/main/java/com/google/cloud/sql/postgres/SocketFactory.java @@ -17,7 +17,7 @@ package com.google.cloud.sql.postgres; import com.google.cloud.sql.ConnectionConfig; -import com.google.cloud.sql.core.CoreSocketFactory; +import com.google.cloud.sql.core.InternalConnectorRegistry; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; @@ -28,7 +28,7 @@ * A Postgres {@link SocketFactory} that establishes a secure connection to a Cloud SQL instance * using ephemeral certificates. * - *

The heavy lifting is done by the singleton {@link CoreSocketFactory} class. + *

The heavy lifting is done by the singleton {@link InternalConnectorRegistry} class. */ public class SocketFactory extends javax.net.SocketFactory { @@ -40,7 +40,7 @@ public class SocketFactory extends javax.net.SocketFactory { private final Properties props; static { - CoreSocketFactory.addArtifactId("postgres-socket-factory"); + InternalConnectorRegistry.addArtifactId("postgres-socket-factory"); } /** @@ -75,7 +75,7 @@ private static Properties createDefaultProperties(String instanceName) { @Override public Socket createSocket() throws IOException { try { - return CoreSocketFactory.connect(props, POSTGRES_SUFFIX); + return InternalConnectorRegistry.connect(props, POSTGRES_SUFFIX); } catch (InterruptedException e) { throw new RuntimeException(e); } diff --git a/jdbc/sqlserver/src/main/java/com/google/cloud/sql/sqlserver/SocketFactory.java b/jdbc/sqlserver/src/main/java/com/google/cloud/sql/sqlserver/SocketFactory.java index 0bcb6aa9f..4e0617db4 100644 --- a/jdbc/sqlserver/src/main/java/com/google/cloud/sql/sqlserver/SocketFactory.java +++ b/jdbc/sqlserver/src/main/java/com/google/cloud/sql/sqlserver/SocketFactory.java @@ -17,7 +17,7 @@ package com.google.cloud.sql.sqlserver; import com.google.cloud.sql.ConnectionConfig; -import com.google.cloud.sql.core.CoreSocketFactory; +import com.google.cloud.sql.core.InternalConnectorRegistry; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Splitter; import java.io.IOException; @@ -33,12 +33,12 @@ * A Microsoft SQL Server {@link SocketFactory} that establishes a secure connection to a Cloud SQL * instance using ephemeral certificates. * - *

The heavy lifting is done by the singleton {@link CoreSocketFactory} class. + *

The heavy lifting is done by the singleton {@link InternalConnectorRegistry} class. */ public class SocketFactory extends javax.net.SocketFactory { static { - CoreSocketFactory.addArtifactId("cloud-sql-connector-jdbc-sqlserver"); + InternalConnectorRegistry.addArtifactId("cloud-sql-connector-jdbc-sqlserver"); } // props are protected, not private, so that they can be accessed from unit tests @@ -74,7 +74,7 @@ public SocketFactory(String socketFactoryConstructorArg) throws UnsupportedEncod @Override public Socket createSocket() throws IOException { try { - return CoreSocketFactory.connect(props); + return InternalConnectorRegistry.connect(props); } catch (InterruptedException e) { throw new RuntimeException(e); } diff --git a/r2dbc/core/src/main/java/com/google/cloud/sql/core/CloudSqlConnectionFactory.java b/r2dbc/core/src/main/java/com/google/cloud/sql/core/CloudSqlConnectionFactory.java index 2b4d52966..e51eb1e06 100644 --- a/r2dbc/core/src/main/java/com/google/cloud/sql/core/CloudSqlConnectionFactory.java +++ b/r2dbc/core/src/main/java/com/google/cloud/sql/core/CloudSqlConnectionFactory.java @@ -52,7 +52,7 @@ public CloudSqlConnectionFactory( @NonNull public Publisher create() { try { - String hostIp = CoreSocketFactory.getHostIp(config); + String hostIp = InternalConnectorRegistry.getHostIp(config); builder.option(HOST, hostIp).option(PORT, SERVER_PROXY_PORT); return supplier.get().create(builder.build()).create(); } catch (IOException e) { @@ -64,7 +64,7 @@ public Publisher create() { @NonNull public ConnectionFactoryMetadata getMetadata() { try { - String hostIp = CoreSocketFactory.getHostIp(config); + String hostIp = InternalConnectorRegistry.getHostIp(config); builder.option(HOST, hostIp).option(PORT, SERVER_PROXY_PORT); return supplier.get().create(builder.build()).getMetadata(); } catch (IOException e) { diff --git a/r2dbc/core/src/main/java/com/google/cloud/sql/core/GcpConnectionFactoryProvider.java b/r2dbc/core/src/main/java/com/google/cloud/sql/core/GcpConnectionFactoryProvider.java index 1c32bab96..b0047fdb2 100644 --- a/r2dbc/core/src/main/java/com/google/cloud/sql/core/GcpConnectionFactoryProvider.java +++ b/r2dbc/core/src/main/java/com/google/cloud/sql/core/GcpConnectionFactoryProvider.java @@ -122,7 +122,7 @@ public ConnectionFactory create(ConnectionFactoryOptions connectionFactoryOption try { // Precompute SSL Data to trigger the initial refresh to happen immediately, // and ensure enableIAMAuth is set correctly. - CoreSocketFactory.getSslData(config); + InternalConnectorRegistry.getSslData(config); String socket = (String) connectionFactoryOptions.getValue(UNIX_SOCKET); if (socket != null) { @@ -136,7 +136,7 @@ public ConnectionFactory create(ConnectionFactoryOptions connectionFactoryOption Mono.fromSupplier( () -> { try { - return CoreSocketFactory.getSslData(config); + return InternalConnectorRegistry.getSslData(config); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/r2dbc/core/src/test/java/com/google/cloud/sql/core/GcpConnectionFactoryProviderTest.java b/r2dbc/core/src/test/java/com/google/cloud/sql/core/GcpConnectionFactoryProviderTest.java index 3099bf623..a9f82e9a0 100644 --- a/r2dbc/core/src/test/java/com/google/cloud/sql/core/GcpConnectionFactoryProviderTest.java +++ b/r2dbc/core/src/test/java/com/google/cloud/sql/core/GcpConnectionFactoryProviderTest.java @@ -66,7 +66,7 @@ public class GcpConnectionFactoryProviderTest { private final CredentialFactory credentialFactory = new StubCredentialFactory(); ListeningScheduledExecutorService defaultExecutor; ListenableFuture clientKeyPair; - CoreSocketFactory coreSocketFactoryStub; + InternalConnectorRegistry internalConnectorRegistryStub; String fakeInstanceName = "myProject:myRegion:myInstance"; @@ -172,18 +172,18 @@ public void setup() throws GeneralSecurityException { clientKeyPair = Futures.immediateFuture(new KeyPair(publicKey, privateKey)); - defaultExecutor = CoreSocketFactory.getDefaultExecutor(); + defaultExecutor = InternalConnectorRegistry.getDefaultExecutor(); ConnectionInfoRepositoryFactory repo = new StubConnectionInfoRepositoryFactory(fakeSuccessHttpTransport(Duration.ofSeconds(0))); - coreSocketFactoryStub = - new CoreSocketFactory( + internalConnectorRegistryStub = + new InternalConnectorRegistry( clientKeyPair, repo, credentialFactory, 3307, - CoreSocketFactory.DEFAULT_MAX_REFRESH_MS, + InternalConnectorRegistry.DEFAULT_MAX_REFRESH_MS, defaultExecutor); } } diff --git a/r2dbc/mysql/src/main/java/com/google/cloud/sql/core/GcpConnectionFactoryProviderMysql.java b/r2dbc/mysql/src/main/java/com/google/cloud/sql/core/GcpConnectionFactoryProviderMysql.java index 3f2f51a2e..af0af9df5 100644 --- a/r2dbc/mysql/src/main/java/com/google/cloud/sql/core/GcpConnectionFactoryProviderMysql.java +++ b/r2dbc/mysql/src/main/java/com/google/cloud/sql/core/GcpConnectionFactoryProviderMysql.java @@ -35,7 +35,7 @@ public class GcpConnectionFactoryProviderMysql extends GcpConnectionFactoryProvi private static final String MYSQL_DRIVER = "mysql"; static { - CoreSocketFactory.addArtifactId("cloud-sql-connector-r2dbc-mysql"); + InternalConnectorRegistry.addArtifactId("cloud-sql-connector-r2dbc-mysql"); } @Override diff --git a/r2dbc/postgres/src/main/java/com/google/cloud/sql/core/GcpConnectionFactoryProviderPostgres.java b/r2dbc/postgres/src/main/java/com/google/cloud/sql/core/GcpConnectionFactoryProviderPostgres.java index ff631f62a..47bb7cb37 100644 --- a/r2dbc/postgres/src/main/java/com/google/cloud/sql/core/GcpConnectionFactoryProviderPostgres.java +++ b/r2dbc/postgres/src/main/java/com/google/cloud/sql/core/GcpConnectionFactoryProviderPostgres.java @@ -37,7 +37,7 @@ public class GcpConnectionFactoryProviderPostgres extends GcpConnectionFactoryPr private static final String LEGACY_POSTGRESQL_DRIVER = "postgres"; static { - CoreSocketFactory.addArtifactId("cloud-sql-connector-r2dbc-postgres"); + InternalConnectorRegistry.addArtifactId("cloud-sql-connector-r2dbc-postgres"); } @Override diff --git a/r2dbc/sqlserver/src/main/java/com/google/cloud/sql/core/GcpConnectionFactoryProviderMssql.java b/r2dbc/sqlserver/src/main/java/com/google/cloud/sql/core/GcpConnectionFactoryProviderMssql.java index 34791128a..0253c97d0 100644 --- a/r2dbc/sqlserver/src/main/java/com/google/cloud/sql/core/GcpConnectionFactoryProviderMssql.java +++ b/r2dbc/sqlserver/src/main/java/com/google/cloud/sql/core/GcpConnectionFactoryProviderMssql.java @@ -31,7 +31,7 @@ public class GcpConnectionFactoryProviderMssql extends GcpConnectionFactoryProvider { static { - CoreSocketFactory.addArtifactId("cloud-sql-connector-r2dbc-mssql"); + InternalConnectorRegistry.addArtifactId("cloud-sql-connector-r2dbc-mssql"); } /** MsSQL driver option value. */