All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vmware.photon.controller.model.adapters.awsadapter.AWSUtils Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2015-2018 VMware, Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License.  You may obtain a copy of
 * the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed
 * under the License is distributed on an "AS IS" BASIS, without warranties or
 * conditions of any kind, EITHER EXPRESS OR IMPLIED.  See the License for the
 * specific language governing permissions and limitations under the License.
 */

package com.vmware.photon.controller.model.adapters.awsadapter;

import static com.amazonaws.retry.PredefinedRetryPolicies.DEFAULT_BACKOFF_STRATEGY;
import static com.amazonaws.retry.PredefinedRetryPolicies.DEFAULT_RETRY_CONDITION;

import static com.vmware.photon.controller.model.adapterapi.EndpointConfigRequest.ARN_KEY;
import static com.vmware.photon.controller.model.adapterapi.EndpointConfigRequest.EXTERNAL_ID_KEY;
import static com.vmware.photon.controller.model.adapterapi.EndpointConfigRequest.SESSION_EXPIRATION_TIME_MICROS_KEY;
import static com.vmware.photon.controller.model.adapterapi.EndpointConfigRequest.SESSION_TOKEN_KEY;
import static com.vmware.photon.controller.model.adapters.awsadapter.AWSConstants.AWS_MOCK_HOST_SYSTEM_PROPERTY;
import static com.vmware.photon.controller.model.adapters.awsadapter.AWSConstants.AWS_S3PROXY_SYSTEM_PROPERTY;
import static com.vmware.photon.controller.model.adapters.awsadapter.AWSConstants.AWS_TAG_NAME;
import static com.vmware.photon.controller.model.adapters.awsadapter.AWSConstants.DEVICE_NAME;
import static com.vmware.photon.controller.model.adapters.awsadapter.AWSConstants.DEVICE_TYPE;
import static com.vmware.photon.controller.model.adapters.awsadapter.AWSConstants.INSTANCE_STATE;
import static com.vmware.photon.controller.model.adapters.awsadapter.AWSConstants.INSTANCE_STATE_PENDING;
import static com.vmware.photon.controller.model.adapters.awsadapter.AWSConstants.INSTANCE_STATE_RUNNING;
import static com.vmware.photon.controller.model.adapters.awsadapter.AWSConstants.INSTANCE_STATE_SHUTTING_DOWN;
import static com.vmware.photon.controller.model.adapters.awsadapter.AWSConstants.INSTANCE_STATE_STOPPED;
import static com.vmware.photon.controller.model.adapters.awsadapter.AWSConstants.INSTANCE_STATE_STOPPING;
import static com.vmware.photon.controller.model.adapters.awsadapter.AWSConstants.PROVISIONED_SSD_MAX_SIZE_IN_MB;
import static com.vmware.photon.controller.model.adapters.awsadapter.AWSConstants.PROVISIONED_SSD_MIN_SIZE_IN_MB;
import static com.vmware.photon.controller.model.adapters.awsadapter.AWSConstants.VOLUME_TYPE;
import static com.vmware.photon.controller.model.adapters.awsadapter.AWSConstants.VOLUME_TYPE_GENERAL_PURPOSED_SSD;
import static com.vmware.photon.controller.model.adapters.awsadapter.AWSConstants.VOLUME_TYPE_PROVISIONED_SSD;
import static com.vmware.photon.controller.model.adapters.awsadapter.util.AWSSecurityGroupClient.DEFAULT_SECURITY_GROUP_NAME;
import static com.vmware.photon.controller.model.util.PhotonModelUriUtils.createInventoryUri;
import static com.vmware.xenon.common.Operation.STATUS_CODE_FORBIDDEN;
import static com.vmware.xenon.common.Operation.STATUS_CODE_UNAUTHORIZED;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.stream.Collectors;

import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.AmazonWebServiceRequest;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.auth.BasicSessionCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.handlers.AsyncHandler;
import com.amazonaws.regions.Regions;
import com.amazonaws.retry.RetryPolicy;
import com.amazonaws.retry.RetryPolicy.BackoffStrategy;
import com.amazonaws.services.cloudwatch.AmazonCloudWatchAsyncClient;
import com.amazonaws.services.cloudwatch.AmazonCloudWatchAsyncClientBuilder;
import com.amazonaws.services.cloudwatch.model.Datapoint;
import com.amazonaws.services.ec2.AmazonEC2AsyncClient;
import com.amazonaws.services.ec2.AmazonEC2AsyncClientBuilder;
import com.amazonaws.services.ec2.model.AmazonEC2Exception;
import com.amazonaws.services.ec2.model.CreateTagsRequest;
import com.amazonaws.services.ec2.model.DeleteSecurityGroupRequest;
import com.amazonaws.services.ec2.model.DeleteSecurityGroupResult;
import com.amazonaws.services.ec2.model.DeleteTagsRequest;
import com.amazonaws.services.ec2.model.DescribeAvailabilityZonesRequest;
import com.amazonaws.services.ec2.model.DescribeAvailabilityZonesResult;
import com.amazonaws.services.ec2.model.DescribeInstancesRequest;
import com.amazonaws.services.ec2.model.DescribeInstancesResult;
import com.amazonaws.services.ec2.model.DescribeTagsRequest;
import com.amazonaws.services.ec2.model.DescribeTagsResult;
import com.amazonaws.services.ec2.model.DescribeVpcsResult;
import com.amazonaws.services.ec2.model.EbsInstanceBlockDeviceSpecification;
import com.amazonaws.services.ec2.model.Filter;
import com.amazonaws.services.ec2.model.Instance;
import com.amazonaws.services.ec2.model.InstanceAttributeName;
import com.amazonaws.services.ec2.model.InstanceBlockDeviceMappingSpecification;
import com.amazonaws.services.ec2.model.InstanceState;
import com.amazonaws.services.ec2.model.InstanceStateChange;
import com.amazonaws.services.ec2.model.ModifyInstanceAttributeRequest;
import com.amazonaws.services.ec2.model.ModifyInstanceAttributeResult;
import com.amazonaws.services.ec2.model.SecurityGroup;
import com.amazonaws.services.ec2.model.Tag;
import com.amazonaws.services.ec2.model.TagDescription;
import com.amazonaws.services.ec2.model.Vpc;
import com.amazonaws.services.elasticloadbalancing.AmazonElasticLoadBalancingAsyncClient;
import com.amazonaws.services.elasticloadbalancing.AmazonElasticLoadBalancingAsyncClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.Bucket;
import com.amazonaws.services.s3.model.ObjectListing;
import com.amazonaws.services.s3.transfer.TransferManager;
import com.amazonaws.services.s3.transfer.TransferManagerBuilder;
import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceAsync;
import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceAsyncClientBuilder;
import com.amazonaws.services.securitytoken.model.AWSSecurityTokenServiceException;
import com.amazonaws.services.securitytoken.model.AssumeRoleRequest;
import com.amazonaws.services.securitytoken.model.AssumeRoleResult;
import com.amazonaws.services.securitytoken.model.Credentials;

import org.apache.commons.collections.CollectionUtils;

import com.vmware.photon.controller.model.UriPaths;
import com.vmware.photon.controller.model.adapters.awsadapter.AWSInstanceContext.AWSNicContext;
import com.vmware.photon.controller.model.adapters.awsadapter.util.AWSAsyncHandler;
import com.vmware.photon.controller.model.adapters.awsadapter.util.AWSClientManager;
import com.vmware.photon.controller.model.adapters.awsadapter.util.AWSCsvBillParser;
import com.vmware.photon.controller.model.adapters.awsadapter.util.AWSDeferredResultAsyncHandler;
import com.vmware.photon.controller.model.adapters.awsadapter.util.AWSSecurityGroupClient;
import com.vmware.photon.controller.model.adapters.registry.operations.ResourceOperation;
import com.vmware.photon.controller.model.adapters.util.ComputeEnumerateAdapterRequest;
import com.vmware.photon.controller.model.adapters.util.Pair;
import com.vmware.photon.controller.model.resources.ComputeDescriptionService;
import com.vmware.photon.controller.model.resources.ComputeService;
import com.vmware.photon.controller.model.resources.ComputeService.PowerState;
import com.vmware.photon.controller.model.resources.DiskService;
import com.vmware.photon.controller.model.resources.ResourceState;
import com.vmware.photon.controller.model.security.util.EncryptionUtils;
import com.vmware.xenon.common.DeferredResult;
import com.vmware.xenon.common.Operation;
import com.vmware.xenon.common.OperationContext;
import com.vmware.xenon.common.Service;
import com.vmware.xenon.common.ServiceHost;
import com.vmware.xenon.common.ServiceStateCollectionUpdateRequest;
import com.vmware.xenon.common.StatelessService;
import com.vmware.xenon.common.Utils;
import com.vmware.xenon.services.common.AuthCredentialsService.AuthCredentialsServiceState;

/**
 * AWS utils.
 */
public class AWSUtils {
    public static final String AWS_FILTER_RESOURCE_ID = "resource-id";
    public static final String AWS_FILTER_VPC_ID = "vpc-id";
    public static final String NO_VALUE = "no-value";
    public static final String TILDA = "~";
    public static final String AWS_MOCK_EC2_ENDPOINT = "/aws-mock/ec2-endpoint/";
    public static final String AWS_MOCK_ACCESSID = "aws-mock-accessid";
    public static final String AWS_MOCK_SECRCTKEY = "aws-mock-secertkey";
    public static final String AWS_MOCK_CLOUDWATCH_ENDPOINT = "/aws-mock/cloudwatch/";
    public static final String AWS_MOCK_LOAD_BALANCING_ENDPOINT = "/aws-mock/load-balancing-endpoint/";
    public static final String AWS_REGION_HEADER = "region";

    public static final String AWS_IMAGE_REGEX = "^ami-\\S+";
    /**
     * -Dphoton-model.aws.masterAccount.accessKey
     * -Dphoton-model.aws.masterAccount.secretKey
     *
     * The AWS credentials of the service accepting ARN-based credential requests. These credentials
     * are used to authenticate to the service account that has been authorized to assume the role
     * of a user's ARN-based AWS account.
     *
     * When a user generates an ARN, they authorize an account ID to assume the role on their
     * behalf - these keys must correspond to that specific account ID.
     */
    public static final String AWS_MASTER_ACCOUNT_ACCESS_KEY_PROPERTY = UriPaths.PROPERTY_PREFIX +
            "aws.masterAccount.accessKey";
    public static final String AWS_MASTER_ACCOUNT_ACCESS_KEY =
            System.getProperty(AWS_MASTER_ACCOUNT_ACCESS_KEY_PROPERTY);

    public static final String AWS_MASTER_ACCOUNT_SECRET_KEY_PROPERTY = UriPaths.PROPERTY_PREFIX +
            "aws.masterAccount.secretKey";
    public static final String AWS_MASTER_ACCOUNT_SECRET_KEY =
            System.getProperty(AWS_MASTER_ACCOUNT_SECRET_KEY_PROPERTY);

    /**
     * -Dphoton-model.aws.sessionExpirationOffset.millis
     *
     * An offset in milliseconds to "look-ahead" for session credential expiration and allow
     * credential refreshing. Defaults to 10 minutes (600000 milliseconds).
     */
    public static final String AWS_EXPIRATION_OFFSET_MILLIS_PROPERTY = UriPaths.PROPERTY_PREFIX +
            "aws.sessionExpirationOffset.millis";
    public static final Long AWS_DEFAULT_EXPIRATION_OFFSET_MILLIS = TimeUnit.MINUTES.toMillis(10);
    public static final Long AWS_EXPIRATION_OFFSET_MILLIS = Long.getLong(
            AWS_EXPIRATION_OFFSET_MILLIS_PROPERTY, AWS_DEFAULT_EXPIRATION_OFFSET_MILLIS);

    /**
     * -Dphoton-model.aws.arnDefaultSessionDuration.seconds
     *
     * The AWS ARN default session duration (in seconds). Defaults to 1 hour (3600 seconds) if not
     * set manually.
     *
     * This property may be between 15 minutes (900 seconds) and 1 hour (3600 seconds), as set by
     * AWS.
     */
    public static final String AWS_ARN_DEFAULT_SESSION_DURATION_SECONDS_PROPERTY =
            UriPaths.PROPERTY_PREFIX + "aws.arnDefaultSessionDuration.seconds";
    private static final Long ARN_DEFAULT_SESSION_DURATION_SECONDS = TimeUnit.HOURS.toSeconds(1);
    private static final Long AWS_MINIMUM_SESSION_DURATION_SECONDS = TimeUnit.MINUTES.toSeconds(15);
    private static final Long AWS_MAXIMUM_SESSION_DURATION_SECONDS = TimeUnit.HOURS.toSeconds(1);
    public static final Long AWS_ARN_SESSION_DURATION_SECONDS = Long.getLong(
            AWS_ARN_DEFAULT_SESSION_DURATION_SECONDS_PROPERTY,
            ARN_DEFAULT_SESSION_DURATION_SECONDS);

    /**
     * -Dphoton-model.aws.max.error.retry
     *
     * The AWS max retry request count. This is how many times to retry the request if it fails.
     * Default is 7.
     */
    public static final String AWS_MAX_ERROR_RETRY_PROPERTY =
            UriPaths.PROPERTY_PREFIX + "aws.max.error.retry";
    public static final int AWS_MAX_ERROR_RETRY = Integer
            .getInteger(AWS_MAX_ERROR_RETRY_PROPERTY, 7);

    /**
     * -Dphoton-model.aws.log.retry.error.attempt
     *
     * Log retry requests after attempt count reaches this threshold.
     * Default is 0 - all retires will be logged.
     */
    public static final String AWS_LOG_RETRY_ERROR_ATTEMPT_PROPERTY =
            UriPaths.PROPERTY_PREFIX + "aws.log.retry.error.attempt";
    public static final int AWS_LOG_RETRY_ERROR_ATTEMPT =
            Integer.getInteger(AWS_LOG_RETRY_ERROR_ATTEMPT_PROPERTY, 0);

    /**
     * Flag to use aws-mock, will be set in test files. Aws-mock is a open-source tool for testing
     * AWS services in a mock EC2 environment.
     *
     * @see aws-mock
     */
    private static boolean IS_AWS_CLIENT_MOCK = false;

    /**
     * Mock Host and port http://: of aws-mock, will be set in test files.
     */
    private static String awsMockHost = null;

    /**
     * Flag to use s3proxy, will be set in test files. s3proxy is a open-source tool for testing AWS
     * services in a mock S3 environment.
     *
     * @see s3proxy
     */
    private static boolean IS_S3_PROXY = false;

    /**
     * Mock Host and port http://: of s3proxy, will be set in test files.
     */
    private static String awsS3ProxyHost = null;

    /**
     * Backoff strategy that uses the DEFAULT_BACKOFF_STRATEGY with added error logging.
     */
    public static class LoggingBackoffStrategy implements BackoffStrategy {
        @Override
        public long delayBeforeNextRetry(AmazonWebServiceRequest originalRequest,
                AmazonClientException exception,
                int retriesAttempted) {
            long delay = DEFAULT_BACKOFF_STRATEGY.delayBeforeNextRetry(
                    originalRequest,
                    exception,
                    retriesAttempted);

            if (retriesAttempted >= AWS_LOG_RETRY_ERROR_ATTEMPT) {
                Utils.log(LoggingBackoffStrategy.class,
                        LoggingBackoffStrategy.class.getSimpleName(),
                        Level.WARNING,
                        "Retriable error on attempt %d for request %s %s, will retry in %s ms: %s",
                        retriesAttempted,
                        Integer.toHexString(System.identityHashCode(originalRequest)),
                        originalRequest, delay, exception);
            }
            return delay;
        }
    }


    public static void setAwsMockHost(String mockHost) {
        awsMockHost = mockHost;
    }

    public static boolean isAwsClientMock(AuthCredentialsServiceState credentials) {
        if (credentials.privateKeyId.startsWith(AWS_MOCK_ACCESSID) &&
                credentials.privateKey.startsWith(AWS_MOCK_SECRCTKEY)) {
            return System.getProperty(AWS_MOCK_HOST_SYSTEM_PROPERTY) == null ? IS_AWS_CLIENT_MOCK : true;
        } else {
            return false;
        }
    }

    public static boolean isAwsClientMock() {
        return System.getProperty(AWS_MOCK_HOST_SYSTEM_PROPERTY) == null ? IS_AWS_CLIENT_MOCK : true;
    }

    public static void setAwsClientMock(boolean isAwsClientMock) {
        IS_AWS_CLIENT_MOCK = isAwsClientMock;
    }

    private static String getAWSMockHost() {
        return System.getProperty(AWS_MOCK_HOST_SYSTEM_PROPERTY) == null ? awsMockHost
                : System.getProperty(AWS_MOCK_HOST_SYSTEM_PROPERTY);

    }

    public static void setAwsS3ProxyHost(String s3ProxyHost) {
        awsMockHost = s3ProxyHost;
    }

    public static boolean isAwsS3Proxy() {
        return System.getProperty(AWS_S3PROXY_SYSTEM_PROPERTY) == null ? IS_S3_PROXY : true;
    }

    public static void setAwsS3Proxy(boolean isAwsS3Proxy) {
        IS_S3_PROXY = isAwsS3Proxy;
    }

    /**
     * Method to get an EC2 Async Client.
     *
     * Allows for ARN-based credentials (as well as traditional key-based credentials), where a set
     * of credentials with the ARN key set will communicate with AWS to trade for a set of session
     * credentials that can allow the instantiation of an Amazon client.
     *
     * @param credentials An {@link AuthCredentialsServiceState} object.
     * @param region The region to get the AWS client in.
     * @param executorService The executor service to run async services in.
     */
    public static DeferredResult getEc2AsyncClient(
            AuthCredentialsServiceState credentials, String region,
            ExecutorService executorService) {
        OperationContext operationContext = OperationContext.getOperationContext();
        return checkAndRefreshCredentials(credentials, region, executorService)
                .thenApply(refreshedCredentials -> {
                    OperationContext.restoreOperationContext(operationContext);
                    return getAsyncClient(refreshedCredentials, region, executorService);
                });
    }

    /**
     * Method to get an EC2 Async Client.
     *
     * Note: ARN-based credentials will not work unless they have already been exchanged to
     * AWS for session credentials. If unset, this method will fail. To enable ARN-based
     * credentials, migrate to {@link #getEc2AsyncClient(AuthCredentialsServiceState, String,
     * ExecutorService)}.
     *
     * @param credentials An {@link AuthCredentialsServiceState} object.
     * @param region The region to get the AWS client in.
     * @param executorService The executor service to run async services in.
     */
    public static AmazonEC2AsyncClient getAsyncClient(
            AuthCredentialsServiceState credentials, String region,
            ExecutorService executorService) {

        ClientConfiguration configuration = createClientConfiguration()
                .withMaxConnections(100);

        AmazonEC2AsyncClientBuilder ec2AsyncClientBuilder = AmazonEC2AsyncClientBuilder
                .standard()
                .withClientConfiguration(configuration)
                .withCredentials(getAwsStaticCredentialsProvider(credentials))
                .withExecutorFactory(() -> executorService);

        if (region == null) {
            region = Regions.DEFAULT_REGION.getName();
        }

        if (isAwsClientMock(credentials)) {
            configuration.addHeader(AWS_REGION_HEADER, region);
            ec2AsyncClientBuilder.setClientConfiguration(configuration);
            AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder.EndpointConfiguration(
                    getAWSMockHost() + AWS_MOCK_EC2_ENDPOINT, region);
            ec2AsyncClientBuilder.setEndpointConfiguration(endpointConfiguration);
        } else {
            ec2AsyncClientBuilder.setRegion(region);
        }

        return (AmazonEC2AsyncClient) ec2AsyncClientBuilder.build();
    }

    static ClientConfiguration createClientConfiguration() {
        return new ClientConfiguration().withRetryPolicy(
                new RetryPolicy(
                        DEFAULT_RETRY_CONDITION,
                        new LoggingBackoffStrategy(),
                        AWS_MAX_ERROR_RETRY,
                        true));
    }

    private static String getAwsS3ProxyHost() {
        return System.getProperty(AWS_S3PROXY_SYSTEM_PROPERTY) == null ? awsS3ProxyHost
                : System.getProperty(AWS_S3PROXY_SYSTEM_PROPERTY);

    }

    public static void validateCredentials(AmazonEC2AsyncClient ec2Client,
            AWSClientManager clientManager, AuthCredentialsServiceState credentials,
            ComputeEnumerateAdapterRequest context, StatelessService service,
            Consumer onSuccess,
            Consumer onFail,
            Runnable onUnaccessible) {

        if (clientManager.isEc2ClientInvalid(credentials, context.regionId)) {
            onUnaccessible.run();
            return;
        }

        // NOTE: If an access to Xenon is required DO USE AWSAsyncHandler which set OperationContext
        ec2Client.describeAvailabilityZonesAsync(new DescribeAvailabilityZonesRequest(),
                new AsyncHandler() {

                    @Override
                    public void onError(Exception e) {
                        if (e instanceof AmazonServiceException) {
                            AmazonServiceException ase = (AmazonServiceException) e;
                            if (ase.getStatusCode() == STATUS_CODE_UNAUTHORIZED ||
                                    ase.getStatusCode() == STATUS_CODE_FORBIDDEN) {
                                clientManager.markEc2ClientInvalid(service, credentials,
                                        context.regionId);
                                // Signal passed creds does not have Access to this region
                                onUnaccessible.run();
                                return;
                            }
                            // Signal the failure
                            onFail.accept(e);
                        }
                    }

                    @Override
                    public void onSuccess(DescribeAvailabilityZonesRequest request,
                            DescribeAvailabilityZonesResult describeAvailabilityZonesResult) {

                        // Signal success
                        onSuccess.accept(describeAvailabilityZonesResult);
                    }
                });
    }

    /**
     * Method to get a CloudWatch Async Client.
     *
     * Allows for ARN-based credentials (as well as traditional key-based credentials), where a set
     * of credentials with the ARN key set will communicate with AWS to trade for a set of session
     * credentials that can allow the instantiation of an Amazon client.
     *
     * @param credentials An {@link AuthCredentialsServiceState} object.
     * @param region The region to get the AWS client in.
     * @param executorService The executor service to run async services in.
     */
    public static DeferredResult getCloudWatchStatsAsyncClient(
            AuthCredentialsServiceState credentials, String region,
            ExecutorService executorService, boolean isMockRequest) {
        OperationContext operationContext = OperationContext.getOperationContext();
        return checkAndRefreshCredentials(credentials, region, executorService)
                .thenApply(refreshedCredentials -> {
                    OperationContext.restoreOperationContext(operationContext);
                    return getStatsAsyncClient(refreshedCredentials, region, executorService,
                            isMockRequest);
                });
    }

    /**
     * Method to get a CloudWatch Async Client.
     *
     * Note: ARN-based credentials will not work unless they have already been exchanged to
     * AWS for session credentials. If unset, this method will fail. To enable ARN-based
     * credentials, migrate to {@link #getCloudWatchStatsAsyncClient(AuthCredentialsServiceState,
     * String, ExecutorService, boolean)}.
     *
     * @param credentials An {@link AuthCredentialsServiceState} object.
     * @param region The region to get the AWS client in.
     * @param executorService The executor service to run async services in.
     */
    public static AmazonCloudWatchAsyncClient getStatsAsyncClient(
            AuthCredentialsServiceState credentials, String region,
            ExecutorService executorService, boolean isMockRequest) {

        ClientConfiguration configuration = createClientConfiguration();

        AmazonCloudWatchAsyncClientBuilder amazonCloudWatchAsyncClientBuilder =
                AmazonCloudWatchAsyncClientBuilder
                        .standard()
                        .withClientConfiguration(configuration)
                        .withCredentials(getAwsStaticCredentialsProvider(credentials))
                        .withExecutorFactory(() -> executorService);

        if (region == null) {
            region = Regions.DEFAULT_REGION.getName();
        }

        if (isAwsClientMock(credentials)) {
            configuration.addHeader(AWS_REGION_HEADER, region);
            amazonCloudWatchAsyncClientBuilder.setClientConfiguration(configuration);
            AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder.EndpointConfiguration(
                    getAWSMockHost() + AWS_MOCK_CLOUDWATCH_ENDPOINT, region);
            amazonCloudWatchAsyncClientBuilder.setEndpointConfiguration(endpointConfiguration);
        } else {
            amazonCloudWatchAsyncClientBuilder.setRegion(region);
        }

        return (AmazonCloudWatchAsyncClient) amazonCloudWatchAsyncClientBuilder.build();
    }

    /**
     * Method to get an S3 transfer manager client.
     *
     * Allows for ARN-based credentials (as well as traditional key-based credentials), where a set
     * of credentials with the ARN key set will communicate with AWS to trade for a set of session
     * credentials that can allow the instantiation of an Amazon client.
     *
     * @param credentials An {@link AuthCredentialsServiceState} object.
     * @param region The region to get the AWS client in.
     * @param executorService The executor service to run async services in.
     */
    public static DeferredResult getS3TransferManagerAsync(
            AuthCredentialsServiceState credentials, String region,
            ExecutorService executorService) {
        OperationContext operationContext = OperationContext.getOperationContext();
        return checkAndRefreshCredentials(credentials, region, executorService)
                .thenApply(refreshedCredentials -> {
                    OperationContext.restoreOperationContext(operationContext);
                    return getS3TransferManager(refreshedCredentials, region, executorService);
                });
    }

    /**
     * Method to get an S3 transfer manager client.
     *
     * Note: ARN-based credentials will not work unless they have already been exchanged to
     * AWS for session credentials. If unset, this method will fail. To enable ARN-based
     * credentials, migrate to {@link #getS3TransferManagerAsync(AuthCredentialsServiceState,
     * String, ExecutorService)}
     *
     * @param credentials An {@link AuthCredentialsServiceState} object.
     * @param region The region to get the AWS client in.
     * @param executorService The executor service to run async services in.
     */
    public static TransferManager getS3TransferManager(AuthCredentialsServiceState credentials,
            String region, ExecutorService executorService) {

        AmazonS3ClientBuilder amazonS3ClientBuilder = AmazonS3ClientBuilder.standard()
                .withCredentials(getAwsStaticCredentialsProvider(credentials))
                .withForceGlobalBucketAccessEnabled(true);

        if (region == null) {
            region = Regions.DEFAULT_REGION.getName();
        }

        if (isAwsS3Proxy()) {
            AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder.EndpointConfiguration(
                    getAwsS3ProxyHost(), region);
            amazonS3ClientBuilder.setEndpointConfiguration(endpointConfiguration);
        } else {
            amazonS3ClientBuilder.setRegion(region);
        }

        TransferManagerBuilder transferManagerBuilder = TransferManagerBuilder.standard()
                .withS3Client(amazonS3ClientBuilder.build())
                .withExecutorFactory(() -> executorService)
                .withShutDownThreadPools(false);

        return transferManagerBuilder.build();
    }

    /**
     * Method to get a load balancing async client.
     *
     * Allows for ARN-based credentials (as well as traditional key-based credentials), where a set
     * of credentials with the ARN key set will communicate with AWS to trade for a set of session
     * credentials that can allow the instantiation of an Amazon client.
     *
     * @param credentials An {@link AuthCredentialsServiceState} object.
     * @param region The region to get the AWS client in.
     * @param executorService The executor service to run async services in.
     */
    public static DeferredResult getAwsLoadBalancingAsyncClient(
            AuthCredentialsServiceState credentials, String region,
            ExecutorService executorService) {
        OperationContext operationContext = OperationContext.getOperationContext();
        return checkAndRefreshCredentials(credentials, region, executorService)
                .thenApply(refreshedCredentials -> {
                    OperationContext.restoreOperationContext(operationContext);
                    return getLoadBalancingAsyncClient(refreshedCredentials, region, executorService);
                });
    }

    /**
     * Method to get a load balancing async client.
     *
     * Note: ARN-based credentials will not work unless they have already been exchanged to
     * AWS for session credentials. If unset, this method will fail. To enable ARN-based
     * credentials, migrate to {@link #getAwsLoadBalancingAsyncClient(AuthCredentialsServiceState,
     * String, ExecutorService)}.
     *
     * @param credentials An {@link AuthCredentialsServiceState} object.
     * @param region The region to get the AWS client in.
     * @param executorService The executor service to run async services in.
     */
    public static AmazonElasticLoadBalancingAsyncClient getLoadBalancingAsyncClient(
            AuthCredentialsServiceState credentials, String region,
            ExecutorService executorService) {

        AmazonElasticLoadBalancingAsyncClientBuilder amazonElasticLoadBalancingAsyncClientBuilder =
                AmazonElasticLoadBalancingAsyncClientBuilder
                        .standard()
                        .withClientConfiguration(createClientConfiguration())
                        .withCredentials(getAwsStaticCredentialsProvider(credentials))
                        .withExecutorFactory(() -> executorService);

        if (region == null) {
            region = Regions.DEFAULT_REGION.getName();
        }

        if (isAwsClientMock(credentials)) {
            AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder.EndpointConfiguration(
                    getAWSMockHost() + AWS_MOCK_LOAD_BALANCING_ENDPOINT, region);
            amazonElasticLoadBalancingAsyncClientBuilder
                    .setEndpointConfiguration(endpointConfiguration);
        } else {
            amazonElasticLoadBalancingAsyncClientBuilder.setRegion(region);
        }

        return (AmazonElasticLoadBalancingAsyncClient) amazonElasticLoadBalancingAsyncClientBuilder
                .build();
    }

    /**
     * Method to get an S3 Async Client.
     *
     * Allows for ARN-based credentials (as well as traditional key-based credentials), where a set
     * of credentials with the ARN key set will communicate with AWS to trade for a set of session
     * credentials that can allow the instantiation of an Amazon client.
     *
     * @param credentials An {@link AuthCredentialsServiceState} object.
     * @param region The region to get the AWS client in.
     * @param executorService The executor service to run async services in.
     */
    public static DeferredResult getS3ClientAsync(
            AuthCredentialsServiceState credentials, String region,
            ExecutorService executorService) {
        OperationContext operationContext = OperationContext.getOperationContext();
        return checkAndRefreshCredentials(credentials, region, executorService)
                .thenApply(refreshedCredentials -> {
                    OperationContext.restoreOperationContext(operationContext);
                    return getS3Client(refreshedCredentials, region);
                });
    }

    /**
     * Method to get an S3 Async Client.
     *
     * Note: ARN-based credentials will not work unless they have already been exchanged to
     * AWS for session credentials. If unset, this method will fail. To enable ARN-based
     * credentials, migrate to {@link #getS3ClientAsync(AuthCredentialsServiceState, String,
     * ExecutorService)}.
     *
     * @param credentials An {@link AuthCredentialsServiceState} object.
     * @param regionId The region to get the AWS client in.
     */
    public static AmazonS3Client getS3Client(AuthCredentialsServiceState credentials,
            String regionId) {

        AmazonS3ClientBuilder amazonS3ClientBuilder = AmazonS3ClientBuilder
                .standard()
                .withClientConfiguration(createClientConfiguration())
                .withCredentials(getAwsStaticCredentialsProvider(credentials))
                .withRegion(regionId);

        if (isAwsClientMock(credentials)) {
            throw new IllegalArgumentException("AWS Mock does not support S3 client");
        }

        return (AmazonS3Client) amazonS3ClientBuilder.build();
    }

    /**
     * Synchronous UnTagging of one or many AWS resources with the provided tags.
     */
    public static void unTagResources(AmazonEC2AsyncClient client, Collection tags,
            String... resourceIds) {
        if (isAwsClientMock()) {
            return;
        }

        DeleteTagsRequest req = new DeleteTagsRequest()
                .withTags(tags)
                .withResources(resourceIds);

        client.deleteTags(req);
    }

    /**
     * Synchronous Tagging of one or many AWS resources with the provided tags.
     */
    public static void tagResources(AmazonEC2AsyncClient client,
            Collection tags, String... resourceIds) {
        if (isAwsClientMock()) {
            return;
        }

        CreateTagsRequest req = new CreateTagsRequest()
                .withResources(resourceIds).withTags(tags);

        client.createTags(req);
    }

    /**
     * Synchronous Tagging of one or many AWS resources with the provided name.
     */
    public static void tagResourcesWithName(AmazonEC2AsyncClient client, String name,
            String... resourceIds) {
        Tag awsNameTag = new Tag().withKey(AWS_TAG_NAME).withValue(name);
        tagResources(client, Collections.singletonList(awsNameTag), resourceIds);
    }

    /*
     * Return the tags for a giving resource
     */
    public static List getResourceTags(String resourceID,
            AmazonEC2AsyncClient client) {
        Filter resource = new Filter().withName(AWS_FILTER_RESOURCE_ID)
                .withValues(resourceID);
        DescribeTagsRequest req = new DescribeTagsRequest()
                .withFilters(resource);
        DescribeTagsResult result = client.describeTags(req);
        return result.getTags();
    }

    public static Filter getFilter(String name, String value) {
        return new Filter().withName(name).withValues(value);
    }

    /**
     * Returns the region Id for the AWS instance
     *
     * @return the region id
     */
    public static String getRegionId(Instance i) {
        // Drop the zone suffix "a" ,"b" etc to get the region Id.
        String zoneId = i.getPlacement().getAvailabilityZone();
        String regiondId = zoneId.substring(0, zoneId.length() - 1);
        return regiondId;
    }

    /**
     * Maps the Aws machine state to {@link PowerState}
     *
     * @param state
     * @return the {@link PowerState} of the machine
     */
    public static PowerState mapToPowerState(InstanceState state) {
        PowerState powerState = PowerState.UNKNOWN;
        switch (state.getCode()) {
        case 16:
            powerState = PowerState.ON;
            break;
        case 80:
            powerState = PowerState.OFF;
            break;
        default:
            break;
        }
        return powerState;
    }

    /**
     * Creates a filter for the instances that are in non terminated state on the AWS endpoint.
     *
     * @return
     */
    public static Filter getAWSNonTerminatedInstancesFilter() {
        // Create a filter to only get non terminated instances from the remote instance.
        List stateValues = new ArrayList<>(Arrays.asList(INSTANCE_STATE_RUNNING,
                INSTANCE_STATE_PENDING, INSTANCE_STATE_STOPPING, INSTANCE_STATE_STOPPED,
                INSTANCE_STATE_SHUTTING_DOWN));
        Filter runningInstanceFilter = new Filter();
        runningInstanceFilter.setName(INSTANCE_STATE);
        runningInstanceFilter.setValues(stateValues);
        return runningInstanceFilter;
    }

    public static List getOrCreateSecurityGroups(AWSInstanceContext aws) {
        return getOrCreateSecurityGroups(aws, aws.getPrimaryNic());
    }

    /*
     * method will create new or validate existing security group has the necessary settings for CM
     * to function. It will return the security group id that is required during instance
     * provisioning. for each nicContext element provided, for each of its securityGroupStates,
     * security group is discovered from AWS in case that there are no securityGroupStates, security
     * group ID is obtained from the custom properties in case that none of the above methods
     * discover a security group, the default one is discovered from AWS in case that none of the
     * above method discover a security group, a new security group is created
     */
    public static List getOrCreateSecurityGroups(AWSInstanceContext aws,
            AWSNicContext nicCtx) {

        String groupId;
        SecurityGroup group;

        List groupIds = new ArrayList<>();

        AWSSecurityGroupClient client = new AWSSecurityGroupClient(aws.amazonEC2Client);

        if (nicCtx != null) {
            if (nicCtx.securityGroupStates != null && !nicCtx.securityGroupStates.isEmpty()) {
                List securityGroupNames = nicCtx.securityGroupStates.stream()
                        .map(securityGroupState -> securityGroupState.name)
                        .collect(Collectors.toList());
                List securityGroups = client.getSecurityGroups(
                        new ArrayList<>(securityGroupNames), nicCtx.vpc.getVpcId());
                for (SecurityGroup securityGroup : securityGroups) {
                    groupIds.add(securityGroup.getGroupId());
                }
                return groupIds;
            }
        }

        // use the security group provided in the description properties
        String sgId = getFromCustomProperties(aws.child.description,
                AWSConstants.AWS_SECURITY_GROUP_ID);
        if (sgId != null) {
            return Arrays.asList(sgId);
        }

        // in case no group is configured in the properties, attempt to discover the default one
        if (nicCtx != null && nicCtx.vpc != null) {
            try {
                group = client.getSecurityGroup(DEFAULT_SECURITY_GROUP_NAME,
                        nicCtx.vpc.getVpcId());
                if (group != null) {
                    return Arrays.asList(group.getGroupId());
                }
            } catch (AmazonServiceException t) {
                if (!t.getMessage().contains(
                        DEFAULT_SECURITY_GROUP_NAME)) {
                    throw t;
                }
            }
        }

        // if the group doesn't exist an exception is thrown. We won't throw a
        // missing group exception
        // we will continue and create the group
        groupId = createSecurityGroupOnDefaultVPC(aws);

        return Collections.singletonList(groupId);
    }

    public static List getOrCreateDefaultSecurityGroup(AmazonEC2AsyncClient amazonEC2Client,
            AWSNicContext nicCtx) {

        AWSSecurityGroupClient client = new AWSSecurityGroupClient(amazonEC2Client);
        // in case no group is configured in the properties, attempt to discover the default one
        if (nicCtx != null && nicCtx.vpc != null) {
            try {
                SecurityGroup group = client.getSecurityGroup(
                        DEFAULT_SECURITY_GROUP_NAME,
                        nicCtx.vpc.getVpcId());
                if (group != null) {
                    return Arrays.asList(group.getGroupId());
                }
            } catch (AmazonServiceException t) {
                if (!t.getMessage().contains(
                        DEFAULT_SECURITY_GROUP_NAME)) {
                    throw t;
                }
            }
        }

        // if the group doesn't exist an exception is thrown. We won't throw a
        // missing group exception
        // we will continue and create the group
        String groupId = client.createDefaultSecurityGroupWithDefaultRules(nicCtx.vpc);

        return Collections.singletonList(groupId);
    }

    // method create a security group in the VPC from custom properties or the default VPC
    private static String createSecurityGroupOnDefaultVPC(AWSInstanceContext aws) {
        String vpcId = null;
        // get the subnet cidr (if any)
        String subnetCidr = null;
        // in case subnet will be obtained from the default vpc, the security group should
        // as well be created there
        Vpc defaultVPC = getDefaultVPC(aws);
        if (defaultVPC != null) {
            vpcId = defaultVPC.getVpcId();
            subnetCidr = defaultVPC.getCidrBlock();
        }

        // no subnet or no vpc is not an option...
        if (subnetCidr == null || vpcId == null) {
            throw new AmazonServiceException("default VPC not found");
        }

        return new AWSSecurityGroupClient(aws.amazonEC2Client)
                .createDefaultSecurityGroupWithDefaultRules(defaultVPC);
    }

    public static String getFromCustomProperties(
            ComputeDescriptionService.ComputeDescription description,
            String key) {
        if (description == null || description.customProperties == null) {
            return null;
        }

        return description.customProperties.get(key);
    }

    /**
     * Gets the default VPC
     */
    public static Vpc getDefaultVPC(AWSInstanceContext aws) {
        DescribeVpcsResult result = aws.amazonEC2Client.describeVpcs();
        List vpcs = result.getVpcs();
        for (Vpc vpc : vpcs) {
            if (vpc.isDefault()) {
                return vpc;
            }
        }
        return null;
    }

    /**
     * Calculate the average burn rate, given a list of datapoints from Amazon AWS.
     */
    public static Double calculateAverageBurnRate(List dpList) {
        if (dpList.size() <= 1) {
            return null;
        }
        Datapoint oldestDatapoint = dpList.get(0);
        Datapoint latestDatapoint = dpList.get(dpList.size() - 1);

        // Adjust oldest datapoint to account for billing cycle when the estimated charges is reset
        // to 0.
        // Iterate over the sublist from the oldestDatapoint element + 1 to the latestDatapoint
        // element (excluding).
        // If the oldestDatapoint value is greater than the latestDatapoint value,
        // move the oldestDatapoint pointer until the oldestDatapoint value is less than the
        // latestDatapoint value.
        // Eg: 4,5,6,7,0,1,2,3 -> 4 is greater than 3. Move the pointer until 0.
        // OldestDatapoint value is 0 and the latestDatapoint value is 3.
        for (Datapoint datapoint : dpList.subList(1, dpList.size() - 1)) {
            if (latestDatapoint.getAverage() > oldestDatapoint.getAverage()) {
                break;
            }
            oldestDatapoint = datapoint;
        }

        double averageBurnRate = (latestDatapoint.getAverage()
                - oldestDatapoint.getAverage())
                / getDateDifference(oldestDatapoint.getTimestamp(),
                        latestDatapoint.getTimestamp(), TimeUnit.HOURS);
        // If there are only 2 datapoints and the oldestDatapoint is greater than the
        // latestDatapoint, value will be negative.
        // Eg: oldestDatapoint = 5 and latestDatapoint = 0, when the billing cycle is reset.
        // In such cases, set the burn rate value to 0
        averageBurnRate = (averageBurnRate < 0 ? 0 : averageBurnRate);
        return averageBurnRate;
    }

    /**
     * Calculate the current burn rate, given a list of datapoints from Amazon AWS.
     */
    public static Double calculateCurrentBurnRate(List dpList) {
        if (dpList.size() <= 7) {
            return null;
        }
        Datapoint dayOldDatapoint = dpList.get(dpList.size() - 7);
        Datapoint latestDatapoint = dpList.get(dpList.size() - 1);

        // Adjust the dayOldDatapoint to account for billing cycle when the estimated charges is
        // reset to 0.
        // Iterate over the sublist from the oldestDatapoint element + 1 to the latestDatapoint
        // element.
        // If the oldestDatapoint value is greater than the latestDatapoint value,
        // move the oldestDatapoint pointer until the oldestDatapoint value is less than the
        // latestDatapoint value.
        // Eg: 4,5,6,7,0,1,2,3 -> 4 is greater than 3. Move the pointer until 0.
        // OldestDatapoint value is 0 and the latestDatapoint value is 3.
        for (Datapoint datapoint : dpList.subList(dpList.size() - 6, dpList.size() - 1)) {
            if (latestDatapoint.getAverage() > dayOldDatapoint.getAverage()) {
                break;
            }
            dayOldDatapoint = datapoint;
        }

        double currentBurnRate = (latestDatapoint.getAverage()
                - dayOldDatapoint.getAverage())
                / getDateDifference(dayOldDatapoint.getTimestamp(),
                        latestDatapoint.getTimestamp(), TimeUnit.HOURS);
        // If there are only 2 datapoints and the oldestDatapoint is greater than the
        // latestDatapoint, value will be negative.
        // Eg: oldestDatapoint = 5 and latestDatapoint = 0, when the billing cycle is reset.
        // In such cases, set the burn rate value to 0
        currentBurnRate = (currentBurnRate < 0 ? 0 : currentBurnRate);
        return currentBurnRate;
    }

    private static long getDateDifference(Date oldDate, Date newDate, TimeUnit timeUnit) {
        long differenceInMillies = newDate.getTime() - oldDate.getTime();
        return timeUnit.convert(differenceInMillies, TimeUnit.MILLISECONDS);
    }

    public static String autoDiscoverBillsBucketName(AmazonS3 s3Client, String awsAccountId) {
        String billFilePrefix = awsAccountId + AWSCsvBillParser.AWS_DETAILED_BILL_CSV_FILE_NAME_MID;
        for (Bucket bucket : s3Client.listBuckets()) {
            // For each bucket accessible to this client, try to search for files with the
            // 'billFilePrefix'
            ObjectListing objectListing = s3Client.listObjects(bucket.getName(), billFilePrefix);
            if (!objectListing.getObjectSummaries().isEmpty()) {
                // This means that this bucket contains zip files representing the detailed csv
                // bills.
                return bucket.getName();
            }
        }
        return null;
    }

    public static void waitForTransitionCompletion(ServiceHost host,
            List stateChangeList,
            final String desiredState, AmazonEC2AsyncClient client,
            BiConsumer callback) {
        InstanceStateChange stateChange = stateChangeList.get(0);

        try {
            DescribeInstancesRequest request = new DescribeInstancesRequest();
            request.withInstanceIds(stateChange.getInstanceId());
            DescribeInstancesResult result = client.describeInstances(request);
            Instance instance = result.getReservations()
                    .stream()
                    .flatMap(r -> r.getInstances().stream())
                    .filter(i -> i.getInstanceId()
                            .equalsIgnoreCase(stateChange.getInstanceId()))
                    .findFirst().orElseThrow(() -> new IllegalArgumentException(
                            String.format("%s instance not found", stateChange.getInstanceId())));

            String state = instance.getState().getName();

            if (state.equals(desiredState)) {
                callback.accept(instance.getState(), null);
            } else {
                host.schedule(() -> waitForTransitionCompletion(host, stateChangeList, desiredState,
                        client, callback), 5, TimeUnit.SECONDS);
            }

        } catch (AmazonServiceException | IllegalArgumentException ase) {
            callback.accept(null, ase);
        }

    }

    public static void setEbsDefaultsIfNotSet(DiskService.DiskState diskState, Boolean persist) {
        diskState.persistent = Optional.ofNullable(diskState.persistent).orElse(persist);

        if (diskState.customProperties == null) {
            diskState.customProperties = new HashMap<>();
        }

        if (diskState.customProperties.get(DEVICE_TYPE) == null) {
            diskState.customProperties.put(DEVICE_TYPE, AWSConstants.AWSStorageType.EBS.getName());
        }

        if (diskState.customProperties.get(VOLUME_TYPE) == null) {
            diskState.customProperties.put(VOLUME_TYPE, VOLUME_TYPE_GENERAL_PURPOSED_SSD);
        }
    }

    public static void validateSizeSupportedByVolumeType(int capacityGiB, String volumeType) {
        if (volumeType.equals(VOLUME_TYPE_PROVISIONED_SSD)) {
            long capacityMBytes = capacityGiB * 1024;
            if (capacityMBytes < PROVISIONED_SSD_MIN_SIZE_IN_MB ||
                    capacityMBytes > PROVISIONED_SSD_MAX_SIZE_IN_MB) {
                String message = String
                        .format("Cannot provision a %s GiB IOPS disk. An io1 type of "
                                + "volume must be at least 4 GiB in size.", capacityGiB);
                throw new IllegalArgumentException(message);
            }
        }
    }

    /**
     * Generates an AWS credentials provider, determining if to use general basic authentication or
     * if the credentials are session-based.
     * @param credentials An {@link AuthCredentialsServiceState} object.
     */
    private static AWSStaticCredentialsProvider getAwsStaticCredentialsProvider(
            AuthCredentialsServiceState credentials) throws AWSSecurityTokenServiceException {

        // If the credentials are non-session based, then simply generate basic AWS credential set
        // and return them.
        if (credentials.customProperties == null ||
                !credentials.customProperties.containsKey(SESSION_TOKEN_KEY)) {
            return new AWSStaticCredentialsProvider(
                    new BasicAWSCredentials(credentials.privateKeyId,
                            EncryptionUtils.decrypt(credentials.privateKey)));
        }

        return new AWSStaticCredentialsProvider(
                new BasicSessionCredentials(credentials.privateKeyId,
                        EncryptionUtils.decrypt(credentials.privateKey),
                        credentials.customProperties.get(SESSION_TOKEN_KEY)));
    }

    /**
     * Helper method to check if a set of credentials have expired and if so, retrieves a set of
     * refreshed credentials. If not, then returns the current credentials set.
     *
     * @param credentials An {@link AuthCredentialsServiceState} object.
     * @param region The region to get the credentials in.
     * @param executorService The executor service to run async services in.
     */
    public static DeferredResult checkAndRefreshCredentials(
            AuthCredentialsServiceState credentials, String region,
            ExecutorService executorService) {
        // If the credentials are non-ARN based, or they are not yet expired, then they may be
        // returned automatically.
        if (credentials.customProperties == null ||
                (!credentials.customProperties.containsKey(ARN_KEY) &&
                        !credentials.customProperties.containsKey(SESSION_TOKEN_KEY)) ||
                (credentials.customProperties.containsKey(SESSION_EXPIRATION_TIME_MICROS_KEY) &&
                        !isExpiredCredentials(credentials))) {
            return DeferredResult.completed(credentials);
        }

        return getArnSessionCredentialsAsync(credentials.customProperties.get(ARN_KEY),
                credentials.customProperties.get(EXTERNAL_ID_KEY), region, executorService)
                .thenApply(AWSUtils::awsSessionCredentialsToAuthCredentialsState);
    }

    /**
     * A helper method to convert an AWS {@link Credentials} object to an
     * {@link AuthCredentialsServiceState} object. This will use the customProperties
     * `SESSION_TOKEN_KEY` and `SESSION_EXPIRATION_TIME_MICROS_KEY` to represent the temporary
     * nature of these credentials.
     */
    public static AuthCredentialsServiceState awsSessionCredentialsToAuthCredentialsState(
            Credentials credentials) {
        AuthCredentialsServiceState authCredentials = new AuthCredentialsServiceState();
        authCredentials.privateKeyId = credentials.getAccessKeyId();
        authCredentials.privateKey = credentials.getSecretAccessKey();
        authCredentials.customProperties = new HashMap<>();
        authCredentials.customProperties.put(SESSION_TOKEN_KEY, credentials.getSessionToken());
        authCredentials.customProperties.put(SESSION_EXPIRATION_TIME_MICROS_KEY,
                String.valueOf(String.valueOf(credentials.getExpiration().getTime())));
        return authCredentials;
    }

    /**
     * A helper method to check if a set of credentials are ARN credentials, assumed via whether
     * or not the ARN_KEY custom property is set.
     *
     * @return True if ARN_KEY is set.
     */
    public static boolean isArnCredentials(AuthCredentialsServiceState credentials) {
        return credentials.customProperties != null &&
                credentials.customProperties.containsKey(ARN_KEY);
    }

    /**
     * Checks if a set of credentials have the key `SESSION_EXPIRATION_TIME_MICROS_KEY` and if so,
     * whether or not that value is less than or equal to the current system time, minus the
     * property set at {@link #AWS_EXPIRATION_OFFSET_MILLIS_PROPERTY}.
     *
     * @return True if the credentials are expired, false otherwise.
     */
    public static boolean isExpiredCredentials(AuthCredentialsServiceState credentials) {
        return credentials != null && credentials.customProperties != null &&
                credentials.customProperties.containsKey(SESSION_EXPIRATION_TIME_MICROS_KEY) &&
                (Long.parseLong(credentials.customProperties
                        .get(SESSION_EXPIRATION_TIME_MICROS_KEY)) - AWS_EXPIRATION_OFFSET_MILLIS)
                        < System.currentTimeMillis();
    }

    /**
     * Returns the designated ARN credentials session duration in seconds. By default, it returns
     * 3600 (1 hour), which is the maximum AWS permits. This is toggleable via system property
     * {@link #AWS_ARN_DEFAULT_SESSION_DURATION_SECONDS_PROPERTY}.
     *
     * The value may be between 900 seconds (15 minutes) and 3600 seconds (1 hour). If the
     * designated duration is not within those bounds, it will be set to the nearest boundary.
     */
    private static Integer getArnSessionDurationSeconds() {
        Long duration = AWS_ARN_SESSION_DURATION_SECONDS;

        if (duration < AWS_MINIMUM_SESSION_DURATION_SECONDS) {
            duration = AWS_MINIMUM_SESSION_DURATION_SECONDS;
            Utils.log(AWSUtils.class, AWSUtils.class.getSimpleName(), Level.WARNING,
                    "AWS ARN session duration may not be lower than 900 seconds. Defaulting to 900 seconds.");
        }

        if (duration > AWS_MAXIMUM_SESSION_DURATION_SECONDS) {
            duration = AWS_MAXIMUM_SESSION_DURATION_SECONDS;
            Utils.log(AWSUtils.class, AWSUtils.class.getSimpleName(), Level.WARNING,
                    "AWS ARN session duration may not be greater than 3600 seconds. Defaulting to 3600 seconds.");
        }

        return Math.toIntExact(duration);
    }

    /**
     * Authenticates and returns a DeferredResult set of session credentials for a valid ARN that
     * authorizes this system's account ID (validated through
     * {@link #AWS_MASTER_ACCOUNT_ACCESS_KEY_PROPERTY} and
     * {@link #AWS_MASTER_ACCOUNT_SECRET_KEY_PROPERTY}) and the externalId parameter.
     *
     * If the system properties are unset, then this call will automatically fail.
     *
     * @param arn The Amazon Resource Name to validate.
     * @param externalId The external ID this ARN has authorized.
     * @param executorService The executor service to issue the request.
     */
    public static DeferredResult getArnSessionCredentialsAsync(String arn,
            String externalId, ExecutorService executorService) {
        return getArnSessionCredentialsAsync(arn, externalId, Regions.DEFAULT_REGION.getName(),
                executorService);
    }

    /**
     * Authenticates and returns a DeferredResult set of session credentials for a valid ARN that
     * authorizes this system's account ID (validated through
     * {@link #AWS_MASTER_ACCOUNT_ACCESS_KEY_PROPERTY} and
     * {@link #AWS_MASTER_ACCOUNT_SECRET_KEY_PROPERTY}) and the externalId parameter.
     *
     * If the system properties are unset, then this call will automatically fail.
     *
     * @param arn The Amazon Resource Name to validate.
     * @param externalId The external ID this ARN has authorized.
     * @param region The region to validate within.
     * @param executorService The executor service to issue the request.
     */
    public static DeferredResult getArnSessionCredentialsAsync(String arn,
            String externalId, String region, ExecutorService executorService) {
        AWSCredentialsProvider serviceAwsCredentials;
        try {
            serviceAwsCredentials = new AWSStaticCredentialsProvider(
                    new BasicAWSCredentials(AWS_MASTER_ACCOUNT_ACCESS_KEY,
                            AWS_MASTER_ACCOUNT_SECRET_KEY));
        } catch (Throwable t) {
            return DeferredResult.failed(t);
        }

        AWSSecurityTokenServiceAsync awsSecurityTokenServiceAsync =
                AWSSecurityTokenServiceAsyncClientBuilder.standard()
                        .withRegion(region)
                        .withCredentials(serviceAwsCredentials)
                        .withExecutorFactory(() -> executorService)
                        .build();

        AssumeRoleRequest assumeRoleRequest = new AssumeRoleRequest()
                .withRoleArn(arn)
                .withRoleSessionName(UUID.randomUUID().toString())
                .withDurationSeconds(getArnSessionDurationSeconds())
                .withExternalId(externalId);

        DeferredResult r = new DeferredResult<>();
        OperationContext operationContext = OperationContext.getOperationContext();
        awsSecurityTokenServiceAsync.assumeRoleAsync(assumeRoleRequest,
                new AsyncHandler() {
                    @Override
                    public void onSuccess(AssumeRoleRequest request, AssumeRoleResult result) {
                        OperationContext.restoreOperationContext(operationContext);
                        r.complete(result);
                    }

                    @Override
                    public void onError(Exception ex) {
                        OperationContext.restoreOperationContext(operationContext);
                        r.fail(ex);
                    }
                });
        return r.thenApply(AssumeRoleResult::getCredentials);
    }

    /**
     * Deletes a security group and retries if the returned error is on the collection of retriable
     * errors that is passed into the method.
     * The operation is retried up to AWS_MAX_ERROR_RETRY times, and an exponential backoff
     * strategy is used.
     */
    public static DeferredResult deleteSecurityGroupWithRetry(
            StatelessService service,
            AmazonEC2AsyncClient client,
            DeleteSecurityGroupRequest req,
            Set retriableErrors,
            int retryCount) {
        String message = "Delete AWS Security Group with id [" + req.getGroupId() + "].";

        AWSDeferredResultAsyncHandler
                handler = new AWSDeferredResultAsyncHandler<>(service, message);

        client.deleteSecurityGroupAsync(req, handler);

        DeferredResult result = new DeferredResult<>();
        handler.toDeferredResult()
                .thenAccept(__ -> result.complete(null))
                .exceptionally(t -> {
                    if (t.getCause() == null ||
                            !(t.getCause() instanceof AmazonEC2Exception) ||
                            !retriableErrors.contains(((AmazonEC2Exception)t.getCause())
                                    .getErrorCode())) {
                        result.fail(t);
                        return null;
                    }

                    if (retryCount < AWS_MAX_ERROR_RETRY) {
                        long delay = (long)Math.pow(2, retryCount) * 1000;
                        service.log(Level.WARNING, "Error deleting SG: [%s]. Error: [%s]. "
                                        + "Retrying in [%s] milliseconds.",
                                req.getGroupId(), t.getMessage(), delay);
                        service.getHost().schedule(() -> {
                            deleteSecurityGroupWithRetry(service, client, req, retriableErrors,
                                    retryCount + 1).whenComplete((a, th) -> {
                                        if (th == null) {
                                            result.complete(null);
                                        } else {
                                            result.fail(th);
                                        }
                                    });
                        }, delay, TimeUnit.MILLISECONDS);
                    } else {
                        service.log(Level.SEVERE, "Error deleting SG: [%s]. Error: [%s]. "
                                        + "Finished retrying.",
                                req.getGroupId(), t.getMessage());
                        result.fail(t);
                    }
                    return null;
                });
        return result;
    }

    public static DeferredResult getUpdateDiskStatusDr(StatelessService service,
            DiskService.DiskStatus status, ResourceState diskState) {
        ((DiskService.DiskState)diskState).status = status;
        return service.sendWithDeferredResult(
                Operation.createPatch(createInventoryUri(service.getHost(), diskState.documentSelfLink))
                        .setBody(diskState)
                        .setReferer(service.getHost().getUri()));
    }

    /**
     * Update attach/detach status of disk
     */
    public static DeferredResult updateDiskState(DiskService.DiskState diskState,
            String operation, Service service) {
        Operation diskOp = null;

        if (operation.equals(ResourceOperation.ATTACH_DISK.operation)) {
            diskState.status = DiskService.DiskStatus.ATTACHED;
            diskOp = Operation.createPatch(createInventoryUri(service.getHost(),
                    diskState.documentSelfLink))
                    .setBody(diskState)
                    .setReferer(service.getUri());
        } else if (operation.equals(ResourceOperation.DETACH_DISK.operation)) {
            diskState.persistent = Boolean.TRUE;
            diskState.status = DiskService.DiskStatus.AVAILABLE;
            diskState.customProperties.remove(DEVICE_NAME);
            diskOp = Operation.createPut(createInventoryUri(service.getHost(), diskState
                    .documentSelfLink))
                    .setBody(diskState)
                    .setReferer(service.getUri());
        }

        return service.sendWithDeferredResult(diskOp);
    }

    /**
     * Add or remove diskLink from ComputeState by sending a ServiceStateCollectionUpdateRequest.
     */
    public static DeferredResult updateComputeState(
            String computeStateLink, List diskLinks,
            String operation, Service service) {
        Map> collectionsToModify = Collections
                .singletonMap(ComputeService.ComputeState.FIELD_NAME_DISK_LINKS,
                        new ArrayList<>(diskLinks));

        Map> collectionsToAdd = null;
        Map> collectionsToRemove = null;
        if (operation.equals(ResourceOperation.ATTACH_DISK.operation)) {
            collectionsToAdd = collectionsToModify;
        } else {
            //DETACH case
            collectionsToRemove = collectionsToModify;
        }

        ServiceStateCollectionUpdateRequest updateDiskLinksRequest = ServiceStateCollectionUpdateRequest
                .create(collectionsToAdd, collectionsToRemove);

        Operation computeStateOp = Operation.createPatch(createInventoryUri(service.getHost(),
                computeStateLink))
                .setBody(updateDiskLinksRequest)
                .setReferer(service.getUri());

        return service.sendWithDeferredResult(computeStateOp);
    }

    public static DeferredResult> updatePersistentDiskAsAvailable(
            List diskLinks, StatelessService service) {
        List> getDiskStatesOp = diskLinks.stream()
                .map(diskLink -> service.sendWithDeferredResult(
                        Operation.createGet(createInventoryUri(service.getHost(), diskLink))
                                .setReferer(service.getHost().getUri()))
                        .exceptionally( exc -> {
                            Utils.log(AWSUtils.class, AWSUtils.class.getSimpleName(), Level.WARNING,
                                    "Exception occured while getting disk state for link '%s' : %s", diskLink, Utils.toString(exc));
                            return null;
                        })
                ).collect(Collectors.toList());

        //As this method is only called in delete operation, we should not fail if we can't find a disk.
        return DeferredResult.allOf(getDiskStatesOp)
                .thenApply(ops -> ops.stream().filter(Objects::nonNull)
                        .map(op -> op.getBody(DiskService.DiskState.class))
                        .filter(diskState -> diskState.persistent != null && diskState.persistent)
                        .map(diskState -> updateDiskAsAvailable(diskState, service))
                        .collect(Collectors.toList()));
    }

    private static String updateDiskAsAvailable(DiskService.DiskState diskState, Service service) {
        AWSUtils.updateDiskState(diskState, ResourceOperation.DETACH_DISK.operation, service)
                .thenApply(op -> op.getBody(DiskService.DiskState.class));
        return diskState.documentSelfLink;
    }

    /**
     *
     * removes the disklinks from the compute.
     */
    public static DeferredResult removeDiskLinks(String computeStateLink,
            List diskLinks, Service service) {

        if (CollectionUtils.isEmpty(diskLinks)) {
            return DeferredResult.completed(new ResourceState());
        }

        return AWSUtils.updateComputeState(computeStateLink, diskLinks,
                ResourceOperation.DETACH_DISK.operation, service)
                .thenApply(op -> (ResourceState) (op.getBody(ComputeService.ComputeState.class)));
    }


    public static DeferredResult setDeleteOnTerminateAttribute(
            AmazonEC2AsyncClient client, String instanceId,
            Map> deleteDiskMapByDeviceName,
            OperationContext opCtx) {
        List instanceBlockDeviceMappingSpecificationList =
                deleteDiskMapByDeviceName.entrySet().stream()
                        .map(entry -> new InstanceBlockDeviceMappingSpecification()
                                .withDeviceName(entry.getKey())
                                .withEbs(
                                        new EbsInstanceBlockDeviceSpecification()
                                                .withDeleteOnTermination(entry.getValue().right)
                                                .withVolumeId(entry.getValue().left)
                                )
                        ).collect(Collectors.toList());

        DeferredResult modifyInstanceAttrDr = new DeferredResult();
        ModifyInstanceAttributeRequest modifyInstanceAttrReq =
                new ModifyInstanceAttributeRequest()
                        .withInstanceId(instanceId).withAttribute(InstanceAttributeName.BlockDeviceMapping)
                        .withBlockDeviceMappings(instanceBlockDeviceMappingSpecificationList);

        AWSAsyncHandler modifyInstanceAttrHandler =
                new AWSAsyncHandler() {
                    @Override
                    protected void handleError(Exception exception) {
                        OperationContext.restoreOperationContext(opCtx);
                        modifyInstanceAttrDr.fail(exception);
                    }

                    @Override
                    protected void handleSuccess(
                            ModifyInstanceAttributeRequest request,
                            ModifyInstanceAttributeResult result) {
                        OperationContext.restoreOperationContext(opCtx);
                        modifyInstanceAttrDr.complete(new DiskService.DiskState());
                    }
                };
        client.modifyInstanceAttributeAsync(modifyInstanceAttrReq, modifyInstanceAttrHandler);
        return modifyInstanceAttrDr;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy