Skip to content

Commit 3815456

Browse files
committed
refactor: move local connection checker to separate class
1 parent 1b5d05c commit 3815456

File tree

2 files changed

+101
-69
lines changed

2 files changed

+101
-69
lines changed

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java

Lines changed: 3 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,6 @@
1717
package com.google.cloud.spanner.connection;
1818

1919
import com.google.api.core.InternalApi;
20-
import com.google.api.gax.core.NoCredentialsProvider;
21-
import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider;
22-
import com.google.api.gax.rpc.UnavailableException;
23-
import com.google.api.gax.rpc.UnimplementedException;
2420
import com.google.auth.Credentials;
2521
import com.google.auth.oauth2.AccessToken;
2622
import com.google.auth.oauth2.GoogleCredentials;
@@ -34,14 +30,10 @@
3430
import com.google.cloud.spanner.SpannerException;
3531
import com.google.cloud.spanner.SpannerExceptionFactory;
3632
import com.google.cloud.spanner.SpannerOptions;
37-
import com.google.cloud.spanner.admin.instance.v1.stub.GrpcInstanceAdminStub;
38-
import com.google.cloud.spanner.admin.instance.v1.stub.InstanceAdminStubSettings;
3933
import com.google.common.annotations.VisibleForTesting;
4034
import com.google.common.base.Preconditions;
4135
import com.google.common.collect.Sets;
42-
import com.google.spanner.admin.instance.v1.ListInstanceConfigsRequest;
4336
import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions;
44-
import java.io.IOException;
4537
import java.util.ArrayList;
4638
import java.util.Arrays;
4739
import java.util.Collections;
@@ -51,7 +43,6 @@
5143
import java.util.regex.Matcher;
5244
import java.util.regex.Pattern;
5345
import javax.annotation.Nullable;
54-
import org.threeten.bp.Duration;
5546

5647
/**
5748
* Internal connection API for Google Cloud Spanner. This class may introduce breaking changes
@@ -153,6 +144,8 @@ public String[] getValidValues() {
153144
}
154145
}
155146

147+
private static final LocalConnectionChecker LOCAL_CONNECTION_CHECKER =
148+
new LocalConnectionChecker();
156149
private static final boolean DEFAULT_USE_PLAIN_TEXT = false;
157150
static final boolean DEFAULT_AUTOCOMMIT = true;
158151
static final boolean DEFAULT_READONLY = false;
@@ -740,65 +733,6 @@ static List<String> parseProperties(String uri) {
740733
return res;
741734
}
742735

743-
/**
744-
* Executes a quick check to see if this connection can actually connect to a local emulator host
745-
* or other (mock) test server, if the options point to localhost instead of Cloud Spanner.
746-
*/
747-
private void checkLocalConnection() {
748-
final String emulatorHost = System.getenv("SPANNER_EMULATOR_HOST");
749-
String host = getHost() == null ? emulatorHost : getHost();
750-
if (host.startsWith("https://")) {
751-
host = host.substring(8);
752-
}
753-
if (host.startsWith("http://")) {
754-
host = host.substring(7);
755-
}
756-
// Only do the check if the host has been set to localhost.
757-
if (host != null && host.startsWith("localhost") && isUsePlainText()) {
758-
// Do a quick check to see if anything is actually running on the host.
759-
try {
760-
InstanceAdminStubSettings.Builder testEmulatorSettings =
761-
InstanceAdminStubSettings.newBuilder()
762-
.setCredentialsProvider(NoCredentialsProvider.create())
763-
.setTransportChannelProvider(
764-
InstantiatingGrpcChannelProvider.newBuilder().setEndpoint(host).build());
765-
testEmulatorSettings
766-
.listInstanceConfigsSettings()
767-
.setSimpleTimeoutNoRetries(Duration.ofSeconds(10L));
768-
try (GrpcInstanceAdminStub stub =
769-
GrpcInstanceAdminStub.create(testEmulatorSettings.build())) {
770-
stub.listInstanceConfigsCallable()
771-
.call(
772-
ListInstanceConfigsRequest.newBuilder()
773-
.setParent(String.format("projects/%s", getProjectId()))
774-
.build());
775-
}
776-
} catch (UnavailableException e) {
777-
String msg;
778-
if (getHost() != null) {
779-
msg =
780-
String.format(
781-
"The connection string '%s' contains host '%s', but no running"
782-
+ " emulator or other server could be found at that address.\n"
783-
+ "Please check the connection string and/or that the emulator is running.",
784-
getUri(), host);
785-
} else {
786-
msg =
787-
String.format(
788-
"The environment variable SPANNER_EMULATOR_HOST has been set to '%s', but no running"
789-
+ " emulator or other server could be found at that address.\n"
790-
+ "Please check the environment variable and/or that the emulator is running.",
791-
emulatorHost);
792-
}
793-
throw SpannerExceptionFactory.newSpannerException(ErrorCode.UNAVAILABLE, msg);
794-
} catch (UnimplementedException e) {
795-
// Ignore, this is probably a local mock server.
796-
} catch (IOException e) {
797-
// Ignore, this method is not checking whether valid credentials have been set.
798-
}
799-
}
800-
}
801-
802736
/**
803737
* Create a new {@link Connection} from this {@link ConnectionOptions}. Calling this method
804738
* multiple times for the same {@link ConnectionOptions} will return multiple instances of {@link
@@ -807,7 +741,7 @@ private void checkLocalConnection() {
807741
* @return a new {@link Connection} to the database referenced by this {@link ConnectionOptions}
808742
*/
809743
public Connection getConnection() {
810-
checkLocalConnection();
744+
LOCAL_CONNECTION_CHECKER.checkLocalConnection(this);
811745
return new ConnectionImpl(this);
812746
}
813747

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright 2021 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+
* http://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.spanner.connection;
18+
19+
import com.google.api.gax.core.NoCredentialsProvider;
20+
import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider;
21+
import com.google.api.gax.rpc.UnavailableException;
22+
import com.google.api.gax.rpc.UnimplementedException;
23+
import com.google.cloud.spanner.ErrorCode;
24+
import com.google.cloud.spanner.SpannerExceptionFactory;
25+
import com.google.cloud.spanner.admin.instance.v1.stub.GrpcInstanceAdminStub;
26+
import com.google.cloud.spanner.admin.instance.v1.stub.InstanceAdminStubSettings;
27+
import com.google.spanner.admin.instance.v1.ListInstanceConfigsRequest;
28+
import java.io.IOException;
29+
import org.threeten.bp.Duration;
30+
31+
/**
32+
* Util class for quickly checking whether a local emulator or test server can be found. A common
33+
* configuration error is to add 'localhost' to the connection string or to forget to unset the
34+
* SPANNER_EMULATOR_HOST environment variable. This can cause cryptic error messages. This util
35+
* checks for common configurations and errors and returns a more understandable error message for
36+
* known misconfigurations.
37+
*/
38+
class LocalConnectionChecker {
39+
40+
/**
41+
* Executes a quick check to see if this connection can actually connect to a local emulator host
42+
* or other (mock) test server, if the options point to localhost instead of Cloud Spanner.
43+
*/
44+
void checkLocalConnection(ConnectionOptions options) {
45+
final String emulatorHost = System.getenv("SPANNER_EMULATOR_HOST");
46+
String host = options.getHost() == null ? emulatorHost : options.getHost();
47+
if (host.startsWith("https://")) {
48+
host = host.substring(8);
49+
}
50+
if (host.startsWith("http://")) {
51+
host = host.substring(7);
52+
}
53+
// Only do the check if the host has been set to localhost.
54+
if (host != null && host.startsWith("localhost") && options.isUsePlainText()) {
55+
// Do a quick check to see if anything is actually running on the host.
56+
try {
57+
InstanceAdminStubSettings.Builder testEmulatorSettings =
58+
InstanceAdminStubSettings.newBuilder()
59+
.setCredentialsProvider(NoCredentialsProvider.create())
60+
.setTransportChannelProvider(
61+
InstantiatingGrpcChannelProvider.newBuilder().setEndpoint(host).build());
62+
testEmulatorSettings
63+
.listInstanceConfigsSettings()
64+
.setSimpleTimeoutNoRetries(Duration.ofSeconds(10L));
65+
try (GrpcInstanceAdminStub stub =
66+
GrpcInstanceAdminStub.create(testEmulatorSettings.build())) {
67+
stub.listInstanceConfigsCallable()
68+
.call(
69+
ListInstanceConfigsRequest.newBuilder()
70+
.setParent(String.format("projects/%s", options.getProjectId()))
71+
.build());
72+
}
73+
} catch (UnavailableException e) {
74+
String msg;
75+
if (options.getHost() != null) {
76+
msg =
77+
String.format(
78+
"The connection string '%s' contains host '%s', but no running"
79+
+ " emulator or other server could be found at that address.\n"
80+
+ "Please check the connection string and/or that the emulator is running.",
81+
options.getUri(), host);
82+
} else {
83+
msg =
84+
String.format(
85+
"The environment variable SPANNER_EMULATOR_HOST has been set to '%s', but no running"
86+
+ " emulator or other server could be found at that address.\n"
87+
+ "Please check the environment variable and/or that the emulator is running.",
88+
emulatorHost);
89+
}
90+
throw SpannerExceptionFactory.newSpannerException(ErrorCode.UNAVAILABLE, msg);
91+
} catch (UnimplementedException e) {
92+
// Ignore, this is probably a local mock server.
93+
} catch (IOException e) {
94+
// Ignore, this method is not checking whether valid credentials have been set.
95+
}
96+
}
97+
}
98+
}

0 commit comments

Comments
 (0)