Skip to content

Commit 1f19f2b

Browse files
authored
test: Add test to ensure R2DBC correctly configures the connector (#2106)
This adds a new test to check that the GcpConnectionFactoryProvider correctly configures the ConnectionConfiguration. This also removes up unused test setup code from the GcpConnectionFactoryProviderTest.
1 parent 4c40d8e commit 1f19f2b

File tree

2 files changed

+120
-159
lines changed

2 files changed

+120
-159
lines changed

r2dbc/core/pom.xml

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -79,18 +79,8 @@
7979
<scope>test</scope>
8080
</dependency>
8181
<dependency>
82-
<groupId>com.google.apis</groupId>
83-
<artifactId>google-api-services-sqladmin</artifactId>
84-
<scope>test</scope>
85-
</dependency>
86-
<dependency>
87-
<groupId>com.google.http-client</groupId>
88-
<artifactId>google-http-client</artifactId>
89-
<scope>test</scope>
90-
</dependency>
91-
<dependency>
92-
<groupId>com.google.http-client</groupId>
93-
<artifactId>google-http-client-gson</artifactId>
82+
<groupId>com.google.truth</groupId>
83+
<artifactId>truth</artifactId>
9484
<scope>test</scope>
9585
</dependency>
9686
</dependencies>

r2dbc/core/src/test/java/com/google/cloud/sql/core/GcpConnectionFactoryProviderTest.java

Lines changed: 118 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -15,175 +15,146 @@
1515
*/
1616
package com.google.cloud.sql.core;
1717

18-
import com.google.api.client.http.HttpStatusCodes;
19-
import com.google.api.client.http.HttpTransport;
20-
import com.google.api.client.http.LowLevelHttpRequest;
21-
import com.google.api.client.http.LowLevelHttpResponse;
22-
import com.google.api.client.json.Json;
23-
import com.google.api.client.json.JsonFactory;
24-
import com.google.api.client.json.gson.GsonFactory;
25-
import com.google.api.client.testing.http.MockHttpTransport;
26-
import com.google.api.client.testing.http.MockLowLevelHttpRequest;
27-
import com.google.api.client.testing.http.MockLowLevelHttpResponse;
28-
import com.google.api.services.sqladmin.model.ConnectSettings;
29-
import com.google.api.services.sqladmin.model.GenerateEphemeralCertResponse;
30-
import com.google.api.services.sqladmin.model.IpMapping;
31-
import com.google.api.services.sqladmin.model.SslCert;
32-
import com.google.common.collect.ImmutableList;
33-
import com.google.common.util.concurrent.Futures;
34-
import com.google.common.util.concurrent.ListenableFuture;
35-
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
36-
import java.io.IOException;
37-
import java.math.BigInteger;
38-
import java.security.GeneralSecurityException;
39-
import java.security.KeyFactory;
40-
import java.security.KeyPair;
41-
import java.security.PrivateKey;
42-
import java.security.PublicKey;
43-
import java.security.cert.Certificate;
44-
import java.security.spec.PKCS8EncodedKeySpec;
45-
import java.security.spec.X509EncodedKeySpec;
46-
import java.time.Duration;
47-
import java.time.ZoneId;
48-
import java.time.ZonedDateTime;
49-
import java.util.Base64;
50-
import java.util.Date;
51-
import java.util.concurrent.ExecutionException;
52-
import javax.security.auth.x500.X500Principal;
53-
import org.bouncycastle.cert.X509CertificateHolder;
54-
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
55-
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
56-
import org.bouncycastle.operator.ContentSigner;
57-
import org.bouncycastle.operator.OperatorCreationException;
58-
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
59-
import org.junit.Before;
18+
import static com.google.common.truth.Truth.assertThat;
19+
20+
import com.google.cloud.sql.AuthType;
21+
import com.google.cloud.sql.IpType;
22+
import com.google.cloud.sql.RefreshStrategy;
23+
import io.netty.handler.ssl.SslContextBuilder;
24+
import io.r2dbc.spi.Connection;
25+
import io.r2dbc.spi.ConnectionFactory;
26+
import io.r2dbc.spi.ConnectionFactoryMetadata;
27+
import io.r2dbc.spi.ConnectionFactoryOptions;
28+
import java.util.Collections;
29+
import java.util.function.Function;
30+
import org.junit.Test;
31+
import org.reactivestreams.Publisher;
6032

6133
public class GcpConnectionFactoryProviderTest {
34+
private final ConnectionFactory unixFactory = new StubConnectionFactory(null);
6235

63-
static final String PUBLIC_IP = "127.0.0.1";
64-
static final String PRIVATE_IP = "10.0.0.1";
65-
final CredentialFactoryProvider stubCredentialFactoryProvider =
66-
new CredentialFactoryProvider(new StubCredentialFactory());
67-
ListeningScheduledExecutorService defaultExecutor;
68-
ListenableFuture<KeyPair> clientKeyPair;
69-
InternalConnectorRegistry internalConnectorRegistryStub;
36+
@Test
37+
public void testCreateWithInstanceName() {
7038

71-
String fakeInstanceName = "myProject:myRegion:myInstance";
39+
ConnectionFactoryOptions.Builder options = ConnectionFactoryOptions.builder();
40+
options.option(ConnectionFactoryOptions.PROTOCOL, "cloudsql");
41+
options.option(ConnectionFactoryOptions.HOST, "project:region:instance");
7242

73-
private static byte[] decodeBase64StripWhitespace(String b64) {
74-
return Base64.getDecoder().decode(b64.replaceAll("\\s", ""));
43+
StubConnectionFactory factory = configureConnection(options.build());
44+
ConnectionConfig config = factory.config;
45+
assertThat(config.getCloudSqlInstance()).isEqualTo("project:region:instance");
7546
}
7647

77-
private String createEphemeralCert(Duration shiftIntoPast)
78-
throws GeneralSecurityException, ExecutionException, OperatorCreationException {
79-
Duration validFor = Duration.ofHours(1);
80-
ZonedDateTime notBefore = ZonedDateTime.now(ZoneId.of("UTC")).minus(shiftIntoPast);
81-
ZonedDateTime notAfter = notBefore.plus(validFor);
48+
@Test
49+
public void testCreateWithUnixSocket() {
8250

83-
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
84-
PKCS8EncodedKeySpec keySpec =
85-
new PKCS8EncodedKeySpec(decodeBase64StripWhitespace(R2dbcTestKeys.SIGNING_CA_PRIVATE_KEY));
86-
PrivateKey signingKey = keyFactory.generatePrivate(keySpec);
51+
ConnectionFactoryOptions.Builder options = ConnectionFactoryOptions.builder();
52+
options.option(ConnectionFactoryOptions.PROTOCOL, "cloudsql");
53+
options.option(ConnectionFactoryOptions.HOST, "project:region:instance");
54+
options.option(GcpConnectionFactoryProvider.UNIX_SOCKET, "/socket/path");
8755

88-
final ContentSigner signer = new JcaContentSignerBuilder("SHA1withRSA").build(signingKey);
56+
StubConnectionFactory factory = configureConnection(options.build());
57+
assertThat(factory).isSameInstanceAs(unixFactory);
58+
}
8959

90-
X500Principal issuer =
91-
new X500Principal("C = US, O = Google\\, Inc, CN=Google Cloud SQL Signing CA foo:baz");
92-
X500Principal subject = new X500Principal("C = US, O = Google\\, Inc, CN=temporary-subject");
60+
@Test
61+
public void testCreateWithAllOptions() {
62+
63+
ConnectionFactoryOptions.Builder options = ConnectionFactoryOptions.builder();
64+
options.option(ConnectionFactoryOptions.PROTOCOL, "cloudsql");
65+
options.option(ConnectionFactoryOptions.HOST, "project:region:instance");
66+
options.option(GcpConnectionFactoryProvider.NAMED_CONNECTOR, "resolver-test");
67+
options.option(GcpConnectionFactoryProvider.IP_TYPES, "private");
68+
options.option(GcpConnectionFactoryProvider.ENABLE_IAM_AUTH, true);
69+
options.option(GcpConnectionFactoryProvider.DELEGATES, "[email protected]");
70+
options.option(GcpConnectionFactoryProvider.TARGET_PRINCIPAL, "[email protected]");
71+
options.option(GcpConnectionFactoryProvider.ADMIN_QUOTA_PROJECT, "quota-project");
72+
options.option(GcpConnectionFactoryProvider.GOOGLE_CREDENTIALS_PATH, "/credentials/path");
73+
options.option(GcpConnectionFactoryProvider.UNIVERSE_DOMAIN, "domain.google.com");
74+
options.option(GcpConnectionFactoryProvider.REFRESH_STRATEGY, "lazy");
75+
76+
StubConnectionFactory factory = configureConnection(options.build());
77+
ConnectionConfig config = factory.config;
78+
79+
assertThat(config.getCloudSqlInstance()).isEqualTo("project:region:instance");
80+
assertThat(config.getNamedConnector()).isEqualTo("resolver-test");
81+
assertThat(config.getIpTypes()).isEqualTo(Collections.singletonList(IpType.PRIVATE));
82+
assertThat(config.getAuthType()).isEqualTo(AuthType.IAM);
83+
assertThat(config.getConnectorConfig().getDelegates())
84+
.isEqualTo(Collections.singletonList("[email protected]"));
85+
assertThat(config.getConnectorConfig().getTargetPrincipal()).isEqualTo("[email protected]");
86+
assertThat(config.getConnectorConfig().getAdminQuotaProject()).isEqualTo("quota-project");
87+
assertThat(config.getConnectorConfig().getGoogleCredentialsPath())
88+
.isEqualTo("/credentials/path");
89+
assertThat(config.getConnectorConfig().getUniverseDomain()).isEqualTo("domain.google.com");
90+
assertThat(config.getConnectorConfig().getRefreshStrategy()).isEqualTo(RefreshStrategy.LAZY);
91+
}
9392

94-
JcaX509v3CertificateBuilder certificateBuilder =
95-
new JcaX509v3CertificateBuilder(
96-
issuer,
97-
BigInteger.ONE,
98-
Date.from(notBefore.toInstant()),
99-
Date.from(notAfter.toInstant()),
100-
subject,
101-
Futures.getDone(clientKeyPair).getPublic());
93+
@Test
94+
public void testCreateWithAdminApiOptions() {
10295

103-
X509CertificateHolder certificateHolder = certificateBuilder.build(signer);
96+
ConnectionFactoryOptions.Builder options = ConnectionFactoryOptions.builder();
97+
options.option(ConnectionFactoryOptions.PROTOCOL, "cloudsql");
98+
options.option(ConnectionFactoryOptions.HOST, "project:region:instance");
99+
options.option(GcpConnectionFactoryProvider.ADMIN_ROOT_URL, "http://example.com/root");
100+
options.option(GcpConnectionFactoryProvider.ADMIN_SERVICE_PATH, "/service");
104101

105-
Certificate cert = new JcaX509CertificateConverter().getCertificate(certificateHolder);
102+
StubConnectionFactory factory = configureConnection(options.build());
103+
ConnectionConfig config = factory.config;
106104

107-
return "-----BEGIN CERTIFICATE-----\n"
108-
+ Base64.getEncoder().encodeToString(cert.getEncoded()).replaceAll("(.{64})", "$1\n")
109-
+ "\n"
110-
+ "-----END CERTIFICATE-----\n";
105+
assertThat(config.getCloudSqlInstance()).isEqualTo("project:region:instance");
106+
assertThat(config.getConnectorConfig().getAdminRootUrl()).isEqualTo("http://example.com/root");
107+
assertThat(config.getConnectorConfig().getAdminServicePath()).isEqualTo("/service");
111108
}
112109

113-
private HttpTransport fakeSuccessHttpTransport(Duration certDuration) {
114-
final JsonFactory jsonFactory = new GsonFactory();
115-
return new MockHttpTransport() {
116-
@Override
117-
public LowLevelHttpRequest buildRequest(String method, String url) {
118-
return new MockLowLevelHttpRequest() {
119-
@Override
120-
public LowLevelHttpResponse execute() throws IOException {
121-
MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
122-
if (method.equals("GET") && url.contains("connectSettings")) {
123-
ConnectSettings settings =
124-
new ConnectSettings()
125-
.setBackendType("SECOND_GEN")
126-
.setIpAddresses(
127-
ImmutableList.of(
128-
new IpMapping().setIpAddress(PUBLIC_IP).setType("PRIMARY"),
129-
new IpMapping().setIpAddress(PRIVATE_IP).setType("PRIVATE")))
130-
.setServerCaCert(new SslCert().setCert(R2dbcTestKeys.SERVER_CA_CERT))
131-
.setDatabaseVersion("POSTGRES14")
132-
.setRegion("myRegion");
133-
settings.setFactory(jsonFactory);
134-
response
135-
.setContent(settings.toPrettyString())
136-
.setContentType(Json.MEDIA_TYPE)
137-
.setStatusCode(HttpStatusCodes.STATUS_CODE_OK);
138-
} else if (method.equals("POST") && url.contains("generateEphemeralCert")) {
139-
GenerateEphemeralCertResponse certResponse = new GenerateEphemeralCertResponse();
140-
try {
141-
certResponse.setEphemeralCert(
142-
new SslCert().setCert(createEphemeralCert(certDuration)));
143-
certResponse.setFactory(jsonFactory);
144-
} catch (GeneralSecurityException
145-
| ExecutionException
146-
| OperatorCreationException e) {
147-
throw new RuntimeException(e);
148-
}
149-
response
150-
.setContent(certResponse.toPrettyString())
151-
.setContentType(Json.MEDIA_TYPE)
152-
.setStatusCode(HttpStatusCodes.STATUS_CODE_OK);
153-
}
154-
return response;
155-
}
156-
};
157-
}
158-
};
159-
}
110+
private static class StubConnectionFactory implements ConnectionFactory {
111+
112+
final ConnectionConfig config;
160113

161-
@Before
162-
public void setup() throws GeneralSecurityException {
114+
private StubConnectionFactory(ConnectionConfig config) {
115+
this.config = config;
116+
}
163117

164-
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
165-
PKCS8EncodedKeySpec privateKeySpec =
166-
new PKCS8EncodedKeySpec(decodeBase64StripWhitespace(R2dbcTestKeys.CLIENT_PRIVATE_KEY));
167-
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
118+
@Override
119+
public Publisher<? extends Connection> create() {
120+
return null;
121+
}
122+
123+
@Override
124+
public ConnectionFactoryMetadata getMetadata() {
125+
return null;
126+
}
127+
}
168128

169-
X509EncodedKeySpec publicKeySpec =
170-
new X509EncodedKeySpec(decodeBase64StripWhitespace(R2dbcTestKeys.CLIENT_PUBLIC_KEY));
171-
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
129+
private StubConnectionFactory configureConnection(ConnectionFactoryOptions options) {
130+
GcpConnectionFactoryProvider p =
131+
new GcpConnectionFactoryProvider() {
132+
@Override
133+
ConnectionFactory tcpSocketConnectionFactory(
134+
ConnectionConfig config,
135+
ConnectionFactoryOptions.Builder optionBuilder,
136+
Function<SslContextBuilder, SslContextBuilder> customizer) {
137+
return new StubConnectionFactory(config);
138+
}
172139

173-
clientKeyPair = Futures.immediateFuture(new KeyPair(publicKey, privateKey));
140+
@Override
141+
ConnectionFactory unixSocketConnectionFactory(
142+
ConnectionFactoryOptions.Builder optionBuilder, String socket) {
143+
return unixFactory;
144+
}
174145

175-
defaultExecutor = InternalConnectorRegistry.getDefaultExecutor();
146+
@Override
147+
ConnectionFactoryOptions.Builder createBuilder(
148+
ConnectionFactoryOptions connectionFactoryOptions) {
149+
return ConnectionFactoryOptions.builder();
150+
}
176151

177-
ConnectionInfoRepositoryFactory repo =
178-
new StubConnectionInfoRepositoryFactory(fakeSuccessHttpTransport(Duration.ofSeconds(0)));
152+
@Override
153+
boolean supportedProtocol(String protocol) {
154+
return "cloudsql".equals(protocol);
155+
}
156+
};
179157

180-
internalConnectorRegistryStub =
181-
new InternalConnectorRegistry(
182-
clientKeyPair,
183-
repo,
184-
stubCredentialFactoryProvider,
185-
3307,
186-
InternalConnectorRegistry.DEFAULT_CONNECT_TIMEOUT_MS,
187-
defaultExecutor);
158+
return (StubConnectionFactory) p.create(options);
188159
}
189160
}

0 commit comments

Comments
 (0)