package com.opower.rest.client.generator.hystrix; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.util.ISO8601DateFormat; import com.fasterxml.jackson.datatype.guava.GuavaModule; import com.fasterxml.jackson.datatype.joda.JodaModule; import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; import com.google.common.collect.ImmutableList; import com.netflix.hystrix.HystrixCommandMetrics; import com.netflix.hystrix.HystrixCommandProperties; import com.netflix.hystrix.util.HystrixRollingNumberEvent; import com.opower.rest.client.ConfigurationCallback; import com.opower.rest.client.generator.core.ClientErrorInterceptor; import com.opower.rest.client.generator.core.ClientResponse; import com.opower.rest.client.generator.core.ResourceInterface; import com.opower.rest.client.generator.core.SimpleUriProvider; import com.opower.rest.client.generator.core.UriProvider; import com.opower.rest.client.generator.executors.ApacheHttpClient4Executor; import com.opower.rest.client.generator.util.HttpResponseCodes; import com.opower.rest.test.StatusCodeMatcher; import com.opower.rest.test.jetty.JettyRule; import com.opower.rest.test.resource.FrobResource; import org.junit.ClassRule; import org.junit.Test; import java.lang.reflect.Method; import static com.opower.rest.client.generator.hystrix.TestHystrixClientBuilder.GROUP_KEY; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** * @author chris.phillips */ public class HystrixErrorHandlingIntTest { private static int PORT = 7999; private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() .setDateFormat(new ISO8601DateFormat()) .registerModule(new GuavaModule()) .registerModule(new JodaModule()); private static final JacksonJsonProvider JACKSON_JSON_PROVIDER = new JacksonJsonProvider(OBJECT_MAPPER) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) // enabling this feature to make deserialization fail .configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true); @ClassRule public static final JettyRule JETTY_RULE = new JettyRule(PORT, HystrixErrorHandlingIntTest.class.getResource("/jersey/1/web.xml").toString()); private static final UriProvider URI_PROVIDER = new SimpleUriProvider(String.format("http://localhost:%s/", PORT)); private static final ResourceInterface<FrobResource> RESOURCE_INTERFACE = new ResourceInterface<>(FrobResource.class); private FrobResource frobResource = new HystrixClient.Builder<FrobResource>(RESOURCE_INTERFACE, URI_PROVIDER, GROUP_KEY) .clientErrorInterceptors(ImmutableList .<ClientErrorInterceptor>of(new TestClientErrorInterceptor())) .commandProperties(new ConfigurationCallback<HystrixCommandProperties.Setter>() { @Override public void configure(HystrixCommandProperties.Setter setter) { setter.withExecutionIsolationThreadTimeoutInMilliseconds(10000); } }) .executor(new ApacheHttpClient4Executor()) .registerProviderInstance(JACKSON_JSON_PROVIDER).build(); /** * Exceptions on the client side, even despite a successful http response, should always be propagated * and shouldn't count towards circuit-breaker * @throws Exception */ @Test public void nonClientResponseFailureThrowsOriginalException() throws Exception { Method frobJsonErrorMethod = FrobResource.class.getMethod("frobJsonError"); try { frobResource.frobJsonError(); fail(); } catch(RuntimeException ex) { assertTrue(ex.getCause() instanceof JsonMappingException); HystrixCommandMetrics metrics = HystrixCommandMetrics.getInstance(HystrixClient.keyForMethod(frobJsonErrorMethod)); assertThat(metrics.getCumulativeCount(HystrixRollingNumberEvent.FAILURE), is(0L)); assertThat(metrics.getCumulativeCount(HystrixRollingNumberEvent.EXCEPTION_THROWN), is(1L)); } } /** * Ensures that the HystrixClientErrorHandler is properly wrapping exceptions in HystrixBadRequestExceptions. This is * verified by checking the failure stats for the HystrixCommand. * @throws Exception */ @Test public void serverResponseErrors() throws Exception { Method frobErrorResponseMethod = FrobResource.class.getMethod("frobErrorResponse", int.class); // this is an error that should trip the circuit-breaker so should up the FAILURE count metricsTest(frobErrorResponseMethod, HttpResponseCodes.SC_INTERNAL_SERVER_ERROR, 1L, 1L); // this is a bad request that should NOT trip the circuit break or up the FAILURE count metricsTest(frobErrorResponseMethod, HttpResponseCodes.SC_BAD_REQUEST, 1L, 2L); } private void metricsTest(Method method, int statusCode, long expectedFailureCount, long expectedExceptionThrownCount) throws Exception { try { frobResource.frobErrorResponse(statusCode); fail(); } catch(StatusCodeMatcher.ResponseError ex) { HystrixCommandMetrics metrics = HystrixCommandMetrics.getInstance(HystrixClient.keyForMethod(method)); assertThat(ex.getClientResponse().getStatus(), is(statusCode)); assertThat(metrics.getCumulativeCount(HystrixRollingNumberEvent.FAILURE), is(expectedFailureCount)); assertThat(metrics.getCumulativeCount(HystrixRollingNumberEvent.EXCEPTION_THROWN), is(expectedExceptionThrownCount)); } } private class TestClientErrorInterceptor implements ClientErrorInterceptor { @Override public void handle(ClientResponse response) throws RuntimeException { throw new StatusCodeMatcher.ResponseError(response); } } }