Java tutorial
// 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.commons.io.IOUtils; 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.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(); FileInputStream ec2PropFile = null; try { EC2Prop.load(new FileInputStream(propertiesFile)); 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); } finally { IOUtils.closeQuietly(ec2PropFile); } 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.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()); 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 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(); } CloudStackServiceOfferingVO 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"); } // we choose first zone? CloudStackZone zone = 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(); CloudStackServiceOfferingVO 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 CloudStackServiceOfferingVO getCSServiceOfferingId(String instanceType) throws Exception { try { if (instanceType == null) instanceType = "m1.small"; // default value return scvoDao.getSvcOfferingByName(instanceType); } 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("xen"); else if (temp.getHyperVisor().equalsIgnoreCase("ovm")) ec2Image.setHypervisor("ovm"); // valid values for hypervisor is 'ovm' and 'xen' 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() : new String()); perm.setIcmpCode(rule.getIcmpCode() != null ? rule.getIcmpCode().toString() : new String()); perm.setIcmpType(rule.getIcmpType() != null ? rule.getIcmpType().toString() : new String()); 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 ("xen"); 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"); } } } }