diff --git a/google-cloud-clients/google-cloud-bigtable-admin/pom.xml b/google-cloud-clients/google-cloud-bigtable-admin/pom.xml index 75603a302648..7de8dd203a18 100644 --- a/google-cloud-clients/google-cloud-bigtable-admin/pom.xml +++ b/google-cloud-clients/google-cloud-bigtable-admin/pom.xml @@ -61,6 +61,12 @@ test-jar test + + com.google.api + gax + testlib + test + junit junit 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 fd9218bb2e95..8e49ced04ece 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 @@ -27,11 +27,14 @@ import com.google.bigtable.admin.v2.ProjectName; 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.PartialListInstancesException; import com.google.cloud.bigtable.admin.v2.models.UpdateInstanceRequest; import com.google.cloud.bigtable.admin.v2.stub.BigtableInstanceAdminStub; import com.google.common.base.Verify; import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.UncheckedExecutionException; import com.google.protobuf.Empty; import java.io.IOException; import java.util.List; @@ -96,7 +99,7 @@ public static BigtableInstanceAdminClient create(@Nonnull BigtableInstanceAdminS return create(settings.getProjectName(), settings.getStubSettings().createStub()); } - /** Constructs an instance of BigtableInstanceAdminClient with the given Projectname and stub. */ + /** Constructs an instance of BigtableInstanceAdminClient with the given ProjectName and stub. */ public static BigtableInstanceAdminClient create(@Nonnull ProjectName projectName, @Nonnull BigtableInstanceAdminStub stub) { return new BigtableInstanceAdminClient(projectName, stub); @@ -110,6 +113,7 @@ private BigtableInstanceAdminClient( } /** Gets the ProjectName this client is associated with. */ + @SuppressWarnings("WeakerAccess") public ProjectName getProjectName() { return projectName; } @@ -125,6 +129,7 @@ public void close() { * * @see CreateInstanceRequest for details. */ + @SuppressWarnings("WeakerAccess") public Instance createInstance(CreateInstanceRequest request) { return awaitFuture(createInstanceAsync(request)); } @@ -134,10 +139,16 @@ public Instance createInstance(CreateInstanceRequest request) { * * @see CreateInstanceRequest for details. */ + @SuppressWarnings("WeakerAccess") public ApiFuture createInstanceAsync(CreateInstanceRequest request) { return ApiFutures.transform( stub.createInstanceOperationCallable().futureCall(request.toProto(projectName)), - Instance.PROTO_TRANSFORMER, + new ApiFunction() { + @Override + public Instance apply(com.google.bigtable.admin.v2.Instance proto) { + return Instance.fromProto(proto); + } + }, MoreExecutors.directExecutor()); } @@ -146,6 +157,7 @@ public ApiFuture createInstanceAsync(CreateInstanceRequest request) { * * @see UpdateInstanceRequest for details. */ + @SuppressWarnings("WeakerAccess") public Instance updateInstance(UpdateInstanceRequest request) { return awaitFuture(updateInstanceAsync(request)); } @@ -155,21 +167,28 @@ public Instance updateInstance(UpdateInstanceRequest request) { * * @see UpdateInstanceRequest for details. */ + @SuppressWarnings("WeakerAccess") public ApiFuture updateInstanceAsync(UpdateInstanceRequest request) { return ApiFutures.transform( stub.partialUpdateInstanceOperationCallable().futureCall(request.toProto(projectName)), - Instance.PROTO_TRANSFORMER, + new ApiFunction() { + @Override + public Instance apply(com.google.bigtable.admin.v2.Instance proto) { + return Instance.fromProto(proto); + } + }, MoreExecutors.directExecutor()); } /** Get the instance representation. */ + @SuppressWarnings("WeakerAccess") public Instance getInstance(String id) { return awaitFuture(getInstanceAsync(id)); } /** Asynchronously gets the instance representation wrapped in a future. */ + @SuppressWarnings("WeakerAccess") public ApiFuture getInstanceAsync(String instanceId) { - InstanceName name = InstanceName.of(projectName.getProject(), instanceId); GetInstanceRequest request = GetInstanceRequest.newBuilder() @@ -178,16 +197,23 @@ public ApiFuture getInstanceAsync(String instanceId) { return ApiFutures.transform( stub.getInstanceCallable().futureCall(request), - Instance.PROTO_TRANSFORMER, + new ApiFunction() { + @Override + public Instance apply(com.google.bigtable.admin.v2.Instance proto) { + return Instance.fromProto(proto); + } + }, MoreExecutors.directExecutor()); } /** Lists all of the instances in the current project. */ + @SuppressWarnings("WeakerAccess") public List listInstances() { return awaitFuture(listInstancesAsync()); } /** Asynchronously lists all of the instances in the current project. */ + @SuppressWarnings("WeakerAccess") public ApiFuture> listInstancesAsync() { ListInstancesRequest request = ListInstancesRequest.newBuilder() .setParent(projectName.toString()) @@ -196,41 +222,47 @@ public ApiFuture> listInstancesAsync() { ApiFuture responseFuture = stub.listInstancesCallable() .futureCall(request); - return ApiFutures.transform(responseFuture, new ApiFunction>() { - @Override - public List apply(ListInstancesResponse proto) { - // NOTE: pagination is intentionally ignored. The server does not implement it. - Verify.verify(proto.getNextPageToken().isEmpty(), - "Server returned an unexpected paginated response"); - - ImmutableList.Builder instances = ImmutableList.builder(); - - for (com.google.bigtable.admin.v2.Instance protoInstance : proto.getInstancesList()) { - instances.add(Instance.PROTO_TRANSFORMER.apply(protoInstance)); - } - - ImmutableList.Builder failedZones = ImmutableList.builder(); - for (String locationStr : proto.getFailedLocationsList()) { - failedZones.add(LocationName.parse(locationStr).getLocation()); - } - - - if (!failedZones.build().isEmpty()) { - throw new PartialListInstancesException(failedZones.build(), instances.build()); - } - - return instances.build(); - } - }, MoreExecutors.directExecutor()); + return ApiFutures + .transform(responseFuture, new ApiFunction>() { + @Override + public List apply(ListInstancesResponse proto) { + // NOTE: pagination is intentionally ignored. The server does not implement it. + Verify.verify(proto.getNextPageToken().isEmpty(), + "Server returned an unexpected paginated response"); + + ImmutableList.Builder instances = ImmutableList.builder(); + + for (com.google.bigtable.admin.v2.Instance protoInstance : proto.getInstancesList()) { + instances.add(Instance.fromProto(protoInstance)); + } + + ImmutableList.Builder failedZones = ImmutableList.builder(); + for (String locationStr : proto.getFailedLocationsList()) { + LocationName fullLocation = LocationName.parse(locationStr); + if (fullLocation == null) { + continue; + } + failedZones.add(fullLocation.getLocation()); + } + + if (!failedZones.build().isEmpty()) { + throw new PartialListInstancesException(failedZones.build(), instances.build()); + } + + return instances.build(); + } + }, MoreExecutors.directExecutor()); } /** Deletes the specified instance. */ + @SuppressWarnings("WeakerAccess") public void deleteInstance(String instanceId) { awaitFuture(deleteInstanceAsync(instanceId)); } /** Asynchronously deletes the specified instance. */ - private ApiFuture deleteInstanceAsync(String instanceId) { + @SuppressWarnings("WeakerAccess") + public ApiFuture deleteInstanceAsync(String instanceId) { InstanceName instanceName = InstanceName.of(projectName.getProject(), instanceId); DeleteInstanceRequest request = DeleteInstanceRequest.newBuilder() @@ -248,39 +280,32 @@ public Void apply(Empty input) { ); } - - private T awaitFuture(ApiFuture future) { - try { - return future.get(); - } catch(Throwable t) { - // TODO(igorbernstein2): figure out a better wrapper exception. - throw new RuntimeException(t); - } - } - /** - * Exception thrown when some zones are unavailable and listInstances is unable to return a full - * instance list. This exception can be inspected to get a partial list. + * 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 + * preserving the call site in the error. The caller's stacktrace will be made available as a + * suppressed exception. */ - public static class PartialListInstancesException extends RuntimeException { - private final List failedZones; - private final List instances; - - PartialListInstancesException(List failedZones, List instances) { - super("Failed to list all instances, some zones where unavailable"); + // TODO(igorbernstein2): try to move this into gax + private T awaitFuture(ApiFuture future) { + RuntimeException error; - this.failedZones = failedZones; - this.instances = instances; + try { + return Futures.getUnchecked(future); + } catch (UncheckedExecutionException e) { + if (e.getCause() instanceof RuntimeException) { + error = (RuntimeException) e.getCause(); + } else { + error = e; + } + } catch (RuntimeException e) { + error = e; } - /** A list of zones, whose unavailability caused this error. */ - public List getFailedZones() { - return failedZones; - } + // Add the caller's stack as a suppressed exception + error.addSuppressed(new RuntimeException("Encountered error while awaiting future")); - /** A partial list of instances that were found in the available zones. */ - public List getInstances() { - return instances; - } + throw error; } + } 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 09f8c20634ee..130cf46d5dfb 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 @@ -49,6 +49,7 @@ public final class CreateClusterRequest { private final com.google.bigtable.admin.v2.CreateClusterRequest.Builder proto = com.google.bigtable.admin.v2.CreateClusterRequest .newBuilder(); + // Partial ids will be set when the project is passed to toProto private final String instanceId; private String zone; @@ -70,12 +71,14 @@ private CreateClusterRequest(String instanceId, String clusterId) { * Sets the zone where the new cluster will be located. Must be different from the existing * cluster. */ + @SuppressWarnings("WeakerAccess") public CreateClusterRequest setZone(String zone) { this.zone = zone; return this; } /** Sets the type of storage used by this cluster to serve its parent instance's tables. */ + @SuppressWarnings("WeakerAccess") public CreateClusterRequest setServeNodes(int numNodes) { proto.getClusterBuilder().setServeNodes(numNodes); return this; @@ -86,6 +89,7 @@ public CreateClusterRequest setServeNodes(int numNodes) { * Defaults to {@code SSD}. */ // TODO(igorbernstein2): try to avoid leaking protobuf generated enums + @SuppressWarnings("WeakerAccess") public CreateClusterRequest setStorageType(StorageType storageType) { proto.getClusterBuilder().setDefaultStorageType(storageType); return this; @@ -113,8 +117,8 @@ String getClusterId() { } /** - * Creates the request protobuf. This method is considered an internal implementation detail and - * not meant to be used by applications. + * 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. */ @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/Instance.java b/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/models/Instance.java index 3be4e4e02c9a..b6cde0128f5b 100644 --- a/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/models/Instance.java +++ b/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/models/Instance.java @@ -15,51 +15,61 @@ */ package com.google.cloud.bigtable.admin.v2.models; -import com.google.api.core.ApiFunction; import com.google.api.core.InternalApi; +import com.google.bigtable.admin.v2.Instance.State; import com.google.bigtable.admin.v2.Instance.Type; import com.google.bigtable.admin.v2.InstanceName; import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import com.google.common.base.Verify; import java.util.Map; import javax.annotation.Nonnull; /** * Represents an existing Cloud Bigtable instance. * - *

A Cloud Bigtable instance is mostly just a container for your clusters and nodes, which do all - * of the real work. + *

A Cloud Bigtable instance is mostly just a container for your clusters and nodes, which do + * all of the real work. */ -public class Instance { +public final class Instance { @Nonnull private final com.google.bigtable.admin.v2.Instance proto; /** - * Wraps the protobuf. This method is considered an internal implementation detail and - * not meant to be used by applications. + * Wraps the protobuf. This method is considered an internal implementation detail and not meant + * to be used by applications. */ @InternalApi - public static ApiFunction PROTO_TRANSFORMER = new ApiFunction() { - @Override - public Instance apply(com.google.bigtable.admin.v2.Instance proto) { - return new Instance(proto); - } - }; + public static Instance fromProto(com.google.bigtable.admin.v2.Instance proto) { + return new Instance(proto); + } - private Instance(com.google.bigtable.admin.v2.Instance proto) { + private Instance(@Nonnull com.google.bigtable.admin.v2.Instance proto) { + Preconditions.checkNotNull(proto); + Preconditions.checkArgument(!proto.getName().isEmpty(), "Name must be set"); this.proto = proto; } /** Gets the instance's id. */ + @SuppressWarnings("WeakerAccess") public String getId() { - return InstanceName.parse(proto.getName()).getInstance(); + // Constructor ensures that name is not null + InstanceName fullName = Verify.verifyNotNull( + InstanceName.parse(proto.getName()), + "Name can never be null"); + + //noinspection ConstantConditions + return fullName.getInstance(); } /** Gets the instance's friendly name. */ + @SuppressWarnings("WeakerAccess") public String getDisplayName() { return proto.getDisplayName(); } /** Gets the instance's current type. Can be DEVELOPMENT or PRODUCTION. */ + @SuppressWarnings("WeakerAccess") public Type getType() { return proto.getType(); } @@ -67,12 +77,22 @@ public Type getType() { /** * Gets the current labels associated with the instance. * - * @see For more details + * @see For more + * details */ + @SuppressWarnings("WeakerAccess") public Map getLabels() { return proto.getLabelsMap(); } + + /** The current state of the instance. */ + // TODO(igorbernstein2): Try to avoid leaking protobuf enums + @SuppressWarnings("WeakerAccess") + public State getState() { + return proto.getState(); + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/models/PartialListInstancesException.java b/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/models/PartialListInstancesException.java new file mode 100644 index 000000000000..54bb98b98979 --- /dev/null +++ b/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/models/PartialListInstancesException.java @@ -0,0 +1,44 @@ +/* + * 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 java.util.List; + +/** + * Exception thrown when some zones are unavailable and listInstances is unable to return a full + * instance list. This exception can be inspected to get a partial list. + */ +public class PartialListInstancesException extends RuntimeException { + private final List failedZones; + private final List instances; + + public PartialListInstancesException(List failedZones, List instances) { + super("Failed to list all instances, some zones where unavailable"); + + this.failedZones = failedZones; + this.instances = instances; + } + + /** A list of zones, whose unavailability caused this error. */ + public List getFailedZones() { + return failedZones; + } + + /** A partial list of instances that were found in the available zones. */ + public List getInstances() { + return instances; + } +} diff --git a/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/models/UpdateInstanceRequest.java b/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/models/UpdateInstanceRequest.java index 01d1ebbf88d3..3203971d9695 100644 --- a/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/models/UpdateInstanceRequest.java +++ b/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/models/UpdateInstanceRequest.java @@ -30,13 +30,14 @@ /** * Parameters for updating an existing Bigtable instance. * - *

Existing instances maybe updated to change their superficial appearance (ie. display name) and - * can also be upgraded from a DEVELOPMENT instance to a PRODUCTION instance. Please note that + *

Existing instances maybe updated to change their superficial appearance (ie. display name) + * and can also be upgraded from a DEVELOPMENT instance to a PRODUCTION instance. Please note that * upgrading to a PRODUCTION instance cannot be undone. */ public class UpdateInstanceRequest { private final String instanceId; - private final PartialUpdateInstanceRequest.Builder builder = PartialUpdateInstanceRequest.newBuilder(); + private final PartialUpdateInstanceRequest.Builder builder = PartialUpdateInstanceRequest + .newBuilder(); /** Builds a new request to update an existing instance with the specified id. */ public static UpdateInstanceRequest of(@Nonnull String instanceId) { @@ -49,7 +50,8 @@ private UpdateInstanceRequest(@Nonnull String instanceId) { } /** Changes the display name of the instance. */ - public UpdateInstanceRequest withDisplayName(@Nonnull String displayName) { + @SuppressWarnings("WeakerAccess") + public UpdateInstanceRequest setDisplayName(@Nonnull String displayName) { Preconditions.checkNotNull(displayName); builder.getInstanceBuilder().setDisplayName(displayName); updateFieldMask(Instance.DISPLAY_NAME_FIELD_NUMBER); @@ -61,7 +63,8 @@ public UpdateInstanceRequest withDisplayName(@Nonnull String displayName) { * Upgrades the instance from a DEVELOPMENT instance to a PRODUCTION instance. This cannot be * undone. */ - public UpdateInstanceRequest withProductionType() { + @SuppressWarnings("WeakerAccess") + public UpdateInstanceRequest setProductionType() { builder.getInstanceBuilder().setType(Type.PRODUCTION); updateFieldMask(Instance.TYPE_FIELD_NUMBER); @@ -71,9 +74,11 @@ public UpdateInstanceRequest withProductionType() { /** * Replaces the labels associated with the instance. * - * @see For more details + * @see For more + * details */ - public UpdateInstanceRequest withLabels(@Nonnull Map labels) { + @SuppressWarnings("WeakerAccess") + public UpdateInstanceRequest setLabels(@Nonnull Map labels) { Preconditions.checkNotNull(labels, "labels can't be null"); builder.getInstanceBuilder().clearLabels(); builder.getInstanceBuilder().putAllLabels(labels); @@ -93,6 +98,10 @@ private void updateFieldMask(int fieldNumber) { */ @InternalApi public PartialUpdateInstanceRequest toProto(ProjectName projectName) { + // Empty field mask implies full resource replacement, which would clear all fields in an empty + // update request. + Preconditions.checkState(!builder.getUpdateMask().getPathsList().isEmpty(), "Update request is empty"); + InstanceName instanceName = InstanceName.of(projectName.getProject(), instanceId); builder.getInstanceBuilder().setName(instanceName.toString()); 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 d76193b7afb5..82c6a78dbacf 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 @@ -17,30 +17,88 @@ import static com.google.common.truth.Truth.assertThat; +import com.google.api.core.ApiFuture; +import com.google.api.core.ApiFutures; +import com.google.api.gax.grpc.GrpcStatusCode; +import com.google.api.gax.longrunning.OperationFuture; +import com.google.api.gax.longrunning.OperationFutures; +import com.google.api.gax.longrunning.OperationSnapshot; +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.CreateInstanceMetadata; +import com.google.bigtable.admin.v2.Instance.Type; +import com.google.bigtable.admin.v2.InstanceName; +import com.google.bigtable.admin.v2.ListInstancesResponse; +import com.google.bigtable.admin.v2.LocationName; +import com.google.bigtable.admin.v2.PartialUpdateInstanceRequest; import com.google.bigtable.admin.v2.ProjectName; +import com.google.bigtable.admin.v2.StorageType; +import com.google.bigtable.admin.v2.UpdateInstanceMetadata; +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.PartialListInstancesException; +import com.google.cloud.bigtable.admin.v2.models.UpdateInstanceRequest; import com.google.cloud.bigtable.admin.v2.stub.BigtableInstanceAdminStub; +import com.google.protobuf.Empty; +import com.google.protobuf.FieldMask; +import io.grpc.Status.Code; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; @RunWith(MockitoJUnitRunner.class) 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 BigtableInstanceAdminClient adminClient; + @Mock private BigtableInstanceAdminStub mockStub; + @Mock + private OperationCallable mockCreateInstanceCallable; + + @Mock + private OperationCallable mockUpdateInstanceCallable; + + @Mock + private UnaryCallable mockGetInstanceCallable; + + @Mock + private UnaryCallable mockListInstancesCallable; + + @Mock + private UnaryCallable mockDeleteInstanceCallable; + + @Before public void setUp() { adminClient = BigtableInstanceAdminClient - .create(ProjectName.of("[PROJECT]"), mockStub); + .create(PROJECT_NAME, mockStub); + + Mockito.when(mockStub.createInstanceOperationCallable()).thenReturn(mockCreateInstanceCallable); + Mockito.when(mockStub.partialUpdateInstanceOperationCallable()) + .thenReturn(mockUpdateInstanceCallable); + + Mockito.when(mockStub.getInstanceCallable()).thenReturn(mockGetInstanceCallable); + Mockito.when(mockStub.listInstancesCallable()).thenReturn(mockListInstancesCallable); + Mockito.when(mockStub.deleteInstanceCallable()).thenReturn(mockDeleteInstanceCallable); } @Test public void testProjectName() { - assertThat(adminClient.getProjectName()).isEqualTo(ProjectName.of("[PROJECT]")); + assertThat(adminClient.getProjectName()).isEqualTo(PROJECT_NAME); } @Test @@ -48,4 +106,211 @@ public void testClose() { adminClient.close(); Mockito.verify(mockStub).close(); } + + @Test + public void testCreateInstance() { + // Setup + com.google.bigtable.admin.v2.CreateInstanceRequest expectedRequest = + com.google.bigtable.admin.v2.CreateInstanceRequest.newBuilder() + .setParent(PROJECT_NAME.toString()) + .setInstanceId(INSTANCE_NAME.getInstance()) + .setInstance( + com.google.bigtable.admin.v2.Instance.newBuilder() + .setType(Type.DEVELOPMENT) + .setDisplayName(INSTANCE_NAME.getInstance()) + ) + .putClusters("cluster1", Cluster.newBuilder() + .setLocation("projects/my-project/locations/us-east1-c") + .setServeNodes(1) + .setDefaultStorageType(StorageType.SSD) + .build() + ) + .build(); + + com.google.bigtable.admin.v2.Instance expectedResponse = com.google.bigtable.admin.v2.Instance + .newBuilder() + .setName(INSTANCE_NAME.toString()) + .build(); + + mockOperationResult(mockCreateInstanceCallable, expectedRequest, expectedResponse); + + // Execute + com.google.cloud.bigtable.admin.v2.models.Instance actualResult = adminClient.createInstance( + CreateInstanceRequest.of(INSTANCE_NAME.getInstance()) + .setType(Type.DEVELOPMENT) + .addCluster("cluster1", "us-east1-c", 1, StorageType.SSD) + ); + + // Verify + assertThat(actualResult).isEqualTo(Instance.fromProto(expectedResponse)); + } + + @Test + public void testUpdateInstance() { + // Setup + com.google.bigtable.admin.v2.PartialUpdateInstanceRequest expectedRequest = + PartialUpdateInstanceRequest.newBuilder() + .setUpdateMask( + FieldMask.newBuilder() + .addPaths("display_name") + ) + .setInstance( + com.google.bigtable.admin.v2.Instance.newBuilder() + .setName(INSTANCE_NAME.toString()) + .setDisplayName("new display name") + ) + .build(); + + com.google.bigtable.admin.v2.Instance expectedResponse = com.google.bigtable.admin.v2.Instance + .newBuilder() + .setName(INSTANCE_NAME.toString()) + .build(); + + mockOperationResult(mockUpdateInstanceCallable, expectedRequest, expectedResponse); + + // Execute + com.google.cloud.bigtable.admin.v2.models.Instance actualResult = adminClient.updateInstance( + UpdateInstanceRequest.of(INSTANCE_NAME.getInstance()) + .setDisplayName("new display name") + ); + + // Verify + assertThat(actualResult).isEqualTo(Instance.fromProto(expectedResponse)); + } + + @Test + public void testGetInstance() { + // Setup + com.google.bigtable.admin.v2.GetInstanceRequest expectedRequest = + com.google.bigtable.admin.v2.GetInstanceRequest.newBuilder() + .setName(INSTANCE_NAME.toString()) + .build(); + + com.google.bigtable.admin.v2.Instance expectedResponse = com.google.bigtable.admin.v2.Instance + .newBuilder() + .setName(INSTANCE_NAME.toString()) + .build(); + + Mockito.when(mockGetInstanceCallable.futureCall(expectedRequest)) + .thenReturn(ApiFutures.immediateFuture(expectedResponse)); + + // Execute + Instance actualResult = adminClient.getInstance(INSTANCE_NAME.getInstance()); + + // Verify + assertThat(actualResult).isEqualTo(Instance.fromProto(expectedResponse)); + } + + @Test + public void testListInstances() { + // Setup + com.google.bigtable.admin.v2.ListInstancesRequest expectedRequest = + com.google.bigtable.admin.v2.ListInstancesRequest.newBuilder() + .setParent(PROJECT_NAME.toString()) + .build(); + + com.google.bigtable.admin.v2.ListInstancesResponse expectedResponse = ListInstancesResponse + .newBuilder() + .addInstances( + com.google.bigtable.admin.v2.Instance.newBuilder().setName(INSTANCE_NAME + "1").build() + ) + .addInstances( + com.google.bigtable.admin.v2.Instance.newBuilder().setName(INSTANCE_NAME + "2").build() + ) + .build(); + + Mockito.when(mockListInstancesCallable.futureCall(expectedRequest)) + .thenReturn(ApiFutures.immediateFuture(expectedResponse)); + + // Execute + List actualResult = adminClient.listInstances(); + + // Verify + assertThat(actualResult).containsExactly( + Instance.fromProto(expectedResponse.getInstances(0)), + Instance.fromProto(expectedResponse.getInstances(1)) + ); + } + + @Test + public void testListInstancesFailedZone() { + // Setup + com.google.bigtable.admin.v2.ListInstancesRequest expectedRequest = + com.google.bigtable.admin.v2.ListInstancesRequest.newBuilder() + .setParent(PROJECT_NAME.toString()) + .build(); + + com.google.bigtable.admin.v2.ListInstancesResponse expectedResponse = ListInstancesResponse + .newBuilder() + .addInstances( + com.google.bigtable.admin.v2.Instance.newBuilder().setName(INSTANCE_NAME + "1").build() + ) + .addFailedLocations( + LocationName.of(PROJECT_NAME.getProject(), "us-east1-d").toString() + ) + .build(); + + Mockito.when(mockListInstancesCallable.futureCall(expectedRequest)) + .thenReturn(ApiFutures.immediateFuture(expectedResponse)); + + // Execute + Exception actualError = null; + + try { + adminClient.listInstances(); + } catch (Exception e) { + actualError = e; + } + + // Verify + assertThat(actualError).isInstanceOf(PartialListInstancesException.class); + assert actualError != null; + PartialListInstancesException partialListError = (PartialListInstancesException) actualError; + + assertThat(partialListError.getInstances()) + .containsExactly(Instance.fromProto(expectedResponse.getInstances(0))); + assertThat(partialListError.getFailedZones()).containsExactly("us-east1-d"); + } + + @Test + public void testDeleteInstance() { + // Setup + com.google.bigtable.admin.v2.DeleteInstanceRequest expectedRequest = + com.google.bigtable.admin.v2.DeleteInstanceRequest.newBuilder() + .setName(INSTANCE_NAME.toString()) + .build(); + + final AtomicBoolean wasCalled = new AtomicBoolean(false); + + Mockito.when(mockDeleteInstanceCallable.futureCall(expectedRequest)) + .thenAnswer(new Answer>() { + @Override + public ApiFuture answer(InvocationOnMock invocationOnMock) { + wasCalled.set(true); + return ApiFutures.immediateFuture(Empty.getDefaultInstance()); + } + }); + + // Execute + adminClient.deleteInstance(INSTANCE_NAME.getInstance()); + + // Verify + assertThat(wasCalled.get()).isTrue(); + } + + + private void mockOperationResult( + OperationCallable callable, ReqT request, RespT response) { + OperationSnapshot operationSnapshot = FakeOperationSnapshot.newBuilder() + .setDone(true) + .setErrorCode(GrpcStatusCode.of(Code.OK)) + .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/CreateClusterRequestTest.java b/google-cloud-clients/google-cloud-bigtable-admin/src/test/java/com/google/cloud/bigtable/admin/v2/models/CreateClusterRequestTest.java new file mode 100644 index 000000000000..cf4c4319a0b1 --- /dev/null +++ b/google-cloud-clients/google-cloud-bigtable-admin/src/test/java/com/google/cloud/bigtable/admin/v2/models/CreateClusterRequestTest.java @@ -0,0 +1,163 @@ +/* + * 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; +import com.google.bigtable.admin.v2.Instance; +import com.google.bigtable.admin.v2.Instance.Type; +import com.google.bigtable.admin.v2.ProjectName; +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 CreateClusterRequestTest { + @Test + public void testProductionSingle() { + CreateInstanceRequest input = CreateInstanceRequest.of("my-instance") + .setType(Type.PRODUCTION) + .addCluster("cluster1", "us-east1-c", 3, StorageType.SSD); + + com.google.bigtable.admin.v2.CreateInstanceRequest actual = input + .toProto(ProjectName.of("my-project")); + + com.google.bigtable.admin.v2.CreateInstanceRequest expected = + com.google.bigtable.admin.v2.CreateInstanceRequest.newBuilder() + .setParent(ProjectName.of("my-project").toString()) + .setInstanceId("my-instance") + .setInstance( + Instance.newBuilder() + .setDisplayName("my-instance") + .setType(Type.PRODUCTION) + ) + .putClusters("cluster1", + Cluster.newBuilder() + .setLocation("projects/my-project/locations/us-east1-c") + .setServeNodes(3) + .setDefaultStorageType(StorageType.SSD) + .build() + ) + .build(); + + assertThat(actual).isEqualTo(expected); + } + + @Test + public void testProductionReplication() { + CreateInstanceRequest input = CreateInstanceRequest.of("my-instance") + .setType(Type.PRODUCTION) + .addCluster("cluster1", "us-east1-c", 3, StorageType.SSD) + .addCluster("cluster2", "us-east1-a", 3, StorageType.SSD); + + com.google.bigtable.admin.v2.CreateInstanceRequest actual = input + .toProto(ProjectName.of("my-project")); + + com.google.bigtable.admin.v2.CreateInstanceRequest expected = + com.google.bigtable.admin.v2.CreateInstanceRequest.newBuilder() + .setParent(ProjectName.of("my-project").toString()) + .setInstanceId("my-instance") + .setInstance( + Instance.newBuilder() + .setDisplayName("my-instance") + .setType(Type.PRODUCTION) + ) + .putClusters("cluster1", + Cluster.newBuilder() + .setLocation("projects/my-project/locations/us-east1-c") + .setServeNodes(3) + .setDefaultStorageType(StorageType.SSD) + .build() + ) + .putClusters("cluster2", + Cluster.newBuilder() + .setLocation("projects/my-project/locations/us-east1-a") + .setServeNodes(3) + .setDefaultStorageType(StorageType.SSD) + .build() + ) + .build(); + + assertThat(actual).isEqualTo(expected); + } + + @Test + public void testDevelopment() { + CreateInstanceRequest input = CreateInstanceRequest.of("my-instance") + .setType(Type.DEVELOPMENT) + .addCluster("cluster1", "us-east1-c", 1, StorageType.SSD); + + com.google.bigtable.admin.v2.CreateInstanceRequest actual = input + .toProto(ProjectName.of("my-project")); + + com.google.bigtable.admin.v2.CreateInstanceRequest expected = + com.google.bigtable.admin.v2.CreateInstanceRequest.newBuilder() + .setParent(ProjectName.of("my-project").toString()) + .setInstanceId("my-instance") + .setInstance( + Instance.newBuilder() + .setDisplayName("my-instance") + .setType(Type.DEVELOPMENT) + ) + .putClusters("cluster1", + Cluster.newBuilder() + .setLocation("projects/my-project/locations/us-east1-c") + .setServeNodes(1) + .setDefaultStorageType(StorageType.SSD) + .build() + ) + .build(); + + assertThat(actual).isEqualTo(expected); + } + + @Test + public void testOptionalFields() { + CreateInstanceRequest input = CreateInstanceRequest.of("my-instance") + .setDisplayName("custom display name") + .addLabel("my label", "with some value") + .addLabel("my other label", "with some value") + .setType(Type.DEVELOPMENT) + .addCluster("cluster1", "us-east1-c", 1, StorageType.SSD); + + com.google.bigtable.admin.v2.CreateInstanceRequest actual = input + .toProto(ProjectName.of("my-project")); + + com.google.bigtable.admin.v2.CreateInstanceRequest expected = + com.google.bigtable.admin.v2.CreateInstanceRequest.newBuilder() + .setParent(ProjectName.of("my-project").toString()) + .setInstanceId("my-instance") + .setInstance( + Instance.newBuilder() + .setDisplayName("custom display name") + .putLabels("my label", "with some value") + .putLabels("my other label", "with some value") + .setType(Type.DEVELOPMENT) + ) + .putClusters("cluster1", + Cluster.newBuilder() + .setLocation("projects/my-project/locations/us-east1-c") + .setServeNodes(1) + .setDefaultStorageType(StorageType.SSD) + .build() + ) + .build(); + + assertThat(actual).isEqualTo(expected); + } +} \ No newline at end of file diff --git a/google-cloud-clients/google-cloud-bigtable-admin/src/test/java/com/google/cloud/bigtable/admin/v2/models/InstanceTest.java b/google-cloud-clients/google-cloud-bigtable-admin/src/test/java/com/google/cloud/bigtable/admin/v2/models/InstanceTest.java new file mode 100644 index 000000000000..1904028744ab --- /dev/null +++ b/google-cloud-clients/google-cloud-bigtable-admin/src/test/java/com/google/cloud/bigtable/admin/v2/models/InstanceTest.java @@ -0,0 +1,71 @@ +/* + * 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.Instance.State; +import com.google.bigtable.admin.v2.Instance.Type; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class InstanceTest { + @Test + public void testFromProto() { + com.google.bigtable.admin.v2.Instance proto = com.google.bigtable.admin.v2.Instance.newBuilder() + .setName("projects/my-project/instances/my-instance") + .setDisplayName("my display name") + .setType(Type.PRODUCTION) + .setState(State.READY) + .putLabels("label1", "value1") + .putLabels("label2", "value2") + .build(); + + Instance result = Instance.PROTO_TRANSFORMER.apply(proto); + + assertThat(result.getId()).isEqualTo("my-instance"); + assertThat(result.getDisplayName()).isEqualTo("my display name"); + assertThat(result.getType()).isEqualTo(Type.PRODUCTION); + assertThat(result.getState()).isEqualTo(State.READY); + assertThat(result.getLabels()).containsExactly( + "label1", "value1", + "label2", "value2" + ); + } + + @Test + public void testRequiresName() { + com.google.bigtable.admin.v2.Instance proto = com.google.bigtable.admin.v2.Instance.newBuilder() + .setDisplayName("my display name") + .setType(Type.PRODUCTION) + .setState(State.READY) + .putLabels("label1", "value1") + .putLabels("label2", "value2") + .build(); + + Exception actualException = null; + + try { + Instance.PROTO_TRANSFORMER.apply(proto); + } catch (Exception e) { + actualException = e; + } + + assertThat(actualException).isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file diff --git a/google-cloud-clients/google-cloud-bigtable-admin/src/test/java/com/google/cloud/bigtable/admin/v2/models/UpdateInstanceRequestTest.java b/google-cloud-clients/google-cloud-bigtable-admin/src/test/java/com/google/cloud/bigtable/admin/v2/models/UpdateInstanceRequestTest.java new file mode 100644 index 000000000000..b5d7b3902853 --- /dev/null +++ b/google-cloud-clients/google-cloud-bigtable-admin/src/test/java/com/google/cloud/bigtable/admin/v2/models/UpdateInstanceRequestTest.java @@ -0,0 +1,116 @@ +/* + * 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.Instance; +import com.google.bigtable.admin.v2.Instance.Type; +import com.google.bigtable.admin.v2.PartialUpdateInstanceRequest; +import com.google.bigtable.admin.v2.ProjectName; +import com.google.common.collect.ImmutableMap; +import com.google.protobuf.FieldMask; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class UpdateInstanceRequestTest { + @Test + public void testEmpty() { + UpdateInstanceRequest input = UpdateInstanceRequest.of("my-instance"); + + Exception actualError = null; + + try { + input.toProto(ProjectName.of("my-project")); + } catch (Exception e) { + actualError = e; + } + + assertThat(actualError).isInstanceOf(IllegalStateException.class); + } + + @Test + public void testDisplayName() { + UpdateInstanceRequest input = UpdateInstanceRequest.of("my-instance") + .setDisplayName("my display name"); + + PartialUpdateInstanceRequest actual = input.toProto(ProjectName.of("my-project")); + + PartialUpdateInstanceRequest expected = PartialUpdateInstanceRequest.newBuilder() + .setUpdateMask( + FieldMask.newBuilder() + .addPaths("display_name") + ) + .setInstance( + Instance.newBuilder() + .setName("projects/my-project/instances/my-instance") + .setDisplayName("my display name") + ) + .build(); + + assertThat(actual).isEqualTo(expected); + } + + @Test + public void testLabels() { + UpdateInstanceRequest input = UpdateInstanceRequest.of("my-instance") + .setLabels(ImmutableMap.of( + "label1", "value1", + "label2", "value2" + )); + + PartialUpdateInstanceRequest actual = input.toProto(ProjectName.of("my-project")); + + PartialUpdateInstanceRequest expected = PartialUpdateInstanceRequest.newBuilder() + .setUpdateMask( + FieldMask.newBuilder() + .addPaths("labels") + ) + .setInstance( + Instance.newBuilder() + .setName("projects/my-project/instances/my-instance") + .putLabels("label1", "value1") + .putLabels("label2", "value2") + ) + .build(); + + assertThat(actual).isEqualTo(expected); + } + + @Test + public void testType() { + UpdateInstanceRequest input = UpdateInstanceRequest.of("my-instance") + .setProductionType(); + + PartialUpdateInstanceRequest actual = input.toProto(ProjectName.of("my-project")); + + PartialUpdateInstanceRequest expected = PartialUpdateInstanceRequest.newBuilder() + .setUpdateMask( + FieldMask.newBuilder() + .addPaths("type") + ) + .setInstance( + Instance.newBuilder() + .setName("projects/my-project/instances/my-instance") + .setType(Type.PRODUCTION) + ) + .build(); + + assertThat(actual).isEqualTo(expected); + } +}