Skip to content

Commit e3eedeb

Browse files
Bigtable: add CRUD for clusters (#3612)
1 parent 8bcc89b commit e3eedeb

File tree

6 files changed

+744
-7
lines changed

6 files changed

+744
-7
lines changed

google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClient.java

+284-3
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,23 @@
1818
import com.google.api.core.ApiFunction;
1919
import com.google.api.core.ApiFuture;
2020
import com.google.api.core.ApiFutures;
21+
import com.google.bigtable.admin.v2.ClusterName;
22+
import com.google.bigtable.admin.v2.DeleteClusterRequest;
2123
import com.google.bigtable.admin.v2.DeleteInstanceRequest;
24+
import com.google.bigtable.admin.v2.GetClusterRequest;
2225
import com.google.bigtable.admin.v2.GetInstanceRequest;
2326
import com.google.bigtable.admin.v2.InstanceName;
27+
import com.google.bigtable.admin.v2.ListClustersRequest;
28+
import com.google.bigtable.admin.v2.ListClustersResponse;
2429
import com.google.bigtable.admin.v2.ListInstancesRequest;
2530
import com.google.bigtable.admin.v2.ListInstancesResponse;
2631
import com.google.bigtable.admin.v2.LocationName;
2732
import com.google.bigtable.admin.v2.ProjectName;
33+
import com.google.cloud.bigtable.admin.v2.models.Cluster;
34+
import com.google.cloud.bigtable.admin.v2.models.CreateClusterRequest;
2835
import com.google.cloud.bigtable.admin.v2.models.CreateInstanceRequest;
2936
import com.google.cloud.bigtable.admin.v2.models.Instance;
37+
import com.google.cloud.bigtable.admin.v2.models.PartialListClustersException;
3038
import com.google.cloud.bigtable.admin.v2.models.PartialListInstancesException;
3139
import com.google.cloud.bigtable.admin.v2.models.UpdateInstanceRequest;
3240
import com.google.cloud.bigtable.admin.v2.stub.BigtableInstanceAdminStub;
@@ -397,6 +405,282 @@ public Void apply(Empty input) {
397405
);
398406
}
399407

408+
/**
409+
* Creates a new cluster in the specified instance.
410+
*
411+
* <p>Sample code:
412+
*
413+
* <pre>{@code
414+
* Cluster cluster = client.createCluster(
415+
* CreateClusterRequest.of("my-instance", "my-new-cluster")
416+
* .setZone("us-east1-c")
417+
* .setServeNodes(3)
418+
* .setStorageType(StorageType.SSD)
419+
* );
420+
* }</pre>
421+
*/
422+
@SuppressWarnings("WeakerAccess")
423+
public Cluster createCluster(CreateClusterRequest request) {
424+
return awaitFuture(createClusterAsync(request));
425+
}
426+
427+
/**
428+
* Asynchronously creates a new cluster in the specified instance.
429+
*
430+
* <p>Sample code:
431+
*
432+
* <pre>{@code
433+
* ApiFuture<Cluster> clusterFuture = client.createClusterAsync(
434+
* CreateClusterRequest.of("my-instance", "my-new-cluster")
435+
* .setZone("us-east1-c")
436+
* .setServeNodes(3)
437+
* .setStorageType(StorageType.SSD)
438+
* );
439+
*
440+
* Cluster cluster = clusterFuture.get();
441+
* }</pre>
442+
*/
443+
@SuppressWarnings("WeakerAccess")
444+
public ApiFuture<Cluster> createClusterAsync(CreateClusterRequest request) {
445+
return ApiFutures.transform(
446+
stub.createClusterOperationCallable().futureCall(request.toProto(projectName)),
447+
new ApiFunction<com.google.bigtable.admin.v2.Cluster, Cluster>() {
448+
@Override
449+
public Cluster apply(com.google.bigtable.admin.v2.Cluster proto) {
450+
return Cluster.fromProto(proto);
451+
}
452+
},
453+
MoreExecutors.directExecutor()
454+
);
455+
}
456+
457+
/**
458+
* Get the cluster representation by ID.
459+
*
460+
* <p>Sample code:
461+
*
462+
* <pre>{@code
463+
* Cluster cluster = client.getCluster("my-instance", "my-cluster");
464+
* }</pre>
465+
*/
466+
@SuppressWarnings("WeakerAccess")
467+
public Cluster getCluster(String instanceId, String clusterId) {
468+
return awaitFuture(getClusterAsync(instanceId, clusterId));
469+
}
470+
471+
/**
472+
* Asynchronously gets the cluster representation by ID.
473+
*
474+
* <p>Sample code:
475+
*
476+
* <pre>{@code
477+
* ApiFuture<Cluster> clusterFuture = client.getClusterAsync("my-instance", "my-cluster");
478+
* Cluster cluster = clusterFuture.get();
479+
* }</pre>
480+
*/
481+
@SuppressWarnings("WeakerAccess")
482+
public ApiFuture<Cluster> getClusterAsync(String instanceId, String clusterId) {
483+
ClusterName name = ClusterName.of(projectName.getProject(), instanceId, clusterId);
484+
485+
GetClusterRequest request = GetClusterRequest.newBuilder()
486+
.setName(name.toString())
487+
.build();
488+
489+
return ApiFutures.transform(
490+
stub.getClusterCallable().futureCall(request),
491+
new ApiFunction<com.google.bigtable.admin.v2.Cluster, Cluster>() {
492+
@Override
493+
public Cluster apply(com.google.bigtable.admin.v2.Cluster proto) {
494+
return Cluster.fromProto(proto);
495+
}
496+
},
497+
MoreExecutors.directExecutor()
498+
);
499+
}
500+
501+
/**
502+
* Lists all clusters in the specified instance.
503+
*
504+
* <p>This method will throw a {@link PartialListClustersException} when any zone is
505+
* unavailable. If partial listing are ok, the exception can be caught and inspected.
506+
*
507+
* <p>Sample code:
508+
*
509+
* <pre>{@code
510+
* try {
511+
* List<Cluster> clusters = cluster.listClusters("my-instance");
512+
* } catch (PartialListClustersException e) {
513+
* System.out.println("The following zones are unavailable: " + e.getUnavailableZones());
514+
* System.out.println("But the following clusters are reachable: " + e.getClusters())
515+
* }
516+
* }</pre>
517+
*/
518+
@SuppressWarnings("WeakerAccess")
519+
public List<Cluster> listClusters(String instanceId) {
520+
return awaitFuture(listClustersAsync(instanceId));
521+
}
522+
523+
/**
524+
* Asynchronously lists all clusters in the specified instance.
525+
*
526+
* <p>This method will throw a {@link PartialListClustersException} when any zone is
527+
* unavailable. If partial listing are ok, the exception can be caught and inspected.
528+
*
529+
* <p>Sample code:
530+
*
531+
* <pre>{@code
532+
* ApiFuture<Cluster> clustersFuture = client.listClustersAsync("my-instance");
533+
*
534+
* ApiFutures.addCallback(clustersFuture, new ApiFutureCallback<List<Cluster>>() {
535+
* public void onFailure(Throwable t) {
536+
* if (t instanceof PartialListClustersException) {
537+
* PartialListClustersException partialError = (PartialListClustersException)t;
538+
* System.out.println("The following zones are unavailable: " + partialError.getUnavailableZones());
539+
* System.out.println("But the following clusters are reachable: " + partialError.getClusters());
540+
* } else {
541+
* t.printStackTrace();
542+
* }
543+
* }
544+
*
545+
* public void onSuccess(List<Cluster> result) {
546+
* System.out.println("Found a complete set of instances: " + result);
547+
* }
548+
* }, MoreExecutors.directExecutor());
549+
* }</pre>
550+
*/
551+
@SuppressWarnings("WeakerAccess")
552+
public ApiFuture<List<Cluster>> listClustersAsync(String instanceId) {
553+
InstanceName name = InstanceName.of(projectName.getProject(), instanceId);
554+
ListClustersRequest request = ListClustersRequest.newBuilder()
555+
.setParent(name.toString())
556+
.build();
557+
558+
return ApiFutures.transform(
559+
stub.listClustersCallable().futureCall(request),
560+
new ApiFunction<ListClustersResponse, List<Cluster>>() {
561+
@Override
562+
public List<Cluster> apply(ListClustersResponse proto) {
563+
// NOTE: serverside pagination is not and will not be implemented, so remaining pages
564+
// are not fetched. However, if that assumption turns out to be wrong, fail fast to
565+
// avoid returning partial data.
566+
Verify.verify(proto.getNextPageToken().isEmpty(),
567+
"Server returned an unexpected paginated response");
568+
569+
ImmutableList.Builder<Cluster> clusters = ImmutableList.builder();
570+
for (com.google.bigtable.admin.v2.Cluster cluster : proto.getClustersList()) {
571+
clusters.add(Cluster.fromProto(cluster));
572+
}
573+
574+
ImmutableList.Builder<String> failedZones = ImmutableList.builder();
575+
for (String locationStr : proto.getFailedLocationsList()) {
576+
LocationName fullLocation = Objects.requireNonNull(LocationName.parse(locationStr));
577+
failedZones.add(fullLocation.getLocation());
578+
}
579+
580+
if (!failedZones.build().isEmpty()) {
581+
throw new PartialListClustersException(failedZones.build(), clusters.build());
582+
}
583+
584+
return clusters.build();
585+
}
586+
},
587+
MoreExecutors.directExecutor()
588+
);
589+
}
590+
591+
/**
592+
* Resizes the cluster's node count. Please note that only clusters that belong to a PRODUCTION
593+
* instance can be resized.
594+
*
595+
* <p>Sample code:
596+
*
597+
* <pre>{@code
598+
* Cluster cluster = cluster.resizeCluster("my-instance", "my-cluster", 30);
599+
* }</pre>
600+
*/
601+
@SuppressWarnings("WeakerAccess")
602+
public Cluster resizeCluster(String instanceId, String clusterId, int numServeNodes) {
603+
return awaitFuture(resizeClusterAsync(instanceId, clusterId, numServeNodes));
604+
}
605+
606+
/**
607+
* Asynchronously resizes the cluster's node count. Please note that only clusters that belong to
608+
* a PRODUCTION instance can be resized.
609+
*
610+
* <pre>{@code
611+
* ApiFuture<Cluster> clusterFuture = cluster.resizeCluster("my-instance", "my-cluster", 30);
612+
* Cluster cluster = clusterFuture.get();
613+
* }</pre>
614+
*/
615+
@SuppressWarnings("WeakerAccess")
616+
public ApiFuture<Cluster> resizeClusterAsync(String instanceId, String clusterId,
617+
int numServeNodes) {
618+
619+
ClusterName name = ClusterName.of(projectName.getProject(), instanceId, clusterId);
620+
621+
com.google.bigtable.admin.v2.Cluster request = com.google.bigtable.admin.v2.Cluster.newBuilder()
622+
.setName(name.toString())
623+
.setServeNodes(numServeNodes)
624+
.build();
625+
626+
return ApiFutures.transform(
627+
stub.updateClusterOperationCallable().futureCall(request),
628+
new ApiFunction<com.google.bigtable.admin.v2.Cluster, Cluster>() {
629+
@Override
630+
public Cluster apply(com.google.bigtable.admin.v2.Cluster proto) {
631+
return Cluster.fromProto(proto);
632+
}
633+
},
634+
MoreExecutors.directExecutor()
635+
);
636+
}
637+
638+
/**
639+
* Deletes the specified cluster. Please note that an instance must have at least 1 cluster. To
640+
* remove the last cluster, please use {@link BigtableInstanceAdminClient#deleteInstance(String)}.
641+
*
642+
* <p>Sample code:
643+
*
644+
* <pre>{@code
645+
* client.deleteCluster("my-instance", "my-cluster");
646+
* }</pre>
647+
*/
648+
@SuppressWarnings("WeakerAccess")
649+
public void deleteCluster(String instanceId, String clusterId) {
650+
awaitFuture(deleteClusterAsync(instanceId, clusterId));
651+
}
652+
653+
/**
654+
* Asynchronously deletes the specified cluster. Please note that an instance must have at least 1
655+
* cluster. To remove the last cluster, please use {@link BigtableInstanceAdminClient#deleteInstanceAsync(String)}.
656+
*
657+
* <p>Sample code:
658+
*
659+
* <pre>{@code
660+
* ApiFuture<Void> future = client.deleteClusterAsync("my-instance", "my-cluster");
661+
* future.get();
662+
* }</pre>
663+
*/
664+
@SuppressWarnings("WeakerAccess")
665+
public ApiFuture<Void> deleteClusterAsync(String instanceId, String clusterId) {
666+
ClusterName name = ClusterName.of(projectName.getProject(), instanceId, clusterId);
667+
668+
DeleteClusterRequest request = DeleteClusterRequest.newBuilder()
669+
.setName(name.toString())
670+
.build();
671+
672+
return ApiFutures.transform(
673+
stub.deleteClusterCallable().futureCall(request),
674+
new ApiFunction<Empty, Void>() {
675+
@Override
676+
public Void apply(Empty input) {
677+
return null;
678+
}
679+
},
680+
MoreExecutors.directExecutor()
681+
);
682+
}
683+
400684
/**
401685
* Awaits the result of a future, taking care to propagate errors while maintaining the call site
402686
* in a suppressed exception. This allows semantic errors to be caught across threads, while
@@ -406,7 +690,6 @@ public Void apply(Empty input) {
406690
// TODO(igorbernstein2): try to move this into gax
407691
private <T> T awaitFuture(ApiFuture<T> future) {
408692
RuntimeException error;
409-
410693
try {
411694
return Futures.getUnchecked(future);
412695
} catch (UncheckedExecutionException e) {
@@ -418,10 +701,8 @@ private <T> T awaitFuture(ApiFuture<T> future) {
418701
} catch (RuntimeException e) {
419702
error = e;
420703
}
421-
422704
// Add the caller's stack as a suppressed exception
423705
error.addSuppressed(new RuntimeException("Encountered error while awaiting future"));
424-
425706
throw error;
426707
}
427708
}

0 commit comments

Comments
 (0)