diff --git a/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClient.java b/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClient.java index d0d690ad9fe2..9246533ed6ca 100644 --- a/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClient.java +++ b/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClient.java @@ -18,15 +18,23 @@ import com.google.api.core.ApiFunction; import com.google.api.core.ApiFuture; import com.google.api.core.ApiFutures; +import com.google.bigtable.admin.v2.ClusterName; +import com.google.bigtable.admin.v2.DeleteClusterRequest; import com.google.bigtable.admin.v2.DeleteInstanceRequest; +import com.google.bigtable.admin.v2.GetClusterRequest; import com.google.bigtable.admin.v2.GetInstanceRequest; import com.google.bigtable.admin.v2.InstanceName; +import com.google.bigtable.admin.v2.ListClustersRequest; +import com.google.bigtable.admin.v2.ListClustersResponse; import com.google.bigtable.admin.v2.ListInstancesRequest; import com.google.bigtable.admin.v2.ListInstancesResponse; import com.google.bigtable.admin.v2.LocationName; import com.google.bigtable.admin.v2.ProjectName; +import com.google.cloud.bigtable.admin.v2.models.Cluster; +import com.google.cloud.bigtable.admin.v2.models.CreateClusterRequest; import com.google.cloud.bigtable.admin.v2.models.CreateInstanceRequest; import com.google.cloud.bigtable.admin.v2.models.Instance; +import com.google.cloud.bigtable.admin.v2.models.PartialListClustersException; import com.google.cloud.bigtable.admin.v2.models.PartialListInstancesException; import com.google.cloud.bigtable.admin.v2.models.UpdateInstanceRequest; import com.google.cloud.bigtable.admin.v2.stub.BigtableInstanceAdminStub; @@ -397,6 +405,282 @@ public Void apply(Empty input) { ); } + /** + * Creates a new cluster in the specified instance. + * + *

Sample code: + * + *

{@code
+   * Cluster cluster = client.createCluster(
+   *   CreateClusterRequest.of("my-instance", "my-new-cluster")
+   *     .setZone("us-east1-c")
+   *     .setServeNodes(3)
+   *     .setStorageType(StorageType.SSD)
+   * );
+   * }
+ */ + @SuppressWarnings("WeakerAccess") + public Cluster createCluster(CreateClusterRequest request) { + return awaitFuture(createClusterAsync(request)); + } + + /** + * Asynchronously creates a new cluster in the specified instance. + * + *

Sample code: + * + *

{@code
+   * ApiFuture clusterFuture = client.createClusterAsync(
+   *   CreateClusterRequest.of("my-instance", "my-new-cluster")
+   *     .setZone("us-east1-c")
+   *     .setServeNodes(3)
+   *     .setStorageType(StorageType.SSD)
+   * );
+   *
+   * Cluster cluster = clusterFuture.get();
+   * }
+ */ + @SuppressWarnings("WeakerAccess") + public ApiFuture createClusterAsync(CreateClusterRequest request) { + return ApiFutures.transform( + stub.createClusterOperationCallable().futureCall(request.toProto(projectName)), + new ApiFunction() { + @Override + public Cluster apply(com.google.bigtable.admin.v2.Cluster proto) { + return Cluster.fromProto(proto); + } + }, + MoreExecutors.directExecutor() + ); + } + + /** + * Get the cluster representation by ID. + * + *

Sample code: + * + *

{@code
+   * Cluster cluster = client.getCluster("my-instance", "my-cluster");
+   * }
+ */ + @SuppressWarnings("WeakerAccess") + public Cluster getCluster(String instanceId, String clusterId) { + return awaitFuture(getClusterAsync(instanceId, clusterId)); + } + + /** + * Asynchronously gets the cluster representation by ID. + * + *

Sample code: + * + *

{@code
+   * ApiFuture clusterFuture = client.getClusterAsync("my-instance", "my-cluster");
+   * Cluster cluster = clusterFuture.get();
+   * }
+ */ + @SuppressWarnings("WeakerAccess") + public ApiFuture getClusterAsync(String instanceId, String clusterId) { + ClusterName name = ClusterName.of(projectName.getProject(), instanceId, clusterId); + + GetClusterRequest request = GetClusterRequest.newBuilder() + .setName(name.toString()) + .build(); + + return ApiFutures.transform( + stub.getClusterCallable().futureCall(request), + new ApiFunction() { + @Override + public Cluster apply(com.google.bigtable.admin.v2.Cluster proto) { + return Cluster.fromProto(proto); + } + }, + MoreExecutors.directExecutor() + ); + } + + /** + * Lists all clusters in the specified instance. + * + *

This method will throw a {@link PartialListClustersException} when any zone is + * unavailable. If partial listing are ok, the exception can be caught and inspected. + * + *

Sample code: + * + *

{@code
+   * try {
+   *   List clusters = cluster.listClusters("my-instance");
+   * } catch (PartialListClustersException e) {
+   *   System.out.println("The following zones are unavailable: " + e.getUnavailableZones());
+   *   System.out.println("But the following clusters are reachable: " + e.getClusters())
+   * }
+   * }
+ */ + @SuppressWarnings("WeakerAccess") + public List listClusters(String instanceId) { + return awaitFuture(listClustersAsync(instanceId)); + } + + /** + * Asynchronously lists all clusters in the specified instance. + * + *

This method will throw a {@link PartialListClustersException} when any zone is + * unavailable. If partial listing are ok, the exception can be caught and inspected. + * + *

Sample code: + * + *

{@code
+   * ApiFuture clustersFuture = client.listClustersAsync("my-instance");
+   *
+   * ApiFutures.addCallback(clustersFuture, new ApiFutureCallback>() {
+   *   public void onFailure(Throwable t) {
+   *     if (t instanceof PartialListClustersException) {
+   *       PartialListClustersException partialError = (PartialListClustersException)t;
+   *       System.out.println("The following zones are unavailable: " + partialError.getUnavailableZones());
+   *       System.out.println("But the following clusters are reachable: " + partialError.getClusters());
+   *     } else {
+   *       t.printStackTrace();
+   *     }
+   *   }
+   *
+   *   public void onSuccess(List result) {
+   *     System.out.println("Found a complete set of instances: " + result);
+   *   }
+   * }, MoreExecutors.directExecutor());
+   * }
+ */ + @SuppressWarnings("WeakerAccess") + public ApiFuture> listClustersAsync(String instanceId) { + InstanceName name = InstanceName.of(projectName.getProject(), instanceId); + ListClustersRequest request = ListClustersRequest.newBuilder() + .setParent(name.toString()) + .build(); + + return ApiFutures.transform( + stub.listClustersCallable().futureCall(request), + new ApiFunction>() { + @Override + public List apply(ListClustersResponse proto) { + // NOTE: serverside pagination is not and will not be implemented, so remaining pages + // are not fetched. However, if that assumption turns out to be wrong, fail fast to + // avoid returning partial data. + Verify.verify(proto.getNextPageToken().isEmpty(), + "Server returned an unexpected paginated response"); + + ImmutableList.Builder clusters = ImmutableList.builder(); + for (com.google.bigtable.admin.v2.Cluster cluster : proto.getClustersList()) { + clusters.add(Cluster.fromProto(cluster)); + } + + ImmutableList.Builder failedZones = ImmutableList.builder(); + for (String locationStr : proto.getFailedLocationsList()) { + LocationName fullLocation = Objects.requireNonNull(LocationName.parse(locationStr)); + failedZones.add(fullLocation.getLocation()); + } + + if (!failedZones.build().isEmpty()) { + throw new PartialListClustersException(failedZones.build(), clusters.build()); + } + + return clusters.build(); + } + }, + MoreExecutors.directExecutor() + ); + } + + /** + * Resizes the cluster's node count. Please note that only clusters that belong to a PRODUCTION + * instance can be resized. + * + *

Sample code: + * + *

{@code
+   * Cluster cluster = cluster.resizeCluster("my-instance", "my-cluster", 30);
+   * }
+ */ + @SuppressWarnings("WeakerAccess") + public Cluster resizeCluster(String instanceId, String clusterId, int numServeNodes) { + return awaitFuture(resizeClusterAsync(instanceId, clusterId, numServeNodes)); + } + + /** + * Asynchronously resizes the cluster's node count. Please note that only clusters that belong to + * a PRODUCTION instance can be resized. + * + *
{@code
+   * ApiFuture clusterFuture = cluster.resizeCluster("my-instance", "my-cluster", 30);
+   * Cluster cluster = clusterFuture.get();
+   * }
+ */ + @SuppressWarnings("WeakerAccess") + public ApiFuture resizeClusterAsync(String instanceId, String clusterId, + int numServeNodes) { + + ClusterName name = ClusterName.of(projectName.getProject(), instanceId, clusterId); + + com.google.bigtable.admin.v2.Cluster request = com.google.bigtable.admin.v2.Cluster.newBuilder() + .setName(name.toString()) + .setServeNodes(numServeNodes) + .build(); + + return ApiFutures.transform( + stub.updateClusterOperationCallable().futureCall(request), + new ApiFunction() { + @Override + public Cluster apply(com.google.bigtable.admin.v2.Cluster proto) { + return Cluster.fromProto(proto); + } + }, + MoreExecutors.directExecutor() + ); + } + + /** + * Deletes the specified cluster. Please note that an instance must have at least 1 cluster. To + * remove the last cluster, please use {@link BigtableInstanceAdminClient#deleteInstance(String)}. + * + *

Sample code: + * + *

{@code
+   * client.deleteCluster("my-instance", "my-cluster");
+   * }
+ */ + @SuppressWarnings("WeakerAccess") + public void deleteCluster(String instanceId, String clusterId) { + awaitFuture(deleteClusterAsync(instanceId, clusterId)); + } + + /** + * Asynchronously deletes the specified cluster. Please note that an instance must have at least 1 + * cluster. To remove the last cluster, please use {@link BigtableInstanceAdminClient#deleteInstanceAsync(String)}. + * + *

Sample code: + * + *

{@code
+   * ApiFuture future = client.deleteClusterAsync("my-instance", "my-cluster");
+   * future.get();
+   * }
+ */ + @SuppressWarnings("WeakerAccess") + public ApiFuture deleteClusterAsync(String instanceId, String clusterId) { + ClusterName name = ClusterName.of(projectName.getProject(), instanceId, clusterId); + + DeleteClusterRequest request = DeleteClusterRequest.newBuilder() + .setName(name.toString()) + .build(); + + return ApiFutures.transform( + stub.deleteClusterCallable().futureCall(request), + new ApiFunction() { + @Override + public Void apply(Empty input) { + return null; + } + }, + MoreExecutors.directExecutor() + ); + } + /** * Awaits the result of a future, taking care to propagate errors while maintaining the call site * in a suppressed exception. This allows semantic errors to be caught across threads, while @@ -406,7 +690,6 @@ public Void apply(Empty input) { // TODO(igorbernstein2): try to move this into gax private T awaitFuture(ApiFuture future) { RuntimeException error; - try { return Futures.getUnchecked(future); } catch (UncheckedExecutionException e) { @@ -418,10 +701,8 @@ private T awaitFuture(ApiFuture future) { } catch (RuntimeException e) { error = e; } - // Add the caller's stack as a suppressed exception error.addSuppressed(new RuntimeException("Encountered error while awaiting future")); - throw error; } } diff --git a/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/models/Cluster.java b/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/models/Cluster.java new file mode 100644 index 000000000000..19ef7a026b69 --- /dev/null +++ b/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/models/Cluster.java @@ -0,0 +1,130 @@ +/* + * Copyright 2018 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 + * + * https://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.bigtable.admin.v2.models; + +import com.google.api.core.InternalApi; +import com.google.bigtable.admin.v2.Cluster.State; +import com.google.bigtable.admin.v2.ClusterName; +import com.google.bigtable.admin.v2.LocationName; +import com.google.bigtable.admin.v2.StorageType; +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import com.google.common.base.Verify; +import javax.annotation.Nonnull; + +/** + * A cluster represents the actual Cloud Bigtable service. Each cluster belongs to a single Cloud + * Bigtable instance, and an instance can have up to 2 clusters. When your application sends + * requests to a Cloud Bigtable instance, those requests are actually handled by one of the clusters + * in the instance. + */ +public class Cluster { + @Nonnull + private final com.google.bigtable.admin.v2.Cluster proto; + + /** + * Wraps a protobuf response. + * + *

This method is considered an internal implementation detail and not meant to be used by + * applications. + */ + @InternalApi + public static Cluster fromProto(com.google.bigtable.admin.v2.Cluster proto) { + return new Cluster(proto); + } + + private Cluster(@Nonnull com.google.bigtable.admin.v2.Cluster proto) { + Preconditions.checkNotNull(proto); + Preconditions.checkArgument(!proto.getName().isEmpty(), "Name must be set"); + this.proto = proto; + } + + + /** Gets the cluster's id. */ + @SuppressWarnings("WeakerAccess") + public String getId() { + // Constructor ensures that name is not null + ClusterName fullName = Verify.verifyNotNull( + ClusterName.parse(proto.getName()), + "Name can never be null"); + //noinspection ConstantConditions + return fullName.getCluster(); + } + + /** Gets the instance id. */ + @SuppressWarnings("WeakerAccess") + public String getInstanceId() { + // Constructor ensures that name is not null + ClusterName fullName = Verify.verifyNotNull( + ClusterName.parse(proto.getName()), + "Name can never be null"); + //noinspection ConstantConditions + return fullName.getInstance(); + + } + + + /** Get the zone where this cluster is located. */ + @SuppressWarnings("WeakerAccess") + public String getZone() { + LocationName location = Verify.verifyNotNull(LocationName.parse(proto.getLocation())); + //noinspection ConstantConditions + return location.getLocation(); + } + + /** Gets the current state of the cluster */ + // TODO(igorbernstein2): try to avoid exposing proto enums + @SuppressWarnings("WeakerAccess") + public State getState() { + return proto.getState(); + } + + /** + * Get the number of nodes allocated to this cluster. More nodes enable higher throughput and more + * consistent performance. + */ + @SuppressWarnings("WeakerAccess") + public int getServeNodes() { + return proto.getServeNodes(); + } + + /** + * The type of storage used by this cluster to serve its parent instance's tables, unless + * explicitly overridden. + */ + // TODO(igorbernstein2): try to avoid exposing proto enums + @SuppressWarnings("WeakerAccess") + public StorageType getStorageType() { + return proto.getDefaultStorageType(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Cluster cluster = (Cluster) o; + return Objects.equal(proto, cluster.proto); + } + + @Override + public int hashCode() { + return Objects.hashCode(proto); + } +} diff --git a/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/models/CreateClusterRequest.java b/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/models/CreateClusterRequest.java index d653be2ac99b..aefccd3f692b 100644 --- a/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/models/CreateClusterRequest.java +++ b/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/models/CreateClusterRequest.java @@ -112,6 +112,9 @@ public com.google.bigtable.admin.v2.CreateClusterRequest toProto(ProjectName pro /** * Gets the clusterId. This method is meant to be used by {@link CreateClusterRequest} and is * considered an internal implementation detail and not meant to be used by applications. + * + *

This method is considered an internal implementation detail and not meant to be used by + * applications. */ @InternalApi String getClusterId() { @@ -121,6 +124,9 @@ String getClusterId() { /** * Creates the request protobuf to be used in {@link CreateInstanceRequest}. This method is * considered an internal implementation detail and not meant to be used by applications. + * + *

This method is considered an internal implementation detail and not meant to be used by + * applications. */ @InternalApi com.google.bigtable.admin.v2.Cluster toEmbeddedProto(ProjectName projectName) { diff --git a/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/models/PartialListClustersException.java b/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/models/PartialListClustersException.java new file mode 100644 index 000000000000..1696877ca443 --- /dev/null +++ b/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/models/PartialListClustersException.java @@ -0,0 +1,50 @@ +/* + * Copyright 2018 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 + * + * https://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.bigtable.admin.v2.models; + +import com.google.api.core.InternalApi; +import com.google.common.collect.ImmutableList; +import java.util.List; + +/** + * Exception thrown when some zones are unavailable and listClusters is unable to return a full + * cluster list. This exception can be inspected to get a partial list. + */ +public class PartialListClustersException extends RuntimeException { + private final List unavailableZones; + private final List clusters; + + /** + *

This method is considered an internal implementation detail and not meant to be used by + * applications. + */ + @InternalApi + public PartialListClustersException(List unavailableZones, List clusters) { + super("Failed to list all instances, some zones where unavailable"); + this.unavailableZones = ImmutableList.copyOf(unavailableZones); + this.clusters = ImmutableList.copyOf(clusters); + } + + /** A list of zones, whose unavailability caused this error. */ + public List getUnavailableZones() { + return unavailableZones; + } + + /** A partial list of clusters that were found in the available zones. */ + public List getClusters() { + return clusters; + } +} diff --git a/google-cloud-clients/google-cloud-bigtable-admin/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClientTest.java b/google-cloud-clients/google-cloud-bigtable-admin/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClientTest.java index 1adbf9be0641..444ba99053ec 100644 --- a/google-cloud-clients/google-cloud-bigtable-admin/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClientTest.java +++ b/google-cloud-clients/google-cloud-bigtable-admin/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClientTest.java @@ -26,16 +26,20 @@ import com.google.api.gax.rpc.OperationCallable; import com.google.api.gax.rpc.UnaryCallable; import com.google.api.gax.rpc.testing.FakeOperationSnapshot; -import com.google.bigtable.admin.v2.Cluster; +import com.google.bigtable.admin.v2.ClusterName; import com.google.bigtable.admin.v2.CreateInstanceMetadata; import com.google.bigtable.admin.v2.Instance.Type; import com.google.bigtable.admin.v2.InstanceName; import com.google.bigtable.admin.v2.LocationName; import com.google.bigtable.admin.v2.ProjectName; import com.google.bigtable.admin.v2.StorageType; +import com.google.bigtable.admin.v2.UpdateClusterMetadata; import com.google.bigtable.admin.v2.UpdateInstanceMetadata; +import com.google.cloud.bigtable.admin.v2.models.Cluster; +import com.google.cloud.bigtable.admin.v2.models.CreateClusterRequest; import com.google.cloud.bigtable.admin.v2.models.CreateInstanceRequest; import com.google.cloud.bigtable.admin.v2.models.Instance; +import com.google.cloud.bigtable.admin.v2.models.PartialListClustersException; import com.google.cloud.bigtable.admin.v2.models.PartialListInstancesException; import com.google.cloud.bigtable.admin.v2.models.UpdateInstanceRequest; import com.google.cloud.bigtable.admin.v2.stub.BigtableInstanceAdminStub; @@ -58,6 +62,8 @@ public class BigtableInstanceAdminClientTest { private static final ProjectName PROJECT_NAME = ProjectName.of("my-project"); private static final InstanceName INSTANCE_NAME = InstanceName.of(PROJECT_NAME.getProject(), "my-instance"); + private static final ClusterName CLUSTER_NAME = + ClusterName.of(INSTANCE_NAME.getProject(), INSTANCE_NAME.getInstance(), "my-cluster"); private BigtableInstanceAdminClient adminClient; @@ -80,6 +86,17 @@ public class BigtableInstanceAdminClientTest { private UnaryCallable mockDeleteInstanceCallable; + @Mock + private OperationCallable mockCreateClusterCallable; + @Mock + private UnaryCallable mockGetClusterCallable; + @Mock + private UnaryCallable mockListClustersCallable; + @Mock + private OperationCallable mockUpdateClusterCallable; + @Mock + private UnaryCallable mockDeleteClusterCallable; + @Before public void setUp() { adminClient = BigtableInstanceAdminClient @@ -92,6 +109,12 @@ public void setUp() { Mockito.when(mockStub.getInstanceCallable()).thenReturn(mockGetInstanceCallable); Mockito.when(mockStub.listInstancesCallable()).thenReturn(mockListInstancesCallable); Mockito.when(mockStub.deleteInstanceCallable()).thenReturn(mockDeleteInstanceCallable); + + Mockito.when(mockStub.createClusterOperationCallable()).thenReturn(mockCreateClusterCallable); + Mockito.when(mockStub.getClusterCallable()).thenReturn(mockGetClusterCallable); + Mockito.when(mockStub.listClustersCallable()).thenReturn(mockListClustersCallable); + Mockito.when(mockStub.updateClusterOperationCallable()).thenReturn(mockUpdateClusterCallable); + Mockito.when(mockStub.deleteClusterCallable()).thenReturn(mockDeleteClusterCallable); } @Test @@ -117,7 +140,7 @@ public void testCreateInstance() { .setType(Type.DEVELOPMENT) .setDisplayName(INSTANCE_NAME.getInstance()) ) - .putClusters("cluster1", Cluster.newBuilder() + .putClusters("cluster1", com.google.bigtable.admin.v2.Cluster.newBuilder() .setLocation("projects/my-project/locations/us-east1-c") .setServeNodes(1) .setDefaultStorageType(StorageType.SSD) @@ -301,6 +324,188 @@ public ApiFuture answer(InvocationOnMock invocationOnMock) { assertThat(wasCalled.get()).isTrue(); } + @Test + public void testCreateCluster() { + // Setup + com.google.bigtable.admin.v2.CreateClusterRequest expectedRequest = + com.google.bigtable.admin.v2.CreateClusterRequest.newBuilder() + .setParent(INSTANCE_NAME.toString()) + .setClusterId(CLUSTER_NAME.getCluster()) + .setCluster( + com.google.bigtable.admin.v2.Cluster.newBuilder() + .setLocation("projects/my-project/locations/us-east1-c") + .setServeNodes(3) + .setDefaultStorageType(StorageType.SSD) + ) + .build(); + com.google.bigtable.admin.v2.Cluster expectedResponse = com.google.bigtable.admin.v2.Cluster + .newBuilder() + .setName(CLUSTER_NAME.toString()) + .build(); + mockOperationResult(mockCreateClusterCallable, expectedRequest, expectedResponse); + + // Execute + Cluster actualResult = adminClient.createCluster( + CreateClusterRequest.of(CLUSTER_NAME.getInstance(), CLUSTER_NAME.getCluster()) + .setZone("us-east1-c") + .setServeNodes(3) + .setStorageType(StorageType.SSD) + ); + // Verify + assertThat(actualResult).isEqualTo(Cluster.fromProto(expectedResponse)); + } + + @Test + public void testGetCluster() { + // Setup + com.google.bigtable.admin.v2.GetClusterRequest expectedRequest = + com.google.bigtable.admin.v2.GetClusterRequest.newBuilder() + .setName(CLUSTER_NAME.toString()) + .build(); + + com.google.bigtable.admin.v2.Cluster expectedResponse = com.google.bigtable.admin.v2.Cluster + .newBuilder() + .setName(CLUSTER_NAME.toString()) + .build(); + + Mockito.when(mockGetClusterCallable.futureCall(expectedRequest)).thenReturn( + ApiFutures.immediateFuture(expectedResponse) + ); + + // Execute + Cluster actualResult = adminClient + .getCluster(CLUSTER_NAME.getInstance(), CLUSTER_NAME.getCluster()); + + // Verify + assertThat(actualResult).isEqualTo(Cluster.fromProto(expectedResponse)); + } + + @Test + public void testListClusters() { + // Setup + com.google.bigtable.admin.v2.ListClustersRequest expectedRequest = + com.google.bigtable.admin.v2.ListClustersRequest.newBuilder() + .setParent(INSTANCE_NAME.toString()) + .build(); + + com.google.bigtable.admin.v2.ListClustersResponse expectedResponse = + com.google.bigtable.admin.v2.ListClustersResponse.newBuilder() + .addClusters( + com.google.bigtable.admin.v2.Cluster.newBuilder() + .setName(CLUSTER_NAME.toString() + "1") + ) + .addClusters( + com.google.bigtable.admin.v2.Cluster.newBuilder() + .setName(CLUSTER_NAME.toString() + "2") + ) + .build(); + + Mockito.when(mockListClustersCallable.futureCall(expectedRequest)).thenReturn( + ApiFutures.immediateFuture(expectedResponse) + ); + + // Execute + List actualResult = adminClient.listClusters(INSTANCE_NAME.getInstance()); + + // Verify + assertThat(actualResult).containsExactly( + Cluster.fromProto(expectedResponse.getClusters(0)), + Cluster.fromProto(expectedResponse.getClusters(1)) + ); + } + + @Test + public void testListClustersFailedZone() { + // Setup + com.google.bigtable.admin.v2.ListClustersRequest expectedRequest = + com.google.bigtable.admin.v2.ListClustersRequest.newBuilder() + .setParent(INSTANCE_NAME.toString()) + .build(); + + com.google.bigtable.admin.v2.ListClustersResponse expectedResponse = + com.google.bigtable.admin.v2.ListClustersResponse.newBuilder() + .addClusters( + com.google.bigtable.admin.v2.Cluster.newBuilder() + .setName(CLUSTER_NAME.toString()) + ) + .addFailedLocations( + LocationName.of(PROJECT_NAME.getProject(), "us-east1-c").toString() + ) + .build(); + + Mockito.when(mockListClustersCallable.futureCall(expectedRequest)).thenReturn( + ApiFutures.immediateFuture(expectedResponse) + ); + + // Execute + Exception actualError = null; + + try { + adminClient.listClusters(INSTANCE_NAME.getInstance()); + } catch (Exception e) { + actualError = e; + } + + // Verify + assertThat(actualError).isInstanceOf(PartialListClustersException.class); + assert actualError != null; + PartialListClustersException partialListError = (PartialListClustersException) actualError; + assertThat(partialListError.getClusters()) + .containsExactly(Cluster.fromProto(expectedResponse.getClusters(0))); + assertThat(partialListError.getUnavailableZones()).containsExactly("us-east1-c"); + } + + @Test + public void testResizeCluster() { + // Setup + com.google.bigtable.admin.v2.Cluster expectedRequest = + com.google.bigtable.admin.v2.Cluster.newBuilder() + .setName(CLUSTER_NAME.toString()) + .setServeNodes(30) + .build(); + + com.google.bigtable.admin.v2.Cluster expectedResponse = + com.google.bigtable.admin.v2.Cluster.newBuilder() + .setName(CLUSTER_NAME.toString()) + .setLocation(LocationName.of(PROJECT_NAME.getProject(), "us-east1-c").toString()) + .setServeNodes(30) + .build(); + + mockOperationResult(mockUpdateClusterCallable, expectedRequest, expectedResponse); + + // Execute + Cluster actualResult = adminClient + .resizeCluster(CLUSTER_NAME.getInstance(), CLUSTER_NAME.getCluster(), 30); + + // Verify + assertThat(actualResult).isEqualTo(Cluster.fromProto(expectedResponse)); + } + + @Test + public void testDeleteCluster() { + // Setup + com.google.bigtable.admin.v2.DeleteClusterRequest expectedRequest = + com.google.bigtable.admin.v2.DeleteClusterRequest.newBuilder() + .setName(CLUSTER_NAME.toString()) + .build(); + + final AtomicBoolean wasCalled = new AtomicBoolean(false); + + Mockito.when(mockDeleteClusterCallable.futureCall(expectedRequest)) + .thenAnswer(new Answer>() { + @Override + public ApiFuture answer(InvocationOnMock invocationOnMock) { + wasCalled.set(true); + return ApiFutures.immediateFuture(Empty.getDefaultInstance()); + } + }); + + // Execute + adminClient.deleteCluster(CLUSTER_NAME.getInstance(), CLUSTER_NAME.getCluster()); + + // Verify + assertThat(wasCalled.get()).isTrue(); + } private void mockOperationResult( OperationCallable callable, ReqT request, RespT response) { @@ -310,10 +515,8 @@ private void mockOperationResult( .setName("fake-name") .setResponse(response) .build(); - OperationFuture operationFuture = OperationFutures .immediateOperationFuture(operationSnapshot); - Mockito.when(callable.futureCall(request)).thenReturn(operationFuture); } } diff --git a/google-cloud-clients/google-cloud-bigtable-admin/src/test/java/com/google/cloud/bigtable/admin/v2/models/ClusterTest.java b/google-cloud-clients/google-cloud-bigtable-admin/src/test/java/com/google/cloud/bigtable/admin/v2/models/ClusterTest.java new file mode 100644 index 000000000000..76e158c7603d --- /dev/null +++ b/google-cloud-clients/google-cloud-bigtable-admin/src/test/java/com/google/cloud/bigtable/admin/v2/models/ClusterTest.java @@ -0,0 +1,67 @@ +/* + * Copyright 2018 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 + * + * https://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.bigtable.admin.v2.models; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.bigtable.admin.v2.Cluster.State; +import com.google.bigtable.admin.v2.StorageType; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ClusterTest { + @Test + public void testFromProto() { + com.google.bigtable.admin.v2.Cluster proto = com.google.bigtable.admin.v2.Cluster.newBuilder() + .setName("projects/my-project/instances/my-instance/clusters/my-cluster") + .setLocation("projects/my-project/locations/us-east1-c") + .setState(State.READY) + .setServeNodes(30) + .setDefaultStorageType(StorageType.SSD) + .build(); + + Cluster result = Cluster.fromProto(proto); + + assertThat(result.getId()).isEqualTo("my-cluster"); + assertThat(result.getInstanceId()).isEqualTo("my-instance"); + assertThat(result.getZone()).isEqualTo("us-east1-c"); + assertThat(result.getState()).isEqualTo(State.READY); + assertThat(result.getServeNodes()).isEqualTo(30); + assertThat(result.getStorageType()).isEqualTo(StorageType.SSD); + } + + @Test + public void testRequiresName() { + com.google.bigtable.admin.v2.Cluster proto = com.google.bigtable.admin.v2.Cluster.newBuilder() + .setLocation("projects/my-project/locations/us-east1-c") + .setState(State.READY) + .setServeNodes(30) + .setDefaultStorageType(StorageType.SSD) + .build(); + + Exception actualException = null; + + try { + Cluster.fromProto(proto); + } catch (Exception e) { + actualException = e; + } + + assertThat(actualException).isInstanceOf(IllegalArgumentException.class); + } +}