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..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 @@ -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,15 @@ 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. 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"); + } + abstract Builder setProto(com.google.spanner.admin.database.v1.Database proto); /** Builds the database from this builder. */ @@ -58,6 +68,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 +83,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 +123,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 +155,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 +166,7 @@ public DatabaseInfo(DatabaseId id, State state) { this.versionRetentionPeriod = null; this.earliestVersionTime = null; this.encryptionConfig = null; + this.defaultLeader = null; this.proto = null; } @@ -158,6 +178,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 +230,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 +259,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 +272,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/main/java/com/google/cloud/spanner/InstanceConfig.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceConfig.java index 1076fae9c3..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 @@ -16,6 +16,10 @@ package com.google.cloud.spanner; +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 * functionality over {@code InstanceConfigInfo}. @@ -25,7 +29,16 @@ 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(), Collections.emptyList(), client); + } + + public InstanceConfig( + InstanceConfigId id, + String displayName, + List replicas, + List leaderOptions, + InstanceAdminClient client) { + super(id, displayName, replicas, leaderOptions); this.client = client; } @@ -36,6 +49,11 @@ 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.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 7b391e4c5d..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 @@ -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,15 +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(), Collections.emptyList()); + } + + 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; } @@ -41,9 +53,20 @@ public String getDisplayName() { return displayName; } - @Override - public int hashCode() { - return Objects.hash(id, 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. + */ + public List getLeaderOptions() { + return leaderOptions; } @Override @@ -51,15 +74,24 @@ 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(replicas, that.replicas) + && Objects.equals(leaderOptions, that.leaderOptions); + } + + @Override + public int hashCode() { + return Objects.hash(id, displayName, replicas, leaderOptions); } @Override public String toString() { - return String.format("Instance Config[%s, %s]", id, displayName); + 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 new file mode 100644 index 0000000000..4d3887c0ad --- /dev/null +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/ReplicaInfo.java @@ -0,0 +1,194 @@ +/* + * 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; + +/** Represents a Cloud Spanner replica information. */ +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. This can be one of the values as specified in + * https://cloud.google.com/spanner/docs/instances#available-configurations-regional. + */ + @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/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); } 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..3b0f56c088 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/InstanceConfigTest.java @@ -0,0 +1,104 @@ +/* + * 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 com.google.spanner.admin.instance.v1.ReplicaInfo.ReplicaType; +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 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", + 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 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") + .build(), + client); + + assertEquals( + new InstanceConfig( + InstanceConfigId.of("my-project", "my-instance-config"), "Display Name", client), + instanceConfig); + } +} 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); + } +} 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..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,17 @@ 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()); + 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 =