private void setAttachmentInfo(
      String volumeId, JsonNode attachment, DateTime detachTime, Resource resource) {
    String instanceId = null;
    if (attachment != null) {
      boolean deleteOnTermination = attachment.get(DELETE_ON_TERMINATION).getBooleanValue();
      if (deleteOnTermination) {
        LOGGER.info(
            String.format("Volume %s had set the deleteOnTermination flag as true", volumeId));
      }
      resource.setAdditionalField(DELETE_ON_TERMINATION, String.valueOf(deleteOnTermination));
      instanceId = attachment.get("instanceId").getTextValue();
    }
    // The subclass can customize the way to get the owner for a volume
    String owner = getOwnerEmailForResource(resource);
    if (owner == null && instanceId != null) {
      owner = instanceToOwner.get(instanceId);
    }
    resource.setOwnerEmail(owner);

    String metaTag = makeMetaTag(instanceId, owner, detachTime);
    LOGGER.info(String.format("Setting Janitor Metatag as %s for volume %s", metaTag, volumeId));
    resource.setTag(JanitorMonkey.JANITOR_META_TAG, metaTag);

    LOGGER.info(String.format("The last detach time of volume %s is %s", volumeId, detachTime));
    resource.setAdditionalField(DETACH_TIME, String.valueOf(detachTime.getMillis()));
  }
  private Resource parseJsonElementToVolumeResource(String region, JsonNode jsonNode) {
    Validate.notNull(jsonNode);
    long createTime = jsonNode.get("createTime").asLong();

    Resource resource =
        new AWSResource()
            .withId(jsonNode.get("volumeId").getTextValue())
            .withRegion(region)
            .withResourceType(AWSResourceType.EBS_VOLUME)
            .withLaunchTime(new Date(createTime));

    JsonNode tags = jsonNode.get("tags");
    StringBuilder description = new StringBuilder();
    JsonNode size = jsonNode.get("size");
    description.append(String.format("size=%s", size == null ? "unknown" : size.getIntValue()));

    if (tags == null || !tags.isArray() || tags.size() == 0) {
      LOGGER.debug(String.format("No tags is found for %s", resource.getId()));
    } else {
      for (Iterator<JsonNode> it = tags.getElements(); it.hasNext(); ) {
        JsonNode tag = it.next();
        String key = tag.get("key").getTextValue();
        String value = tag.get("value").getTextValue();
        description.append(String.format("; %s=%s", key, value));
        resource.setTag(key, value);
        if (key.equals(PURPOSE)) {
          resource.setAdditionalField(PURPOSE, value);
        }
      }
      resource.setDescription(description.toString());
    }
    ((AWSResource) resource).setAWSResourceState(jsonNode.get("state").getTextValue());
    return resource;
  }
 private void markResource(Resource resource, DateTime now) {
   if (resource.getExpectedTerminationTime() == null) {
     Date terminationTime = calendar.getBusinessDay(new Date(now.getMillis()), retentionDays);
     resource.setExpectedTerminationTime(terminationTime);
     resource.setTerminationReason(
         String.format(
             "Launch config older than %d days. Not in Discovery. No ELB.",
             launchConfigAgeThreshold + retentionDays));
   } else {
     LOGGER.info(
         String.format("Resource %s is already marked as cleanup candidate.", resource.getId()));
   }
 }
 private String getBatchUrl(String region, List<Resource> batch) {
   StringBuilder batchUrl = new StringBuilder(eddaClient.getBaseUrl(region) + "/aws/volumes/");
   boolean isFirst = true;
   for (Resource resource : batch) {
     if (!isFirst) {
       batchUrl.append(',');
     } else {
       isFirst = false;
     }
     batchUrl.append(resource.getId());
   }
   batchUrl.append(
       ";data.state=in-use;_since=0;_expand;_meta:"
           + "(ltime,data:(volumeId,attachments:(deleteOnTermination,instanceId)))");
   return batchUrl.toString();
 }
  /** {@inheritDoc} */
  @Override
  public boolean isValid(Resource resource) {
    Validate.notNull(resource);
    if (!"ASG".equals(resource.getResourceType().name())) {
      return true;
    }

    if (StringUtils.isNotEmpty(resource.getAdditionalField(ASGJanitorCrawler.ASG_FIELD_ELBS))) {
      LOGGER.info(String.format("ASG %s has ELBs.", resource.getId()));
      return true;
    }

    if (instanceValidator.hasActiveInstance(resource)) {
      LOGGER.info(String.format("ASG %s has active instance.", resource.getId()));
      return true;
    }

    String lcName = resource.getAdditionalField(ASGJanitorCrawler.ASG_FIELD_LC_NAME);
    DateTime now = new DateTime(calendar.now().getTimeInMillis());
    if (StringUtils.isEmpty(lcName)) {
      LOGGER.error(
          String.format("Failed to find launch configuration for ASG %s", resource.getId()));
      markResource(resource, now);
      return false;
    }

    String lcCreationTime =
        resource.getAdditionalField(ASGJanitorCrawler.ASG_FIELD_LC_CREATION_TIME);
    if (StringUtils.isEmpty(lcCreationTime)) {
      LOGGER.error(
          String.format("Failed to find creation time for launch configuration %s", lcName));
      return true;
    }

    DateTime createTime = new DateTime(Long.parseLong(lcCreationTime));
    if (now.isBefore(createTime.plusDays(launchConfigAgeThreshold))) {
      LOGGER.info(
          String.format(
              "The launch configuation %s has not been created for more than %d days",
              lcName, launchConfigAgeThreshold));
      return true;
    }
    LOGGER.info(
        String.format(
            "The launch configuation %s has been created for more than %d days",
            lcName, launchConfigAgeThreshold));

    if (lastChangeDaysThreshold != null) {
      String lastChangeTimeField =
          resource.getAdditionalField(EddaASGJanitorCrawler.ASG_FIELD_LAST_CHANGE_TIME);
      if (StringUtils.isNotBlank(lastChangeTimeField)) {
        DateTime lastChangeTime = new DateTime(Long.parseLong(lastChangeTimeField));
        if (lastChangeTime.plusDays(lastChangeDaysThreshold).isAfter(now)) {
          LOGGER.info(
              String.format(
                  "ASG %s had change during the last %d days",
                  resource.getId(), lastChangeDaysThreshold));
          return true;
        }
      }
    }

    markResource(resource, now);
    return false;
  }
  /**
   * Adds information of last attachment to the resources. To be compatible with the AWS
   * implementation of the same crawler, add the information to the JANITOR_META tag. It always uses
   * the latest information to update the tag in this resource (not writing back to AWS) no matter
   * if the tag exists.
   *
   * @param resources the volume resources
   */
  private void addLastAttachmentInfo(List<Resource> resources) {
    Validate.notNull(resources);
    LOGGER.info(
        String.format("Updating the latest attachment info for %d resources", resources.size()));
    Map<String, List<Resource>> regionToResources = Maps.newHashMap();
    for (Resource resource : resources) {
      List<Resource> regionalList = regionToResources.get(resource.getRegion());
      if (regionalList == null) {
        regionalList = Lists.newArrayList();
        regionToResources.put(resource.getRegion(), regionalList);
      }
      regionalList.add(resource);
    }
    for (Map.Entry<String, List<Resource>> entry : regionToResources.entrySet()) {
      LOGGER.info(
          String.format(
              "Updating the latest attachment info for %d resources in region %s",
              resources.size(), entry.getKey()));
      for (List<Resource> batch : Lists.partition(entry.getValue(), BATCH_SIZE)) {
        LOGGER.info(String.format("Processing batch of size %d", batch.size()));
        String batchUrl = getBatchUrl(entry.getKey(), batch);
        JsonNode batchResult = null;
        try {
          batchResult = eddaClient.getJsonNodeFromUrl(batchUrl);
        } catch (IOException e) {
          LOGGER.error("Failed to get response for the batch.", e);
        }
        Map<String, Resource> idToResource = Maps.newHashMap();
        for (Resource resource : batch) {
          idToResource.put(resource.getId(), resource);
        }
        if (batchResult == null || !batchResult.isArray()) {
          throw new RuntimeException(
              String.format(
                  "Failed to get valid document from %s, got: %s", batchUrl, batchResult));
        }

        Set<String> processedIds = Sets.newHashSet();
        for (Iterator<JsonNode> it = batchResult.getElements(); it.hasNext(); ) {
          JsonNode elem = it.next();
          JsonNode data = elem.get("data");
          String volumeId = data.get("volumeId").getTextValue();
          Resource resource = idToResource.get(volumeId);
          JsonNode attachments = data.get("attachments");

          Validate.isTrue(attachments.isArray() && attachments.size() > 0);
          JsonNode attachment = attachments.get(0);

          JsonNode ltime = elem.get("ltime");
          if (ltime == null || ltime.isNull()) {
            continue;
          }
          DateTime detachTime = new DateTime(ltime.asLong());
          processedIds.add(volumeId);
          setAttachmentInfo(volumeId, attachment, detachTime, resource);
        }

        for (Map.Entry<String, Resource> volumeEntry : idToResource.entrySet()) {
          String id = volumeEntry.getKey();
          if (!processedIds.contains(id)) {
            Resource resource = volumeEntry.getValue();
            LOGGER.info(
                String.format(
                    "Volume %s never was attached, use createTime %s as the detachTime",
                    id, resource.getLaunchTime()));
            setAttachmentInfo(id, null, new DateTime(resource.getLaunchTime().getTime()), resource);
          }
        }
      }
    }
  }