Skip to content

Commit 4474f16

Browse files
authored
feat: Add support for PSC network connections (#1347)
Adds new value for `ipTypes` jdbc connection parameter: `PSC`. When ipTypes=PSC, the connector will connect to the database instance using Private Service Connect instead of the instance's public or private IP address.
1 parent 2d412a6 commit 4474f16

File tree

9 files changed

+97
-20
lines changed

9 files changed

+97
-20
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ public static void setApplicationName(String applicationName) {
303303
* Creates a secure socket representing a connection to a Cloud SQL instance.
304304
*
305305
* @param instanceName Name of the Cloud SQL instance.
306-
* @param ipTypes Preferred type of IP to use ("PRIVATE", "PUBLIC")
306+
* @param ipTypes Preferred type of IP to use ("PRIVATE", "PUBLIC", "PSC")
307307
* @return the newly created Socket.
308308
* @throws IOException if error occurs during socket creation.
309309
*/

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,19 +193,27 @@ private Metadata fetchMetadata(CloudSqlInstanceName instanceName, AuthType authT
193193

194194
checkDatabaseCompatibility(instanceMetadata, authType, instanceName.getConnectionName());
195195

196+
Map<String, String> ipAddrs = new HashMap<>();
197+
if (instanceMetadata.getIpAddresses() != null) {
198+
// Update the IP addresses and types need to connect with the instance.
199+
for (IpMapping addr : instanceMetadata.getIpAddresses()) {
200+
ipAddrs.put(addr.getType(), addr.getIpAddress());
201+
}
202+
}
203+
204+
// resolve DnsName into IP address for PSC
205+
if (instanceMetadata.getDnsName() != null && !instanceMetadata.getDnsName().isEmpty()) {
206+
ipAddrs.put("PSC", instanceMetadata.getDnsName());
207+
}
208+
196209
// Verify the instance has at least one IP type assigned that can be used to connect.
197-
if (instanceMetadata.getIpAddresses().isEmpty()) {
210+
if (ipAddrs.isEmpty()) {
198211
throw new IllegalStateException(
199212
String.format(
200213
"[%s] Unable to connect to Cloud SQL instance: instance does not have an assigned "
201214
+ "IP address.",
202215
instanceName.getConnectionName()));
203216
}
204-
// Update the IP addresses and types need to connect with the instance.
205-
Map<String, String> ipAddrs = new HashMap<>();
206-
for (IpMapping addr : instanceMetadata.getIpAddresses()) {
207-
ipAddrs.put(addr.getType(), addr.getIpAddress());
208-
}
209217

210218
// Update the Server CA certificate used to create the SSL connection with the instance.
211219
try {

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,8 @@ public void testGetPreferredIpTypes() throws Exception {
149149
new Metadata(
150150
ImmutableMap.of(
151151
"PUBLIC", "10.1.2.3",
152-
"PRIVATE", "10.10.10.10"),
152+
"PRIVATE", "10.10.10.10",
153+
"PSC", "abcde.12345.us-central1.sql.goog"),
153154
null),
154155
sslData,
155156
Date.from(Instant.now().plus(1, ChronoUnit.HOURS)));
@@ -177,6 +178,8 @@ public void testGetPreferredIpTypes() throws Exception {
177178
assertThat(instance.getPreferredIp(Arrays.asList("PRIVATE", "PUBLIC")))
178179
.isEqualTo("10.10.10.10");
179180
assertThat(instance.getPreferredIp(Arrays.asList("PRIVATE"))).isEqualTo("10.10.10.10");
181+
assertThat(instance.getPreferredIp(Arrays.asList("PSC")))
182+
.isEqualTo("abcde.12345.us-central1.sql.goog");
180183
}
181184

182185
@Test

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,23 +113,30 @@ public OAuth2RefreshHandler getRefreshHandler(String refreshToken, Date expirati
113113
}
114114

115115
public void addConnectSettingsResponse(
116-
String instanceConnectionName, String publicIp, String privateIp, String databaseVersion) {
116+
String instanceConnectionName,
117+
String publicIp,
118+
String privateIp,
119+
String databaseVersion,
120+
String pscHostname) {
117121
CloudSqlInstanceName cloudSqlInstanceName = new CloudSqlInstanceName(instanceConnectionName);
118122

119123
ArrayList<IpMapping> ipMappings = new ArrayList<>();
120-
if (!publicIp.isEmpty()) {
124+
if (publicIp != null && !publicIp.isEmpty()) {
121125
ipMappings.add(new IpMapping().setIpAddress(publicIp).setType("PRIMARY"));
122126
}
123-
if (!privateIp.isEmpty()) {
127+
if (privateIp != null && !privateIp.isEmpty()) {
124128
ipMappings.add(new IpMapping().setIpAddress(privateIp).setType("PRIVATE"));
125129
}
126-
130+
if (ipMappings.isEmpty()) {
131+
ipMappings = null;
132+
}
127133
ConnectSettings settings =
128134
new ConnectSettings()
129135
.setBackendType("SECOND_GEN")
130136
.setIpAddresses(ipMappings)
131137
.setServerCaCert(new SslCert().setCert(TestKeys.SERVER_CA_CERT))
132138
.setDatabaseVersion(databaseVersion)
139+
.setDnsName(pscHostname)
133140
.setRegion(cloudSqlInstanceName.getRegionId());
134141
settings.setFactory(GsonFactory.getDefaultInstance());
135142

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

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ public class SqlAdminApiFetcherTest {
3939

4040
public static final String SAMPLE_PUBLIC_IP = "34.1.2.3";
4141
public static final String SAMPLE_PRIVATE_IP = "10.0.0.1";
42+
public static final String SAMPLE_PSC_IP = "10.0.0.2";
43+
public static final String SAMPLE_PCS_DNS_NAME = "abcde.12345.us-central1.sql.goog";
4244
public static final String INSTANCE_CONNECTION_NAME = "p:r:i";
4345
public static final String DATABASE_VERSION = "POSTGRES14";
4446

@@ -63,6 +65,35 @@ public void testFetchInstanceData_returnsIpAddresses()
6365
Map<String, String> ipAddrs = instanceData.getIpAddrs();
6466
assertThat(ipAddrs.get("PRIMARY")).isEqualTo(SAMPLE_PUBLIC_IP);
6567
assertThat(ipAddrs.get("PRIVATE")).isEqualTo(SAMPLE_PRIVATE_IP);
68+
assertThat(ipAddrs.get("PSC")).isEqualTo(SAMPLE_PCS_DNS_NAME);
69+
}
70+
71+
@Test
72+
public void testFetchInstanceData_returnsPscForNonIpDatabase()
73+
throws ExecutionException, InterruptedException, GeneralSecurityException,
74+
OperatorCreationException {
75+
76+
MockAdminApi mockAdminApi = new MockAdminApi();
77+
mockAdminApi.addConnectSettingsResponse(
78+
INSTANCE_CONNECTION_NAME, null, null, DATABASE_VERSION, SAMPLE_PCS_DNS_NAME);
79+
mockAdminApi.addGenerateEphemeralCertResponse(INSTANCE_CONNECTION_NAME, Duration.ofHours(1));
80+
81+
SqlAdminApiFetcher fetcher =
82+
new StubApiFetcherFactory(mockAdminApi.getHttpTransport())
83+
.create(new StubCredentialFactory().create());
84+
85+
InstanceData instanceData =
86+
fetcher.getInstanceData(
87+
new CloudSqlInstanceName(INSTANCE_CONNECTION_NAME),
88+
() -> Optional.empty(),
89+
AuthType.PASSWORD,
90+
newTestExecutor(),
91+
Futures.immediateFuture(mockAdminApi.getClientKeyPair()));
92+
assertThat(instanceData.getSslContext()).isInstanceOf(SSLContext.class);
93+
94+
Map<String, String> ipAddrs = instanceData.getIpAddrs();
95+
assertThat(ipAddrs.get("PSC")).isEqualTo(SAMPLE_PCS_DNS_NAME);
96+
assertThat(ipAddrs.size()).isEqualTo(1);
6697
}
6798

6899
private ListeningScheduledExecutorService newTestExecutor() {
@@ -129,7 +160,11 @@ private MockAdminApi buildMockAdminApi(String instanceConnectionName, String dat
129160
throws GeneralSecurityException, OperatorCreationException {
130161
MockAdminApi mockAdminApi = new MockAdminApi();
131162
mockAdminApi.addConnectSettingsResponse(
132-
instanceConnectionName, SAMPLE_PUBLIC_IP, SAMPLE_PRIVATE_IP, databaseVersion);
163+
instanceConnectionName,
164+
SAMPLE_PUBLIC_IP,
165+
SAMPLE_PRIVATE_IP,
166+
databaseVersion,
167+
SAMPLE_PCS_DNS_NAME);
133168
mockAdminApi.addGenerateEphemeralCertResponse(instanceConnectionName, Duration.ofHours(1));
134169
return mockAdminApi;
135170
}

docs/jdbc-mariadb.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,13 @@ Note: The host portion of the JDBC URL is currently unused, and has no effect on
4747

4848
### Specifying IP Types
4949

50-
"The `ipTypes` argument is used to specify a preferred order of IP types used to connect via a comma delimited list. For example, `ipTypes=PUBLIC,PRIVATE` will use the instance's Public IP if it exists, otherwise private. The value `ipTypes=PRIVATE` will force the Cloud SQL instance to connect via it's private IP. If not specified, the default used is `ipTypes=PUBLIC,PRIVATE`.
50+
"The `ipTypes` argument is used to specify a preferred order of IP types used
51+
to connect via a comma delimited list. For example, `ipTypes=PUBLIC,PRIVATE`
52+
will use the instance's Public IP if it exists, otherwise private. The
53+
value `ipTypes=PRIVATE` will force the Cloud SQL instance to connect via
54+
it's private IP. The value `ipTypes=PSC` will force the Cloud SQL instance to
55+
connect to the database via [Private Service Connect](https://cloud.google.com/vpc/docs/private-service-connect).
56+
If not specified, the default used is `ipTypes=PUBLIC,PRIVATE`.
5157

5258
For more info on connecting using a private IP address, see [Requirements for Private IP](https://cloud.google.com/sql/docs/mysql/private-ip#requirements_for_private_ip).
5359

docs/jdbc-mysql.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,14 @@ jdbc:mysql:///<DATABASE_NAME>?cloudSqlInstance=<INSTANCE_CONNECTION_NAME>&socket
4949
Note: The host portion of the JDBC URL is currently unused, and has no effect on the connection process. The SocketFactory will get your instances IP address based on the provided `cloudSqlInstance` arg.
5050

5151
### Specifying IP Types
52-
53-
"The `ipTypes` argument is used to specify a preferred order of IP types used to connect via a comma delimited list. For example, `ipTypes=PUBLIC,PRIVATE` will use the instance's Public IP if it exists, otherwise private. The value `ipTypes=PRIVATE` will force the Cloud SQL instance to connect via it's private IP. If not specified, the default used is `ipTypes=PUBLIC,PRIVATE`.
52+
53+
"The `ipTypes` argument is used to specify a preferred order of IP types used
54+
to connect via a comma delimited list. For example, `ipTypes=PUBLIC,PRIVATE`
55+
will use the instance's Public IP if it exists, otherwise private. The
56+
value `ipTypes=PRIVATE` will force the Cloud SQL instance to connect via
57+
it's private IP. The value `ipTypes=PSC` will force the Cloud SQL instance to
58+
connect to the database via [Private Service Connect](https://cloud.google.com/vpc/docs/private-service-connect).
59+
If not specified, the default used is `ipTypes=PUBLIC,PRIVATE`.
5460

5561
For more info on connecting using a private IP address, see [Requirements for Private IP](https://cloud.google.com/sql/docs/mysql/private-ip#requirements_for_private_ip).
5662

docs/jdbc-postgres.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,14 @@ jdbc:postgresql:///<DATABASE_NAME>?cloudSqlInstance=<INSTANCE_CONNECTION_NAME>&s
4141
Note: The host portion of the JDBC URL is currently unused, and has no effect on the connection process. The SocketFactory will get your instances IP address based on the provided `cloudSqlInstance` arg.
4242

4343
### Specifying IP Types
44-
45-
"The `ipTypes` argument is used to specify a preferred order of IP types used to connect via a comma delimited list. For example, `ipTypes=PUBLIC,PRIVATE` will use the instance's Public IP if it exists, otherwise private. The value `ipTypes=PRIVATE` will force the Cloud SQL instance to connect via it's private IP. If not specified, the default used is `ipTypes=PUBLIC,PRIVATE`.
44+
45+
"The `ipTypes` argument is used to specify a preferred order of IP types used
46+
to connect via a comma delimited list. For example, `ipTypes=PUBLIC,PRIVATE`
47+
will use the instance's Public IP if it exists, otherwise private. The
48+
value `ipTypes=PRIVATE` will force the Cloud SQL instance to connect via
49+
it's private IP. The value `ipTypes=PSC` will force the Cloud SQL instance to
50+
connect to the database via [Private Service Connect](https://cloud.google.com/vpc/docs/private-service-connect).
51+
If not specified, the default used is `ipTypes=PUBLIC,PRIVATE`.
4652

4753
For more info on connecting using a private IP address, see [Requirements for Private IP](https://cloud.google.com/sql/docs/mysql/private-ip#requirements_for_private_ip).
4854

docs/jdbc-sqlserver.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,14 @@ jdbc:sqlserver://localhost;databaseName=<DATABASE_NAME>;socketFactoryClass=com.g
4343
Note: The host portion of the JDBC URL is currently unused, and has no effect on the connection process. The SocketFactory will get your instances IP address based on the provided `socketFactoryConstructorArg` arg.
4444

4545
### Specifying IP Types
46-
47-
"The `ipTypes` argument is used to specify a preferred order of IP types used to connect via a comma delimited list. For example, `ipTypes=PUBLIC,PRIVATE` will use the instance's Public IP if it exists, otherwise private. The value `ipTypes=PRIVATE` will force the Cloud SQL instance to connect via it's private IP. If not specified, the default used is `ipTypes=PUBLIC,PRIVATE`.
46+
47+
"The `ipTypes` argument is used to specify a preferred order of IP types used
48+
to connect via a comma delimited list. For example, `ipTypes=PUBLIC,PRIVATE`
49+
will use the instance's Public IP if it exists, otherwise private. The
50+
value `ipTypes=PRIVATE` will force the Cloud SQL instance to connect via
51+
it's private IP. The value `ipTypes=PSC` will force the Cloud SQL instance to
52+
connect to the database via [Private Service Connect](https://cloud.google.com/vpc/docs/private-service-connect).
53+
If not specified, the default used is `ipTypes=PUBLIC,PRIVATE`.
4854

4955
IP types can be specified by appending the ipTypes argument to `socketFactoryConstructorArg` using query syntax, such as:
5056

0 commit comments

Comments
 (0)