Skip to content

Commit e5a5e3d

Browse files
authored
feat: expose options to override spanner endpoint and talking to spanner with plaintext connection (#266)
* feat: expose options to override spanner endpoint and talking to spanner with plaintext connection.
1 parent 1d7876d commit e5a5e3d

File tree

11 files changed

+227
-6
lines changed

11 files changed

+227
-6
lines changed

google-cloud-spanner-cassandra/src/main/java/com/google/cloud/spanner/adapter/Adapter.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import com.google.common.collect.ImmutableSet;
3333
import com.google.spanner.adapter.v1.AdapterClient;
3434
import com.google.spanner.adapter.v1.AdapterSettings;
35+
import io.grpc.ManagedChannelBuilder;
3536
import java.io.IOException;
3637
import java.net.ServerSocket;
3738
import java.net.Socket;
@@ -92,6 +93,9 @@ void start() {
9293
if (credentials == null) {
9394
credentials = GoogleCredentials.getApplicationDefault();
9495
}
96+
if (options.usePlainText()) {
97+
credentials = null;
98+
}
9599
final CredentialsProvider credentialsProvider = setUpCredentialsProvider(credentials);
96100

97101
InstantiatingGrpcChannelProvider.Builder channelProviderBuilder =
@@ -102,6 +106,11 @@ void start() {
102106
channelProviderBuilder.setExecutor(executor);
103107
}
104108

109+
if (options.usePlainText()) {
110+
LOG.warn("Using plain text channel. This should not be used in production.");
111+
channelProviderBuilder.setChannelConfigurator(ManagedChannelBuilder::usePlaintext);
112+
}
113+
105114
channelProviderBuilder
106115
.setAllowNonDefaultServiceAccount(true)
107116
.setChannelPoolSettings(

google-cloud-spanner-cassandra/src/main/java/com/google/cloud/spanner/adapter/AdapterOptions.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ static class Builder {
4040
private Credentials credentials;
4141
private BuiltInMetricsRecorder metricsRecorder;
4242
private boolean useVirtualThreads = false;
43+
private boolean usePlainText = false;
4344

4445
/** The Cloud Spanner endpoint. */
4546
Builder spannerEndpoint(String spannerEndpoint) {
@@ -106,6 +107,12 @@ Builder useVirtualThreads(boolean useVirtualThreads) {
106107
return this;
107108
}
108109

110+
/** (Optional) Whether to use a plaintext connection to Spanner. */
111+
Builder usePlainText(boolean usePlainText) {
112+
this.usePlainText = usePlainText;
113+
return this;
114+
}
115+
109116
AdapterOptions build() {
110117
return new AdapterOptions(this);
111118
}
@@ -121,6 +128,7 @@ AdapterOptions build() {
121128
private Credentials credentials;
122129
private BuiltInMetricsRecorder metricsRecorder;
123130
private boolean useVirtualThreads;
131+
private boolean usePlainText;
124132

125133
private AdapterOptions(Builder builder) {
126134
this.spannerEndpoint = builder.spannerEndpoint;
@@ -133,6 +141,7 @@ private AdapterOptions(Builder builder) {
133141
this.credentials = builder.credentials;
134142
this.metricsRecorder = builder.metricsRecorder;
135143
this.useVirtualThreads = builder.useVirtualThreads;
144+
this.usePlainText = builder.usePlainText;
136145
}
137146

138147
static Builder newBuilder() {
@@ -178,4 +187,8 @@ Optional<Duration> getMaxCommitDelay() {
178187
BuiltInMetricsRecorder getMetricsRecorder() {
179188
return metricsRecorder;
180189
}
190+
191+
boolean usePlainText() {
192+
return usePlainText;
193+
}
181194
}

google-cloud-spanner-cassandra/src/main/java/com/google/cloud/spanner/adapter/Launcher.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,8 @@ private AdapterOptions buildAdapterOptions(
224224
.databaseUri(config.getDatabaseUri())
225225
.inetAddress(config.getHostAddress())
226226
.numGrpcChannels(config.getNumGrpcChannels())
227-
.metricsRecorder(metricsRecorder);
227+
.metricsRecorder(metricsRecorder)
228+
.usePlainText(config.usePlainText());
228229
if (config.getMaxCommitDelayMillis() != null) {
229230
opBuilder.maxCommitDelay(Duration.ofMillis(config.getMaxCommitDelayMillis()));
230231
}

google-cloud-spanner-cassandra/src/main/java/com/google/cloud/spanner/adapter/LauncherConfig.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ static LauncherConfig fromUserConfigs(UserConfigs userConfigs) throws UnknownHos
5858

5959
final String globalSpannerEndpoint;
6060
final boolean globalEnableBuiltInMetrics;
61+
final boolean usePlainText;
6162
HealthCheckConfig healthCheckConfig = null;
6263

6364
if (userConfigs.getGlobalClientConfigs() != null) {
@@ -73,17 +74,21 @@ static LauncherConfig fromUserConfigs(UserConfigs userConfigs) throws UnknownHos
7374
HealthCheckConfig.fromEndpointString(
7475
userConfigs.getGlobalClientConfigs().getHealthCheckEndpoint());
7576
}
77+
usePlainText =
78+
userConfigs.getGlobalClientConfigs().getUsePlainText() != null
79+
&& userConfigs.getGlobalClientConfigs().getUsePlainText();
7680
} else {
7781
globalSpannerEndpoint = ConfigConstants.DEFAULT_SPANNER_ENDPOINT;
7882
globalEnableBuiltInMetrics = false;
83+
usePlainText = false;
7984
}
8085

8186
List<ListenerConfig> listenerConfigs = new ArrayList<>();
8287
for (ListenerConfigs listener : userConfigs.getListeners()) {
8388
validateListenerConfig(listener);
8489
listenerConfigs.add(
8590
ListenerConfig.fromListenerConfigs(
86-
listener, globalSpannerEndpoint, globalEnableBuiltInMetrics));
91+
listener, globalSpannerEndpoint, globalEnableBuiltInMetrics, usePlainText));
8792
}
8893

8994
return new LauncherConfig(listenerConfigs, healthCheckConfig);
@@ -124,6 +129,7 @@ final class ListenerConfig {
124129
private final int numGrpcChannels;
125130
@Nullable private final Integer maxCommitDelayMillis;
126131
private final boolean enableBuiltInMetrics;
132+
private final boolean usePlainText;
127133

128134
private ListenerConfig(Builder builder) {
129135
this.databaseUri = builder.databaseUri;
@@ -133,6 +139,7 @@ private ListenerConfig(Builder builder) {
133139
this.numGrpcChannels = builder.numGrpcChannels;
134140
this.maxCommitDelayMillis = builder.maxCommitDelayMillis;
135141
this.enableBuiltInMetrics = builder.enableBuiltInMetrics;
142+
this.usePlainText = builder.usePlainText;
136143
}
137144

138145
public String getDatabaseUri() {
@@ -164,8 +171,15 @@ public boolean isEnableBuiltInMetrics() {
164171
return enableBuiltInMetrics;
165172
}
166173

174+
public boolean usePlainText() {
175+
return usePlainText;
176+
}
177+
167178
static ListenerConfig fromListenerConfigs(
168-
ListenerConfigs listener, String globalSpannerEndpoint, boolean globalEnableBuiltInMetrics)
179+
ListenerConfigs listener,
180+
String globalSpannerEndpoint,
181+
boolean globalEnableBuiltInMetrics,
182+
boolean usePlainText)
169183
throws UnknownHostException {
170184
String host = listener.getHost() != null ? listener.getHost() : ConfigConstants.DEFAULT_HOST;
171185
int port = listener.getPort() != null ? listener.getPort() : ConfigConstants.DEFAULT_PORT;
@@ -183,6 +197,7 @@ static ListenerConfig fromListenerConfigs(
183197
.numGrpcChannels(numGrpcChannels)
184198
.maxCommitDelayMillis(maxCommitDelayMillis)
185199
.enableBuiltInMetrics(globalEnableBuiltInMetrics)
200+
.usePlainText(usePlainText)
186201
.build();
187202
}
188203

@@ -193,6 +208,9 @@ static ListenerConfig fromProperties(Map<String, String> properties) throws Unkn
193208
Integer.parseInt(
194209
properties.getOrDefault(
195210
ConfigConstants.PORT_PROP_KEY, String.valueOf(ConfigConstants.DEFAULT_PORT)));
211+
String spannerEndpoint =
212+
properties.getOrDefault(
213+
ConfigConstants.SPANNER_ENDPOINT_PROP_KEY, ConfigConstants.DEFAULT_SPANNER_ENDPOINT);
196214
int numGrpcChannels =
197215
Integer.parseInt(
198216
properties.getOrDefault(
@@ -204,15 +222,19 @@ static ListenerConfig fromProperties(Map<String, String> properties) throws Unkn
204222
boolean enableBuiltInMetrics =
205223
Boolean.parseBoolean(
206224
properties.getOrDefault(ConfigConstants.ENABLE_BUILTIN_METRICS_PROP_KEY, "false"));
225+
boolean usePlainText =
226+
Boolean.parseBoolean(
227+
properties.getOrDefault(ConfigConstants.USE_PLAINTEXT_PROP_KEY, "false"));
207228

208229
return newBuilder()
209230
.databaseUri(properties.get(ConfigConstants.DATABASE_URI_PROP_KEY))
210231
.hostAddress(InetAddress.getByName(host))
211232
.port(port)
212-
.spannerEndpoint(ConfigConstants.DEFAULT_SPANNER_ENDPOINT)
233+
.spannerEndpoint(spannerEndpoint)
213234
.numGrpcChannels(numGrpcChannels)
214235
.maxCommitDelayMillis(maxCommitDelayMillis)
215236
.enableBuiltInMetrics(enableBuiltInMetrics)
237+
.usePlainText(usePlainText)
216238
.build();
217239
}
218240

@@ -228,6 +250,7 @@ static class Builder {
228250
private int numGrpcChannels;
229251
@Nullable private Integer maxCommitDelayMillis;
230252
private boolean enableBuiltInMetrics;
253+
private boolean usePlainText;
231254

232255
public Builder databaseUri(String databaseUri) {
233256
this.databaseUri = databaseUri;
@@ -264,6 +287,11 @@ public Builder enableBuiltInMetrics(boolean enableBuiltInMetrics) {
264287
return this;
265288
}
266289

290+
public Builder usePlainText(boolean usePlainText) {
291+
this.usePlainText = usePlainText;
292+
return this;
293+
}
294+
267295
public ListenerConfig build() {
268296
return new ListenerConfig(this);
269297
}

google-cloud-spanner-cassandra/src/main/java/com/google/cloud/spanner/adapter/SpannerCqlSessionBuilder.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ public final class SpannerCqlSessionBuilder
6565
private TransportChannelProvider channelProvider = null;
6666
private Credentials credentials;
6767
private boolean useVirtualThreads;
68+
private boolean usePlainText;
6869

6970
/**
7071
* Wraps the default CQL session with a SpannerCqlSession instance.
@@ -145,6 +146,15 @@ protected SpannerCqlSessionBuilder setUseVirtualThreads(boolean useVirtualThread
145146
return this;
146147
}
147148

149+
/**
150+
* (Optional, default `false`) Enables/disables plaintext connections to the Spanner endpoint.
151+
* This should only be used for testing against a local fake Spanner instance.
152+
*/
153+
public SpannerCqlSessionBuilder setUsePlainText(boolean usePlainText) {
154+
this.usePlainText = usePlainText;
155+
return this;
156+
}
157+
148158
/**
149159
* Creates the session with the options set by this builder.
150160
*
@@ -270,6 +280,7 @@ private void createAndStartAdapter() {
270280
.maxCommitDelay(maxCommitDelay.orElse(null))
271281
.metricsRecorder(metricsRecorder)
272282
.useVirtualThreads(useVirtualThreads)
283+
.usePlainText(usePlainText)
273284
.build();
274285
adapter = new Adapter(adapterOptions);
275286
adapter.start();

google-cloud-spanner-cassandra/src/main/java/com/google/cloud/spanner/adapter/configs/ConfigConstants.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public final class ConfigConstants {
2323
private ConfigConstants() {}
2424

2525
public static final String DEFAULT_SPANNER_ENDPOINT = "spanner.googleapis.com:443";
26+
public static final String SPANNER_ENDPOINT_PROP_KEY = "spannerEndpoint";
2627
public static final String DATABASE_URI_PROP_KEY = "databaseUri";
2728
public static final String HOST_PROP_KEY = "host";
2829
public static final String PORT_PROP_KEY = "port";
@@ -34,4 +35,5 @@ private ConfigConstants() {}
3435
public static final String ENABLE_BUILTIN_METRICS_PROP_KEY = "enableBuiltInMetrics";
3536
public static final String HEALTH_CHECK_PORT_PROP_KEY = "healthCheckPort";
3637
public static final String CONFIG_FILE_PROP_KEY = "configFilePath";
38+
public static final String USE_PLAINTEXT_PROP_KEY = "usePlainText";
3739
}

google-cloud-spanner-cassandra/src/main/java/com/google/cloud/spanner/adapter/configs/GlobalClientConfigs.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,31 @@ public class GlobalClientConfigs {
2323
private final String spannerEndpoint;
2424
private final Boolean enableBuiltInMetrics;
2525
private final String healthCheckEndpoint;
26+
private final Boolean usePlainText;
2627

2728
public GlobalClientConfigs(
28-
String spannerEndpoint, Boolean enableBuiltInMetrics, String healthCheckEndpoint) {
29+
String spannerEndpoint,
30+
Boolean enableBuiltInMetrics,
31+
String healthCheckEndpoint,
32+
Boolean usePlainText) {
2933
this.spannerEndpoint = spannerEndpoint;
3034
this.enableBuiltInMetrics = enableBuiltInMetrics;
3135
this.healthCheckEndpoint = healthCheckEndpoint;
36+
this.usePlainText = usePlainText;
37+
}
38+
39+
public GlobalClientConfigs(
40+
String spannerEndpoint, Boolean enableBuiltInMetrics, String healthCheckEndpoint) {
41+
this(spannerEndpoint, enableBuiltInMetrics, healthCheckEndpoint, null);
3242
}
3343

3444
public static GlobalClientConfigs fromMap(Map<String, Object> yamlMap) {
3545
String spannerEndpoint = (String) yamlMap.get("spannerEndpoint");
3646
Boolean enableBuiltInMetrics = (Boolean) yamlMap.get("enableBuiltInMetrics");
3747
String healthCheckEndpoint = (String) yamlMap.get("healthCheckEndpoint");
38-
return new GlobalClientConfigs(spannerEndpoint, enableBuiltInMetrics, healthCheckEndpoint);
48+
Boolean usePlainText = (Boolean) yamlMap.get("usePlainText");
49+
return new GlobalClientConfigs(
50+
spannerEndpoint, enableBuiltInMetrics, healthCheckEndpoint, usePlainText);
3951
}
4052

4153
public String getSpannerEndpoint() {
@@ -49,4 +61,8 @@ public Boolean getEnableBuiltInMetrics() {
4961
public String getHealthCheckEndpoint() {
5062
return healthCheckEndpoint;
5163
}
64+
65+
public Boolean getUsePlainText() {
66+
return usePlainText;
67+
}
5268
}

google-cloud-spanner-cassandra/src/test/java/com/google/cloud/spanner/adapter/LauncherConfigParserTest.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,28 @@ public void testParse_withConfigFileAndOtherParams_usesConfigFile() throws Excep
104104
assertThat(listenerConfig2.getPort()).isEqualTo(9043);
105105
}
106106

107+
@Test
108+
public void testParse_withValidUsePlainTextConfigFile() throws Exception {
109+
String configFile =
110+
getClass().getClassLoader().getResource("valid-useplaintext-config.yaml").getFile();
111+
Map<String, String> properties = new HashMap<>();
112+
properties.put("configFilePath", configFile);
113+
114+
LauncherConfig config = LauncherConfigParser.parse(properties);
115+
116+
assertThat(config.getListeners()).hasSize(2);
117+
ListenerConfig listenerConfig1 = config.getListeners().get(0);
118+
assertThat(listenerConfig1.getSpannerEndpoint()).isEqualTo("localhost:15000");
119+
assertThat(listenerConfig1.usePlainText()).isTrue();
120+
121+
ListenerConfig listenerConfig2 = config.getListeners().get(1);
122+
assertThat(listenerConfig2.getDatabaseUri())
123+
.isEqualTo("projects/my-project/instances/my-instance/databases/my-database-2");
124+
assertThat(listenerConfig2.getPort()).isEqualTo(9043);
125+
assertThat(listenerConfig2.getSpannerEndpoint()).isEqualTo("localhost:15000");
126+
assertThat(listenerConfig2.usePlainText()).isTrue();
127+
}
128+
107129
@Test
108130
public void testParse_withSystemProperties() throws Exception {
109131
Map<String, String> properties = new HashMap<>();
@@ -134,6 +156,41 @@ public void testParse_withSystemProperties() throws Exception {
134156
}
135157
}
136158

159+
@Test
160+
public void testParse_withUsePlainTextSystemProperties() throws Exception {
161+
Map<String, String> properties = new HashMap<>();
162+
properties.put("databaseUri", DEFAULT_DATABASE_URI);
163+
properties.put("host", "127.0.0.1");
164+
properties.put("port", "9042");
165+
properties.put("numGrpcChannels", "8");
166+
properties.put("maxCommitDelayMillis", "100");
167+
properties.put("enableBuiltInMetrics", "true");
168+
properties.put("healthCheckPort", "8080");
169+
properties.put("spannerEndpoint", "localhost:15000");
170+
properties.put("usePlainText", "true");
171+
172+
try (MockedStatic<InetAddress> mockedInetAddress = mockStatic(InetAddress.class)) {
173+
InetAddress mockAddress = mock(InetAddress.class);
174+
when(mockAddress.getHostAddress()).thenReturn("127.0.0.1");
175+
mockedInetAddress.when(() -> InetAddress.getByName("127.0.0.1")).thenReturn(mockAddress);
176+
177+
LauncherConfig config = LauncherConfigParser.parse(properties);
178+
assertThat(config.getListeners()).hasSize(1);
179+
ListenerConfig listenerConfig = config.getListeners().get(0);
180+
assertThat(listenerConfig.getDatabaseUri()).isEqualTo(DEFAULT_DATABASE_URI);
181+
assertThat(listenerConfig.getPort()).isEqualTo(9042);
182+
assertThat(listenerConfig.getHostAddress().getHostAddress()).isEqualTo("127.0.0.1");
183+
assertThat(listenerConfig.getNumGrpcChannels()).isEqualTo(8);
184+
assertThat(listenerConfig.getMaxCommitDelayMillis()).isEqualTo(100);
185+
assertThat(listenerConfig.isEnableBuiltInMetrics()).isTrue();
186+
assertThat(config.getHealthCheckConfig()).isNotNull();
187+
assertThat(config.getHealthCheckConfig().getPort()).isEqualTo(8080);
188+
assertThat(listenerConfig.getSpannerEndpoint()).isNotNull();
189+
assertThat(listenerConfig.getSpannerEndpoint()).isEqualTo("localhost:15000");
190+
assertThat(listenerConfig.usePlainText()).isTrue();
191+
}
192+
}
193+
137194
@Test
138195
public void testParse_withMissingDatabaseUri_throwsIllegalArgumentException() {
139196
Map<String, String> properties = Collections.emptyMap();

0 commit comments

Comments
 (0)