/** * 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 io.airlift.airship.agent; import com.google.common.collect.ImmutableMap; import com.google.common.io.Resources; import com.google.inject.Guice; import com.google.inject.Injector; import io.airlift.airship.shared.HttpUriBuilder; import io.airlift.airship.shared.Installation; import io.airlift.airship.shared.InstallationHelper; import io.airlift.airship.shared.InstallationRepresentation; import io.airlift.airship.shared.SlotStatus; import io.airlift.airship.shared.VersionsUtil; import io.airlift.configuration.ConfigurationFactory; import io.airlift.configuration.ConfigurationModule; import io.airlift.discovery.client.testing.TestingDiscoveryModule; import io.airlift.event.client.EventModule; import io.airlift.http.client.FullJsonResponseHandler.JsonResponse; import io.airlift.http.client.HttpClient; import io.airlift.http.client.JsonBodyGenerator; import io.airlift.http.client.Request; import io.airlift.http.client.StatusResponseHandler.StatusResponse; import io.airlift.http.client.jetty.JettyHttpClient; import io.airlift.http.server.testing.TestingHttpServer; import io.airlift.http.server.testing.TestingHttpServerModule; import io.airlift.jaxrs.JaxrsModule; import io.airlift.json.JsonCodec; import io.airlift.json.JsonModule; import io.airlift.node.testing.TestingNodeModule; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import java.io.File; import java.net.URI; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.UUID; import static com.google.common.base.Charsets.UTF_8; import static com.google.common.net.HttpHeaders.CONTENT_TYPE; import static io.airlift.airship.shared.ExtraAssertions.assertEqualsNoOrder; import static io.airlift.airship.shared.FileUtils.createTempDir; import static io.airlift.airship.shared.FileUtils.deleteRecursively; import static io.airlift.airship.shared.HttpUriBuilder.uriBuilderFrom; import static io.airlift.airship.shared.SlotLifecycleState.RUNNING; import static io.airlift.airship.shared.SlotLifecycleState.STOPPED; import static io.airlift.airship.shared.SlotLifecycleState.TERMINATED; import static io.airlift.http.client.FullJsonResponseHandler.createFullJsonResponseHandler; import static io.airlift.http.client.JsonResponseHandler.createJsonResponseHandler; import static io.airlift.http.client.StaticBodyGenerator.createStaticBodyGenerator; import static io.airlift.http.client.StatusResponseHandler.createStatusResponseHandler; import static io.airlift.json.JsonCodec.jsonCodec; import static io.airlift.json.JsonCodec.listJsonCodec; import static io.airlift.json.JsonCodec.mapJsonCodec; import static io.airlift.testing.Closeables.closeQuietly; import static javax.ws.rs.core.Response.Status; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; public class TestServer { private static final int NOT_ALLOWED = 405; private HttpClient client; private TestingHttpServer server; private Agent agent; private final JsonCodec<InstallationRepresentation> installationCodec = jsonCodec(InstallationRepresentation.class); private final JsonCodec<Map<String, Object>> mapCodec = mapJsonCodec(String.class, Object.class); private final JsonCodec<List<Map<String, Object>>> listCodec = listJsonCodec(mapCodec); private InstallationHelper installationHelper; private Installation appleInstallation; private Installation bananaInstallation; private File tempDir; @BeforeClass public void startServer() throws Exception { tempDir = createTempDir("agent"); Map<String, String> properties = ImmutableMap.<String, String>builder() .put("agent.id", UUID.randomUUID().toString()) .put("agent.coordinator-uri", "http://localhost:9999/") .put("agent.slots-dir", tempDir.getAbsolutePath()) .put("discovery.uri", "fake://server") .build(); Injector injector = Guice.createInjector( new TestingDiscoveryModule(), new TestingNodeModule(), new JsonModule(), new TestingHttpServerModule(), new JaxrsModule(), new EventModule(), new AgentMainModule(), new ConfigurationModule(new ConfigurationFactory(properties))); server = injector.getInstance(TestingHttpServer.class); agent = injector.getInstance(Agent.class); server.start(); client = new JettyHttpClient(); installationHelper = new InstallationHelper(); appleInstallation = installationHelper.getAppleInstallation(); bananaInstallation = installationHelper.getBananaInstallation(); } @BeforeMethod public void resetState() { for (Slot slot : agent.getAllSlots()) { if (slot.status().getAssignment() != null) { slot.stop(); } agent.terminateSlot(slot.getId()); } assertTrue(agent.getAllSlots().isEmpty()); } @AfterClass public void stopServer() throws Exception { closeQuietly(client); if (server != null) { server.stop(); } if (tempDir != null) { deleteRecursively(tempDir); } if (installationHelper != null) { installationHelper.destroy(); } } @Test public void testGetSlotStatus() throws Exception { SlotStatus slotStatus = agent.install(appleInstallation); Request request = Request.Builder.prepareGet().setUri(urlFor("/v1/agent/slot", slotStatus.getId().toString())).build(); Map<String, Object> response = client.execute(request, createJsonResponseHandler(mapCodec, Status.OK.getStatusCode())); Map<String, Object> expected = mapCodec.fromJson(Resources.toString(Resources.getResource("slot-status.json"), UTF_8)); expected.put("id", slotStatus.getId().toString()); expected.put("shortId", slotStatus.getId().toString()); expected.put("self", urlFor(slotStatus).toASCIIString()); expected.put("externalUri", urlFor(slotStatus).toASCIIString()); expected.put("version", slotStatus.getVersion()); expected.put("location", slotStatus.getLocation()); expected.put("shortLocation", slotStatus.getLocation()); expected.put("installPath", slotStatus.getInstallPath()); // agent does not return instance id or expected status expected.remove("instanceId"); expected.remove("expectedBinary"); expected.remove("expectedConfig"); expected.remove("expectedStatus"); assertEquals(response, expected); } @Test public void testGetAllSlotStatusEmpty() throws Exception { Request request = Request.Builder.prepareGet().setUri(urlFor("/v1/agent/slot")).build(); List<Map<String, Object>> response = client.execute(request, createJsonResponseHandler(listCodec, Status.OK.getStatusCode())); assertEquals(response, Collections.<Object>emptyList()); } @Test public void testGetAllSlotStatus() throws Exception { SlotStatus appleSlotStatus = agent.install(appleInstallation); SlotStatus bananaSlotStatus = agent.install(bananaInstallation); Request request = Request.Builder.prepareGet().setUri(urlFor("/v1/agent/slot")).build(); List<Map<String, Object>> response = client.execute(request, createJsonResponseHandler(listCodec, Status.OK.getStatusCode())); List<Map<String, Object>> expected = listCodec.fromJson(Resources.toString(Resources.getResource("slot-status-list.json"), UTF_8)); expected.get(0).put("id", appleSlotStatus.getId().toString()); expected.get(0).put("shortId", appleSlotStatus.getId().toString()); expected.get(0).put("version", appleSlotStatus.getVersion()); expected.get(0).put("self", urlFor(appleSlotStatus).toASCIIString()); expected.get(0).put("externalUri", urlFor(appleSlotStatus).toASCIIString()); expected.get(0).put("location", appleSlotStatus.getLocation()); expected.get(0).put("shortLocation", appleSlotStatus.getLocation()); expected.get(0).put("installPath", appleSlotStatus.getInstallPath()); expected.get(0).put("resources", ImmutableMap.<String, Integer>of("memory", 512)); expected.get(1).put("id", bananaSlotStatus.getId().toString()); expected.get(1).put("shortId", bananaSlotStatus.getId().toString()); expected.get(1).put("version", bananaSlotStatus.getVersion()); expected.get(1).put("self", urlFor(bananaSlotStatus).toASCIIString()); expected.get(1).put("externalUri", urlFor(bananaSlotStatus).toASCIIString()); expected.get(1).put("location", bananaSlotStatus.getLocation()); expected.get(1).put("shortLocation", bananaSlotStatus.getLocation()); expected.get(1).put("installPath", bananaSlotStatus.getInstallPath()); expected.get(1).put("resources", ImmutableMap.<String, Integer>of("cpu", 1)); assertEqualsNoOrder(response, expected); } @Test public void testInstallSlot() throws Exception { Request request = Request.Builder.preparePost() .setUri(urlFor("/v1/agent/slot")) .setHeader(CONTENT_TYPE, MediaType.APPLICATION_JSON) .setBodyGenerator(JsonBodyGenerator.jsonBodyGenerator(installationCodec, InstallationRepresentation.from(appleInstallation))) .build(); JsonResponse<Map<String, Object>> response = client.execute(request, createFullJsonResponseHandler(mapCodec)); assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode()); Slot slot = agent.getAllSlots().iterator().next(); assertEquals(response.getHeader(HttpHeaders.LOCATION), uriBuilderFrom(server.getBaseUrl()).appendPath("/v1/agent/slot/").appendPath(slot.getId().toString()).toString()); assertEquals(response.getHeader(CONTENT_TYPE), MediaType.APPLICATION_JSON); Map<String, Object> expected = ImmutableMap.<String, Object>builder() .put("id", slot.getId().toString()) .put("shortId", slot.getId().toString()) .put("binary", appleInstallation.getAssignment().getBinary()) .put("shortBinary", appleInstallation.getAssignment().getBinary()) .put("config", appleInstallation.getAssignment().getConfig()) .put("shortConfig", appleInstallation.getAssignment().getConfig()) .put("self", urlFor(slot).toASCIIString()) .put("externalUri", urlFor(slot).toASCIIString()) .put("location", slot.status().getLocation()) .put("shortLocation", slot.status().getLocation()) .put("status", STOPPED.toString()) .put("version", slot.status().getVersion()) .put("installPath", slot.status().getInstallPath()) .put("resources", ImmutableMap.<String, Integer>of("memory", 512)) .build(); assertEquals(response.getValue(), expected); } @Test public void testTerminateSlot() throws Exception { SlotStatus slotStatus = agent.install(appleInstallation); Request request = Request.Builder.prepareDelete() .setUri(urlFor("/v1/agent/slot", slotStatus.getId().toString())) .build(); Map<String, Object> response = client.execute(request, createJsonResponseHandler(mapCodec, Status.OK.getStatusCode())); assertNull(agent.getSlot(slotStatus.getId())); Map<String, Object> expected = ImmutableMap.<String, Object>builder() .put("id", slotStatus.getId().toString()) .put("shortId", slotStatus.getId().toString()) .put("self", urlFor(slotStatus).toASCIIString()) .put("externalUri", urlFor(slotStatus).toASCIIString()) .put("location", slotStatus.getLocation()) .put("shortLocation", slotStatus.getLocation()) .put("status", TERMINATED.toString()) .put("version", VersionsUtil.createSlotVersion(slotStatus.getId(), TERMINATED, null)) .put("resources", ImmutableMap.<String, Integer>of()) .build(); assertEquals(response, expected); } @Test public void testTerminateUnknownSlot() throws Exception { Request request = Request.Builder.prepareDelete() .setUri(urlFor("/v1/agent/slot/unknown")) .build(); StatusResponse response = client.execute(request, createStatusResponseHandler()); assertEquals(response.getStatusCode(), Status.NOT_FOUND.getStatusCode()); } @Test public void testPutNotAllowed() throws Exception { String json = Resources.toString(Resources.getResource("slot-status.json"), UTF_8); Request request = Request.Builder.preparePut() .setUri(urlFor("/v1/agent/slot")) .setHeader(CONTENT_TYPE, MediaType.APPLICATION_JSON) .setBodyGenerator(createStaticBodyGenerator(json, UTF_8)) .build(); StatusResponse response = client.execute(request, createStatusResponseHandler()); assertEquals(response.getStatusCode(), NOT_ALLOWED); assertNull(agent.getSlot(UUID.randomUUID())); } @Test public void testAssign() throws Exception { SlotStatus slotStatus = agent.install(appleInstallation); String json = installationCodec.toJson(InstallationRepresentation.from(appleInstallation)); Request request = Request.Builder.preparePut() .setUri(urlFor(slotStatus, "assignment")) .setHeader(CONTENT_TYPE, MediaType.APPLICATION_JSON) .setBodyGenerator(createStaticBodyGenerator(json, UTF_8)) .build(); Map<String, Object> response = client.execute(request, createJsonResponseHandler(mapCodec, Status.OK.getStatusCode())); Map<String, Object> expected = ImmutableMap.<String, Object>builder() .put("id", slotStatus.getId().toString()) .put("shortId", slotStatus.getId().toString()) .put("binary", appleInstallation.getAssignment().getBinary()) .put("shortBinary", appleInstallation.getAssignment().getBinary()) .put("config", appleInstallation.getAssignment().getConfig()) .put("shortConfig", appleInstallation.getAssignment().getConfig()) .put("self", urlFor(slotStatus).toASCIIString()) .put("externalUri", urlFor(slotStatus).toASCIIString()) .put("location", slotStatus.getLocation()) .put("shortLocation", slotStatus.getLocation()) .put("status", STOPPED.toString()) .put("version", slotStatus.getVersion()) .put("installPath", slotStatus.getInstallPath()) .put("resources", ImmutableMap.<String, Integer>of("memory", 512)) .build(); assertEquals(response, expected); } @Test public void testStart() throws Exception { SlotStatus slotStatus = agent.install(appleInstallation); Request request = Request.Builder.preparePut() .setUri(urlFor(slotStatus, "lifecycle")) .setBodyGenerator(createStaticBodyGenerator("running", UTF_8)) .build(); Map<String, Object> response = client.execute(request, createJsonResponseHandler(mapCodec, Status.OK.getStatusCode())); Map<String, Object> expected = ImmutableMap.<String, Object>builder() .put("id", slotStatus.getId().toString()) .put("shortId", slotStatus.getId().toString()) .put("binary", appleInstallation.getAssignment().getBinary()) .put("shortBinary", appleInstallation.getAssignment().getBinary()) .put("config", appleInstallation.getAssignment().getConfig()) .put("shortConfig", appleInstallation.getAssignment().getConfig()) .put("self", urlFor(slotStatus).toASCIIString()) .put("externalUri", urlFor(slotStatus).toASCIIString()) .put("location", slotStatus.getLocation()) .put("shortLocation", slotStatus.getLocation()) .put("status", RUNNING.toString()) .put("version", VersionsUtil.createSlotVersion(slotStatus.getId(), RUNNING, appleInstallation.getAssignment())) .put("installPath", slotStatus.getInstallPath()) .put("resources", ImmutableMap.<String, Integer>of("memory", 512)) .build(); assertEquals(response, expected); } @Test public void testStop() throws Exception { SlotStatus slotStatus = agent.install(appleInstallation); agent.getSlot(slotStatus.getId()).start(); Request request = Request.Builder.preparePut() .setUri(urlFor(slotStatus, "lifecycle")) .setBodyGenerator(createStaticBodyGenerator("stopped", UTF_8)) .build(); Map<String, Object> response = client.execute(request, createJsonResponseHandler(mapCodec, Status.OK.getStatusCode())); Map<String, Object> expected = ImmutableMap.<String, Object>builder() .put("id", slotStatus.getId().toString()) .put("shortId", slotStatus.getId().toString()) .put("binary", appleInstallation.getAssignment().getBinary()) .put("shortBinary", appleInstallation.getAssignment().getBinary()) .put("config", appleInstallation.getAssignment().getConfig()) .put("shortConfig", appleInstallation.getAssignment().getConfig()) .put("self", urlFor(slotStatus).toASCIIString()) .put("externalUri", urlFor(slotStatus).toASCIIString()) .put("location", slotStatus.getLocation()) .put("shortLocation", slotStatus.getLocation()) .put("status", STOPPED.toString()) .put("version", slotStatus.getVersion()) .put("installPath", slotStatus.getInstallPath()) .put("resources", ImmutableMap.<String, Integer>of("memory", 512)) .build(); assertEquals(response, expected); } @Test public void testRestart() throws Exception { SlotStatus slotStatus = agent.install(appleInstallation); Request request = Request.Builder.preparePut() .setUri(urlFor(slotStatus, "lifecycle")) .setBodyGenerator(createStaticBodyGenerator("restarting", UTF_8)) .build(); Map<String, Object> response = client.execute(request, createJsonResponseHandler(mapCodec, Status.OK.getStatusCode())); Map<String, Object> expected = ImmutableMap.<String, Object>builder() .put("id", slotStatus.getId().toString()) .put("shortId", slotStatus.getId().toString()) .put("binary", appleInstallation.getAssignment().getBinary()) .put("shortBinary", appleInstallation.getAssignment().getBinary()) .put("config", appleInstallation.getAssignment().getConfig()) .put("shortConfig", appleInstallation.getAssignment().getConfig()) .put("self", urlFor(slotStatus).toASCIIString()) .put("externalUri", urlFor(slotStatus).toASCIIString()) .put("location", slotStatus.getLocation()) .put("shortLocation", slotStatus.getLocation()) .put("status", RUNNING.toString()) .put("version", VersionsUtil.createSlotVersion(slotStatus.getId(), RUNNING, appleInstallation.getAssignment())) .put("installPath", slotStatus.getInstallPath()) .put("resources", ImmutableMap.<String, Integer>of("memory", 512)) .build(); assertEquals(response, expected); } @Test public void testLifecycleUnknown() throws Exception { SlotStatus slotStatus = agent.install(appleInstallation); Request request = Request.Builder.preparePut() .setUri(urlFor(slotStatus, "lifecycle")) .setBodyGenerator(createStaticBodyGenerator("unknown", UTF_8)) .build(); StatusResponse response = client.execute(request, createStatusResponseHandler()); assertEquals(response.getStatusCode(), Status.BAD_REQUEST.getStatusCode()); } private URI urlFor(String... pathParts) { HttpUriBuilder builder = uriBuilderFrom(server.getBaseUrl()); for (String pathPart : pathParts) { builder.appendPath(pathPart); } return builder.build(); } private URI urlFor(Slot slot, String... pathParts) { return urlFor(slot.status(), pathParts); } private URI urlFor(SlotStatus slotStatus, String... pathParts) { HttpUriBuilder builder = uriBuilderFrom(server.getBaseUrl()) .appendPath("/v1/agent/slot/") .appendPath(slotStatus.getId().toString()); for (String pathPart : pathParts) { builder.appendPath(pathPart); } return builder.build(); } }