@Override
  public ImmutableList<ServiceDescriptor> getServiceInventory(Iterable<SlotStatus> allSlotStatus) {
    ImmutableList.Builder<ServiceDescriptor> newDescriptors = ImmutableList.builder();
    for (SlotStatus slotStatus : allSlotStatus) {
      // if the self reference is null, the slot is totally offline so skip for now
      if (slotStatus.getSelf() == null) {
        continue;
      }

      List<ServiceDescriptor> serviceDescriptors = getServiceInventory(slotStatus);
      if (serviceDescriptors == null) {
        continue;
      }
      for (ServiceDescriptor serviceDescriptor : serviceDescriptors) {
        newDescriptors.add(
            new ServiceDescriptor(
                null,
                slotStatus.getId().toString(),
                serviceDescriptor.getType(),
                serviceDescriptor.getPool(),
                slotStatus.getLocation(),
                slotStatus.getState() == SlotLifecycleState.RUNNING
                    ? ServiceState.RUNNING
                    : ServiceState.STOPPED,
                interpolateProperties(serviceDescriptor.getProperties(), slotStatus)));
      }
    }
    return newDescriptors.build();
  }
  @Test
  public void testTerminate() throws Exception {
    initializeOneAgent();

    AgentStatus agentStatus = coordinator.getAgentByAgentId(agentId);
    SlotStatus apple1Status = agentStatus.getSlotStatus(apple1SotId);
    SlotStatus apple2Status = agentStatus.getSlotStatus(apple2SlotId);

    Request request =
        Request.Builder.prepareDelete()
            .setUri(
                coordinatorUriBuilder()
                    .appendPath("/v1/slot")
                    .addParameter("host", "apple*")
                    .build())
            .build();
    List<SlotStatusRepresentation> actual =
        httpClient.execute(
            request, createJsonResponseHandler(slotStatusesCodec, Status.OK.getStatusCode()));

    apple1Status = apple1Status.changeState(TERMINATED);
    apple2Status = apple2Status.changeState(TERMINATED);
    SlotStatus bananaStatus = coordinator.getAgentByAgentId(agentId).getSlotStatus(bananaSlotId);

    List<SlotStatusRepresentation> expected =
        ImmutableList.of(
            slotStatusRepresentationFactory.create(apple1Status),
            slotStatusRepresentationFactory.create(apple2Status.changeState(TERMINATED)));

    assertEqualsNoOrder(actual, expected);

    assertEquals(apple1Status.getState(), TERMINATED);
    assertEquals(apple2Status.getState(), TERMINATED);
    assertEquals(bananaStatus.getState(), STOPPED);
  }
  private void assertOkResponse(Response response, SlotLifecycleState state, UUID... slotIds) {
    assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());

    AgentStatus agentStatus = coordinator.getAgentByAgentId(agentId);
    Builder<SlotStatusRepresentation> builder = ImmutableList.builder();
    for (UUID slotId : slotIds) {
      SlotStatus slotStatus = agentStatus.getSlotStatus(slotId);
      builder.add(
          SlotStatusRepresentation.from(slotStatus.changeState(state), prefixSize, MOCK_REPO));
      assertEquals(slotStatus.getAssignment(), APPLE_ASSIGNMENT);
    }
    assertEqualsNoOrder((Collection<?>) response.getEntity(), builder.build());
    assertNull(
        response
            .getMetadata()
            .get("Content-Type")); // content type is set by jersey based on @Produces
  }
 private Map<String, String> interpolateProperties(
     Map<String, String> properties, SlotStatus slotStatus) {
   ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
   for (Entry<String, String> entry : properties.entrySet()) {
     String key = entry.getKey();
     String value = entry.getValue();
     value = value.replaceAll(Pattern.quote("${airship.host}"), slotStatus.getSelf().getHost());
     builder.put(key, value);
   }
   return builder.build();
 }
  @Test
  public void testLifecycleUnknown() throws Exception {
    initializeOneAgent();

    Request request =
        Request.Builder.preparePut()
            .setUri(
                coordinatorUriBuilder()
                    .appendPath("/v1/slot/lifecycle")
                    .addParameter("binary", "apple:*")
                    .build())
            .setBodyGenerator(createStaticBodyGenerator("unknown", UTF_8))
            .build();
    StatusResponse response = httpClient.execute(request, createStatusResponseHandler());

    AgentStatus agentStatus = coordinator.getAgentByAgentId(agentId);
    SlotStatus apple1Status = agentStatus.getSlotStatus(apple1SotId);
    SlotStatus apple2Status = agentStatus.getSlotStatus(apple2SlotId);
    SlotStatus bananaStatus = agentStatus.getSlotStatus(bananaSlotId);

    assertEquals(response.getStatusCode(), Status.BAD_REQUEST.getStatusCode());
    assertEquals(apple1Status.getState(), STOPPED);
    assertEquals(apple2Status.getState(), STOPPED);
    assertEquals(bananaStatus.getState(), STOPPED);
  }
  @Test
  public void testKill() throws Exception {
    initializeOneAgent();

    coordinator.setState(RUNNING, Predicates.<SlotStatus>alwaysTrue(), null);

    Request request =
        Request.Builder.preparePut()
            .setUri(
                coordinatorUriBuilder()
                    .appendPath("/v1/slot/lifecycle")
                    .addParameter("binary", "apple:*")
                    .build())
            .setBodyGenerator(createStaticBodyGenerator("killing", UTF_8))
            .build();
    List<SlotStatusRepresentation> actual =
        httpClient.execute(
            request, createJsonResponseHandler(slotStatusesCodec, Status.OK.getStatusCode()));

    AgentStatus agentStatus = coordinator.getAgentByAgentId(agentId);
    SlotStatus apple1Status = agentStatus.getSlotStatus(apple1SotId);
    SlotStatus apple2Status = agentStatus.getSlotStatus(apple2SlotId);
    SlotStatus bananaStatus = agentStatus.getSlotStatus(bananaSlotId);

    List<SlotStatusRepresentation> expected =
        ImmutableList.of(
            slotStatusRepresentationFactory.create(apple1Status),
            slotStatusRepresentationFactory.create(apple2Status));

    assertEqualsNoOrder(actual, expected);
    assertEquals(apple1Status.getState(), STOPPED);
    assertEquals(apple2Status.getState(), STOPPED);
    assertEquals(bananaStatus.getState(), RUNNING);
  }
  @Test
  public void testUpgrade() throws Exception {
    initializeOneAgent();

    UpgradeVersions upgradeVersions = new UpgradeVersions("2.0", "2.0");
    Request request =
        Request.Builder.preparePost()
            .setUri(
                coordinatorUriBuilder()
                    .appendPath("/v1/slot/assignment")
                    .addParameter("host", "apple*")
                    .build())
            .setHeader(CONTENT_TYPE, APPLICATION_JSON)
            .setBodyGenerator(jsonBodyGenerator(upgradeVersionsCodec, upgradeVersions))
            .build();
    List<SlotStatusRepresentation> actual =
        httpClient.execute(
            request, createJsonResponseHandler(slotStatusesCodec, Status.OK.getStatusCode()));

    AgentStatus agentStatus = coordinator.getAgentByAgentId(agentId);
    SlotStatus apple1Status = agentStatus.getSlotStatus(apple1SotId);
    SlotStatus apple2Status = agentStatus.getSlotStatus(apple2SlotId);
    SlotStatus bananaStatus = agentStatus.getSlotStatus(bananaSlotId);

    List<SlotStatusRepresentation> expected =
        ImmutableList.of(
            slotStatusRepresentationFactory.create(apple1Status),
            slotStatusRepresentationFactory.create(apple2Status));

    assertEqualsNoOrder(actual, expected);

    assertEquals(apple1Status.getState(), STOPPED);
    assertEquals(apple2Status.getState(), STOPPED);
    assertEquals(bananaStatus.getState(), STOPPED);

    assertEquals(
        apple1Status.getAssignment(),
        upgradeVersions.upgradeAssignment(MOCK_REPO, APPLE_ASSIGNMENT));
    assertEquals(
        apple2Status.getAssignment(),
        upgradeVersions.upgradeAssignment(MOCK_REPO, APPLE_ASSIGNMENT));
    assertEquals(bananaStatus.getAssignment(), BANANA_ASSIGNMENT);
  }
  private List<ServiceDescriptor> getServiceInventory(SlotStatus slotStatus) {
    Assignment assignment = slotStatus.getAssignment();
    if (assignment == null) {
      return null;
    }

    String config = assignment.getConfig();

    File cacheFile = getCacheFile(config);
    if (cacheFile.canRead()) {
      try {
        String json = CharStreams.toString(Files.newReaderSupplier(cacheFile, Charsets.UTF_8));
        List<ServiceDescriptor> descriptors = descriptorsJsonCodec.fromJson(json);
        invalidServiceInventory.remove(config);
        return descriptors;
      } catch (Exception ignored) {
        // delete the bad cache file
        cacheFile.delete();
      }
    }

    InputSupplier<? extends InputStream> configFile =
        ConfigUtils.newConfigEntrySupplier(repository, config, "airship-service-inventory.json");
    if (configFile == null) {
      return null;
    }

    try {
      String json;
      try {
        json = CharStreams.toString(CharStreams.newReaderSupplier(configFile, Charsets.UTF_8));
      } catch (FileNotFoundException e) {
        // no service inventory in the config, so replace with json null so caching works
        json = "null";
      }
      invalidServiceInventory.remove(config);

      // cache json
      cacheFile.getParentFile().mkdirs();
      Files.write(json, cacheFile, Charsets.UTF_8);

      List<ServiceDescriptor> descriptors = descriptorsJsonCodec.fromJson(json);
      return descriptors;
    } catch (Exception e) {
      if (invalidServiceInventory.add(config)) {
        log.error(e, "Unable to read service inventory for %s" + config);
      }
    }
    return null;
  }
  @BeforeMethod
  public void setup() throws Exception {
    NodeInfo nodeInfo = new NodeInfo("testing");

    MockProvisioner provisioner = new MockProvisioner();
    coordinator =
        new Coordinator(
            nodeInfo,
            new HttpServerInfo(new HttpServerConfig(), nodeInfo),
            new CoordinatorConfig().setStatusExpiration(new Duration(1, TimeUnit.DAYS)),
            provisioner.getCoordinatorFactory(),
            provisioner.getAgentFactory(),
            MOCK_REPO,
            provisioner,
            new InMemoryStateManager(),
            new MockServiceInventory());
    resource = new CoordinatorLifecycleResource(coordinator, MOCK_REPO);

    apple1SlotId = UUID.randomUUID();
    SlotStatus appleSlotStatus1 =
        createSlotStatus(
            apple1SlotId,
            URI.create("fake://foo/v1/agent/slot/apple1"),
            URI.create("fake://foo/v1/agent/slot/apple1"),
            "instance",
            "/location",
            STOPPED,
            APPLE_ASSIGNMENT,
            "/apple1",
            ImmutableMap.<String, Integer>of());
    apple2SlotId = UUID.randomUUID();
    SlotStatus appleSlotStatus2 =
        createSlotStatus(
            apple2SlotId,
            URI.create("fake://foo/v1/agent/slot/apple1"),
            URI.create("fake://foo/v1/agent/slot/apple1"),
            "instance",
            "/location",
            STOPPED,
            APPLE_ASSIGNMENT,
            "/apple2",
            ImmutableMap.<String, Integer>of());
    bananaSlotId = UUID.randomUUID();
    SlotStatus bananaSlotStatus =
        createSlotStatus(
            bananaSlotId,
            URI.create("fake://foo/v1/agent/slot/banana"),
            URI.create("fake://foo/v1/agent/slot/banana"),
            "instance",
            "/location",
            STOPPED,
            BANANA_ASSIGNMENT,
            "/banana",
            ImmutableMap.<String, Integer>of());

    agentId = UUID.randomUUID().toString();
    AgentStatus agentStatus =
        new AgentStatus(
            agentId,
            ONLINE,
            "instance-id",
            URI.create("fake://foo/"),
            URI.create("fake://foo/"),
            "/unknown/location",
            "instance.type",
            ImmutableList.of(appleSlotStatus1, appleSlotStatus2, bananaSlotStatus),
            ImmutableMap.of("cpu", 8, "memory", 1024));

    prefixSize =
        shortestUniquePrefix(
            asList(
                appleSlotStatus1.getId().toString(),
                appleSlotStatus2.getId().toString(),
                bananaSlotStatus.getId().toString()),
            MIN_PREFIX_SIZE);

    provisioner.addAgents(agentStatus);
    coordinator.updateAllAgents();
  }