Skip to content

Commit c251493

Browse files
committed
chore: Use domain name from JDBC URL for Postgres and Mysql, Part of #2043.
When connecting to a Cloud SQL database using a domain name like `db.example.com`, you may now set the domain name in the JDBC URL. For example, you can use a URL like "jdbc:mysql://db.example.com/my-schema?socketFactory=socketFactory=com.google.cloud.sql.mysql.SocketFactory" The socket factory will detect "db.example.com" and look up the TXT record to resolve the instance name. See #2043 for the whole feature definition.
1 parent a5f924b commit c251493

File tree

12 files changed

+229
-18
lines changed

12 files changed

+229
-18
lines changed

core/src/main/java/com/google/cloud/sql/ConnectorConfig.java

+5-4
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public class ConnectorConfig {
3434
private final String adminRootUrl;
3535
private final String adminServicePath;
3636
private final Supplier<GoogleCredentials> googleCredentialsSupplier;
37-
private final Function<String,String> instanceNameResolver;
37+
private final Function<String, String> instanceNameResolver;
3838
private final GoogleCredentials googleCredentials;
3939
private final String googleCredentialsPath;
4040
private final String adminQuotaProject;
@@ -53,7 +53,7 @@ private ConnectorConfig(
5353
String adminQuotaProject,
5454
String universeDomain,
5555
RefreshStrategy refreshStrategy,
56-
Function<String,String> instanceNameResolver) {
56+
Function<String, String> instanceNameResolver) {
5757
this.targetPrincipal = targetPrincipal;
5858
this.delegates = delegates;
5959
this.adminRootUrl = adminRootUrl;
@@ -162,7 +162,7 @@ public static class Builder {
162162
private String adminQuotaProject;
163163
private String universeDomain;
164164
private RefreshStrategy refreshStrategy = RefreshStrategy.BACKGROUND;
165-
private Function<String,String> instanceNameResolver;
165+
private Function<String, String> instanceNameResolver;
166166

167167
public Builder withTargetPrincipal(String targetPrincipal) {
168168
this.targetPrincipal = targetPrincipal;
@@ -214,7 +214,8 @@ public Builder withRefreshStrategy(RefreshStrategy refreshStrategy) {
214214
this.refreshStrategy = refreshStrategy;
215215
return this;
216216
}
217-
public Builder withInstanceNameResolver(Function<String,String> instanceNameResolver) {
217+
218+
public Builder withInstanceNameResolver(Function<String, String> instanceNameResolver) {
218219
this.instanceNameResolver = instanceNameResolver;
219220
return this;
220221
}

core/src/main/java/com/google/cloud/sql/InstanceNameResolver.java

-5
This file was deleted.

core/src/main/java/com/google/cloud/sql/core/ConnectionConfig.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,8 @@ public Builder withIpTypes(List<IpType> ipTypes) {
327327
this.ipTypes = ipTypes;
328328
return this;
329329
}
330-
/** Set domainName as. */
330+
331+
/** Set domainName. */
331332
public Builder withDomainName(String domainName) {
332333
this.domainName = domainName;
333334
return this;

core/src/main/java/com/google/cloud/sql/core/Connector.java

-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import com.google.cloud.sql.ConnectorConfig;
2020
import com.google.cloud.sql.CredentialFactory;
2121
import com.google.cloud.sql.RefreshStrategy;
22-
import com.google.common.base.Strings;
2322
import com.google.common.util.concurrent.ListenableFuture;
2423
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
2524
import java.io.File;
@@ -29,7 +28,6 @@
2928
import java.security.KeyPair;
3029
import java.util.concurrent.ConcurrentHashMap;
3130
import java.util.concurrent.ExecutionException;
32-
import java.util.function.Function;
3331
import javax.net.ssl.SSLSocket;
3432
import jnr.unixsocket.UnixSocketAddress;
3533
import jnr.unixsocket.UnixSocketChannel;

core/src/test/java/com/google/cloud/sql/ConnectorConfigTest.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,8 @@ public void testHashCode() {
426426
wantGoogleCredentialsPath,
427427
wantAdminQuotaProject,
428428
null, // universeDomain
429-
wantRefreshStrategy // refreshStrategy
429+
wantRefreshStrategy, // refreshStrategy
430+
null // instanceNameResolver
430431
));
431432
}
432433
}

jdbc/mariadb/src/main/java/com/google/cloud/sql/mariadb/SocketFactory.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,22 @@ public class SocketFactory extends ConfigurableSocketFactory {
3737
}
3838

3939
private Configuration conf;
40+
private String host;
4041

4142
public SocketFactory() {}
4243

4344
@Override
4445
public void setConfiguration(Configuration conf, String host) {
4546
// Ignore the hostname
4647
this.conf = conf;
48+
this.host = host;
4749
}
4850

4951
@Override
5052
public Socket createSocket() throws IOException {
5153
try {
5254
return InternalConnectorRegistry.getInstance()
53-
.connect(ConnectionConfig.fromConnectionProperties(conf.nonMappedOptions()));
55+
.connect(ConnectionConfig.fromConnectionProperties(conf.nonMappedOptions(), host));
5456
} catch (InterruptedException e) {
5557
throw new RuntimeException(e);
5658
}

jdbc/mysql-j-8/src/main/java/com/google/cloud/sql/mysql/SocketFactory.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public <T extends Closeable> T connect(
6060
T socket =
6161
(T)
6262
InternalConnectorRegistry.getInstance()
63-
.connect(ConnectionConfig.fromConnectionProperties(props));
63+
.connect(ConnectionConfig.fromConnectionProperties(props, host));
6464
return socket;
6565
}
6666

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.sql.mysql;
18+
19+
import static com.google.common.truth.Truth.assertThat;
20+
import static com.google.common.truth.Truth.assertWithMessage;
21+
22+
import com.google.cloud.sql.ConnectorConfig;
23+
import com.google.cloud.sql.ConnectorRegistry;
24+
import com.google.common.collect.ImmutableList;
25+
import com.zaxxer.hikari.HikariConfig;
26+
import com.zaxxer.hikari.HikariDataSource;
27+
import java.sql.*;
28+
import java.util.ArrayList;
29+
import java.util.List;
30+
import java.util.Properties;
31+
import java.util.concurrent.TimeUnit;
32+
import org.junit.Before;
33+
import org.junit.BeforeClass;
34+
import org.junit.Rule;
35+
import org.junit.Test;
36+
import org.junit.rules.Timeout;
37+
import org.junit.runner.RunWith;
38+
import org.junit.runners.JUnit4;
39+
40+
@RunWith(JUnit4.class)
41+
public class JdbcMysqlJ8DomainNameIntegrationTests {
42+
43+
private static final String CONNECTION_NAME = System.getenv("MYSQL_CONNECTION_NAME");
44+
private static final String DB_NAME = System.getenv("MYSQL_DB");
45+
private static final String DB_USER = System.getenv("MYSQL_USER");
46+
private static final String DB_PASSWORD = System.getenv("MYSQL_PASS");
47+
private static final ImmutableList<String> requiredEnvVars =
48+
ImmutableList.of("MYSQL_USER", "MYSQL_PASS", "MYSQL_DB", "MYSQL_CONNECTION_NAME");
49+
@Rule public Timeout globalTimeout = new Timeout(80, TimeUnit.SECONDS);
50+
private HikariDataSource connectionPool;
51+
52+
@BeforeClass
53+
public static void checkEnvVars() {
54+
// Check that required env vars are set
55+
requiredEnvVars.forEach(
56+
(varName) ->
57+
assertWithMessage(
58+
String.format(
59+
"Environment variable '%s' must be set to perform these tests.", varName))
60+
.that(System.getenv(varName))
61+
.isNotEmpty());
62+
}
63+
64+
@Before
65+
public void setUpPool() throws SQLException {
66+
// Set up URL parameters
67+
String jdbcURL = String.format("jdbc:mysql://db.example.com/%s", DB_NAME);
68+
Properties connProps = new Properties();
69+
connProps.setProperty("user", DB_USER);
70+
connProps.setProperty("password", DB_PASSWORD);
71+
connProps.setProperty("socketFactory", "com.google.cloud.sql.mysql.SocketFactory");
72+
73+
// Register a resolver that resolves `db.example.com` to the connection name
74+
connProps.setProperty("cloudSqlNamedConnector", "resolver-test");
75+
ConnectorRegistry.register(
76+
"resolver-test",
77+
new ConnectorConfig.Builder()
78+
.withInstanceNameResolver((n) -> "db.example.com".equals(n) ? CONNECTION_NAME : null)
79+
.build());
80+
81+
// Initialize connection pool
82+
HikariConfig config = new HikariConfig();
83+
config.setJdbcUrl(jdbcURL);
84+
config.setDataSourceProperties(connProps);
85+
config.setConnectionTimeout(10000); // 10s
86+
87+
this.connectionPool = new HikariDataSource(config);
88+
}
89+
90+
@Test
91+
public void pooledConnectionTest() throws SQLException {
92+
93+
List<Timestamp> rows = new ArrayList<>();
94+
try (Connection conn = connectionPool.getConnection()) {
95+
try (PreparedStatement selectStmt = conn.prepareStatement("SELECT NOW() as TS")) {
96+
ResultSet rs = selectStmt.executeQuery();
97+
while (rs.next()) {
98+
rows.add(rs.getTimestamp("TS"));
99+
}
100+
}
101+
}
102+
assertThat(rows.size()).isEqualTo(1);
103+
}
104+
}

jdbc/mysql-j-8/src/test/java/com/google/cloud/sql/mysql/JdbcMysqlJ8IntegrationTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public static void checkEnvVars() {
6262
@Before
6363
public void setUpPool() throws SQLException {
6464
// Set up URL parameters
65-
String jdbcURL = String.format("jdbc:mysql:///%s", DB_NAME);
65+
String jdbcURL = String.format("jdbc:mysql://db.example.com/%s", DB_NAME);
6666
Properties connProps = new Properties();
6767
connProps.setProperty("user", DB_USER);
6868
connProps.setProperty("password", DB_PASSWORD);

jdbc/postgres/src/main/java/com/google/cloud/sql/postgres/SocketFactory.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ public class SocketFactory extends javax.net.SocketFactory {
3838
private static final String DEPRECATED_SOCKET_ARG = "SocketFactoryArg";
3939
private static final String POSTGRES_SUFFIX = "/.s.PGSQL.5432";
4040

41+
/** The connection property containing the hostname from the JDBC url. */
42+
private static final String POSTGRES_HOST_PROP = "PGHOST";
43+
4144
private final Properties props;
4245

4346
static {
@@ -78,7 +81,9 @@ private static Properties createDefaultProperties(String instanceName) {
7881
public Socket createSocket() throws IOException {
7982
try {
8083
return InternalConnectorRegistry.getInstance()
81-
.connect(ConnectionConfig.fromConnectionProperties(props));
84+
.connect(
85+
ConnectionConfig.fromConnectionProperties(
86+
props, props.getProperty(POSTGRES_HOST_PROP)));
8287
} catch (InterruptedException e) {
8388
throw new RuntimeException(e);
8489
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.sql.postgres;
18+
19+
import static com.google.common.truth.Truth.assertThat;
20+
import static com.google.common.truth.Truth.assertWithMessage;
21+
22+
import com.google.cloud.sql.ConnectorConfig;
23+
import com.google.cloud.sql.ConnectorRegistry;
24+
import com.google.common.collect.ImmutableList;
25+
import com.zaxxer.hikari.HikariConfig;
26+
import com.zaxxer.hikari.HikariDataSource;
27+
import java.sql.*;
28+
import java.util.ArrayList;
29+
import java.util.List;
30+
import java.util.Properties;
31+
import java.util.concurrent.TimeUnit;
32+
import org.junit.Before;
33+
import org.junit.BeforeClass;
34+
import org.junit.Rule;
35+
import org.junit.Test;
36+
import org.junit.rules.Timeout;
37+
import org.junit.runner.RunWith;
38+
import org.junit.runners.JUnit4;
39+
40+
@RunWith(JUnit4.class)
41+
public class JdbcPostgresDomainNameIntegrationTests {
42+
43+
private static final String CONNECTION_NAME = System.getenv("POSTGRES_CONNECTION_NAME");
44+
private static final String DB_NAME = System.getenv("POSTGRES_DB");
45+
private static final String DB_USER = System.getenv("POSTGRES_USER");
46+
private static final String DB_PASSWORD = System.getenv("POSTGRES_PASS");
47+
private static final ImmutableList<String> requiredEnvVars =
48+
ImmutableList.of("POSTGRES_USER", "POSTGRES_PASS", "POSTGRES_DB", "POSTGRES_CONNECTION_NAME");
49+
@Rule public Timeout globalTimeout = new Timeout(80, TimeUnit.SECONDS);
50+
51+
private HikariDataSource connectionPool;
52+
53+
@BeforeClass
54+
public static void checkEnvVars() {
55+
// Check that required env vars are set
56+
requiredEnvVars.forEach(
57+
(varName) ->
58+
assertWithMessage(
59+
String.format(
60+
"Environment variable '%s' must be set to perform these tests.", varName))
61+
.that(System.getenv(varName))
62+
.isNotEmpty());
63+
}
64+
65+
@Before
66+
public void setUpPool() throws SQLException {
67+
// Set up URL parameters
68+
String jdbcURL = String.format("jdbc:postgresql://db.example.com/%s", DB_NAME);
69+
Properties connProps = new Properties();
70+
connProps.setProperty("user", DB_USER);
71+
connProps.setProperty("password", DB_PASSWORD);
72+
connProps.setProperty("socketFactory", "com.google.cloud.sql.postgres.SocketFactory");
73+
connProps.setProperty("cloudSqlNamedConnector", "resolver-test");
74+
75+
ConnectorRegistry.register(
76+
"resolver-test",
77+
new ConnectorConfig.Builder()
78+
.withInstanceNameResolver((n) -> "db.example.com".equals(n) ? CONNECTION_NAME : null)
79+
.build());
80+
81+
// Initialize connection pool
82+
HikariConfig config = new HikariConfig();
83+
config.setJdbcUrl(jdbcURL);
84+
config.setDataSourceProperties(connProps);
85+
config.setConnectionTimeout(10000); // 10s
86+
87+
this.connectionPool = new HikariDataSource(config);
88+
}
89+
90+
@Test
91+
public void pooledConnectionTest() throws SQLException {
92+
93+
List<Timestamp> rows = new ArrayList<>();
94+
try (Connection conn = connectionPool.getConnection()) {
95+
try (PreparedStatement selectStmt = conn.prepareStatement("SELECT NOW() as TS")) {
96+
ResultSet rs = selectStmt.executeQuery();
97+
while (rs.next()) {
98+
rows.add(rs.getTimestamp("TS"));
99+
}
100+
}
101+
}
102+
assertThat(rows.size()).isEqualTo(1);
103+
}
104+
}

jdbc/postgres/src/test/java/com/google/cloud/sql/postgres/JdbcPostgresIntegrationTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public static void checkEnvVars() {
6363
@Before
6464
public void setUpPool() throws SQLException {
6565
// Set up URL parameters
66-
String jdbcURL = String.format("jdbc:postgresql:///%s", DB_NAME);
66+
String jdbcURL = String.format("jdbc:postgresql://db.example.com/%s", DB_NAME);
6767
Properties connProps = new Properties();
6868
connProps.setProperty("user", DB_USER);
6969
connProps.setProperty("password", DB_PASSWORD);

0 commit comments

Comments
 (0)