/** * 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.collapser; import com.google.common.collect.Sets; import com.netflix.hystrix.HystrixEventType; import com.netflix.hystrix.HystrixInvokableInfo; import com.netflix.hystrix.HystrixRequestLog; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser; 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 org.junit.Before; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import rx.Observable; import java.util.ArrayList; import java.util.List; import java.util.Set; 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.assertTrue; /** * Created by dmgcodevil */ public abstract class BasicCollapserTest extends BasicHystrixTest { protected abstract UserService createUserService(); private UserService userService; @Before public void setUp() throws Exception { userService = createUserService(); } @Test public void testGetUserById() throws ExecutionException, InterruptedException { Future<User> f1 = userService.getUserById("1"); Future<User> f2 = userService.getUserById("2"); Future<User> f3 = userService.getUserById("3"); Future<User> f4 = userService.getUserById("4"); Future<User> f5 = userService.getUserById("5"); assertEquals("name: 1", f1.get().getName()); assertEquals("name: 2", f2.get().getName()); assertEquals("name: 3", f3.get().getName()); assertEquals("name: 4", f4.get().getName()); assertEquals("name: 5", f5.get().getName()); // assert that the batch command 'getUserByIds' was in fact // executed and that it executed only once assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); HystrixInvokableInfo<?> command = HystrixRequestLog.getCurrentRequest() .getAllExecutedCommands().iterator().next(); // assert the command is the one we're expecting assertEquals("getUserByIds", command.getCommandKey().name()); // confirm that it was a COLLAPSED command execution assertTrue(command.getExecutionEvents().contains(HystrixEventType.COLLAPSED)); // and that it was successful assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS)); } @Test public void testReactive() throws Exception { final Observable<User> u1 = userService.getUserByIdReactive("1"); final Observable<User> u2 = userService.getUserByIdReactive("2"); final Observable<User> u3 = userService.getUserByIdReactive("3"); final Observable<User> u4 = userService.getUserByIdReactive("4"); final Observable<User> u5 = userService.getUserByIdReactive("5"); final Iterable<User> users = Observable.merge(u1, u2, u3, u4, u5).toBlocking().toIterable(); Set<String> expectedIds = Sets.newHashSet("1", "2", "3", "4", "5"); for (User cUser : users) { assertEquals(expectedIds.remove(cUser.getId()), true); } assertEquals(expectedIds.isEmpty(), true); assertEquals(1, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); HystrixInvokableInfo<?> command = HystrixRequestLog.getCurrentRequest() .getAllExecutedCommands().iterator().next(); // assert the command is the one we're expecting assertEquals("getUserByIds", command.getCommandKey().name()); // confirm that it was a COLLAPSED command execution assertTrue(command.getExecutionEvents().contains(HystrixEventType.COLLAPSED)); // and that it was successful assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS)); } @Test public void testGetUserByIdWithFallback() throws ExecutionException, InterruptedException { Future<User> f1 = userService.getUserByIdWithFallback("1"); Future<User> f2 = userService.getUserByIdWithFallback("2"); Future<User> f3 = userService.getUserByIdWithFallback("3"); Future<User> f4 = userService.getUserByIdWithFallback("4"); Future<User> f5 = userService.getUserByIdWithFallback("5"); assertEquals("name: 1", f1.get().getName()); assertEquals("name: 2", f2.get().getName()); assertEquals("name: 3", f3.get().getName()); assertEquals("name: 4", f4.get().getName()); assertEquals("name: 5", f5.get().getName()); // two command should be executed: "getUserByIdWithFallback" and "getUserByIdsWithFallback" assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); HystrixInvokableInfo<?> getUserByIdsWithFallback = getHystrixCommandByKey("getUserByIdsWithFallback"); com.netflix.hystrix.HystrixInvokableInfo getUserByIdsFallback = getHystrixCommandByKey("getUserByIdsFallback"); // confirm that command has failed assertTrue(getUserByIdsWithFallback.getExecutionEvents().contains(HystrixEventType.FAILURE)); assertTrue(getUserByIdsWithFallback.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); // and that fallback was successful assertTrue(getUserByIdsFallback.getExecutionEvents().contains(HystrixEventType.SUCCESS)); } @Test public void testGetUserByIdWithFallbackWithThrowableParam() throws ExecutionException, InterruptedException { Future<User> f1 = userService.getUserByIdWithFallbackWithThrowableParam("1"); Future<User> f2 = userService.getUserByIdWithFallbackWithThrowableParam("2"); Future<User> f3 = userService.getUserByIdWithFallbackWithThrowableParam("3"); Future<User> f4 = userService.getUserByIdWithFallbackWithThrowableParam("4"); Future<User> f5 = userService.getUserByIdWithFallbackWithThrowableParam("5"); assertEquals("name: 1", f1.get().getName()); assertEquals("name: 2", f2.get().getName()); assertEquals("name: 3", f3.get().getName()); assertEquals("name: 4", f4.get().getName()); assertEquals("name: 5", f5.get().getName()); // 4 commands should be executed assertEquals(4, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size()); HystrixInvokableInfo<?> batchCommand = getHystrixCommandByKey("getUserByIdsThrowsException"); com.netflix.hystrix.HystrixInvokableInfo fallback1 = getHystrixCommandByKey("getUserByIdsFallbackWithThrowableParam1"); com.netflix.hystrix.HystrixInvokableInfo fallback2 = getHystrixCommandByKey("getUserByIdsFallbackWithThrowableParam2"); com.netflix.hystrix.HystrixInvokableInfo fallback3 = getHystrixCommandByKey("getUserByIdsFallbackWithThrowableParam3"); // confirm that command has failed assertTrue(batchCommand.getExecutionEvents().contains(HystrixEventType.FAILURE)); assertTrue(fallback1.getExecutionEvents().contains(HystrixEventType.FAILURE)); assertTrue(fallback2.getExecutionEvents().contains(HystrixEventType.FAILURE)); assertTrue(fallback2.getExecutionEvents().contains(HystrixEventType.FALLBACK_SUCCESS)); // and that last fallback3 was successful assertTrue(fallback3.getExecutionEvents().contains(HystrixEventType.SUCCESS)); } @Test(expected = IllegalStateException.class) public void testGetUserByIdWrongBatchMethodArgType() { userService.getUserByIdWrongBatchMethodArgType("1"); } @Test(expected = IllegalStateException.class) public void testGetUserByIdWrongBatchMethodReturnType() { userService.getUserByIdWrongBatchMethodArgType("1"); } @Test(expected = IllegalStateException.class) public void testGetUserByIdWrongCollapserMethodReturnType() { userService.getUserByIdWrongCollapserMethodReturnType("1"); } @Test(expected = IllegalStateException.class) public void testGetUserByIdWrongCollapserMultipleArgs() { userService.getUserByIdWrongCollapserMultipleArgs("1", "2"); } @Test(expected = IllegalStateException.class) public void testGetUserByIdWrongCollapserNoArgs() { userService.getUserByIdWrongCollapserNoArgs(); } public static class UserService { public static final Logger log = LoggerFactory.getLogger(UserService.class); public static final User DEFAULT_USER = new User("def", "def"); @HystrixCollapser(batchMethod = "getUserByIds", collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds", value = "200")}) public Future<User> getUserById(String id) { return null; } @HystrixCollapser(batchMethod = "getUserByIdsWithFallback", collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds", value = "200")}) public Future<User> getUserByIdWithFallback(String id) { return null; } @HystrixCollapser(batchMethod = "getUserByIds", collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds", value = "200")}) public Observable<User> getUserByIdReactive(String id) { return null; } @HystrixCollapser(batchMethod = "getUserByIdsThrowsException", collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds", value = "200")}) public Future<User> getUserByIdWithFallbackWithThrowableParam(String id) { return null; } @HystrixCommand( fallbackMethod = "getUserByIdsFallbackWithThrowableParam1", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "10000")// for debug }) public List<User> getUserByIdsThrowsException(List<String> ids) { throw new RuntimeException("getUserByIdsFails failed"); } @HystrixCommand(fallbackMethod = "getUserByIdsFallbackWithThrowableParam2") private List<User> getUserByIdsFallbackWithThrowableParam1(List<String> ids, Throwable e) { if (e.getMessage().equals("getUserByIdsFails failed")) { throw new RuntimeException("getUserByIdsFallbackWithThrowableParam1 failed"); } List<User> users = new ArrayList<User>(); for (String id : ids) { users.add(new User(id, "name: " + id)); } return users; } @HystrixCommand(fallbackMethod = "getUserByIdsFallbackWithThrowableParam3") private List<User> getUserByIdsFallbackWithThrowableParam2(List<String> ids) { throw new RuntimeException("getUserByIdsFallbackWithThrowableParam2 failed"); } @HystrixCommand private List<User> getUserByIdsFallbackWithThrowableParam3(List<String> ids, Throwable e) { if (!e.getMessage().equals("getUserByIdsFallbackWithThrowableParam2 failed")) { throw new RuntimeException("getUserByIdsFallbackWithThrowableParam3 failed"); } List<User> users = new ArrayList<User>(); for (String id : ids) { users.add(new User(id, "name: " + id)); } return users; } @HystrixCommand(commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "10000")// for debug }) public List<User> getUserByIds(List<String> ids) { List<User> users = new ArrayList<User>(); for (String id : ids) { users.add(new User(id, "name: " + id)); } log.debug("executing on thread id: {}", Thread.currentThread().getId()); return users; } @HystrixCommand(fallbackMethod = "getUserByIdsFallback", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "10000")// for debug }) public List<User> getUserByIdsWithFallback(List<String> ids) { throw new RuntimeException("not found"); } @HystrixCommand private List<User> getUserByIdsFallback(List<String> ids) { List<User> users = new ArrayList<User>(); for (String id : ids) { users.add(new User(id, "name: " + id)); } return users; } // wrong return type, expected: Future<User> or User, because batch command getUserByIds returns List<User> @HystrixCollapser(batchMethod = "getUserByIds") public Long getUserByIdWrongCollapserMethodReturnType(String id) { return null; } @HystrixCollapser(batchMethod = "getUserByIds") public Future<User> getUserByIdWrongCollapserMultipleArgs(String id, String name) { return null; } @HystrixCollapser(batchMethod = "getUserByIds") public Future<User> getUserByIdWrongCollapserNoArgs() { return null; } @HystrixCollapser(batchMethod = "getUserByIdsWrongBatchMethodArgType") public Future<User> getUserByIdWrongBatchMethodArgType(String id) { return null; } // wrong arg type, expected: List<String> @HystrixCommand public List<User> getUserByIdsWrongBatchMethodArgType(List<Integer> ids) { return null; } @HystrixCollapser(batchMethod = "getUserByIdsWrongBatchMethodReturnType") public Future<User> getUserByIdWrongBatchMethodReturnType(String id) { return null; } // wrong return type, expected: List<User> @HystrixCommand public List<Integer> getUserByIdsWrongBatchMethodReturnType(List<String> ids) { return null; } } }