package com.netflix.niws.loadbalancer; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.netflix.discovery.CacheRefreshedEvent; import com.netflix.discovery.EurekaClient; import com.netflix.discovery.EurekaEventListener; import com.netflix.loadbalancer.ServerListUpdater; import org.easymock.Capture; import org.easymock.EasyMock; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import javax.inject.Provider; import java.util.Queue; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** * @author David Liu */ public class EurekaNotificationServerListUpdaterTest { private EurekaClient eurekaClientMock; private EurekaClient eurekaClientMock2; private ThreadPoolExecutor testExecutor; @Before public void setUp() { eurekaClientMock = setUpEurekaClientMock(); eurekaClientMock2 = setUpEurekaClientMock(); // use a test executor so that the tests do not share executors testExecutor = new ThreadPoolExecutor( 2, 2 * 5, 0, TimeUnit.NANOSECONDS, new ArrayBlockingQueue<Runnable>(1000), new ThreadFactoryBuilder() .setNameFormat("EurekaNotificationServerListUpdater-%d") .setDaemon(true) .build() ); } @Test public void testUpdating() throws Exception { EurekaNotificationServerListUpdater serverListUpdater = new EurekaNotificationServerListUpdater( new Provider<EurekaClient>() { @Override public EurekaClient get() { return eurekaClientMock; } }, testExecutor ); try { Capture<EurekaEventListener> eventListenerCapture = new Capture<EurekaEventListener>(); eurekaClientMock.registerEventListener(EasyMock.capture(eventListenerCapture)); EasyMock.replay(eurekaClientMock); final AtomicBoolean firstTime = new AtomicBoolean(false); final CountDownLatch firstLatch = new CountDownLatch(1); final CountDownLatch secondLatch = new CountDownLatch(1); serverListUpdater.start(new ServerListUpdater.UpdateAction() { @Override public void doUpdate() { if (firstTime.compareAndSet(false, true)) { firstLatch.countDown(); } else { secondLatch.countDown(); } } }); eventListenerCapture.getValue().onEvent(new CacheRefreshedEvent()); Assert.assertTrue(firstLatch.await(2, TimeUnit.SECONDS)); // wait a bit for the updateQueued flag to be reset for (int i = 1; i < 10; i++) { if (serverListUpdater.updateQueued.get()) { Thread.sleep(i * 100); } else { break; } } eventListenerCapture.getValue().onEvent(new CacheRefreshedEvent()); Assert.assertTrue(secondLatch.await(2, TimeUnit.SECONDS)); } finally { serverListUpdater.stop(); EasyMock.verify(eurekaClientMock); } } @Test public void testStopWithCommonExecutor() throws Exception { EurekaNotificationServerListUpdater serverListUpdater1 = new EurekaNotificationServerListUpdater( new Provider<EurekaClient>() { @Override public EurekaClient get() { return eurekaClientMock; } }, testExecutor ); EurekaNotificationServerListUpdater serverListUpdater2 = new EurekaNotificationServerListUpdater( new Provider<EurekaClient>() { @Override public EurekaClient get() { return eurekaClientMock2; } }, testExecutor ); Capture<EurekaEventListener> eventListenerCapture = new Capture<EurekaEventListener>(); eurekaClientMock.registerEventListener(EasyMock.capture(eventListenerCapture)); Capture<EurekaEventListener> eventListenerCapture2 = new Capture<EurekaEventListener>(); eurekaClientMock2.registerEventListener(EasyMock.capture(eventListenerCapture2)); EasyMock.replay(eurekaClientMock); EasyMock.replay(eurekaClientMock2); final CountDownLatch updateCountLatch = new CountDownLatch(2); serverListUpdater1.start(new ServerListUpdater.UpdateAction() { @Override public void doUpdate() { updateCountLatch.countDown(); } }); serverListUpdater2.start(new ServerListUpdater.UpdateAction() { @Override public void doUpdate() { updateCountLatch.countDown(); } }); eventListenerCapture.getValue().onEvent(new CacheRefreshedEvent()); eventListenerCapture2.getValue().onEvent(new CacheRefreshedEvent()); Assert.assertTrue(updateCountLatch.await(2, TimeUnit.SECONDS)); // latch is for both serverListUpdater1.stop(); serverListUpdater2.stop(); EasyMock.verify(eurekaClientMock); EasyMock.verify(eurekaClientMock2); } @Test public void testTaskAlreadyQueued() throws Exception { EurekaNotificationServerListUpdater serverListUpdater = new EurekaNotificationServerListUpdater( new Provider<EurekaClient>() { @Override public EurekaClient get() { return eurekaClientMock; } }, testExecutor ); try { Capture<EurekaEventListener> eventListenerCapture = new Capture<EurekaEventListener>(); eurekaClientMock.registerEventListener(EasyMock.capture(eventListenerCapture)); EasyMock.replay(eurekaClientMock); final CountDownLatch countDownLatch = new CountDownLatch(1); serverListUpdater.start(new ServerListUpdater.UpdateAction() { @Override public void doUpdate() { if (countDownLatch.getCount() == 0) { Assert.fail("should only countdown once"); } countDownLatch.countDown(); } }); eventListenerCapture.getValue().onEvent(new CacheRefreshedEvent()); eventListenerCapture.getValue().onEvent(new CacheRefreshedEvent()); Assert.assertTrue(countDownLatch.await(2, TimeUnit.SECONDS)); Thread.sleep(100); // sleep a bit more Assert.assertFalse(serverListUpdater.updateQueued.get()); } finally { serverListUpdater.stop(); EasyMock.verify(eurekaClientMock); } } @Test public void testSubmitExceptionClearQueued() { ThreadPoolExecutor executorMock = EasyMock.createMock(ThreadPoolExecutor.class); EasyMock.expect(executorMock.submit(EasyMock.isA(Runnable.class))) .andThrow(new RejectedExecutionException("test exception")); EasyMock.expect(executorMock.isShutdown()).andReturn(Boolean.FALSE); EurekaNotificationServerListUpdater serverListUpdater = new EurekaNotificationServerListUpdater( new Provider<EurekaClient>() { @Override public EurekaClient get() { return eurekaClientMock; } }, executorMock ); try { Capture<EurekaEventListener> eventListenerCapture = new Capture<EurekaEventListener>(); eurekaClientMock.registerEventListener(EasyMock.capture(eventListenerCapture)); EasyMock.replay(eurekaClientMock); EasyMock.replay(executorMock); serverListUpdater.start(new ServerListUpdater.UpdateAction() { @Override public void doUpdate() { Assert.fail("should not reach here"); } }); eventListenerCapture.getValue().onEvent(new CacheRefreshedEvent()); Assert.assertFalse(serverListUpdater.updateQueued.get()); } finally { serverListUpdater.stop(); EasyMock.verify(executorMock); EasyMock.verify(eurekaClientMock); } } @Test public void testEurekaClientUnregister() { ThreadPoolExecutor executorMock = EasyMock.createMock(ThreadPoolExecutor.class); EasyMock.expect(executorMock.isShutdown()).andReturn(Boolean.TRUE); EurekaNotificationServerListUpdater serverListUpdater = new EurekaNotificationServerListUpdater( new Provider<EurekaClient>() { @Override public EurekaClient get() { return eurekaClientMock; } }, executorMock ); try { Capture<EurekaEventListener> registeredListener = new Capture<EurekaEventListener>(); eurekaClientMock.registerEventListener(EasyMock.capture(registeredListener)); EasyMock.replay(eurekaClientMock); EasyMock.replay(executorMock); serverListUpdater.start(new ServerListUpdater.UpdateAction() { @Override public void doUpdate() { Assert.fail("should not reach here"); } }); registeredListener.getValue().onEvent(new CacheRefreshedEvent()); } finally { EasyMock.verify(executorMock); EasyMock.verify(eurekaClientMock); } } @Test(expected = IllegalStateException.class) public void testFailIfDiscoveryIsNotAvailable() { EurekaNotificationServerListUpdater serverListUpdater = new EurekaNotificationServerListUpdater( new Provider<EurekaClient>() { @Override public EurekaClient get() { return null; } }, testExecutor ); serverListUpdater.start(new ServerListUpdater.UpdateAction() { @Override public void doUpdate() { Assert.fail("Should not reach here"); } }); } private EurekaClient setUpEurekaClientMock() { final EurekaClient eurekaClientMock = EasyMock.createMock(EurekaClient.class); EasyMock .expect(eurekaClientMock.unregisterEventListener(EasyMock.isA(EurekaEventListener.class))) .andReturn(true).times(1); return eurekaClientMock; } }