/** * Gets all volumes that are not attached to any instance. Janitor Monkey only considers * unattached volumes as cleanup candidates, so there is no need to get volumes that are in-use. * * @param region * @return */ private List<Resource> getUnattachedVolumeResourcesInRegion(String region, String... volumeIds) { String url = eddaClient.getBaseUrl(region) + "/aws/volumes;"; if (volumeIds != null && volumeIds.length != 0) { url += StringUtils.join(volumeIds, ','); LOGGER.info( String.format("Getting volumes in region %s for %d ids", region, volumeIds.length)); } else { LOGGER.info(String.format("Getting all unattached volumes in region %s", region)); } url += ";state=available;_expand:(volumeId,createTime,size,state,tags)"; JsonNode jsonNode = null; try { jsonNode = eddaClient.getJsonNodeFromUrl(url); } catch (Exception e) { LOGGER.error( String.format( "Failed to get Jason node from edda for unattached volumes in region %s.", region), e); } if (jsonNode == null || !jsonNode.isArray()) { throw new RuntimeException( String.format("Failed to get valid document from %s, got: %s", url, jsonNode)); } List<Resource> resources = Lists.newArrayList(); for (Iterator<JsonNode> it = jsonNode.getElements(); it.hasNext(); ) { resources.add(parseJsonElementToVolumeResource(region, it.next())); } return resources; }
private void updateInstanceToOwner(String region) { LOGGER.info(String.format("Getting owners for all instances in region %s", region)); long startTime = DateTime.now().minusDays(LOOKBACK_DAYS).getMillis(); String url = String.format( "%s/view/instances;_since=%d;state.name=running;tags.key=owner;" + "_expand:(instanceId,tags:(key,value))", eddaClient.getBaseUrl(region), startTime); JsonNode jsonNode = null; try { jsonNode = eddaClient.getJsonNodeFromUrl(url); } catch (Exception e) { LOGGER.error( String.format( "Failed to get Jason node from edda for instance owners in region %s.", region), e); } if (jsonNode == null || !jsonNode.isArray()) { throw new RuntimeException( String.format("Failed to get valid document from %s, got: %s", url, jsonNode)); } for (Iterator<JsonNode> it = jsonNode.getElements(); it.hasNext(); ) { JsonNode elem = it.next(); String instanceId = elem.get("instanceId").getTextValue(); JsonNode tags = elem.get("tags"); if (tags == null || !tags.isArray() || tags.size() == 0) { continue; } for (Iterator<JsonNode> tagsIt = tags.getElements(); tagsIt.hasNext(); ) { JsonNode tag = tagsIt.next(); String tagKey = tag.get("key").getTextValue(); if ("owner".equals(tagKey)) { instanceToOwner.put(instanceId, tag.get("value").getTextValue()); break; } } } }
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(); }
/** * 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); } } } } }