/* * Copyright 2014 Avanza Bank AB * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.avanza.astrix.ft.hystrix; import static org.junit.Assert.assertEquals; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import com.avanza.astrix.beans.core.AstrixBeanKey; import com.avanza.astrix.beans.core.BasicFuture; import com.avanza.astrix.beans.ft.BeanFaultToleranceFactorySpi; import com.avanza.astrix.context.AstrixApplicationContext; import com.avanza.astrix.context.AstrixContext; import com.avanza.astrix.context.TestAstrixConfigurer; import com.avanza.astrix.core.AstrixFaultToleranceProxy; import com.avanza.astrix.core.ServiceUnavailableException; import com.avanza.astrix.provider.core.AstrixApiProvider; import com.avanza.astrix.provider.core.DefaultBeanSettings; import com.avanza.astrix.provider.core.Library; import com.avanza.astrix.test.util.AssertBlockPoller; import com.netflix.hystrix.Hystrix; import com.netflix.hystrix.HystrixCommandKey; import com.netflix.hystrix.HystrixCommandMetrics; import com.netflix.hystrix.HystrixEventType; import com.netflix.hystrix.util.HystrixRollingNumberEvent; import rx.Observable; public class HystrixFaulttoleranceIntegrationTest { private static final AtomicInteger counter = new AtomicInteger(0); private AstrixApplicationContext context; private TestAstrixConfigurer astrixConfigurer = new TestAstrixConfigurer(); private Ping ping; @Before public void setup() throws InterruptedException { Hystrix.reset(); counter.incrementAndGet(); astrixConfigurer.registerApiProvider(PingApiProvider.class); astrixConfigurer.registerApiProvider(CorruptPingApiProvider.class); astrixConfigurer.enableFaultTolerance(true); context = (AstrixApplicationContext) astrixConfigurer.configure(); ping = context.getBean(Ping.class); initMetrics(ping); } private void initMetrics(Ping ping) throws InterruptedException { // Black hystrix magic here :( try { ping.ping("foo"); } catch (Exception e) { } HystrixFaultToleranceFactory faultTolerance = (HystrixFaultToleranceFactory) AstrixApplicationContext.class.cast(this.context).getInstance(BeanFaultToleranceFactorySpi.class); HystrixCommandKey key = faultTolerance.getCommandKey(AstrixBeanKey.create(Ping.class)); HystrixCommandMetrics.getInstance(key).getCumulativeCount(HystrixEventType.SUCCESS); } @Test public void usesHystrixFaultToleranceProxyProviderPluginToApplyFaultToleranceToLibraries() throws Exception { assertEquals(0, getAppliedFaultToleranceCount(Ping.class)); assertEquals("foo", ping.ping("foo")); eventually(() -> { assertEquals(1, getAppliedFaultToleranceCount(Ping.class)); }); assertEquals("foo", ping.ping("foo")); eventually(() -> { assertEquals(2, getAppliedFaultToleranceCount(Ping.class)); }); } @Ignore("Fails for maven clean install, works standalone") @Test public void observable() throws Exception { assertEquals(0, getAppliedFaultToleranceCount(Ping.class)); assertEquals("foo", ping.observePing("foo").toBlocking().first()); eventually(() -> { assertEquals(1, getAppliedFaultToleranceCount(Ping.class)); }, 10_000); } /* * The following three tests test core abstractions in com.avanza.astrix.ft but uses the * hystrix-implementation to get a full integration test of the desired behavior. * (The desired behaviour is that the consumer should be protected from poorly designed code * which might block despite returning Future/Observable types. */ @Test(timeout = 2000) public void usesThreadIsolationByDefaultForFutureReturnTypes() throws Exception { CorruptPing ping = context.getBean(CorruptPing.class); for (int i = 0; i < 100; i++) { try { ping.foreverBlockingQueue("foo"); } catch (ServiceUnavailableException e) { } } } @Test(timeout = 2000) public void excpetionsOfTypesOtherThanServiceUnavailableExceptionDoesNotCountAsFailure() throws Exception { CorruptPing ping = context.getBean(CorruptPing.class); for (int i = 0; i < 100; i++) { try { ping.foreverBlockingQueue("foo"); } catch (ServiceUnavailableException e) { } } } @DefaultBeanSettings(initialTimeout=10) public interface CorruptPing { Observable<String> blockingObserve(String foo); Future<String> foreverBlockingQueue(String foo); Future<String> neverEndingFuture(String foo); } public static class CorruptPingImpl implements CorruptPing { private final CountDownLatch countDownLatch = new CountDownLatch(1); public Observable<String> blockingObserve(final String msg) { block(); return Observable.just(msg); } private void block() { try { countDownLatch.await(); // Simulate blocking construction of observable } catch (InterruptedException e) { } } @Override public Future<String> foreverBlockingQueue(String msg) { block(); return new BasicFuture<String>(msg); } @Override public Future<String> neverEndingFuture(String msg) { return new BasicFuture<>(); // Never set result on future } } private int getAppliedFaultToleranceCount(Class<?> beanType) { HystrixCommandKey commandKey = getFaultTolerance(context).getCommandKey(AstrixBeanKey.create(beanType)); return getEventCountForCommand(HystrixRollingNumberEvent.SUCCESS, commandKey); } private static HystrixFaultToleranceFactory getFaultTolerance(AstrixContext astrixContext) { BeanFaultToleranceFactorySpi ftStrategy = AstrixApplicationContext.class.cast(astrixContext).getInstance(BeanFaultToleranceFactorySpi.class); assertEquals(HystrixFaultToleranceFactory.class, ftStrategy.getClass()); return (HystrixFaultToleranceFactory) ftStrategy; } private int getEventCountForCommand(HystrixRollingNumberEvent hystrixRollingNumberEvent, HystrixCommandKey commandKey) { HystrixCommandMetrics metrics = HystrixCommandMetrics.getInstance(commandKey); if (metrics == null) { return 0; } int currentConcurrentExecutionCount = (int) metrics.getCumulativeCount(hystrixRollingNumberEvent); return currentConcurrentExecutionCount; } public interface Ping { String ping(String msg); Observable<String> observePing(String msg); } public static class PingImpl implements Ping { @Override public String ping(String msg) { return msg; } @Override public Observable<String> observePing(String msg) { return Observable.just(msg); } } @AstrixApiProvider public static class PingApiProvider { @AstrixFaultToleranceProxy @Library public Ping ping() { return new PingImpl(); } } @AstrixApiProvider public static class CorruptPingApiProvider { @AstrixFaultToleranceProxy @Library public CorruptPing observablePing() { return new CorruptPingImpl(); } } private void eventually(Runnable assertion) throws InterruptedException { eventually(assertion, 3000); } private void eventually(Runnable assertion, int timeout) throws InterruptedException { new AssertBlockPoller(timeout, 25).check(assertion); } }