Skip to content
This repository was archived by the owner on Dec 8, 2021. It is now read-only.

Commit 5270018

Browse files
authored
feat: environment-variable override of the spanner endpoint (#1269)
When the `SPANNER_EMULATOR_HOST` environment variable is set, the endpoint/credentials for data and admin connections are overridden by `${SPANNER_EMULATOR_HOST}` and `grpc::InsecureChannelCredentials()` respectively. Fixes #1200.
1 parent 45961b8 commit 5270018

File tree

8 files changed

+205
-114
lines changed

8 files changed

+205
-114
lines changed

google/cloud/spanner/connection_options.cc

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,22 @@ std::string BaseUserAgentPrefix() {
3333

3434
ConnectionOptions::ConnectionOptions(
3535
std::shared_ptr<grpc::ChannelCredentials> credentials)
36-
: credentials_(std::move(credentials)),
36+
: emulator_override_(false),
37+
credentials_(std::move(credentials)),
3738
endpoint_("spanner.googleapis.com"),
3839
num_channels_(4),
3940
user_agent_prefix_(internal::BaseUserAgentPrefix()),
4041
background_threads_factory_([] {
4142
return google::cloud::internal::make_unique<
4243
google::cloud::internal::AutomaticallyCreatedBackgroundThreads>();
4344
}) {
45+
auto emulator_addr = google::cloud::internal::GetEnv("SPANNER_EMULATOR_HOST");
46+
if (emulator_addr.has_value()) {
47+
emulator_override_ = true;
48+
credentials_ = grpc::InsecureChannelCredentials();
49+
endpoint_ = *emulator_addr;
50+
}
51+
4452
auto tracing =
4553
google::cloud::internal::GetEnv("GOOGLE_CLOUD_CPP_ENABLE_TRACING");
4654
if (tracing.has_value()) {

google/cloud/spanner/connection_options.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ class ConnectionOptions {
5252
/// `grpc::GoogleDefaultCredentials()`.
5353
ConnectionOptions& set_credentials(
5454
std::shared_ptr<grpc::ChannelCredentials> v) {
55-
credentials_ = std::move(v);
55+
if (!emulator_override_) credentials_ = std::move(v);
5656
return *this;
5757
}
5858

@@ -69,7 +69,7 @@ class ConnectionOptions {
6969
* simulator, or (2) to use a beta or EAP version of the service.
7070
*/
7171
ConnectionOptions& set_endpoint(std::string v) {
72-
endpoint_ = std::move(v);
72+
if (!emulator_override_) endpoint_ = std::move(v);
7373
return *this;
7474
}
7575

@@ -197,6 +197,7 @@ class ConnectionOptions {
197197
}
198198

199199
private:
200+
bool emulator_override_; // credentials_ and endpoint_ frozen
200201
std::shared_ptr<grpc::ChannelCredentials> credentials_;
201202
std::string endpoint_;
202203
int num_channels_;

google/cloud/spanner/connection_options_test.cc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ using ::testing::HasSubstr;
2929
using ::testing::StartsWith;
3030

3131
TEST(ConnectionOptionsTest, Credentials) {
32+
EnvironmentVariableRestore restore("SPANNER_EMULATOR_HOST");
33+
google::cloud::internal::UnsetEnv("SPANNER_EMULATOR_HOST");
34+
3235
// In the CI environment grpc::GoogleDefaultCredentials() may assert. Use the
3336
// insecure credentials to initialize the options in any unit test.
3437
auto expected = grpc::InsecureChannelCredentials();
@@ -42,12 +45,35 @@ TEST(ConnectionOptionsTest, Credentials) {
4245
}
4346

4447
TEST(ConnectionOptionsTest, AdminEndpoint) {
48+
EnvironmentVariableRestore restore("SPANNER_EMULATOR_HOST");
49+
google::cloud::internal::UnsetEnv("SPANNER_EMULATOR_HOST");
50+
4551
ConnectionOptions options(grpc::InsecureChannelCredentials());
4652
EXPECT_EQ("spanner.googleapis.com", options.endpoint());
4753
options.set_endpoint("invalid-endpoint");
4854
EXPECT_EQ("invalid-endpoint", options.endpoint());
4955
}
5056

57+
TEST(ConnectionOptionsTest, SpannerEmulator) {
58+
EnvironmentVariableRestore restore("SPANNER_EMULATOR_HOST");
59+
google::cloud::internal::SetEnv("SPANNER_EMULATOR_HOST", "localhost:9010");
60+
61+
ConnectionOptions options(std::shared_ptr<grpc::ChannelCredentials>{});
62+
63+
// When SPANNER_EMULATOR_HOST is set, the passed credentials are overridden
64+
// (by grpc::InsecureChannelCredentials()) and further updates are ignored.
65+
EXPECT_NE(std::shared_ptr<grpc::ChannelCredentials>{}, options.credentials());
66+
auto other_credentials = grpc::InsecureChannelCredentials();
67+
options.set_credentials(other_credentials);
68+
EXPECT_NE(other_credentials, options.credentials());
69+
70+
// When SPANNER_EMULATOR_HOST is set, the default endpoint is overridden (by
71+
// ${SPANNER_EMULATOR_HOST}), and further updates are ignored.
72+
EXPECT_EQ("localhost:9010", options.endpoint());
73+
options.set_endpoint("invalid-endpoint");
74+
EXPECT_EQ("localhost:9010", options.endpoint());
75+
}
76+
5177
TEST(ConnectionOptionsTest, NumChannels) {
5278
ConnectionOptions options(grpc::InsecureChannelCredentials());
5379
int num_channels = options.num_channels();

google/cloud/spanner/integration_tests/client_integration_test.cc

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ using ::testing::UnorderedElementsAreArray;
3434

3535
class ClientIntegrationTest : public ::testing::Test {
3636
public:
37+
ClientIntegrationTest()
38+
: emulator_(google::cloud::internal::GetEnv("SPANNER_EMULATOR_HOST")
39+
.has_value()) {}
40+
3741
static void SetUpTestSuite() {
3842
client_ = google::cloud::internal::make_unique<Client>(
3943
MakeConnection(spanner_testing::DatabaseEnvironment::GetDatabase()));
@@ -60,6 +64,7 @@ class ClientIntegrationTest : public ::testing::Test {
6064
static void TearDownTestSuite() { client_ = nullptr; }
6165

6266
protected:
67+
bool emulator_;
6368
static std::unique_ptr<Client> client_;
6469
};
6570

@@ -335,6 +340,8 @@ TEST_F(ClientIntegrationTest, ExecuteQueryDml) {
335340

336341
/// @test Test ExecutePartitionedDml
337342
TEST_F(ClientIntegrationTest, ExecutePartitionedDml) {
343+
if (emulator_) return;
344+
338345
auto& client = *client_;
339346
auto insert_result =
340347
client_->Commit([&client](Transaction txn) -> StatusOr<Mutations> {
@@ -546,6 +553,8 @@ StatusOr<std::vector<std::vector<Value>>> AddSingerDataToTable(Client client) {
546553
}
547554

548555
TEST_F(ClientIntegrationTest, PartitionRead) {
556+
if (emulator_) return;
557+
549558
auto expected_rows = AddSingerDataToTable(*client_);
550559
ASSERT_STATUS_OK(expected_rows);
551560

@@ -587,6 +596,8 @@ TEST_F(ClientIntegrationTest, PartitionRead) {
587596
}
588597

589598
TEST_F(ClientIntegrationTest, PartitionQuery) {
599+
if (emulator_) return;
600+
590601
auto expected_rows = AddSingerDataToTable(*client_);
591602
ASSERT_STATUS_OK(expected_rows);
592603

@@ -628,6 +639,8 @@ TEST_F(ClientIntegrationTest, PartitionQuery) {
628639
}
629640

630641
TEST_F(ClientIntegrationTest, ExecuteBatchDml) {
642+
if (emulator_) return;
643+
631644
auto statements = {
632645
SqlStatement("INSERT INTO Singers (SingerId, FirstName, LastName) "
633646
"VALUES(1, 'Foo1', 'Bar1')"),
@@ -685,6 +698,8 @@ TEST_F(ClientIntegrationTest, ExecuteBatchDml) {
685698
}
686699

687700
TEST_F(ClientIntegrationTest, ExecuteBatchDmlMany) {
701+
if (emulator_) return;
702+
688703
std::vector<SqlStatement> v;
689704
constexpr auto kBatchSize = 200;
690705
for (int i = 0; i < kBatchSize; ++i) {
@@ -755,6 +770,8 @@ TEST_F(ClientIntegrationTest, ExecuteBatchDmlMany) {
755770
}
756771

757772
TEST_F(ClientIntegrationTest, ExecuteBatchDmlFailure) {
773+
if (emulator_) return;
774+
758775
auto statements = {
759776
SqlStatement("INSERT INTO Singers (SingerId, FirstName, LastName) "
760777
"VALUES(1, 'Foo1', 'Bar1')"),
@@ -785,6 +802,8 @@ TEST_F(ClientIntegrationTest, ExecuteBatchDmlFailure) {
785802
}
786803

787804
TEST_F(ClientIntegrationTest, AnalyzeSql) {
805+
if (emulator_) return;
806+
788807
auto txn = MakeReadOnlyTransaction();
789808
auto sql = SqlStatement(
790809
"SELECT * FROM Singers "
@@ -810,9 +829,11 @@ TEST_F(ClientIntegrationTest, ProfileQuery) {
810829
EXPECT_TRUE(stats);
811830
EXPECT_GT(stats->size(), 0);
812831

813-
auto plan = rows.ExecutionPlan();
814-
EXPECT_TRUE(plan);
815-
EXPECT_GT(plan->plan_nodes_size(), 0);
832+
if (!emulator_) {
833+
auto plan = rows.ExecutionPlan();
834+
EXPECT_TRUE(plan);
835+
EXPECT_GT(plan->plan_nodes_size(), 0);
836+
}
816837
}
817838

818839
TEST_F(ClientIntegrationTest, ProfileDml) {
@@ -836,9 +857,11 @@ TEST_F(ClientIntegrationTest, ProfileDml) {
836857
EXPECT_TRUE(stats);
837858
EXPECT_GT(stats->size(), 0);
838859

839-
auto plan = profile_result.ExecutionPlan();
840-
EXPECT_TRUE(plan);
841-
EXPECT_GT(plan->plan_nodes_size(), 0);
860+
if (!emulator_) {
861+
auto plan = profile_result.ExecutionPlan();
862+
EXPECT_TRUE(plan);
863+
EXPECT_GT(plan->plan_nodes_size(), 0);
864+
}
842865
}
843866

844867
} // namespace

google/cloud/spanner/integration_tests/database_admin_integration_test.cc

Lines changed: 54 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ using ::testing::EndsWith;
3333

3434
/// @test Verify the basic CRUD operations for databases work.
3535
TEST(DatabaseAdminClient, DatabaseBasicCRUD) {
36+
auto emulator =
37+
google::cloud::internal::GetEnv("SPANNER_EMULATOR_HOST").has_value();
3638
auto project_id =
3739
google::cloud::internal::GetEnv("GOOGLE_CLOUD_PROJECT").value_or("");
3840
ASSERT_FALSE(project_id.empty());
@@ -44,7 +46,7 @@ TEST(DatabaseAdminClient, DatabaseBasicCRUD) {
4446
auto test_iam_service_account =
4547
google::cloud::internal::GetEnv("GOOGLE_CLOUD_CPP_SPANNER_IAM_TEST_SA")
4648
.value_or("");
47-
ASSERT_FALSE(test_iam_service_account.empty());
49+
ASSERT_TRUE(emulator || !test_iam_service_account.empty());
4850

4951
Instance const in(project_id, *instance_id);
5052

@@ -85,55 +87,57 @@ TEST(DatabaseAdminClient, DatabaseBasicCRUD) {
8587
ASSERT_STATUS_OK(get_result);
8688
EXPECT_EQ(database->name(), get_result->name());
8789

88-
auto current_policy = client.GetIamPolicy(db);
89-
ASSERT_STATUS_OK(current_policy);
90-
EXPECT_EQ(0, current_policy->bindings_size());
91-
92-
std::string const reader_role = "roles/spanner.databaseReader";
93-
std::string const writer_role = "roles/spanner.databaseUser";
94-
std::string const expected_member =
95-
"serviceAccount:" + test_iam_service_account;
96-
auto& binding = *current_policy->add_bindings();
97-
binding.set_role(reader_role);
98-
*binding.add_members() = expected_member;
99-
100-
auto updated_policy = client.SetIamPolicy(db, *current_policy);
101-
ASSERT_STATUS_OK(updated_policy);
102-
EXPECT_EQ(1, updated_policy->bindings_size());
103-
ASSERT_EQ(reader_role, updated_policy->bindings().Get(0).role());
104-
ASSERT_EQ(1, updated_policy->bindings().Get(0).members().size());
105-
ASSERT_EQ(expected_member,
106-
updated_policy->bindings().Get(0).members().Get(0));
107-
108-
// Perform a different update using the the OCC loop API:
109-
updated_policy =
110-
client.SetIamPolicy(db, [&test_iam_service_account,
111-
&writer_role](google::iam::v1::Policy current) {
112-
std::string const expected_member =
113-
"serviceAccount:" + test_iam_service_account;
114-
auto& binding = *current.add_bindings();
115-
binding.set_role(writer_role);
116-
*binding.add_members() = expected_member;
117-
return current;
118-
});
119-
ASSERT_STATUS_OK(updated_policy);
120-
EXPECT_EQ(2, updated_policy->bindings_size());
121-
ASSERT_EQ(writer_role, updated_policy->bindings().Get(1).role());
122-
ASSERT_EQ(1, updated_policy->bindings().Get(1).members().size());
123-
ASSERT_EQ(expected_member,
124-
updated_policy->bindings().Get(1).members().Get(0));
125-
126-
// Fetch the Iam Policy again.
127-
current_policy = client.GetIamPolicy(db);
128-
ASSERT_STATUS_OK(current_policy);
129-
EXPECT_THAT(*updated_policy, IsProtoEqual(*current_policy));
130-
131-
auto test_iam_permission_result =
132-
client.TestIamPermissions(db, {"spanner.databases.read"});
133-
ASSERT_STATUS_OK(test_iam_permission_result);
134-
ASSERT_EQ(1, test_iam_permission_result->permissions_size());
135-
ASSERT_EQ("spanner.databases.read",
136-
test_iam_permission_result->permissions(0));
90+
if (!emulator) {
91+
auto current_policy = client.GetIamPolicy(db);
92+
ASSERT_STATUS_OK(current_policy);
93+
EXPECT_EQ(0, current_policy->bindings_size());
94+
95+
std::string const reader_role = "roles/spanner.databaseReader";
96+
std::string const writer_role = "roles/spanner.databaseUser";
97+
std::string const expected_member =
98+
"serviceAccount:" + test_iam_service_account;
99+
auto& binding = *current_policy->add_bindings();
100+
binding.set_role(reader_role);
101+
*binding.add_members() = expected_member;
102+
103+
auto updated_policy = client.SetIamPolicy(db, *current_policy);
104+
ASSERT_STATUS_OK(updated_policy);
105+
EXPECT_EQ(1, updated_policy->bindings_size());
106+
ASSERT_EQ(reader_role, updated_policy->bindings().Get(0).role());
107+
ASSERT_EQ(1, updated_policy->bindings().Get(0).members().size());
108+
ASSERT_EQ(expected_member,
109+
updated_policy->bindings().Get(0).members().Get(0));
110+
111+
// Perform a different update using the the OCC loop API:
112+
updated_policy =
113+
client.SetIamPolicy(db, [&test_iam_service_account, &writer_role](
114+
google::iam::v1::Policy current) {
115+
std::string const expected_member =
116+
"serviceAccount:" + test_iam_service_account;
117+
auto& binding = *current.add_bindings();
118+
binding.set_role(writer_role);
119+
*binding.add_members() = expected_member;
120+
return current;
121+
});
122+
ASSERT_STATUS_OK(updated_policy);
123+
EXPECT_EQ(2, updated_policy->bindings_size());
124+
ASSERT_EQ(writer_role, updated_policy->bindings().Get(1).role());
125+
ASSERT_EQ(1, updated_policy->bindings().Get(1).members().size());
126+
ASSERT_EQ(expected_member,
127+
updated_policy->bindings().Get(1).members().Get(0));
128+
129+
// Fetch the Iam Policy again.
130+
current_policy = client.GetIamPolicy(db);
131+
ASSERT_STATUS_OK(current_policy);
132+
EXPECT_THAT(*updated_policy, IsProtoEqual(*current_policy));
133+
134+
auto test_iam_permission_result =
135+
client.TestIamPermissions(db, {"spanner.databases.read"});
136+
ASSERT_STATUS_OK(test_iam_permission_result);
137+
ASSERT_EQ(1, test_iam_permission_result->permissions_size());
138+
ASSERT_EQ("spanner.databases.read",
139+
test_iam_permission_result->permissions(0));
140+
}
137141

138142
auto get_ddl_result = client.GetDatabaseDdl(db);
139143
ASSERT_STATUS_OK(get_ddl_result);

google/cloud/spanner/integration_tests/instance_admin_integration_test.cc

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ class InstanceAdminClientTest : public testing::Test {
3939

4040
protected:
4141
void SetUp() override {
42+
emulator_ =
43+
google::cloud::internal::GetEnv("SPANNER_EMULATOR_HOST").has_value();
4244
project_id_ =
4345
google::cloud::internal::GetEnv("GOOGLE_CLOUD_PROJECT").value_or("");
4446
instance_id_ =
@@ -52,6 +54,7 @@ class InstanceAdminClientTest : public testing::Test {
5254
.value_or("");
5355
}
5456
InstanceAdminClient client_;
57+
bool emulator_;
5558
std::string project_id_;
5659
std::string instance_id_;
5760
std::string run_slow_integration_tests_;
@@ -170,21 +173,23 @@ TEST_F(InstanceAdminClientTestWithCleanup, InstanceCRUDOperations) {
170173
EXPECT_THAT(instance->name(), HasSubstr(instance_id));
171174
EXPECT_EQ("test-display-name", instance->display_name());
172175
EXPECT_NE(0, instance->node_count());
173-
EXPECT_NE(0, instance->labels_size());
174-
EXPECT_EQ(instance_config, instance->config());
175-
EXPECT_EQ("label-value", instance->labels().at("label-key"));
176-
177-
// Then update the instance
178-
f = client_.UpdateInstance(UpdateInstanceRequestBuilder(*instance)
179-
.SetDisplayName("New display name")
180-
.AddLabels({{"new-key", "new-value"}})
181-
.SetNodeCount(2)
182-
.Build());
183-
instance = f.get();
184-
EXPECT_EQ("New display name", instance->display_name());
185-
EXPECT_EQ(2, instance->labels_size());
186-
EXPECT_EQ("new-value", instance->labels().at("new-key"));
187-
EXPECT_EQ(2, instance->node_count());
176+
if (!emulator_) {
177+
EXPECT_NE(0, instance->labels_size());
178+
EXPECT_EQ(instance_config, instance->config());
179+
EXPECT_EQ("label-value", instance->labels().at("label-key"));
180+
181+
// Then update the instance
182+
f = client_.UpdateInstance(UpdateInstanceRequestBuilder(*instance)
183+
.SetDisplayName("New display name")
184+
.AddLabels({{"new-key", "new-value"}})
185+
.SetNodeCount(2)
186+
.Build());
187+
instance = f.get();
188+
EXPECT_EQ("New display name", instance->display_name());
189+
EXPECT_EQ(2, instance->labels_size());
190+
EXPECT_EQ("new-value", instance->labels().at("new-key"));
191+
EXPECT_EQ(2, instance->node_count());
192+
}
188193
auto status = client_.DeleteInstance(in);
189194
EXPECT_STATUS_OK(status);
190195
}
@@ -213,6 +218,8 @@ TEST_F(InstanceAdminClientTest, InstanceConfig) {
213218
}
214219

215220
TEST_F(InstanceAdminClientTest, InstanceIam) {
221+
if (emulator_) return;
222+
216223
ASSERT_FALSE(project_id_.empty());
217224
ASSERT_FALSE(instance_id_.empty());
218225
ASSERT_FALSE(test_iam_service_account_.empty());

0 commit comments

Comments
 (0)