http://git-wip-us.apache.org/repos/asf/stratos/blob/0984bcef/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..a67fe8d --- /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.util.ComputeServiceBuilderUtil; +import org.apache.stratos.cloud.controller.domain.IaasProvider; +import org.apache.stratos.cloud.controller.util.CloudControllerConstants; +import org.apache.stratos.cloud.controller.iaases.validators.CloudstackPartitionValidator; +import org.apache.stratos.cloud.controller.iaases.validators.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) { + + // Snapshot id is not there in IaaS.createVolume() method in stratos 4.0.0 + //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; + } +} \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/stratos/blob/0984bcef/components/org.apache.stratos.cloud.controller/src/main/java/org/apache/stratos/cloud/controller/iaases/DockerIaas.java ---------------------------------------------------------------------- diff --git a/components/org.apache.stratos.cloud.controller/src/main/java/org/apache/stratos/cloud/controller/iaases/DockerIaas.java b/components/org.apache.stratos.cloud.controller/src/main/java/org/apache/stratos/cloud/controller/iaases/DockerIaas.java new file mode 100644 index 0000000..da2916b --- /dev/null +++ b/components/org.apache.stratos.cloud.controller/src/main/java/org/apache/stratos/cloud/controller/iaases/DockerIaas.java @@ -0,0 +1,151 @@ +/* + * 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 org.apache.stratos.cloud.controller.iaases; + +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.util.ComputeServiceBuilderUtil; +import org.apache.stratos.cloud.controller.domain.IaasProvider; +import org.apache.stratos.cloud.controller.iaases.validators.DockerPartitionValidator; +import org.apache.stratos.cloud.controller.iaases.validators.PartitionValidator; +import org.jclouds.compute.ComputeService; +import org.jclouds.compute.domain.Image; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.Template; + +import java.util.Set; + +/** + * Docker iaas provider definition. + */ +public class DockerIaas extends Iaas { + + private static final Log log = LogFactory.getLog(AWSEC2Iaas.class); + + public DockerIaas(IaasProvider iaasProvider) { + super(iaasProvider); + } + + @Override + public void buildComputeServiceAndTemplate() { + // builds and sets Compute Service + ComputeServiceBuilderUtil.buildDefaultComputeService(getIaasProvider()); + + // builds and sets Template + buildTemplate(); + } + + @Override + public void setDynamicPayload() { + log.warn("Not implemented: DockerIaas.setDynamicPayload()"); + } + + @Override + public String associateAddress(NodeMetadata node) { + log.warn("Not implemented: DockerIaas.associateAddress()"); + return null; + } + + @Override + public String associatePredefinedAddress(NodeMetadata node, String ip) { + log.warn("Not implemented: DockerIaas.associatePredefinedAddress()"); + return null; + } + + @Override + public void releaseAddress(String ip) { + log.warn("Not implemented: DockerIaas.releaseAddress()"); + } + + @Override + public boolean createKeyPairFromPublicKey(String region, String keyPairName, String publicKey) { + return false; + } + + @Override + public boolean isValidRegion(String region) throws InvalidRegionException { + return true; + } + + @Override + public boolean isValidZone(String region, String zone) throws InvalidZoneException { + return true; + } + + @Override + public boolean isValidHost(String zone, String host) throws InvalidHostException { + return true; + } + + @Override + public PartitionValidator getPartitionValidator() { + return new DockerPartitionValidator(); + } + + @Override + public void buildTemplate() { + IaasProvider iaasProvider = getIaasProvider(); + ComputeService computeService = iaasProvider.getComputeService(); + Set<? extends Image> images = computeService.listImages(); + Image image = findImage(images, iaasProvider.getImage()); + if(image == null) { + throw new CloudControllerException(String.format("Docker image not found: %s", iaasProvider.getImage())); + } + Template template = computeService.templateBuilder().fromImage(image).build(); + iaasProvider.setTemplate(template); + } + + private Image findImage(Set<? extends Image> images, String name) { + for(Image image : images) { + if(image.getDescription().contains(name)) + return image; + } + return null; + } + + @Override + public String createVolume(int sizeGB, String snapshotId) { + return null; + } + + @Override + public String attachVolume(String instanceId, String volumeId, String deviceName) { + return null; + } + + @Override + public void detachVolume(String instanceId, String volumeId) { + + } + + @Override + public void deleteVolume(String volumeId) { + + } + + @Override + public String getIaasDevice(String device) { + return null; + } +} http://git-wip-us.apache.org/repos/asf/stratos/blob/0984bcef/components/org.apache.stratos.cloud.controller/src/main/java/org/apache/stratos/cloud/controller/iaases/GCEIaas.java ---------------------------------------------------------------------- diff --git a/components/org.apache.stratos.cloud.controller/src/main/java/org/apache/stratos/cloud/controller/iaases/GCEIaas.java b/components/org.apache.stratos.cloud.controller/src/main/java/org/apache/stratos/cloud/controller/iaases/GCEIaas.java new file mode 100644 index 0000000..ba186b2 --- /dev/null +++ b/components/org.apache.stratos.cloud.controller/src/main/java/org/apache/stratos/cloud/controller/iaases/GCEIaas.java @@ -0,0 +1,459 @@ +/* + * 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 org.apache.stratos.cloud.controller.iaases; + +import java.util.*; + +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.util.ComputeServiceBuilderUtil; +import org.apache.stratos.cloud.controller.domain.IaasProvider; +import org.apache.stratos.cloud.controller.domain.NetworkInterface; +import org.apache.stratos.cloud.controller.iaases.validators.GCEPartitionValidator; +import org.apache.stratos.cloud.controller.iaases.validators.PartitionValidator; +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.jclouds.ContextBuilder; +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 org.apache.stratos.cloud.controller.util.CloudControllerConstants; +import org.jclouds.collect.IterableWithMarker; +import org.jclouds.googlecomputeengine.GoogleComputeEngineApi; +import org.jclouds.googlecomputeengine.features.DiskApi; +import org.jclouds.googlecomputeengine.features.InstanceApi; +import org.jclouds.googlecomputeengine.features.RegionApi; +import org.jclouds.googlecomputeengine.features.ZoneApi; +import org.jclouds.googlecomputeengine.domain.Disk; +import org.jclouds.googlecomputeengine.domain.Instance; +import org.jclouds.googlecomputeengine.domain.Region; +import org.jclouds.googlecomputeengine.domain.Zone; +import org.jclouds.googlecomputeengine.domain.Operation; +import org.jclouds.googlecomputeengine.options.AttachDiskOptions; +import org.jclouds.googlecomputeengine.options.AttachDiskOptions.DiskType; + +import com.google.inject.Key; +import com.google.inject.Injector; +import com.google.inject.TypeLiteral; +import com.google.inject.name.Names; + +import static org.jclouds.util.Predicates2.retry; +import static java.util.concurrent.TimeUnit.SECONDS; + +import com.google.common.base.Predicate; + +import java.util.concurrent.atomic.AtomicReference; + +import com.google.common.util.concurrent.Atomics; + +public class GCEIaas extends Iaas { + + + private static final Log log = LogFactory.getLog(GCEIaas.class); + + private static final String PROJECTNAME = "projectName"; + + public GCEIaas(IaasProvider iaasProvider) { + super(iaasProvider); + } + + @Override + public void buildComputeServiceAndTemplate() { + + IaasProvider iaasInfo = getIaasProvider(); + + // builds and sets Compute Service + ComputeServiceBuilderUtil.buildDefaultComputeService(iaasInfo); + + + // builds and sets Template + buildTemplate(); + + } + + public void buildTemplate() { + IaasProvider iaasInfo = getIaasProvider(); + + if (iaasInfo.getComputeService() == null) { + String msg = "Compute service is null for IaaS provider: " + + iaasInfo.getName(); + log.fatal(msg); + throw new CloudControllerException(msg); + } + + log.info("gce buildTemplate"); + + TemplateBuilder templateBuilder = iaasInfo.getComputeService() + .templateBuilder(); + + // set image id specified + templateBuilder.imageId(iaasInfo.getImage()); + + String zone = iaasInfo.getProperty(CloudControllerConstants.AVAILABILITY_ZONE); + if(zone != null) { + Set<? extends Location> locations = iaasInfo.getComputeService().listAssignableLocations(); + for(Location location : locations) { + if(location.getScope().toString().equalsIgnoreCase(CloudControllerConstants.ZONE_ELEMENT) && + location.getId().equals(zone)) { + templateBuilder.locationId(location.getId()); + log.info("ZONE has been set as " + zone + + " with id: " + location.getId()); + break; + } + } + } + + if (iaasInfo.getProperty(CloudControllerConstants.INSTANCE_TYPE) != null) { + // set instance type eg: m1.large + templateBuilder.hardwareId(iaasInfo.getProperty(CloudControllerConstants.INSTANCE_TYPE)); + } + + // build the Template + Template template = templateBuilder.build(); + + if(zone != null) { + if(!template.getLocation().getId().equals(zone)) { + log.warn("couldn't find assignable ZONE of id :" + zone + + " in the IaaS. Hence using the default location as " + template.getLocation().getScope().toString() + + " with the id " + template.getLocation().getId()); + } + } + + // 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? - default mode is + // non-blocking + boolean blockUntilRunning = Boolean.parseBoolean(iaasInfo + .getProperty("autoAssignIp")); + 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(22, 80, 8080, 443, 8243); + + if (zone != null) { + templateBuilder.locationId(zone); + log.debug("setting location to " + zone); + } + + // ability to define tags with Key-value pairs + Map<String, String> keyValuePairTagsMap = new HashMap<String, String>(); + + for (String propertyKey : iaasInfo.getProperties().keySet()){ + if(propertyKey.startsWith(CloudControllerConstants.TAGS_AS_KEY_VALUE_PAIRS_PREFIX)) { + keyValuePairTagsMap.put(propertyKey.substring(CloudControllerConstants.TAGS_AS_KEY_VALUE_PAIRS_PREFIX.length()), + iaasInfo.getProperties().get(propertyKey)); + template.getOptions() + .userMetadata(keyValuePairTagsMap); + } + log.info("usermeta data key:"+ propertyKey + " value: " + iaasInfo.getProperties().get(propertyKey)); + } + + if (iaasInfo.getNetworkInterfaces() != null) { + List<String> networks = new ArrayList<String>(iaasInfo.getNetworkInterfaces().length); + for (NetworkInterface ni:iaasInfo.getNetworkInterfaces()) { + networks.add(ni.getNetworkUuid()); + log.info("using network interface " + ni.getNetworkUuid()); + } + template.getOptions().as(TemplateOptions.class).networks(networks); + log.info("using network interface " + networks); + } + + // set Template + iaasInfo.setTemplate(template); + } + + @Override + public void setDynamicPayload() { + // in vCloud case we need to run a script + IaasProvider iaasInfo = getIaasProvider(); + + if (iaasInfo.getTemplate() == null || iaasInfo.getPayload() == null) { + if (log.isDebugEnabled()) { + log.debug("Payload for GCE not found"); + } + return; + } + + // Payload is a String value + String payload = new String(iaasInfo.getPayload()); + + log.info("setDynamicPayload " + payload); + + Map<String, String> keyValuePairTagsMap = new HashMap<String, String>(); + keyValuePairTagsMap.put("stratos_usermetadata", payload); + iaasInfo.getTemplate().getOptions().userMetadata(keyValuePairTagsMap); + } + + @Override + public boolean createKeyPairFromPublicKey(String region, String keyPairName, String publicKey) { + + // Not applicable for GCE - Not called by stratos cloud controller as well + return false; + } + + @Override + public String associateAddress(NodeMetadata node) { + + // TODO + return ""; + + } + + @Override + public String associatePredefinedAddress(NodeMetadata node, String ip) { + return ""; + } + + @Override + public void releaseAddress(String ip) { + // TODO + } + + @Override + public boolean isValidRegion(String region) throws InvalidRegionException { + IaasProvider iaasInfo = getIaasProvider(); + + if (region == null || iaasInfo == null) { + String msg = "Region or IaaSProvider is null: region: " + region + " - IaaSProvider: " + iaasInfo; + log.error(msg); + throw new InvalidRegionException(msg); + } + + GoogleComputeEngineApi api = getGCEApi(); + RegionApi regionApi = api.getRegionApiForProject(iaasInfo.getProperty(PROJECTNAME)); + + for(IterableWithMarker<Region> page : regionApi.list()) { + for(Region r : page) { + if (region.equalsIgnoreCase(r.getName())) { + log.debug("Found a matching region: " + region); + return true; + } + } + } + + 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(); + + if (zone == null || iaasInfo == null) { + String msg = "Zone or IaaSProvider is null: region: " + region + + " zone: " + zone + " - IaaSProvider: " + iaasInfo; + log.error(msg); + throw new InvalidZoneException(msg); + } + + GoogleComputeEngineApi api = getGCEApi(); + ZoneApi zoneApi = api.getZoneApiForProject(iaasInfo.getProperty(PROJECTNAME)); + + for(IterableWithMarker<Zone> page : zoneApi.list()) { + for(Zone z : page) { + if (zone.equalsIgnoreCase(z.getName())) { + log.debug("Found a matching zone: " + zone); + return true; + } + } + } + + String msg = "Invalid zone: " + zone + " in the region: " + region + " and of the iaas: " + iaasInfo.getType(); + log.error(msg); + throw new InvalidZoneException(msg); + } + + @Override + public boolean isValidHost(String zone, String host) throws InvalidHostException { + IaasProvider iaasInfo = getIaasProvider(); + + // Not called by cloud controller + // there's no such concept in GCE + + 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 GCEPartitionValidator(); + } + + @Override + public String createVolume(int sizeGB, String snapshotId) { + // generate a random diskname + Random rand = new Random(); + String diskName = "stratos-disk-" + rand.nextInt(100000); + DiskApi diskApi = getGCEDiskApi(); + String zone = getZone(); + + log.debug("Creating volume: " + diskName + " in zone: " + zone + " of size: " + sizeGB); + + Operation oper = diskApi.createInZone(diskName, sizeGB, zone); + + oper = waitGCEOperationDone(oper); + if (oper.getStatus() != Operation.Status.DONE) { + log.error("Failed to create volume: " + diskName + " of size: " + sizeGB + + " in zone: " + zone + " operation: " + oper); + return null; + } + + return diskName; + } + + @Override + public String attachVolume(String instanceId, String volumeId, String deviceName) { + DiskApi diskApi = getGCEDiskApi(); + InstanceApi instApi = getGCEInstanceApi(); + String zone = getZone(); + + log.debug("Trying to attach volume: " + volumeId + " to instance: " + instanceId + + " in zone: " + zone + " at devicename: " + deviceName); + + Disk disk = diskApi.getInZone(zone, volumeId); + if (disk == null) { + log.error("Failed to get volume: " + volumeId + " in zone: " + zone); + return null; + } + + log.debug("Found volumeId: " + volumeId + " volume: " + disk); + + Operation oper = instApi.attachDiskInZone(zone, instanceId, + new AttachDiskOptions().type(DiskType.PERSISTENT) + .source(disk.getSelfLink()) + .mode(AttachDiskOptions.DiskMode.READ_WRITE) + .deviceName(deviceName)); + oper = waitGCEOperationDone(oper); + if (oper.getStatus() != Operation.Status.DONE) { + log.error("Failed to attach volume: " + volumeId + " to instance: " + instanceId + + " in zone: " + zone + " at device: " + deviceName + " operation: " + oper); + return null; + } + + return volumeId; + } + + @Override + public void detachVolume(String instanceId, String volumeId) { + InstanceApi instApi = getGCEInstanceApi(); + String zone = getZone(); + Instance inst = instApi.getInZone(zone, instanceId); + + log.debug("Trying to detach volume: " + volumeId + " from instance: " + instanceId + + " " + inst + " in zone: " + zone); + + if (inst == null) { + log.error("Failed to find instance: " + instanceId + " in zone: " + zone); + return; + } + + for(Instance.AttachedDisk disk : inst.getDisks()) { + Instance.PersistentAttachedDisk persistentDisk = (Instance.PersistentAttachedDisk)disk; + + log.debug("Found disk - src: " + persistentDisk.getSourceDiskName() + + " devicename: " + persistentDisk.getDeviceName()); + + if (persistentDisk.getSourceDiskName().equals(volumeId)) { + Operation oper = instApi.detachDiskInZone(zone, instanceId, persistentDisk.getDeviceName().get()); + oper = waitGCEOperationDone(oper); + if (oper.getStatus() != Operation.Status.DONE) { + log.error("Failed to detach volume: " + volumeId + " to instance: " + instanceId + + " in zone: " + zone + " at device: " + persistentDisk.getDeviceName() + + " result operation: " + oper); + } + return; + } + } + + log.error("Cannot find volume: " + volumeId + " in instance: " + instanceId); + } + + @Override + public void deleteVolume(String volumeId) { + DiskApi diskApi = getGCEDiskApi(); + String zone = getZone(); + + log.debug("Deleting volume: " + volumeId + " in zone: " + zone); + + Operation oper = diskApi.deleteInZone(zone, volumeId); + + oper = waitGCEOperationDone(oper); + if (oper.getStatus() != Operation.Status.DONE) { + log.error("Failed to delete volume: " + volumeId + " in zone: " + zone + + " operation: " + oper); + } + } + + @Override + public String getIaasDevice(String device) { + return device; + } + + private String getZone() { + IaasProvider iaasInfo = getIaasProvider(); + return iaasInfo.getProperty(CloudControllerConstants.AVAILABILITY_ZONE); + } + + private GoogleComputeEngineApi getGCEApi() { + IaasProvider iaasInfo = getIaasProvider(); + ComputeServiceContext context = iaasInfo.getComputeService().getContext(); + GoogleComputeEngineApi api = context.unwrapApi(GoogleComputeEngineApi.class); + + return api; + } + + private DiskApi getGCEDiskApi() { + IaasProvider iaasInfo = getIaasProvider(); + String projectName = iaasInfo.getProperty(PROJECTNAME); + return getGCEApi().getDiskApiForProject(projectName); + } + + private InstanceApi getGCEInstanceApi() { + IaasProvider iaasInfo = getIaasProvider(); + String projectName = iaasInfo.getProperty(PROJECTNAME); + return getGCEApi().getInstanceApiForProject(projectName); + } + + private Operation waitGCEOperationDone(Operation operation) { + int maxWaitTime = 15; // 15 seconds + IaasProvider iaasInfo = getIaasProvider(); + Injector injector = ContextBuilder.newBuilder(iaasInfo.getProvider()) + .credentials(iaasInfo.getIdentity(), iaasInfo.getCredential()) + .buildInjector(); + Predicate<AtomicReference<Operation>> zoneOperationDonePredicate = + injector.getInstance(Key.get(new TypeLiteral<Predicate<AtomicReference<Operation>>>() { + }, Names.named("zone"))); + AtomicReference<Operation> operationReference = Atomics.newReference(operation); + retry(zoneOperationDonePredicate, maxWaitTime, 1, SECONDS).apply(operationReference); + + return operationReference.get(); + } +} + http://git-wip-us.apache.org/repos/asf/stratos/blob/0984bcef/components/org.apache.stratos.cloud.controller/src/main/java/org/apache/stratos/cloud/controller/iaases/Iaas.java ---------------------------------------------------------------------- diff --git a/components/org.apache.stratos.cloud.controller/src/main/java/org/apache/stratos/cloud/controller/iaases/Iaas.java b/components/org.apache.stratos.cloud.controller/src/main/java/org/apache/stratos/cloud/controller/iaases/Iaas.java new file mode 100644 index 0000000..5faeca1 --- /dev/null +++ b/components/org.apache.stratos.cloud.controller/src/main/java/org/apache/stratos/cloud/controller/iaases/Iaas.java @@ -0,0 +1,173 @@ +/* + * 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 org.apache.stratos.cloud.controller.iaases; + +import org.jclouds.compute.ComputeService; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.Template; +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.domain.IaasProvider; +import org.apache.stratos.cloud.controller.iaases.validators.PartitionValidator; + +/** + * All IaaSes that are going to support by Cloud Controller, should extend this abstract class. + */ +public abstract class Iaas { + /** + * Reference to the corresponding {@link IaasProvider} + */ + private IaasProvider iaasProvider; + + public Iaas(IaasProvider iaasProvider) { + this.setIaasProvider(iaasProvider); + } + + public IaasProvider getIaasProvider() { + return iaasProvider; + } + + public void setIaasProvider(IaasProvider iaasProvider) { + this.iaasProvider = iaasProvider; + } + + /** + * This should build the {@link ComputeService} object and the {@link Template} object, + * using the information from {@link IaasProvider} and should set the built + * {@link ComputeService} object in the {@link IaasProvider#setComputeService(ComputeService)} + * and also should set the built {@link Template} object in the + * {@link IaasProvider#setTemplate(Template)}. + */ + public abstract void buildComputeServiceAndTemplate(); + + /** + * This method provides a way to set payload that can be obtained from {@link IaasProvider#getPayload()} + * in the {@link Template} of this IaaS. + */ + public abstract void setDynamicPayload(); + + /** + * This will obtain an IP address from the allocated list and associate that IP with this node. + * @param node Node to be associated with an IP. + * @return associated public IP. + */ + public abstract String associateAddress(NodeMetadata node); + + /** + * This will obtain a predefined IP address and associate that IP with this node, if ip is already in use allocate ip from pool + * (through associateAddress()) + * @param node Node to be associated with an IP. + * @ip preallocated floating Ip + * @return associated public IP. + */ + public abstract String associatePredefinedAddress(NodeMetadata node, String ip); + + /** + * This will deallocate/release the given IP address back to pool. + * @param iaasInfo corresponding {@link IaasProvider} + * @param ip public IP address to be released. + */ + public abstract void releaseAddress(String ip); + + /** + * This method should create a Key Pair corresponds to a given public key in the respective region having the name given. + * Also should override the value of the key pair in the {@link Template} of this IaaS. + * @param region region that the key pair will get created. + * @param keyPairName name of the key pair. NOTE: Jclouds adds a prefix : <code>jclouds#</code> + * @param publicKey public key, from which the key pair will be created. + * @return whether the key pair creation is successful or not. + */ + public abstract boolean createKeyPairFromPublicKey(String region, String keyPairName, String publicKey); + + /** + * Validate a given region name against a particular IaaS. + * If a particular IaaS doesn't have a concept called region, it can simply throw {@link InvalidRegionException}. + * @param region name of the region. + * @return whether the region is valid. + * @throws InvalidRegionException if the region is invalid. + */ + public abstract boolean isValidRegion(String region) throws InvalidRegionException; + + /** + * Validate a given zone name against a particular region in an IaaS. + * If a particular IaaS doesn't have a concept called zone, it can simply throw {@link InvalidZoneException}. + * @param region region of the IaaS that the zone belongs to. + * @param zone + * @return whether the zone is valid in the given region or not. + * @throws InvalidZoneException if the zone is invalid in a given region. + */ + public abstract boolean isValidZone(String region, String zone) throws InvalidZoneException, InvalidRegionException; + + /** + * Validate a given host id against a particular zone in an IaaS. + * If a particular IaaS doesn't have a concept called hosts, it can simply throw {@link InvalidHostException}. + * @param zone zone of the IaaS that the host belongs to. + * @param host + * @return whether the host is valid in the given zone or not. + * @throws InvalidHostException if the host is invalid in a given zone. + */ + public abstract boolean isValidHost(String zone, String host) throws InvalidHostException; + + /** + * provides the {@link PartitionValidator} corresponds to this particular IaaS. + * @return {@link PartitionValidator} + */ + public abstract PartitionValidator getPartitionValidator(); + + /** + * Builds only the jclouds {@link Template} + */ + public abstract void buildTemplate(); + + /** + * Create a new volume in the respective Iaas. + * @param sizeGB size of the volume in Giga Bytes. + * @return Id of the created volume. + */ + public abstract String createVolume(int sizeGB, String snapshotId); + + /** + * Attach a given volume to an instance at the specified device path. + * @param instanceId of the instance. + * @param volumeId volume id of the volume to be attached. + * @param deviceName name of the device that the volume would bind to. + * @return the status of the attachment. + */ + public abstract String attachVolume(String instanceId, String volumeId, String deviceName); + + /** + * Detach a given volume from the given instance. + * @param instanceId of the instance. + * @param volumeId volume id of the volume to be detached. + */ + public abstract void detachVolume(String instanceId, String volumeId); + + /** + * Delete a given volume. + * @param volumeId volume id of the volume to be detached. + */ + public abstract void deleteVolume(String volumeId); + + /** + * This returns the device of the volume specified by the user. This is depends on IAAS. + * For an instance /dev/sdf maps to /dev/xvdf in EC2. + */ + public abstract String getIaasDevice(String device); +} http://git-wip-us.apache.org/repos/asf/stratos/blob/0984bcef/components/org.apache.stratos.cloud.controller/src/main/java/org/apache/stratos/cloud/controller/iaases/OpenstackNovaIaas.java ---------------------------------------------------------------------- diff --git a/components/org.apache.stratos.cloud.controller/src/main/java/org/apache/stratos/cloud/controller/iaases/OpenstackNovaIaas.java b/components/org.apache.stratos.cloud.controller/src/main/java/org/apache/stratos/cloud/controller/iaases/OpenstackNovaIaas.java new file mode 100644 index 0000000..46edca8 --- /dev/null +++ b/components/org.apache.stratos.cloud.controller/src/main/java/org/apache/stratos/cloud/controller/iaases/OpenstackNovaIaas.java @@ -0,0 +1,740 @@ +/* + * 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 org.apache.stratos.cloud.controller.iaases; + +import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +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.util.ComputeServiceBuilderUtil; +import org.apache.stratos.cloud.controller.domain.IaasProvider; +import org.apache.stratos.cloud.controller.domain.NetworkInterface; +import org.apache.stratos.cloud.controller.util.CloudControllerConstants; +import org.apache.stratos.cloud.controller.util.CloudControllerUtil; +import org.apache.stratos.cloud.controller.iaases.validators.OpenstackNovaPartitionValidator; +import org.apache.stratos.cloud.controller.iaases.validators.PartitionValidator; +import org.jclouds.compute.ComputeServiceContext; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.NodeMetadataBuilder; +import org.jclouds.compute.domain.Template; +import org.jclouds.compute.domain.TemplateBuilder; +import org.jclouds.compute.options.TemplateOptions; +import org.jclouds.openstack.nova.v2_0.NovaApi; +import org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions; +import org.jclouds.openstack.nova.v2_0.domain.FloatingIP; +import org.jclouds.openstack.nova.v2_0.domain.HostAggregate; +import org.jclouds.openstack.nova.v2_0.domain.KeyPair; +import org.jclouds.openstack.nova.v2_0.domain.Network; +import org.jclouds.openstack.nova.v2_0.domain.Volume; +import org.jclouds.openstack.nova.v2_0.domain.VolumeAttachment; +import org.jclouds.openstack.nova.v2_0.domain.zonescoped.AvailabilityZone; +import org.jclouds.openstack.nova.v2_0.extensions.AvailabilityZoneApi; +import org.jclouds.openstack.nova.v2_0.extensions.FloatingIPApi; +import org.jclouds.openstack.nova.v2_0.extensions.HostAggregateApi; +import org.jclouds.openstack.nova.v2_0.extensions.KeyPairApi; +import org.jclouds.openstack.nova.v2_0.extensions.VolumeApi; +import org.jclouds.openstack.nova.v2_0.extensions.VolumeAttachmentApi; +import org.jclouds.openstack.nova.v2_0.options.CreateVolumeOptions; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.concurrent.TimeoutException; + +public class OpenstackNovaIaas extends Iaas { + + private static final Log log = LogFactory.getLog(OpenstackNovaIaas.class); + private static final String SUCCESSFUL_LOG_LINE = "A key-pair is created successfully in "; + private static final String FAILED_LOG_LINE = "Key-pair is unable to create in "; + + public OpenstackNovaIaas(IaasProvider iaasProvider) { + super(iaasProvider); + } + + @Override + public void buildComputeServiceAndTemplate() { + + IaasProvider iaasInfo = getIaasProvider(); + + // builds and sets Compute Service + ComputeServiceBuilderUtil.buildDefaultComputeService(iaasInfo); + + // builds and sets Template + buildTemplate(); + + } + + public void buildTemplate() { + IaasProvider iaasInfo = getIaasProvider(); + + if (iaasInfo.getComputeService() == null) { + throw new CloudControllerException( + "Compute service is null for IaaS provider: " + + iaasInfo.getName()); + } + + TemplateBuilder templateBuilder = iaasInfo.getComputeService() + .templateBuilder(); + templateBuilder.imageId(iaasInfo.getImage()); + if(!(iaasInfo instanceof IaasProvider)) { + templateBuilder.locationId(iaasInfo.getType()); + } + + // to avoid creation of template objects in each and every time, we + // create all at once! + + String instanceType; + + // set instance type + if (((instanceType = iaasInfo.getProperty(CloudControllerConstants.INSTANCE_TYPE)) != null)) { + + templateBuilder.hardwareId(instanceType); + } + + Template template = templateBuilder.build(); + + // In Openstack the call to IaaS should be blocking, in order to retrieve + // IP addresses. + boolean blockUntilRunning = true; + if(iaasInfo.getProperty(CloudControllerConstants.BLOCK_UNTIL_RUNNING) != null) { + blockUntilRunning = Boolean.parseBoolean(iaasInfo.getProperty( + CloudControllerConstants.BLOCK_UNTIL_RUNNING)); + } + 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[] {}); + + if (iaasInfo.getProperty(CloudControllerConstants.SECURITY_GROUPS) != null) { + template.getOptions() + .as(NovaTemplateOptions.class) + .securityGroupNames( + iaasInfo.getProperty(CloudControllerConstants.SECURITY_GROUPS).split( + CloudControllerConstants.ENTRY_SEPARATOR)); + } + + if (iaasInfo.getProperty(CloudControllerConstants.KEY_PAIR) != null) { + template.getOptions().as(NovaTemplateOptions.class) + .keyPairName(iaasInfo.getProperty(CloudControllerConstants.KEY_PAIR)); + } + + if (iaasInfo.getNetworkInterfaces() != null) { + Set<Network> novaNetworksSet = new LinkedHashSet<Network>(iaasInfo.getNetworkInterfaces().length); + for (NetworkInterface ni:iaasInfo.getNetworkInterfaces()) { + novaNetworksSet.add(Network.builder().networkUuid(ni.getNetworkUuid()).fixedIp(ni.getFixedIp()) + .portUuid(ni.getPortUuid()).build()); + } + template.getOptions().as(NovaTemplateOptions.class).novaNetworks(novaNetworksSet); + } + + if (iaasInfo.getProperty(CloudControllerConstants.AVAILABILITY_ZONE) != null) { + template.getOptions().as(NovaTemplateOptions.class) + .availabilityZone(iaasInfo.getProperty(CloudControllerConstants.AVAILABILITY_ZONE)); + } + + //TODO +// if (iaas.getProperty(CloudControllerConstants.HOST) != null) { +// template.getOptions().as(NovaTemplateOptions.class) +// .(CloudControllerConstants.HOST); +// } + + // set Template + iaasInfo.setTemplate(template); + } + + @Override + public void setDynamicPayload() { + + IaasProvider iaasInfo = getIaasProvider(); + + if (iaasInfo.getTemplate() != null && iaasInfo.getPayload() != null) { + + iaasInfo.getTemplate().getOptions().as(NovaTemplateOptions.class) + .userData(iaasInfo.getPayload()); + } + + } + + @Override + public synchronized boolean createKeyPairFromPublicKey(String region, String keyPairName, + String publicKey) { + + IaasProvider iaasInfo = getIaasProvider(); + + String openstackNovaMsg = " Openstack-nova. Region: " + region + + " - Name: "; + + ComputeServiceContext context = iaasInfo.getComputeService() + .getContext(); + NovaApi novaApi = context.unwrapApi(NovaApi.class); + KeyPairApi api = novaApi.getKeyPairExtensionForZone(region).get(); + + KeyPair keyPair = api.createWithPublicKey(keyPairName, publicKey); + + if (keyPair != null) { + + iaasInfo.getTemplate().getOptions().as(NovaTemplateOptions.class) + .keyPairName(keyPair.getName()); + + log.info(SUCCESSFUL_LOG_LINE + openstackNovaMsg + keyPair.getName()); + return true; + } + + log.error(FAILED_LOG_LINE + openstackNovaMsg); + return false; + + } + + @Override + public synchronized String associateAddress(NodeMetadata node) { + + IaasProvider iaasInfo = getIaasProvider(); + + ComputeServiceContext context = iaasInfo.getComputeService() + .getContext(); + + String region = ComputeServiceBuilderUtil.extractRegion(iaasInfo); + + NovaApi novaApi = context.unwrapApi(NovaApi.class); + FloatingIPApi floatingIp = novaApi.getFloatingIPExtensionForZone( + region).get(); + + String ip = null; + // first try to find an unassigned IP. + ArrayList<FloatingIP> unassignedIps = Lists.newArrayList(Iterables + .filter(floatingIp.list(), + new Predicate<FloatingIP>() { + + @Override + public boolean apply(FloatingIP arg0) { + return arg0.getInstanceId() == null; + } + + })); + + if (!unassignedIps.isEmpty()) { + // try to prevent multiple parallel launches from choosing the same + // ip. + Collections.shuffle(unassignedIps); + ip = Iterables.getLast(unassignedIps).getIp(); + } + + // if no unassigned IP is available, we'll try to allocate an IP. + if (ip == null || ip.isEmpty()) { + String defaultFloatingIpPool = iaasInfo.getProperty(CloudControllerConstants.DEFAULT_FLOATING_IP_POOL); + FloatingIP allocatedFloatingIP; + if ((defaultFloatingIpPool == null) || "".equals(defaultFloatingIpPool)) { + allocatedFloatingIP = floatingIp.create(); + } else { + allocatedFloatingIP = floatingIp.allocateFromPool(defaultFloatingIpPool); + } + if (allocatedFloatingIP == null) { + String msg = "Failed to allocate an IP address."; + log.error(msg); + throw new CloudControllerException(msg); + } + ip = allocatedFloatingIP.getIp(); + } + + // wait till the fixed IP address gets assigned - this is needed before + // we associate a public IP + while (node.getPrivateAddresses() == null) { + CloudControllerUtil.sleep(1000); + } + + if (node.getPublicAddresses() != null + && node.getPublicAddresses().iterator().hasNext()) { + log.info("A public IP (" + + node.getPublicAddresses().iterator().next() + + ") is already allocated to the instance [id] : " + + node.getId()); + return null; + } + + int retries = 0; + //TODO make 5 configurable + while (retries < 5 + && !associateIp(floatingIp, ip, node.getProviderId())) { + + // wait for 5s + CloudControllerUtil.sleep(5000); + retries++; + } + + log.info("Successfully associated an IP address " + ip + + " for node with id: " + node.getId()); + + return ip; + } + + @Override + public synchronized String associatePredefinedAddress (NodeMetadata node, String ip) { + if(log.isDebugEnabled()) { + log.debug("OpenstackNovaIaas:associatePredefinedAddress:ip:" + ip); + } + + IaasProvider iaasInfo = getIaasProvider(); + + ComputeServiceContext context = iaasInfo.getComputeService() + .getContext(); + + String region = ComputeServiceBuilderUtil.extractRegion(iaasInfo); + + FloatingIPApi floatingIp = context.unwrapApi(NovaApi.class).getFloatingIPExtensionForZone( + region).get(); + + if(log.isDebugEnabled()) { + log.debug("OpenstackNovaIaas:associatePredefinedAddress:floatingip:" + floatingIp); + } + + // get the list of all unassigned IP. + ArrayList<FloatingIP> unassignedIps = Lists.newArrayList(Iterables + .filter(floatingIp.list(), + new Predicate<FloatingIP>() { + + @Override + public boolean apply(FloatingIP arg0) { + // FIXME is this the correct filter? + return arg0.getFixedIp() == null; + } + + })); + + boolean isAvailable = false; + for (FloatingIP fip : unassignedIps) { + if(log.isDebugEnabled()) { + log.debug("OpenstackNovaIaas:associatePredefinedAddress:iterating over available floatingip:" + fip); + } + if (ip.equals(fip.getIp())) { + if(log.isDebugEnabled()) { + log.debug(String.format("OpenstackNovaIaas:associatePredefinedAddress:floating ip in use:%s /ip:%s", fip, ip)); + } + isAvailable = true; + break; + } + } + + if (isAvailable) { + // assign ip + if(log.isDebugEnabled()) { + log.debug("OpenstackNovaIaas:associatePredefinedAddress:assign floating ip:" + ip); + } + // exercise same code as in associateAddress() + // wait till the fixed IP address gets assigned - this is needed before + // we associate a public IP + + while (node.getPrivateAddresses() == null) { + CloudControllerUtil.sleep(1000); + } + + int retries = 0; + while (retries < 5 + && !associateIp(floatingIp, ip, node.getProviderId())) { + + // wait for 5s + CloudControllerUtil.sleep(5000); + retries++; + } + + NodeMetadataBuilder.fromNodeMetadata(node) + .publicAddresses(ImmutableSet.of(ip)).build(); + + log.info("OpenstackNovaIaas:associatePredefinedAddress:Successfully associated an IP address " + ip + + " for node with id: " + node.getId()); + } else { + // unable to allocate predefined ip, + log.info("OpenstackNovaIaas:associatePredefinedAddress:Unable to allocate predefined ip:" + + " for node with id: " + node.getId()); + return ""; + } + + + NodeMetadataBuilder.fromNodeMetadata(node) + .publicAddresses(ImmutableSet.of(ip)).build(); + + log.info("OpenstackNovaIaas:associatePredefinedAddress::Successfully associated an IP address " + ip + + " for node with id: " + node.getId()); + + return ip; + + } + + @Override + public synchronized void releaseAddress(String ip) { + + IaasProvider iaasInfo = getIaasProvider(); + + ComputeServiceContext context = iaasInfo.getComputeService() + .getContext(); + + String region = ComputeServiceBuilderUtil.extractRegion(iaasInfo); + + NovaApi novaApi = context.unwrapApi(NovaApi.class); + FloatingIPApi floatingIPApi = novaApi.getFloatingIPExtensionForZone(region).get(); + + for (FloatingIP floatingIP : floatingIPApi.list()) { + if (floatingIP.getIp().equals(ip)) { + floatingIPApi.delete(floatingIP.getId()); + break; + } + } + + } + + private boolean associateIp(FloatingIPApi api, String ip, String id) { + try { + api.addToServer(ip, id); + return true; + } catch (RuntimeException ex) { + return false; + } + } + + @Override + public boolean isValidRegion(String region) throws InvalidRegionException { + IaasProvider iaasInfo = getIaasProvider(); + + // jclouds' zone = region in openstack + if (region == null || iaasInfo == null) { + String msg = + "Region or IaaSProvider is null: region: " + region + " - IaaSProvider: " + + iaasInfo; + log.error(msg); + throw new InvalidRegionException(msg); + } + + ComputeServiceContext context = iaasInfo.getComputeService().getContext(); + NovaApi novaApi = context.unwrapApi(NovaApi.class); + Set<String> zones = novaApi.getConfiguredZones(); + for (String configuredZone : zones) { + if (region.equalsIgnoreCase(configuredZone)) { + if (log.isDebugEnabled()) { + log.debug("Found a matching region: " + region); + } + return true; + } + } + + 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(); + + // jclouds availability zone = stratos zone + if (region == null || zone == null || iaasInfo == null) { + String msg = "Host or Zone or IaaSProvider is null: region: " + region + " - zone: " + + zone + " - IaaSProvider: " + iaasInfo; + log.error(msg); + throw new InvalidZoneException(msg); + } + ComputeServiceContext context = iaasInfo.getComputeService().getContext(); + NovaApi novaApi = context.unwrapApi(NovaApi.class); + Optional<? extends AvailabilityZoneApi> availabilityZoneApi = novaApi.getAvailabilityZoneApi(region); + for (AvailabilityZone z : availabilityZoneApi.get().list()) { + + if (zone.equalsIgnoreCase(z.getName())) { + if (log.isDebugEnabled()) { + log.debug("Found a matching availability zone: " + zone); + } + return true; + } + } + + String msg = "Invalid zone: " + zone +" in the region: "+region+ " and of the iaas: "+iaasInfo.getType(); + log.error(msg); + throw new InvalidZoneException(msg); + + } + + @Override + public boolean isValidHost(String zone, String host) throws InvalidHostException { + IaasProvider iaasInfo = getIaasProvider(); + + if (host == null || zone == null || iaasInfo == null) { + String msg = String.format("Host or Zone or IaaSProvider is null: host: %s - zone: %s - IaaSProvider: %s", host, zone, iaasInfo); + log.error(msg); + throw new InvalidHostException(msg); + } + ComputeServiceContext context = iaasInfo.getComputeService().getContext(); + NovaApi novaApi = context.unwrapApi(NovaApi.class); + HostAggregateApi hostApi = novaApi.getHostAggregateExtensionForZone(zone).get(); + for (HostAggregate hostAggregate : hostApi.list()) { + for (String configuredHost : hostAggregate.getHosts()) { + if (host.equalsIgnoreCase(configuredHost)) { + if (log.isDebugEnabled()) { + log.debug("Found a matching host: " + host); + } + return true; + } + } + } + + String msg = String.format("Invalid host: %s in the zone: %s and of the iaas: %s", host, zone, iaasInfo.getType()); + log.error(msg); + throw new InvalidHostException(msg); + } + + @Override + public PartitionValidator getPartitionValidator() { + return new OpenstackNovaPartitionValidator(); + } + + @Override + public String createVolume(int sizeGB, String snapshotId) { + IaasProvider iaasInfo = getIaasProvider(); + + if (iaasInfo == null) { + log.fatal(String.format("Cannot create a new volume with snapshot ID : %s", snapshotId)); + return null; + } + + String region = ComputeServiceBuilderUtil.extractRegion(iaasInfo); + String zone = ComputeServiceBuilderUtil.extractZone(iaasInfo); + + if (region == null) { + log.fatal(String.format("Cannot create a new volume. Extracted region is null for Iaas : %s", iaasInfo)); + return null; + } + ComputeServiceContext context = iaasInfo.getComputeService().getContext(); + + NovaApi novaApi = context.unwrapApi(NovaApi.class); + VolumeApi volumeApi = novaApi.getVolumeExtensionForZone(region).get(); + Volume volume; + if(StringUtils.isEmpty(snapshotId)){ + if(log.isDebugEnabled()){ + log.info("Creating a volume in the zone " + zone); + } + volume = volumeApi.create(sizeGB, CreateVolumeOptions.Builder.availabilityZone(zone)); + }else{ + if(log.isDebugEnabled()){ + log.info("Creating a volume in the zone " + zone + " from the shanpshot " + snapshotId); + } + volume = volumeApi.create(sizeGB, CreateVolumeOptions.Builder.availabilityZone(zone).snapshotId(snapshotId)); + } + + if (volume == null) { + log.fatal(String.format("Volume creation was unsuccessful. [region] : %s [zone] : %s of Iaas : %s", region, zone, iaasInfo)); + return null; + } + + String volumeId = volume.getId(); + /* + Volume.Status volumeStatus = this.getVolumeStatus(volumeApi, volumeId); + + if(!(volumeStatus == Volume.Status.AVAILABLE || volumeStatus == Volume.Status.CREATING)){ + log.error(String.format("Error while creating [volume id] %s. Volume status is %s", volumeId, volumeStatus)); + return volumeId; + } + try { + if(!waitForStatus(volumeApi, volumeId, Volume.Status.AVAILABLE)){ + log.error("Volume did not become AVAILABLE. Current status is " + volume.getStatus()); + } + } catch (TimeoutException e) { + log.error("[Volume ID] " + volumeId + "did not become AVAILABLE within expected timeout"); + return volumeId; + } + */ + log.info(String.format("Successfully created a new volume [id]: %s in [region] : %s [zone] : %s of Iaas : %s [Volume ID]%s", volume.getId(), region, zone, iaasInfo, volume.getId())); + return volumeId; + } + + private boolean waitForStatus(String volumeId, Volume.Status expectedStatus, int timeoutInMins) throws TimeoutException { + int timeout = 1000 * 60 * timeoutInMins; + long timout = System.currentTimeMillis() + timeout; + + IaasProvider iaasInfo = getIaasProvider(); + String region = ComputeServiceBuilderUtil.extractRegion(iaasInfo); + ComputeServiceContext context = iaasInfo.getComputeService().getContext();; + NovaApi novaApi = context.unwrapApi(NovaApi.class); + VolumeApi volumeApi = novaApi.getVolumeExtensionForZone(region).get(); + Volume.Status volumeStatus = this.getVolumeStatus(volumeApi, volumeId); + + while(volumeStatus != expectedStatus){ + try { + if(log.isDebugEnabled()){ + log.debug(String.format("Volume %s is still NOT in %s. Current State=%s", volumeId, expectedStatus, volumeStatus)); + } + if(volumeStatus == Volume.Status.ERROR){ + log.error("Volume " + volumeId + " is in state ERROR"); + return false; + } + Thread.sleep(1000); + volumeStatus = this.getVolumeStatus(volumeApi, volumeId); + 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; + } + + @Override + public String attachVolume(String instanceId, String volumeId, String deviceName) { + IaasProvider iaasInfo = getIaasProvider(); + + if (StringUtils.isEmpty(volumeId)) { + log.error("Volume provided to attach can not be null"); + } + + if (StringUtils.isEmpty(instanceId)) { + log.error("Instance provided to attach can not be null"); + } + + ComputeServiceContext context = iaasInfo.getComputeService() + .getContext(); + String region = ComputeServiceBuilderUtil.extractRegion(iaasInfo); + String device = deviceName == null ? "/dev/vdc" : deviceName; + + if (region == null) { + log.fatal(String.format("Cannot attach the volume [id]: %s. Extracted region is null for Iaas : %s", volumeId, iaasInfo)); + return null; + } + + NovaApi novaApi = context.unwrapApi(NovaApi.class); + VolumeApi volumeApi = novaApi.getVolumeExtensionForZone(region).get(); + VolumeAttachmentApi volumeAttachmentApi = novaApi.getVolumeAttachmentExtensionForZone(region).get(); + + Volume.Status volumeStatus = this.getVolumeStatus(volumeApi, volumeId); + + if (log.isDebugEnabled()) { + log.debug("Volume " + volumeId + " is in state " + volumeStatus); + } + + if (!(volumeStatus == Volume.Status.AVAILABLE || volumeStatus == Volume.Status.CREATING)) { + log.error(String.format("Volume %s can not be attached. Volume status is %s", volumeId, volumeStatus)); + return null; + } + + boolean volumeBecameAvailable = false, volumeBecameAttached = false; + try { + volumeBecameAvailable = waitForStatus(volumeId, Volume.Status.AVAILABLE, 5); + } catch (TimeoutException e) { + log.error("[Volume ID] " + volumeId + "did not become AVAILABLE within expected timeout"); + } + + VolumeAttachment attachment = null; + if (volumeBecameAvailable) { + attachment = volumeAttachmentApi.attachVolumeToServerAsDevice(volumeId, instanceId, device); + + try { + volumeBecameAttached = waitForStatus(volumeId, Volume.Status.IN_USE, 2); + } catch (TimeoutException e) { + log.error("[Volume ID] " + volumeId + "did not become IN_USE within expected timeout"); + } + } + try { + // waiting 5seconds till volumes are actually attached. + Thread.sleep(5000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + if (attachment == null) { + log.fatal(String.format("Volume [id]: %s attachment for instance [id]: %s was unsuccessful. [region] : %s of Iaas : %s", volumeId, instanceId, region, iaasInfo)); + return null; + } + + 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. [region] : %s of Iaas : %s", volumeId, instanceId, region, iaasInfo)); + return "Attaching"; + } + + @Override + public void detachVolume(String instanceId, String volumeId) { + IaasProvider iaasInfo = getIaasProvider(); + + ComputeServiceContext context = iaasInfo.getComputeService() + .getContext(); + + String region = ComputeServiceBuilderUtil.extractRegion(iaasInfo); + + if(region == null) { + log.fatal(String.format("Cannot detach the volume [id]: %s from the instance [id]: %s. Extracted region is null for Iaas : %s", volumeId, instanceId, iaasInfo)); + return; + } + if(log.isDebugEnabled()) { + log.debug(String.format("Starting to detach volume %s from the instance %s", volumeId, instanceId)); + } + + NovaApi novaApi = context.unwrapApi(NovaApi.class); + VolumeAttachmentApi api = novaApi.getVolumeAttachmentExtensionForZone(region).get(); + if (api.detachVolumeFromServer(volumeId, instanceId)) { + log.info(String.format("Detachment of Volume [id]: %s from instance [id]: %s was successful. [region] : %s of Iaas : %s", volumeId, instanceId, region, iaasInfo)); + }else{ + log.error(String.format("Detachment of Volume [id]: %s from instance [id]: %s was unsuccessful. [volume Status] : %s", volumeId, instanceId, region, iaasInfo)); + } + + } + + @Override + public void deleteVolume(String volumeId) { + IaasProvider iaasInfo = getIaasProvider(); + + ComputeServiceContext context = iaasInfo.getComputeService() + .getContext(); + + String region = ComputeServiceBuilderUtil.extractRegion(iaasInfo); + + if(region == null) { + log.fatal(String.format("Cannot delete the volume [id]: %s. Extracted region is null for Iaas : %s", volumeId, iaasInfo)); + return; + } + + NovaApi novaApi = context.unwrapApi(NovaApi.class); + VolumeApi api = novaApi.getVolumeExtensionForZone(region).get(); + if (api.delete(volumeId)) { + log.info(String.format("Deletion of Volume [id]: %s was successful. [region] : %s of Iaas : %s", volumeId, region, iaasInfo)); + } + } + + @Override + public String getIaasDevice(String device) { + return device; + } + + private Volume.Status getVolumeStatus(VolumeApi volumeApi, String volumeId){ + return volumeApi.get(volumeId).getStatus(); + } +}
