package io.airlift.airship.integration; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ListMultimap; import com.google.inject.Binder; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Module; import com.google.inject.util.Modules; import io.airlift.airship.cli.Airship; import io.airlift.airship.cli.Airship.AirshipCommand; import io.airlift.airship.cli.Config; import io.airlift.airship.cli.InteractiveUser; import io.airlift.airship.cli.OutputFormat; import io.airlift.airship.coordinator.Coordinator; import io.airlift.airship.coordinator.CoordinatorMainModule; import io.airlift.airship.coordinator.InMemoryStateManager; import io.airlift.airship.coordinator.Provisioner; import io.airlift.airship.coordinator.StateManager; import io.airlift.airship.coordinator.StaticProvisionerModule; import io.airlift.airship.coordinator.TestingMavenRepository; import io.airlift.airship.shared.AgentStatusRepresentation; import io.airlift.airship.shared.Assignment; import io.airlift.airship.shared.CoordinatorStatusRepresentation; import io.airlift.airship.shared.SlotLifecycleState; import io.airlift.airship.shared.SlotStatusRepresentation; import io.airlift.configuration.ConfigurationFactory; import io.airlift.configuration.ConfigurationModule; import io.airlift.event.client.EventModule; import io.airlift.http.server.testing.TestingHttpServer; import io.airlift.http.server.testing.TestingHttpServerModule; import io.airlift.jaxrs.JaxrsModule; import io.airlift.json.JsonModule; import io.airlift.node.NodeModule; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.io.File; import java.util.List; import java.util.Map; import java.util.UUID; import static com.google.inject.Scopes.SINGLETON; import static io.airlift.airship.shared.AssignmentHelper.APPLE_ASSIGNMENT; import static io.airlift.airship.shared.AssignmentHelper.APPLE_ASSIGNMENT_2; import static io.airlift.airship.shared.AssignmentHelper.BANANA_ASSIGNMENT; import static io.airlift.airship.shared.AssignmentHelper.BANANA_ASSIGNMENT_EXACT; import static io.airlift.airship.shared.FileUtils.createTempDir; import static io.airlift.airship.shared.FileUtils.deleteRecursively; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; public class TestCliIntegration { private TestingHttpServer coordinatorServer; private Coordinator coordinator; private InMemoryStateManager stateManager; private MockLocalProvisioner provisioner; private File binaryRepoDir; private File localBinaryRepoDir; private File expectedStateDir; private File serviceInventoryCacheDir; private Config config; private MockInteractiveUser interactiveUser; private MockOutputFormat outputFormat; @BeforeClass public void setUp() throws Exception { try { binaryRepoDir = TestingMavenRepository.createBinaryRepoDir(); } catch (Exception e) { e.printStackTrace(); throw e; } localBinaryRepoDir = createTempDir("localBinaryRepoDir"); expectedStateDir = createTempDir("expected-state"); serviceInventoryCacheDir = createTempDir("service-inventory-cache"); Map<String, String> coordinatorProperties = ImmutableMap.<String, String>builder() .put("node.environment", "prod") .put("airship.version", "123") .put("coordinator.binary-repo", binaryRepoDir.toURI().toString()) .put("coordinator.default-group-id", "prod") .put("coordinator.binary-repo.local", localBinaryRepoDir.toString()) .put("coordinator.status.expiration", "1s") .put("coordinator.agent.default-config", "@agent.config") .put("coordinator.aws.access-key", "my-access-key") .put("coordinator.aws.secret-key", "my-secret-key") .put("coordinator.aws.agent.ami", "ami-0123abcd") .put("coordinator.aws.agent.keypair", "keypair") .put("coordinator.aws.agent.security-group", "default") .put("coordinator.aws.agent.default-instance-type", "t1.micro") .put("coordinator.expected-state.dir", expectedStateDir.getAbsolutePath()) .put("coordinator.service-inventory.cache-dir", serviceInventoryCacheDir.getAbsolutePath()) .build(); Injector coordinatorInjector = Guice.createInjector(new TestingHttpServerModule(), new NodeModule(), new JsonModule(), new JaxrsModule(), new EventModule(), new CoordinatorMainModule(), Modules.override(new StaticProvisionerModule()).with(new Module() { @Override public void configure(Binder binder) { binder.bind(StateManager.class).to(InMemoryStateManager.class).in(SINGLETON); binder.bind(Provisioner.class).to(MockLocalProvisioner.class).in(SINGLETON); } }), new ConfigurationModule(new ConfigurationFactory(coordinatorProperties))); coordinatorServer = coordinatorInjector.getInstance(TestingHttpServer.class); coordinator = coordinatorInjector.getInstance(Coordinator.class); stateManager = (InMemoryStateManager) coordinatorInjector.getInstance(StateManager.class); provisioner = (MockLocalProvisioner) coordinatorInjector.getInstance(Provisioner.class); provisioner.autoStartInstances = true; coordinator.start(); coordinatorServer.start(); } @BeforeMethod public void resetState() throws Exception { provisioner.clearCoordinators(); coordinator.updateAllCoordinatorsAndWait(); assertEquals(coordinator.getCoordinators().size(), 1); provisioner.clearAgents(); coordinator.updateAllAgentsAndWait(); assertTrue(coordinator.getAgents().isEmpty()); stateManager.clearAll(); assertTrue(coordinator.getAllSlotStatus().isEmpty()); config = new Config(); interactiveUser = new MockInteractiveUser(true); outputFormat = new MockOutputFormat(); } @AfterClass public void stopServer() throws Exception { provisioner.clearAgents(); provisioner.clearCoordinators(); if (coordinatorServer != null) { coordinatorServer.stop(); } if (binaryRepoDir != null) { deleteRecursively(binaryRepoDir); } if (expectedStateDir != null) { deleteRecursively(expectedStateDir); } if (serviceInventoryCacheDir != null) { deleteRecursively(serviceInventoryCacheDir); } if (localBinaryRepoDir != null) { deleteRecursively(localBinaryRepoDir); } } @Test public void testLocalModeHappyPath() throws Exception { execute("environment", "add", "local", coordinatorServer.getBaseUrl().toASCIIString()); execute("show"); assertNotNull(outputFormat.slots); assertEquals(outputFormat.slots.size(), 0); assertNull(outputFormat.coordinators); assertNull(outputFormat.agents); execute("agent", "show"); assertNull(outputFormat.slots); assertNull(outputFormat.slots); assertNull(outputFormat.coordinators); assertEquals(outputFormat.agents.size(), 0); execute("agent", "provision"); assertNull(outputFormat.slots); assertNull(outputFormat.slots); assertNull(outputFormat.coordinators); assertEquals(outputFormat.agents.size(), 1); AgentStatusRepresentation agent = outputFormat.agents.get(0); execute("coordinator", "show"); assertNull(outputFormat.slots); assertNull(outputFormat.slots); assertEquals(outputFormat.coordinators.size(), 1); assertNull(outputFormat.agents); execute("install", APPLE_ASSIGNMENT.getConfig(), APPLE_ASSIGNMENT.getBinary()); assertNotNull(outputFormat.slots); assertEquals(outputFormat.slots.size(), 1); SlotStatusRepresentation slot = outputFormat.slots.get(0); UUID slotId = slot.getId(); assertNotNull(slotId); assertSlotStatus(slot, slotId, APPLE_ASSIGNMENT, SlotLifecycleState.STOPPED, agent); assertNull(outputFormat.coordinators); assertEquals(outputFormat.agents.size(), 1); execute("start", "-c", APPLE_ASSIGNMENT.getConfig()); assertNotNull(outputFormat.slots); assertEquals(outputFormat.slots.size(), 1); assertSlotStatus(outputFormat.slots.get(0), slotId, APPLE_ASSIGNMENT, SlotLifecycleState.RUNNING, agent); assertNull(outputFormat.coordinators); assertNull(outputFormat.agents); execute("reset-to-actual", "-c", APPLE_ASSIGNMENT.getConfig()); assertNotNull(outputFormat.slots); assertEquals(outputFormat.slots.size(), 1); assertSlotStatus(outputFormat.slots.get(0), slotId, APPLE_ASSIGNMENT, SlotLifecycleState.RUNNING, agent); assertNull(outputFormat.coordinators); assertNull(outputFormat.agents); execute("stop", "-c", APPLE_ASSIGNMENT.getConfig()); assertNotNull(outputFormat.slots); assertEquals(outputFormat.slots.size(), 1); assertSlotStatus(outputFormat.slots.get(0), slotId, APPLE_ASSIGNMENT, SlotLifecycleState.STOPPED, agent); assertNull(outputFormat.coordinators); assertNull(outputFormat.agents); execute("restart", "-c", APPLE_ASSIGNMENT.getConfig()); assertNotNull(outputFormat.slots); assertEquals(outputFormat.slots.size(), 1); assertSlotStatus(outputFormat.slots.get(0), slotId, APPLE_ASSIGNMENT, SlotLifecycleState.RUNNING, agent); assertNull(outputFormat.coordinators); assertNull(outputFormat.agents); execute("upgrade", "-c", APPLE_ASSIGNMENT.getConfig(), "2.0", "@2.0"); assertNotNull(outputFormat.slots); assertEquals(outputFormat.slots.size(), 1); assertSlotStatus(outputFormat.slots.get(0), slotId, APPLE_ASSIGNMENT_2, SlotLifecycleState.RUNNING, agent); assertNull(outputFormat.coordinators); assertNull(outputFormat.agents); execute("start", "-c", APPLE_ASSIGNMENT_2.getConfig()); assertNotNull(outputFormat.slots); assertEquals(outputFormat.slots.size(), 1); assertSlotStatus(outputFormat.slots.get(0), slotId, APPLE_ASSIGNMENT_2, SlotLifecycleState.RUNNING, agent); assertNull(outputFormat.coordinators); assertNull(outputFormat.agents); execute("restart", "-c", APPLE_ASSIGNMENT_2.getConfig()); assertNotNull(outputFormat.slots); assertEquals(outputFormat.slots.size(), 1); assertSlotStatus(outputFormat.slots.get(0), slotId, APPLE_ASSIGNMENT_2, SlotLifecycleState.RUNNING, agent); assertNull(outputFormat.coordinators); assertNull(outputFormat.agents); execute("kill", "-c", APPLE_ASSIGNMENT_2.getConfig()); assertNotNull(outputFormat.slots); assertEquals(outputFormat.slots.size(), 1); assertSlotStatus(outputFormat.slots.get(0), slotId, APPLE_ASSIGNMENT_2, SlotLifecycleState.STOPPED, agent); assertNull(outputFormat.coordinators); assertNull(outputFormat.agents); execute("restart", "-c", APPLE_ASSIGNMENT_2.getConfig()); assertNotNull(outputFormat.slots); assertEquals(outputFormat.slots.size(), 1); assertSlotStatus(outputFormat.slots.get(0), slotId, APPLE_ASSIGNMENT_2, SlotLifecycleState.RUNNING, agent); assertNull(outputFormat.coordinators); assertNull(outputFormat.agents); execute("terminate", "-c", APPLE_ASSIGNMENT_2.getConfig()); assertNotNull(outputFormat.slots); assertEquals(outputFormat.slots.size(), 1); assertSlotStatus(outputFormat.slots.get(0), slotId, APPLE_ASSIGNMENT_2, SlotLifecycleState.RUNNING, agent); assertNull(outputFormat.coordinators); assertNull(outputFormat.agents); execute("stop", "-c", APPLE_ASSIGNMENT_2.getConfig()); assertNotNull(outputFormat.slots); assertEquals(outputFormat.slots.size(), 1); assertSlotStatus(outputFormat.slots.get(0), slotId, APPLE_ASSIGNMENT_2, SlotLifecycleState.STOPPED, agent); assertNull(outputFormat.coordinators); assertNull(outputFormat.agents); execute("terminate", "-c", APPLE_ASSIGNMENT_2.getConfig()); assertNotNull(outputFormat.slots); assertEquals(outputFormat.slots.size(), 1); assertSlotStatus(outputFormat.slots.get(0), slotId, APPLE_ASSIGNMENT_2, SlotLifecycleState.TERMINATED, agent); assertNull(outputFormat.coordinators); assertNull(outputFormat.agents); execute("show"); assertNotNull(outputFormat.slots); assertEquals(outputFormat.slots.size(), 0); assertNull(outputFormat.coordinators); assertNull(outputFormat.agents); } @Test public void testSlotFilter() throws Exception { execute("environment", "add", "local", coordinatorServer.getBaseUrl().toASCIIString()); execute("agent", "provision"); assertNull(outputFormat.slots); assertNull(outputFormat.slots); assertNull(outputFormat.coordinators); assertEquals(outputFormat.agents.size(), 1); execute("agent", "provision"); assertNull(outputFormat.slots); assertNull(outputFormat.slots); assertNull(outputFormat.coordinators); assertEquals(outputFormat.agents.size(), 1); AgentStatusRepresentation agent = outputFormat.agents.get(0); execute("install", APPLE_ASSIGNMENT.getConfig(), APPLE_ASSIGNMENT.getBinary()); assertNotNull(outputFormat.slots); assertEquals(outputFormat.slots.size(), 1); execute("install", APPLE_ASSIGNMENT.getConfig(), APPLE_ASSIGNMENT.getBinary()); assertNotNull(outputFormat.slots); assertEquals(outputFormat.slots.size(), 1); execute("install", BANANA_ASSIGNMENT.getConfig(), BANANA_ASSIGNMENT.getBinary()); assertNotNull(outputFormat.slots); assertEquals(outputFormat.slots.size(), 1); execute("install", BANANA_ASSIGNMENT.getConfig(), BANANA_ASSIGNMENT.getBinary()); assertNotNull(outputFormat.slots); assertEquals(outputFormat.slots.size(), 1); ListMultimap<Assignment, SlotStatusRepresentation> slots; execute("show"); slots = slotsByAssignment(); assertEquals(slots.get(APPLE_ASSIGNMENT).size(), 2); assertEquals(slots.get(BANANA_ASSIGNMENT_EXACT).size(), 2); String appleSlotId = slots.get(APPLE_ASSIGNMENT).get(0).getId().toString(); String appleShortSlotId = slots.get(APPLE_ASSIGNMENT).get(0).getShortId(); String bananaSlotId = slots.get(BANANA_ASSIGNMENT_EXACT).get(0).getId().toString(); String bananaShortSlotId = slots.get(BANANA_ASSIGNMENT_EXACT).get(0).getShortId(); execute("show", "--all"); slots = slotsByAssignment(); assertEquals(slots.get(APPLE_ASSIGNMENT).size(), 2); assertEquals(slots.get(BANANA_ASSIGNMENT_EXACT).size(), 2); execute("show", "-u", appleSlotId); slots = slotsByAssignment(); assertEquals(slots.get(APPLE_ASSIGNMENT).size(), 1); assertEquals(slots.get(APPLE_ASSIGNMENT).get(0).getId().toString(), appleSlotId); assertEquals(slots.get(BANANA_ASSIGNMENT_EXACT).size(), 0); execute("show", "-u", appleShortSlotId, "-u", bananaShortSlotId); slots = slotsByAssignment(); assertEquals(slots.get(APPLE_ASSIGNMENT).size(), 1); assertEquals(slots.get(APPLE_ASSIGNMENT).get(0).getId().toString(), appleSlotId); assertEquals(slots.get(BANANA_ASSIGNMENT_EXACT).size(), 1); assertEquals(slots.get(BANANA_ASSIGNMENT_EXACT).get(0).getId().toString(), bananaSlotId); execute("show", "-u", appleSlotId, "-u", bananaSlotId); slots = slotsByAssignment(); assertEquals(slots.get(APPLE_ASSIGNMENT).size(), 1); assertEquals(slots.get(APPLE_ASSIGNMENT).get(0).getId().toString(), appleSlotId); assertEquals(slots.get(BANANA_ASSIGNMENT_EXACT).size(), 1); assertEquals(slots.get(BANANA_ASSIGNMENT_EXACT).get(0).getId().toString(), bananaSlotId); execute("show", "-c", APPLE_ASSIGNMENT.getConfig()); slots = slotsByAssignment(); assertEquals(slots.get(APPLE_ASSIGNMENT).size(), 2); assertEquals(slots.get(BANANA_ASSIGNMENT_EXACT).size(), 0); execute("show", "-b", APPLE_ASSIGNMENT.getBinary()); slots = slotsByAssignment(); assertEquals(slots.get(APPLE_ASSIGNMENT).size(), 2); assertEquals(slots.get(BANANA_ASSIGNMENT_EXACT).size(), 0); execute("show", "-h", agent.getInternalHost()); slots = slotsByAssignment(); assertEquals(slots.get(APPLE_ASSIGNMENT).size(), 2); assertEquals(slots.get(BANANA_ASSIGNMENT_EXACT).size(), 2); execute("show", "-h", agent.getInternalIp()); slots = slotsByAssignment(); assertEquals(slots.get(APPLE_ASSIGNMENT).size(), 2); assertEquals(slots.get(BANANA_ASSIGNMENT_EXACT).size(), 2); execute("show", "-h", agent.getExternalHost()); slots = slotsByAssignment(); assertEquals(slots.get(APPLE_ASSIGNMENT).size(), 2); assertEquals(slots.get(BANANA_ASSIGNMENT_EXACT).size(), 2); execute("show", "-m", agent.getInstanceId()); slots = slotsByAssignment(); assertEquals(slots.get(APPLE_ASSIGNMENT).size(), 1); assertEquals(slots.get(BANANA_ASSIGNMENT_EXACT).size(), 1); execute("start", "-b", BANANA_ASSIGNMENT_EXACT.getBinary()); slots = slotsByAssignment(); assertEquals(slots.get(APPLE_ASSIGNMENT).size(), 0); assertEquals(slots.get(BANANA_ASSIGNMENT_EXACT).size(), 2); execute("start", "-s", SlotLifecycleState.RUNNING.toString()); slots = slotsByAssignment(); assertEquals(slots.get(APPLE_ASSIGNMENT).size(), 0); assertEquals(slots.get(BANANA_ASSIGNMENT_EXACT).size(), 2); execute("start", "-s", SlotLifecycleState.STOPPED.toString()); slots = slotsByAssignment(); assertEquals(slots.get(APPLE_ASSIGNMENT).size(), 2); assertEquals(slots.get(BANANA_ASSIGNMENT_EXACT).size(), 0); } private ListMultimap<Assignment, SlotStatusRepresentation> slotsByAssignment() { assertNotNull(outputFormat.slots); ArrayListMultimap<Assignment, SlotStatusRepresentation> slotsByAssignment = ArrayListMultimap.create(); for (SlotStatusRepresentation slot : outputFormat.slots) { slotsByAssignment.put(new Assignment(slot.getBinary(), slot.getConfig()), slot); } return slotsByAssignment; } private void assertSlotStatus(SlotStatusRepresentation slot, UUID expectedSlotId, Assignment expectedAssignment, SlotLifecycleState expectedState, AgentStatusRepresentation expectedAgent) { assertEquals(slot.getId(), expectedSlotId); if (expectedState != SlotLifecycleState.TERMINATED) { assertEquals(slot.getBinary(), expectedAssignment.getBinary()); assertEquals(slot.getConfig(), expectedAssignment.getConfig()); assertNotNull(slot.getInstallPath()); } else { assertNull(slot.getBinary()); assertNull(slot.getConfig()); assertNull(slot.getInstallPath()); } assertEquals(slot.getStatus(), expectedState.toString()); assertEquals(slot.getInstanceId(), expectedAgent.getInstanceId()); assertTrue(slot.getLocation().startsWith(expectedAgent.getLocation())); assertTrue(slot.getLocation().endsWith(slot.getShortLocation())); assertTrue(slot.getSelf().toASCIIString().startsWith(expectedAgent.getSelf().toASCIIString())); assertTrue(slot.getExternalUri().toASCIIString().startsWith(expectedAgent.getExternalUri().toASCIIString())); } private void execute(String... args) throws Exception { outputFormat.clear(); AirshipCommand command = Airship.AIRSHIP_PARSER.parse(ImmutableList.<String>builder().add("--debug").add(args).build()); command.config = config; if (command instanceof Airship.AirshipCommanderCommand) { Airship.AirshipCommanderCommand airshipCommanderCommand = (Airship.AirshipCommanderCommand) command; airshipCommanderCommand.execute("local", outputFormat, interactiveUser); } else { command.execute(); } } private static class MockOutputFormat implements OutputFormat { private List<CoordinatorStatusRepresentation> coordinators; private List<AgentStatusRepresentation> agents; private List<SlotStatusRepresentation> slots; public void clear() { coordinators = null; agents = null; slots = null; } @Override public void displayCoordinators(Iterable<CoordinatorStatusRepresentation> coordinators) { this.coordinators = ImmutableList.copyOf(coordinators); } @Override public void displayAgents(Iterable<AgentStatusRepresentation> agents) { this.agents = ImmutableList.copyOf(agents); } @Override public void displaySlots(Iterable<SlotStatusRepresentation> slots) { this.slots = ImmutableList.copyOf(slots); } } private static class MockInteractiveUser implements InteractiveUser { private boolean answer; private MockInteractiveUser(boolean answer) { this.answer = answer; } @Override public boolean ask(String question, boolean defaultValue) { return answer; } } }