Skip to content

Commit 704e624

Browse files
WIP: Bigtable: add CRUD for clusters
Another step in continuing the work in: https://github.com/spollapally/google-cloud-java/tree/instance-admin-client/google-cloud-clients/google-cloud-bigtable Please note that this PR contains a copy of CreateClusterRequest from googleapis#3569.
1 parent 4eca7a4 commit 704e624

File tree

4 files changed

+581
-0
lines changed

4 files changed

+581
-0
lines changed

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

+321
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,30 @@
1515
*/
1616
package com.google.cloud.bigtable.admin.v2;
1717

18+
import com.google.api.core.ApiFunction;
19+
import com.google.api.core.ApiFuture;
20+
import com.google.api.core.ApiFutures;
21+
import com.google.bigtable.admin.v2.ClusterName;
22+
import com.google.bigtable.admin.v2.DeleteClusterRequest;
23+
import com.google.bigtable.admin.v2.GetClusterRequest;
24+
import com.google.bigtable.admin.v2.InstanceName;
25+
import com.google.bigtable.admin.v2.ListClustersRequest;
26+
import com.google.bigtable.admin.v2.ListClustersResponse;
27+
import com.google.bigtable.admin.v2.LocationName;
1828
import com.google.bigtable.admin.v2.ProjectName;
29+
import com.google.cloud.bigtable.admin.v2.models.Cluster;
30+
import com.google.cloud.bigtable.admin.v2.models.CreateClusterRequest;
31+
import com.google.cloud.bigtable.admin.v2.models.PartialListClustersException;
1932
import com.google.cloud.bigtable.admin.v2.stub.BigtableInstanceAdminStub;
33+
import com.google.common.base.Verify;
34+
import com.google.common.collect.ImmutableList;
35+
import com.google.common.util.concurrent.Futures;
36+
import com.google.common.util.concurrent.MoreExecutors;
37+
import com.google.common.util.concurrent.UncheckedExecutionException;
38+
import com.google.protobuf.Empty;
2039
import java.io.IOException;
40+
import java.util.List;
41+
import java.util.Objects;
2142
import javax.annotation.Nonnull;
2243

2344
/**
@@ -105,4 +126,304 @@ public ProjectName getProjectName() {
105126
public void close() {
106127
stub.close();
107128
}
129+
130+
/**
131+
* Creates a new cluster in the specified instance.
132+
*
133+
* <p>Sample code:
134+
*
135+
* <pre>{@code
136+
* Cluster cluster = client.createCluster(
137+
* CreateClusterRequest.of("my-instance", "my-new-cluster")
138+
* .setZone("us-east1-c")
139+
* .setServeNodes(3)
140+
* .setStorageType(StorageType.SSD)
141+
* );
142+
* }</pre>
143+
*/
144+
@SuppressWarnings("WeakerAccess")
145+
public Cluster createCluster(CreateClusterRequest request) {
146+
return awaitFuture(createClusterAsync(request));
147+
}
148+
149+
/**
150+
* Asynchronously creates a new cluster in the specified instance.
151+
*
152+
* <p>Sample code:
153+
*
154+
* <pre>{@code
155+
* ApiFuture<Cluster> clusterFuture = client.createClusterAsync(
156+
* CreateClusterRequest.of("my-instance", "my-new-cluster")
157+
* .setZone("us-east1-c")
158+
* .setServeNodes(3)
159+
* .setStorageType(StorageType.SSD)
160+
* );
161+
*
162+
* Cluster cluste = clusterFuture.get();
163+
* }</pre>
164+
*/
165+
@SuppressWarnings("WeakerAccess")
166+
public ApiFuture<Cluster> createClusterAsync(CreateClusterRequest request) {
167+
return ApiFutures.transform(
168+
stub.createClusterOperationCallable().futureCall(request.toProto(projectName)),
169+
new ApiFunction<com.google.bigtable.admin.v2.Cluster, Cluster>() {
170+
@Override
171+
public Cluster apply(com.google.bigtable.admin.v2.Cluster proto) {
172+
return Cluster.fromProto(proto);
173+
}
174+
},
175+
MoreExecutors.directExecutor()
176+
);
177+
}
178+
179+
/**
180+
* Get the cluster representation by ID.
181+
*
182+
* <p>Sample code:
183+
*
184+
* <pre>{@code
185+
* Cluster cluster = client.getCluster("my-instance", "my-cluster");
186+
* }</pre>
187+
*/
188+
@SuppressWarnings("WeakerAccess")
189+
public Cluster getCluster(String instanceId, String clusterId) {
190+
return awaitFuture(getClusterAsync(instanceId, clusterId));
191+
}
192+
193+
/**
194+
* Asynchronously gets the cluster representation by ID.
195+
*
196+
* <p>Sample code:
197+
*
198+
* <pre>{@code
199+
* ApiFuture<Cluster> clusterFuture = client.getClusterAsync("my-instance", "my-cluster");
200+
* Cluster cluster = clusterFuture.get();
201+
* }</pre>
202+
*/
203+
@SuppressWarnings("WeakerAccess")
204+
public ApiFuture<Cluster> getClusterAsync(String instanceId, String clusterId) {
205+
ClusterName name = ClusterName.of(projectName.getProject(), instanceId, clusterId);
206+
207+
GetClusterRequest request = GetClusterRequest.newBuilder()
208+
.setName(name.toString())
209+
.build();
210+
211+
return ApiFutures.transform(
212+
stub.getClusterCallable().futureCall(request),
213+
new ApiFunction<com.google.bigtable.admin.v2.Cluster, Cluster>() {
214+
@Override
215+
public Cluster apply(com.google.bigtable.admin.v2.Cluster proto) {
216+
return Cluster.fromProto(proto);
217+
}
218+
},
219+
MoreExecutors.directExecutor()
220+
);
221+
}
222+
223+
/**
224+
* Lists all clusters in the specified instance.
225+
*
226+
* <p>This method will throw a {@link PartialListClustersException} when any zone is
227+
* unavailable. If partial listing are ok, the exception can be caught and inspected.
228+
*
229+
* <p>Sample code:
230+
*
231+
* <pre>{@code
232+
* try {
233+
* List<Cluster> clusters = clister.listClusters("my-instance");
234+
* } catch (PartialListClustersException e) {
235+
* System.out.println("The following zones are unavailable: " + e.getUnavailableZones());
236+
* System.out.println("But the following clusters are reachable: " + e.getClusters())
237+
* }
238+
* }</pre>
239+
*/
240+
@SuppressWarnings("WeakerAccess")
241+
public List<Cluster> listClusters(String instanceId) {
242+
return awaitFuture(listClustersAsync(instanceId));
243+
}
244+
245+
/**
246+
* Asynchronously lists all clusters in the specified instance.
247+
*
248+
* <p>This method will throw a {@link PartialListClustersException} when any zone is
249+
* unavailable. If partial listing are ok, the exception can be caught and inspected.
250+
*
251+
* <p>Sample code:
252+
*
253+
* <pre>{@code
254+
* ApiFuture<Cluster> clustersFuture = client.listClustersAsync("my-instance");
255+
*
256+
* ApiFutures.addCallback(clustersFuture, new ApiFutureCallback<List<Cluster>>() {
257+
* public void onFailure(Throwable t) {
258+
* if (t instanceof PartialListClustersException) {
259+
* PartialListClustersException partialError = (PartialListClustersException)t;
260+
* System.out.println("The following zones are unavailable: " + partialError.getUnavailableZones());
261+
* System.out.println("But the following clusters are reachable: " + partialError.getClusters());
262+
* } else {
263+
* t.printStackTrace();
264+
* }
265+
* }
266+
*
267+
* public void onSuccess(List<Cluster> result) {
268+
* System.out.println("Found a complete set of instances: " + result);
269+
* }
270+
* }, MoreExecutors.directExecutor());
271+
* }</pre>
272+
*/
273+
@SuppressWarnings("WeakerAccess")
274+
public ApiFuture<List<Cluster>> listClustersAsync(String instanceId) {
275+
InstanceName name = InstanceName.of(projectName.getProject(), instanceId);
276+
ListClustersRequest request = ListClustersRequest.newBuilder()
277+
.setParent(name.toString())
278+
.build();
279+
280+
return ApiFutures.transform(
281+
stub.listClustersCallable().futureCall(request),
282+
new ApiFunction<ListClustersResponse, List<Cluster>>() {
283+
@Override
284+
public List<Cluster> apply(ListClustersResponse proto) {
285+
// NOTE: pagination is intentionally ignored. The server does not implement it and never
286+
// will.
287+
Verify.verify(proto.getNextPageToken().isEmpty(),
288+
"Server returned an unexpected paginated response");
289+
290+
ImmutableList.Builder<Cluster> clusters = ImmutableList.builder();
291+
for (com.google.bigtable.admin.v2.Cluster cluster : proto.getClustersList()) {
292+
clusters.add(Cluster.fromProto(cluster));
293+
}
294+
295+
ImmutableList.Builder<String> failedZones = ImmutableList.builder();
296+
for (String locationStr : proto.getFailedLocationsList()) {
297+
LocationName fullLocation = Objects.requireNonNull(LocationName.parse(locationStr));
298+
failedZones.add(fullLocation.getLocation());
299+
}
300+
301+
if (!failedZones.build().isEmpty()) {
302+
throw new PartialListClustersException(failedZones.build(), clusters.build());
303+
}
304+
305+
return clusters.build();
306+
}
307+
},
308+
MoreExecutors.directExecutor()
309+
);
310+
}
311+
312+
/**
313+
* Resizes the cluster's node count. Please note that only clusters that belong to a PRODUCTION
314+
* instance can be resized.
315+
*
316+
* <p>Sample code:
317+
*
318+
* <pre>{@code
319+
* Cluster cluster = clister.resizeCluster("my-instance", "my-cluster", 30);
320+
* }</pre>
321+
*/
322+
@SuppressWarnings("WeakerAccess")
323+
public Cluster resizeCluster(String instanceId, String clusterId, int numServeNodes) {
324+
return awaitFuture(resizeClusterAsync(instanceId, clusterId, numServeNodes));
325+
}
326+
327+
/**
328+
* Asynchronously resizes the cluster's node count. Please note that only clusters that belong to
329+
* a PRODUCTION instance can be resized.
330+
*
331+
* <pre>{@code
332+
* ApiFuture<Cluster> clusterFuture = clister.resizeCluster("my-instance", "my-cluster", 30);
333+
* Cluster cluster = clusterFuture.get();
334+
* }</pre>
335+
*/
336+
@SuppressWarnings("WeakerAccess")
337+
public ApiFuture<Cluster> resizeClusterAsync(String instanceId, String clusterId,
338+
int numServeNodes) {
339+
340+
ClusterName name = ClusterName.of(projectName.getProject(), instanceId, clusterId);
341+
342+
com.google.bigtable.admin.v2.Cluster request = com.google.bigtable.admin.v2.Cluster.newBuilder()
343+
.setName(name.toString())
344+
.setServeNodes(numServeNodes)
345+
.build();
346+
347+
return ApiFutures.transform(
348+
stub.updateClusterOperationCallable().futureCall(request),
349+
new ApiFunction<com.google.bigtable.admin.v2.Cluster, Cluster>() {
350+
@Override
351+
public Cluster apply(com.google.bigtable.admin.v2.Cluster proto) {
352+
return Cluster.fromProto(proto);
353+
}
354+
},
355+
MoreExecutors.directExecutor()
356+
);
357+
}
358+
359+
/**
360+
* Deletes the specified cluster. Please note that an instance must have at least 1 cluster. To
361+
* remove the last cluster, please use {@link BigtableInstanceAdminClient#deleteInstance(String)}.
362+
*
363+
* <p>Sample code:
364+
*
365+
* <pre>{@code
366+
* client.deleteCluster("my-instance", "my-cluster");
367+
* }</pre>
368+
*/
369+
@SuppressWarnings("WeakerAccess")
370+
public void deleteCluster(String instanceId, String clusterId) {
371+
awaitFuture(deleteClusterAsync(instanceId, clusterId));
372+
}
373+
374+
/**
375+
* Asynchronously deletes the specified cluster. Please note that an instance must have at least 1
376+
* cluster. To remove the last cluster, please use {@link BigtableInstanceAdminClient#deleteInstanceAsync(String)}.
377+
*
378+
* <p>Sample code:
379+
*
380+
* <pre>{@code
381+
* ApiFuture<Void> future = client.deleteClusterAsync("my-instance", "my-cluster");
382+
* future.get();
383+
* }</pre>
384+
*/
385+
@SuppressWarnings("WeakerAccess")
386+
public ApiFuture<Void> deleteClusterAsync(String instanceId, String clusterId) {
387+
ClusterName name = ClusterName.of(projectName.getProject(), instanceId, clusterId);
388+
389+
DeleteClusterRequest request = DeleteClusterRequest.newBuilder()
390+
.setName(name.toString())
391+
.build();
392+
393+
return ApiFutures.transform(
394+
stub.deleteClusterCallable().futureCall(request),
395+
new ApiFunction<Empty, Void>() {
396+
@Override
397+
public Void apply(Empty input) {
398+
return null;
399+
}
400+
},
401+
MoreExecutors.directExecutor()
402+
);
403+
}
404+
405+
/**
406+
* Awaits the result of a future, taking care to propagate errors while maintaining the call site
407+
* in a suppressed exception. This allows semantic errors to be caught across threads, while
408+
* preserving the call site in the error. The caller's stacktrace will be made available as a
409+
* suppressed exception.
410+
*/
411+
// TODO(igorbernstein2): try to move this into gax
412+
private <T> T awaitFuture(ApiFuture<T> future) {
413+
RuntimeException error;
414+
try {
415+
return Futures.getUnchecked(future);
416+
} catch (UncheckedExecutionException e) {
417+
if (e.getCause() instanceof RuntimeException) {
418+
error = (RuntimeException) e.getCause();
419+
} else {
420+
error = e;
421+
}
422+
} catch (RuntimeException e) {
423+
error = e;
424+
}
425+
// Add the caller's stack as a suppressed exception
426+
error.addSuppressed(new RuntimeException("Encountered error while awaiting future"));
427+
throw error;
428+
}
108429
}

0 commit comments

Comments
 (0)