com.streamreduce.util.AWSClient.java Source code

Java tutorial

Introduction

Here is the source code for com.streamreduce.util.AWSClient.java

Source

/*
 * Copyright 2012 Nodeable Inc
 *
 *    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.streamreduce.util;

import com.google.common.collect.ImmutableSet;
import com.google.inject.Module;
import com.streamreduce.ProviderIdConstants;
import com.streamreduce.core.model.*;
import com.streamreduce.core.service.exception.InvalidCredentialsException;
import net.sf.json.*;
import net.sf.json.xml.XMLSerializer;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.Header;
import org.apache.http.message.BasicHeader;
import org.jclouds.ContextBuilder;
import org.jclouds.aws.ec2.AWSEC2ApiMetadata;
import org.jclouds.aws.ec2.AWSEC2Client;
import org.jclouds.aws.ec2.domain.Tag;
import org.jclouds.aws.ec2.reference.AWSEC2Constants;
import org.jclouds.aws.ec2.util.TagFilters;
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.domain.StorageMetadata;
import org.jclouds.cloudwatch.CloudWatchAsyncApi;
import org.jclouds.cloudwatch.CloudWatchApi;
import org.jclouds.compute.ComputeService;
import org.jclouds.compute.ComputeServiceContext;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.predicates.NodePredicates;
import org.jclouds.domain.Location;
import org.jclouds.domain.LocationBuilder;
import org.jclouds.domain.LocationScope;
import org.jclouds.logging.slf4j.config.SLF4JLoggingModule;
import org.jclouds.predicates.RetryablePredicate;
import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.RestContext;
import org.jclouds.sshj.config.SshjSshClientModule;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.google.common.base.Predicates.not;
import static com.google.common.collect.Sets.filter;
import static org.jclouds.compute.predicates.NodePredicates.TERMINATED;
import static org.jclouds.compute.predicates.NodePredicates.all;

/**
 * AWSClient provides necessary methods for interacting with Amazon Web Services.
 */
public class AWSClient extends ExternalIntegrationClient {
    private static final String BUCKET_PREFIX = "com.streamreduce";
    private static final String S3_OBJECT_EVENT_PREFIX = "event/";
    private static final String S3_OBJECT_SOBA_MESSAGE_PREFIX = "message/";

    private BlobStoreContext blobStoreContext;
    private ComputeServiceContext computeServiceContext;
    private RestContext<CloudWatchApi, CloudWatchAsyncApi> cloudWatchContext;

    private static final String EC2_API_BASE = "ec2.amazonaws.com";

    public AWSClient(Connection connection) {
        super(connection);
        Assert.isTrue(connection.getProviderId().equals(ProviderIdConstants.AWS_PROVIDER_ID));
    }

    public AWSClient(OutboundConfiguration outboundConfiguration) {
        super(outboundConfiguration);
        Assert.isTrue(outboundConfiguration.getProtocol().equals("s3"));
    }

    /**
     * Destroys a compute instance and returns the result.
     *
     * @param nodeId the node id to destroy
     *
     * @return the success of the destroy attempt
     *
     * @throws InvalidCredentialsException if the connection's credentials are invalid
     */
    public boolean destroyEC2Instance(String nodeId) throws InvalidCredentialsException {
        getComputeServiceContext().getComputeService().destroyNode(nodeId);

        NodeMetadata nodeMetadata = getEC2Instance(nodeId);

        return new RetryablePredicate<>(NodePredicates.RUNNING, 30, 5, TimeUnit.SECONDS).apply(nodeMetadata);
    }

    /**
     * Reboots the instance and returns the result.
     *
     * @param nodeId the node id to reboot
     *
     * @return the success of the reboot attempt
     *
     * @throws InvalidCredentialsException if the connection's credentials are invalid
     */
    public boolean rebootEC2Instance(String nodeId) throws InvalidCredentialsException {
        getComputeServiceContext().getComputeService().rebootNode(nodeId);

        NodeMetadata nodeMetadata = getEC2Instance(nodeId);

        return new RetryablePredicate<>(NodePredicates.RUNNING, 30, 5, TimeUnit.SECONDS).apply(nodeMetadata);
    }

    /**
     * Returns the node metadata for the given node id.
     *
     * @param nodeId the jclouds node id
     *
     * @return the node metadata
     *
     * @throws InvalidCredentialsException if the connection's credentials are invalid
     */
    public NodeMetadata getEC2Instance(String nodeId) throws InvalidCredentialsException {
        return getComputeServiceContext().getComputeService().getNodeMetadata(nodeId);
    }

    /**
     * Return the {@link Tag}s currently applied to the EC2 instance identified by the node id passed in.
     *
     * @param nodeId the node id of the EC2 instance we're interested in
     *
     * @return set of tag objects.
     *
     * @throws InvalidCredentialsException if the connection's credentials are invalid
     */
    public Set<Tag> getEC2InstanceTags(String nodeId) throws InvalidCredentialsException {
        AWSEC2Client ec2Client = AWSEC2Client.class
                .cast(getComputeServiceContext().unwrap(AWSEC2ApiMetadata.CONTEXT_TOKEN).getApi());

        return ec2Client.getTagServices().describeTagsInRegion(null,
                TagFilters.filters().resourceType(TagFilters.ResourceType.INSTANCE).resourceId(nodeId).build());
    }

    /**
     * Returns a list of JSONObjects representing the compute nodes available to this AWS connection.
     *
     * @return list of JSONObjects representing the compute nodes
     *
     * @throws InvalidCredentialsException if the client's credentials are invalid
     */
    public List<JSONObject> getEC2Instances() throws InvalidCredentialsException {
        ComputeService computeService = getComputeServiceContext().getComputeService();
        Set<? extends NodeMetadata> rawComputeNodes = filter(computeService.listNodesDetailsMatching(all()),
                not(TERMINATED));
        List<JSONObject> computeNodes = new ArrayList<>();

        for (NodeMetadata computeNode : rawComputeNodes) {
            computeNodes.add(JSONObject.fromObject(computeNode));
        }

        return computeNodes;
    }

    public Set<? extends StorageMetadata> getS3Buckets() throws InvalidCredentialsException {
        BlobStore store = getBlobStoreContext().getBlobStore();
        return store.list();
    }

    /**
     * Returns a list of JSONObjects representing the storage blobs available to this AWS connection.
     *
     * @return list of JSONObjects representing the storage blobs
     *
     * @throws InvalidCredentialsException if the client's credentials are invalid
     */
    public List<JSONObject> getS3BucketsAsJson() throws InvalidCredentialsException {
        Set<? extends StorageMetadata> rawBlobs = getS3Buckets();
        List<JSONObject> blobs = new ArrayList<>();

        for (StorageMetadata storageMetadata : rawBlobs) {
            blobs.add(JSONObject.fromObject(storageMetadata));
        }

        return blobs;
    }

    public List<JSONObject> describeRegions() throws IOException, InvalidCredentialsException {
        Map<String, String> params = new HashMap<>();
        params.put("Action", "DescribeRegions");
        params.put("Version", "2012-08-15");
        JSONObject rawResponse = makeRequest(EC2_API_BASE, params);
        return getJSONChildren("regionInfo", rawResponse);
    }

    public List<JSONObject> describeImages(String endpoint, Set<String> imageIds)
            throws IOException, InvalidCredentialsException {
        Map<String, String> params = new HashMap<>();
        params.put("Action", "DescribeImages");
        params.put("Version", "2012-08-15");
        int count = 0;
        for (String imageId : imageIds) {
            count += 1;
            params.put("ImageId." + count, imageId);
        }
        JSONObject rawResponse = makeRequest(endpoint, params);
        return getJSONChildren("item", rawResponse.getJSONObject("imagesSet"));
    }

    public List<JSONObject> describeInstances(String endpoint) throws IOException, InvalidCredentialsException {
        List<JSONObject> response = new ArrayList<>();
        HashMap<String, String> params = new HashMap<>();
        params.put("Action", "DescribeInstances");
        params.put("Version", "2012-08-15");
        params.put("Filter.1.Name", "instance-state-name");
        params.put("Filter.1.Value.1", "running");
        params.put("Filter.1.Value.2", "stopped");
        JSONObject rawResponse = makeRequest(endpoint, params);
        for (JSONObject reservation : getJSONChildren("item", rawResponse.getJSONObject("reservationSet"))) {
            response.addAll(getJSONChildren("item", reservation.getJSONObject("instancesSet")));
        }
        return response;
    }

    public List<JSONObject> describeInstanceStatus(String endpoint)
            throws IOException, InvalidCredentialsException {
        List<JSONObject> response = new ArrayList<>();
        HashMap<String, String> params = new HashMap<>();
        String nextToken = null;
        do {
            params.put("Action", "DescribeInstanceStatus");
            params.put("Version", "2012-08-15");
            if (nextToken != null) {
                params.put("NextToken", nextToken);
            }
            JSONObject rawResponse = makeRequest(endpoint, params);
            response.addAll(getJSONChildren("item", rawResponse.getJSONObject("instanceStatusSet")));
            Object obj = rawResponse.get("NextToken");
            nextToken = (String) obj;
        } while (nextToken != null);
        return response;
    }

    public List<JSONObject> listMetrics(String endpoint) throws IOException, InvalidCredentialsException {
        List<JSONObject> response = new ArrayList<>();
        String nextToken = null;
        do {
            HashMap<String, String> params = new HashMap<>();
            params.put("Action", "ListMetrics");
            params.put("Version", "2010-08-01");
            if (nextToken != null) {
                params.put("NextToken", nextToken);
            }
            JSONObject rawResponse = makeRequest(endpoint, params);
            response.addAll(getJSONChildren("member",
                    rawResponse.getJSONObject("ListMetricsResult").getJSONObject("Metrics")));
            Object obj = rawResponse.getJSONObject("ListMetricsResult").get("NextToken");
            nextToken = (String) obj;
        } while (nextToken != null);
        return response;
    }

    public List<JSONObject> getMetricStatistics(String endpoint, String namespace, String metricName,
            String dimensionName, String dimensionValue) throws IOException, InvalidCredentialsException {
        Map<String, String> params = new HashMap<>();
        params.put("Action", "GetMetricStatistics");
        params.put("Version", "2010-08-01");
        params.put("Namespace", namespace);
        params.put("MetricName", metricName);
        params.put("Dimensions.member.1.Name", dimensionName);
        params.put("Dimensions.member.1.Value", dimensionValue);
        params.put("Statistics.member.1", "Average");
        long now = new Date().getTime();
        params.put("StartTime", getFormattedTimestamp(new Date(now - 60 * 60 * 1000)));
        params.put("EndTime", getFormattedTimestamp(new Date(now)));
        params.put("Period", "60");
        JSONObject rawResponse = makeRequest(endpoint, params);
        return getJSONChildren("member",
                rawResponse.getJSONObject("GetMetricStatisticsResult").getJSONObject("Datapoints"));
    }

    public Map<String, JSONArray> describeInstanceAndImageForRunningAndStoppedInstances(String endpoint)
            throws IOException, InvalidCredentialsException {
        Map<String, JSONArray> instanceAndImageByInstanceId = new HashMap<>();
        Map<String, JSONObject> instancesById = new HashMap<>();
        Map<String, JSONObject> imagesById = new HashMap<>();
        List<JSONObject> instances = describeInstances(endpoint);
        for (JSONObject instance : instances) {
            String instanceId = instance.getString("instanceId");
            String imageId = instance.getString("imageId");
            instancesById.put(instanceId, instance);
            imagesById.put(imageId, null);
        }
        List<JSONObject> images = describeImages(endpoint, imagesById.keySet());
        for (JSONObject image : images) {
            String imageId = image.getString("imageId");
            imagesById.put(imageId, image);
        }
        for (JSONObject instance : instances) {
            String instanceId = instance.getString("instanceId");
            String imageId = instance.getString("imageId");
            JSONArray arr = new JSONArray();
            arr.element(instancesById.get(instanceId));
            arr.element(imagesById.get(imageId));
            instanceAndImageByInstanceId.put(instanceId, arr);
        }
        return instanceAndImageByInstanceId;
    }

    public Map<String, JSONObject> getMetricsStatisticsForRunningInstances(String endpoint)
            throws IOException, InvalidCredentialsException {
        Map<String, JSONObject> statisticsByInstance = new HashMap<>();
        List<JSONObject> instances = describeInstances(endpoint);
        List<JSONObject> metrics = listMetrics(endpoint.replace("ec2", "monitoring"));
        List<JSONObject> runningInstances = filterInstancesByInstanceState("running", instances);
        for (JSONObject instance : runningInstances) {
            String instanceId = instance.getString("instanceId");
            List<JSONObject> instanceMetrics = filterMetricsByInstanceId(instanceId, metrics);
            JSONObject statistics = new JSONObject();
            for (JSONObject metric : instanceMetrics) {
                List<JSONObject> datapoints = getMetricStatistics(endpoint.replace("ec2", "monitoring"),
                        metric.getString("Namespace"), metric.getString("MetricName"), "InstanceId", instanceId);
                statistics.element(metric.getString("MetricName"), datapoints.get(0));
            }
            statisticsByInstance.put(instanceId, statistics);
        }
        return statisticsByInstance;
    }

    private List<JSONObject> filterInstancesByInstanceState(String instanceState, List<JSONObject> instances) {
        List<JSONObject> filtered = new ArrayList<>();
        for (JSONObject instance : instances) {
            String stateName = instance.getJSONObject("instanceState").getString("name");
            if (stateName.equals(instanceState)) {
                filtered.add(instance);
            }
        }
        return filtered;
    }

    private List<JSONObject> filterMetricsByInstanceId(String instanceId, List<JSONObject> metrics) {
        List<JSONObject> filtered = new ArrayList<>();
        for (JSONObject metric : metrics) {
            JSONObject dimension = metric.getJSONObject("Dimensions").getJSONObject("member");
            String dimensionName = dimension.getString("Name");
            String dimensionValue = dimension.getString("Value");
            if (dimensionName.equals("InstanceId") && dimensionValue.equals(instanceId)) {
                filtered.add(metric);
            }
        }
        return filtered;
    }

    public List<JSONObject> getService(String endpoint) throws IOException, InvalidCredentialsException {
        JSONObject rawResponse = makeS3Request(endpoint, null, "/");
        return getJSONChildren("Bucket", rawResponse.getJSONObject("Buckets"));
    }

    public JSONObject getBucketLocation(String endpoint, String bucket)
            throws IOException, InvalidCredentialsException {
        JSONObject rawResponse = makeS3Request(endpoint, bucket, "/?location");
        return new JSONObject().element("LocationConstraint",
                rawResponse == null ? "US" : rawResponse.get("#text"));
    }

    public List<JSONObject> getBucketsWithLocation(String endpoint)
            throws IOException, InvalidCredentialsException {
        List<JSONObject> buckets = getService("s3.amazonaws.com");
        for (JSONObject bucket : buckets) {
            JSONObject location = getBucketLocation("s3.amazonaws.com", bucket.getString("Name"));
            bucket.element("LocationConstraint", location.get("LocationConstraint"));
        }
        return buckets;
    }

    private JSONObject makeRequest(String endpoint, Map<String, String> params)
            throws IOException, InvalidCredentialsException {
        String url = signUrl("https://" + endpoint + "/", params);
        String rawResponse = HTTPUtils.openUrl(url, "GET", null, null, null, null, null, null);
        JSONObject json = (JSONObject) new XMLSerializer().read(rawResponse);
        return json;
    }

    private JSONObject makeS3Request(String endpoint, String bucket, String path)
            throws IOException, InvalidCredentialsException {
        String date = getS3FormattedTimestamp(new Date());
        String signature = calculateS3Signature((bucket == null ? "" : "/" + bucket) + path,
                getConnectionCredentials().getCredential(), date);
        List<Header> requestHeader = new ArrayList<>();
        requestHeader.add(new BasicHeader("Date", date));
        requestHeader.add(new BasicHeader("Authorization",
                "AWS " + getConnectionCredentials().getIdentity() + ":" + signature));
        String url = "https://" + (bucket == null ? "" : bucket + ".") + endpoint + path;
        String rawResponse = HTTPUtils.openUrl(url, "GET", null, null, null, null, requestHeader, null);
        JSON json = new XMLSerializer().read(rawResponse);
        return (json instanceof JSONObject ? (JSONObject) json : null);
    }

    private List<JSONObject> getJSONChildren(String key, JSONObject json) {
        List<JSONObject> array = new ArrayList<>();
        Object obj = json.get(key);
        if (obj instanceof JSONObject) {
            array.add((JSONObject) obj);
        } else if (obj instanceof JSONArray) {
            array.addAll((JSONArray) obj);
        }
        return array;
    }

    private String signUrl(String rawUrl, Map<String, String> params) {
        if (params == null) {
            params = new HashMap<>();
        }
        // http://docs.amazonwebservices.com/general/latest/gr/signature-version-2.html
        params.put("AWSAccessKeyId", getConnectionCredentials().getIdentity());
        params.put("Timestamp", getFormattedTimestamp(new Date()));
        params.put("SignatureVersion", "2");
        params.put("SignatureMethod", "HmacSHA256");
        params.put("Signature", calculateSignature(rawUrl, getConnectionCredentials().getCredential(), params));
        return rawUrl + "?" + getQueryString(params);
    }

    private String getFormattedTimestamp(Date date) {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
        df.setTimeZone(TimeZone.getTimeZone("UTC"));
        return df.format(date);
    }

    private String getS3FormattedTimestamp(Date date) {
        SimpleDateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
        df.setTimeZone(TimeZone.getTimeZone("UTC"));
        return df.format(date);
    }

    private String getQueryString(Map<String, String> params) {
        StringBuilder query = new StringBuilder();
        for (Iterator<Map.Entry<String, String>> iter = params.entrySet().iterator(); iter.hasNext();) {
            Map.Entry<String, String> param = iter.next();
            query.append(urlEncode(param.getKey())).append("=").append(urlEncode(param.getValue()));
            if (iter.hasNext())
                query.append("&");
        }
        return query.toString();
    }

    private String calculateSignature(String rawUrl, String key, Map<String, String> params) {
        StringBuilder canonical = new StringBuilder();
        try {
            canonical.append("GET").append("\n");
            canonical.append(new URL(rawUrl).getHost()).append("\n");
            canonical.append("/").append("\n");
            SortedMap<String, String> sorted = new TreeMap<>(params);
            canonical.append(getQueryString(sorted));
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256"));
            byte[] sign = mac.doFinal(canonical.toString().getBytes("UTF-8"));
            return new String(Base64.encodeBase64(sign));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private String calculateS3Signature(String rawUrl, String key, String date) {
        StringBuilder canonical = new StringBuilder();
        try {
            canonical.append("GET").append("\n");
            canonical.append("\n");
            canonical.append("\n");
            canonical.append(date).append("\n");
            canonical.append(rawUrl);
            Mac mac = Mac.getInstance("HmacSHA1");
            mac.init(new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA1"));
            byte[] sign = mac.doFinal(canonical.toString().getBytes("UTF-8"));
            return new String(Base64.encodeBase64(sign));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private String urlEncode(String value) {
        if (value == null)
            return "";
        try {
            return URLEncoder.encode(value, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Returns the human readable name for the region id passed in.
     *
     * @param regionId the region id to get its human readable name
     *
     * @return the human readable name or the region id if a human readable name isn't known
     */
    public static String getRegionName(String regionId) {
        // Map the region ids to human readable names (Known regions as of 16/08/2011)
        // Note: This will be unnecessary once jclouds has LocationMetadata
        // Note: This will need to be refactored when we get multiple cloud providers supported if jclouds
        //       isn't updated by then.
        if (regionId.equals("ap-northeast-1")) {
            return "Asia Pacific (Tokyo)";
        } else if (regionId.equals("ap-southeast-1")) {
            return "Asia Pacific (Singapore)";
        } else if (regionId.equals("eu-west-1")) {
            return "EU West (Ireland)";
        } else if (regionId.equals("sa-east-1")) {
            return "South America (Sao Paulo)";
        } else if (regionId.equals("us-east-1")) {
            return "US East (Virginia)";
        } else if (regionId.equals("us-west-1")) {
            return "US West (California)";
        } else if (regionId.equals("us-west-2")) {
            return "US West (Oregon)";
        } else {
            return regionId; // Reasonable default
        }
    }

    /**
     * Returns the iso3166 code for the region id passed in.
     *
     * @param regionId the region id to get its iso3166 code
     *
     * @return the iso3166 code or the region id if a iso3166 code isn't known
     */
    public static String getRegionIso3166Code(String regionId) {
        if (regionId.equals("ap-northeast-1")) {
            return "JP-13";
        } else if (regionId.equals("ap-southeast-1")) {
            return "SG";
        } else if (regionId.equals("eu-west-1")) {
            return "IE";
        } else if (regionId.equals("sa-east-1")) {
            return "BR-SP";
        } else if (regionId.equals("us-east-1")) {
            return "US-VA";
        } else if (regionId.equals("us-west-1")) {
            return "US-CA";
        } else if (regionId.equals("us-west-2")) {
            return "US-OR";
        } else {
            return regionId; // Reasonable default
        }
    }

    /**
     * Returns the human readable name for the hardware id passed in.
     *
     * @param hardwareId the hardware id to get its human readable name
     *
     * @return the human readable name or the hardware id if a human readable name isn't known
     */
    public static String getHardwareName(String hardwareId) {
        // Map the hardware ids to human readable names (Known hardware ids as of 16/08/2011)
        // http://aws.amazon.com/ec2/instance-types/
        // Note: This will be unnecessary once jclouds has LocationMetadata
        // Note: This will need to be refactored when we get multiple cloud providers supported if jclouds
        //       isn't updated by then.
        if (hardwareId.equals("c1.medium")) {
            return "High-CPU Medium";
        } else if (hardwareId.equals("c1.xlarge")) {
            return "High-CPU Extra Large";
        } else if (hardwareId.equals("cg1.4xlarge")) {
            return "Cluster GPU Quadruple Extra Large";
        } else if (hardwareId.equals("cc1.4xlarge")) {
            return "Cluster Compute Quadruple Extra Large";
        } else if (hardwareId.equals("m1.large")) {
            return "Large";
        } else if (hardwareId.equals("m1.small")) {
            return "Small";
        } else if (hardwareId.equals("m1.xlarge")) {
            return "Extra Large";
        } else if (hardwareId.equals("m2.xlarge")) {
            return "High-Memory Extra Large";
        } else if (hardwareId.equals("m2.2xlarge")) {
            return "High-Memory Double Extra Large";
        } else if (hardwareId.equals("m2.4xlarge")) {
            return "High-Memory Quadruple Extra Large";
        } else if (hardwareId.equals("t1.micro")) {
            return "Micro";
        } else {
            return hardwareId; // Reasonable default
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void validateConnection() throws InvalidCredentialsException, IOException {
        getComputeServiceContext();
        getBlobStoreContext();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void cleanUp() {
        try {
            if (computeServiceContext != null) {
                computeServiceContext.close();
            }
        } catch (Exception e) {
            LOGGER.error("Unable to clean up the AWS EC2 connection.", e);
        }
        try {
            if (cloudWatchContext != null) {
                cloudWatchContext.close();
            }
        } catch (Exception e) {
            LOGGER.error("Unable to clean up the AWS Cloudwatch connection.", e);
        }
        try {
            if (blobStoreContext != null) {
                blobStoreContext.close();
            }
        } catch (Exception e) {
            LOGGER.error("Unable to clean up the AWS Blobstore connection.", e);
        }
    }

    /**
     * Returns the jclouds {@link ComputeServiceContext} for AWS EC2.
     *
     * @return the jclouds compute service context
     *
     * @throws InvalidCredentialsException if the credentials associated with this client are invalid
     */
    public ComputeServiceContext getComputeServiceContext() throws InvalidCredentialsException {
        if (computeServiceContext == null) {
            Properties overrides = new Properties();

            // Empty the default AMI queries for quicker image lookup
            overrides.setProperty(AWSEC2Constants.PROPERTY_EC2_AMI_QUERY, "");
            overrides.setProperty(AWSEC2Constants.PROPERTY_EC2_CC_AMI_QUERY, "");

            String username = getConnectionCredentials().getIdentity();
            String password = getConnectionCredentials().getCredential();

            if (!StringUtils.hasText(username) || !StringUtils.hasText(password)) {
                throw new InvalidCredentialsException(
                        "You must supply an identity/username for cloud connections.");
            }

            try {
                computeServiceContext = ContextBuilder.newBuilder("aws-ec2").credentials(username, password)
                        .modules(ImmutableSet.<Module>of(new SshjSshClientModule(), new SLF4JLoggingModule()))
                        .overrides(overrides).buildView(ComputeServiceContext.class);

                // since the compute service context doesn't try to auth against AWS until it
                // needs to, make a call to listNodes() to force the auth.
                computeServiceContext.getComputeService().listNodes();
            } catch (AuthorizationException ae) {
                throw new InvalidCredentialsException(ae);
            }
        }

        return computeServiceContext;
    }

    /**
     * Returns the jclouds {@link BlobStoreContext} for AWS S3.
     *
     * @return the jclouds blob store context
     *
     * @throws InvalidCredentialsException if the credentials associated with this client are invalid
     */
    public BlobStoreContext getBlobStoreContext() throws InvalidCredentialsException {
        if (blobStoreContext == null) {
            String username = getConnectionCredentials().getIdentity();
            String password = getConnectionCredentials().getCredential();

            if (!StringUtils.hasText(username) || !StringUtils.hasText(password)) {
                throw new InvalidCredentialsException(
                        "You must supply an identity/username for cloud connections.");
            }

            try {
                blobStoreContext = ContextBuilder.newBuilder("aws-s3").credentials(username, password)
                        .modules(ImmutableSet.<Module>of(new SLF4JLoggingModule()))
                        .buildView(BlobStoreContext.class);
            } catch (AuthorizationException ae) {
                throw new InvalidCredentialsException(ae);
            }
        }

        return blobStoreContext;
    }

    /**
     * Returns the jclouds {@link RestContext} for AWS CloudWatch.
     *
     * @return the jclouds RestContext for AWS Cloudwatch
     *
     * @throws InvalidCredentialsException if the credentials associated with this client are invalid
     */
    public RestContext<CloudWatchApi, CloudWatchAsyncApi> getCloudWatchServiceContext()
            throws InvalidCredentialsException {
        if (cloudWatchContext == null) {
            String username = getConnectionCredentials().getIdentity();
            String password = getConnectionCredentials().getCredential();

            try {
                cloudWatchContext = ContextBuilder.newBuilder("aws-cloudwatch").credentials(username, password)
                        .modules(ImmutableSet.<Module>of(new SLF4JLoggingModule())).build();
            } catch (AuthorizationException ae) {
                throw new InvalidCredentialsException(ae);
            }
        }

        return cloudWatchContext;
    }

    /**
     *
     * @param outboundConfiguration
     * @return the name of the created bucket
     */
    public String createBucket(OutboundConfiguration outboundConfiguration) throws InvalidCredentialsException {
        BlobStore s3BlobStore = getBlobStoreContext().getBlobStore();
        String bucketName = convertOutboundConnectionToBucketName(outboundConfiguration);

        Location location = null;
        if (StringUtils.hasText(outboundConfiguration.getDestination())) {
            LocationBuilder builder = new LocationBuilder();
            builder.id(outboundConfiguration.getDestination());
            builder.scope(LocationScope.REGION);
            builder.description(outboundConfiguration.getDestination());

            location = builder.build();
        }

        s3BlobStore.createContainerInLocation(location, bucketName); //defaults to us-standard if location is null
        return bucketName;
    }

    /**
     *
     * @param outboundConfiguration
     * @param payloadIdentifier
     * @param payload bytes to be written to S3.
     * @return an eTag of the object that was just created.
     */
    public String pushToS3(OutboundConfiguration outboundConfiguration, String payloadIdentifier, byte[] payload)
            throws InvalidCredentialsException {
        BlobStore s3BlobStore = getBlobStoreContext().getBlobStore();
        String bucketName = createBucket(outboundConfiguration);
        Blob blob = s3BlobStore.blobBuilder(payloadIdentifier).payload(payload).build();
        return s3BlobStore.putBlob(bucketName, blob);
    }

    /**
     * Extracts a bucket name from an OutboundConfiguration.  If the OutboundConfiguration's namespace property is non-null
     * and contains text, that property is used as the bucketName.  Otherwise, a default bucketname in the form of
     * "com.streamreduce.${account.id}" is used to create the bucket.
     * @param outboundConfiguration - The OutboundConfiguration reference representing an outbound operation to S3
     * @return Either OutboundConfiguration.getNamespace() or "com.streamreduce.${account.id}" if namespace
     * is empty.
     */
    String convertOutboundConnectionToBucketName(OutboundConfiguration outboundConfiguration) {
        if (StringUtils.hasText(outboundConfiguration.getNamespace())) {
            return outboundConfiguration.getNamespace().toLowerCase();
        } else {
            return BUCKET_PREFIX + "." + outboundConfiguration.getOriginatingConnection().getAccount().getId()
                    .toString().toLowerCase();
        }
    }

}