/** * Copyright 2016 Netflix, Inc. * * 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.netflix.hystrix.contrib.javanica.test.common.fallback; import com.netflix.hystrix.HystrixEventType; import com.netflix.hystrix.HystrixInvokableInfo; import com.netflix.hystrix.HystrixRequestLog; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; import com.netflix.hystrix.contrib.javanica.command.AsyncResult; import com.netflix.hystrix.contrib.javanica.exception.FallbackDefinitionException; import com.netflix.hystrix.contrib.javanica.test.common.BasicHystrixTest; import com.netflix.hystrix.contrib.javanica.test.common.domain.Domain; import com.netflix.hystrix.contrib.javanica.test.common.domain.User; import com.netflix.hystrix.exception.HystrixBadRequestException; import org.apache.commons.lang3.Validate; import org.junit.Before; import org.junit.Test; import rx.Observable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import static com.netflix.hystrix.contrib.javanica.test.common.CommonUtils.getHystrixCommandByKey; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public abstract class BasicCommandFallbackTest extends BasicHystrixTest { private UserService userService; protected abstract UserService createUserService(); @Before public void setUp() throws Exception { userService = createUserService(); } @Test public void testGetUserAsyncWithFallback() throws ExecutionException, InterruptedException { Future<User> f1 = userService.getUserAsync(" ", "name: "); assertEquals("def", f1.get().getName()); assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); HystrixInvokableInfo<?> command = HystrixRequestLog.getCurrentRequest() .getAllExecutedCommands().iterator().next(); assertEquals("getUserAsync", command.getCommandKey().name()); // confirm that 'getUserAsync' command has failed assertTrue(command.getExecutionEvents().contains(HystrixEventType.FAILURE)); // and that fallback waw successful assertTrue(command.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); } @Test public void testGetUserSyncWithFallback() { User u1 = userService.getUserSync(" ", "name: "); assertEquals("def", u1.getName()); assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); HystrixInvokableInfo<?> command = HystrixRequestLog.getCurrentRequest() .getAllExecutedCommands().iterator().next(); assertEquals("getUserSync", command.getCommandKey().name()); // confirm that command has failed assertTrue(command.getExecutionEvents().contains(HystrixEventType.FAILURE)); // and that fallback was successful assertTrue(command.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); } /** * * **************************** * * * * TEST FALLBACK COMMANDS * * * * **************************** * */ @Test public void testGetUserAsyncWithFallbackCommand() throws ExecutionException, InterruptedException { Future<User> f1 = userService.getUserAsyncFallbackCommand(" ", "name: "); assertEquals("def", f1.get().getName()); assertEquals(3, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); HystrixInvokableInfo<?> getUserAsyncFallbackCommand = getHystrixCommandByKey( "getUserAsyncFallbackCommand"); com.netflix.hystrix.HystrixInvokableInfo firstFallbackCommand = getHystrixCommandByKey("firstFallbackCommand"); com.netflix.hystrix.HystrixInvokableInfo secondFallbackCommand = getHystrixCommandByKey("secondFallbackCommand"); assertEquals("getUserAsyncFallbackCommand", getUserAsyncFallbackCommand.getCommandKey().name()); // confirm that command has failed assertTrue(getUserAsyncFallbackCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); // confirm that first fallback has failed assertTrue(firstFallbackCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); // and that second fallback was successful assertTrue(secondFallbackCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); } @Test public void testGetUserAsyncFallbackAsyncCommand() throws ExecutionException, InterruptedException { Future<User> f1 = userService.getUserAsyncFallbackAsyncCommand(" ", "name: "); assertEquals("def", f1.get().getName()); assertEquals(4, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); HystrixInvokableInfo<?> getUserAsyncFallbackAsyncCommand = getHystrixCommandByKey( "getUserAsyncFallbackAsyncCommand"); com.netflix.hystrix.HystrixInvokableInfo firstAsyncFallbackCommand = getHystrixCommandByKey("firstAsyncFallbackCommand"); com.netflix.hystrix.HystrixInvokableInfo secondAsyncFallbackCommand = getHystrixCommandByKey("secondAsyncFallbackCommand"); com.netflix.hystrix.HystrixInvokableInfo thirdAsyncFallbackCommand = getHystrixCommandByKey("thirdAsyncFallbackCommand"); assertEquals("getUserAsyncFallbackAsyncCommand", getUserAsyncFallbackAsyncCommand.getCommandKey().name()); // confirm that command has failed assertTrue(getUserAsyncFallbackAsyncCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); // confirm that first fallback has failed assertTrue(firstAsyncFallbackCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); // and that second fallback was successful assertTrue(secondAsyncFallbackCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); assertTrue(thirdAsyncFallbackCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); assertTrue(thirdAsyncFallbackCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); } @Test public void testGetUserSyncWithFallbackCommand() { User u1 = userService.getUserSyncFallbackCommand(" ", "name: "); assertEquals("def", u1.getName()); assertEquals(3, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); HystrixInvokableInfo<?> getUserSyncFallbackCommand = getHystrixCommandByKey( "getUserSyncFallbackCommand"); com.netflix.hystrix.HystrixInvokableInfo firstFallbackCommand = getHystrixCommandByKey("firstFallbackCommand"); com.netflix.hystrix.HystrixInvokableInfo secondFallbackCommand = getHystrixCommandByKey("secondFallbackCommand"); assertEquals("getUserSyncFallbackCommand", getUserSyncFallbackCommand.getCommandKey().name()); // confirm that command has failed assertTrue(getUserSyncFallbackCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); // confirm that first fallback has failed assertTrue(firstFallbackCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); // and that second fallback was successful assertTrue(secondFallbackCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); } @Test public void testAsyncCommandWithAsyncFallbackCommand() throws ExecutionException, InterruptedException { Future<User> userFuture = userService.asyncCommandWithAsyncFallbackCommand("", ""); User user = userFuture.get(); assertEquals("def", user.getId()); assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); HystrixInvokableInfo<?> asyncCommandWithAsyncFallbackCommand = getHystrixCommandByKey("asyncCommandWithAsyncFallbackCommand"); com.netflix.hystrix.HystrixInvokableInfo asyncFallbackCommand = getHystrixCommandByKey("asyncFallbackCommand"); // confirm that command has failed assertTrue(asyncCommandWithAsyncFallbackCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); assertTrue(asyncCommandWithAsyncFallbackCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); // and that second fallback was successful assertTrue(asyncFallbackCommand.getExecutionEvents().contains(HystrixEventType.SUCCESS)); } @Test(expected = FallbackDefinitionException.class) public void testAsyncCommandWithAsyncFallback() { userService.asyncCommandWithAsyncFallback("", ""); } @Test(expected = FallbackDefinitionException.class) public void testCommandWithWrongFallbackReturnType() { userService.commandWithWrongFallbackReturnType("", ""); } @Test(expected = FallbackDefinitionException.class) public void testAsyncCommandWithWrongFallbackReturnType() { userService.asyncCommandWithWrongFallbackReturnType("", ""); } @Test(expected = FallbackDefinitionException.class) public void testCommandWithWrongFallbackParams() { userService.commandWithWrongFallbackParams("1", "2"); } @Test(expected = FallbackDefinitionException.class) public void testCommandWithFallbackReturnSuperType() { userService.commandWithFallbackReturnSuperType("", ""); } @Test public void testCommandWithFallbackReturnSubType() { User user = (User) userService.commandWithFallbackReturnSubType("", ""); assertEquals("def", user.getName()); } @Test public void testCommandWithFallbackWithAdditionalParameter() { User user = userService.commandWithFallbackWithAdditionalParameter("", ""); assertEquals("def", user.getName()); } @Test(expected = HystrixBadRequestException.class) public void testCommandThrowsHystrixBadRequestExceptionWithNoCause() { try { userService.commandThrowsHystrixBadRequestExceptionWithNoCause(null, null); } finally { HystrixInvokableInfo<?> asyncCommandWithAsyncFallbackCommand = getHystrixCommandByKey("commandThrowsHystrixBadRequestExceptionWithNoCause"); assertFalse(asyncCommandWithAsyncFallbackCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); } } public static class UserService { @HystrixCommand(fallbackMethod = "fallback") public Future<User> getUserAsync(final String id, final String name) { validate(id, name); // validate logic can be inside and outside of AsyncResult#invoke method return new AsyncResult<User>() { @Override public User invoke() { // validate(id, name); possible put validation logic here, in case of any exception a fallback method will be invoked return new User(id, name + id); // it should be network call } }; } @HystrixCommand(fallbackMethod = "fallback") public User getUserSync(String id, String name) { validate(id, name); return new User(id, name + id); // it should be network call } private User fallback(String id, String name) { return new User("def", "def"); } @HystrixCommand(fallbackMethod = "firstFallbackCommand") public Future<User> getUserAsyncFallbackCommand(final String id, final String name) { return new AsyncResult<User>() { @Override public User invoke() { validate(id, name); return new User(id, name + id); // it should be network call } }; } @HystrixCommand(fallbackMethod = "firstFallbackCommand") public User getUserSyncFallbackCommand(String id, String name) { validate(id, name); return new User(id, name + id); // it should be network call } // FALLBACK COMMANDS METHODS: // This fallback methods will be processed as hystrix commands @HystrixCommand(fallbackMethod = "secondFallbackCommand") private User firstFallbackCommand(String id, String name) { validate(id, name); return new User(id, name + id); // it should be network call } @HystrixCommand(fallbackMethod = "staticFallback") private User secondFallbackCommand(String id, String name) { validate(id, name); return new User(id, name + id); // it should be network call } @HystrixCommand(fallbackMethod = "firstAsyncFallbackCommand") public Future<User> getUserAsyncFallbackAsyncCommand(final String id, final String name) { return new AsyncResult<User>() { @Override public User invoke() { throw new RuntimeException("getUserAsyncFallbackAsyncCommand failed"); } }; } @HystrixCommand(fallbackMethod = "secondAsyncFallbackCommand") private Future<User> firstAsyncFallbackCommand(final String id, final String name) { return new AsyncResult<User>() { @Override public User invoke() { throw new RuntimeException("firstAsyncFallbackCommand failed"); } }; } @HystrixCommand(fallbackMethod = "thirdAsyncFallbackCommand") private Future<User> secondAsyncFallbackCommand(final String id, final String name, final Throwable e) { return new AsyncResult<User>() { @Override public User invoke() { if ("firstAsyncFallbackCommand failed".equals(e.getMessage())) { throw new RuntimeException("secondAsyncFallbackCommand failed"); } return new User(id, name + id); } }; } @HystrixCommand(fallbackMethod = "fallbackWithAdditionalParam") private Future<User> thirdAsyncFallbackCommand(final String id, final String name) { return new AsyncResult<User>() { @Override public User invoke() { throw new RuntimeException("thirdAsyncFallbackCommand failed"); } }; } private User fallbackWithAdditionalParam(final String id, final String name, final Throwable e) { if (!"thirdAsyncFallbackCommand failed".equals(e.getMessage())) { throw new RuntimeException("fallbackWithAdditionalParam failed"); } return new User("def", "def"); } @HystrixCommand(fallbackMethod = "asyncFallbackCommand", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "100000") }) public Future<User> asyncCommandWithAsyncFallbackCommand(final String id, final String name) { return new AsyncResult<User>() { @Override public User invoke() { validate(id, name); return new User(id, name + id); // it should be network call } }; } @HystrixCommand(fallbackMethod = "asyncFallback", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "100000") }) public Future<User> asyncCommandWithAsyncFallback(final String id, final String name) { return new AsyncResult<User>() { @Override public User invoke() { validate(id, name); return new User(id, name + id); // it should be network call } }; } public Future<User> asyncFallback(final String id, final String name) { return Observable.just(new User("def", "def")).toBlocking().toFuture(); } @HystrixCommand public Future<User> asyncFallbackCommand(final String id, final String name) { return new AsyncResult<User>() { @Override public User invoke() { return new User("def", "def"); // it should be network call } }; } @HystrixCommand(fallbackMethod = "fallbackWithAdditionalParameter") public User commandWithFallbackWithAdditionalParameter(final String id, final String name) { validate(id, name); return new User(id, name + id); } public User fallbackWithAdditionalParameter(final String id, final String name, Throwable e) { if (e == null) { throw new RuntimeException("exception should be not null"); } return new User("def", "def"); } @HystrixCommand(fallbackMethod = "fallbackWithStringReturnType") public User commandWithWrongFallbackReturnType(final String id, final String name) { validate(id, name); return new User(id, name); } @HystrixCommand(fallbackMethod = "fallbackWithStringReturnType") public Future<User> asyncCommandWithWrongFallbackReturnType(final String id, final String name) { return new AsyncResult<User>() { @Override public User invoke() { return new User("def", "def"); // it should be network call } }; } @HystrixCommand(fallbackMethod = "fallbackWithoutParameters") public User commandWithWrongFallbackParams(final String id, final String name) { return new User(id, name); } @HystrixCommand(fallbackMethod = "fallbackReturnSubTypeOfDomain") public Domain commandWithFallbackReturnSubType(final String id, final String name) { validate(id, name); return new User(id, name); } @HystrixCommand(fallbackMethod = "fallbackReturnSuperTypeOfDomain") public User commandWithFallbackReturnSuperType(final String id, final String name) { validate(id, name); return new User(id, name); } @HystrixCommand(fallbackMethod = "staticFallback") public User commandThrowsHystrixBadRequestExceptionWithNoCause(final String id, final String name){ throw new HystrixBadRequestException("invalid arguments"); } private User fallbackReturnSubTypeOfDomain(final String id, final String name) { return new User("def", "def"); } private Domain fallbackReturnSuperTypeOfDomain(final String id, final String name) { return new User("def", "def"); } private String fallbackWithStringReturnType(final String id, final String name) { return null; } private User fallbackWithoutParameters() { return null; } private User staticFallback(String id, String name) { return new User("def", "def"); } private void validate(String id, String name) { Validate.notBlank(id); Validate.notBlank(name); } } }