Repository: stratos Updated Branches: refs/heads/master f2f2f0c68 -> de9b62bb1
Revert "Fixing file name mismatch issue" This reverts commit e04f271ceb9d861170fe47841d762e8ff84cb17f. Project: http://git-wip-us.apache.org/repos/asf/stratos/repo Commit: http://git-wip-us.apache.org/repos/asf/stratos/commit/de05f5c5 Tree: http://git-wip-us.apache.org/repos/asf/stratos/tree/de05f5c5 Diff: http://git-wip-us.apache.org/repos/asf/stratos/diff/de05f5c5 Branch: refs/heads/master Commit: de05f5c551eea399327a10dd1555b4edf52d2786 Parents: 8769c4b Author: Imesh Gunaratne <[email protected]> Authored: Fri Nov 21 14:44:54 2014 +0530 Committer: Imesh Gunaratne <[email protected]> Committed: Fri Nov 21 14:44:54 2014 +0530 ---------------------------------------------------------------------- .../cloud/controller/iaases/CloudStackIaas.java | 540 +++++++++++++++++++ .../validate/CloudStackPartitionValidator.java | 53 ++ 2 files changed, 593 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/stratos/blob/de05f5c5/components/org.apache.stratos.cloud.controller/src/main/java/org/apache/stratos/cloud/controller/iaases/CloudStackIaas.java ---------------------------------------------------------------------- diff --git a/components/org.apache.stratos.cloud.controller/src/main/java/org/apache/stratos/cloud/controller/iaases/CloudStackIaas.java b/components/org.apache.stratos.cloud.controller/src/main/java/org/apache/stratos/cloud/controller/iaases/CloudStackIaas.java new file mode 100644 index 0000000..c38ce0a --- /dev/null +++ b/components/org.apache.stratos.cloud.controller/src/main/java/org/apache/stratos/cloud/controller/iaases/CloudStackIaas.java @@ -0,0 +1,540 @@ +package org.apache.stratos.cloud.controller.iaases; + + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.stratos.cloud.controller.exception.CloudControllerException; +import org.apache.stratos.cloud.controller.exception.InvalidHostException; +import org.apache.stratos.cloud.controller.exception.InvalidRegionException; +import org.apache.stratos.cloud.controller.exception.InvalidZoneException; +import org.apache.stratos.cloud.controller.interfaces.Iaas; +import org.apache.stratos.cloud.controller.jcloud.ComputeServiceBuilderUtil; +import org.apache.stratos.cloud.controller.pojo.IaasProvider; +import org.apache.stratos.cloud.controller.util.CloudControllerConstants; +import org.apache.stratos.cloud.controller.validate.CloudStackPartitionValidator; +import org.apache.stratos.cloud.controller.validate.interfaces.PartitionValidator; +import org.jclouds.cloudstack.CloudStackApi; +import org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions; +import org.jclouds.cloudstack.domain.*; +import org.jclouds.cloudstack.features.VolumeApi; +import org.jclouds.cloudstack.options.ListPublicIPAddressesOptions; +import org.jclouds.cloudstack.options.ListZonesOptions; +import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.Template; +import org.jclouds.compute.domain.TemplateBuilder; +import org.jclouds.compute.options.TemplateOptions; +import org.jclouds.domain.Location; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeoutException; + +public class CloudStackIaas extends Iaas { + + private static final Log log = LogFactory.getLog(CloudStackIaas.class); + + public CloudStackIaas(IaasProvider iaasProvider) { + super(iaasProvider); + } + + @Override + public void buildComputeServiceAndTemplate() { + // builds and sets Compute Service + ComputeServiceBuilderUtil.buildDefaultComputeService(getIaasProvider()); + // builds and sets Template + buildTemplate(); + } + + @Override + public void buildTemplate() { + + IaasProvider iaasInfo = getIaasProvider(); + + //if compute service is not available + if (iaasInfo.getComputeService() == null) { + String msg = "Compute service is null for IaaS provider: " + + iaasInfo.getName(); + log.error(msg); + throw new CloudControllerException(msg); + } + + //create templateBuilder + TemplateBuilder templateBuilder = iaasInfo.getComputeService() + .templateBuilder(); + + //**SET PROPERTIES TO templateBuilder OBJECT**// + + /** + * PROPERTY - 1 + * set image id specified + */ + templateBuilder.imageId(iaasInfo.getImage()); + + /** + * PROPERTY-2 + * if user has specified a zone in cloud-controller.xml, set the zone into templateBuilder object + * (user should provide the zone id for this, because zone name is not unique in cloudstack) + */ + if (iaasInfo.getProperty(CloudControllerConstants.AVAILABILITY_ZONE) != null) { + Set<? extends Location> locations = iaasInfo.getComputeService().listAssignableLocations(); + for (Location location : locations) { + if (location.getId().equals(iaasInfo.getProperty(CloudControllerConstants.AVAILABILITY_ZONE))) { + //if the zone is valid set the zone to templateBuilder Object + templateBuilder.locationId(location.getId()); + log.info("Zone has been set as " + iaasInfo.getProperty(CloudControllerConstants.AVAILABILITY_ZONE) + + " with id: " + location.getId()); + break; + } + } + } + + /** + * PROPERTY-3 + * if user has specified an instance type in cloud-controller.xml, set the instance type into templateBuilder + * object.(service offering) + *Important:Specify the Service Offering type ID. Not the name. Because the name is not unique in cloudstack. + */ + if (iaasInfo.getProperty(CloudControllerConstants.INSTANCE_TYPE) != null) { + templateBuilder.hardwareId(iaasInfo.getProperty(CloudControllerConstants.INSTANCE_TYPE)); + } + + //build the template + Template template = templateBuilder.build(); + + /**if you wish to auto assign IPs, instance spawning call should be + * blocking, but if you + * wish to assign IPs manually, it can be non-blocking. + * is auto-assign-ip mode or manual-assign-ip mode? + */ + boolean blockUntilRunning = Boolean.parseBoolean(iaasInfo + .getProperty(CloudControllerConstants.AUTO_ASSIGN_IP)); + template.getOptions().as(TemplateOptions.class) + .blockUntilRunning(blockUntilRunning); + + // this is required in order to avoid creation of additional security + // groups by Jclouds. + template.getOptions().as(TemplateOptions.class) + .inboundPorts(new int[]{}); + + + //**SET CLOUDSTACK SPECIFIC PROPERTIES TO TEMPLATE OBJECT**// + + //set security group - If you are using basic zone + if (iaasInfo.getProperty(CloudControllerConstants.SECURITY_GROUP_IDS) != null) { + template.getOptions() + .as(CloudStackTemplateOptions.class) + .securityGroupIds(Arrays.asList(iaasInfo.getProperty(CloudControllerConstants.SECURITY_GROUP_IDS) + .split(CloudControllerConstants.ENTRY_SEPARATOR))); + } + + + /** + * set network ID - If you are using advanced zone + * in cloudstack sometimes we get unautorized exception if we didn't specify the + * domain ID and user name + */ + if (iaasInfo.getProperty(CloudControllerConstants.NETWORK_IDS) != null) { + template.getOptions() + .as(CloudStackTemplateOptions.class) + .networks(Arrays.asList(iaasInfo.getProperty(CloudControllerConstants.NETWORK_IDS) + .split(CloudControllerConstants.ENTRY_SEPARATOR))); + } + + //set user name + if (iaasInfo.getProperty(CloudControllerConstants.USER_NAME) != null) { + template.getOptions().as(CloudStackTemplateOptions.class) + .account(iaasInfo.getProperty(CloudControllerConstants.USER_NAME)); + } + //set domain ID + if (iaasInfo.getProperty(CloudControllerConstants.DOMAIN_ID) != null) { + template.getOptions().as(CloudStackTemplateOptions.class) + .domainId(iaasInfo.getProperty(CloudControllerConstants.DOMAIN_ID)); + } + + /** + *Set key pair + * in cloudstack sometimes we get unauthorized exception if we didn't specify the + * domain ID and user name + */ + if (iaasInfo.getProperty(CloudControllerConstants.KEY_PAIR) != null) { + template.getOptions().as(CloudStackTemplateOptions.class) + .keyPair(iaasInfo.getProperty(CloudControllerConstants.KEY_PAIR)); + } + + // ability to define tags + if (iaasInfo.getProperty(CloudControllerConstants.TAGS) != null) { + template.getOptions() + .as(CloudStackTemplateOptions.class) + .tags(Arrays.asList(iaasInfo.getProperty(CloudControllerConstants.TAGS) + .split(CloudControllerConstants.ENTRY_SEPARATOR))); + } + //set disk offering to the instance + if (iaasInfo.getProperty(CloudControllerConstants.DISK_OFFERING) != null) { + template.getOptions() + .as(CloudStackTemplateOptions.class) + .diskOfferingId(iaasInfo.getProperty(CloudControllerConstants.DISK_OFFERING)); + } + + // set Template + iaasInfo.setTemplate(template); + } + + @Override + public void setDynamicPayload() { + IaasProvider iaasInfo = getIaasProvider(); + if (iaasInfo.getTemplate() != null && iaasInfo.getPayload() != null) { + iaasInfo.getTemplate().getOptions().as(CloudStackTemplateOptions.class) + .userMetadata(convertByteArrayToHashMap(iaasInfo.getPayload())); + } + } + + /** + * IMPORTANT + * In cloudstack we can assign public IPs, if we are using an advanced zone only. If we are using a basic zone + * we cannot assign public ips. + * <p/> + * When we use an advanced zone, a public IP address will get automatically assigned to the vm. So we don't need + * to find an unallocated IP address and assign that address to the vm. If you are using a basic zone you cannot + * assign public IPs + * <p/> + * So this method will find the IP that has been assigned to the vm and return it. + */ + @Override + public String associateAddress(NodeMetadata node) { + + IaasProvider iaasInfo = getIaasProvider(); + ComputeServiceContext context = iaasInfo.getComputeService().getContext(); + CloudStackApi cloudStackApi = context.unwrapApi(CloudStackApi.class); + String ip = null; + + // get all allocated IPs + ListPublicIPAddressesOptions listPublicIPAddressesOptions = new ListPublicIPAddressesOptions(); + listPublicIPAddressesOptions.zoneId(iaasInfo.getProperty(CloudControllerConstants.AVAILABILITY_ZONE)); + + Set<PublicIPAddress> publicIPAddresses = cloudStackApi.getAddressApi() + .listPublicIPAddresses(listPublicIPAddressesOptions); + + String id = node.getProviderId(); //vm ID + + for (PublicIPAddress publicIPAddress : publicIPAddresses) { + if (publicIPAddress.getVirtualMachineId().equals(id)) { //check whether this instance has + // already got an public ip or not + ip = publicIPAddress.getIPAddress(); //A public ip has been successfully assigned to the vm + log.info("Successfully associated an IP address " + ip + + " for node with id: " + node.getId()); + break; + } + + } + + if (ip == null || ip.isEmpty()) { //IP has not been successfully assigned to VM(That means there are + // no more IPs available for the VM) + String msg = "No address associated for node with id: " + node.getId(); + log.warn(msg); + throw new CloudControllerException(msg); + } + + return ip; + } + + @Override + public String associatePredefinedAddress(NodeMetadata node, String ip) { + return ""; + } + + @Override + public void releaseAddress(String ip) { + IaasProvider iaasInfo = getIaasProvider(); + ComputeServiceContext context = iaasInfo.getComputeService().getContext(); + CloudStackApi cloudStackApi = context.unwrapApi(CloudStackApi.class); + cloudStackApi.getAddressApi().disassociateIPAddress(ip); + } + + @Override + public boolean createKeyPairFromPublicKey(String region, String keyPairName, String publicKey) { + + IaasProvider iaasInfo = getIaasProvider(); + ComputeServiceContext context = iaasInfo.getComputeService().getContext(); + CloudStackApi cloudStackApi = context.unwrapApi(CloudStackApi.class); + SshKeyPair sshKeyPair = cloudStackApi.getSSHKeyPairApi().createSSHKeyPair(keyPairName); + + if (sshKeyPair != null) { + + iaasInfo.getTemplate().getOptions().as(CloudStackTemplateOptions.class) + .keyPair(sshKeyPair.getName()); + + log.info("A key-pair is created successfully - Key Pair Name: " + sshKeyPair.getName()); + return true; + } + log.error("Key-pair is unable to create"); + return false; + } + + @Override + public boolean isValidRegion(String region) throws InvalidRegionException { + + IaasProvider iaasInfo = getIaasProvider(); + //no such method in Jclouds cloudstack api + String msg = "Invalid region: " + region + " in the iaas: " + iaasInfo.getType(); + log.error(msg); + throw new InvalidRegionException(msg); + } + + @Override + public boolean isValidZone(String region, String zone) throws InvalidZoneException { + + IaasProvider iaasInfo = getIaasProvider(); + ComputeServiceContext context = iaasInfo.getComputeService().getContext(); + CloudStackApi cloudStackApi = context.unwrapApi(CloudStackApi.class); + ListZonesOptions listZonesOptions = new ListZonesOptions(); + listZonesOptions.available(true); + Set<Zone> zoneSet = cloudStackApi.getZoneApi().listZones(listZonesOptions); + + for (org.jclouds.cloudstack.domain.Zone configuredZone : zoneSet) { + if (configuredZone.getName().equalsIgnoreCase(zone)) { + return true; + } + } + String msg = "Invalid zone: " + zone + " in the iaas: " + iaasInfo.getType(); + log.error(msg); + throw new InvalidZoneException(msg); + } + + @Override + public boolean isValidHost(String zone, String host) throws InvalidHostException { + + IaasProvider iaasInfo = getIaasProvider(); + // there's no such method in jclouds cloustack api + String msg = "Invalid host: " + host + " in the zone: " + zone + " and of the iaas: " + iaasInfo.getType(); + log.error(msg); + throw new InvalidHostException(msg); + + } + + @Override + public PartitionValidator getPartitionValidator() { + return new CloudStackPartitionValidator(); + } + + @Override + public String createVolume(int sizeGB, String snapshotId) { + + //todo return volume ID if volume is created + IaasProvider iaasInfo = getIaasProvider(); + ComputeServiceContext context = iaasInfo.getComputeService() + .getContext(); + + String zone = ComputeServiceBuilderUtil.extractZone(iaasInfo); + String diskOfferingID = iaasInfo.getTemplate().getOptions().as(CloudStackTemplateOptions.class) + .getDiskOfferingId(); + if (zone == null && diskOfferingID == null) { + log.error("Could not create a volume in the , [zone] : " + zone + " of Iaas : " + iaasInfo); + return null; + } + + VolumeApi volumeApi = context.unwrapApi(CloudStackApi.class).getVolumeApi(); + + Volume volume; + if (StringUtils.isEmpty(snapshotId)) { + if (log.isInfoEnabled()) { + log.info("Creating a volume in the zone " + zone); + } + + //cloudstack jcloud api does not return a volume object + volumeApi.createVolumeFromCustomDiskOfferingInZone(null, diskOfferingID, zone, sizeGB); + + // volume = blockStoreApi.createVolumeInAvailabilityZone(zone, sizeGB); + } else { + if (log.isInfoEnabled()) { + log.info("Creating a volume in the zone " + zone + " from the snapshot " + snapshotId); + } + volumeApi.createVolumeFromSnapshotInZone(null, diskOfferingID, zone); + } + + return null; + } + + @Override + public String attachVolume(String instanceId, String volumeId, String deviceName) { + IaasProvider iaasInfo = getIaasProvider(); + ComputeServiceContext context = iaasInfo.getComputeService() + .getContext(); + CloudStackApi cloudStackApi = context.unwrapApi(CloudStackApi.class); + + //get volume + org.jclouds.cloudstack.domain.Volume volume = cloudStackApi.getVolumeApi().getVolume(volumeId); + + //get current volume state + Volume.State volumeState = volume.getState(); + + if (log.isDebugEnabled()) { + log.debug("Volume " + volumeId + " is in state " + volumeState); + } + + //if volume is not available, not allocated or cannot use + //volume state ALLOCATED means that volume has not been attached to any instance. + + //TODO there is an error with logic. + if (!(volumeState == Volume.State.ALLOCATED || volumeState == Volume.State.CREATING + || volumeState == Volume.State.READY)) { + log.error(String.format("Volume %s can not be attached. Volume status is %s", volumeId, volumeState)); + } + + //check whether the account of volume and instance is same + if (!volume.getAccount().equals(cloudStackApi.getVirtualMachineApi() + .getVirtualMachine(instanceId).getAccount())) { + log.error(String.format("Volume %s can not be attached. Instance account and Volume account " + + "are not the same ", volumeId)); + } + + boolean volumeBecameAvailable = false, volumeBecameAttached = false; + + try { + if (volumeState == Volume.State.CREATING) { + + volumeBecameAvailable = waitForStatus(volumeId, Volume.State.ALLOCATED, 5); + + } else if (volumeState == Volume.State.READY) { + volumeBecameAvailable = true; + } + + } catch (TimeoutException e) { + log.error("[Volume ID] " + volumeId + "did not become ALLOCATED within expected timeout"); + } + + //if volume state is 'ALLOCATED' + if (volumeBecameAvailable) { + + //attach volume into instance + cloudStackApi.getVolumeApi().attachVolume(volumeId, instanceId); + + try { + volumeBecameAttached = waitForStatus(volumeId, Volume.State.READY, 2); + } catch (TimeoutException e) { + log.error("[Volume ID] " + volumeId + "did not become READY within expected timeout"); + } + } + + try { + // waiting 5seconds till volumes are actually attached. + Thread.sleep(5000); + } catch (InterruptedException ignored) { + + } + + //If volume state is not 'READY' + if (!volumeBecameAttached) { + log.error(String.format("[Volume ID] %s attachment is called, but not yet became attached", volumeId)); + } + + log.info(String.format("Volume [id]: %s attachment for instance [id]: %s was successful [status]: Attaching." + + " of Iaas : %s", volumeId, instanceId, iaasInfo)); + + return "Attaching"; + + } + + @Override + public void detachVolume(String instanceId, String volumeId) { + + + IaasProvider iaasInfo = getIaasProvider(); + + ComputeServiceContext context = iaasInfo.getComputeService() + .getContext(); + + if (log.isDebugEnabled()) { + log.debug(String.format("Starting to detach volume %s from the instance %s", volumeId, instanceId)); + } + + CloudStackApi cloudStackApi = context.unwrapApi(CloudStackApi.class); + + cloudStackApi.getVolumeApi().detachVolume(volumeId); + + try { + //TODO this is true only for newly created volumes + if (waitForStatus(volumeId, Volume.State.ALLOCATED, 5)) { + log.info(String.format("Detachment of Volume [id]: %s from instance [id]: %s was successful of Iaas : %s", volumeId, instanceId, iaasInfo)); + } + } catch (TimeoutException e) { + log.error(String.format("Detachment of Volume [id]: %s from instance [id]: %s was unsuccessful. [volume Status] : %s", volumeId, instanceId, iaasInfo)); + } + + } + + @Override + public void deleteVolume(String volumeId) { + IaasProvider iaasInfo = getIaasProvider(); + ComputeServiceContext context = iaasInfo.getComputeService() + .getContext(); + CloudStackApi cloudStackApi = context.unwrapApi(CloudStackApi.class); + cloudStackApi.getVolumeApi().deleteVolume(volumeId); + log.info("Deletion of Volume [id]: " + volumeId + " was successful. " + + " of Iaas : " + iaasInfo); + } + + @Override + public String getIaasDevice(String device) {//todo implement this method(auto generated method) + return null; + } + + private boolean waitForStatus(String volumeId, Volume.State expectedStatus, int timeoutInMilliseconds) throws TimeoutException { + int timeout = 1000 * 60 * timeoutInMilliseconds; + long timout = System.currentTimeMillis() + timeout; + + IaasProvider iaasInfo = getIaasProvider(); + ComputeServiceContext context = iaasInfo.getComputeService().getContext(); + CloudStackApi cloudStackApi = context.unwrapApi(CloudStackApi.class); + + //get volume + org.jclouds.cloudstack.domain.Volume volume = cloudStackApi.getVolumeApi().getVolume(volumeId); + + Volume.State volumeState = volume.getState(); + + while (volumeState != expectedStatus) { + try { + if (log.isDebugEnabled()) { + log.debug(String.format("Volume %s is still NOT in %s. Current State=%s", volumeId, expectedStatus, volumeState)); + } + if (volumeState == Volume.State.FAILED || volumeState == Volume.State.DESTROYED || volumeState == Volume.State.UNRECOGNIZED) { + log.error("Volume " + volumeId + " is in state" + volumeState); + return false; + } + + Thread.sleep(1000); + volumeState = volume.getState(); + if (System.currentTimeMillis() > timout) { + throw new TimeoutException(); + } + } catch (InterruptedException e) { + // Ignoring the exception + } + } + if (log.isDebugEnabled()) { + log.debug(String.format("Volume %s status became %s", volumeId, expectedStatus)); + } + + return true; + } + + private Map<String, String> convertByteArrayToHashMap(byte[] byteArray) { + + Map<String, String> map = new HashMap<String, String>(); + + String stringFromByteArray = new String(byteArray); + String[] keyValuePairs = stringFromByteArray.split(","); + + for (String keyValuePair : keyValuePairs) { + String[] keyValue = keyValuePair.split("="); + if (keyValue.length > 1) { + map.put(keyValue[0], keyValue[1]); + } + } + + return map; + } +} http://git-wip-us.apache.org/repos/asf/stratos/blob/de05f5c5/components/org.apache.stratos.cloud.controller/src/main/java/org/apache/stratos/cloud/controller/validate/CloudStackPartitionValidator.java ---------------------------------------------------------------------- diff --git a/components/org.apache.stratos.cloud.controller/src/main/java/org/apache/stratos/cloud/controller/validate/CloudStackPartitionValidator.java b/components/org.apache.stratos.cloud.controller/src/main/java/org/apache/stratos/cloud/controller/validate/CloudStackPartitionValidator.java new file mode 100644 index 0000000..4f3fbce --- /dev/null +++ b/components/org.apache.stratos.cloud.controller/src/main/java/org/apache/stratos/cloud/controller/validate/CloudStackPartitionValidator.java @@ -0,0 +1,53 @@ +package org.apache.stratos.cloud.controller.validate; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.stratos.cloud.controller.exception.InvalidPartitionException; +import org.apache.stratos.cloud.controller.interfaces.Iaas; +import org.apache.stratos.cloud.controller.pojo.IaasProvider; +import org.apache.stratos.cloud.controller.util.CloudControllerConstants; +import org.apache.stratos.cloud.controller.util.CloudControllerUtil; +import org.apache.stratos.cloud.controller.validate.interfaces.PartitionValidator; +import org.apache.stratos.messaging.domain.topology.Scope; + +import java.util.Properties; + + +public class CloudStackPartitionValidator implements PartitionValidator { + + + private static final Log log = LogFactory.getLog(AWSEC2PartitionValidator.class); + private IaasProvider iaasProvider; + private Iaas iaas; + + + @Override + public void setIaasProvider(IaasProvider iaas) { + this.iaasProvider = iaas; + this.iaas = iaas.getIaas(); + } + + @Override + public IaasProvider validate(String partitionId, Properties properties) throws InvalidPartitionException { + + try { + IaasProvider updatedIaasProvider = new IaasProvider(iaasProvider); + Iaas updatedIaas = CloudControllerUtil.getIaas(updatedIaasProvider); + updatedIaas.setIaasProvider(updatedIaasProvider); + + if (properties.containsKey(Scope.zone.toString())) { + String zone = properties.getProperty(Scope.zone.toString()); + iaas.isValidZone(null, zone); + updatedIaasProvider.setProperty(CloudControllerConstants.AVAILABILITY_ZONE, zone); + updatedIaas = CloudControllerUtil.getIaas(updatedIaasProvider); + updatedIaas.setIaasProvider(updatedIaasProvider); + } + + } catch (Exception ex) { + String msg = "Invalid Partition Detected : "+partitionId+". Cause: "+ex.getMessage(); + log.error(msg, ex); + throw new InvalidPartitionException(msg, ex); + } + return iaasProvider; + } +}
