package com.sequenceiq.cloudbreak.cloud.aws.task; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import com.amazonaws.services.autoscaling.AmazonAutoScalingClient; import com.amazonaws.services.autoscaling.model.Activity; import com.amazonaws.services.autoscaling.model.DescribeScalingActivitiesRequest; import com.amazonaws.services.ec2.AmazonEC2Client; import com.amazonaws.services.ec2.model.DescribeInstanceStatusRequest; import com.amazonaws.services.ec2.model.DescribeInstanceStatusResult; import com.amazonaws.services.ec2.model.DescribeSpotInstanceRequestsRequest; import com.amazonaws.services.ec2.model.DescribeSpotInstanceRequestsResult; import com.amazonaws.services.ec2.model.InstanceStatus; import com.amazonaws.services.ec2.model.SpotInstanceRequest; import com.google.common.collect.Lists; import com.sequenceiq.cloudbreak.cloud.aws.AwsClient; import com.sequenceiq.cloudbreak.cloud.aws.CloudFormationStackUtil; import com.sequenceiq.cloudbreak.cloud.aws.view.AwsCredentialView; import com.sequenceiq.cloudbreak.cloud.context.AuthenticatedContext; import com.sequenceiq.cloudbreak.cloud.exception.CloudConnectorException; import com.sequenceiq.cloudbreak.cloud.task.PollBooleanStateTask; @Component(ASGroupStatusCheckerTask.NAME) @Scope(value = "prototype") public class ASGroupStatusCheckerTask extends PollBooleanStateTask { public static final String NAME = "aSGroupStatusCheckerTask"; private static final int MAX_INSTANCE_ID_SIZE = 100; private static final int INSTANCE_RUNNING = 16; private static final int COMPLETED = 100; private static final String CANCELLED = "Cancelled"; private static final String WAIT_FOR_SPOT_INSTANCES_STATUS_CODE = "WaitingForSpotInstanceId"; private static final String SPOT_ID_PATTERN = "sir-[a-z0-9]{8}"; private static final String LOW_SPOT_PRICE_STATUS_CODE = "price-too-low"; private static final Logger LOGGER = LoggerFactory.getLogger(ASGroupStatusCheckerTask.class); private String autoScalingGroupName; private Integer requiredInstances; private AwsClient awsClient; private CloudFormationStackUtil cloudFormationStackUtil; private AmazonAutoScalingClient autoScalingClient; private Optional<Activity> latestActivity; public ASGroupStatusCheckerTask(AuthenticatedContext authenticatedContext, String asGroupName, Integer requiredInstances, AwsClient awsClient, CloudFormationStackUtil cloudFormationStackUtil) { super(authenticatedContext, true); this.autoScalingGroupName = asGroupName; this.requiredInstances = requiredInstances; this.awsClient = awsClient; this.cloudFormationStackUtil = cloudFormationStackUtil; this.autoScalingClient = awsClient.createAutoScalingClient(new AwsCredentialView(getAuthenticatedContext().getCloudCredential()), getAuthenticatedContext().getCloudContext().getLocation().getRegion().value()); List<Activity> autoScalingActivities = getAutoScalingActivities(); this.latestActivity = autoScalingActivities.stream().findFirst(); } @Override public Boolean call() { LOGGER.info("Checking status of Auto Scaling group '{}'", autoScalingGroupName); AmazonEC2Client amazonEC2Client = awsClient.createAccess(new AwsCredentialView(getAuthenticatedContext().getCloudCredential()), getAuthenticatedContext().getCloudContext().getLocation().getRegion().value()); List<String> instanceIds = cloudFormationStackUtil.getInstanceIds(autoScalingClient, autoScalingGroupName); if (instanceIds.size() < requiredInstances) { LOGGER.debug("Instances in AS group: {}, needed: {}", instanceIds.size(), requiredInstances); List<Activity> activities = getAutoScalingActivities(); if (latestActivity.isPresent()) { checkForSpotRequest(latestActivity.get(), amazonEC2Client); activities = activities.stream().filter(activity -> activity.getStartTime().after(latestActivity.get().getStartTime())) .collect(Collectors.toList()); } for (Activity activity : activities) { if (activity.getProgress().equals(COMPLETED) && CANCELLED.equals(activity.getStatusCode())) { throw new CloudConnectorException(activity.getStatusMessage()); } } return false; } List<DescribeInstanceStatusResult> describeInstanceStatusResultList = new ArrayList<>(); List<List<String>> partitionedInstanceIdsList = Lists.partition(instanceIds, MAX_INSTANCE_ID_SIZE); for (List<String> partitionedInstanceIds : partitionedInstanceIdsList) { DescribeInstanceStatusRequest describeInstanceStatusRequest = new DescribeInstanceStatusRequest().withInstanceIds(partitionedInstanceIds); DescribeInstanceStatusResult describeResult = amazonEC2Client.describeInstanceStatus(describeInstanceStatusRequest); describeInstanceStatusResultList.add(describeResult); } List<InstanceStatus> instanceStatusList = describeInstanceStatusResultList.stream() .flatMap(describeInstanceStatusResult -> describeInstanceStatusResult.getInstanceStatuses().stream()) .collect(Collectors.toList()); if (instanceStatusList.size() < requiredInstances) { LOGGER.debug("Instances up: {}, needed: {}", instanceStatusList.size(), requiredInstances); return false; } for (InstanceStatus status : instanceStatusList) { if (INSTANCE_RUNNING != status.getInstanceState().getCode()) { LOGGER.debug("Instances are up but not all of them are in running state."); return false; } } return true; } private void checkForSpotRequest(Activity activity, AmazonEC2Client amazonEC2Client) { if (WAIT_FOR_SPOT_INSTANCES_STATUS_CODE.equals(activity.getStatusCode())) { Pattern pattern = Pattern.compile(SPOT_ID_PATTERN); Matcher matcher = pattern.matcher(activity.getStatusMessage()); if (matcher.find()) { String spotId = matcher.group(0); DescribeSpotInstanceRequestsResult spotResult = amazonEC2Client.describeSpotInstanceRequests( new DescribeSpotInstanceRequestsRequest().withSpotInstanceRequestIds(spotId)); Optional<SpotInstanceRequest> request = spotResult.getSpotInstanceRequests().stream().findFirst(); if (request.isPresent()) { if (LOW_SPOT_PRICE_STATUS_CODE.equals(request.get().getStatus().getCode())) { throw new CloudConnectorException(request.get().getStatus().getMessage()); } } } } } private List<Activity> getAutoScalingActivities() { DescribeScalingActivitiesRequest describeScalingActivitiesRequest = new DescribeScalingActivitiesRequest().withAutoScalingGroupName(autoScalingGroupName); return autoScalingClient.describeScalingActivities(describeScalingActivitiesRequest).getActivities(); } }