/*
* 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.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
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.pojo.NetworkInterface;
import org.apache.stratos.cloud.controller.util.CloudControllerConstants;
import org.apache.stratos.cloud.controller.util.CloudControllerUtil;
import org.apache.stratos.cloud.controller.validate.OpenstackNovaPartitionValidator;
import org.apache.stratos.cloud.controller.validate.interfaces.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.NovaApiMetadata;
import org.jclouds.openstack.nova.v2_0.NovaAsyncApi;
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 org.jclouds.rest.RestContext;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
@SuppressWarnings("deprecation")
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();
RestContext<NovaApi, NovaAsyncApi> nova = context.unwrap();
KeyPairApi api = nova.getApi().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);
RestContext<NovaApi, NovaAsyncApi> nova = context.unwrap();
FloatingIPApi floatingIp = nova.getApi().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()) {
FloatingIP allocatedFloatingIP = floatingIp.create();
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();
@SuppressWarnings("deprecation")
NovaApi novaClient = context.unwrap(NovaApiMetadata.CONTEXT_TOKEN).getApi();
String region = ComputeServiceBuilderUtil.extractRegion(iaasInfo);
FloatingIPApi floatingIp = novaClient.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("OpenstackNovaIaas:associatePredefinedAddress:floating ip in use:" + fip + " /ip:" + 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);
@SuppressWarnings("deprecation")
RestContext<NovaApi, NovaAsyncApi> nova = context.unwrap();
@SuppressWarnings("deprecation")
FloatingIPApi floatingIPApi = nova.getApi()
.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();
RestContext<NovaApi, NovaAsyncApi> nova = context.unwrap();
Set<String> zones = nova.getApi().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();
RestContext<NovaApi, NovaAsyncApi> nova = context.unwrap();
AvailabilityZoneAPI zoneApi = nova.getApi().getAvailabilityZoneApi(region);
for (AvailabilityZone z : zoneApi.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 = "Host or Zone or IaaSProvider is null: host: " + host + " - zone: " +
zone + " - IaaSProvider: " + iaasInfo;
log.error(msg);
throw new InvalidHostException(msg);
}
ComputeServiceContext context = iaasInfo.getComputeService().getContext();
RestContext<NovaApi, NovaAsyncApi> nova = context.unwrap();
HostAggregateApi hostApi = nova.getApi().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 = "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 OpenstackNovaPartitionValidator();
}
@Override
public String createVolume(int sizeGB) {
IaasProvider iaasInfo = getIaasProvider();
String region = ComputeServiceBuilderUtil.extractRegion(iaasInfo);
String zone = ComputeServiceBuilderUtil.extractZone(iaasInfo);
if (region == null || iaasInfo == null) {
log.fatal("Cannot create a new volume in the [region] : "+region
+" of Iaas : "+iaasInfo);
return null;
}
ComputeServiceContext context = iaasInfo.getComputeService().getContext();
RestContext<NovaApi, NovaAsyncApi> nova = context.unwrap();
VolumeApi api = nova.getApi().getVolumeExtensionForZone(region).get();
Volume volume = api.create(sizeGB, CreateVolumeOptions.Builder.availabilityZone(zone));
if (volume == null) {
log.fatal("Volume creation was unsuccessful. [region] : " + region+" [zone] : " + zone
+ " of Iaas : " + iaasInfo);
return null;
}
log.info("Successfully created a new volume [id]: "+volume.getId()
+" in [region] : "+region+" [zone] : "+zone+" of Iaas : "+iaasInfo);
return volume.getId();
}
@Override
public String attachVolume(String instanceId, String volumeId, String deviceName) {
IaasProvider iaasInfo = getIaasProvider();
ComputeServiceContext context = iaasInfo.getComputeService()
.getContext();
String region = ComputeServiceBuilderUtil.extractRegion(iaasInfo);
String device = deviceName == null ? "/dev/vdc" : deviceName;
if(region == null) {
log.fatal("Cannot attach the volume [id]: "+volumeId+" in the [region] : "+region
+" of Iaas : "+iaasInfo);
return null;
}
RestContext<NovaApi, NovaAsyncApi> nova = context.unwrap();
VolumeAttachmentApi api = nova.getApi().getVolumeAttachmentExtensionForZone(region).get();
VolumeAttachment attachment = api.attachVolumeToServerAsDevice(volumeId, instanceId, device);
if (attachment == null) {
log.fatal("Volume [id]: "+volumeId+" attachment for instance [id]: "+instanceId
+" was unsuccessful. [region] : " + region
+ " of Iaas : " + iaasInfo);
return null;
}
log.info("Volume [id]: "+volumeId+" attachment for instance [id]: "+instanceId
+" was successful [status]: "+"Attaching"+". [region] : " + region
+ " of Iaas : " + 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("Cannot detach the volume [id]: "+volumeId+" from the instance [id]: "+instanceId
+" of the [region] : "+region
+" of Iaas : "+iaasInfo);
return;
}
RestContext<NovaApi, NovaAsyncApi> nova = context.unwrap();
VolumeAttachmentApi api = nova.getApi().getVolumeAttachmentExtensionForZone(region).get();
if (api.detachVolumeFromServer(volumeId, instanceId)) {
log.info("Detachment of Volume [id]: "+volumeId+" from instance [id]: "+instanceId
+" was successful. [region] : " + region
+ " of Iaas : " + 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("Cannot delete the volume [id]: "+volumeId+" of the [region] : "+region
+" of Iaas : "+iaasInfo);
return;
}
RestContext<NovaApi, NovaAsyncApi> nova = context.unwrap();
VolumeApi api = nova.getApi().getVolumeExtensionForZone(region).get();
if (api.delete(volumeId)) {
log.info("Deletion of Volume [id]: "+volumeId+" was successful. [region] : " + region
+ " of Iaas : " + iaasInfo);
}
}
@Override
public String getIaasDevice(String device) {
return device;
}
}