From 5d8c7ba5bb1ba04e144610f58fa9ea360a67a8b8 Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Mon, 5 Jul 2021 19:31:54 +1000 Subject: [PATCH 01/12] feat: expose leader options in instance config Exposes the list of leader options for an instance from an instance config. --- .../google/cloud/spanner/InstanceConfig.java | 19 ++++- .../cloud/spanner/InstanceConfigInfo.java | 30 ++++++-- .../cloud/spanner/InstanceAdminGaxTest.java | 9 ++- .../cloud/spanner/InstanceConfigTest.java | 70 +++++++++++++++++++ 4 files changed, 119 insertions(+), 9 deletions(-) create mode 100644 google-cloud-spanner/src/test/java/com/google/cloud/spanner/InstanceConfigTest.java diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceConfig.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceConfig.java index 1076fae9c3..9cdc529042 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceConfig.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceConfig.java @@ -16,6 +16,9 @@ package com.google.cloud.spanner; +import java.util.Collections; +import java.util.List; + /** * Represents a Cloud Spanner instance config.{@code InstanceConfig} adds a layer of service related * functionality over {@code InstanceConfigInfo}. @@ -25,7 +28,15 @@ public class InstanceConfig extends InstanceConfigInfo { private final InstanceAdminClient client; public InstanceConfig(InstanceConfigId id, String displayName, InstanceAdminClient client) { - super(id, displayName); + this(id, displayName, Collections.emptyList(), client); + } + + public InstanceConfig( + InstanceConfigId id, + String displayName, + List leaderOptions, + InstanceAdminClient client) { + super(id, displayName, leaderOptions); this.client = client; } @@ -36,6 +47,10 @@ public InstanceConfig reload() { static InstanceConfig fromProto( com.google.spanner.admin.instance.v1.InstanceConfig proto, InstanceAdminClient client) { - return new InstanceConfig(InstanceConfigId.of(proto.getName()), proto.getDisplayName(), client); + return new InstanceConfig( + InstanceConfigId.of(proto.getName()), + proto.getDisplayName(), + proto.getLeaderOptionsList(), + client); } } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceConfigInfo.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceConfigInfo.java index 7b391e4c5d..587022796a 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceConfigInfo.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceConfigInfo.java @@ -16,6 +16,8 @@ package com.google.cloud.spanner; +import java.util.Collections; +import java.util.List; import java.util.Objects; /** Represents a Cloud Spanner instance config resource. */ @@ -23,10 +25,16 @@ public class InstanceConfigInfo { private final InstanceConfigId id; private final String displayName; + private final List leaderOptions; public InstanceConfigInfo(InstanceConfigId id, String displayName) { + this(id, displayName, Collections.emptyList()); + } + + public InstanceConfigInfo(InstanceConfigId id, String displayName, List leaderOptions) { this.id = id; this.displayName = displayName; + this.leaderOptions = leaderOptions; } /* @@ -41,9 +49,12 @@ public String getDisplayName() { return displayName; } - @Override - public int hashCode() { - return Objects.hash(id, displayName); + /** + * Allowed values of the default leader schema option for databases in instances that use this + * instance configuration. + */ + public List getLeaderOptions() { + return leaderOptions; } @Override @@ -51,15 +62,22 @@ public boolean equals(Object o) { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (!(o instanceof InstanceConfigInfo)) { return false; } InstanceConfigInfo that = (InstanceConfigInfo) o; - return that.id.equals(id) && that.displayName.equals(displayName); + return Objects.equals(id, that.id) + && Objects.equals(displayName, that.displayName) + && Objects.equals(leaderOptions, that.leaderOptions); + } + + @Override + public int hashCode() { + return Objects.hash(id, displayName, leaderOptions); } @Override public String toString() { - return String.format("Instance Config[%s, %s]", id, displayName); + return String.format("Instance Config[%s, %s, %s]", id, displayName, leaderOptions); } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InstanceAdminGaxTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InstanceAdminGaxTest.java index 6edb9fffde..c5a317ce5d 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InstanceAdminGaxTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InstanceAdminGaxTest.java @@ -45,6 +45,7 @@ import io.grpc.inprocess.InProcessServerBuilder; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -353,8 +354,13 @@ public void getInstanceConfigTest() { for (int i = 0; i < 2; i++) { InstanceConfigName name2 = InstanceConfigName.of(PROJECT, "INSTANCE_CONFIG"); String displayName = "displayName1615086568"; + List leaderOptions = Arrays.asList("leader option 1", "leader option 2"); InstanceConfig expectedResponse = - InstanceConfig.newBuilder().setName(name2.toString()).setDisplayName(displayName).build(); + InstanceConfig.newBuilder() + .setName(name2.toString()) + .setDisplayName(displayName) + .addAllLeaderOptions(leaderOptions) + .build(); if (exceptionAtCall == 0) { mockInstanceAdmin.addException(exception); } @@ -368,6 +374,7 @@ public void getInstanceConfigTest() { client.getInstanceConfig(name.toString()); Assert.assertEquals(displayName, actualResponse.getDisplayName()); + Assert.assertEquals(leaderOptions, actualResponse.getLeaderOptions()); List actualRequests = mockInstanceAdmin.getRequests(); Assert.assertEquals(i + 1, actualRequests.size()); } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InstanceConfigTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InstanceConfigTest.java new file mode 100644 index 0000000000..2d11f85582 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InstanceConfigTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +import java.util.Arrays; +import org.junit.Before; +import org.junit.Test; + +public class InstanceConfigTest { + + private InstanceAdminClient client; + + @Before + public void setUp() { + client = mock(InstanceAdminClient.class); + } + + @Test + public void testInstanceConfigFromProtoWithoutLeaderOptions() { + final InstanceConfig instanceConfig = + InstanceConfig.fromProto( + com.google.spanner.admin.instance.v1.InstanceConfig.newBuilder() + .setDisplayName("Display Name") + .setName("projects/my-project/instanceConfigs/my-instance-config") + .build(), + client); + + assertEquals( + new InstanceConfig( + InstanceConfigId.of("my-project", "my-instance-config"), "Display Name", client), + instanceConfig); + } + + @Test + public void testInstanceConfigFromProtoWithLeaderOptions() { + final InstanceConfig instanceConfig = + InstanceConfig.fromProto( + com.google.spanner.admin.instance.v1.InstanceConfig.newBuilder() + .setDisplayName("Display Name") + .setName("projects/my-project/instanceConfigs/my-instance-config") + .addAllLeaderOptions(Arrays.asList("Leader Option 1", "Leader Option 2")) + .build(), + client); + + assertEquals( + new InstanceConfig( + InstanceConfigId.of("my-project", "my-instance-config"), + "Display Name", + Arrays.asList("Leader Option 1", "Leader Option 2"), + client), + instanceConfig); + } +} From d88fb43fe4077ebe61e52d0bccdd6e45b83ffd77 Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Mon, 5 Jul 2021 20:00:44 +1000 Subject: [PATCH 02/12] feat: expose default leader in database --- .../com/google/cloud/spanner/Database.java | 1 + .../google/cloud/spanner/DatabaseInfo.java | 37 ++++++++++++++-- .../google/cloud/spanner/DatabaseTest.java | 42 +++++++++---------- 3 files changed, 55 insertions(+), 25 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java index 05ba3f2edf..30d5017272 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java @@ -187,6 +187,7 @@ static Database fromProto( .setVersionRetentionPeriod(proto.getVersionRetentionPeriod()) .setEarliestVersionTime(Timestamp.fromProto(proto.getEarliestVersionTime())) .setEncryptionConfig(CustomerManagedEncryption.fromProtoOrNull(proto.getEncryptionConfig())) + .setDefaultLeader(proto.getDefaultLeader()) .setProto(proto) .build(); } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java index 101fdd4e64..233f40a94f 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java @@ -24,6 +24,7 @@ /** Represents a Cloud Spanner database. */ public class DatabaseInfo { + public abstract static class Builder { abstract Builder setState(State state); @@ -44,6 +45,11 @@ public abstract static class Builder { */ public abstract Builder setEncryptionConfig(CustomerManagedEncryption encryptionConfig); + /** The read-write region which will be used for the database's leader replicas. */ + public Builder setDefaultLeader(String defaultLeader) { + throw new UnsupportedOperationException("Unimplemented"); + } + abstract Builder setProto(com.google.spanner.admin.database.v1.Database proto); /** Builds the database from this builder. */ @@ -58,6 +64,7 @@ abstract static class BuilderImpl extends Builder { private String versionRetentionPeriod; private Timestamp earliestVersionTime; private CustomerManagedEncryption encryptionConfig; + private String defaultLeader; private com.google.spanner.admin.database.v1.Database proto; BuilderImpl(DatabaseId id) { @@ -72,6 +79,7 @@ abstract static class BuilderImpl extends Builder { this.versionRetentionPeriod = other.versionRetentionPeriod; this.earliestVersionTime = other.earliestVersionTime; this.encryptionConfig = other.encryptionConfig; + this.defaultLeader = other.defaultLeader; this.proto = other.proto; } @@ -111,6 +119,12 @@ public Builder setEncryptionConfig(@Nullable CustomerManagedEncryption encryptio return this; } + @Override + public Builder setDefaultLeader(String defaultLeader) { + this.defaultLeader = defaultLeader; + return this; + } + @Override Builder setProto(@Nullable com.google.spanner.admin.database.v1.Database proto) { this.proto = proto; @@ -137,6 +151,7 @@ public enum State { private final String versionRetentionPeriod; private final Timestamp earliestVersionTime; private final CustomerManagedEncryption encryptionConfig; + private final String defaultLeader; private final com.google.spanner.admin.database.v1.Database proto; public DatabaseInfo(DatabaseId id, State state) { @@ -147,6 +162,7 @@ public DatabaseInfo(DatabaseId id, State state) { this.versionRetentionPeriod = null; this.earliestVersionTime = null; this.encryptionConfig = null; + this.defaultLeader = null; this.proto = null; } @@ -158,6 +174,7 @@ public DatabaseInfo(DatabaseId id, State state) { this.versionRetentionPeriod = builder.versionRetentionPeriod; this.earliestVersionTime = builder.earliestVersionTime; this.encryptionConfig = builder.encryptionConfig; + this.defaultLeader = builder.defaultLeader; this.proto = builder.proto; } @@ -209,6 +226,15 @@ public Timestamp getEarliestVersionTime() { return encryptionConfig; } + /** + * The read-write region which contains the database's leader replicas. If this value was not + * explicitly set during a create database or update database ddl operations, it will be {@code + * NULL}. + */ + public @Nullable String getDefaultLeader() { + return defaultLeader; + } + /** Returns the raw proto instance that was used to construct this {@link Database}. */ public @Nullable com.google.spanner.admin.database.v1.Database getProto() { return proto; @@ -229,7 +255,8 @@ public boolean equals(Object o) { && Objects.equals(restoreInfo, that.restoreInfo) && Objects.equals(versionRetentionPeriod, that.versionRetentionPeriod) && Objects.equals(earliestVersionTime, that.earliestVersionTime) - && Objects.equals(encryptionConfig, that.encryptionConfig); + && Objects.equals(encryptionConfig, that.encryptionConfig) + && Objects.equals(defaultLeader, that.defaultLeader); } @Override @@ -241,19 +268,21 @@ public int hashCode() { restoreInfo, versionRetentionPeriod, earliestVersionTime, - encryptionConfig); + encryptionConfig, + defaultLeader); } @Override public String toString() { return String.format( - "Database[%s, %s, %s, %s, %s, %s, %s]", + "Database[%s, %s, %s, %s, %s, %s, %s, %s]", id.getName(), state, createTime, restoreInfo, versionRetentionPeriod, earliestVersionTime, - encryptionConfig); + encryptionConfig, + defaultLeader); } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java index e29b457cc9..d1e8d2e31f 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java @@ -16,6 +16,7 @@ package com.google.cloud.spanner; +import static com.google.cloud.spanner.DatabaseInfo.State.CREATING; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.verify; @@ -61,6 +62,7 @@ public class DatabaseTest { .setEncryptionStatus(Status.newBuilder().setCode(Code.OK.getNumber())) .setKmsKeyVersion(KMS_KEY_VERSION) .build()); + private static final String DEFAULT_LEADER = "default-leader"; @Mock DatabaseAdminClient dbClient; @@ -100,27 +102,13 @@ public void listDatabaseOperations() { @Test public void fromProto() { Database db = createDatabase(); - assertThat(db.getId().getName()).isEqualTo(NAME); - assertThat(db.getState()).isEqualTo(DatabaseInfo.State.CREATING); - assertThat(db.getVersionRetentionPeriod()).isEqualTo(VERSION_RETENTION_PERIOD); - assertThat(db.getEarliestVersionTime()).isEqualTo(EARLIEST_VERSION_TIME); - assertThat(db.getEncryptionConfig()) - .isEqualTo(EncryptionConfigs.customerManagedEncryption(KMS_KEY_NAME)); - } - - @Test - public void testFromProtoWithEncryptionConfig() { - com.google.spanner.admin.database.v1.Database proto = - com.google.spanner.admin.database.v1.Database.newBuilder() - .setName(NAME) - .setEncryptionConfig( - com.google.spanner.admin.database.v1.EncryptionConfig.newBuilder() - .setKmsKeyName("some-key") - .build()) - .build(); - Database db = Database.fromProto(proto, dbClient); - assertThat(db.getEncryptionConfig()).isNotNull(); - assertThat(db.getEncryptionConfig().getKmsKeyName()).isEqualTo("some-key"); + assertEquals(NAME, db.getId().getName()); + assertEquals(CREATING, db.getState()); + assertEquals(VERSION_RETENTION_PERIOD, db.getVersionRetentionPeriod()); + assertEquals(EARLIEST_VERSION_TIME, db.getEarliestVersionTime()); + assertEquals( + EncryptionConfigs.customerManagedEncryption(KMS_KEY_NAME), db.getEncryptionConfig()); + assertEquals(DEFAULT_LEADER, db.getDefaultLeader()); } @Test @@ -138,6 +126,17 @@ public void testBuildWithEncryptionConfig() { "projects/my-project/locations/some-location/keyRings/my-keyring/cryptoKeys/my-key"); } + @Test + public void testBuildWithDefaultLeader() { + Database db = + dbClient + .newDatabaseBuilder(DatabaseId.of("my-project", "my-instance", "my-database")) + .setDefaultLeader(DEFAULT_LEADER) + .build(); + + assertEquals(DEFAULT_LEADER, db.getDefaultLeader()); + } + @Test public void getIAMPolicy() { Database database = @@ -186,6 +185,7 @@ private Database createDatabase() { .setVersionRetentionPeriod(VERSION_RETENTION_PERIOD) .setEncryptionConfig(ENCRYPTION_CONFIG) .addAllEncryptionInfo(ENCRYPTION_INFOS) + .setDefaultLeader(DEFAULT_LEADER) .build(); return Database.fromProto(proto, dbClient); } From 255e66cc8e8e60150722a58a9f579c0362f83ae0 Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Tue, 6 Jul 2021 09:04:01 +1000 Subject: [PATCH 03/12] test: add it test for instance leader options --- .../java/com/google/cloud/spanner/it/ITInstanceAdminTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITInstanceAdminTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITInstanceAdminTest.java index 340f0a5958..8d01ea1cb4 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITInstanceAdminTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITInstanceAdminTest.java @@ -57,9 +57,11 @@ public void instanceConfigOperations() { List configs = new ArrayList<>(); Iterators.addAll(configs, instanceClient.listInstanceConfigs().iterateAll().iterator()); assertThat(configs.isEmpty()).isFalse(); + configs.forEach(config -> assertThat(config.getLeaderOptions()).isNotEmpty()); InstanceConfig config = instanceClient.getInstanceConfig(configs.get(0).getId().getInstanceConfig()); assertThat(config.getId()).isEqualTo(configs.get(0).getId()); + assertThat(config.getLeaderOptions()).isNotEmpty(); config = config.reload(); assertThat(config.getId()).isEqualTo(configs.get(0).getId()); } From 0f56320b3852fe010e4112a12fec02b8c6c1d180 Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Tue, 6 Jul 2021 10:06:27 +1000 Subject: [PATCH 04/12] test: add it test for database default leader --- .../spanner/it/ITDatabaseLeaderTest.java | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseLeaderTest.java diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseLeaderTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseLeaderTest.java new file mode 100644 index 0000000000..488896bd07 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseLeaderTest.java @@ -0,0 +1,127 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.it; + +import static org.junit.Assert.assertEquals; + +import com.google.api.gax.paging.Page; +import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.InstanceAdminClient; +import com.google.cloud.spanner.InstanceConfig; +import com.google.cloud.spanner.IntegrationTestEnv; +import com.google.cloud.spanner.ParallelIntegrationTest; +import java.time.Duration; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@Category(ParallelIntegrationTest.class) +@RunWith(JUnit4.class) +public class ITDatabaseLeaderTest { + + public static final Duration WAIT_TIME = Duration.ofMinutes(5); + @ClassRule public static IntegrationTestEnv env = new IntegrationTestEnv(); + + private InstanceAdminClient instanceAdminClient; + private DatabaseAdminClient databaseAdminClient; + private String instanceId; + private String databaseId; + + @Before + public void setUp() { + instanceId = env.getTestHelper().getInstanceId().getInstance(); + databaseId = env.getTestHelper().getUniqueDatabaseId(); + instanceAdminClient = env.getTestHelper().getClient().getInstanceAdminClient(); + databaseAdminClient = env.getTestHelper().getClient().getDatabaseAdminClient(); + } + + @After + public void tearDown() { + databaseAdminClient.dropDatabase(instanceId, databaseId); + } + + @Test + public void testCreateDatabaseWithDefaultLeader() + throws InterruptedException, ExecutionException, TimeoutException { + final Page instanceConfigs = instanceAdminClient.listInstanceConfigs(); + final InstanceConfig instanceConfig = instanceConfigs.iterateAll().iterator().next(); + final List leaderOptions = instanceConfig.getLeaderOptions(); + final String leader = leaderOptions.get(0); + + final Database createdDatabase = + databaseAdminClient + .createDatabase( + instanceId, + databaseId, + Collections.singletonList( + "ALTER DATABASE `" + + databaseId + + "` SET OPTIONS ( default_leader = '" + + leader + + "' )")) + .get(WAIT_TIME.toMillis(), TimeUnit.MILLISECONDS); + assertEquals(leader, createdDatabase.getDefaultLeader()); + + final Database retrievedDatabase = databaseAdminClient.getDatabase(instanceId, databaseId); + assertEquals(leader, retrievedDatabase.getDefaultLeader()); + + final Database listedDatabase = listDatabasesWithId(databaseId).get(0); + assertEquals(leader, listedDatabase.getDefaultLeader()); + } + + @Test + public void testCreateDatabaseWithoutDefaultLeader() + throws InterruptedException, ExecutionException, TimeoutException { + final Database createdDatabase = + databaseAdminClient + .createDatabase(instanceId, databaseId, Collections.emptyList()) + .get(WAIT_TIME.toMillis(), TimeUnit.MILLISECONDS); + assertEquals("", createdDatabase.getDefaultLeader()); + + final Database retrievedDatabase = databaseAdminClient.getDatabase(instanceId, databaseId); + assertEquals("", retrievedDatabase.getDefaultLeader()); + + final Database listedDatabase = listDatabasesWithId(databaseId).get(0); + assertEquals("", listedDatabase.getDefaultLeader()); + } + + private List listDatabasesWithId(String databaseId) { + Page page = databaseAdminClient.listDatabases(instanceId); + Stream stream = StreamSupport.stream(page.getValues().spliterator(), false); + while (page.hasNextPage()) { + page = page.getNextPage(); + stream = Stream.concat(stream, StreamSupport.stream(page.getValues().spliterator(), false)); + } + + return stream + .filter(database -> database.getId().getDatabase().equals(databaseId)) + .collect(Collectors.toList()); + } +} From af76ad52175c98fb04bfdb96240ecf14f469134d Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Tue, 6 Jul 2021 11:11:23 +1000 Subject: [PATCH 05/12] feat: create replica info / type model --- .../com/google/cloud/spanner/ReplicaInfo.java | 190 ++++++++++++++++++ .../google/cloud/spanner/ReplicaInfoTest.java | 57 ++++++ .../google/cloud/spanner/ReplicaTypeTest.java | 66 ++++++ 3 files changed, 313 insertions(+) create mode 100644 google-cloud-spanner/src/main/java/com/google/cloud/spanner/ReplicaInfo.java create mode 100644 google-cloud-spanner/src/test/java/com/google/cloud/spanner/ReplicaInfoTest.java create mode 100644 google-cloud-spanner/src/test/java/com/google/cloud/spanner/ReplicaTypeTest.java diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ReplicaInfo.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ReplicaInfo.java new file mode 100644 index 0000000000..1ccb049134 --- /dev/null +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ReplicaInfo.java @@ -0,0 +1,190 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner; + +import com.google.common.annotations.VisibleForTesting; +import java.util.Objects; + +public class ReplicaInfo { + + abstract static class Builder { + abstract Builder setLocation(String location); + + abstract Builder setType(ReplicaType type); + + abstract Builder setDefaultLeaderLocation(boolean defaultLeaderLocation); + + abstract Builder setProto(com.google.spanner.admin.instance.v1.ReplicaInfo proto); + + public abstract ReplicaInfo build(); + } + + public static class BuilderImpl extends Builder { + + private String location; + private ReplicaType type; + private boolean defaultLeaderLocation; + private com.google.spanner.admin.instance.v1.ReplicaInfo proto; + + /** The location of the serving resources. */ + @Override + Builder setLocation(String location) { + this.location = location; + return this; + } + + /** The type of the replica, as per {@link ReplicaType}. */ + @Override + Builder setType(ReplicaType type) { + this.type = type; + return this; + } + + /** + * If true, this location is designated as the default leader location where leader replicas are + * placed. + */ + @Override + Builder setDefaultLeaderLocation(boolean defaultLeaderLocation) { + this.defaultLeaderLocation = defaultLeaderLocation; + return this; + } + + @Override + Builder setProto(com.google.spanner.admin.instance.v1.ReplicaInfo proto) { + this.proto = proto; + return this; + } + + @Override + public ReplicaInfo build() { + return new ReplicaInfo(this); + } + } + + public static ReplicaInfo fromProto(com.google.spanner.admin.instance.v1.ReplicaInfo proto) { + return newBuilder() + .setLocation(proto.getLocation()) + .setType(ReplicaType.fromProto(proto.getType())) + .setDefaultLeaderLocation(proto.getDefaultLeaderLocation()) + .setProto(proto) + .build(); + } + + static Builder newBuilder() { + return new BuilderImpl(); + } + + private final String location; + private final ReplicaType type; + private final boolean defaultLeaderLocation; + private final com.google.spanner.admin.instance.v1.ReplicaInfo proto; + + @VisibleForTesting + ReplicaInfo( + String location, + ReplicaType type, + boolean defaultLeaderLocation, + com.google.spanner.admin.instance.v1.ReplicaInfo proto) { + this.location = location; + this.type = type; + this.defaultLeaderLocation = defaultLeaderLocation; + this.proto = proto; + } + + ReplicaInfo(BuilderImpl builder) { + this.location = builder.location; + this.type = builder.type; + this.defaultLeaderLocation = builder.defaultLeaderLocation; + this.proto = builder.proto; + } + + public String getLocation() { + return location; + } + + public ReplicaType getType() { + return type; + } + + public boolean isDefaultLeaderLocation() { + return defaultLeaderLocation; + } + + public com.google.spanner.admin.instance.v1.ReplicaInfo getProto() { + return proto; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ReplicaInfo)) { + return false; + } + ReplicaInfo that = (ReplicaInfo) o; + return defaultLeaderLocation == that.defaultLeaderLocation + && Objects.equals(location, that.location) + && type == that.type; + } + + @Override + public int hashCode() { + return Objects.hash(location, type, defaultLeaderLocation); + } + + @Override + public String toString() { + return "ReplicaInfo{" + + "location='" + + location + + '\'' + + ", type=" + + type + + ", defaultLeaderLocation=" + + defaultLeaderLocation + + '}'; + } + + /** + * Indicates the type of the replica. See the replica types documentation at + * https://cloud.google.com/spanner/docs/replication#replica_types for more details. + */ + public enum ReplicaType { + TYPE_UNSPECIFIED, + READ_WRITE, + READ_ONLY, + WITNESS; + + public static ReplicaType fromProto( + com.google.spanner.admin.instance.v1.ReplicaInfo.ReplicaType proto) { + switch (proto) { + case TYPE_UNSPECIFIED: + return ReplicaType.TYPE_UNSPECIFIED; + case READ_WRITE: + return ReplicaType.READ_WRITE; + case READ_ONLY: + return ReplicaType.READ_ONLY; + case WITNESS: + return ReplicaType.WITNESS; + default: + throw new IllegalArgumentException("Unrecognized replica type " + proto); + } + } + } +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ReplicaInfoTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ReplicaInfoTest.java new file mode 100644 index 0000000000..96005791f3 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ReplicaInfoTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner; + +import static com.google.cloud.spanner.ReplicaInfo.ReplicaType.READ_WRITE; +import static org.junit.Assert.assertEquals; + +import com.google.cloud.spanner.ReplicaInfo.ReplicaType; +import org.junit.Test; + +public class ReplicaInfoTest { + + @Test + public void testBuildReplicaInfo() { + final String location = "Location"; + final ReplicaType type = READ_WRITE; + final boolean defaultLeaderLocation = true; + final com.google.spanner.admin.instance.v1.ReplicaInfo proto = + com.google.spanner.admin.instance.v1.ReplicaInfo.newBuilder().build(); + + assertEquals( + new ReplicaInfo(location, type, defaultLeaderLocation, proto), + ReplicaInfo.newBuilder() + .setLocation(location) + .setType(type) + .setDefaultLeaderLocation(defaultLeaderLocation) + .setProto(proto) + .build()); + } + + @Test + public void testFromProto() { + final com.google.spanner.admin.instance.v1.ReplicaInfo proto = + com.google.spanner.admin.instance.v1.ReplicaInfo.newBuilder() + .setLocation("Location") + .setType(com.google.spanner.admin.instance.v1.ReplicaInfo.ReplicaType.READ_WRITE) + .setDefaultLeaderLocation(true) + .build(); + + assertEquals( + new ReplicaInfo("Location", READ_WRITE, true, proto), ReplicaInfo.fromProto(proto)); + } +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ReplicaTypeTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ReplicaTypeTest.java new file mode 100644 index 0000000000..065a94ba22 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ReplicaTypeTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner; + +import static org.junit.Assert.assertEquals; + +import com.google.cloud.spanner.ReplicaInfo.ReplicaType; +import org.junit.Test; + +public class ReplicaTypeTest { + + @Test + public void testTypeUnspecifiedReplicaType() { + final ReplicaType replicaType = + ReplicaType.fromProto( + com.google.spanner.admin.instance.v1.ReplicaInfo.ReplicaType.TYPE_UNSPECIFIED); + + assertEquals(ReplicaType.TYPE_UNSPECIFIED, replicaType); + } + + @Test + public void testReadWriteReplicaType() { + final ReplicaType replicaType = + ReplicaType.fromProto( + com.google.spanner.admin.instance.v1.ReplicaInfo.ReplicaType.READ_WRITE); + + assertEquals(ReplicaType.READ_WRITE, replicaType); + } + + @Test + public void testReadOnlyReplicaType() { + final ReplicaType replicaType = + ReplicaType.fromProto( + com.google.spanner.admin.instance.v1.ReplicaInfo.ReplicaType.READ_ONLY); + + assertEquals(ReplicaType.READ_ONLY, replicaType); + } + + @Test + public void testWitnessReplicaType() { + final ReplicaType replicaType = + ReplicaType.fromProto(com.google.spanner.admin.instance.v1.ReplicaInfo.ReplicaType.WITNESS); + + assertEquals(ReplicaType.WITNESS, replicaType); + } + + @Test(expected = IllegalArgumentException.class) + public void testUnrecognizedReplicaType() { + ReplicaType.fromProto( + com.google.spanner.admin.instance.v1.ReplicaInfo.ReplicaType.UNRECOGNIZED); + } +} From 11d7ee8d5343eef15c46d6816ad49b0fa0aa2df7 Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Tue, 6 Jul 2021 11:22:38 +1000 Subject: [PATCH 06/12] feat: expose replicas in instance config --- .../google/cloud/spanner/InstanceConfig.java | 7 ++- .../cloud/spanner/InstanceConfigInfo.java | 28 ++++++++--- .../com/google/cloud/spanner/ReplicaInfo.java | 1 + .../cloud/spanner/InstanceConfigTest.java | 50 ++++++++++++++++--- 4 files changed, 69 insertions(+), 17 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceConfig.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceConfig.java index 9cdc529042..6afbc028e1 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceConfig.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceConfig.java @@ -18,6 +18,7 @@ import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; /** * Represents a Cloud Spanner instance config.{@code InstanceConfig} adds a layer of service related @@ -28,15 +29,16 @@ public class InstanceConfig extends InstanceConfigInfo { private final InstanceAdminClient client; public InstanceConfig(InstanceConfigId id, String displayName, InstanceAdminClient client) { - this(id, displayName, Collections.emptyList(), client); + this(id, displayName, Collections.emptyList(), Collections.emptyList(), client); } public InstanceConfig( InstanceConfigId id, String displayName, + List replicas, List leaderOptions, InstanceAdminClient client) { - super(id, displayName, leaderOptions); + super(id, displayName, replicas, leaderOptions); this.client = client; } @@ -50,6 +52,7 @@ static InstanceConfig fromProto( return new InstanceConfig( InstanceConfigId.of(proto.getName()), proto.getDisplayName(), + proto.getReplicasList().stream().map(ReplicaInfo::fromProto).collect(Collectors.toList()), proto.getLeaderOptionsList(), client); } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceConfigInfo.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceConfigInfo.java index 587022796a..43d4fbf953 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceConfigInfo.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceConfigInfo.java @@ -25,21 +25,25 @@ public class InstanceConfigInfo { private final InstanceConfigId id; private final String displayName; + private final List replicas; private final List leaderOptions; public InstanceConfigInfo(InstanceConfigId id, String displayName) { - this(id, displayName, Collections.emptyList()); + this(id, displayName, Collections.emptyList(), Collections.emptyList()); } - public InstanceConfigInfo(InstanceConfigId id, String displayName, List leaderOptions) { + public InstanceConfigInfo( + InstanceConfigId id, + String displayName, + List replicas, + List leaderOptions) { this.id = id; this.displayName = displayName; + this.replicas = replicas; this.leaderOptions = leaderOptions; } - /* - * Returns the id of this instance config. - */ + /** Returns the id of this instance config. */ public InstanceConfigId getId() { return id; } @@ -49,6 +53,14 @@ public String getDisplayName() { return displayName; } + /** + * The geographic placement of nodes in this instance configuration and their replication + * properties. + */ + public List getReplicas() { + return replicas; + } + /** * Allowed values of the default leader schema option for databases in instances that use this * instance configuration. @@ -68,16 +80,18 @@ public boolean equals(Object o) { InstanceConfigInfo that = (InstanceConfigInfo) o; return Objects.equals(id, that.id) && Objects.equals(displayName, that.displayName) + && Objects.equals(replicas, that.replicas) && Objects.equals(leaderOptions, that.leaderOptions); } @Override public int hashCode() { - return Objects.hash(id, displayName, leaderOptions); + return Objects.hash(id, displayName, replicas, leaderOptions); } @Override public String toString() { - return String.format("Instance Config[%s, %s, %s]", id, displayName, leaderOptions); + return String.format( + "Instance Config[%s, %s, %s, %s]", id, displayName, replicas, leaderOptions); } } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ReplicaInfo.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ReplicaInfo.java index 1ccb049134..e8469b9772 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ReplicaInfo.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ReplicaInfo.java @@ -19,6 +19,7 @@ import com.google.common.annotations.VisibleForTesting; import java.util.Objects; +/** Represents a Cloud Spanner replica information. */ public class ReplicaInfo { abstract static class Builder { diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InstanceConfigTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InstanceConfigTest.java index 2d11f85582..3b0f56c088 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InstanceConfigTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InstanceConfigTest.java @@ -19,6 +19,7 @@ import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; +import com.google.spanner.admin.instance.v1.ReplicaInfo.ReplicaType; import java.util.Arrays; import org.junit.Before; import org.junit.Test; @@ -33,38 +34,71 @@ public void setUp() { } @Test - public void testInstanceConfigFromProtoWithoutLeaderOptions() { + public void testInstanceConfigFromProto() { final InstanceConfig instanceConfig = InstanceConfig.fromProto( com.google.spanner.admin.instance.v1.InstanceConfig.newBuilder() .setDisplayName("Display Name") .setName("projects/my-project/instanceConfigs/my-instance-config") + .addAllLeaderOptions(Arrays.asList("Leader Option 1", "Leader Option 2")) + .addAllReplicas( + Arrays.asList( + com.google.spanner.admin.instance.v1.ReplicaInfo.newBuilder() + .setLocation("Replica Location 1") + .setType(ReplicaType.READ_WRITE) + .setDefaultLeaderLocation(true) + .build(), + com.google.spanner.admin.instance.v1.ReplicaInfo.newBuilder() + .setLocation("Replica Location 2") + .setType(ReplicaType.READ_ONLY) + .setDefaultLeaderLocation(false) + .build(), + com.google.spanner.admin.instance.v1.ReplicaInfo.newBuilder() + .setLocation("Replica Location 3") + .setType(ReplicaType.WITNESS) + .setDefaultLeaderLocation(false) + .build())) .build(), client); assertEquals( new InstanceConfig( - InstanceConfigId.of("my-project", "my-instance-config"), "Display Name", client), + InstanceConfigId.of("my-project", "my-instance-config"), + "Display Name", + Arrays.asList( + ReplicaInfo.newBuilder() + .setLocation("Replica Location 1") + .setType(ReplicaInfo.ReplicaType.READ_WRITE) + .setDefaultLeaderLocation(true) + .build(), + ReplicaInfo.newBuilder() + .setLocation("Replica Location 2") + .setType(ReplicaInfo.ReplicaType.READ_ONLY) + .setDefaultLeaderLocation(false) + .build(), + ReplicaInfo.newBuilder() + .setLocation("Replica Location 3") + .setType(ReplicaInfo.ReplicaType.WITNESS) + .setDefaultLeaderLocation(false) + .build()), + Arrays.asList("Leader Option 1", "Leader Option 2"), + client), instanceConfig); } @Test - public void testInstanceConfigFromProtoWithLeaderOptions() { + public void testInstanceConfigFromProtoWithoutReplicasAndLeaderOptions() { final InstanceConfig instanceConfig = InstanceConfig.fromProto( com.google.spanner.admin.instance.v1.InstanceConfig.newBuilder() .setDisplayName("Display Name") .setName("projects/my-project/instanceConfigs/my-instance-config") - .addAllLeaderOptions(Arrays.asList("Leader Option 1", "Leader Option 2")) .build(), client); assertEquals( new InstanceConfig( - InstanceConfigId.of("my-project", "my-instance-config"), - "Display Name", - Arrays.asList("Leader Option 1", "Leader Option 2"), - client), + InstanceConfigId.of("my-project", "my-instance-config"), "Display Name", client), instanceConfig); } } From ad0edf09a3bee2b75b61304551beee2842f57cc7 Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Tue, 6 Jul 2021 11:29:31 +1000 Subject: [PATCH 07/12] test: add it test for replicas in instance config --- .../java/com/google/cloud/spanner/it/ITInstanceAdminTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITInstanceAdminTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITInstanceAdminTest.java index 8d01ea1cb4..8aed812c9c 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITInstanceAdminTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITInstanceAdminTest.java @@ -58,10 +58,12 @@ public void instanceConfigOperations() { Iterators.addAll(configs, instanceClient.listInstanceConfigs().iterateAll().iterator()); assertThat(configs.isEmpty()).isFalse(); configs.forEach(config -> assertThat(config.getLeaderOptions()).isNotEmpty()); + configs.forEach(config -> assertThat(config.getReplicas()).isNotEmpty()); InstanceConfig config = instanceClient.getInstanceConfig(configs.get(0).getId().getInstanceConfig()); assertThat(config.getId()).isEqualTo(configs.get(0).getId()); assertThat(config.getLeaderOptions()).isNotEmpty(); + assertThat(config.getReplicas()).isNotEmpty(); config = config.reload(); assertThat(config.getId()).isEqualTo(configs.get(0).getId()); } From a0ea4e27f6bec26dddf031979cbacb6f386dc9ac Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Thu, 15 Jul 2021 10:59:44 +1000 Subject: [PATCH 08/12] test: skip leader tests for emulator --- .../com/google/cloud/spanner/it/ITDatabaseLeaderTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseLeaderTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseLeaderTest.java index 488896bd07..9169f41dbf 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseLeaderTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseLeaderTest.java @@ -16,7 +16,9 @@ package com.google.cloud.spanner.it; +import static com.google.cloud.spanner.testing.EmulatorSpannerHelper.isUsingEmulator; import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeFalse; import com.google.api.gax.paging.Page; import com.google.cloud.spanner.Database; @@ -36,6 +38,7 @@ import java.util.stream.StreamSupport; import org.junit.After; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -54,6 +57,11 @@ public class ITDatabaseLeaderTest { private String instanceId; private String databaseId; + @BeforeClass + public static void beforeClass() { + assumeFalse("leader options are not supported in the emulator", isUsingEmulator()); + } + @Before public void setUp() { instanceId = env.getTestHelper().getInstanceId().getInstance(); From ce14795b16f9505bdf9123da4444c17a1ca74384 Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Thu, 15 Jul 2021 11:14:17 +1000 Subject: [PATCH 09/12] test: skip emulator for instance admin test Skips leader options / replicas checking related tests for the emulator. --- .../cloud/spanner/it/ITDatabaseLeaderTest.java | 2 +- .../cloud/spanner/it/ITInstanceAdminTest.java | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseLeaderTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseLeaderTest.java index 9169f41dbf..757e5245d1 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseLeaderTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseLeaderTest.java @@ -59,7 +59,7 @@ public class ITDatabaseLeaderTest { @BeforeClass public static void beforeClass() { - assumeFalse("leader options are not supported in the emulator", isUsingEmulator()); + assumeFalse("The emulator does not support leader options", isUsingEmulator()); } @Before diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITInstanceAdminTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITInstanceAdminTest.java index 8aed812c9c..75e6663b98 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITInstanceAdminTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITInstanceAdminTest.java @@ -57,17 +57,23 @@ public void instanceConfigOperations() { List configs = new ArrayList<>(); Iterators.addAll(configs, instanceClient.listInstanceConfigs().iterateAll().iterator()); assertThat(configs.isEmpty()).isFalse(); - configs.forEach(config -> assertThat(config.getLeaderOptions()).isNotEmpty()); - configs.forEach(config -> assertThat(config.getReplicas()).isNotEmpty()); InstanceConfig config = instanceClient.getInstanceConfig(configs.get(0).getId().getInstanceConfig()); assertThat(config.getId()).isEqualTo(configs.get(0).getId()); - assertThat(config.getLeaderOptions()).isNotEmpty(); - assertThat(config.getReplicas()).isNotEmpty(); config = config.reload(); assertThat(config.getId()).isEqualTo(configs.get(0).getId()); } + @Test + public void instanceConfigLeaderOptions() { + assumeFalse("The emulator does not support leader options", isUsingEmulator()); + List configs = new ArrayList<>(); + Iterators.addAll(configs, instanceClient.listInstanceConfigs().iterateAll().iterator()); + + configs.forEach(config -> assertThat(config.getReplicas()).isNotEmpty()); + configs.forEach(config -> assertThat(config.getLeaderOptions()).isNotEmpty()); + } + @Test public void listInstances() { Instance instance = From 78f7a214e9e502e4597fce39253e5c39a274ae12 Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Fri, 16 Jul 2021 13:21:58 +1000 Subject: [PATCH 10/12] docs: add link for leader config accepted values --- .../main/java/com/google/cloud/spanner/DatabaseInfo.java | 6 +++++- .../src/main/java/com/google/cloud/spanner/ReplicaInfo.java | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java index 233f40a94f..b58769bcb5 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java @@ -45,7 +45,11 @@ public abstract static class Builder { */ public abstract Builder setEncryptionConfig(CustomerManagedEncryption encryptionConfig); - /** The read-write region which will be used for the database's leader replicas. */ + /** + * The read-write region which will be used for the database's leader replicas. This can be one + * of the values as specified in + * https://cloud.google.com/spanner/docs/instances#available-configurations-multi-region. + */ public Builder setDefaultLeader(String defaultLeader) { throw new UnsupportedOperationException("Unimplemented"); } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ReplicaInfo.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ReplicaInfo.java index e8469b9772..bd40af5a52 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ReplicaInfo.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ReplicaInfo.java @@ -41,7 +41,10 @@ public static class BuilderImpl extends Builder { private boolean defaultLeaderLocation; private com.google.spanner.admin.instance.v1.ReplicaInfo proto; - /** The location of the serving resources. */ + /** + * The location of the serving resources. This can be one of the values as specified in + * https://cloud.google.com/spanner/docs/instances#available-configurations-multi-region. + */ @Override Builder setLocation(String location) { this.location = location; From 739dcf75f1f3647356705d8bfec3614dc5690502 Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Fri, 16 Jul 2021 13:24:24 +1000 Subject: [PATCH 11/12] docs: fix the link for replica infos. --- .../src/main/java/com/google/cloud/spanner/ReplicaInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ReplicaInfo.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ReplicaInfo.java index bd40af5a52..4d3887c0ad 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ReplicaInfo.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ReplicaInfo.java @@ -43,7 +43,7 @@ public static class BuilderImpl extends Builder { /** * The location of the serving resources. This can be one of the values as specified in - * https://cloud.google.com/spanner/docs/instances#available-configurations-multi-region. + * https://cloud.google.com/spanner/docs/instances#available-configurations-regional. */ @Override Builder setLocation(String location) { From 8a7fe0fa711fdb5996856222f8817e9e42a49230 Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Fri, 16 Jul 2021 16:30:37 +1000 Subject: [PATCH 12/12] test: skip / removes leader tests We decided not to have setting the default leader tests, because it requires a multi-regional instance. The one we currently have for running the tests is regional, so the cost of setup would not justify the testing. We also skip the get leader options test, since the feature is not enabled in production yet. --- .../spanner/it/ITDatabaseLeaderTest.java | 135 ------------------ .../cloud/spanner/it/ITInstanceAdminTest.java | 2 + 2 files changed, 2 insertions(+), 135 deletions(-) delete mode 100644 google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseLeaderTest.java diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseLeaderTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseLeaderTest.java deleted file mode 100644 index 757e5245d1..0000000000 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseLeaderTest.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.cloud.spanner.it; - -import static com.google.cloud.spanner.testing.EmulatorSpannerHelper.isUsingEmulator; -import static org.junit.Assert.assertEquals; -import static org.junit.Assume.assumeFalse; - -import com.google.api.gax.paging.Page; -import com.google.cloud.spanner.Database; -import com.google.cloud.spanner.DatabaseAdminClient; -import com.google.cloud.spanner.InstanceAdminClient; -import com.google.cloud.spanner.InstanceConfig; -import com.google.cloud.spanner.IntegrationTestEnv; -import com.google.cloud.spanner.ParallelIntegrationTest; -import java.time.Duration; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.experimental.categories.Category; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@Category(ParallelIntegrationTest.class) -@RunWith(JUnit4.class) -public class ITDatabaseLeaderTest { - - public static final Duration WAIT_TIME = Duration.ofMinutes(5); - @ClassRule public static IntegrationTestEnv env = new IntegrationTestEnv(); - - private InstanceAdminClient instanceAdminClient; - private DatabaseAdminClient databaseAdminClient; - private String instanceId; - private String databaseId; - - @BeforeClass - public static void beforeClass() { - assumeFalse("The emulator does not support leader options", isUsingEmulator()); - } - - @Before - public void setUp() { - instanceId = env.getTestHelper().getInstanceId().getInstance(); - databaseId = env.getTestHelper().getUniqueDatabaseId(); - instanceAdminClient = env.getTestHelper().getClient().getInstanceAdminClient(); - databaseAdminClient = env.getTestHelper().getClient().getDatabaseAdminClient(); - } - - @After - public void tearDown() { - databaseAdminClient.dropDatabase(instanceId, databaseId); - } - - @Test - public void testCreateDatabaseWithDefaultLeader() - throws InterruptedException, ExecutionException, TimeoutException { - final Page instanceConfigs = instanceAdminClient.listInstanceConfigs(); - final InstanceConfig instanceConfig = instanceConfigs.iterateAll().iterator().next(); - final List leaderOptions = instanceConfig.getLeaderOptions(); - final String leader = leaderOptions.get(0); - - final Database createdDatabase = - databaseAdminClient - .createDatabase( - instanceId, - databaseId, - Collections.singletonList( - "ALTER DATABASE `" - + databaseId - + "` SET OPTIONS ( default_leader = '" - + leader - + "' )")) - .get(WAIT_TIME.toMillis(), TimeUnit.MILLISECONDS); - assertEquals(leader, createdDatabase.getDefaultLeader()); - - final Database retrievedDatabase = databaseAdminClient.getDatabase(instanceId, databaseId); - assertEquals(leader, retrievedDatabase.getDefaultLeader()); - - final Database listedDatabase = listDatabasesWithId(databaseId).get(0); - assertEquals(leader, listedDatabase.getDefaultLeader()); - } - - @Test - public void testCreateDatabaseWithoutDefaultLeader() - throws InterruptedException, ExecutionException, TimeoutException { - final Database createdDatabase = - databaseAdminClient - .createDatabase(instanceId, databaseId, Collections.emptyList()) - .get(WAIT_TIME.toMillis(), TimeUnit.MILLISECONDS); - assertEquals("", createdDatabase.getDefaultLeader()); - - final Database retrievedDatabase = databaseAdminClient.getDatabase(instanceId, databaseId); - assertEquals("", retrievedDatabase.getDefaultLeader()); - - final Database listedDatabase = listDatabasesWithId(databaseId).get(0); - assertEquals("", listedDatabase.getDefaultLeader()); - } - - private List listDatabasesWithId(String databaseId) { - Page page = databaseAdminClient.listDatabases(instanceId); - Stream stream = StreamSupport.stream(page.getValues().spliterator(), false); - while (page.hasNextPage()) { - page = page.getNextPage(); - stream = Stream.concat(stream, StreamSupport.stream(page.getValues().spliterator(), false)); - } - - return stream - .filter(database -> database.getId().getDatabase().equals(databaseId)) - .collect(Collectors.toList()); - } -} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITInstanceAdminTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITInstanceAdminTest.java index 75e6663b98..fcda2603ca 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITInstanceAdminTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITInstanceAdminTest.java @@ -35,6 +35,7 @@ import java.util.Random; import org.junit.Before; import org.junit.ClassRule; +import org.junit.Ignore; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; @@ -64,6 +65,7 @@ public void instanceConfigOperations() { assertThat(config.getId()).isEqualTo(configs.get(0).getId()); } + @Ignore("Feature is not yet enabled in production") @Test public void instanceConfigLeaderOptions() { assumeFalse("The emulator does not support leader options", isUsingEmulator());