diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallableFactory.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallableFactory.java index 33e2ff886e..b18200bd13 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallableFactory.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonCallableFactory.java @@ -30,7 +30,6 @@ package com.google.api.gax.httpjson; import com.google.api.core.InternalApi; -import com.google.api.core.ObsoleteApi; import com.google.api.gax.longrunning.OperationSnapshot; import com.google.api.gax.rpc.BatchingCallSettings; import com.google.api.gax.rpc.Callables; @@ -41,11 +40,15 @@ import com.google.api.gax.rpc.PagedCallSettings; import com.google.api.gax.rpc.ServerStreamingCallSettings; import com.google.api.gax.rpc.ServerStreamingCallable; +import com.google.api.gax.rpc.StatusCode; import com.google.api.gax.rpc.UnaryCallSettings; import com.google.api.gax.rpc.UnaryCallable; import com.google.api.gax.tracing.SpanName; +import com.google.api.gax.tracing.TracedBatchingCallable; +import com.google.api.gax.tracing.TracedServerStreamingCallable; import com.google.api.gax.tracing.TracedUnaryCallable; import com.google.common.base.Preconditions; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nonnull; @@ -59,8 +62,9 @@ public class HttpJsonCallableFactory { private HttpJsonCallableFactory() {} - private static UnaryCallable createDirectUnaryCallable( - HttpJsonCallSettings httpJsonCallSettings) { + private static UnaryCallable createUnaryCallable( + HttpJsonCallSettings httpJsonCallSettings, + Set retryableCodes) { UnaryCallable callable = new HttpJsonDirectCallable<>( httpJsonCallSettings.getMethodDescriptor(), httpJsonCallSettings.getTypeRegistry()); @@ -70,33 +74,7 @@ private static UnaryCallable createDi new HttpJsonUnaryRequestParamCallable<>( callable, httpJsonCallSettings.getParamsExtractor()); } - return callable; - } - - /** Create httpJson UnaryCallable with request mutator. */ - static UnaryCallable createUnaryCallable( - UnaryCallable innerCallable, - UnaryCallSettings callSettings, - HttpJsonCallSettings httpJsonCallSettings, - ClientContext clientContext) { - UnaryCallable callable = - new HttpJsonExceptionCallable<>(innerCallable, callSettings.getRetryableCodes()); - callable = - Callables.retrying( - callable, callSettings, clientContext, httpJsonCallSettings.getRequestMutator()); - return callable.withDefaultCallContext(clientContext.getDefaultCallContext()); - } - - /** Use {@link #createUnaryCallable createUnaryCallable} method instead. */ - @ObsoleteApi("Please use other httpJson UnaryCallable method instead") - static UnaryCallable createUnaryCallable( - UnaryCallable innerCallable, - UnaryCallSettings callSettings, - ClientContext clientContext) { - UnaryCallable callable = - new HttpJsonExceptionCallable<>(innerCallable, callSettings.getRetryableCodes()); - callable = Callables.retrying(callable, callSettings, clientContext); - return callable.withDefaultCallContext(clientContext.getDefaultCallContext()); + return new HttpJsonExceptionCallable<>(callable, retryableCodes); } /** @@ -111,12 +89,15 @@ public static UnaryCallable createBas HttpJsonCallSettings httpJsonCallSettings, UnaryCallSettings callSettings, ClientContext clientContext) { - UnaryCallable callable = createDirectUnaryCallable(httpJsonCallSettings); - callable = new HttpJsonExceptionCallable<>(callable, callSettings.getRetryableCodes()); - callable = - Callables.retrying( - callable, callSettings, clientContext, httpJsonCallSettings.getRequestMutator()); - + UnaryCallable callable = + createUnaryCallable(httpJsonCallSettings, callSettings.getRetryableCodes()); + if (httpJsonCallSettings.getRequestMutator() != null) { + callable = + Callables.retrying( + callable, callSettings, clientContext, httpJsonCallSettings.getRequestMutator()); + } else { + callable = Callables.retrying(callable, callSettings, clientContext); + } return callable; } @@ -133,16 +114,15 @@ public static UnaryCallable createUna HttpJsonCallSettings httpJsonCallSettings, UnaryCallSettings callSettings, ClientContext clientContext) { - UnaryCallable innerCallable = - createDirectUnaryCallable(httpJsonCallSettings); - - innerCallable = + UnaryCallable callable = + createBaseUnaryCallable(httpJsonCallSettings, callSettings, clientContext); + callable = new TracedUnaryCallable<>( - innerCallable, + callable, clientContext.getTracerFactory(), getSpanName(httpJsonCallSettings.getMethodDescriptor())); - return createUnaryCallable(innerCallable, callSettings, httpJsonCallSettings, clientContext); + return callable.withDefaultCallContext(clientContext.getDefaultCallContext()); } /** @@ -159,9 +139,8 @@ UnaryCallable createPagedCallable( HttpJsonCallSettings httpJsonCallSettings, PagedCallSettings pagedCallSettings, ClientContext clientContext) { - UnaryCallable callable = createDirectUnaryCallable(httpJsonCallSettings); - callable = - createUnaryCallable(callable, pagedCallSettings, httpJsonCallSettings, clientContext); + UnaryCallable callable = + createUnaryCallable(httpJsonCallSettings, pagedCallSettings.getRetryableCodes()); UnaryCallable pagedCallable = Callables.paged(callable, pagedCallSettings); return pagedCallable.withDefaultCallContext(clientContext.getDefaultCallContext()); @@ -181,9 +160,14 @@ public static UnaryCallable createBat HttpJsonCallSettings httpJsonCallSettings, BatchingCallSettings batchingCallSettings, ClientContext clientContext) { - UnaryCallable callable = createDirectUnaryCallable(httpJsonCallSettings); + UnaryCallable callable = + createBaseUnaryCallable(httpJsonCallSettings, batchingCallSettings, clientContext); callable = - createUnaryCallable(callable, batchingCallSettings, httpJsonCallSettings, clientContext); + new TracedBatchingCallable<>( + callable, + clientContext.getTracerFactory(), + getSpanName(httpJsonCallSettings.getMethodDescriptor()), + batchingCallSettings.getBatchingDescriptor()); callable = Callables.batching(callable, batchingCallSettings, clientContext); return callable.withDefaultCallContext(clientContext.getDefaultCallContext()); } @@ -224,6 +208,13 @@ ServerStreamingCallable createServerStreamingCallable( } callable = Callables.retrying(callable, streamingCallSettings, clientContext); + + callable = + new TracedServerStreamingCallable<>( + callable, + clientContext.getTracerFactory(), + getSpanName(httpJsoncallSettings.getMethodDescriptor())); + return callable.withDefaultCallContext(clientContext.getDefaultCallContext()); } diff --git a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/RetryingTest.java b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/RetryingTest.java index f7b9935d31..c5da5a6897 100644 --- a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/RetryingTest.java +++ b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/RetryingTest.java @@ -53,7 +53,13 @@ import com.google.api.gax.rpc.StatusCode.Code; import com.google.api.gax.rpc.UnaryCallSettings; import com.google.api.gax.rpc.UnaryCallable; +import com.google.api.gax.rpc.UnavailableException; import com.google.api.gax.rpc.UnknownException; +import com.google.api.gax.rpc.testing.FakeCallContext; +import com.google.api.gax.rpc.testing.FakeCallableFactory; +import com.google.api.gax.rpc.testing.FakeChannel; +import com.google.api.gax.rpc.testing.FakeStatusCode; +import com.google.api.gax.rpc.testing.FakeTransportChannel; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.UncheckedExecutionException; @@ -123,7 +129,8 @@ public void resetClock() { ClientContext.newBuilder() .setExecutor(executor) .setClock(fakeClock) - .setDefaultCallContext(HttpJsonCallContext.createDefault()) + .setDefaultCallContext(FakeCallContext.createDefault()) + .setTransportChannel(FakeTransportChannel.create(new FakeChannel())) .build(); } @@ -135,23 +142,25 @@ public void teardown() { @Test public void retry() { ImmutableSet retryable = ImmutableSet.of(Code.UNAVAILABLE); + FakeStatusCode fakeStatusCode = FakeStatusCode.of(Code.UNAVAILABLE); + UnavailableException unavailableException = + new UnavailableException(null, fakeStatusCode, true); Mockito.when(callInt.futureCall((Integer) any(), (ApiCallContext) any())) - .thenReturn(ApiFutures.immediateFailedFuture(HTTP_SERVICE_UNAVAILABLE_EXCEPTION)) - .thenReturn(ApiFutures.immediateFailedFuture(HTTP_SERVICE_UNAVAILABLE_EXCEPTION)) - .thenReturn(ApiFutures.immediateFailedFuture(HTTP_SERVICE_UNAVAILABLE_EXCEPTION)) + .thenReturn(ApiFutures.immediateFailedFuture(unavailableException)) + .thenReturn(ApiFutures.immediateFailedFuture(unavailableException)) + .thenReturn(ApiFutures.immediateFailedFuture(unavailableException)) .thenReturn(ApiFutures.immediateFuture(2)); UnaryCallSettings callSettings = createSettings(retryable, FAST_RETRY_SETTINGS); UnaryCallable callable = - HttpJsonCallableFactory.createUnaryCallable( - callInt, callSettings, httpJsonCallSettings, clientContext); + FakeCallableFactory.createUnaryCallable(callInt, callSettings, clientContext); assertThat(callable.call(initialRequest)).isEqualTo(2); // Capture the argument passed to futureCall ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class); verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class)); - assertThat(argumentCaptor.getAllValues()).containsExactly(0, 0, 0, 0).inOrder(); + assertThat(argumentCaptor.getAllValues()).containsExactly(1, 1, 1, 1).inOrder(); } @Test @@ -181,79 +190,78 @@ public void retryTotalTimeoutExceeded() { .build(); UnaryCallSettings callSettings = createSettings(retryable, retrySettings); UnaryCallable callable = - HttpJsonCallableFactory.createUnaryCallable( - callInt, callSettings, httpJsonCallSettings, clientContext); + FakeCallableFactory.createUnaryCallable(callInt, callSettings, clientContext); assertThrows(ApiException.class, () -> callable.call(initialRequest)); // Capture the argument passed to futureCall ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class); verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class)); - assertThat(argumentCaptor.getAllValues()).containsExactly(0); + assertThat(argumentCaptor.getAllValues()).containsExactly(1); } @Test public void retryMaxAttemptsExceeded() { ImmutableSet retryable = ImmutableSet.of(Code.UNAVAILABLE); + FakeStatusCode fakeStatusCode = FakeStatusCode.of(Code.UNAVAILABLE); + UnavailableException unavailableException = + new UnavailableException(null, fakeStatusCode, true); Mockito.when(callInt.futureCall((Integer) any(), (ApiCallContext) any())) - .thenReturn(ApiFutures.immediateFailedFuture(HTTP_SERVICE_UNAVAILABLE_EXCEPTION)) - .thenReturn(ApiFutures.immediateFailedFuture(HTTP_SERVICE_UNAVAILABLE_EXCEPTION)) + .thenReturn(ApiFutures.immediateFailedFuture(unavailableException)) + .thenReturn(ApiFutures.immediateFailedFuture(unavailableException)) .thenReturn(ApiFutures.immediateFuture(2)); RetrySettings retrySettings = FAST_RETRY_SETTINGS.toBuilder().setMaxAttempts(2).build(); UnaryCallSettings callSettings = createSettings(retryable, retrySettings); UnaryCallable callable = - HttpJsonCallableFactory.createUnaryCallable( - callInt, callSettings, httpJsonCallSettings, clientContext); + FakeCallableFactory.createUnaryCallable(callInt, callSettings, clientContext); assertThrows(ApiException.class, () -> callable.call(initialRequest)); // Capture the argument passed to futureCall ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class); verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class)); - assertThat(argumentCaptor.getAllValues()).containsExactly(0, 0).inOrder(); + assertThat(argumentCaptor.getAllValues()).containsExactly(1, 1).inOrder(); } @Test public void retryWithinMaxAttempts() { ImmutableSet retryable = ImmutableSet.of(Code.UNAVAILABLE); + FakeStatusCode fakeStatusCode = FakeStatusCode.of(Code.UNAVAILABLE); + UnavailableException unavailableException = + new UnavailableException(null, fakeStatusCode, true); Mockito.when(callInt.futureCall((Integer) any(), (ApiCallContext) any())) - .thenReturn(ApiFutures.immediateFailedFuture(HTTP_SERVICE_UNAVAILABLE_EXCEPTION)) - .thenReturn(ApiFutures.immediateFailedFuture(HTTP_SERVICE_UNAVAILABLE_EXCEPTION)) + .thenReturn(ApiFutures.immediateFailedFuture(unavailableException)) + .thenReturn(ApiFutures.immediateFailedFuture(unavailableException)) .thenReturn(ApiFutures.immediateFuture(2)); RetrySettings retrySettings = FAST_RETRY_SETTINGS.toBuilder().setMaxAttempts(3).build(); UnaryCallSettings callSettings = createSettings(retryable, retrySettings); UnaryCallable callable = - HttpJsonCallableFactory.createUnaryCallable( - callInt, callSettings, httpJsonCallSettings, clientContext); + FakeCallableFactory.createUnaryCallable(callInt, callSettings, clientContext); assertThat(callable.call(initialRequest)).isEqualTo(2); // Capture the argument passed to futureCall ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class); verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class)); - assertThat(argumentCaptor.getAllValues()).containsExactly(0, 0, 0).inOrder(); + assertThat(argumentCaptor.getAllValues()).containsExactly(1, 1, 1).inOrder(); } @Test public void retryOnStatusUnknown() { ImmutableSet retryable = ImmutableSet.of(Code.UNKNOWN); - HttpResponseException throwable = - new HttpResponseException.Builder( - HttpStatusCodes.STATUS_CODE_TEMPORARY_REDIRECT, - "temporary redirect", - new HttpHeaders()) - .build(); + FakeStatusCode fakeStatusCode = FakeStatusCode.of(Code.UNAVAILABLE); + UnavailableException unavailableException = + new UnavailableException(null, fakeStatusCode, true); Mockito.when(callInt.futureCall((Integer) any(), (ApiCallContext) any())) - .thenReturn(ApiFutures.immediateFailedFuture(throwable)) - .thenReturn(ApiFutures.immediateFailedFuture(throwable)) - .thenReturn(ApiFutures.immediateFailedFuture(throwable)) + .thenReturn(ApiFutures.immediateFailedFuture(unavailableException)) + .thenReturn(ApiFutures.immediateFailedFuture(unavailableException)) + .thenReturn(ApiFutures.immediateFailedFuture(unavailableException)) .thenReturn(ApiFutures.immediateFuture(2)); UnaryCallSettings callSettings = createSettings(retryable, FAST_RETRY_SETTINGS); UnaryCallable callable = - HttpJsonCallableFactory.createUnaryCallable( - callInt, callSettings, httpJsonCallSettings, clientContext); + FakeCallableFactory.createUnaryCallable(callInt, callSettings, clientContext); assertThat(callable.call(initialRequest)).isEqualTo(2); // Capture the argument passed to futureCall ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class); verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class)); - assertThat(argumentCaptor.getAllValues()).containsExactly(0, 0, 0, 0).inOrder(); + assertThat(argumentCaptor.getAllValues()).containsExactly(1, 1, 1, 1).inOrder(); } @Test @@ -265,14 +273,13 @@ public void retryOnUnexpectedException() { UnaryCallSettings callSettings = createSettings(retryable, FAST_RETRY_SETTINGS); UnaryCallable callable = - HttpJsonCallableFactory.createUnaryCallable( - callInt, callSettings, httpJsonCallSettings, clientContext); + FakeCallableFactory.createUnaryCallable(callInt, callSettings, clientContext); ApiException exception = assertThrows(ApiException.class, () -> callable.call(initialRequest)); assertThat(exception).hasCauseThat().isSameInstanceAs(throwable); // Capture the argument passed to futureCall ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class); verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class)); - assertThat(argumentCaptor.getAllValues()).containsExactly(0).inOrder(); + assertThat(argumentCaptor.getAllValues()).containsExactly(1).inOrder(); } @Test @@ -294,82 +301,62 @@ public void retryNoRecover() { UnaryCallSettings callSettings = createSettings(retryable, FAST_RETRY_SETTINGS); UnaryCallable callable = - HttpJsonCallableFactory.createUnaryCallable( - callInt, callSettings, httpJsonCallSettings, clientContext); + FakeCallableFactory.createUnaryCallable(callInt, callSettings, clientContext); ApiException exception = assertThrows(ApiException.class, () -> callable.call(initialRequest)); assertThat(exception).isSameInstanceAs(apiException); // Capture the argument passed to futureCall ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class); verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class)); - assertThat(argumentCaptor.getAllValues()).containsExactly(0); + assertThat(argumentCaptor.getAllValues()).containsExactly(1); } @Test public void retryKeepFailing() { ImmutableSet retryable = ImmutableSet.of(Code.UNAVAILABLE); - HttpResponseException throwable = - new HttpResponseException.Builder( - HttpStatusCodes.STATUS_CODE_SERVICE_UNAVAILABLE, "Unavailable", new HttpHeaders()) - .build(); + FakeStatusCode fakeStatusCode = FakeStatusCode.of(Code.UNAVAILABLE); + UnavailableException unavailableException = + new UnavailableException("Unavailable", null, fakeStatusCode, true); Mockito.when(callInt.futureCall((Integer) any(), (ApiCallContext) any())) - .thenReturn(ApiFutures.immediateFailedFuture(throwable)); + .thenReturn(ApiFutures.immediateFailedFuture(unavailableException)); UnaryCallSettings callSettings = createSettings(retryable, FAST_RETRY_SETTINGS); UnaryCallable callable = - HttpJsonCallableFactory.createUnaryCallable( - callInt, callSettings, httpJsonCallSettings, clientContext); + FakeCallableFactory.createUnaryCallable(callInt, callSettings, clientContext); // Need to advance time inside the call. ApiFuture future = callable.futureCall(initialRequest); UncheckedExecutionException exception = assertThrows(UncheckedExecutionException.class, () -> Futures.getUnchecked(future)); assertThat(exception).hasCauseThat().isInstanceOf(ApiException.class); - assertThat(exception).hasCauseThat().hasMessageThat().contains("Unavailable"); + assertThat(exception.getMessage()).contains("Unavailable"); // Capture the argument passed to futureCall ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class); verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class)); - assertThat(argumentCaptor.getValue()).isEqualTo(0); + assertThat(argumentCaptor.getValue()).isEqualTo(1); } @Test public void testKnownStatusCode() { ImmutableSet retryable = ImmutableSet.of(Code.UNAVAILABLE); - String throwableMessage = - "{\n" - + " \"error\": {\n" - + " \"errors\": [\n" - + " {\n" - + " \"domain\": \"global\",\n" - + " \"reason\": \"FAILED_PRECONDITION\",\n" - + " }\n" - + " ],\n" - + " \"code\": 400,\n" - + " \"message\": \"Failed precondition.\"\n" - + " }\n" - + "}"; - HttpResponseException throwable = - new HttpResponseException.Builder( - HTTP_CODE_PRECONDITION_FAILED, "precondition failed", new HttpHeaders()) - .setMessage(throwableMessage) - .build(); + FakeStatusCode fakeStatusCode = FakeStatusCode.of(Code.FAILED_PRECONDITION); Mockito.when(callInt.futureCall((Integer) any(), (ApiCallContext) any())) - .thenReturn(ApiFutures.immediateFailedFuture(throwable)); + .thenReturn( + ApiFutures.immediateFailedFuture( + new FailedPreconditionException("known", null, fakeStatusCode, false))); UnaryCallSettings callSettings = UnaryCallSettings.newUnaryCallSettingsBuilder() .setRetryableCodes(retryable) .build(); UnaryCallable callable = - HttpJsonCallableFactory.createUnaryCallable( - callInt, callSettings, httpJsonCallSettings, clientContext); + FakeCallableFactory.createUnaryCallable(callInt, callSettings, clientContext); ApiException exception = assertThrows(FailedPreconditionException.class, () -> callable.call(initialRequest)); - assertThat(exception.getStatusCode().getTransportCode()) - .isEqualTo(HTTP_CODE_PRECONDITION_FAILED); - assertThat(exception).hasMessageThat().contains("precondition failed"); + assertThat(exception.getStatusCode().getCode().toString()).isEqualTo(fakeStatusCode.toString()); + assertThat(exception.getMessage()).isEqualTo("known"); // Capture the argument passed to futureCall ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class); verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class)); - assertThat(argumentCaptor.getAllValues()).containsExactly(0).inOrder(); + assertThat(argumentCaptor.getAllValues()).containsExactly(1).inOrder(); } @Test @@ -382,15 +369,14 @@ public void testUnknownStatusCode() { .setRetryableCodes(retryable) .build(); UnaryCallable callable = - HttpJsonCallableFactory.createUnaryCallable( - callInt, callSettings, httpJsonCallSettings, clientContext); + FakeCallableFactory.createUnaryCallable(callInt, callSettings, clientContext); UnknownException exception = assertThrows(UnknownException.class, () -> callable.call(initialRequest)); assertThat(exception).hasMessageThat().isEqualTo("java.lang.RuntimeException: unknown"); // Capture the argument passed to futureCall ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class); verify(callInt, atLeastOnce()).futureCall(argumentCaptor.capture(), any(ApiCallContext.class)); - assertThat(argumentCaptor.getAllValues()).containsExactly(0).inOrder(); + assertThat(argumentCaptor.getAllValues()).containsExactly(1).inOrder(); } public static UnaryCallSettings createSettings(