From 68761a7de17489c02362e079ca766ee06da5e247 Mon Sep 17 00:00:00 2001 From: Mira Leung Date: Wed, 17 Feb 2021 13:02:58 -0800 Subject: [PATCH] feat(operations): Add WaitOperation API surface [gax-java] (#1284) * fix(lro): Add Operation name to get, list requests * fix: add name header to {Cancel,Delete}Operation * feat(operations): Add WaitOperation API surface * fix: update OperationsClient comments * fix: Add override annotations to GrpcOperationsStub * fix: update comment in OperationsClient * fix: comment cleanup * fix: MockOperationsImpl error message --- .../google/longrunning/OperationsClient.java | 57 ++++++++++++++++++- .../longrunning/OperationsSettings.java | 10 ++++ .../longrunning/stub/GrpcOperationsStub.java | 37 ++++++++++++ .../longrunning/stub/OperationsStub.java | 5 ++ .../stub/OperationsStubSettings.java | 25 +++++++- .../longrunning/MockOperationsImpl.java | 21 +++++++ .../longrunning/OperationsClientTest.java | 43 ++++++++++++++ 7 files changed, 196 insertions(+), 2 deletions(-) diff --git a/gax-grpc/src/main/java/com/google/longrunning/OperationsClient.java b/gax-grpc/src/main/java/com/google/longrunning/OperationsClient.java index c1b826e78..c1210d7db 100644 --- a/gax-grpc/src/main/java/com/google/longrunning/OperationsClient.java +++ b/gax-grpc/src/main/java/com/google/longrunning/OperationsClient.java @@ -192,7 +192,7 @@ public final Operation getOperation(String name) { * } * * - * @param request The request object containing all of the parameters for the API call. + * @param request the request object containing all of the parameters for the API call * @throws com.google.api.gax.rpc.ApiException if the remote call fails */ private final Operation getOperation(GetOperationRequest request) { @@ -502,6 +502,61 @@ public final UnaryCallable deleteOperationCallabl return stub.deleteOperationCallable(); } + /** + * Waits until the specified long-running operation is done or reaches at most a specified + * timeout, returning the latest state. If the operation is already done, the latest state is + * immediately returned. If the timeout specified is greater than the default HTTP/RPC timeout, + * the HTTP/RPC timeout is used. If the server does not support this method, it returns + * `google.rpc.Code.UNIMPLEMENTED`. Note that this method is on a best-effort basis. It may return + * the latest state before the specified timeout (including immediately), meaning even an + * immediate response is no guarantee that the operation is done. + * + *

Sample code: + * + *


+   * try (OperationsClient operationsClient = OperationsClient.create()) {
+   *   String name = "";
+   *   WaitOperationRequest request = WaitOperationRequest.newBuilder()
+   *     .setName(name)
+   *     .setTimeout(Duration.ofMillis(100))
+   *     .build();
+   *   Operation response = operationsClient.waitOperation(request);
+   * }
+   * 
+ * + * @param request the request object containing all of the parameters for the API call. + * @throws com.google.api.gax.rpc.ApiException if the remote call fails + */ + public final Operation waitOperation(WaitOperationRequest request) { + return waitOperationCallable().call(request); + } + + /** + * Waits until the specified long-running operation is done or reaches at most a specified + * timeout, returning the latest state. If the operation is already done, the latest state is + * immediately returned. If the timeout specified is greater than the default HTTP/RPC timeout, + * the HTTP/RPC timeout is used. If the server does not support this method, it returns + * `google.rpc.Code.UNIMPLEMENTED`. Note that this method is on a best-effort basis. It may return + * the latest state before the specified timeout (including immediately), meaning even an + * immediate response is no guarantee that the operation is done. + * + *

Sample code: + * + *


+   * try (OperationsClient operationsClient = OperationsClient.create()) {
+   *   String name = "";
+   *   WaitOperationRequest request = WaitOperationRequest.newBuilder()
+   *     .setName(name)
+   *     .setTimeout(Duration.ofMillis(100))
+   *     .build();
+   *   ApiFuture<Operation> future = operationsClient.waitOperationCallable().futureCall(request);
+   * }
+   * 
+ */ + public final UnaryCallable waitOperationCallable() { + return stub.waitOperationCallable(); + } + @Override public final void close() { stub.close(); diff --git a/gax-grpc/src/main/java/com/google/longrunning/OperationsSettings.java b/gax-grpc/src/main/java/com/google/longrunning/OperationsSettings.java index c1cc54773..507711f47 100644 --- a/gax-grpc/src/main/java/com/google/longrunning/OperationsSettings.java +++ b/gax-grpc/src/main/java/com/google/longrunning/OperationsSettings.java @@ -68,6 +68,11 @@ public UnaryCallSettings deleteOperationSettings( return ((OperationsStubSettings) getStubSettings()).deleteOperationSettings(); } + /** Returns the object with the settings used for calls to waitOperation. */ + public UnaryCallSettings waitOperationSettings() { + return ((OperationsStubSettings) getStubSettings()).waitOperationSettings(); + } + public static final OperationsSettings create(OperationsStubSettings stub) throws IOException { return new OperationsSettings.Builder(stub.toBuilder()).build(); } @@ -166,6 +171,11 @@ public UnaryCallSettings.Builder deleteOperationS return getStubSettingsBuilder().deleteOperationSettings(); } + /** Returns the builder for the settings used for calls to deleteOperation. */ + public UnaryCallSettings.Builder waitOperationSettings() { + return getStubSettingsBuilder().waitOperationSettings(); + } + @Override public OperationsSettings build() throws IOException { return new OperationsSettings(this); diff --git a/gax-grpc/src/main/java/com/google/longrunning/stub/GrpcOperationsStub.java b/gax-grpc/src/main/java/com/google/longrunning/stub/GrpcOperationsStub.java index 5388109b8..e44c5b003 100644 --- a/gax-grpc/src/main/java/com/google/longrunning/stub/GrpcOperationsStub.java +++ b/gax-grpc/src/main/java/com/google/longrunning/stub/GrpcOperationsStub.java @@ -46,6 +46,7 @@ import com.google.longrunning.ListOperationsRequest; import com.google.longrunning.ListOperationsResponse; import com.google.longrunning.Operation; +import com.google.longrunning.WaitOperationRequest; import com.google.protobuf.Empty; import io.grpc.MethodDescriptor; import io.grpc.protobuf.ProtoUtils; @@ -97,6 +98,15 @@ public class GrpcOperationsStub extends OperationsStub { ProtoUtils.marshaller(DeleteOperationRequest.getDefaultInstance())) .setResponseMarshaller(ProtoUtils.marshaller(Empty.getDefaultInstance())) .build(); + private static final MethodDescriptor + waitOperationMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName("google.longrunning.Operations/WaitOperation") + .setRequestMarshaller( + ProtoUtils.marshaller(WaitOperationRequest.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Operation.getDefaultInstance())) + .build(); private final BackgroundResource backgroundResources; @@ -106,6 +116,7 @@ public class GrpcOperationsStub extends OperationsStub { listOperationsPagedCallable; private final UnaryCallable cancelOperationCallable; private final UnaryCallable deleteOperationCallable; + private final UnaryCallable waitOperationCallable; private final GrpcStubCallableFactory callableFactory; @@ -199,6 +210,19 @@ public Map extract(DeleteOperationRequest request) { } }) .build(); + GrpcCallSettings waitOperationTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(waitOperationMethodDescriptor) + .setParamsExtractor( + new RequestParamsExtractor() { + @Override + public Map extract(WaitOperationRequest request) { + ImmutableMap.Builder params = ImmutableMap.builder(); + params.put("name", String.valueOf(request.getName())); + return params.build(); + } + }) + .build(); this.getOperationCallable = callableFactory.createUnaryCallable( @@ -215,31 +239,44 @@ public Map extract(DeleteOperationRequest request) { this.deleteOperationCallable = callableFactory.createUnaryCallable( deleteOperationTransportSettings, settings.deleteOperationSettings(), clientContext); + this.waitOperationCallable = + callableFactory.createUnaryCallable( + waitOperationTransportSettings, settings.waitOperationSettings(), clientContext); backgroundResources = new BackgroundResourceAggregation(clientContext.getBackgroundResources()); } + @Override public UnaryCallable getOperationCallable() { return getOperationCallable; } + @Override public UnaryCallable listOperationsPagedCallable() { return listOperationsPagedCallable; } + @Override public UnaryCallable listOperationsCallable() { return listOperationsCallable; } + @Override public UnaryCallable cancelOperationCallable() { return cancelOperationCallable; } + @Override public UnaryCallable deleteOperationCallable() { return deleteOperationCallable; } + @Override + public UnaryCallable waitOperationCallable() { + return waitOperationCallable; + } + @Override public final void close() { shutdown(); diff --git a/gax-grpc/src/main/java/com/google/longrunning/stub/OperationsStub.java b/gax-grpc/src/main/java/com/google/longrunning/stub/OperationsStub.java index db104738a..e0eefbd96 100644 --- a/gax-grpc/src/main/java/com/google/longrunning/stub/OperationsStub.java +++ b/gax-grpc/src/main/java/com/google/longrunning/stub/OperationsStub.java @@ -40,6 +40,7 @@ import com.google.longrunning.ListOperationsRequest; import com.google.longrunning.ListOperationsResponse; import com.google.longrunning.Operation; +import com.google.longrunning.WaitOperationRequest; import com.google.protobuf.Empty; /** @@ -71,6 +72,10 @@ public UnaryCallable deleteOperationCallable() { throw new UnsupportedOperationException("Not implemented: deleteOperationCallable()"); } + public UnaryCallable waitOperationCallable() { + throw new UnsupportedOperationException("Not implemented: waitOperationCallable()"); + } + @Override public abstract void close(); } diff --git a/gax-grpc/src/main/java/com/google/longrunning/stub/OperationsStubSettings.java b/gax-grpc/src/main/java/com/google/longrunning/stub/OperationsStubSettings.java index dce049842..1de1411bd 100644 --- a/gax-grpc/src/main/java/com/google/longrunning/stub/OperationsStubSettings.java +++ b/gax-grpc/src/main/java/com/google/longrunning/stub/OperationsStubSettings.java @@ -61,6 +61,7 @@ import com.google.longrunning.ListOperationsRequest; import com.google.longrunning.ListOperationsResponse; import com.google.longrunning.Operation; +import com.google.longrunning.WaitOperationRequest; import com.google.protobuf.Empty; import java.io.IOException; import org.threeten.bp.Duration; @@ -75,6 +76,7 @@ public class OperationsStubSettings extends StubSettings listOperationsSettings; private final UnaryCallSettings cancelOperationSettings; private final UnaryCallSettings deleteOperationSettings; + private final UnaryCallSettings waitOperationSettings; /** Returns the object with the settings used for calls to getOperation. */ public UnaryCallSettings getOperationSettings() { @@ -98,6 +100,11 @@ public UnaryCallSettings deleteOperationSettings( return deleteOperationSettings; } + /** Returns the object with the settings used for calls to waitOperation. */ + public UnaryCallSettings waitOperationSettings() { + return waitOperationSettings; + } + @BetaApi("A restructuring of stub classes is planned, so this may break in the future") public OperationsStub createStub() throws IOException { if (getTransportChannelProvider() @@ -151,6 +158,7 @@ protected OperationsStubSettings(Builder settingsBuilder) throws IOException { listOperationsSettings = settingsBuilder.listOperationsSettings().build(); cancelOperationSettings = settingsBuilder.cancelOperationSettings().build(); deleteOperationSettings = settingsBuilder.deleteOperationSettings().build(); + waitOperationSettings = settingsBuilder.waitOperationSettings().build(); } private static final PagedListDescriptor @@ -215,6 +223,7 @@ public static class Builder extends StubSettings.Builder cancelOperationSettings; private final UnaryCallSettings.Builder deleteOperationSettings; + private final UnaryCallSettings.Builder waitOperationSettings; private static final ImmutableMap> RETRYABLE_CODE_DEFINITIONS; @@ -265,6 +274,8 @@ protected Builder(ClientContext clientContext) { deleteOperationSettings = UnaryCallSettings.newUnaryCallSettingsBuilder(); + waitOperationSettings = UnaryCallSettings.newUnaryCallSettingsBuilder(); + unaryMethodSettingsBuilders = ImmutableList.>of( getOperationSettings, @@ -302,6 +313,11 @@ private static Builder initDefaults(Builder builder) { .setRetryableCodes(RETRYABLE_CODE_DEFINITIONS.get("idempotent")) .setRetrySettings(RETRY_PARAM_DEFINITIONS.get("default")); + builder + .waitOperationSettings() + .setRetryableCodes(RETRYABLE_CODE_DEFINITIONS.get("idempotent")) + .setRetrySettings(RETRY_PARAM_DEFINITIONS.get("default")); + return builder; } @@ -312,13 +328,15 @@ protected Builder(OperationsStubSettings settings) { listOperationsSettings = settings.listOperationsSettings.toBuilder(); cancelOperationSettings = settings.cancelOperationSettings.toBuilder(); deleteOperationSettings = settings.deleteOperationSettings.toBuilder(); + waitOperationSettings = settings.waitOperationSettings.toBuilder(); unaryMethodSettingsBuilders = ImmutableList.>of( getOperationSettings, listOperationsSettings, cancelOperationSettings, - deleteOperationSettings); + deleteOperationSettings, + waitOperationSettings); } /** @@ -358,6 +376,11 @@ public UnaryCallSettings.Builder deleteOperationS return deleteOperationSettings; } + /** Returns the builder for the settings used for calls to waitOperation. */ + public UnaryCallSettings.Builder waitOperationSettings() { + return waitOperationSettings; + } + @Override public OperationsStubSettings build() throws IOException { return new OperationsStubSettings(this); diff --git a/gax-grpc/src/test/java/com/google/longrunning/MockOperationsImpl.java b/gax-grpc/src/test/java/com/google/longrunning/MockOperationsImpl.java index 27c40f220..f2b6eef03 100644 --- a/gax-grpc/src/test/java/com/google/longrunning/MockOperationsImpl.java +++ b/gax-grpc/src/test/java/com/google/longrunning/MockOperationsImpl.java @@ -130,4 +130,25 @@ public void cancelOperation( responseObserver.onError(new IllegalArgumentException("Unrecognized response type")); } } + + @Override + public void waitOperation( + WaitOperationRequest request, StreamObserver responseObserver) { + Object response = responses.remove(); + if (response instanceof Operation) { + requests.add(request); + responseObserver.onNext((Operation) response); + responseObserver.onCompleted(); + } else if (response instanceof Exception) { + responseObserver.onError((Exception) response); + } else { + responseObserver.onError( + new IllegalArgumentException( + String.format( + "Unrecognized response type %s, expected %s or %s", + response.getClass().getName(), + Operation.class.getName(), + Exception.class.getName()))); + } + } } diff --git a/gax-grpc/src/test/java/com/google/longrunning/OperationsClientTest.java b/gax-grpc/src/test/java/com/google/longrunning/OperationsClientTest.java index ac41b8f66..aa335e91c 100644 --- a/gax-grpc/src/test/java/com/google/longrunning/OperationsClientTest.java +++ b/gax-grpc/src/test/java/com/google/longrunning/OperationsClientTest.java @@ -38,6 +38,7 @@ import com.google.api.gax.rpc.InvalidArgumentException; import com.google.common.collect.Lists; import com.google.protobuf.AbstractMessage; +import com.google.protobuf.Duration; import com.google.protobuf.Empty; import io.grpc.Status; import io.grpc.StatusRuntimeException; @@ -235,4 +236,46 @@ public void deleteOperationExceptionTest() throws Exception { // Expected exception } } + + @Test + @SuppressWarnings("all") + public void waitOperationTest() { + String name2 = "name2-1052831874"; + boolean done = true; + Operation expectedResponse = Operation.newBuilder().setName(name2).setDone(done).build(); + mockOperations.addResponse(expectedResponse); + + String name = "name3373707"; + Duration timeout = Duration.newBuilder().setSeconds(5).build(); + WaitOperationRequest request = + WaitOperationRequest.newBuilder().setName(name).setTimeout(timeout).build(); + + Operation actualResponse = client.waitOperation(request); + Assert.assertEquals(expectedResponse, actualResponse); + + List actualRequests = mockOperations.getRequests(); + Assert.assertEquals(1, actualRequests.size()); + WaitOperationRequest actualRequest = (WaitOperationRequest) actualRequests.get(0); + + Assert.assertEquals(name, actualRequest.getName()); + } + + @Test + @SuppressWarnings("all") + public void waitOperationExceptionTest() throws Exception { + StatusRuntimeException exception = new StatusRuntimeException(Status.INVALID_ARGUMENT); + mockOperations.addException(exception); + + try { + String name = "name3373707"; + Duration timeout = Duration.newBuilder().setSeconds(5).build(); + WaitOperationRequest request = + WaitOperationRequest.newBuilder().setName(name).setTimeout(timeout).build(); + + client.waitOperation(request); + Assert.fail("No exception raised"); + } catch (InvalidArgumentException e) { + // Expected exception + } + } }