/**
* 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.error;
import com.netflix.hystrix.HystrixEventType;
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.test.common.BasicHystrixTest;
import com.netflix.hystrix.contrib.javanica.test.common.domain.User;
import com.netflix.hystrix.exception.ExceptionNotWrappedByHystrix;
import com.netflix.hystrix.exception.HystrixRuntimeException;
import org.junit.Before;
import org.junit.Test;
import org.mockito.MockitoAnnotations;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import static com.netflix.hystrix.contrib.javanica.test.common.CommonUtils.getHystrixCommandByKey;
import static org.junit.Assert.*;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
/**
* Created by dmgcodevil
*/
public abstract class BasicErrorPropagationTest extends BasicHystrixTest {
private static final String COMMAND_KEY = "getUserById";
private static final Map<String, User> USERS;
static {
USERS = new HashMap<String, User>();
USERS.put("1", new User("1", "user_1"));
USERS.put("2", new User("2", "user_2"));
USERS.put("3", new User("3", "user_3"));
}
private UserService userService;
@MockitoAnnotations.Mock
private FailoverService failoverService;
protected abstract UserService createUserService();
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
userService = createUserService();
userService.setFailoverService(failoverService);
}
@Test(expected = BadRequestException.class)
public void testGetUserByBadId() throws NotFoundException {
try {
String badId = "";
userService.getUserById(badId);
} finally {
assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size());
com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey(COMMAND_KEY);
// will not affect metrics
assertFalse(getUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE));
// and will not trigger fallback logic
verify(failoverService, never()).getDefUser();
}
}
@Test(expected = NotFoundException.class)
public void testGetNonExistentUser() throws NotFoundException {
try {
userService.getUserById("4"); // user with id 4 doesn't exist
} finally {
assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size());
com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey(COMMAND_KEY);
// will not affect metrics
assertFalse(getUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE));
// and will not trigger fallback logic
verify(failoverService, never()).getDefUser();
}
}
@Test // don't expect any exceptions because fallback must be triggered
public void testActivateUser() throws NotFoundException, ActivationException {
try {
userService.activateUser("1"); // this method always throws ActivationException
} finally {
assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size());
com.netflix.hystrix.HystrixInvokableInfo activateUserCommand = getHystrixCommandByKey("activateUser");
// will not affect metrics
assertTrue(activateUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE));
assertTrue(activateUserCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS));
// and will not trigger fallback logic
verify(failoverService, atLeastOnce()).activate();
}
}
@Test(expected = RuntimeOperationException.class)
public void testBlockUser() throws NotFoundException, ActivationException, OperationException {
try {
userService.blockUser("1"); // this method always throws ActivationException
} finally {
assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size());
com.netflix.hystrix.HystrixInvokableInfo activateUserCommand = getHystrixCommandByKey("blockUser");
// will not affect metrics
assertTrue(activateUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE));
assertTrue(activateUserCommand.getExecutionEvents().contains(HystrixEventType.FALLBACK_FAILURE));
}
}
@Test(expected = NotFoundException.class)
public void testPropagateCauseException() throws NotFoundException {
userService.deleteUser("");
}
@Test(expected = UserException.class)
public void testUserExceptionThrownFromCommand() {
userService.userFailureWithoutFallback();
}
@Test
public void testHystrixExceptionThrownFromCommand() {
try {
userService.timedOutWithoutFallback();
} catch (HystrixRuntimeException e) {
assertEquals(TimeoutException.class, e.getCause().getClass());
} catch (Throwable e) {
assertTrue("'HystrixRuntimeException' is expected exception.", false);
}
}
@Test
public void testUserExceptionThrownFromFallback() {
try {
userService.userFailureWithFallback();
} catch (UserException e) {
assertEquals(1, e.level);
} catch (Throwable e) {
assertTrue("'UserException' is expected exception.", false);
}
}
@Test
public void testUserExceptionThrownFromFallbackCommand() {
try {
userService.userFailureWithFallbackCommand();
} catch (UserException e) {
assertEquals(2, e.level);
} catch (Throwable e) {
assertTrue("'UserException' is expected exception.", false);
}
}
@Test
public void testCommandAndFallbackErrorsComposition() {
try {
userService.commandAndFallbackErrorsComposition();
} catch (HystrixFlowException e) {
assertEquals(UserException.class, e.commandException.getClass());
assertEquals(UserException.class, e.fallbackException.getClass());
UserException commandException = (UserException) e.commandException;
UserException fallbackException = (UserException) e.fallbackException;
assertEquals(0, commandException.level);
assertEquals(1, fallbackException.level);
} catch (Throwable e) {
assertTrue("'HystrixFlowException' is expected exception.", false);
}
}
@Test
public void testCommandWithFallbackThatFailsByTimeOut() {
try {
userService.commandWithFallbackThatFailsByTimeOut();
} catch (HystrixRuntimeException e) {
assertEquals(TimeoutException.class, e.getCause().getClass());
} catch (Throwable e) {
assertTrue("'HystrixRuntimeException' is expected exception.", false);
}
}
@Test
public void testCommandThrowsNotWrappedException() {
try {
userService.throwNotWrappedCheckedException();
fail();
} catch (NotWrappedCheckedException e) {
// pass
} catch (Throwable e) {
fail("'NotWrappedCheckedException' is expected exception.");
}finally {
assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size());
com.netflix.hystrix.HystrixInvokableInfo getUserCommand = getHystrixCommandByKey("throwNotWrappedCheckedException");
// record failure in metrics
assertTrue(getUserCommand.getExecutionEvents().contains(HystrixEventType.FAILURE));
// and will not trigger fallback logic
verify(failoverService, never()).activate();
}
}
public static class UserService {
private FailoverService failoverService;
public void setFailoverService(FailoverService failoverService) {
this.failoverService = failoverService;
}
@HystrixCommand
public Object deleteUser(String id) throws NotFoundException {
throw new NotFoundException("");
}
@HystrixCommand(
commandKey = COMMAND_KEY,
ignoreExceptions = {
BadRequestException.class,
NotFoundException.class
},
fallbackMethod = "fallback")
public User getUserById(String id) throws NotFoundException {
validate(id);
if (!USERS.containsKey(id)) {
throw new NotFoundException("user with id: " + id + " not found");
}
return USERS.get(id);
}
@HystrixCommand(
ignoreExceptions = {BadRequestException.class, NotFoundException.class},
fallbackMethod = "activateFallback")
public void activateUser(String id) throws NotFoundException, ActivationException {
validate(id);
if (!USERS.containsKey(id)) {
throw new NotFoundException("user with id: " + id + " not found");
}
// always throw this exception
throw new ActivationException("user cannot be activate");
}
@HystrixCommand(
ignoreExceptions = {BadRequestException.class, NotFoundException.class},
fallbackMethod = "blockUserFallback")
public void blockUser(String id) throws NotFoundException, OperationException {
validate(id);
if (!USERS.containsKey(id)) {
throw new NotFoundException("user with id: " + id + " not found");
}
// always throw this exception
throw new OperationException("user cannot be blocked");
}
private User fallback(String id) {
return failoverService.getDefUser();
}
private void activateFallback(String id) {
failoverService.activate();
}
@HystrixCommand(ignoreExceptions = {RuntimeException.class})
private void blockUserFallback(String id) {
throw new RuntimeOperationException("blockUserFallback has failed");
}
private void validate(String val) throws BadRequestException {
if (val == null || val.length() == 0) {
throw new BadRequestException("parameter cannot be null ot empty");
}
}
@HystrixCommand(fallbackMethod = "voidFallback")
void throwNotWrappedCheckedException() throws NotWrappedCheckedException {
throw new NotWrappedCheckedException();
}
private void voidFallback(){
failoverService.activate();
}
/*********************************************************************************/
@HystrixCommand
String userFailureWithoutFallback() throws UserException {
throw new UserException();
}
@HystrixCommand(commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1")})
String timedOutWithoutFallback() {
return "";
}
/*********************************************************************************/
@HystrixCommand(fallbackMethod = "userFailureWithFallback_f_0")
String userFailureWithFallback() {
throw new UserException();
}
String userFailureWithFallback_f_0() {
throw new UserException(1);
}
/*********************************************************************************/
@HystrixCommand(fallbackMethod = "userFailureWithFallbackCommand_f_0")
String userFailureWithFallbackCommand() {
throw new UserException(0);
}
@HystrixCommand(fallbackMethod = "userFailureWithFallbackCommand_f_1")
String userFailureWithFallbackCommand_f_0() {
throw new UserException(1);
}
@HystrixCommand
String userFailureWithFallbackCommand_f_1() {
throw new UserException(2);
}
/*********************************************************************************/
@HystrixCommand(fallbackMethod = "commandAndFallbackErrorsComposition_f_0")
String commandAndFallbackErrorsComposition() {
throw new UserException();
}
String commandAndFallbackErrorsComposition_f_0(Throwable commandError) {
throw new HystrixFlowException(commandError, new UserException(1));
}
/*********************************************************************************/
@HystrixCommand(fallbackMethod = "commandWithFallbackThatFailsByTimeOut_f_0")
String commandWithFallbackThatFailsByTimeOut() {
throw new UserException(0);
}
@HystrixCommand(commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1")})
String commandWithFallbackThatFailsByTimeOut_f_0() {
return "";
}
}
private class FailoverService {
public User getDefUser() {
return new User("def", "def");
}
public void activate() {
}
}
// exceptions
private static class NotFoundException extends Exception {
private NotFoundException(String message) {
super(message);
}
}
private static class BadRequestException extends RuntimeException {
private BadRequestException(String message) {
super(message);
}
}
private static class ActivationException extends Exception {
private ActivationException(String message) {
super(message);
}
}
private static class OperationException extends Throwable {
private OperationException(String message) {
super(message);
}
}
private static class RuntimeOperationException extends RuntimeException {
private RuntimeOperationException(String message) {
super(message);
}
}
private static class NotWrappedCheckedException extends Exception implements ExceptionNotWrappedByHystrix {
}
static class UserException extends RuntimeException {
final int level;
public UserException() {
this(0);
}
public UserException(int level) {
this.level = level;
}
}
static class HystrixFlowException extends RuntimeException {
final Throwable commandException;
final Throwable fallbackException;
public HystrixFlowException(Throwable commandException, Throwable fallbackException) {
this.commandException = commandException;
this.fallbackException = fallbackException;
}
}
}