Skip to content

Commit 427b155

Browse files
Bigtable: add CRUD for instances (#3569)
1 parent b9b4aff commit 427b155

File tree

11 files changed

+1514
-10
lines changed

11 files changed

+1514
-10
lines changed

google-cloud-clients/google-cloud-bigtable-admin/pom.xml

+6
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@
6161
<type>test-jar</type>
6262
<scope>test</scope>
6363
</dependency>
64+
<dependency>
65+
<groupId>com.google.api</groupId>
66+
<artifactId>gax</artifactId>
67+
<classifier>testlib</classifier>
68+
<scope>test</scope>
69+
</dependency>
6470
<dependency>
6571
<groupId>junit</groupId>
6672
<artifactId>junit</artifactId>

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

+327-8
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.DeleteInstanceRequest;
22+
import com.google.bigtable.admin.v2.GetInstanceRequest;
23+
import com.google.bigtable.admin.v2.InstanceName;
24+
import com.google.bigtable.admin.v2.ListInstancesRequest;
25+
import com.google.bigtable.admin.v2.ListInstancesResponse;
26+
import com.google.bigtable.admin.v2.LocationName;
1827
import com.google.bigtable.admin.v2.ProjectName;
28+
import com.google.cloud.bigtable.admin.v2.models.CreateInstanceRequest;
29+
import com.google.cloud.bigtable.admin.v2.models.Instance;
30+
import com.google.cloud.bigtable.admin.v2.models.PartialListInstancesException;
31+
import com.google.cloud.bigtable.admin.v2.models.UpdateInstanceRequest;
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
/**
@@ -29,12 +50,9 @@
2950
* <pre>{@code
3051
* try(BigtableInstanceAdminClient client = BigtableInstanceAdminClient.create(ProjectName.of("my-project"))) {
3152
* CreateInstanceRequest request = CreateInstanceRequest.of("my-instance")
32-
* .addFamily("cf1")
33-
* .addFamily("cf2", GCRULES.maxVersions(10))
34-
* .addSplit(ByteString.copyFromUtf8("b"))
35-
* .addSplit(ByteString.copyFromUtf8("q"));
53+
* .addCluster("my-cluster", "us-east1-c", 3, StorageType.SSD);
3654
*
37-
* client.createInstance(request);
55+
* Instance instance = client.createInstance(request);
3856
* }
3957
* }</pre>
4058
*
@@ -48,7 +66,7 @@
4866
*
4967
* <pre>{@code
5068
* BigtableInstanceAdminSettings settings = BigtableInstanceAdminSettings.newBuilder()
51-
* .setProjectName(ProjectName.of("[PROJECT]"))
69+
* .setProjectName(ProjectName.of("my-project"))
5270
* .setCredentialsProvider(FixedCredentialsProvider.create(myCredentials))
5371
* .build();
5472
*
@@ -59,7 +77,7 @@
5977
*
6078
* <pre>{@code
6179
* BigtableInstanceAdminSettings settings = BigtableInstanceAdminSettings.newBuilder()
62-
* .setProjectName(ProjectName.of("[PROJECT]"))
80+
* .setProjectName(ProjectName.of("my-project"))
6381
* .setEndpoint(myEndpoint)
6482
* .build();
6583
*
@@ -82,7 +100,7 @@ public static BigtableInstanceAdminClient create(@Nonnull BigtableInstanceAdminS
82100
return create(settings.getProjectName(), settings.getStubSettings().createStub());
83101
}
84102

85-
/** Constructs an instance of BigtableInstanceAdminClient with the given Projectname and stub. */
103+
/** Constructs an instance of BigtableInstanceAdminClient with the given ProjectName and stub. */
86104
public static BigtableInstanceAdminClient create(@Nonnull ProjectName projectName,
87105
@Nonnull BigtableInstanceAdminStub stub) {
88106
return new BigtableInstanceAdminClient(projectName, stub);
@@ -96,6 +114,7 @@ private BigtableInstanceAdminClient(
96114
}
97115

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

0 commit comments

Comments
 (0)