package com.workshare.msnos.usvc; import static com.workshare.msnos.core.CoreHelper.asSet; import static com.workshare.msnos.core.CoreHelper.fakeSystemTime; import static com.workshare.msnos.core.CoreHelper.newAgentIden; import static com.workshare.msnos.core.CoreHelper.newCloudIden; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.IOException; import java.net.InetSocketAddress; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import com.sun.net.httpserver.HttpContext; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import com.workshare.msnos.core.Cloud; import com.workshare.msnos.core.Message; import com.workshare.msnos.core.RemoteAgent; import com.workshare.msnos.core.payloads.HealthcheckPayload; import com.workshare.msnos.soup.time.SystemTime; import com.workshare.msnos.usvc.api.RestApi; @SuppressWarnings("restriction") public class HealthcheckerTest { private static final String PATH = "files"; private static final String NAME = "content"; private static final int HTTP_PORT = 19999; private Healthchecker healthchecker; private ScheduledExecutorService scheduler; private Microcloud microcloud; private HttpServer httpServer; @Before public void setUp() throws Exception { Cloud cloud = mock(Cloud.class); when(cloud.getIden()).thenReturn(newCloudIden()); microcloud = mock(Microcloud.class); when(microcloud.getCloud()).thenReturn(cloud); scheduler = mock(ScheduledExecutorService.class); healthchecker = new Healthchecker(microcloud, scheduler); httpServer = null; } @After public void tearDown() throws Exception { SystemTime.reset(); } @After public void stopHttpServer() throws Exception { if (httpServer != null) httpServer.stop(0); } @Test public void shouldOnIOExceptionMarkAllApisFaulty() throws Exception { RemoteMicroservice remote = setupRemoteMicroservice(); setupNOHealthcheck(); startAndRunCheck(); assertTrue(allApisFaulty(remote)); } @Test public void shouldOnFaultyHealthCheckMarkAllApisFaulty() throws Exception { RemoteMicroservice remote = setupRemoteMicroservice(); setupHealthcheck(500); startAndRunCheck(); assertTrue(allApisFaulty(remote)); } @Test public void shouldOn200HealthCheckMarkAllApisWorking() throws Exception { RemoteMicroservice remote = setupRemoteMicroservice(); setupHealthcheck(200); startAndRunCheck(); assertTrue(allApisWorking(remote)); } @Test public void shouldOn200HealthSendHCKMessageToCloud() throws Exception { RemoteMicroservice remote = setupRemoteMicroservice(); setupHealthcheck(200); startAndRunCheck(); assertHealthcheckMessageSent(remote, true); } @Test public void shouldOnFaultyHealthCheckSendHCKMessageToCloud() throws Exception { RemoteMicroservice remote = setupRemoteMicroservice(); setupHealthcheck(500); startAndRunCheck(); assertHealthcheckMessageSent(remote, false); } @Test public void shouldSendENQToMicroservicesPeriodically() throws Exception { fakeSystemTime(100000); RemoteMicroservice remote = setupRemoteMicroservice(); setupHealthcheck(200); healthchecker = new Healthchecker(microcloud, scheduler); healthchecker.start(); fakeSystemTime(SystemTime.asMillis()+2*Healthchecker.ENQ_PERIOD); runCheck(); Message message = getLastMessageSent(Message.Type.ENQ); assertNotNull(message); assertEquals(remote.getAgent().getIden(), message.getTo()); } @Test public void shouldNotSendENQToMicroservicesIfLastEnquiryTimeNotElapsed() throws Exception { fakeSystemTime(100000); RemoteMicroservice remote = setupRemoteMicroservice(); setupHealthcheck(200); healthchecker = new Healthchecker(microcloud, scheduler); healthchecker.start(); fakeSystemTime(100000+(int)(Healthchecker.ENQ_PERIOD*0.75)); remote.setApis(new HashSet<RestApi>()); fakeSystemTime(100000+(int)(Healthchecker.ENQ_PERIOD*1.25)); runCheck(); Message message = getLastMessageSent(Message.Type.ENQ); assertNull(message); } @Test public void shouldSkipRecentlyCheckedServices() throws Exception { RemoteMicroservice remote = setupRemoteMicroservice(); when(remote.getLastChecked()).thenReturn(123456L); healthchecker = new Healthchecker(microcloud, scheduler); healthchecker.start(); fakeSystemTime(remote.getLastChecked()+1000); runCheck(); verify(remote, never()).markFaulty(); verify(remote, never()).markWorking(); } @Test public void shouldOn200HealthCheckMarkAllApisWorkingWheMultipleChecksAndOneFailingO() throws Exception { RestApi checkOk = new RestApi(PATH ,HTTP_PORT).onHost("127.0.0.1").asHealthCheck(); RestApi checkKO = new RestApi(PATH, HTTP_PORT).onHost("0.0.0.0").asHealthCheck(); RemoteMicroservice remote = setupRemoteMicroservice("name", checkOk, checkKO); setupHealthcheck(200); startAndRunCheck(); assertTrue(allApisWorking(remote)); } @Test public void shouldBeHealhtIfNoHealchecks() throws IOException { RestApi checkOk = new RestApi(PATH ,HTTP_PORT).onHost("127.0.0.1"); RestApi checkKO = new RestApi(PATH, HTTP_PORT).onHost("0.0.0.0"); RemoteMicroservice remote = setupRemoteMicroservice("name", checkOk, checkKO); startAndRunCheck(); assertTrue(allApisWorking(remote)); } protected void startAndRunCheck() { healthchecker.start(); runCheck(); } private void setupHealthcheck(final int code) throws IOException { setupNOHealthcheck(); HttpContext context = httpServer.createContext("/"); context.setHandler(new HttpHandler() { @Override public void handle(HttpExchange httpExchange) throws IOException { httpExchange.sendResponseHeaders(code, -1); } }); } protected HttpServer setupNOHealthcheck() throws IOException { return setUpHttpServer("127.0.0.1", HTTP_PORT); } private HttpServer setUpHttpServer(String host, int port) throws IOException { if (httpServer == null) { httpServer = HttpServer.create(); httpServer.bind(new InetSocketAddress(host, port), port); httpServer.start(); } return httpServer; } private Runnable capturePeriodicRunableCheck() { ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class); verify(scheduler, atLeastOnce()).scheduleAtFixedRate(captor.capture(), anyInt(), anyInt(), any(TimeUnit.class)); return captor.getValue(); } private Message getLastMessageSent(Message.Type typeOf) { List<Message> messages = getLastMessagesSent(); for (Message message : messages) { if (typeOf.equals(message.getType())) return message; } return null; } private List<Message> getLastMessagesSent() { try { ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class); verify(microcloud, atLeastOnce()).send(captor.capture()); return captor.getAllValues(); } catch (Throwable any) { return Collections.emptyList(); } } private void runCheck() { Runnable runnable = capturePeriodicRunableCheck(); runnable.run(); } private boolean allApisWorking(RemoteMicroservice remote) { for (RestApi rest : remote.getApis()) { if (rest.isFaulty()) return false; } return true; } private boolean allApisFaulty(RemoteMicroservice remote) { for (RestApi rest : remote.getApis()) { if (!rest.isFaulty()) return false; } return true; } private RemoteMicroservice setupRemoteMicroservice() throws Exception { return setupRemoteMicroserviceMultipleAPIsAndHealthCheck(NAME, PATH, "127.0.0.1", "10.10.10.25", "10.10.10.91", "10.10.10.143"); } private void assertHealthcheckMessageSent(RemoteMicroservice remote, final boolean working) { Message message = getLastMessageSent(Message.Type.HCK); assertNotNull(message); assertEquals(microcloud.getCloud().getIden(), message.getTo()); HealthcheckPayload payload = (HealthcheckPayload)message.getData(); assertEquals(working, payload.isWorking()); assertEquals(remote.getAgent().getIden(), payload.getIden()); } private RemoteMicroservice setupRemoteMicroserviceMultipleAPIsAndHealthCheck(String name, String endpoint, String host1, String host2, String host3, String host4) throws Exception { RestApi alfa = new RestApi(endpoint, HTTP_PORT).onHost(host1).asHealthCheck(); RestApi beta = new RestApi(endpoint, HTTP_PORT).onHost(host2); RestApi thre = new RestApi(endpoint, HTTP_PORT).onHost(host3); RestApi four = new RestApi(endpoint, HTTP_PORT).onHost(host4); return setupRemoteMicroservice(name, alfa, beta, thre, four); } private RemoteMicroservice setupRemoteMicroservice(String name, RestApi... apis) { RemoteAgent agent = mock(RemoteAgent.class); when(agent.getIden()).thenReturn(newAgentIden()); RemoteMicroservice remote = spy(new RemoteMicroservice(name, agent, asSet(apis))); when(remote.getLastChecked()).thenAnswer(new Answer<Long>() { @Override public Long answer(InvocationOnMock invocation) throws Throwable { return 0l; // force check every time }}); when(microcloud.getMicroServices()).thenReturn(Arrays.asList(remote)); return remote; } }