// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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.cloud.bridge.service.core.ec2;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.SignatureException;
import java.sql.SQLException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
import org.xml.sax.SAXException;
import com.cloud.bridge.model.CloudStackServiceOfferingVO;
import com.cloud.bridge.persist.dao.CloudStackAccountDao;
import com.cloud.bridge.persist.dao.CloudStackSvcOfferingDao;
import com.cloud.bridge.persist.dao.OfferingDao;
import com.cloud.bridge.service.UserContext;
import com.cloud.bridge.service.core.ec2.EC2ImageAttributes.ImageAttribute;
import com.cloud.bridge.service.exception.EC2ServiceException;
import com.cloud.bridge.service.exception.EC2ServiceException.ClientError;
import com.cloud.bridge.service.exception.EC2ServiceException.ServerError;
import com.cloud.bridge.util.ConfigurationHelper;
import com.cloud.stack.CloudStackApi;
import com.cloud.stack.models.CloudStackAccount;
import com.cloud.stack.models.CloudStackDiskOffering;
import com.cloud.stack.models.CloudStackInfoResponse;
import com.cloud.stack.models.CloudStackIngressRule;
import com.cloud.stack.models.CloudStackIpAddress;
import com.cloud.stack.models.CloudStackKeyPair;
import com.cloud.stack.models.CloudStackKeyValue;
import com.cloud.stack.models.CloudStackNetwork;
import com.cloud.stack.models.CloudStackNetworkOffering;
import com.cloud.stack.models.CloudStackNic;
import com.cloud.stack.models.CloudStackOsType;
import com.cloud.stack.models.CloudStackPasswordData;
import com.cloud.stack.models.CloudStackResourceLimit;
import com.cloud.stack.models.CloudStackResourceTag;
import com.cloud.stack.models.CloudStackSecurityGroup;
import com.cloud.stack.models.CloudStackServiceOffering;
import com.cloud.stack.models.CloudStackSnapshot;
import com.cloud.stack.models.CloudStackTemplate;
import com.cloud.stack.models.CloudStackTemplatePermission;
import com.cloud.stack.models.CloudStackUser;
import com.cloud.stack.models.CloudStackUserVm;
import com.cloud.stack.models.CloudStackVolume;
import com.cloud.stack.models.CloudStackZone;
import com.cloud.utils.component.ManagerBase;


/**
 * EC2Engine processes the ec2 commands and calls their cloudstack analogs
 *
 */
@Component
public class EC2Engine extends ManagerBase {
    protected final static Logger logger = Logger.getLogger(EC2Engine.class);
    String managementServer = null;
    String cloudAPIPort = null;

    @Inject
    CloudStackSvcOfferingDao scvoDao;
    @Inject
    OfferingDao ofDao;
    @Inject
    CloudStackAccountDao accDao;

    private CloudStackApi _eng = null;

    private CloudStackAccount currentAccount = null;

    public EC2Engine() throws IOException {
    }

    @Override
    public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {

        try {
            loadConfigValues();
        } catch (IOException e) {
            logger.error("EC2Engine Configuration failure", e);
            throw new ConfigurationException("EC2Engine configuration failure");
        }
        return true;
    }

    /**
     * Which management server to we talk to?
     * Load a mapping form Amazon values for 'instanceType' to cloud defined
     * diskOfferingId and serviceOfferingId.
     *
     * @throws IOException
     */
    private void loadConfigValues() throws IOException {
        File propertiesFile = ConfigurationHelper.findConfigurationFile("ec2-service.properties");
        if (null != propertiesFile) {
            logger.info("Use EC2 properties file: " + propertiesFile.getAbsolutePath());
            Properties EC2Prop = new Properties();
            try (FileInputStream ec2PropFile = new FileInputStream(propertiesFile)) {
                EC2Prop.load(ec2PropFile);
            } catch (FileNotFoundException e) {
                logger.warn("Unable to open properties file: " + propertiesFile.getAbsolutePath(), e);
            } catch (IOException e) {
                logger.warn("Unable to read properties file: " + propertiesFile.getAbsolutePath(), e);
            }
            managementServer = EC2Prop.getProperty("managementServer");
            cloudAPIPort = EC2Prop.getProperty("cloudAPIPort", null);

            try {
                if (ofDao.getOfferingCount() == 0) {
                    String strValue = EC2Prop.getProperty("m1.small.serviceId");
                    if (strValue != null)
                        ofDao.setOfferMapping("m1.small", strValue);

                    strValue = EC2Prop.getProperty("m1.large.serviceId");
                    if (strValue != null)
                        ofDao.setOfferMapping("m1.large", strValue);

                    strValue = EC2Prop.getProperty("m1.xlarge.serviceId");
                    if (strValue != null)
                        ofDao.setOfferMapping("m1.xlarge", strValue);

                    strValue = EC2Prop.getProperty("c1.medium.serviceId");
                    if (strValue != null)
                        ofDao.setOfferMapping("c1.medium", strValue);

                    strValue = EC2Prop.getProperty("c1.xlarge.serviceId");
                    if (strValue != null)
                        ofDao.setOfferMapping("c1.xlarge", strValue);

                    strValue = EC2Prop.getProperty("m2.xlarge.serviceId");
                    if (strValue != null)
                        ofDao.setOfferMapping("m2.xlarge", strValue);

                    strValue = EC2Prop.getProperty("m2.2xlarge.serviceId");
                    if (strValue != null)
                        ofDao.setOfferMapping("m2.2xlarge", strValue);

                    strValue = EC2Prop.getProperty("m2.4xlarge.serviceId");
                    if (strValue != null)
                        ofDao.setOfferMapping("m2.4xlarge", strValue);

                    strValue = EC2Prop.getProperty("cc1.4xlarge.serviceId");
                    if (strValue != null)
                        ofDao.setOfferMapping("cc1.4xlarge", strValue);
                }
            } catch (Exception e) {
                logger.error("Unexpected exception ", e);
            }
        } else
            logger.error("ec2-service.properties not found");
    }

    /**
     * Helper function to manage the api connection
     *
     * @return
     */
    private CloudStackApi getApi() {
        if (_eng == null) {
            _eng = new CloudStackApi(managementServer, cloudAPIPort, false);
        }
        // regardless of whether _eng is initialized, we must make sure
        // access/secret keys are current with what's in the UserCredentials
        _eng.setApiKey(UserContext.current().getAccessKey());
        _eng.setSecretKey(UserContext.current().getSecretKey());
        return _eng;
    }

    /**
     * Verifies account can access CloudStack
     *
     * @param accessKey
     * @param secretKey
     * @return
     * @throws EC2ServiceException
     */
    public boolean validateAccount(String accessKey, String secretKey) throws EC2ServiceException {
        String oldApiKey = null;
        String oldSecretKey = null;

        if (accessKey == null || secretKey == null) {
            return false;
        }

        // okay, instead of using the getApi() nonsense for validate, we are going to manage _eng
        if (_eng == null) {
            _eng = new CloudStackApi(managementServer, cloudAPIPort, false);
        }

        try {
            oldApiKey = _eng.getApiKey();
            oldSecretKey = _eng.getSecretKey();
        } catch (Exception e) {
            // we really don't care, and expect this
        }
        try {
            _eng.setApiKey(accessKey);
            _eng.setSecretKey(secretKey);
            List<CloudStackAccount> accts = _eng.listAccounts(null, null, null, null, null, null, null, null);
            if (oldApiKey != null && oldSecretKey != null) {
                _eng.setApiKey(oldApiKey);
                _eng.setSecretKey(oldSecretKey);
            }
            if (accts == null) {
                return false;
            }
            return true;
        } catch (Exception e) {
            logger.error("Validate account failed!");
            throw new EC2ServiceException(ClientError.AuthFailure, "User not authorised");
        }
    }

    /**
     * Creates a security group
     *
     * @param groupName
     * @param groupDesc
     * @return
     */
    public EC2SecurityGroup createSecurityGroup(String groupName, String groupDesc) {
        EC2SecurityGroup sg = new EC2SecurityGroup();
        try {
            CloudStackSecurityGroup grp = getApi().createSecurityGroup(groupName, null, groupDesc, null);
            if (grp != null && grp.getId() != null) {
                sg.setId(grp.getId());
            }
        } catch (Exception e) {
            logger.error("EC2 CreateSecurityGroup - ", e);
            handleException(e);
        }
        return sg;
    }

    /**
     * Deletes a security group
     *
     * @param groupName
     * @return
     */
    public boolean deleteSecurityGroup(String groupName) {
        try {
            CloudStackInfoResponse resp = getApi().deleteSecurityGroup(null, null, null, groupName);
            if (resp != null) {
                return resp.getSuccess();
            }
            return false;
        } catch (Exception e) {
            logger.error("EC2 DeleteSecurityGroup - ", e);
            handleException(e);
        }
        return false;
    }

    /**
     * returns a list of security groups
     *
     * @param request
     * @return
     */
    public EC2DescribeSecurityGroupsResponse describeSecurityGroups(EC2DescribeSecurityGroups request) {
        EC2DescribeSecurityGroupsResponse response = new EC2DescribeSecurityGroupsResponse();
        try {
            response = listSecurityGroups(request.getGroupSet());
            EC2GroupFilterSet gfs = request.getFilterSet();
            if (gfs != null) {
                response = gfs.evaluate(response);
            }
        } catch (Exception e) {
            logger.error("EC2 DescribeSecurityGroups - ", e);
            handleException(e);
        }
        return response;
    }

    /**
     * CloudStack supports revoke only by using the ruleid of the ingress rule.
     * We list all security groups and find the matching group and use the first ruleId we find.
     *
     * @param request
     * @return
     */
    public boolean revokeSecurityGroup(EC2AuthorizeRevokeSecurityGroup request) {
        try {
            String[] groupSet = new String[1];
            groupSet[0] = request.getName();
            String ruleId = null;

            EC2IpPermission[] items = request.getIpPermissionSet();

            EC2DescribeSecurityGroupsResponse response = listSecurityGroups(groupSet);
            EC2SecurityGroup[] groups = response.getGroupSet();
            if (groups.length == 0) {
                throw new Exception("Unable to find security group name");
            }

            for (EC2SecurityGroup group : groups) {
                EC2IpPermission[] perms = group.getIpPermissionSet();
                for (EC2IpPermission perm : perms) {
                    ruleId = doesRuleMatch(items[0], perm);
                    if (ruleId != null)
                        break;
                }
            }

            if (null == ruleId)
                throw new Exception("Specified Ip permission is invalid");
            CloudStackInfoResponse resp = getApi().revokeSecurityGroupIngress(ruleId);
            if (resp != null) {
                return resp.getSuccess();
            }
        } catch (Exception e) {
            logger.error("EC2 revokeSecurityGroupIngress" + " - " + e.getMessage());
            handleException(e);
        }
        return false;
    }

    /**
     * authorizeSecurityGroup
     *
     * @param request - ip permission parameters
     */
    public boolean authorizeSecurityGroup(EC2AuthorizeRevokeSecurityGroup request) {
        EC2IpPermission[] items = request.getIpPermissionSet();

        try {
            for (EC2IpPermission ipPerm : items) {
                EC2SecurityGroup[] groups = ipPerm.getUserSet();

                List<CloudStackKeyValue> secGroupList = new ArrayList<CloudStackKeyValue>();
                for (EC2SecurityGroup group : groups) {
                    CloudStackKeyValue pair = new CloudStackKeyValue();
                    pair.setKeyValue(group.getAccount(), group.getName());
                    secGroupList.add(pair);
                }
                CloudStackSecurityGroup resp = null;
                if (ipPerm.getProtocol().equalsIgnoreCase("icmp")) {
                    resp =
                            getApi().authorizeSecurityGroupIngress(null, constructList(ipPerm.getIpRangeSet()), null, null, ipPerm.getIcmpCode(), ipPerm.getIcmpType(),
                                    ipPerm.getProtocol(), null, request.getName(), null, secGroupList);
                } else {
                    resp =
                            getApi().authorizeSecurityGroupIngress(null, constructList(ipPerm.getIpRangeSet()), null, ipPerm.getToPort().longValue(), null, null,
                                    ipPerm.getProtocol(), null, request.getName(), ipPerm.getFromPort().longValue(), secGroupList);
                }
                if (resp != null) {
                    List<CloudStackIngressRule> ingressRules = resp.getIngressRules();
                    for (CloudStackIngressRule ingressRule : ingressRules)
                        if (ingressRule.getRuleId() == null)
                            return false;
                } else {
                    return false;
                }
            }
        } catch (Exception e) {
            logger.error("EC2 AuthorizeSecurityGroupIngress - ", e);
            handleException(e);
        }
        return true;
    }

    /**
     * Does the permission from the request (left) match the permission from the cloudStack query (right).
     * If the cloudStack rule matches then we return its ruleId.
     *
     * @param permLeft
     * @param permRight
     * @return ruleId of the cloudstack rule
     */
    private String doesRuleMatch(EC2IpPermission permLeft, EC2IpPermission permRight) {
        int matches = 0;

        if (null != permLeft.getIcmpType() && null != permLeft.getIcmpCode()) {
            if (null == permRight.getIcmpType() || null == permRight.getIcmpCode())
                return null;

            if (!permLeft.getIcmpType().equalsIgnoreCase(permRight.getIcmpType()))
                return null;
            if (!permLeft.getIcmpCode().equalsIgnoreCase(permRight.getIcmpCode()))
                return null;
            matches++;
        }

        // -> "Valid Values for EC2 security groups: tcp | udp | icmp or the corresponding protocol number (6 | 17 | 1)."
        if (null != permLeft.getProtocol()) {
            if (null == permRight.getProtocol())
                return null;

            String protocol = permLeft.getProtocol();
            if (protocol.equals("6"))
                protocol = "tcp";
            else if (protocol.equals("17"))
                protocol = "udp";
            else if (protocol.equals("1"))
                protocol = "icmp";

            if (!protocol.equalsIgnoreCase(permRight.getProtocol()))
                return null;
            matches++;
        }

        if (null != permLeft.getCIDR()) {
            if (null == permRight.getCIDR())
                return null;

            if (!permLeft.getCIDR().equalsIgnoreCase(permRight.getCIDR()))
                return null;
            matches++;
        }

        // -> is the port(s) from the request (left) a match of the rule's port(s)
        if (0 != permLeft.getFromPort()) {
            // -> -1 means all ports match
            if (-1 != permLeft.getFromPort()) {
                if (permLeft.getFromPort().compareTo(permRight.getFromPort()) != 0 || permLeft.getToPort().compareTo(permRight.getToPort()) != 0)
                    return null;
            }
            matches++;
        }

        // -> was permLeft set up properly with at least one property to match?
        if (0 == matches)
            return null;
        else
            return permRight.getRuleId();
    }

    /**
     * Returns a list of all snapshots
     *
     * @param request
     * @return
     */
    public EC2DescribeSnapshotsResponse describeSnapshots(EC2DescribeSnapshots request) {
        EC2DescribeSnapshotsResponse response = new EC2DescribeSnapshotsResponse();
        EC2SnapshotFilterSet sfs = request.getFilterSet();
        EC2TagKeyValue[] tagKeyValueSet = request.getResourceTagSet();

        try {
            response = listSnapshots(request.getSnapshotSet(), getResourceTags(tagKeyValueSet));
            if (response == null) {
                return new EC2DescribeSnapshotsResponse();
            }
            EC2Snapshot[] snapshots = response.getSnapshotSet();
            // -> query to get the volume size for each snapshot
            HashMap<String, Long> volumeIdSize = new HashMap<String, Long>();
            for (EC2Snapshot snap : snapshots) {
                Boolean duplicateVolume = false;
                Long size = null;
                if (volumeIdSize.containsKey(snap.getVolumeId())) {
                    size = volumeIdSize.get(snap.getVolumeId());
                    duplicateVolume = true;
                    break;
                }
                if (!duplicateVolume) {
                    EC2DescribeVolumesResponse volumes = new EC2DescribeVolumesResponse();
                    volumes = listVolumes(snap.getVolumeId(), null, volumes, null);
                    EC2Volume[] volumeSet = volumes.getVolumeSet();
                    if (volumeSet.length > 0)
                        size = volumeSet[0].getSize();
                    volumeIdSize.put(snap.getVolumeId(), size);
                }
                snap.setVolumeSize(size);
            }
            if (sfs != null) {
                response = sfs.evaluate(response);
            }
        } catch (Exception e) {
            logger.error("EC2 DescribeSnapshots - ", e);
            handleException(e);
        }
        return response;
    }

    /**
     * Creates a snapshot
     *
     * @param volumeId
     * @return
     */
    public EC2Snapshot createSnapshot(String volumeId) {
        EC2Snapshot ec2Snapshot = new EC2Snapshot();
        try {
            CloudStackSnapshot snap = getApi().createSnapshot(volumeId, null, null, null);
            if (snap == null) {
                throw new Exception("Unable to create snapshot");
            }
            ec2Snapshot.setId(snap.getId());
            ec2Snapshot.setName(snap.getName());
            ec2Snapshot.setType(snap.getSnapshotType());
            ec2Snapshot.setAccountName(snap.getAccountName());
            ec2Snapshot.setDomainId(snap.getDomainId());
            ec2Snapshot.setCreated(snap.getCreated());
            ec2Snapshot.setVolumeId(snap.getVolumeId());

            List<CloudStackVolume> vols = getApi().listVolumes(null, null, null, snap.getVolumeId(), null, null, null, null, null, null, null, null);

            if (vols.size() > 0) {
                assert (vols.get(0).getSize() != null);
                Long sizeInGB = vols.get(0).getSize().longValue() / 1073741824;
                ec2Snapshot.setVolumeSize(sizeInGB);
            }
        } catch (Exception e) {
            logger.error("EC2 CreateSnapshot - ", e);
            handleException(e);
        }
        return ec2Snapshot;
    }

    /**
     * Deletes a snapshot
     *
     * @param snapshotId
     * @return
     */
    public boolean deleteSnapshot(String snapshotId) {
        try {

            CloudStackInfoResponse resp = getApi().deleteSnapshot(snapshotId);
            if (resp != null) {
                return resp.getSuccess();
            }

            return false;
        } catch (Exception e) {
            logger.error("EC2 DeleteSnapshot - ", e);
            handleException(e);
        }
        return false;
    }

    /**
     * Modify an existing template
     *
     * @param request
     * @return
     */
    public boolean modifyImageAttribute(EC2ModifyImageAttribute request) {
        try {
            if (request.getAttribute().equals(ImageAttribute.launchPermission)) {
                EC2ImageLaunchPermission[] launchPermissions = request.getLaunchPermissionSet();
                for (EC2ImageLaunchPermission launchPermission : launchPermissions) {
                    String accounts = "";
                    Boolean isPublic = null;
                    EC2ImageLaunchPermission.Operation operation = launchPermission.getLaunchPermOp();
                    List<String> accountOrGroupList = launchPermission.getLaunchPermissionList();
                    if (accountOrGroupList != null && !accountOrGroupList.isEmpty()) {
                        boolean first = true;
                        for (String accountOrGroup : accountOrGroupList) {
                            if ("all".equalsIgnoreCase(accountOrGroup)) {
                                if (operation.equals(EC2ImageLaunchPermission.Operation.add)) {
                                    isPublic = true;
                                } else {
                                    isPublic = false;
                                }
                            } else {
                                if (!first) {
                                    accounts = accounts + ",";
                                }
                                accounts = accounts + accountOrGroup;
                                first = false;
                            }
                        }
                    }
                    CloudStackInfoResponse resp = getApi().updateTemplatePermissions(request.getImageId(), accounts, null, null, isPublic, operation.toString());
                    if (!resp.getSuccess())
                        return false;
                }
                return true;
            } else if (request.getAttribute().equals(ImageAttribute.description)) {
                CloudStackTemplate resp = getApi().updateTemplate(request.getImageId(), null, request.getDescription(), null, null, null, null);
                if (resp != null) {
                    return true;
                }
                return false;
            }
        } catch (Exception e) {
            logger.error("EC2 modifyImageAttribute - ", e);
            handleException(e);
        }
        return false;
    }

    public EC2ImageAttributes describeImageAttribute(EC2DescribeImageAttribute request) {
        EC2ImageAttributes imageAtts = new EC2ImageAttributes();

        try {
            imageAtts.setImageId(request.getImageId());
            if (request.getAttribute().equals(ImageAttribute.launchPermission)) {
                CloudStackTemplatePermission tempPerm = getApi().listTemplatePermissions(request.getImageId(), null, null);
                if (tempPerm != null) {
                    imageAtts.setDomainId(tempPerm.getDomainId());

                    List<String> accntList = tempPerm.getAccounts();
                    imageAtts.setAccountNamesWithLaunchPermission(accntList);

                    imageAtts.setIsPublic(tempPerm.getIsPublic());
                }
            } else if (request.getAttribute().equals(ImageAttribute.description)) {
                EC2DescribeImagesResponse descriptionResp = new EC2DescribeImagesResponse();
                listTemplates(request.getImageId(), descriptionResp);
                if (descriptionResp.getImageSet() != null) {
                    EC2Image[] images = descriptionResp.getImageSet();
                    imageAtts.setDescription(images[0].getDescription());
                }
            }

        } catch (Exception e) {
            logger.error("EC2 describeImageAttribute - ", e);
            handleException(e);
        }

        return imageAtts;
    }

    // handlers
    /**
     * return password data from the instance
     *
     * @param instanceId
     * @return
     */
    public EC2PasswordData getPasswordData(String instanceId) {
        EC2PasswordData passwdData = new EC2PasswordData();
        try {
            CloudStackPasswordData resp = getApi().getVMPassword(instanceId);
            if (resp != null) {
                passwdData.setInstanceId(instanceId);
                passwdData.setEncryptedPassword(resp.getEncryptedpassword());
            }
        } catch (Exception e) {
            logger.error("EC2 GetPasswordData - ", e);
            handleException(e);
        }
        return passwdData;
    }

    /**
     * Lists SSH KeyPairs on the system
     *
     * @param request
     * @return
     */
    public EC2DescribeKeyPairsResponse describeKeyPairs(EC2DescribeKeyPairs request) {
        EC2DescribeKeyPairsResponse response = new EC2DescribeKeyPairsResponse();
        try {
            response = listKeyPairs(request.getKeyNames());
            EC2KeyPairFilterSet kfs = request.getKeyFilterSet();
            if (kfs != null)
                response = kfs.evaluate(response);
        } catch (Exception e) {
            logger.error("EC2 DescribeKeyPairs - ", e);
            handleException(e);
        }
        return response;
    }

    /**
     * Delete SSHKeyPair
     *
     * @param request
     * @return
     */
    public boolean deleteKeyPair(EC2DeleteKeyPair request) {
        CloudStackInfoResponse resp = new CloudStackInfoResponse();
        String keyPairName = request.getKeyName();
        try {
            resp = getApi().deleteSSHKeyPair(keyPairName, null, null);
            if (resp == null) {
                throw new Exception("Ivalid CloudStack API response");
            }
        } catch (Exception e) {
            logger.error("EC2 DeleteKeyPair - ", e);
            handleException(e);
        }
        return resp.getSuccess();
    }

    /**
     * Create SSHKeyPair
     *
     * @param request
     * @return
     */
    public EC2SSHKeyPair createKeyPair(EC2CreateKeyPair request) {
        String keyPairName = request.getKeyName();
        EC2SSHKeyPair response = new EC2SSHKeyPair();
        try {
            CloudStackKeyPair resp = getApi().createSSHKeyPair(keyPairName, null, null);
            if (resp == null) {
                throw new Exception("Ivalid CloudStack API response");
            }
            response.setFingerprint(resp.getFingerprint());
            response.setKeyName(resp.getName());
            response.setPrivateKey(resp.getPrivatekey());
        } catch (Exception e) {
            logger.error("EC2 CreateKeyPair - ", e);
            handleException(e);
        }
        return response;
    }

    /**
     * Import an existing SSH KeyPair
     *
     * @param request
     * @return
     */
    public EC2SSHKeyPair importKeyPair(EC2ImportKeyPair request) {
        EC2SSHKeyPair response = new EC2SSHKeyPair();
        try {
            CloudStackKeyPair resp = getApi().registerSSHKeyPair(request.getKeyName(), request.getPublicKeyMaterial());
            if (resp == null) {
                throw new Exception("Ivalid CloudStack API response");
            }
            response.setFingerprint(resp.getFingerprint());
            response.setKeyName(resp.getName());
            response.setPrivateKey(resp.getPrivatekey());
        } catch (Exception e) {
            logger.error("EC2 ImportKeyPair - ", e);
            handleException(e);
        }
        return response;
    }

    /**
     * list ip addresses that have been allocated
     *
     * @param request
     * @return
     */
    public EC2DescribeAddressesResponse describeAddresses(EC2DescribeAddresses request) {
        EC2DescribeAddressesResponse response = new EC2DescribeAddressesResponse();
        try {
            response = listAddresses(request.getPublicIpsSet());
            EC2AddressFilterSet afs = request.getFilterSet();
            if (afs != null)
                response = afs.evaluate(response);
        } catch (Exception e) {
            logger.error("EC2 DescribeAddresses - ", e);
            handleException(e);
        }
        return response;
    }

    /**
     * release an IP Address
     *
     * @param request
     * @return
     */
    public boolean releaseAddress(EC2ReleaseAddress request) {
        try {
            List<CloudStackIpAddress> cloudIps = getApi().listPublicIpAddresses(null, null, null, null, null, request.getPublicIp(), null, null, null);
            if (cloudIps == null)
                throw new Exception("Specified ipAddress doesn't exist");
            CloudStackIpAddress cloudIp = cloudIps.get(0);
            CloudStackInfoResponse resp = getApi().disassociateIpAddress(cloudIp.getId());
            if (resp != null) {
                return resp.getSuccess();
            }
        } catch (Exception e) {
            logger.error("EC2 ReleaseAddress - ", e);
            handleException(e);
        }
        return false;
    }

    /**
     * Associate an address with an instance
     *
     * @param request
     * @return
     */
    public boolean associateAddress(EC2AssociateAddress request) {
        try {
            List<CloudStackIpAddress> cloudIps = getApi().listPublicIpAddresses(null, null, null, null, null, request.getPublicIp(), null, null, null);
            if (cloudIps == null)
                throw new Exception("Specified ipAddress doesn't exist");
            CloudStackIpAddress cloudIp = cloudIps.get(0);

            List<CloudStackUserVm> vmList =
                    getApi().listVirtualMachines(null, null, true, null, null, null, null, request.getInstanceId(), null, null, null, null, null, null, null, null, null);
            if (vmList == null || vmList.size() == 0) {
                throw new Exception("Instance not found");
            }
            CloudStackUserVm cloudVm = vmList.get(0);

            CloudStackInfoResponse resp = getApi().enableStaticNat(cloudIp.getId(), cloudVm.getId());
            if (resp != null) {
                return resp.getSuccess();
            }
        } catch (Exception e) {
            logger.error("EC2 AssociateAddress - ", e);
            handleException(e);
        }
        return false;
    }

    /**
     * Disassociate an address from an instance
     *
     * @param request
     * @return
     */
    public boolean disassociateAddress(EC2DisassociateAddress request) {
        try {
            List<CloudStackIpAddress> cloudIps = getApi().listPublicIpAddresses(null, null, null, null, null, request.getPublicIp(), null, null, null);
            if (cloudIps == null)
                throw new Exception("Specified ipAddress doesn't exist");
            CloudStackIpAddress cloudIp = cloudIps.get(0);

            CloudStackInfoResponse resp = getApi().disableStaticNat(cloudIp.getId());
            if (resp != null) {
                return resp.getSuccess();
            }
        } catch (Exception e) {
            logger.error("EC2 DisassociateAddress - ", e);
            handleException(e);
        }
        return false;
    }

    /**
     * Allocate an address
     *
     * @param request
     * @return
     */
    public EC2Address allocateAddress() {
        EC2Address ec2Address = new EC2Address();
        try {
            // this gets our networkId
            CloudStackAccount caller = getCurrentAccount();

            CloudStackZone zone = findZone();
            //CloudStackNetwork net = findNetwork(zone);
            //CloudStackIpAddress resp = getApi().associateIpAddress(null, null, null, "0036952d-48df-4422-9fd0-94b0885e18cb");
            CloudStackIpAddress resp = getApi().associateIpAddress(zone.getId(), caller.getName(), caller.getDomainId(), null);
            ec2Address.setAssociatedInstanceId(resp.getId());

            if (resp.getIpAddress() == null) {
                List<CloudStackIpAddress> addrList = getApi().listPublicIpAddresses(null, null, null, null, null, null, null, null, null);
                if (addrList != null && addrList.size() > 0) {
                    for (CloudStackIpAddress addr : addrList) {
                        if (addr.getId().equalsIgnoreCase(resp.getId())) {
                            ec2Address.setIpAddress(addr.getIpAddress());
                        }
                    }
                }
            } else {
                ec2Address.setIpAddress(resp.getIpAddress());
            }
        } catch (Exception e) {
            logger.error("EC2 AllocateAddress - ", e);
            handleException(e);
        }
        return ec2Address;
    }

    /**
     * List of templates available.  We only support the imageSet version of this call or when no search parameters are passed
     * which results in asking for all templates.
     *
     * @param request
     * @return
     */
    public EC2DescribeImagesResponse describeImages(EC2DescribeImages request) {
        EC2DescribeImagesResponse images = new EC2DescribeImagesResponse();
        try {
            String[] templateIds = request.getImageSet();
            EC2ImageFilterSet ifs = request.getFilterSet();

            if (templateIds.length == 0) {
                images = listTemplates(null, images);
            } else {
                for (String s : templateIds) {
                    images = listTemplates(s, images);
                }
            }
            if (ifs != null)
                return ifs.evaluate(images);
        } catch (Exception e) {
            logger.error("EC2 DescribeImages - ", e);
            handleException(e);
        }
        return images;
    }

    /**
     * Create a template
     * Amazon API just gives us the instanceId to create the template from.
     * But our createTemplate function requires the volumeId and osTypeId.
     * So to get that we must make the following sequence of cloud API calls:
     * 1) listVolumes&virtualMachineId=   -- gets the volumeId
     * 2) listVirtualMachinees&id=        -- gets the templateId
     * 3) listTemplates&id=               -- gets the osTypeId
     *
     * If we have to start and stop the VM in question then this function is
     * going to take a long time to complete.
     *
     * @param request
     * @return
     */
    public EC2CreateImageResponse createImage(EC2CreateImage request) {
        EC2CreateImageResponse response = new EC2CreateImageResponse();
        boolean needsRestart = false;
        String volumeId = null;
        try {
            // [A] Creating a template from a VM volume should be from the ROOT volume
            //     Also for this to work the VM must be in a Stopped state so we 'reboot' it if its not
            EC2DescribeVolumesResponse volumes = new EC2DescribeVolumesResponse();
            volumes = listVolumes(null, request.getInstanceId(), volumes, null);
            EC2Volume[] volSet = volumes.getVolumeSet();
            for (EC2Volume vol : volSet) {
                if (vol.getType().equalsIgnoreCase("ROOT")) {
                    String vmState = vol.getVMState();
                    if (vmState.equalsIgnoreCase("running") || vmState.equalsIgnoreCase("starting")) {
                        needsRestart = true;
                        if (!stopVirtualMachine(request.getInstanceId()))
                            throw new Exception("Instance must be in a stopped state");
                    }
                    volumeId = vol.getId();
                    break;
                }
            }

            // [B] The parameters must be in sorted order for proper signature generation
            EC2DescribeInstancesResponse instances = new EC2DescribeInstancesResponse();
            instances = lookupInstances(request.getInstanceId(), instances, null);
            EC2Instance[] instanceSet = instances.getInstanceSet();
            String templateId = instanceSet[0].getTemplateId();

            EC2DescribeImagesResponse images = new EC2DescribeImagesResponse();
            images = listTemplates(templateId, images);
            EC2Image[] imageSet = images.getImageSet();
            String osTypeId = imageSet[0].getOsTypeId();

            CloudStackTemplate resp =
                    getApi().createTemplate((request.getDescription() == null ? "" : request.getDescription()), request.getName(), osTypeId, null, null, null, null, null,
                            null, volumeId);
            if (resp == null || resp.getId() == null) {
                throw new Exception("Image couldn't be created");
            }

            //if template was created succesfully, create the new image response
            response.setId(resp.getId());

            // [C] If we stopped the virtual machine now we need to restart it
            if (needsRestart) {
                if (!startVirtualMachine(request.getInstanceId()))
                    throw new Exception("Failed to start the stopped instance");
            }
        } catch (Exception e) {
            logger.error("EC2 CreateImage - ", e);
            handleException(e);
        }
        return response;
    }

    /**
     * Register a template
     *
     * @param request
     * @return
     */
    public EC2CreateImageResponse registerImage(EC2RegisterImage request) {
        EC2CreateImageResponse image = new EC2CreateImageResponse();
        try {
            List<CloudStackTemplate> templates =
                    getApi().registerTemplate((request.getDescription() == null ? request.getName() : request.getDescription()), request.getFormat(),
                            request.getHypervisor(), request.getName(), toOSTypeId(request.getOsTypeName()), request.getLocation(), toZoneId(request.getZoneName(), null), null,
                            null, null, null, null, null, null, null, null);
            if (templates != null) {
                // technically we will only ever register a single template...
                for (CloudStackTemplate template : templates) {
                    if (template != null && template.getId() != null) {
                        image.setId(template.getId().toString());
                        return image;
                    }
                }
            }
        } catch (Exception e) {
            logger.error("EC2 RegisterImage - ", e);
            handleException(e);
        }
        return image;
    }

    /**
     * Deregister a template(image)
     * Our implementation is different from Amazon in that we do delete the template
     * when we deregister it.   The cloud API has not deregister call.
     *
     * @param image
     * @return
     */
    public boolean deregisterImage(EC2Image image) {
        try {
            CloudStackInfoResponse resp = getApi().deleteTemplate(image.getId(), null);
            return resp.getSuccess();
        } catch (Exception e) {
            logger.error("EC2 DeregisterImage - ", e);
            handleException(e);
        }
        return false;
    }

    /**
     * list instances
     *
     * @param request
     * @return
     */
    public EC2DescribeInstancesResponse describeInstances(EC2DescribeInstances request) {
        EC2DescribeInstancesResponse response = new EC2DescribeInstancesResponse();
        try {
            EC2TagKeyValue[] tagKeyValueSet = request.getResourceTagSet();
            response = listVirtualMachines(request.getInstancesSet(), request.getFilterSet(), getResourceTags(tagKeyValueSet));
        } catch (Exception e) {
            logger.error("EC2 DescribeInstances - ", e);
            handleException(e);
        }
        return response;
    }

    /**
     * list Zones
     *
     * @param request
     * @return
     */
    public EC2DescribeAvailabilityZonesResponse describeAvailabilityZones(EC2DescribeAvailabilityZones request) {
        EC2DescribeAvailabilityZonesResponse availableZones = new EC2DescribeAvailabilityZonesResponse();
        try {
            availableZones = listZones(request.getZoneSet(), null);
            EC2AvailabilityZonesFilterSet azfs = request.getFilterSet();
            if (azfs != null)
                availableZones = azfs.evaluate(availableZones);
        } catch (Exception e) {
            logger.error("EC2 DescribeAvailabilityZones - ", e);
            handleException(e);
        }
        return availableZones;
    }

    /**
     * list volumes
     *
     * @param request
     * @return
     */
    public EC2DescribeVolumesResponse describeVolumes(EC2DescribeVolumes request) {
        EC2DescribeVolumesResponse volumes = new EC2DescribeVolumesResponse();
        EC2VolumeFilterSet vfs = request.getFilterSet();
        EC2TagKeyValue[] tagKeyValueSet = request.getResourceTagSet();
        try {
            String[] volumeIds = request.getVolumeSet();
            if (0 == volumeIds.length) {
                volumes = listVolumes(null, null, volumes, getResourceTags(tagKeyValueSet));
            } else {
                for (String s : volumeIds)
                    volumes = listVolumes(s, null, volumes, getResourceTags(tagKeyValueSet));
            }
            if (vfs != null)
                volumes = vfs.evaluate(volumes);
        } catch (Exception e) {
            logger.error("EC2 DescribeVolumes - ", e);
            handleException(e);
        }
        return volumes;
    }

    /**
     * Attach a volume to an instance
     *
     * @param request
     * @return
     */
    public EC2Volume attachVolume(EC2Volume request) {
        EC2Volume resp = new EC2Volume();
        try {
            request.setDeviceId(mapDeviceToCloudDeviceId(request.getDevice()));
            CloudStackVolume vol = getApi().attachVolume(request.getId(), request.getInstanceId(), request.getDeviceId());
            if (vol != null) {
                resp.setAttached(vol.getAttached());
                resp.setCreated(vol.getCreated());
                resp.setDevice(request.getDevice());
                resp.setDeviceId(vol.getDeviceId());
                resp.setHypervisor(vol.getHypervisor());
                resp.setId(vol.getId());
                resp.setInstanceId(vol.getVirtualMachineId());
                resp.setSize(vol.getSize());
                resp.setSnapshotId(vol.getSnapshotId());
                resp.setState(vol.getState());
                resp.setType(vol.getVolumeType());
                resp.setVMState(vol.getVirtualMachineState());
                resp.setAttachmentState(mapToAmazonVolumeAttachmentState(vol.getVirtualMachineState()));
                resp.setZoneName(vol.getZoneName());
            }
        } catch (Exception e) {
            logger.error("EC2 AttachVolume - ", e);
            handleException(e);
        }
        return resp;
    }

    /**
     * Detach a volume from an instance
     *
     * @param request
     * @return
     */
    public EC2Volume detachVolume(EC2Volume request) {
        EC2Volume resp = new EC2Volume();
        try {
            // verifying if instanceId and deviceId provided is valid
            EC2DescribeVolumesResponse volumes = new EC2DescribeVolumesResponse();
            volumes = listVolumes(request.getId(), null, volumes, null);
            if (volumes != null) {
                EC2Volume[] volumeSet = volumes.getVolumeSet();
                if (request.getInstanceId() != null) {
                    if (!request.getInstanceId().equalsIgnoreCase(volumeSet[0].getInstanceId()))
                        throw new Exception("Volume is not attached to the Instance");
                }
                if (request.getDevice() != null) {
                    String devicePath = null;
                    if (volumeSet[0].getDeviceId() != null)
                        devicePath = cloudDeviceIdToDevicePath(volumeSet[0].getHypervisor(), volumeSet[0].getDeviceId());
                    if (!request.getDevice().equalsIgnoreCase(devicePath))
                        throw new Exception("Volume is not attached to the Device");
                }
            }

            CloudStackVolume vol = getApi().detachVolume(null, request.getId(), null);
            if (vol != null) {
                resp.setAttached(vol.getAttached());
                resp.setCreated(vol.getCreated());
                resp.setDevice(request.getDevice());
                resp.setDeviceId(vol.getDeviceId());
                resp.setHypervisor(vol.getHypervisor());
                resp.setId(vol.getId());
                resp.setInstanceId(vol.getVirtualMachineId());
                resp.setSize(vol.getSize());
                resp.setSnapshotId(vol.getSnapshotId());
                resp.setState(vol.getState());
                resp.setType(vol.getVolumeType());
                resp.setVMState(vol.getVirtualMachineState());
                resp.setAttachmentState("detached");
                resp.setZoneName(vol.getZoneName());
            }
        } catch (Exception e) {
            logger.error("EC2 DetachVolume - ", e);
            handleException(e);
        }
        return resp;
    }

    /**
     * Create a volume
     *
     * @param request
     * @return
     */
    public EC2Volume createVolume(EC2CreateVolume request) {
        EC2Volume resp = new EC2Volume();
        try {
            // -> put either snapshotid or diskofferingid on the request
            String snapshotId = request.getSnapshotId();
            Long size = request.getSize();
            String diskOfferingId = null;
            String taggedDiskOffering = null;

            if (snapshotId == null) {
                List<CloudStackDiskOffering> disks = getApi().listDiskOfferings(null, null, null, null);
                for (CloudStackDiskOffering offer : disks) {
                    if (offer.isCustomized()) {
                        if (offer.getTags() == null) {
                            diskOfferingId = offer.getId();
                            break;
                        } else {
                            taggedDiskOffering = offer.getId();
                        }
                    }
                }
                if (diskOfferingId == null) {
                    diskOfferingId = taggedDiskOffering;
                }
                if (diskOfferingId == null)
                    throw new EC2ServiceException(ServerError.InternalError, "No Customize Disk Offering Found");
            }

            // -> no volume name is given in the Amazon request but is required in the cloud API
            CloudStackVolume vol =
                    getApi().createVolume(UUID.randomUUID().toString(), null, diskOfferingId, null, size, snapshotId, toZoneId(request.getZoneName(), null));
            if (vol != null) {
                resp.setAttached(vol.getAttached());
                resp.setCreated(vol.getCreated());
                //resp.setDevice();
                resp.setDeviceId(vol.getDeviceId());
                resp.setHypervisor(vol.getHypervisor());
                resp.setId(vol.getId());
                resp.setInstanceId(vol.getVirtualMachineId());
                resp.setSize(vol.getSize());
                resp.setSnapshotId(vol.getSnapshotId());
                if (vol.getState() != null) {
                    resp.setState(mapToAmazonVolState(vol.getState()));
                }
                resp.setType(vol.getVolumeType());
                resp.setVMState(vol.getVirtualMachineState());
                resp.setAttachmentState("detached");
                resp.setZoneName(vol.getZoneName());
            }
        } catch (Exception e) {
            logger.error("EC2 CreateVolume - ", e);
            handleException(e);
        }
        return resp;
    }

    /**
     * Delete a volume
     *
     * @param request
     * @return
     */
    public EC2Volume deleteVolume(EC2Volume request) {
        try {
            CloudStackInfoResponse resp = getApi().deleteVolume(request.getId());
            if (resp != null) {
                request.setState("deleted");
            }
        } catch (Exception e) {
            logger.error("EC2 DeleteVolume - ", e);
            handleException(e);
        }
        return request;
    }

    /**
     * Create/Delete tags
     *
     * @param request
     * @param operation
     * @return
     */
    public boolean modifyTags(EC2Tags request, String operation) {
        try {
            List<CloudStackKeyValue> resourceTagList = new ArrayList<CloudStackKeyValue>();
            for (EC2TagKeyValue resourceTag : request.getResourceTags()) {
                CloudStackKeyValue pair = new CloudStackKeyValue();
                pair.setKeyValue(resourceTag.getKey(), resourceTag.getValue());
                resourceTagList.add(pair);
            }
            EC2TagTypeId[] resourceTypeSet = request.getResourceTypeSet();
            for (EC2TagTypeId resourceType : resourceTypeSet) {
                String cloudStackResourceType = mapToCloudStackResourceType(resourceType.getResourceType());
                List<String> resourceIdList = new ArrayList<String>();
                for (String resourceId : resourceType.getResourceIds())
                    resourceIdList.add(resourceId);
                CloudStackInfoResponse resp = new CloudStackInfoResponse();
                if (operation.equalsIgnoreCase("create"))
                    resp = getApi().createTags(cloudStackResourceType, resourceIdList, resourceTagList);
                else if (operation.equalsIgnoreCase("delete"))
                    resp = getApi().deleteTags(cloudStackResourceType, resourceIdList, resourceTagList);
                else
                    throw new EC2ServiceException(ServerError.InternalError, "Unknown operation.");
                if (resp.getSuccess() == false)
                    return false;
            }
            return true;
        } catch (Exception e) {
            logger.error("EC2 Create/Delete Tags - ", e);
            handleException(e);
        }
        return false;
    }

    /**
     * Describe tags
     *
     * @param request
     * @return
     */
    public EC2DescribeTagsResponse describeTags(EC2DescribeTags request) {
        EC2DescribeTagsResponse tagResponse = new EC2DescribeTagsResponse();
        try {
            tagResponse = new EC2DescribeTagsResponse();
            List<CloudStackResourceTag> resourceTagList = getApi().listTags(null, null, null, true, null);

            if (resourceTagList != null && resourceTagList.size() > 0) {
                for (CloudStackResourceTag resourceTag : resourceTagList) {
                    EC2ResourceTag tag = new EC2ResourceTag();
                    tag.setResourceId(resourceTag.getResourceId());
                    tag.setResourceType(mapToAmazonResourceType(resourceTag.getResourceType()));
                    tag.setKey(resourceTag.getKey());
                    if (resourceTag.getValue() != null)
                        tag.setValue(resourceTag.getValue());
                    tagResponse.addTags(tag);
                }
            }

            EC2TagsFilterSet tfs = request.getFilterSet();
            if (tfs != null)
                tagResponse = tfs.evaluate(tagResponse);
        } catch (Exception e) {
            logger.error("EC2 DescribeTags - ", e);
            handleException(e);
        }
        return tagResponse;
    }

    /**
     * Reboot an instance or instances
     *
     * @param request
     * @return
     */
    public boolean rebootInstances(EC2RebootInstances request) {
        EC2Instance[] vms = null;

        // -> reboot is not allowed on destroyed (i.e., terminated) instances
        try {
            String[] instanceSet = request.getInstancesSet();
            EC2DescribeInstancesResponse previousState = listVirtualMachines(instanceSet, null, null);
            vms = previousState.getInstanceSet();

            // -> send reboot requests for each found VM
            for (EC2Instance vm : vms) {
                if (vm.getState().equalsIgnoreCase("Destroyed"))
                    continue;

                CloudStackUserVm resp = getApi().rebootVirtualMachine(vm.getId());
                if (logger.isDebugEnabled())
                    logger.debug("Rebooting VM " + resp.getId() + " job " + resp.getJobId());
            }

            // -> if some specified VMs where not found we have to tell the caller
            if (instanceSet.length != vms.length)
                throw new Exception("One or more instanceIds do not exist, other instances rebooted.");

            return true;
        } catch (Exception e) {
            logger.error("EC2 RebootInstances - ", e);
            handleException(e);
        }
        return false;
    }

    /**
     * Using a template (AMI), launch n instances
     *
     * @param request
     * @return
     */
    public EC2RunInstancesResponse runInstances(EC2RunInstances request) {
        EC2RunInstancesResponse instances = new EC2RunInstancesResponse();
        int createInstances = 0;
        int canCreateInstances = -1;
        int countCreated = 0;
        String groupIds = null;
        String groupNames = null;

        try {
            // ugly...
            canCreateInstances = calculateAllowedInstances();
            if (-1 == canCreateInstances)
                canCreateInstances = request.getMaxCount();

            if (canCreateInstances < request.getMinCount()) {
                logger.info("EC2 RunInstances - min count too big (" + request.getMinCount() + "), " + canCreateInstances + " left to allocate");
                throw new Exception("Min Count is greater than the number of instances left to allocate");
            }

            if (canCreateInstances < request.getMaxCount())
                createInstances = request.getMinCount();
            else
                createInstances = request.getMaxCount();

            //find CS service Offering ID
            String instanceType = "m1.small";
            if (request.getInstanceType() != null) {
                instanceType = request.getInstanceType();
            }
            CloudStackServiceOffering svcOffering = getCSServiceOfferingId(instanceType);
            if (svcOffering == null) {
                logger.info("No ServiceOffering found to be defined by name, please contact the administrator " + instanceType);
                throw new Exception("instanceType not found");
            }

            // zone stuff
            String zoneId = toZoneId(request.getZoneName(), null);

            List<CloudStackZone> zones = getApi().listZones(null, null, zoneId, null);
            if (zones == null || zones.size() == 0) {
                logger.info("EC2 RunInstances - zone [" + request.getZoneName() + "] not found!");
                throw new Exception("zone not found");
            }
            zones.get(0);

            // network
            //CloudStackNetwork network = findNetwork(zone);

            // for EC2 security groups either a group ID or a group name is accepted
            String[] sgIdList = request.getSecurityGroupIdSet();
            String[] sgNameList = request.getSecurityGroupNameSet();
            if (sgIdList.length != 0 && sgNameList.length != 0)
                throw new EC2ServiceException(ClientError.InvalidParameterCombination, " for EC2 groups either a group ID or a group name is accepted");

            if (sgIdList.length != 0)
                groupIds = constructList(sgIdList);
            if (sgNameList.length != 0)
                groupNames = constructList(sgNameList);

            // now actually deploy the vms
            for (int i = 0; i < createInstances; i++) {
                try {
                    CloudStackUserVm resp =
                            getApi().deployVirtualMachine(svcOffering.getId(), request.getTemplateId(), zoneId, null, null, null, null, null, null, null,
                                    request.getKeyName(), null, null, groupIds, groupNames, request.getSize().longValue(), request.getUserData());
                    EC2Instance vm = new EC2Instance();
                    vm.setId(resp.getId().toString());
                    vm.setName(resp.getName());
                    vm.setZoneName(resp.getZoneName());
                    vm.setTemplateId(resp.getTemplateId().toString());
                    if (resp.getSecurityGroupList() != null && resp.getSecurityGroupList().size() > 0) {
                        List<CloudStackSecurityGroup> securityGroupList = resp.getSecurityGroupList();
                        for (CloudStackSecurityGroup securityGroup : securityGroupList) {
                            EC2SecurityGroup param = new EC2SecurityGroup();
                            param.setId(securityGroup.getId());
                            param.setName(securityGroup.getName());
                            vm.addGroupName(param);
                        }
                    }
                    vm.setState(resp.getState());
                    vm.setCreated(resp.getCreated());
                    List<CloudStackNic> nicList = resp.getNics();
                    for (CloudStackNic nic : nicList) {
                        if (nic.getIsDefault()) {
                            vm.setPrivateIpAddress(nic.getIpaddress());
                            break;
                        }
                    }
                    vm.setIpAddress(resp.getIpAddress());
                    vm.setAccountName(resp.getAccountName());
                    vm.setDomainId(resp.getDomainId());
                    vm.setHypervisor(mapToAmazonHypervisorType(resp.getHypervisor()));
                    vm.setServiceOffering(svcOffering.getName());
                    vm.setKeyPairName(resp.getKeyPairName());
                    instances.addInstance(vm);
                    countCreated++;
                } catch (Exception e) {
                    logger.error("Failed to deploy VM number: " + (i + 1) + " due to error: " + e.getMessage());
                    break;
                }
            }
            if (0 == countCreated) {
                // TODO, we actually need to destroy left-over VMs when the exception is thrown
                throw new Exception("Insufficient Instance Capacity");
            }
            logger.debug("Could deploy " + countCreated + " VM's successfully");
        } catch (Exception e) {
            logger.error("EC2 RunInstances - ", e);
            handleException(e);
        }
        return instances;
    }

    /**
     * Start an instance or instances
     *
     * @param request
     * @return
     */
    public EC2StartInstancesResponse startInstances(EC2StartInstances request) {
        EC2StartInstancesResponse instances = new EC2StartInstancesResponse();
        EC2Instance[] vms = null;

        // -> first determine the current state of each VM (becomes it previous state)
        try {
            EC2DescribeInstancesResponse previousState = listVirtualMachines(request.getInstancesSet(), null, null);
            vms = previousState.getInstanceSet();

            // -> send start requests for each item
            for (EC2Instance vm : vms) {
                vm.setPreviousState(vm.getState());

                // -> if its already running then we don't care
                if (vm.getState().equalsIgnoreCase("Running") || vm.getState().equalsIgnoreCase("Destroyed")) {
                    instances.addInstance(vm);
                    continue;
                }

                CloudStackUserVm resp = getApi().startVirtualMachine(vm.getId());
                if (resp != null) {
                    vm.setState(resp.getState());
                    if (logger.isDebugEnabled())
                        logger.debug("Starting VM " + vm.getId() + " job " + resp.getJobId());
                }
                instances.addInstance(vm);
            }
        } catch (Exception e) {
            logger.error("EC2 StartInstances - ", e);
            handleException(e);
        }
        return instances;
    }

    /**
     * Stop an instance or instances
     *
     * @param request
     * @return
     */
    public EC2StopInstancesResponse stopInstances(EC2StopInstances request) {
        EC2StopInstancesResponse instances = new EC2StopInstancesResponse();
        EC2Instance[] virtualMachines = null;

        // -> first determine the current state of each VM (becomes it previous state)
        try {
            String[] instanceSet = request.getInstancesSet();
            Boolean forced = request.getForce();

            EC2DescribeInstancesResponse previousState = listVirtualMachines(instanceSet, null, null);
            virtualMachines = previousState.getInstanceSet();

            // -> send stop requests for each item
            for (EC2Instance vm : virtualMachines) {
                vm.setPreviousState(vm.getState());
                CloudStackUserVm resp = null;
                if (request.getDestroyInstances()) {
                    if (vm.getState().equalsIgnoreCase("Destroyed")) {
                        instances.addInstance(vm);
                        continue;
                    }
                    resp = getApi().destroyVirtualMachine(vm.getId());
                    if (logger.isDebugEnabled())
                        logger.debug("Destroying VM " + vm.getId() + " job " + resp.getJobId());
                } else {
                    if (vm.getState().equalsIgnoreCase("Stopped") || vm.getState().equalsIgnoreCase("Destroyed")) {
                        instances.addInstance(vm);
                        continue;
                    }
                    resp = getApi().stopVirtualMachine(vm.getId(), forced);
                    if (logger.isDebugEnabled())
                        logger.debug("Stopping VM " + vm.getId() + " job " + resp.getJobId());
                }
                if (resp != null) {
                    vm.setState(resp.getState());
                    instances.addInstance(vm);
                }
            }
        } catch (Exception e) {
            logger.error("EC2 StopInstances - ", e);
            handleException(e);
        }
        return instances;
    }

    /**
     * @param request
     * @return
     * @throws Exception
     */
    public boolean modifyInstanceAttribute(EC2ModifyInstanceAttribute request) {
        boolean status = true;
        String instanceId = request.getInstanceId();
        try {
            // AWS requires VM to be in stopped state to modify 'InstanceType' and 'UserData'
            EC2DescribeInstancesResponse vmResponse = new EC2DescribeInstancesResponse();
            vmResponse = lookupInstances(instanceId, vmResponse, null);
            EC2Instance[] instances = vmResponse.getInstanceSet();
            if (!instances[0].getState().equalsIgnoreCase("stopped")) {
                throw new Exception("Cannot modify, instance should be in stopped state");
            }

            if (request.getInstanceType() != null) {
                String instanceType = request.getInstanceType();
                CloudStackServiceOffering svcOffering = getCSServiceOfferingId(instanceType);
                if (svcOffering == null)
                    throw new Exception("instanceType not found");
                CloudStackUserVm userVm = getApi().changeServiceForVirtualMachine(instanceId, svcOffering.getId());
                status = (userVm != null);
            }
            if (status != false && request.getUserData() != null) {
                CloudStackUserVm userVm = getApi().updateVirtualMachine(instanceId, null, null, null, null, request.getUserData());
                status = (userVm != null);
            }
        } catch (Exception e) {
            logger.error("ModifyInstanceAttribute - ", e);
            handleException(e);
        }
        return status;
    }

    /**
     * RunInstances includes a min and max count of requested instances to create.
     * We have to be able to create the min number for the user or none at all.  So
     * here we determine what the user has left to create.
     *
     * @return -1 means no limit exists, other positive numbers give max number left that
     *         the user can create.
     */
    private int calculateAllowedInstances() throws Exception {
        int maxAllowed = -1;

        CloudStackAccount ourAccount = getCurrentAccount();

        if (ourAccount == null) {
            // This should never happen, but
            // we will return -99999 if this happens...
            return -99999;
        }

        // if accountType is Admin == 1, then let's return -1
        if (ourAccount.getAccountType() == 1)
            return -1;

        // -> get the user limits on instances
        // "0" represents instances:
        // http://download.cloud.com/releases/2.2.0/api_2.2.8/user/listResourceLimits.html
        List<CloudStackResourceLimit> limits = getApi().listResourceLimits(null, null, null, null, "0");
        if (limits != null && limits.size() > 0) {
            maxAllowed = (int)limits.get(0).getMax().longValue();
            if (maxAllowed == -1)
                return -1;   // no limit

            EC2DescribeInstancesResponse existingVMS = listVirtualMachines(null, null, null);
            EC2Instance[] vmsList = existingVMS.getInstanceSet();
            return (maxAllowed - vmsList.length);
        } else {
            return 0;
        }
    }

    /**
     * Performs the cloud API listVirtualMachines one or more times.
     *
     * @param virtualMachineIds - an array of instances we are interested in getting information on
     * @param ifs - filter out unwanted instances
     */
    private EC2DescribeInstancesResponse listVirtualMachines(String[] virtualMachineIds, EC2InstanceFilterSet ifs, List<CloudStackKeyValue> resourceTags)
            throws Exception {
        EC2DescribeInstancesResponse instances = new EC2DescribeInstancesResponse();

        if (null == virtualMachineIds || 0 == virtualMachineIds.length) {
            instances = lookupInstances(null, instances, resourceTags);
        } else {
            for (int i = 0; i < virtualMachineIds.length; i++) {
                instances = lookupInstances(virtualMachineIds[i], instances, resourceTags);
            }
        }

        if (null == ifs)
            return instances;
        else
            return ifs.evaluate(instances);
    }

    /**
     * Get one or more templates depending on the volumeId parameter.
     *
     * @param volumeId   - if interested in one specific volume, null if want to list all volumes
     * @param instanceId - if interested in volumes for a specific instance, null if instance is not important
     */
    private EC2DescribeVolumesResponse listVolumes(String volumeId, String instanceId, EC2DescribeVolumesResponse volumes, List<CloudStackKeyValue> resourceTagSet)
            throws Exception {

        List<CloudStackVolume> vols = getApi().listVolumes(null, null, null, volumeId, null, null, null, null, null, instanceId, null, resourceTagSet);
        if (vols != null && vols.size() > 0) {
            for (CloudStackVolume vol : vols) {
                EC2Volume ec2Vol = new EC2Volume();
                ec2Vol.setId(vol.getId());
                if (vol.getAttached() != null)
                    ec2Vol.setAttached(vol.getAttached());
                ec2Vol.setCreated(vol.getCreated());

                if (vol.getDeviceId() != null)
                    ec2Vol.setDeviceId(vol.getDeviceId());
                ec2Vol.setHypervisor(vol.getHypervisor());

                if (vol.getSnapshotId() != null)
                    ec2Vol.setSnapshotId(vol.getSnapshotId());
                ec2Vol.setState(mapToAmazonVolState(vol.getState()));
                ec2Vol.setSize(vol.getSize());
                ec2Vol.setType(vol.getVolumeType());

                if (vol.getVirtualMachineId() != null) {
                    ec2Vol.setInstanceId(vol.getVirtualMachineId());
                    if (vol.getVirtualMachineState() != null) {
                        ec2Vol.setVMState(vol.getVirtualMachineState());
                        ec2Vol.setAttachmentState(mapToAmazonVolumeAttachmentState(vol.getVirtualMachineState()));
                    }
                } else {
                    ec2Vol.setAttachmentState("detached");
                }

                ec2Vol.setZoneName(vol.getZoneName());

                List<CloudStackKeyValue> resourceTags = vol.getTags();
                for (CloudStackKeyValue resourceTag : resourceTags) {
                    EC2TagKeyValue param = new EC2TagKeyValue();
                    param.setKey(resourceTag.getKey());
                    if (resourceTag.getValue() != null)
                        param.setValue(resourceTag.getValue());
                    ec2Vol.addResourceTag(param);
                }

                volumes.addVolume(ec2Vol);
            }
        }

        return volumes;
    }

    /**
     * Translate the given zone name into the required zoneId.  Query for
     * a list of all zones and match the zone name given.   Amazon uses zone
     * names while the Cloud API often requires the zoneId.
     *
     * @param zoneName - (e.g., 'AH'), if null return the first zone in the available list
     *
     * @return the zoneId that matches the given zone name
     */
    private String toZoneId(String zoneName, String domainId) throws Exception {
        EC2DescribeAvailabilityZonesResponse zones = null;
        String[] interestedZones = null;

        if (null != zoneName) {
            interestedZones = new String[1];
            interestedZones[0] = zoneName;
        } else {
            CloudStackZone zone = findZone();
            if (zone != null) {
                return zone.getId();
            }
        }

        zones = listZones(interestedZones, domainId);

        if (zones == null || zones.getAvailabilityZoneSet().length == 0)
            throw new Exception("Unknown zoneName value");

        EC2AvailabilityZone[] zoneSet = zones.getAvailabilityZoneSet();
        return zoneSet[0].getId();
    }

    /**
     * Convert from the Amazon instanceType strings to Cloud serviceOfferingId
     *
     */

    private CloudStackServiceOffering getCSServiceOfferingId(String instanceType) throws Exception {
        try {
            if (instanceType == null)
                instanceType = "m1.small"; // default value
            List<CloudStackServiceOffering> serviceOfferings = getApi().listServiceOfferings(null, null, false, null, instanceType, null, null);
            return serviceOfferings.get(0);
        } catch (Exception e) {
            logger.error("Error while retrieving ServiceOffering information by name - ", e);
            throw new Exception("No ServiceOffering found to be defined by name");
        }
    }

    /**
     * Convert from the Cloud serviceOfferingId to the Amazon instanceType strings based
     * on the loaded map.
     *
     * @param serviceOfferingId
     * @return A valid value for the Amazon defined instanceType
     * @throws SQLException, ClassNotFoundException, IllegalAccessException, InstantiationException
     */
    private String serviceOfferingIdToInstanceType(String serviceOfferingId) throws Exception {
        try {

            CloudStackServiceOfferingVO offering = scvoDao.getSvcOfferingById(serviceOfferingId);
            if (offering == null) {
                logger.warn("No instanceType match for serviceOfferingId: [" + serviceOfferingId + "]");
                return "m1.small";
            }
            return offering.getName();
        } catch (Exception e) {
            logger.error("Error while retrieving ServiceOffering information by id - ", e);
            throw new Exception(e.getMessage() != null ? e.getMessage() : e.toString());
        }
    }

    /**
     * Match the value in the 'description' field of the listOsTypes response to get
     * the osTypeId.
     *
     * @param osTypeName
     * @return the Cloud.com API osTypeId
     */
    private String toOSTypeId(String osTypeName) throws Exception {
        try {
            List<CloudStackOsType> osTypes = getApi().listOsTypes(null, null, null);
            for (CloudStackOsType osType : osTypes) {
                if (osType.getDescription().toLowerCase().indexOf(osTypeName.toLowerCase()) != -1)
                    return osType.getId();
            }
            return null;
        } catch (Exception e) {
            logger.error("List OS Types - ", e);
            throw new Exception(e.getMessage() != null ? e.getMessage() : e.toString());
        }

    }

    /**
     * More than one place we need to access the defined list of zones.  If given a specific
     * list of zones of interest, then only values from those zones are returned.
     *
     * @param interestedZones - can be null, should be a subset of all zones
     *
     * @return EC2DescribeAvailabilityZonesResponse
     */
    private EC2DescribeAvailabilityZonesResponse listZones(String[] interestedZones, String domainId) throws Exception {
        EC2DescribeAvailabilityZonesResponse zones = new EC2DescribeAvailabilityZonesResponse();

        List<CloudStackZone> cloudZones = getApi().listZones(true, domainId, null, null);
        if (cloudZones != null && cloudZones.size() > 0) {
            for (CloudStackZone cloudZone : cloudZones) {
                boolean matched = false;
                if (interestedZones.length > 0) {
                    for (String zoneName : interestedZones) {
                        if (zoneName.equalsIgnoreCase(cloudZone.getName())) {
                            matched = true;
                            break;
                        }
                    }
                } else {
                    matched = true;
                }
                if (!matched)
                    continue;
                EC2AvailabilityZone ec2Zone = new EC2AvailabilityZone();
                ec2Zone.setId(cloudZone.getId().toString());
                ec2Zone.setMessage(cloudZone.getAllocationState());
                ec2Zone.setName(cloudZone.getName());

                zones.addAvailabilityZone(ec2Zone);
            }
        }
        return zones;
    }

    /**
     * Get information on one or more virtual machines depending on the instanceId parameter.
     *
     * @param instanceId - if null then return information on all existing instances, otherwise
     *                     just return information on the matching instance.
     * @param instances  - a container object to fill with one or more EC2Instance objects
     *
     * @return the same object passed in as the "instances" parameter modified with one or more
     *         EC2Instance objects loaded.
     */
    private EC2DescribeInstancesResponse lookupInstances(String instanceId, EC2DescribeInstancesResponse instances, List<CloudStackKeyValue> resourceTagSet)
            throws Exception {

        String instId = instanceId != null ? instanceId : null;
        List<CloudStackUserVm> vms =
                getApi().listVirtualMachines(null, null, true, null, null, null, null, instId, null, null, null, null, null, null, null, null, resourceTagSet);

        if (vms != null && vms.size() > 0) {
            for (CloudStackUserVm cloudVm : vms) {
                EC2Instance ec2Vm = new EC2Instance();

                ec2Vm.setId(cloudVm.getId().toString());
                ec2Vm.setName(cloudVm.getName());
                ec2Vm.setZoneName(cloudVm.getZoneName());
                ec2Vm.setTemplateId(cloudVm.getTemplateId().toString());
                ec2Vm.setGroup(cloudVm.getGroup());
                ec2Vm.setState(cloudVm.getState());
                ec2Vm.setCreated(cloudVm.getCreated());
                ec2Vm.setIpAddress(cloudVm.getIpAddress());
                ec2Vm.setAccountName(cloudVm.getAccountName());
                ec2Vm.setDomainId(cloudVm.getDomainId());
                ec2Vm.setHypervisor(mapToAmazonHypervisorType(cloudVm.getHypervisor()));
                ec2Vm.setRootDeviceType(cloudVm.getRootDeviceType());
                ec2Vm.setRootDeviceId(cloudVm.getRootDeviceId());
                ec2Vm.setServiceOffering(serviceOfferingIdToInstanceType(cloudVm.getServiceOfferingId().toString()));
                ec2Vm.setKeyPairName(cloudVm.getKeyPairName());

                List<CloudStackNic> nics = cloudVm.getNics();
                for (CloudStackNic nic : nics) {
                    if (nic.getIsDefault()) {
                        ec2Vm.setPrivateIpAddress(nic.getIpaddress());
                        break;
                    }
                }

                List<CloudStackKeyValue> resourceTags = cloudVm.getTags();
                for (CloudStackKeyValue resourceTag : resourceTags) {
                    EC2TagKeyValue param = new EC2TagKeyValue();
                    param.setKey(resourceTag.getKey());
                    if (resourceTag.getValue() != null)
                        param.setValue(resourceTag.getValue());
                    ec2Vm.addResourceTag(param);
                }

                if (cloudVm.getSecurityGroupList() != null && cloudVm.getSecurityGroupList().size() > 0) {
                    List<CloudStackSecurityGroup> securityGroupList = cloudVm.getSecurityGroupList();
                    for (CloudStackSecurityGroup securityGroup : securityGroupList) {
                        EC2SecurityGroup param = new EC2SecurityGroup();
                        param.setId(securityGroup.getId());
                        param.setName(securityGroup.getName());
                        ec2Vm.addGroupName(param);
                    }
                }

                instances.addInstance(ec2Vm);
            }
        } else {
            if (instanceId != null) {
                //no such instance found
                throw new Exception("Instance not found");
            }
        }
        return instances;
    }

    /**
     * Get one or more templates depending on the templateId parameter.
     *
     * @param templateId - if null then return information on all existing templates, otherwise
     *                     just return information on the matching template.
     * @param images     - a container object to fill with one or more EC2Image objects
     *
     * @return the same object passed in as the "images" parameter modified with one or more
     *         EC2Image objects loaded.
     */
    private EC2DescribeImagesResponse listTemplates(String templateId, EC2DescribeImagesResponse images) throws Exception {
        try {
            List<CloudStackTemplate> result = new ArrayList<CloudStackTemplate>();

            if (templateId != null) {
                List<CloudStackTemplate> template = getApi().listTemplates("executable", null, null, null, templateId, null, null, null);
                if (template != null) {
                    result.addAll(template);
                }
            } else {
                List<CloudStackTemplate> selfExecutable = getApi().listTemplates("selfexecutable", null, null, null, null, null, null, null);
                if (selfExecutable != null) {
                    result.addAll(selfExecutable);
                }

                List<CloudStackTemplate> featured = getApi().listTemplates("featured", null, null, null, null, null, null, null);
                if (featured != null) {
                    result.addAll(featured);
                }

                List<CloudStackTemplate> sharedExecutable = getApi().listTemplates("sharedexecutable", null, null, null, null, null, null, null);
                if (sharedExecutable != null) {
                    result.addAll(sharedExecutable);
                }

                List<CloudStackTemplate> community = getApi().listTemplates("community", null, null, null, null, null, null, null);
                if (community != null) {
                    result.addAll(community);
                }
            }

            if (result != null && result.size() > 0) {
                for (CloudStackTemplate temp : result) {
                    EC2Image ec2Image = new EC2Image();
                    ec2Image.setId(temp.getId().toString());
                    ec2Image.setAccountName(temp.getAccount());
                    ec2Image.setName(temp.getName());
                    ec2Image.setDescription(temp.getDisplayText());
                    ec2Image.setOsTypeId(temp.getOsTypeId().toString());
                    ec2Image.setIsPublic(temp.getIsPublic());
                    ec2Image.setState(temp.getIsReady() ? "available" : "pending");
                    ec2Image.setDomainId(temp.getDomainId());
                    if (temp.getHyperVisor().equalsIgnoreCase("xenserver"))
                        ec2Image.setHypervisor("xenserver");
                    else if (temp.getHyperVisor().equalsIgnoreCase("ovm"))
                        ec2Image.setHypervisor("ovm"); // valid values for hypervisor is 'ovm' and 'xenserver'
                    else
                        ec2Image.setHypervisor("");
                    if (temp.getDisplayText() == null)
                        ec2Image.setArchitecture("");
                    else if (temp.getDisplayText().indexOf("x86_64") != -1)
                        ec2Image.setArchitecture("x86_64");
                    else if (temp.getDisplayText().indexOf("i386") != -1)
                        ec2Image.setArchitecture("i386");
                    else
                        ec2Image.setArchitecture("");
                    List<CloudStackKeyValue> resourceTags = temp.getTags();
                    for (CloudStackKeyValue resourceTag : resourceTags) {
                        EC2TagKeyValue param = new EC2TagKeyValue();
                        param.setKey(resourceTag.getKey());
                        if (resourceTag.getValue() != null)
                            param.setValue(resourceTag.getValue());
                        ec2Image.addResourceTag(param);
                    }
                    images.addImage(ec2Image);
                }
            }
            return images;
        } catch (Exception e) {
            logger.error("List Templates - ", e);
            throw new Exception(e.getMessage() != null ? e.getMessage() : e.toString());
        }
    }

    /**
     * List security groups
     *
     * @param interestedGroups
     * @return
     * @throws EC2ServiceException
     * @throws UnsupportedEncodingException
     * @throws SignatureException
     * @throws IOException
     * @throws SAXException
     * @throws ParserConfigurationException
     * @throws ParseException
     */
    private EC2DescribeSecurityGroupsResponse listSecurityGroups(String[] interestedGroups) throws Exception {
        try {
            EC2DescribeSecurityGroupsResponse groupSet = new EC2DescribeSecurityGroupsResponse();

            List<CloudStackSecurityGroup> groups = getApi().listSecurityGroups(null, null, null, true, null, null, null);
            if (groups != null && groups.size() > 0)
                for (CloudStackSecurityGroup group : groups) {
                    boolean matched = false;
                    if (interestedGroups.length > 0) {
                        for (String groupName : interestedGroups) {
                            if (groupName.equalsIgnoreCase(group.getName())) {
                                matched = true;
                                break;
                            }
                        }
                    } else {
                        matched = true;
                    }
                    if (!matched)
                        continue;
                    EC2SecurityGroup ec2Group = new EC2SecurityGroup();
                    // not sure if we should set both account and account name to accountname
                    ec2Group.setAccount(group.getAccountName());
                    ec2Group.setAccountName(group.getAccountName());
                    ec2Group.setName(group.getName());
                    ec2Group.setDescription(group.getDescription());
                    ec2Group.setDomainId(group.getDomainId());
                    ec2Group.setId(group.getId().toString());
                    toPermission(ec2Group, group);
                    List<CloudStackKeyValue> resourceTags = group.getTags();
                    for (CloudStackKeyValue resourceTag : resourceTags) {
                        EC2TagKeyValue param = new EC2TagKeyValue();
                        param.setKey(resourceTag.getKey());
                        if (resourceTag.getValue() != null)
                            param.setValue(resourceTag.getValue());
                        ec2Group.addResourceTag(param);
                    }
                    groupSet.addGroup(ec2Group);
                }
            return groupSet;
        } catch (Exception e) {
            logger.error("List Security Groups - ", e);
            throw new Exception(e.getMessage() != null ? e.getMessage() : e.toString());
        }
    }

    private EC2DescribeKeyPairsResponse listKeyPairs(String[] keyNames) throws Exception {
        try {
            EC2DescribeKeyPairsResponse keyPairSet = new EC2DescribeKeyPairsResponse();

            List<CloudStackKeyPair> keyPairs = getApi().listSSHKeyPairs(null, null, null);
            if (keyPairs != null && keyPairs.size() > 0) {
                for (CloudStackKeyPair keyPair : keyPairs) {
                    boolean matched = false;
                    if (keyNames.length > 0) {
                        for (String keyName : keyNames) {
                            if (keyName.equalsIgnoreCase(keyPair.getName())) {
                                matched = true;
                                break;
                            }
                        }
                    } else
                        matched = true;
                    if (!matched)
                        continue;
                    EC2SSHKeyPair ec2KeyPair = new EC2SSHKeyPair();
                    ec2KeyPair.setFingerprint(keyPair.getFingerprint());
                    ec2KeyPair.setKeyName(keyPair.getName());
                    ec2KeyPair.setPrivateKey(keyPair.getPrivatekey());

                    keyPairSet.addKeyPair(ec2KeyPair);
                }
            }
            return keyPairSet;
        } catch (Exception e) {
            logger.error("List Keypairs - ", e);
            throw new Exception(e.getMessage() != null ? e.getMessage() : e.toString());
        }
    }

    private EC2DescribeAddressesResponse listAddresses(String[] addressNames) throws Exception {
        try {
            EC2DescribeAddressesResponse addressSet = new EC2DescribeAddressesResponse();

            List<CloudStackIpAddress> addresses = getApi().listPublicIpAddresses(null, null, null, null, null, null, null, null, null);
            if (addresses != null && addresses.size() > 0) {
                for (CloudStackIpAddress address : addresses) {
                    boolean matched = false;
                    if (addressNames.length > 0) {
                        for (String addressName : addressNames) {
                            if (address.getIpAddress().equalsIgnoreCase(addressName)) {
                                matched = true;
                                break;
                            }
                        }
                    } else
                        matched = true;

                    if (!matched)
                        continue;

                    EC2Address ec2Address = new EC2Address();
                    ec2Address.setIpAddress(address.getIpAddress());
                    if (address.getVirtualMachineId() != null)
                        ec2Address.setAssociatedInstanceId(address.getVirtualMachineId().toString());
                    addressSet.addAddress(ec2Address);
                }
            }
            return addressSet;
        } catch (Exception e) {
            logger.error("List Addresses - ", e);
            throw new Exception(e.getMessage() != null ? e.getMessage() : e.toString());
        }
    }

    private EC2DescribeSnapshotsResponse listSnapshots(String[] snapshotIds, List<CloudStackKeyValue> resourceTagSet) throws Exception {
        try {
            EC2DescribeSnapshotsResponse snapshotSet = new EC2DescribeSnapshotsResponse();

            List<CloudStackSnapshot> snapshots = getApi().listSnapshots(null, null, null, null, null, null, null, null, null, resourceTagSet);
            if (snapshots != null && snapshots.size() > 0) {
                for (CloudStackSnapshot snapshot : snapshots) {
                    boolean matched = false;
                    if (snapshotIds.length > 0) {
                        for (String snapshotId : snapshotIds) {
                            if (snapshot.getId().equalsIgnoreCase(snapshotId)) {
                                matched = true;
                                break;
                            }
                        }
                    } else
                        matched = true;

                    if (!matched)
                        continue;

                    EC2Snapshot ec2Snapshot = new EC2Snapshot();
                    ec2Snapshot.setId(snapshot.getId());
                    ec2Snapshot.setName(snapshot.getName());
                    ec2Snapshot.setVolumeId(snapshot.getVolumeId());
                    ec2Snapshot.setType(snapshot.getSnapshotType());
                    ec2Snapshot.setState(snapshot.getState());
                    ec2Snapshot.setCreated(snapshot.getCreated());
                    ec2Snapshot.setAccountName(snapshot.getAccountName());
                    ec2Snapshot.setDomainId(snapshot.getDomainId());

                    List<CloudStackKeyValue> resourceTags = snapshot.getTags();
                    for (CloudStackKeyValue resourceTag : resourceTags) {
                        EC2TagKeyValue param = new EC2TagKeyValue();
                        param.setKey(resourceTag.getKey());
                        if (resourceTag.getValue() != null)
                            param.setValue(resourceTag.getValue());
                        ec2Snapshot.addResourceTag(param);
                    }
                    snapshotSet.addSnapshot(ec2Snapshot);
                }
            }
            return snapshotSet;
        } catch (Exception e) {
            logger.error("List Snapshots - ", e);
            throw new Exception(e.getMessage() != null ? e.getMessage() : e.toString());
        }
    }

    /**
     * Convert ingress rule to EC2IpPermission records
     *
     * @param response
     * @param group
     * @return
     */
    private boolean toPermission(EC2SecurityGroup response, CloudStackSecurityGroup group) {
        List<CloudStackIngressRule> rules = group.getIngressRules();

        if (rules == null || rules.isEmpty())
            return false;

        for (CloudStackIngressRule rule : rules) {
            EC2IpPermission perm = new EC2IpPermission();
            perm.setProtocol(rule.getProtocol());
            perm.setFromPort(rule.getStartPort());
            perm.setToPort(rule.getEndPort());
            perm.setRuleId(rule.getRuleId() != null ? rule.getRuleId().toString() : "");
            perm.setIcmpCode(rule.getIcmpCode() != null ? rule.getIcmpCode().toString() : "");
            perm.setIcmpType(rule.getIcmpType() != null ? rule.getIcmpType().toString() : "");
            perm.setCIDR(rule.getCidr());
            perm.addIpRange(rule.getCidr());

            if (rule.getAccountName() != null && rule.getSecurityGroupName() != null) {
                EC2SecurityGroup newGroup = new EC2SecurityGroup();
                newGroup.setAccount(rule.getAccountName());
                newGroup.setName(rule.getSecurityGroupName());
                perm.addUser(newGroup);
            }
            response.addIpPermission(perm);
        }
        return true;
    }

    /**
     * Find the current account based on the SecretKey
     *
     * @return
     * @throws Exception
     */
    public CloudStackAccount getCurrentAccount() throws Exception {
        if (currentAccount != null) {
            // verify this is the same account!!!
            for (CloudStackUser user : currentAccount.getUser()) {
                if (user.getSecretkey() != null && user.getSecretkey().equalsIgnoreCase(UserContext.current().getSecretKey())) {
                    return currentAccount;
                }
            }
        }
        // otherwise let's find this user/account
        List<CloudStackAccount> accounts = getApi().listAccounts(null, null, null, null, null, null, null, null);
        for (CloudStackAccount account : accounts) {
            CloudStackUser[] users = account.getUser();
            for (CloudStackUser user : users) {
                String userSecretKey = user.getSecretkey();
                if (userSecretKey != null && userSecretKey.equalsIgnoreCase(UserContext.current().getSecretKey())) {
                    currentAccount = account;
                    return account;
                }
            }
        }
        // if we get here, there is something wrong...
        return null;
    }

    /**
     * List networkOfferings by zone with securityGroup enabled
     *
     * @param zoneId
     * @return
     * @throws Exception
     */
    private CloudStackNetwork getNetworksWithSecurityGroupEnabled(String zoneId) throws Exception {
        List<CloudStackNetwork> networks = getApi().listNetworks(null, null, null, null, null, null, null, null, null, zoneId);
        List<CloudStackNetwork> netWithSecGroup = new ArrayList<CloudStackNetwork>();
        for (CloudStackNetwork network : networks) {
            if (!network.getNetworkOfferingAvailability().equalsIgnoreCase("unavailable") && network.getSecurityGroupEnabled())
                netWithSecGroup.add(network);
        }
        // we'll take the first one
        return netWithSecGroup.get(0);
    }

    /**
     * Create a network
     *
     * @param zoneId
     * @param offering
     * @param owner
     * @return
     * @throws Exception
     */
    private CloudStackNetwork createDefaultGuestNetwork(String zoneId, CloudStackNetworkOffering offering, CloudStackAccount owner) throws Exception {
        return getApi().createNetwork(owner.getName() + "-network", owner.getName() + "-network", offering.getId(), zoneId, owner.getName(), owner.getDomainId(), true,
                null, null, null, null, null, null, null, null);
    }

    /**
     * List of networks without securityGroup enabled by zone
     *
     * @param zoneId
     * @return
     * @throws Exception
     */
    private CloudStackNetwork getNetworksWithoutSecurityGroupEnabled(String zoneId) throws Exception {
        // grab current account
        CloudStackAccount caller = getCurrentAccount();

        //check if account has any networks in the system
        List<CloudStackNetwork> networks = getApi().listNetworks(caller.getName(), caller.getDomainId(), null, true, null, null, null, null, null, zoneId);

        //listRequired offerings in the system - the network created from this offering has to be specified in deployVm command
        List<CloudStackNetworkOffering> reuquiredOfferings = getApi().listNetworkOfferings("Required", null, null, null, true, null, null, null, null, null, zoneId);
        if (reuquiredOfferings != null && !reuquiredOfferings.isEmpty()) {
            if (networks != null && !networks.isEmpty()) {
                //pick up the first required network from the network list
                for (CloudStackNetwork network : networks) {
                    for (CloudStackNetworkOffering requiredOffering : reuquiredOfferings) {
                        logger.debug("[reqd/virtual} offering: " + requiredOffering.getId() + " network " + network.getNetworkOfferingId());
                        if (network.getNetworkOfferingId().equals(requiredOffering.getId())) {
                            return network;
                        }
                    }
                }
            } else {
                //create new network and return it
                return createDefaultGuestNetwork(zoneId, reuquiredOfferings.get(0), caller);
            }
        } else {
            //find all optional network offerings in the system
            List<CloudStackNetworkOffering> optionalOfferings = getApi().listNetworkOfferings("Optional", null, null, null, true, null, null, null, null, null, zoneId);
            if (optionalOfferings != null && !optionalOfferings.isEmpty()) {
                if (networks != null && !networks.isEmpty()) {
                    for (CloudStackNetwork network : networks) {
                        for (CloudStackNetworkOffering optionalOffering : optionalOfferings) {
                            logger.debug("[optional] offering: " + optionalOffering.getId() + " network " + network.getNetworkOfferingId());
                            if (network.getNetworkOfferingId().equals(optionalOffering.getId())) {
                                return network;
                            }
                        }
                    }
                }
            }
        }

        // if we get this far and haven't returned already return an error
        throw new Exception("Unable to find an appropriate network for account ");
    }

    /**
     * Find a suitable network to use for deployVM
     *
     * @param zone
     * @return
     * @throws Exception
     */
    private CloudStackNetwork findNetwork(CloudStackZone zone) throws Exception {
        if (zone == null)
            return null;

        // for basic networking, we don't specify a networkid for deployvm
        if (zone.getNetworkType().equalsIgnoreCase("basic"))
            return null;

        if (zone.getSecurityGroupsEnabled()) {
            // find system security group enabled network
            return getNetworksWithSecurityGroupEnabled(zone.getId());

        } else {
            return getNetworksWithoutSecurityGroupEnabled(zone.getId());
        }
    }

    private CloudStackZone findZone() throws Exception {
        CloudStackAccount caller = getCurrentAccount();
        List<CloudStackZone> cloudZones;

        String defaultZoneId = getDefaultZoneId(caller.getId());
        if (defaultZoneId != null) {
            cloudZones = getApi().listZones(true, null, defaultZoneId, null);
        } else {
            // caller.getDomainId doesn't work in user mode
            // List<CloudStackZone> cloudZones = getApi().listZones(true, caller.getDomainId(), null, null);
            cloudZones = getApi().listZones(true, null, null, null);
        }
        if (cloudZones != null && cloudZones.size() > 0) {
            return cloudZones.get(0);
        }
        return null;
    }

    /**
     * Finds the defaultZone marked for the account
     */
    private String getDefaultZoneId(String accountId) throws Exception {
        try {
            return accDao.getDefaultZoneId(accountId);
        } catch (Exception e) {
            logger.error("Error while retrieving Account information by id - ", e);
            throw new Exception("Unable to retrieve account account information");
        }
    }

    /**
     * Windows has its own device strings.
     *
     * @param hypervisor
     * @param deviceId
     * @return
     */
    public String cloudDeviceIdToDevicePath(String hypervisor, String deviceId) {
        Integer devId = new Integer(deviceId);
        if (null != hypervisor && hypervisor.toLowerCase().contains("windows")) {
            switch (devId) {
            case 1:
                return "xvdb";
            case 2:
                return "xvdc";
            case 3:
                return "xvdd";
            case 4:
                return "xvde";
            case 5:
                return "xvdf";
            case 6:
                return "xvdg";
            case 7:
                return "xvdh";
            case 8:
                return "xvdi";
            case 9:
                return "xvdj";
            default:
                return new String("" + deviceId);
            }
        } else {    // -> assume its unix
            switch (devId) {
            case 1:
                return "/dev/sdb";
            case 2:
                return "/dev/sdc";
            case 3:
                return "/dev/sdd";
            case 4:
                return "/dev/sde";
            case 5:
                return "/dev/sdf";
            case 6:
                return "/dev/sdg";
            case 7:
                return "/dev/sdh";
            case 8:
                return "/dev/sdi";
            case 9:
                return "/dev/sdj";
            default:
                return new String("" + deviceId);
            }
        }
    }

    /**
     * Translate the device name string into a Cloud Stack deviceId.
     * deviceId 3 is reserved for CDROM and 0 for the ROOT disk
     *
     * @param device string
     * @return deviceId value
     */
    private String mapDeviceToCloudDeviceId(String device) throws Exception {
        if (device.equalsIgnoreCase("/dev/sdb"))
            return "1";
        else if (device.equalsIgnoreCase("/dev/sdc"))
            return "2";
        else if (device.equalsIgnoreCase("/dev/sde"))
            return "4";
        else if (device.equalsIgnoreCase("/dev/sdf"))
            return "5";
        else if (device.equalsIgnoreCase("/dev/sdg"))
            return "6";
        else if (device.equalsIgnoreCase("/dev/sdh"))
            return "7";
        else if (device.equalsIgnoreCase("/dev/sdi"))
            return "8";
        else if (device.equalsIgnoreCase("/dev/sdj"))
            return "9";

        else if (device.equalsIgnoreCase("/dev/xvdb"))
            return "1";
        else if (device.equalsIgnoreCase("/dev/xvdc"))
            return "2";
        else if (device.equalsIgnoreCase("/dev/xvde"))
            return "4";
        else if (device.equalsIgnoreCase("/dev/xvdf"))
            return "5";
        else if (device.equalsIgnoreCase("/dev/xvdg"))
            return "6";
        else if (device.equalsIgnoreCase("/dev/xvdh"))
            return "7";
        else if (device.equalsIgnoreCase("/dev/xvdi"))
            return "8";
        else if (device.equalsIgnoreCase("/dev/xvdj"))
            return "9";

        else if (device.equalsIgnoreCase("xvdb"))
            return "1";
        else if (device.equalsIgnoreCase("xvdc"))
            return "2";
        else if (device.equalsIgnoreCase("xvde"))
            return "4";
        else if (device.equalsIgnoreCase("xvdf"))
            return "5";
        else if (device.equalsIgnoreCase("xvdg"))
            return "6";
        else if (device.equalsIgnoreCase("xvdh"))
            return "7";
        else if (device.equalsIgnoreCase("xvdi"))
            return "8";
        else if (device.equalsIgnoreCase("xvdj"))
            return "9";

        else
            throw new Exception("Device is not supported");
    }

    /**
     * Map CloudStack instance state to Amazon state strings
     *
     * @param state
     * @return
     */
    private String mapToAmazonVolState(String state) {
        if (state.equalsIgnoreCase("Allocated") || state.equalsIgnoreCase("Creating") || state.equalsIgnoreCase("Ready"))
            return "available";

        if (state.equalsIgnoreCase("Destroy"))
            return "deleting";

        return "error";
    }

    /**
     * Map CloudStack VM state to Amazon volume attachment state
     *
     * @param CloudStack VM state
     * @return Amazon Volume attachment state
     */
    private String mapToAmazonVolumeAttachmentState(String vmState) {
        if (vmState.equalsIgnoreCase("Running") || vmState.equalsIgnoreCase("Stopping") || vmState.equalsIgnoreCase("Stopped")) {
            return "attached";
        } else if (vmState.equalsIgnoreCase("Starting")) {
            return "attaching";
        } else { // VM state is 'destroyed' or 'error' or other
            return "detached";
        }
    }

    /**
     * Map Amazon resourceType to CloudStack resourceType
     *
     * @param Amazon resourceType
     * @return CloudStack resourceType
     */
    private String mapToCloudStackResourceType(String resourceType) {
        if (resourceType.equalsIgnoreCase("image"))
            return ("template");
        else if (resourceType.equalsIgnoreCase("instance"))
            return ("userVm");
        else if (resourceType.equalsIgnoreCase("security-group"))
            return ("securityGroup");
        else
            return resourceType;
    }

    /**
     * Map Amazon resourceType to CloudStack resourceType
     *
     * @param CloudStack resourceType
     * @return Amazon resourceType
     */
    private String mapToAmazonResourceType(String resourceType) {
        if (resourceType.equalsIgnoreCase("template"))
            return ("image");
        else if (resourceType.equalsIgnoreCase("userVm"))
            return ("instance");
        else if (resourceType.equalsIgnoreCase("securityGroup"))
            return ("security-group");
        else
            return (resourceType.toLowerCase());
    }

    /**
     * Map CloudStack hypervisor to CloudStack hypervisor
     *
     * @param CloudStack hypervisor
     * @return Amazon hypervisor
     */
    private String mapToAmazonHypervisorType(String hypervisor) {
        if (hypervisor.equalsIgnoreCase("Xenserver"))
            return ("xenserver");
        else if (hypervisor.equalsIgnoreCase("Ovm"))
            return ("ovm");
        else
            return ("");
    }

    /**
     * Stop an instance
     * Wait until one specific VM has stopped
     *
     * @param instanceId
     * @return
     * @throws Exception
     */
    private boolean stopVirtualMachine(String instanceId) throws Exception {
        try {
            CloudStackUserVm resp = getApi().stopVirtualMachine(instanceId, false);
            if (logger.isDebugEnabled())
                logger.debug("Stopping VM " + instanceId);
            return resp != null;
        } catch (Exception e) {
            logger.error("StopVirtualMachine - ", e);
            throw new Exception(e.getMessage() != null ? e.getMessage() : e.toString());
        }
    }

    /**
     * Start an existing stopped instance(VM)
     *
     * @param instanceId
     * @return
     * @throws Exception
     */
    private boolean startVirtualMachine(String instanceId) throws Exception {
        try {
            CloudStackUserVm resp = getApi().startVirtualMachine(instanceId);
            if (logger.isDebugEnabled())
                logger.debug("Starting VM " + instanceId);
            return resp != null;
        } catch (Exception e) {
            logger.error("StartVirtualMachine - ", e);
            throw new Exception(e.getMessage() != null ? e.getMessage() : e.toString());
        }
    }

    /**
     * Cloud Stack API takes a comma separated list as a parameter.
     *
     * @throws UnsupportedEncodingException
     */
    private String constructList(String[] elements) throws UnsupportedEncodingException {
        if (null == elements || 0 == elements.length)
            return null;
        StringBuffer elementList = new StringBuffer();

        for (int i = 0; i < elements.length; i++) {
            if (0 < i)
                elementList.append(",");
            elementList.append(elements[i]);
        }
        return elementList.toString();
    }

    private List<CloudStackKeyValue> getResourceTags(EC2TagKeyValue[] tagKeyValueSet) {
        List<CloudStackKeyValue> resourceTags = new ArrayList<CloudStackKeyValue>();
        for (EC2TagKeyValue tagKeyValue : tagKeyValueSet) {
            CloudStackKeyValue resourceTag = new CloudStackKeyValue();
            resourceTag.setKeyValue(tagKeyValue.getKey(), tagKeyValue.getValue());
            resourceTags.add(resourceTag);
        }
        return resourceTags;
    }

    private void handleException(Exception e) {
        String[] error = e.getMessage().split("Error Code - ");
        String errorMessage = error[0];
        if (error.length == 2) { // error code has been supplied
            int errorCode = Integer.parseInt(error[1]);
            if (errorCode == 431) {
                if (errorMessage.contains("Object vm_instance(uuid:") && errorMessage.contains(") does not exist")) {
                    throw new EC2ServiceException(ClientError.InvalidInstanceID_NotFound, "Specified Instance ID does not exist");
                } else if (errorMessage.contains("Unable to find security group by name") || errorMessage.contains("Unable to find security group") ||
                        (errorMessage.contains("Object security_group(uuid:") && errorMessage.contains(") does not exist")) ||
                        errorMessage.contains("Unable to find group by name ")) {
                    throw new EC2ServiceException(ClientError.InvalidGroup_NotFound, "Specified Security Group does not exist");
                } else if (errorMessage.contains("Invalid port numbers")) {
                    throw new EC2ServiceException(ClientError.InvalidPermission_Malformed, "Specified Port value is invalid");
                } else if (errorMessage.contains("Nonexistent account") && errorMessage.contains("when trying to authorize security group rule for security group")) {
                    throw new EC2ServiceException(ClientError.InvalidPermission_Malformed, "Specified account doesn't exist");
                } else if (errorMessage.contains("Nonexistent group") && errorMessage.contains("unable to authorize security group rule")) {
                    throw new EC2ServiceException(ClientError.InvalidPermission_Malformed, "Specified source security group doesn't exist");
                } else if (errorMessage.contains("Invalid protocol")) {
                    throw new EC2ServiceException(ClientError.InvalidPermission_Malformed, "Specified protocol is invalid");
                } else if (errorMessage.contains("is an Invalid CIDR")) {
                    throw new EC2ServiceException(ClientError.InvalidPermission_Malformed, "Specified CIDR is invalid");
                } else if (errorMessage.contains("Nonexistent account")) {
                    throw new EC2ServiceException(ClientError.InvalidParameterValue, "Specified Account name is invalid");
                } else if (errorMessage.contains("Object volumes(uuid:") && errorMessage.contains(") does not exist")) {
                    throw new EC2ServiceException(ClientError.InvalidVolume_NotFound, "Specified Volume ID doesn't exist");
                } else if (errorMessage.contains("Object snapshots(uuid:") && errorMessage.contains(") does not exist")) {
                    throw new EC2ServiceException(ClientError.InvalidSnapshot_NotFound, "Specified Snapshot ID doesn't exist");
                } else if ((errorMessage.contains("A key pair with name '") && errorMessage.contains("' does not exist")) ||
                        (errorMessage.contains("A key pair with name '") && errorMessage.contains("' was not found"))) {
                    throw new EC2ServiceException(ClientError.InvalidKeyPair_NotFound, "Specified Key pair name is invalid");
                } else if (errorMessage.contains("A key pair with name '") && errorMessage.contains("' already exists")) {
                    throw new EC2ServiceException(ClientError.InvalidKeyPair_Duplicate, "Specified Key pair already exists");
                } else if (errorMessage.contains("Unknown zoneName value")) {
                    throw new EC2ServiceException(ClientError.InvalidZone_NotFound, "Specified AvailabilityZone name is invalid");
                } else if (errorMessage.contains("specify a volume that is not attached to any VM")) {
                    throw new EC2ServiceException(ClientError.DependencyViolation, "Specified Volume is attached to a VM");
                } else if (errorMessage.contains("Object vm_template(uuid: ") && errorMessage.contains(") does not exist")) {
                    throw new EC2ServiceException(ClientError.InvalidAMIID_NotFound, "Specified Image ID does not exist");
                } else if (errorMessage.contains("unable to find template by id")) {
                    throw new EC2ServiceException(ClientError.InvalidAMIID_NotFound, "Specified Image ID does not exist");
                } else if (errorMessage.contains("a group with name") && errorMessage.contains("already exists")) {
                    throw new EC2ServiceException(ClientError.InvalidGroup_Duplicate, "Specified Security Group already exists");
                } else if (errorMessage.contains("specified volume is not attached to a VM")) {
                    throw new EC2ServiceException(ClientError.IncorrectState, "Specified volume is not in the correct state 'attached' for detachment");
                } else if (errorMessage.contains("Snapshot with specified snapshotId is not in BackedUp state yet and can't be used for volume creation")) {
                    throw new EC2ServiceException(ClientError.IncorrectState, "Specified snapshot is not in the correct state 'completed' for volume creation");
                } else if (errorMessage.contains("Can't delete snapshotshot 4 due to it is not in BackedUp Status")) {
                    throw new EC2ServiceException(ClientError.IncorrectState, "Specified snapshot is not in the correct state 'completed' for deletion");
                } else if (errorMessage.contains("Public key is invalid")) {
                    throw new EC2ServiceException(ClientError.InvalidKeyPair_Format, "Format of the specified key is invalid");
                } else if (errorMessage.contains("Invalid resource type")) {
                    throw new EC2ServiceException(ClientError.InvalidParameterValue, "Specified resourceId is invalid");
                } else if (errorMessage.contains("Unable to find tags by parameters specified")) {
                    throw new EC2ServiceException(ClientError.InvalidParameterValue, "Specified resourceTag for the specified resourceId doesn't exist");
                } else if (errorMessage.contains("Failed to enable static nat for the ip address with specified ipId "
                        + "as vm with specified vmId is already associated with specified currentIp")) {
                    throw new EC2ServiceException(ClientError.InvalidParameterValue, "Specified publicIp is already associated to the specified VM");
                } else if (errorMessage.contains("Specified IP address id is not associated with any vm Id")) {
                    throw new EC2ServiceException(ClientError.InvalidParameterValue, "Specified publicIp is not associated to any VM");
                } else if (errorMessage.contains("specify a VM that is either running or stopped")) {
                    throw new EC2ServiceException(ClientError.IncorrectInstanceState, "Unable to attach. Specified instances is in an incorrect state");
                } else if (errorMessage.contains("specify a valid data volume")) {
                    throw new EC2ServiceException(ClientError.InvalidVolume_NotFound, "Specified volume doen't exist");
                } else if (errorMessage.contains("VolumeId is not in Ready state, but  in state Allocated. Cannot take snapshot")) {
                    throw new EC2ServiceException(ClientError.IncorrectState, "Cannot take snapshot. Specified volume is not in the correct state");
                } else if (errorMessage.contains("Can't delete snapshot") && errorMessage.contains("it is not in BackedUp Status")) {
                    throw new EC2ServiceException(ClientError.IncorrectState, "Cannot delete snapshot. Specified snapshot is not in the correct state");
                } else if (errorMessage.contains("Invalid port range")) {
                    throw new EC2ServiceException(ClientError.InvalidPermission_Malformed, "The specified port range is invalid");
                } else if (errorMessage.contains("specify a valid User VM")) {
                    throw new EC2ServiceException(ClientError.InvalidInstanceID_NotFound, "Specified instance is invalid");
                } else if (errorMessage.contains("No password for VM with specified id found")) {
                    throw new EC2ServiceException(ClientError.InvalidParameterValue, "No password for VM with the specified id found");
                } else if (errorMessage.contains("make sure the virtual machine is stopped and not in an error state before upgrading")) {
                    throw new EC2ServiceException(ClientError.IncorrectInstanceState, "Unable to modify. Specified instances is not in the correct state 'Stopped'");
                } else if (errorMessage.contains("Not upgrading vm") && errorMessage.contains("it already has the" + " requested service offering")) {
                    throw new EC2ServiceException(ClientError.InvalidParameterValue, "Unable to modify. Specified instance already has the requested instanceType");
                }
                // Can't enable static, ip address with specified id is a sourceNat ip address ?
                else {
                    throw new EC2ServiceException(ClientError.InvalidParameterValue, "The value supplied for a parameter is invalid");
                }
            } else if (errorCode == 536) {
                if (errorMessage.contains("Cannot delete group when it's in use by virtual machines")) {
                    throw new EC2ServiceException(ClientError.InvalidGroup_InUse, "Group is in use by a virtual machine");
                } else {
                    throw new EC2ServiceException(ClientError.DependencyViolation, "Specified resource is in use");
                }
            } else if (errorCode == 531) {
                if (errorMessage.contains("Acct") && errorMessage.contains("does not have permission to launch" + " instances from Tmpl")) {
                    throw new EC2ServiceException(ClientError.AuthFailure, "User not authorized to operate on the specified AMI");
                } else {
                    throw new EC2ServiceException(ClientError.AuthFailure, "User not authorized");
                }
            } else if (errorCode == 530) {
                if (errorMessage.contains("deviceId") && errorMessage.contains("is used by VM")) {
                    throw new EC2ServiceException(ClientError.InvalidDevice_InUse, "Specified Device is already being used by the VM");
                } else if (errorMessage.contains("Entity already exists")) {
                    throw new EC2ServiceException(ClientError.InvalidParameterValue, "Specified resource tag already exists");
                } else if (errorMessage.contains("Template") && errorMessage.contains("has not been completely downloaded")) {
                    throw new EC2ServiceException(ClientError.InvalidAMIID_NotFound, "Specified ImageId is unavailable");
                } else if (errorMessage.contains("cannot stop VM") && errorMessage.contains("when it is in state Starting")) {
                    throw new EC2ServiceException(ClientError.IncorrectInstanceState,
                            "Unable to stop. One or more of the specified instances is in an incorrect state 'pending'");
                } else if (errorMessage.contains("Failed to authorize security group ingress rule(s)")) {
                    throw new EC2ServiceException(ClientError.InvalidParameterValue, "Specified Ip-permission is invalid" + " or the Ip-permission already exists");
                } else if (errorMessage.contains("Failed to reboot vm instance")) {
                    throw new EC2ServiceException(ClientError.IncorrectInstanceState, "Unable to reboot. One or more of the specified instances is in an incorrect state");
                } else if (errorMessage.contains("specify a template that is not currently being downloaded")) {
                    throw new EC2ServiceException(ClientError.IncorrectState, "Unable to deregister. Image is not in the correct state 'available'");
                } else {
                    throw new EC2ServiceException(ServerError.InternalError, "An unexpected error occured");
                }
            } else if (errorCode == 534) {
                if (errorMessage.contains("Maximum number of resources of type 'volume' for account") && errorMessage.contains("has been exceeded")) {
                    throw new EC2ServiceException(ClientError.VolumeLimitExceeded, "You have reached the limit on the number of volumes that can be created");
                } else if (errorMessage.contains("Maximum number of resources of type 'public_ip' for account") && errorMessage.contains("has been exceeded")) {
                    throw new EC2ServiceException(ClientError.AddressLimitExceeded,
                            "You have reached the limit on the number of elastic ip addresses your account can have");
                } else if (errorMessage.contains("Unable to apply save userdata entry on router")) {
                    throw new EC2ServiceException(ClientError.InvalidParameterValue, "The value supplied for parameter UserData is invalid");
                } else {
                    throw new EC2ServiceException(ServerError.InternalError, "An unexpected error occured");
                }
            } else if (errorCode == 533) {
                if (errorMessage.contains("Unable to create a deployment for VM")) {
                    throw new EC2ServiceException(ClientError.InsufficientInstanceCapacity, "There is insufficient capacity available to deploy a VM");
                } else if (errorMessage.contains("Insufficient address capacity")) {
                    throw new EC2ServiceException(ServerError.InsufficientAddressCapacity, "Not enough available addresses to satisfy your minimum request");
                } else {
                    throw new EC2ServiceException(ServerError.InternalError, "There is insufficient capacity");
                }
            } else if (errorCode == 401) {
                if (errorMessage.contains("Unauthorized")) {
                    throw new EC2ServiceException(ClientError.AuthFailure, "User not authorised");
                } else {
                    throw new EC2ServiceException(ClientError.AuthFailure, "User not authorised");
                }
            } else {
                throw new EC2ServiceException(ServerError.InternalError, "An unexpected error occured");
            }
        } else {
            if (errorMessage.contains("Unknown zoneName value")) {
                throw new EC2ServiceException(ClientError.InvalidZone_NotFound, "AvailabilityZone name is invalid");
            } else if (errorMessage.contains("No ServiceOffering found to be defined by name")) {
                throw new EC2ServiceException(ClientError.InvalidParameterValue, "Specified InstanceType is invalid");
            } else if (errorMessage.contains("Specified Ip permission is invalid")) {
                throw new EC2ServiceException(ClientError.InvalidPermission_Malformed, "Specified Ip permission is invalid");
            } else if (errorMessage.contains("One or more instanceIds do not exist, other instances rebooted")) {
                throw new EC2ServiceException(ClientError.InvalidInstanceID_NotFound, "One or more InstanceId doesn't exist, other instances rebooted");
            } else if (errorMessage.contains("Device is not supported")) {
                throw new EC2ServiceException(ClientError.InvalidParameterValue, "Value specified for parameter Device is invalid");
            } else if (errorMessage.contains("Volume is not attached to the Instance")) {
                throw new EC2ServiceException(ClientError.InvalidAttachment_NotFound, "Specified Volume is not attached to the specified Instance");
            } else if (errorMessage.contains("Volume is not attached to the Device")) {
                throw new EC2ServiceException(ClientError.InvalidAttachment_NotFound, "Specified Volume is not attached to the specified device");
            } else if (errorMessage.contains("Unable to create snapshot")) {
                throw new EC2ServiceException(ServerError.InternalError, "Unable to create snapshot");
            } else if (errorMessage.contains("Instance must be in stopped state")) {
                throw new EC2ServiceException(ClientError.IncorrectInstanceState, "Specified instance is not in the correct state 'stopped'");
            } else if (errorMessage.contains("Image couldn't be created")) {
                throw new EC2ServiceException(ServerError.InternalError, "Unable to create image");
            } else if (errorMessage.contains("Failed to start the stopped instance")) {
                throw new EC2ServiceException(ServerError.InternalError, "Unable to start the instance that was stopped during image creation");
            } else if (errorMessage.contains("One or more of instanceIds specified is in stopped state")) {
                throw new EC2ServiceException(ClientError.IncorrectInstanceState,
                        "Unable to reboot. One or more of the specified instances is in an incorrect state 'stopped'");
            } else if (errorMessage.contains("Specified ipAddress doesn't exist")) {
                throw new EC2ServiceException(ClientError.InvalidParameterValue, "Specified publicIp doesn't exist");
            } else if (errorMessage.contains("Min Count is greater than the number of instances left to allocate")) {
                throw new EC2ServiceException(ClientError.InstanceLimitExceeded, "Specified MinCount parameter is greater than the number of instances you can create");
            } else if (errorMessage.contains("instanceType not found")) {
                throw new EC2ServiceException(ClientError.InvalidParameterValue, "Specified instanceType not found");
            } else if (errorMessage.contains("zone not found")) {
                throw new EC2ServiceException(ClientError.InvalidZone_NotFound, "Specified zone doesn't exist");
            } else if (errorMessage.contains("Both groupId and groupName has been specified")) {
                throw new EC2ServiceException(ClientError.InvalidParameterCombination, " for EC2 groups either a group ID or a group name is accepted");
            } else if (errorMessage.contains("Insufficient Instance Capacity")) {
                throw new EC2ServiceException(ServerError.InsufficientInstanceCapacity, "Insufficient Instance Capacity");
            } else if (errorMessage.contains("Unable to find security group name")) {
                throw new EC2ServiceException(ClientError.InvalidGroup_NotFound, "Specified Security Group does not exist");
            } else if (errorMessage.contains("Instance not found")) {
                throw new EC2ServiceException(ClientError.InvalidInstanceID_NotFound, "One or more of the specified instanceId not found");
            } else if (errorMessage.contains("Cannot modify, instance should be in stopped state")) {
                throw new EC2ServiceException(ClientError.IncorrectInstanceState,
                        "Unable to modify instance attribute. Specified instance is not in the correct state 'stopped'");
            } else {
                throw new EC2ServiceException(ServerError.InternalError, "An unexpected error occured");
            }
        }
    }
}
