package com.workshare.msnos.usvc;
import static com.workshare.msnos.core.CoreHelper.asPublicNetwork;
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.randomUUID;
import static com.workshare.msnos.core.CoreHelper.runScheduledTasks;
import static com.workshare.msnos.core.MessagesHelper.newAPPMessage;
import static com.workshare.msnos.core.MessagesHelper.newFaultMessage;
import static com.workshare.msnos.core.MessagesHelper.newHCKMessage;
import static com.workshare.msnos.core.MessagesHelper.newPresenceMessage;
import static com.workshare.msnos.core.MessagesHelper.newQNEMessage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ScheduledExecutorService;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import com.workshare.msnos.core.Agent;
import com.workshare.msnos.core.Cloud;
import com.workshare.msnos.core.Cloud.Listener;
import com.workshare.msnos.core.Iden;
import com.workshare.msnos.core.LocalAgent;
import com.workshare.msnos.core.Message;
import com.workshare.msnos.core.MsnosException;
import com.workshare.msnos.core.RemoteAgent;
import com.workshare.msnos.core.RemoteEntity;
import com.workshare.msnos.core.Ring;
import com.workshare.msnos.core.payloads.QnePayload;
import com.workshare.msnos.core.protocols.ip.BaseEndpoint;
import com.workshare.msnos.core.protocols.ip.Endpoint;
import com.workshare.msnos.core.protocols.ip.Endpoint.Type;
import com.workshare.msnos.core.protocols.ip.HttpEndpoint;
import com.workshare.msnos.core.protocols.ip.Network;
import com.workshare.msnos.soup.time.SystemTime;
import com.workshare.msnos.usvc.api.RestApi;
import com.workshare.msnos.usvc.api.routing.strategies.CachingRoutingStrategy;
public class MicrocloudTest {
private Microcloud microcloud;
private Cloud cloud;
private Microservice local;
private ScheduledExecutorService executor;
@BeforeClass
public static void stopClock() {
System.setProperty(CachingRoutingStrategy.SYSP_TIMEOUT, "0");
}
@AfterClass
public static void restartClock() {
SystemTime.reset();
}
@Before
public void prepare() throws MsnosException {
cloud = Mockito.mock(Cloud.class);
when(cloud.getIden()).thenReturn(new Iden(Iden.Type.CLD, new UUID(111, 111)));
when(cloud.getRing()).thenReturn(Ring.random());
executor = Mockito.mock(ScheduledExecutorService.class);
microcloud = new Microcloud(cloud, executor);
LocalAgent agent = mock(LocalAgent.class);
when(agent.getIden()).thenReturn(newAgentIden());
local = new Microservice("fluffy", agent);
local.join(microcloud);
}
@Test
public void shouldCreateRemoteMicroserviceOnQNE() throws IOException {
RemoteAgent remoteAgent = newRemoteAgent();
simulateMessageFromCloud(newQNEMessage(remoteAgent, "content"));
assertAgentInMicroserviceList(remoteAgent);
}
@Test
public void shouldUpdateLastcheckedRemoteMicroserviceOnHCK() throws IOException {
RemoteMicroservice remote = setupRemoteMicroservice("10.10.10.10", "remote", "/endpoint");
fakeSystemTime(123456789);
simulateMessageFromCloud(newHCKMessage(remote, true));
remote = microcloud.getMicroServices().get(0);
assertEquals(123456789, remote.getLastChecked());
}
@Test
public void shouldCreateBoundRestApisWhenRestApiNotBound() throws Exception {
RemoteEntity remoteAgent = newRemoteAgentWithFakeHosts("10.10.10.10");
RestApi unboundApi = new RestApi("/test", 9999);
simulateMessageFromCloud(newQNEMessage(remoteAgent, "content", unboundApi));
RemoteMicroservice remote = microcloud.getMicroServices().get(0);
RestApi api = getFirstRestApi(remote);
assertEquals(api.getHost(), "10.10.10.10");
}
@Test
public void shouldUpdateMicroserviceApisIfPresent() throws Exception {
UUID uuid = new UUID(11, 22);
simulateRemoteMicroserviceJoin(uuid, "24.24.24.24", "remote", createRestApi("content", "/files"));
simulateRemoteMicroserviceJoin(uuid, "24.24.24.24", "remote", createRestApi("content", "/healthcheck"));
assertEquals(1, microcloud.getMicroServices().size());
assertEquals(2, microcloud.getMicroServices().get(0).getApis().size());
}
@Test
public void shouldReturnCorrectApiWhenSearchingById() throws Exception {
RestApi api1 = getFirstRestApi(setupRemoteMicroservice("24.24.24.24", "content", "/files"));
RestApi api2 = getFirstRestApi(setupRemoteMicroservice("11.11.11.11", "content", "/folders"));
RestApi api3 = getFirstRestApi(setupRemoteMicroservice("23.23.23.23", "content", "/files"));
RestApi api4 = getFirstRestApi(setupRemoteMicroservice("25.22.22.22", "content", "/files"));
assertEquals(api1, microcloud.searchApiById(api1.getId()));
assertEquals(api2, microcloud.searchApiById(api2.getId()));
assertEquals(api3, microcloud.searchApiById(api3.getId()));
assertEquals(api4, microcloud.searchApiById(api4.getId()));
}
@Test
public void shouldRemoveRemoteApisOnAgentFault() throws Exception {
RemoteMicroservice remote = setupRemoteMicroservice("24.24.24.24", "content", "/files");
RestApi api = getFirstRestApi(remote);
assertEquals(api, microcloud.searchApi(local, "/files"));
simulateMessageFromCloud(newFaultMessage(remote.getAgent()));
assertNull(microcloud.searchApi(local, "/files"));
}
@Test
public void shouldRemoveMicroserviceOnAgentFault() throws Exception {
RemoteMicroservice remoteMicroservice = setupRemoteMicroservice("10.10.10.10", "remote", "/endpoint");
simulateMessageFromCloud(newFaultMessage(remoteMicroservice.getAgent()));
assertFalse(microcloud.getMicroServices().contains(remoteMicroservice));
}
@Test
public void shouldRemoveMicroserviceOnAgentLeave() throws Exception {
RemoteMicroservice remoteMicroservice = setupRemoteMicroservice("10.10.10.10", "remote", "/endpoint");
simulateMessageFromCloud(newPresenceMessage(remoteMicroservice.getAgent(), false));
assertFalse(microcloud.getMicroServices().contains(remoteMicroservice));
}
@Test
public void shouldBeAbleToMarkRestApiAsFaulty() throws Exception {
String endpoint = "/files";
setupRemoteMicroservice("10.10.10.10", "content", endpoint);
setupRemoteMicroservice("10.10.10.11", "content", endpoint);
RestApi result1 = microcloud.searchApi(local, endpoint);
result1.markFaulty();
RestApi result2 = microcloud.searchApi(local, endpoint);
assertNotEquals(result2.getId(), result1.getId());
}
@Test
public void shouldReturnCorrectRestApi() throws Exception {
setupRemoteMicroservice("10.10.10.10", "content", "/files");
setupRemoteMicroservice("10.10.10.10", "content", "/folders");
RestApi result = microcloud.searchApi(local, "/files");
assertTrue("/files".equals(result.getPath()));
}
@Test
public void shouldNOTSelectApisExposedBySelf() throws Exception {
local.publish(new RestApi("alfa", 1234));
RemoteMicroservice remote = setupRemoteMicroservice("10.10.10.10", "test", "alfa");
RestApi expected = getFirstRestApi(remote);
assertEquals(expected, local.searchApi("alfa"));
assertEquals(expected, microcloud.searchApi(local, "alfa"));
}
@Test
public void shouldSearchesReturnNullIfApiListIsEmpty() throws Exception {
assertNull(microcloud.searchApi(local, "don't care"));
assertNull(microcloud.searchApiById(1033l));
}
@Test
public void shouldAddPassiveToListOnPassiveJoin() throws Exception {
PassiveService passiveService = new PassiveService(microcloud, "testPassive", "10.10.10.10", 9999, "http://10.10.10.10/healthcheck/");
passiveService.join();
assertEquals(1, microcloud.getPassiveServices().size());
}
@Test
public void shouldBeAbleToSearchForPassiveServicesByUUID() throws Exception {
PassiveService passiveService = new PassiveService(microcloud, "testPassive", "10.10.10.10", 9999, "http://10.10.10.10/healthcheck/");
passiveService.join();
UUID search = passiveService.getUuid();
assertEquals(microcloud.searchPassives(search), passiveService);
}
@Test(expected = IllegalArgumentException.class)
public void shouldOnlyAllowJoinedPassivelyToPublishApis() throws Exception {
PassiveService unjoined = new PassiveService(microcloud, "testUnjoined", "10.10.10.10", 9999, "http://10.11.11.11/healthcheck/");
unjoined.publish(mock(RestApi.class));
}
@Test
public void shouldRegisterRemoteMsnosEndpoints() throws Exception {
final String host = "24.24.24.24";
final RestApi api = createMsnosApi(host);
RemoteMicroservice remote = simulateRemoteMicroserviceJoin(randomUUID(), host, "remote", api);
HttpEndpoint endpoint = new HttpEndpoint(remote, api);
verify(cloud).registerRemoteMsnosEndpoint(endpoint);
}
@Test
public void shouldRegisterLocalMsnosEndpointsOnPublish() throws Exception {
final String host = "24.24.24.24";
final RestApi api = createMsnosApi(host);
ensureLocalNetworkPresent(asPublicNetwork(api.getHost()));
local.publish(api);
HttpEndpoint endpoint = new HttpEndpoint(local, api);
verify(cloud).registerLocalMsnosEndpoint(endpoint);
}
@Test
public void shouldRegisterLocalAnonymousMsnosEndpointsOnPublish() throws Exception {
final String host = "21.21.21.21";
final RestApi api = createMsnosApi(null);
ensureLocalNetworkPresent(asPublicNetwork(host));
local.publish(api);
HttpEndpoint endpoint = new HttpEndpoint(local, api.onHost(host));
verify(cloud).registerLocalMsnosEndpoint(endpoint);
}
@Test
public void shouldSendQNEWhenPublishingEndpoints() throws Exception {
final RestApi api = createRestApi("name", "path");
local.publish(api);
Message qne = assertMesageSent(Message.Type.QNE, local.getAgent().getIden());
assertEquals(api, ((QnePayload)qne.getData()).getApis().iterator().next());
}
@Test
public void shouldSendPresenceWhenRegisterMsnosApiInOrderToUpdateRemoteAgentEndpoints() throws Exception {
final RestApi api = createMsnosApi("24.24.24.24");
ensureLocalNetworkPresent(asPublicNetwork(api.getHost()));
local.publish(api);
assertMesageSent(Message.Type.PRS, local.getAgent().getIden());
}
@Test
public void shouldProcessExternalMessage() throws MsnosException {
RemoteMicroservice remote = simulateRemoteMicroserviceJoin(randomUUID(), "24.24.24.24", "name", createRestApi("name", "/foo"));
Message message = newAPPMessage(remote, cloud);
final Type type = Endpoint.Type.HTTP;
microcloud.process(message, type);
verify(cloud).process(message, type.toString());
}
@Test
public void shouldEnquiryUponReceivingMessagesFromUnknownMicroservices() throws Exception {
RestApi restApi = createRestApi("name", "/foo");
Endpoint ep = new BaseEndpoint(Type.UDP, asPublicNetwork("24.24.24.24"));
RemoteAgent agent = new RemoteAgent(randomUUID(), cloud, asSet(ep));
RemoteMicroservice remote = new RemoteMicroservice("name", agent, asSet(restApi));
simulateMessageFromCloud(newAPPMessage(remote, cloud));
runScheduledTasks(executor);
Message message = sentMessages().get(0);
assertEquals(Message.Type.ENQ, message.getType());
assertEquals(agent.getIden(), message.getTo());
}
@Test
public void shouldNOTEnquiryUnknownMicroserviceMultipleTimes() throws Exception {
RestApi restApi = createRestApi("name", "/foo");
Endpoint ep = new BaseEndpoint(Type.UDP, asPublicNetwork("24.24.24.24"));
RemoteAgent agent = new RemoteAgent(randomUUID(), cloud, asSet(ep));
RemoteMicroservice remote = new RemoteMicroservice("name", agent, asSet(restApi));
simulateMessageFromCloud(newAPPMessage(remote, cloud));
simulateMessageFromCloud(newAPPMessage(remote, cloud));
List<Message> messageList = sentMessages();
boolean enquired = false;
for (Message message: messageList) {
if (Message.Type.ENQ == message.getType() && agent.getIden() == message.getTo()) {
if (!enquired)
enquired = true;
else
fail("Multiple subsequent enquiries were sent!");
}
}
}
@Test
public void shouldNOTEnquiryUponReceivingMessagesFromCloud() throws Exception {
RemoteAgent agent = new RemoteAgent(randomUUID(), cloud, Collections.<Endpoint>emptySet() );
RemoteMicroservice remote = new RemoteMicroservice("name", agent, Collections.<RestApi>emptySet());
simulateMessageFromCloud(newHCKMessage(remote, true));
assertEquals(0, sentMessages().size());
}
@Test
public void shouldNOTEnquiryUponReceivingLeavingPresenceFromAgent() throws Exception {
RemoteAgent agent = new RemoteAgent(randomUUID(), cloud, Collections.<Endpoint>emptySet() );
simulateMessageFromCloud(newPresenceMessage(agent, false));
assertEquals(0, sentMessages().size());
}
@Test
public void shouldBeAbleToCheckRestApiEvenWhenFaulty() throws Exception {
RemoteMicroservice ms = setupRemoteMicroservice("10.10.10.10", "content", "/foo");
RestApi api = getFirstRestApi(ms);
assertTrue(microcloud.canServe("/foo"));
api.markFaulty();
assertTrue(microcloud.canServe("/foo"));
}
private Message assertMesageSent(final Message.Type type, final Iden iden) throws MsnosException {
for (Message message : sentMessages()) {
if (message.getType() == type && message.getFrom().equals(iden))
return message;
}
fail("Message of type "+type+" from "+iden+" not found!");
return null;
}
private List<Message> sentMessages() throws MsnosException {
try {
ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
verify(cloud, atLeastOnce()).send(captor.capture());
List<Message> messages = captor.getAllValues();
return messages;
} catch (Throwable ex) {
return Collections.emptyList();
}
}
private void ensureLocalNetworkPresent(Network network) {
Endpoint endpoint = new BaseEndpoint(Type.UDP, network);
when(local.getAgent().getEndpoints()).thenReturn(asSet(endpoint));
}
private RemoteMicroservice setupRemoteMicroservice(String host, String name, String apiPath) {
short port = 9999;
RestApi restApi = new RestApi(apiPath, port).onHost(host);
return simulateRemoteMicroserviceJoin(UUID.randomUUID(), host, name, restApi);
}
private RemoteMicroservice simulateRemoteMicroserviceJoin(UUID uuid, String host, String name, RestApi restApi) {
Endpoint ep = new BaseEndpoint(Type.UDP, asPublicNetwork(host));
RemoteAgent agent = new RemoteAgent(uuid, cloud, asSet(ep));
putRemoteAgentInCloudAgentsList(agent);
RemoteMicroservice remote = new RemoteMicroservice(name, agent, asSet(restApi));
simulateMessageFromCloud(newQNEMessage(remote.getAgent(), name, restApi));
return remote;
}
private Message simulateMessageFromCloud(final Message message) {
ArgumentCaptor<Cloud.Listener> cloudListener = ArgumentCaptor.forClass(Cloud.Listener.class);
verify(cloud, atLeastOnce()).addSynchronousListener(cloudListener.capture());
for (Listener listener : cloudListener.getAllValues()) {
listener.onMessage(message);
}
return message;
}
private RestApi getFirstRestApi(RemoteMicroservice remote) {
Set<RestApi> apis = remote.getApis();
return apis.iterator().next();
}
private RestApi createRestApi(String name, String path) {
return new RestApi(path, 9999).onHost("24.24.24.24");
}
private RestApi createMsnosApi(String host) {
return new RestApi("/to/path", 9999, host, RestApi.Type.MSNOS_HTTP, false);
}
private RemoteEntity newRemoteAgentWithFakeHosts(String address) throws Exception {
List<String> tokens = Arrays.asList(address.split("\\."));
byte[] nibbles = new byte[tokens.size()];
for (int i = 0; i < nibbles.length; i++) {
nibbles[i] = Byte.valueOf(tokens.get(i));
}
return newRemoteAgent(UUID.randomUUID(), new BaseEndpoint(Type.UDP, new Network(nibbles, (short)1)));
}
private RemoteAgent newRemoteAgent() {
return newRemoteAgent(UUID.randomUUID());
}
private RemoteAgent newRemoteAgent(final UUID uuid, BaseEndpoint... endpoints) {
RemoteAgent remote = new RemoteAgent(uuid, cloud, new HashSet<Endpoint>(Arrays.asList(endpoints)));
putRemoteAgentInCloudAgentsList(remote);
return remote;
}
private void putRemoteAgentInCloudAgentsList(final RemoteAgent agent) {
when(cloud.getRemoteAgents()).thenReturn(new HashSet<RemoteAgent>(Arrays.asList(agent)));
when(cloud.find(agent.getIden())).thenReturn(agent);
}
private void assertAgentInMicroserviceList(Agent remoteAgent) {
assertEquals(remoteAgent, microcloud.getMicroServices().iterator().next().getAgent());
}
}