Skip to content

Commit

Permalink
Improve handling of connections through unix domain sockets. (#210)
Browse files Browse the repository at this point in the history
  • Loading branch information
kurtisvg authored Apr 17, 2020
1 parent d86a454 commit 8a75c58
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 40 deletions.
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,16 @@ this purpose.

### Connection via Unix Sockets

The library will automatically detect when it is running on GAE Standard, and will connect via the
provided unix socket for reduced latency.
To connect using a Unix domain socket (such as the one created by the Cloud SQL
proxy), you can use the `unixSocketPath` property to specify a path to a local
file instead of connecting directly over TCP.

To force the library to connect to a unix socket (typically created by the Cloud SQL proxy) when
running outside of the GAE-Standard environment, set the environment variable
`CLOUD_SQL_FORCE_UNIX_SOCKET` to any value.
Example using MySQL:
```
jdbc:mysql:///<DATABASE_NAME>?unixSocketPath=</PATH/TO/UNIX/SOCKET>&cloudSqlInstance=<INSTANCE_CONNECTION_NAME>&socketFactory=com.google.cloud.sql.mysql.SocketFactory&user=<MYSQL_USER_NAME>&password=<MYSQL_USER_PASSWORD>
```

Example using PostgreSQL:
```
jdbc:postgresql:///<DATABASE_NAME>?unixSocketPath=</PATH/TO/UNIX/SOCKET>&cloudSqlInstance=<INSTANCE_CONNECTION_NAME>&socketFactory=com.google.cloud.sql.postgres.SocketFactory&user=<POSTGRESQL_USER_NAME>&password=<POSTGRESQL_USER_PASSWORD>
```
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class SocketFactory implements com.mysql.jdbc.SocketFactory {

@Override
public Socket connect(String hostname, int portNumber, Properties props) throws IOException {
socket = CoreSocketFactory.connect(props, CoreSocketFactory.MYSQL_SOCKET_FILE_FORMAT);
socket = CoreSocketFactory.connect(props);
return socket;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public class SocketFactory implements com.mysql.cj.api.io.SocketFactory {
@Override
public Socket connect(String host, int portNumber, Properties props, int loginTimeout)
throws IOException {
socket = CoreSocketFactory.connect(props, CoreSocketFactory.MYSQL_SOCKET_FILE_FORMAT);
socket = CoreSocketFactory.connect(props);
return socket;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public <T extends Closeable> T connect(
public <T extends Closeable> T connect(
String host, int portNumber, Properties props, int loginTimeout) throws IOException {
@SuppressWarnings("unchecked")
T socket = (T) CoreSocketFactory.connect(props, CoreSocketFactory.MYSQL_SOCKET_FILE_FORMAT);
T socket = (T) CoreSocketFactory.connect(props);
return socket;
}

Expand Down
73 changes: 42 additions & 31 deletions core/src/main/java/com/google/cloud/sql/core/CoreSocketFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,7 @@
*/
public final class CoreSocketFactory {
public static final String CLOUD_SQL_INSTANCE_PROPERTY = "cloudSqlInstance";
public static final String MYSQL_SOCKET_FILE_FORMAT = "/cloudsql/%s";
public static final String POSTGRES_SOCKET_FILE_FORMAT = "/cloudsql/%s/.s.PGSQL.5432";
private static final String UNIX_SOCKET_PROPERTY = "unixSocketPath";

/**
* Property used to set the application name for the underlying SQLAdmin client.
Expand Down Expand Up @@ -149,62 +148,74 @@ static ListeningScheduledExecutorService getDefaultExecutor() {
MoreExecutors.getExitingScheduledExecutorService(executor));
}

/** Extracts the Unix socket argument from specified properties object. If unset, returns null. */
private static String getUnixSocketArg(Properties props) {
String unixSocketPath = props.getProperty(UNIX_SOCKET_PROPERTY);
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.",
UNIX_SOCKET_PROPERTY));
return "/cloudsql/" + props.getProperty(CLOUD_SQL_INSTANCE_PROPERTY);
}
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 {
return connect(props, null);
}

/**
* Creates a socket representing a connection to a Cloud SQL instance.
*
* <p>Depending on the environment, it may return either a SSL Socket or a Unix Socket.
* <p>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 socketPathFormat) throws IOException {
public static Socket connect(Properties props, String unixPathSuffix) throws IOException {
// Gather parameters
final String csqlInstanceName = props.getProperty(CLOUD_SQL_INSTANCE_PROPERTY);
final List<String> ipTypes = listIpTypes(props.getProperty("ipTypes", DEFAULT_IP_TYPES));
final boolean forceUnixSocket = System.getenv("CLOUD_SQL_FORCE_UNIX_SOCKET") != null;

// Validate parameters
Preconditions.checkArgument(
csqlInstanceName != null,
"cloudSqlInstance property not set. Please specify this property in the JDBC URL or the "
+ "connection Properties with value in form \"project:region:instance\"");

// GAE Standard + GCF provide a connection path at "/cloudsql/<CONNECTION_NAME>"
if (forceUnixSocket || runningOnGaeStandard() || runningOnGoogleCloudFunctions()) {
// Connect using the specified Unix socket
String unixSocket = getUnixSocketArg(props);
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.", csqlInstanceName));
UnixSocketAddress socketAddress =
new UnixSocketAddress(new File(String.format(socketPathFormat, csqlInstanceName)));
"Connecting to Cloud SQL instance [%s] via unix socket at %s.",
csqlInstanceName, unixSocket));
UnixSocketAddress socketAddress = new UnixSocketAddress(new File(unixSocket));
return UnixSocketChannel.open(socketAddress).socket();
}

final List<String> ipTypes = listIpTypes(props.getProperty("ipTypes", DEFAULT_IP_TYPES));
logger.info(
String.format("Connecting to Cloud SQL instance [%s] via SSL socket.", csqlInstanceName));
return getInstance().createSslSocket(csqlInstanceName, ipTypes);
}

/** Returns {@code true} if running in a Google App Engine Standard runtime. */
private static boolean runningOnGaeStandard() {
// gaeEnv="standard" indicates standard instances
String gaeEnv = System.getenv("GAE_ENV");
// runEnv="Production" requires to rule out Java 8 emulated environments
String runEnv = System.getProperty("com.google.appengine.runtime.environment");
// gaeRuntime="java11" in Java 11 environments (no emulated environments)
String gaeRuntime = System.getenv("GAE_RUNTIME");

return "standard".equals(gaeEnv)
&& ("Production".equals(runEnv) || "java11".equals(gaeRuntime));
}

/** Returns {@code true} if running in a Google Cloud Functions runtime. */
private static boolean runningOnGoogleCloudFunctions() {
// Functions automatically sets a few variables we can use to guess the env:
// See https://cloud.google.com/functions/docs/env-var#nodejs_10_and_subsequent_runtimes
return System.getenv("K_SERVICE") != null && System.getenv("K_REVISION") != null;
}

/**
* Creates a secure socket representing a connection to a Cloud SQL instance.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class SocketFactory extends javax.net.SocketFactory {
private static final Logger logger = Logger.getLogger(SocketFactory.class.getName());

private static final String DEPRECATED_SOCKET_ARG = "SocketFactoryArg";
private static final String POSTGRES_SUFFIX = "/.s.PGSQL.5432";

private Properties props;

Expand All @@ -51,6 +52,7 @@ public SocketFactory(Properties info) {
DEPRECATED_SOCKET_ARG, CoreSocketFactory.CLOUD_SQL_INSTANCE_PROPERTY));
info.setProperty(CoreSocketFactory.CLOUD_SQL_INSTANCE_PROPERTY, oldInstanceKey);
}

this.props = info;
}

Expand All @@ -68,7 +70,7 @@ private static Properties createDefaultProperties(String instanceName) {

@Override
public Socket createSocket() throws IOException {
return CoreSocketFactory.connect(props, CoreSocketFactory.POSTGRES_SOCKET_FILE_FORMAT);
return CoreSocketFactory.connect(props, POSTGRES_SUFFIX);
}

@Override
Expand Down

0 comments on commit 8a75c58

Please sign in to comment.