/*******************************************************************************
* Copyright (c) 2011 GigaSpaces Technologies Ltd. All rights reserved
*
* Licensed 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.cloudifysource.esc.driver.provisioning.jclouds;
import com.google.common.base.Predicate;
import com.google.inject.Module;
import com.j_spaces.kernel.Environment;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.net.util.SubnetUtils;
import org.apache.commons.net.util.SubnetUtils.SubnetInfo;
import org.cloudifysource.domain.cloud.Cloud;
import org.cloudifysource.domain.cloud.FileTransferModes;
import org.cloudifysource.domain.cloud.compute.ComputeTemplate;
import org.cloudifysource.dsl.internal.CloudifyConstants;
import org.cloudifysource.dsl.rest.response.ControllerDetails;
import org.cloudifysource.esc.driver.provisioning.BaseProvisioningDriver;
import org.cloudifysource.esc.driver.provisioning.CloudProvisioningException;
import org.cloudifysource.esc.driver.provisioning.MachineDetails;
import org.cloudifysource.esc.driver.provisioning.ManagementProvisioningContext;
import org.cloudifysource.esc.driver.provisioning.ProvisioningContext;
import org.cloudifysource.esc.driver.provisioning.context.ValidationContext;
import org.cloudifysource.esc.driver.provisioning.validation.ValidationMessageType;
import org.cloudifysource.esc.driver.provisioning.validation.ValidationResultType;
import org.cloudifysource.esc.jclouds.JCloudsDeployer;
import org.cloudifysource.esc.util.JCloudsUtils;
import org.cloudifysource.esc.util.Utils;
import org.jclouds.apis.ApiMetadata;
import org.jclouds.apis.Apis;
import org.jclouds.compute.ComputeServiceContext;
import org.jclouds.compute.domain.ComputeMetadata;
import org.jclouds.compute.domain.Hardware;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.domain.Location;
import org.jclouds.domain.LoginCredentials;
import org.jclouds.ec2.EC2AsyncClient;
import org.jclouds.ec2.EC2Client;
import org.jclouds.ec2.domain.KeyPair;
import org.jclouds.ec2.services.KeyPairClient;
import org.jclouds.openstack.nova.v2_0.NovaApi;
import org.jclouds.openstack.nova.v2_0.NovaAsyncApi;
import org.jclouds.openstack.nova.v2_0.extensions.KeyPairApi;
import org.jclouds.openstack.nova.v2_0.extensions.SecurityGroupApi;
import org.jclouds.providers.ProviderMetadata;
import org.jclouds.providers.Providers;
import org.jclouds.rest.RestContext;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.regex.Pattern;
/**************
* A jclouds-based CloudifyProvisioning implementation. Uses the JClouds Compute Context API to provision an image with
* linux installed and ssh available. If GigaSpaces is not already installed on the new machine, this class will install
* gigaspaces and run the agent.
*
* @author barakme, noak
* @since 2.0.0
*/
public class DefaultProvisioningDriver extends BaseProvisioningDriver {
private static final String FILE_SEPARATOR = System.getProperty("file.separator");
private static final String PUBLIC_IP_REGEX = "org.cloudifysource.default-cloud-driver.public-ip-regex";
private static final String PUBLIC_IP_CIDR = "org.cloudifysource.default-cloud-driver.public-ip-cidr";
private static final String PRIVATE_IP_REGEX = "org.cloudifysource.default-cloud-driver.private-ip-regex";
private static final String PRIVATE_IP_CIDR = "org.cloudifysource.default-cloud-driver.private-ip-cidr";
private static final int CLOUD_NODE_STATE_POLLING_INTERVAL = 2000;
private static final String DEFAULT_EC2_WINDOWS_USERNAME = "Administrator";
private static final String EC2_API = "aws-ec2";
private static final String VCLOUD = "vcloud";
private static final String OPENSTACK_API = "openstack-nova";
private static final String CLOUDSTACK = "cloudstack";
private static final String ENDPOINT_OVERRIDE = "jclouds.endpoint";
private static final String CLOUDS_FOLDER_PATH = Environment.getHomeDirectory() + "clouds";
private static final int MAX_VERBOSE_IDS_LENGTH = 5;
private static final int DEFAULT_STOP_MANAGEMENT_TIMEOUT = 15;
// TODO: should it be volatile?
private static ResourceBundle defaultProvisioningDriverMessageBundle;
private String groovyFile;
private String propertiesFile;
private JCloudsDeployer deployer;
private SubnetInfo privateSubnetInfo;
private Pattern privateIpPattern;
private SubnetInfo publicSubnetInfo;
private Pattern publicIpPattern;
private int stopManagementMachinesTimeoutInMinutes = DEFAULT_STOP_MANAGEMENT_TIMEOUT;
@Override
public void setCustomDataFile(final File customDataFile) {
logger.info("Received custom data file: " + customDataFile);
}
@Override
public MachineDetails[] getExistingManagementServers(final ControllerDetails[] controllers)
throws CloudProvisioningException, UnsupportedOperationException {
throw new UnsupportedOperationException(
"Locating management servers from file information is not supported in this cloud driver");
}
@Override
public Object getComputeContext() {
ComputeServiceContext computeContext = null;
if (deployer != null) {
computeContext = deployer.getContext();
}
return computeContext;
}
protected String getProviderForTemplate(final String templateName, final ComputeTemplate template) {
// provider is not dependent on the template
return cloud.getProvider().getProvider();
}
/**
* 1. Provider/API name
* 2. Authentication to the cloud
* 3. Image IDs
* 4. Hardware IDs
* 5. Location IDs
* 6. Security groups
* 7. Key-pair names (TODO: finger-print check)
* @param validationContext The object through which writing of validation messages is done
* @throws CloudProvisioningException
*/
@Override
public void validateCloudConfiguration(
final ValidationContext validationContext) throws CloudProvisioningException {
// TODO : move the security groups to the Template section (instead of custom map),
// it is now supported by jclouds.
final String providerName = cloud.getProvider().getProvider();
String cloudFolder = CLOUDS_FOLDER_PATH + FILE_SEPARATOR + cloud.getName();
groovyFile = cloudFolder + FILE_SEPARATOR + cloud.getName() + "-cloud.groovy";
propertiesFile = cloudFolder + FILE_SEPARATOR + cloud.getName() + "-cloud.properties";
String apiId;
boolean endpointRequired = false;
try {
validationContext.validationOngoingEvent(ValidationMessageType.TOP_LEVEL_VALIDATION_MESSAGE,
getFormattedMessage("validating_provider_or_api_name", providerName));
final ProviderMetadata providerMetadata = Providers.withId(providerName);
final ApiMetadata apiMetadata = providerMetadata.getApiMetadata();
apiId = apiMetadata.getId();
validationContext.validationEventEnd(ValidationResultType.OK);
} catch (final NoSuchElementException e) {
// there is no jclouds Provider by that name, this could be the name of an API used in a private cloud
try {
final ApiMetadata apiMetadata = Apis.withId(providerName);
apiId = apiMetadata.getId();
endpointRequired = true;
validationContext.validationEventEnd(ValidationResultType.OK);
} catch (final NoSuchElementException ex) {
validationContext.validationEventEnd(ValidationResultType.ERROR);
throw new CloudProvisioningException(getFormattedMessage("error_provider_or_api_name_validation",
providerName, cloudFolder), ex);
}
}
validateComputeTemplates(endpointRequired, apiId, validationContext);
}
@Override
public void initDeployer(final Cloud cloud) {
if (this.deployer != null) {
return;
}
try {
this.stopManagementMachinesTimeoutInMinutes = Utils.getInteger(cloud.getCustom().get(CloudifyConstants
.STOP_MANAGEMENT_TIMEOUT_IN_MINUTES), DEFAULT_STOP_MANAGEMENT_TIMEOUT);
this.deployer = createDeployer(cloud);
initIPFilters(cloud);
} catch (final Exception e) {
publishEvent("connection_to_cloud_api_failed", cloud.getProvider() .getProvider());
throw new IllegalStateException("Failed to create cloud Deployer", e);
}
}
@Override
public MachineDetails startMachine(final ProvisioningContext context, final long duration, final TimeUnit unit)
throws TimeoutException, CloudProvisioningException {
logger.fine(this.getClass().getName()
+ ": startMachine, management mode: " + management);
final long end = System.currentTimeMillis() + unit.toMillis(duration);
if (System.currentTimeMillis() > end) {
throw new TimeoutException("Starting a new machine timed out");
}
String groupName = serverNamePrefix + counter.incrementAndGet();
logger.fine("Starting a new cloud server with group: " + groupName);
return createServer(end, groupName, context.getLocationId());
}
@Override
protected MachineDetails createServer(
final String serverName,
final long endTime,
final ComputeTemplate template) throws CloudProvisioningException, TimeoutException {
return createServer(endTime, serverName, null);
}
public Set<Module> setupModules(final String templateName, final ComputeTemplate template) {
// does not depend on templates by default.
return new HashSet<Module>();
}
private MachineDetails createServer(final long end, final String groupName,
final String locationIdOverride) throws CloudProvisioningException {
final ComputeTemplate cloudTemplate = this.cloud.getCloudCompute().getTemplates().get(
this.cloudTemplateName);
String locationId;
if (locationIdOverride == null) {
locationId = cloudTemplate.getLocationId();
} else {
locationId = locationIdOverride;
}
NodeMetadata node;
final MachineDetails machineDetails;
publishEvent(EVENT_STARTING_MACHINE_WITH_NAME, groupName);
node = deployer.createServer(groupName, locationId);
final String nodeId = node.getId();
// At this point the machine is starting. Any error beyond this point
// must clean up the machine
try {
// wait for node to reach RUNNING state
publishEvent(EVENT_WAITING_FOR_NODE_TO_BE_AVAILABLE, groupName);
node = waitForNodeToBecomeReady(nodeId, end);
publishEvent(EVENT_MACHINE_STARTED, node.getName(), node.getPublicAddresses());
machineDetails = createMachineDetailsFromNode(node);
final FileTransferModes fileTransfer = cloudTemplate
.getFileTransfer();
if (this.cloud.getProvider().getProvider().equals("aws-ec2") && fileTransfer == FileTransferModes.CIFS) {
// Special password handling for windows on EC2
if (machineDetails.getRemotePassword() == null) {
// The template did not specify a password, so we must be
// using the aws windows password mechanism.
handleEC2WindowsCredentials(end, node, machineDetails,cloudTemplate);
}
} else {
// credentials required special handling.
handleServerCredentials(machineDetails, cloudTemplate);
}
} catch (final Exception e) {
// catch any exception - to prevent a cloud machine leaking.
logger.log(Level.SEVERE, "Cloud machine was started but an error occurred during initialization. Shutting "
+ "down machine", e);
deployer.shutdownMachine(nodeId);
throw new CloudProvisioningException(e);
}
return machineDetails;
}
private void handleEC2WindowsCredentials(
final long end,
final NodeMetadata node,
final MachineDetails machineDetails,
final ComputeTemplate cloudTemplate) throws FileNotFoundException,
InterruptedException,
TimeoutException,
CloudProvisioningException {
File pemFile;
if (this.management) {
final File localDirectory = new File(cloudTemplate.getAbsoluteUploadDir());
pemFile = new File(localDirectory, cloudTemplate.getKeyFile());
} else {
final String localDirectoryName = cloudTemplate.getLocalDirectory();
logger.fine("local dir name is: " + localDirectoryName);
final File localDirectory = new File(localDirectoryName);
pemFile = new File(localDirectory, cloudTemplate.getKeyFile());
}
if (!pemFile.exists()) {
throw new FileNotFoundException("Could not find key file: " + pemFile);
}
String password;
if (cloudTemplate.getPassword() == null) {
// get the password using Amazon API
this.publishEvent("waiting_for_ec2_windows_password", node.getId());
final LoginCredentials credentials = new EC2WindowsPasswordHandler().getPassword(node,
this.deployer.getContext(), end, pemFile);
password = credentials.getPassword();
this.publishEvent("ec2_windows_password_retrieved", node.getId());
} else {
password = cloudTemplate.getPassword();
}
String username = cloudTemplate.getUsername();
if (username == null) {
username = DEFAULT_EC2_WINDOWS_USERNAME;
}
machineDetails.setRemoteUsername(username);
machineDetails.setRemotePassword(password);
machineDetails.setFileTransferMode(cloudTemplate.getFileTransfer());
machineDetails.setRemoteExecutionMode(cloudTemplate
.getRemoteExecution());
}
private NodeMetadata waitForNodeToBecomeReady(
final String id,
final long end) throws CloudProvisioningException, InterruptedException, TimeoutException {
NodeMetadata node;
while (System.currentTimeMillis() < end) {
node = deployer.getServerByID(id);
if (node == null) {
logger.fine("Server Status (" + id + ") Not Found, please wait...");
Thread.sleep(CLOUD_NODE_STATE_POLLING_INTERVAL);
break;
} else {
switch (node.getStatus()) {
case RUNNING:
return node;
case PENDING:
logger.fine("Server Status (" + id + ") still PENDING, please wait...");
Thread.sleep(CLOUD_NODE_STATE_POLLING_INTERVAL);
break;
case TERMINATED:
case ERROR:
case UNRECOGNIZED:
case SUSPENDED:
default:
throw new CloudProvisioningException("Failed to allocate server - Cloud reported node in "
+ node.getStatus().toString() + " state. Node details: " + node);
}
}
}
throw new TimeoutException("Node failed to reach RUNNING mode in time");
}
@Override
public MachineDetails[] startManagementMachines(final ManagementProvisioningContext context, final long duration,
final TimeUnit unit)
throws TimeoutException, CloudProvisioningException {
final int numberOfManagementMachines = this.cloud.getProvider().getNumberOfManagementMachines();
if (duration < 0) {
throw new TimeoutException("Starting a new machine timed out");
}
final long endTime = System.currentTimeMillis() + unit.toMillis(duration);
logger.fine("Received request to start " + numberOfManagementMachines + " management machines") ;
final String managementMachinePrefix = this.cloud.getProvider().getManagementGroup();
if (StringUtils.isBlank(managementMachinePrefix)) {
throw new CloudProvisioningException("The management group name is missing - " +
"can't locate existing servers!");
}
// first check if management already exists
publishEvent(EVENT_RETRIEVE_EXISTING_MANAGEMENT_MACHINES, managementMachinePrefix);
final MachineDetails[] existingManagementServers = getExistingManagementServers();
if (existingManagementServers.length > 0) {
final String serverDescriptions =
createExistingServersDescription(managementMachinePrefix, existingManagementServers);
throw new CloudProvisioningException("Found existing servers matching group "
+ managementMachinePrefix + ": " + serverDescriptions);
} else {
logger.fine("Did not find any existing management machines. continuing with bootstrap");
}
// launch the management machines
publishEvent(EVENT_ATTEMPT_START_MGMT_VMS);
final MachineDetails[] createdMachines = doStartManagementMachines(endTime, numberOfManagementMachines);
publishEvent(EVENT_MGMT_VMS_STARTED);
return createdMachines;
}
@Override
public boolean stopMachine(final String serverIp, final long duration,
final TimeUnit unit) throws CloudProvisioningException,
TimeoutException, InterruptedException {
boolean stopResult;
logger.info("Received request to shutdown machine with ip " + serverIp);
final NodeMetadata server = deployer.getServerWithIP(serverIp);
if (server != null) {
logger.info("Found machine : " + serverIp + "-" + server.getId() + ". Shutting it down and waiting for " +
"shutdown to complete");
deployer.shutdownMachineAndWait(server.getId(), unit, duration);
logger.info("Machine " + serverIp + "-" + server.getId() + " shutdown has finished.");
stopResult = true;
} else {
logger.log(Level.SEVERE, "Received shutdown request for machine with ip " + serverIp
+ " but this IP could not be found in the Cloud machine list");
stopResult = false;
}
return stopResult;
}
@Override
public void stopManagementMachines() throws TimeoutException, CloudProvisioningException {
initDeployer(this.cloud);
final String managementMachinePrefix = this.cloud.getProvider().getManagementGroup();
logger.fine("Received request to stop management machines. timeout is "
+ stopManagementMachinesTimeoutInMinutes + " minutes");
publishEvent(EVENT_RETRIEVE_EXISTING_MANAGEMENT_MACHINES, managementMachinePrefix);
final MachineDetails[] managementServers = getExistingManagementServers();
if (managementServers.length == 0) {
throw new CloudProvisioningException("Could not find any management machines for this "
+ "cloud (management machine prefix is: " + this.serverNamePrefix + ")");
}
final Set<String> machineIps = new HashSet<String>();
for (final MachineDetails machineDetails : managementServers) {
machineIps.add(machineDetails.getPublicAddress());
}
publishEvent(EVENT_DESTROYING_MACHINES, machineIps.toString());
try {
this.deployer.shutdownMachinesByIds(managementServers, stopManagementMachinesTimeoutInMinutes);
publishEvent(EVENT_MACHINES_DESTROYED_SUCCESSFULLY, machineIps.toString());
} catch (final InterruptedException e) {
throw new CloudProvisioningException(e);
}
}
/*
* (non-Javadoc)
*
* @see org.cloudifysource.esc.driver.provisioning.jclouds.ManagementLocator#getExistingManagementServers()
*/
@Override
public MachineDetails[] getExistingManagementServers() throws CloudProvisioningException {
final String managementMachinePrefix = this.serverNamePrefix;
Set<? extends NodeMetadata> existingManagementServers;
try {
Predicate<ComputeMetadata> filter = new Predicate<ComputeMetadata>() {
@Override
public boolean apply(final ComputeMetadata input) {
final NodeMetadata node = (NodeMetadata) input;
logger.finest("Found server " + node);
if (node.getGroup() == null) {
return false;
}
// only running or pending nodes are interesting
return (node.getStatus() == NodeMetadata.Status.RUNNING
|| node.getStatus() == NodeMetadata.Status.PENDING)
&& node.getGroup().toLowerCase().startsWith(managementMachinePrefix.toLowerCase());
}
};
existingManagementServers = this.deployer.getServers(filter);
} catch (final Exception e) {
throw new CloudProvisioningException("Failed to read existing management servers: " + e.getMessage(), e);
}
final MachineDetails[] result = new MachineDetails[existingManagementServers.size()];
int i = 0;
for (final NodeMetadata node : existingManagementServers) {
result[i] = createMachineDetailsFromNode(node);
i++;
}
return result;
}
@Override
protected void handleProvisioningFailure(
final int numberOfManagementMachines,
final int numberOfErrors,
final Exception firstCreationException,
final MachineDetails[] createdManagementMachines) throws CloudProvisioningException {
logger.severe("Of the required " + numberOfManagementMachines + " management machines, " + numberOfErrors
+ " failed to start.");
if (numberOfManagementMachines > numberOfErrors) {
logger.severe("Shutting down the other managememnt machines");
for (final MachineDetails machineDetails : createdManagementMachines) {
if (machineDetails != null) {
logger.severe("Shutting down machine: " + machineDetails);
this.deployer
.shutdownMachine(machineDetails.getMachineId());
}
}
}
throw new CloudProvisioningException("One or more managememnt machines failed. "
+ "The first encountered error was: " + firstCreationException.getMessage(), firstCreationException);
}
@Override
public void close() {
if (deployer != null) {
deployer.close();
}
}
private void populateIPs(final NodeMetadata node, final MachineDetails md, final ComputeTemplate template) {
final CloudAddressResolver resolver = new CloudAddressResolver();
final String privateAddress = resolver.getAddress(node.getPrivateAddresses(),
node.getPublicAddresses(), privateSubnetInfo, this.privateIpPattern);
final String publicAddress = resolver.getAddress(node.getPublicAddresses(), node.getPrivateAddresses(),
publicSubnetInfo, this.publicIpPattern);
md.setPrivateAddress(privateAddress);
md.setPublicAddress(publicAddress);
}
private MachineDetails createMachineDetailsFromNode(final NodeMetadata node) {
final ComputeTemplate template = this.cloud.getCloudCompute().getTemplates().get(this.cloudTemplateName);
final MachineDetails md = createMachineDetailsForTemplate(template);
md.setCloudifyInstalled(false);
md.setInstallationDirectory(null);
md.setMachineId(node.getId());
populateIPs(node, md, template);
final String username = createMachineUsername(node, template);
final String password = createMachinePassword(node, template);
md.setRemoteUsername(username);
md.setRemotePassword(password);
// this will ensure that the availability zone is added to GSA that
// starts on this machine.
Location location = node.getLocation();
if (location != null) {
md.setLocationId(location.getId());
}
md.setOpenFilesLimit(template.getOpenFilesLimit());
return md;
}
private String createMachineUsername(final NodeMetadata node,
final ComputeTemplate template) {
// Template configuration takes precedence.
if (template.getUsername() != null) {
return template.getUsername();
}
// Check if node returned a username
if (node.getCredentials() != null) {
final String serverIdentity = node.getCredentials().identity;
if (serverIdentity != null) {
return serverIdentity;
}
}
return null;
}
private String createMachinePassword(final NodeMetadata node,
final ComputeTemplate template) {
// Template configuration takes precedence.
if (template.getPassword() != null) {
return template.getPassword();
}
// Check if node returned a username - some clouds support this
// (Rackspace, for instance)
if (node.getCredentials() != null
&& node.getCredentials().getOptionalPassword() != null) {
if (node.getCredentials().getOptionalPassword().isPresent()) {
return node.getCredentials().getPassword();
}
}
return null;
}
/**
*
* @param cloud The cloud object that contains cerdentials.
* @return A {@link JCloudsDeployer} object for remote cloud operations.
* @throws IOException In case of an IO error.
*/
public JCloudsDeployer createDeployer(final Cloud cloud) throws IOException {
logger.fine("Creating JClouds context deployer with user: " + cloud.getUser().getUser());
final ComputeTemplate cloudTemplate = cloud.getCloudCompute().getTemplates().get(
cloudTemplateName);
logger.fine("Cloud Template: " + cloudTemplateName + ". Details: " + cloudTemplate);
final Properties props = new Properties();
props.putAll(cloudTemplate.getOverrides());
JCloudsDeployer deployer = new JCloudsDeployer(cloud.getProvider().getProvider(), cloud.getUser().getUser(),
cloud.getUser().getApiKey(), props, setupModules(cloudTemplateName, cloudTemplate));
deployer.setImageId(cloudTemplate.getImageId());
deployer.setMinRamMegabytes(cloudTemplate.getMachineMemoryMB());
deployer.setHardwareId(cloudTemplate.getHardwareId());
deployer.setExtraOptions(cloudTemplate.getOptions());
return deployer;
}
private void validateComputeTemplates(final boolean endpointRequired, final String apiId,
final ValidationContext validationContext) throws CloudProvisioningException {
JCloudsDeployer deployer = null;
String templateName = "";
String imageId = "";
String hardwareId = "";
String locationId = "";
try {
validationContext.validationEvent(ValidationMessageType.TOP_LEVEL_VALIDATION_MESSAGE,
getFormattedMessage("validating_all_templates"));
for (final Entry<String, ComputeTemplate> entry : cloud.getCloudCompute().getTemplates().entrySet()) {
templateName = entry.getKey();
validationContext.validationEvent(ValidationMessageType.GROUP_VALIDATION_MESSAGE,
getFormattedMessage("validating_template", templateName));
final ComputeTemplate template = entry.getValue();
final String endpoint = getEndpoint(template);
if (endpointRequired && StringUtils.isBlank(endpoint)) {
throw new CloudProvisioningException("Endpoint not defined. Please add a \"jclouds.endpoint\""
+ " entry in the template's overrides section");
}
try {
validationContext.validationOngoingEvent(ValidationMessageType.ENTRY_VALIDATION_MESSAGE,
getFormattedMessage("validating_cloud_credentials"));
final Properties templateProps = new Properties();
final Map<String, Object> templateOverrides = template.getOverrides();
templateProps.putAll(templateOverrides);
logger.fine("Creating a new cloud deployer");
String providerForTemplate = getProviderForTemplate(templateName, template);
deployer = new JCloudsDeployer(providerForTemplate,
cloud.getUser().getUser(),
cloud.getUser().getApiKey(), templateProps, setupModules(templateName, template));
logger.log(Level.FINE, "Making an API call to verify credentials");
deployer.getAllLocations();
validationContext.validationEventEnd(ValidationResultType.OK);
} catch (Exception e) {
closeDeployer(deployer);
validationContext.validationEventEnd(ValidationResultType.ERROR);
throw new CloudProvisioningException(getFormattedMessage("error_cloud_credentials_validation",
groovyFile, propertiesFile), e);
}
imageId = template.getImageId();
hardwareId = template.getHardwareId();
locationId = template.getLocationId();
deployer.setImageId(imageId);
deployer.setHardwareId(hardwareId);
deployer.setExtraOptions(template.getOptions());
// TODO: check this memory validation
// deployer.setMinRamMegabytes(template.getMachineMemoryMB());
try {
validationContext.validationOngoingEvent(ValidationMessageType.ENTRY_VALIDATION_MESSAGE,
getFormattedMessage("validating_image_hardware_location_combination",
imageId == null ? "" : imageId, hardwareId == null ? "" : hardwareId,
locationId == null ? "" : locationId));
// calling JCloudsDeployer.getTemplate effectively tests the above configuration through jclouds
deployer.getTemplate(locationId);
validationContext.validationEventEnd(ValidationResultType.OK);
} catch (final Exception ex) {
validationContext.validationEventEnd(ValidationResultType.ERROR);
if (apiId.equalsIgnoreCase(OPENSTACK_API) && this.isVerboseValidation) {
validateLocationID(locationId);
validateHardwareID(hardwareId);
validateImageID(imageId);
}
throw new CloudProvisioningException(
getFormattedMessage("error_image_hardware_location_combination_validation",
imageId == null ? "" : imageId,
hardwareId == null ? "" : hardwareId, locationId == null ? "" : locationId,
groovyFile, propertiesFile), ex);
}
if (isKnownAPI(apiId)) {
validateSecurityGroupsForTemplate(template, apiId, deployer.getContext(), validationContext);
validateKeyPairForTemplate(template, apiId, deployer.getContext(), validationContext);
}
validationContext.validationOngoingEvent(ValidationMessageType.GROUP_VALIDATION_MESSAGE,
getFormattedMessage("template_validated", templateName));
validationContext.validationEventEnd(ValidationResultType.OK);
closeDeployer(deployer);
}
} finally {
closeDeployer(deployer);
}
}
private void validateImageID(final String imageId) throws CloudProvisioningException {
Image img = deployer.getContext().getComputeService().getImage(imageId);
if (img == null) {
Set<? extends Image> allImages = deployer.getAllImages();
StringBuilder sb = new StringBuilder();
sb.append(System.getProperty("line.separator"));
int index = 0;
for (Image image : allImages) {
if (index > MAX_VERBOSE_IDS_LENGTH) {
sb.append("etc...");
break;
}
index++;
sb.append(image.getId());
if (image.getName() != null) {
sb.append(" - ").append(image.getName());
}
sb.append(System.getProperty("line.separator"));
}
throw new CloudProvisioningException(
getFormattedMessage("error_image_id_validation",
imageId == null ? "" : imageId, sb.toString()));
}
}
private void validateHardwareID(final String hardwareId) throws CloudProvisioningException {
final Set<? extends Hardware> allHardwareProfiles = deployer.getContext()
.getComputeService().listHardwareProfiles();
final List<String> ids = new ArrayList<String>();
for (Hardware hardware : allHardwareProfiles) {
if (hardware.getId().equals(hardwareId)) {
return;
}
ids.add(hardware.getId());
}
final String message = createVerboseIdValidationMessage(ids);
throw new CloudProvisioningException(
getFormattedMessage("error_hardware_id_validation",
hardwareId == null ? "" : hardwareId, message));
}
private void validateLocationID(final String locationId)
throws CloudProvisioningException {
if (locationId != null) {
Set<? extends Location> allLocations = deployer.getAllLocations();
final List<String> ids = new ArrayList<String>();
for (Location location : allLocations) {
if (location.getId().equals(locationId)) {
return;
}
ids.add(location.getId());
}
String message = createVerboseIdValidationMessage(ids);
throw new CloudProvisioningException(
getFormattedMessage("error_location_id_validation",
locationId, message));
}
}
private String createVerboseIdValidationMessage(final List<String> ids) {
final StringBuilder sb = new StringBuilder();
sb.append(System.getProperty("line.separator"));
int index = 1;
for (String string : ids) {
if (index > MAX_VERBOSE_IDS_LENGTH) {
sb.append("etc...");
break;
}
sb.append(string);
sb.append(System.getProperty("line.separator"));
index++;
}
return sb.toString();
}
private void validateSecurityGroupsForTemplate(final ComputeTemplate template, final String apiId,
final ComputeServiceContext computeServiceContext, final ValidationContext validationContext)
throws CloudProvisioningException {
String locationId = template.getLocationId();
if (StringUtils.isBlank(locationId) && apiId.equalsIgnoreCase(OPENSTACK_API)) {
locationId = getOpenstackLocationByHardwareId(template.getHardwareId());
}
if (locationId == null) {
throw new CloudProvisioningException("locationId is missing");
}
Object securityGroupsObj = template.getOptions().get("securityGroupNames");
if (securityGroupsObj == null) {
securityGroupsObj = template.getOptions().get("securityGroups");
}
if (securityGroupsObj != null) {
if (securityGroupsObj instanceof String[]) {
final String[] securityGroupsArr = (String[]) securityGroupsObj;
if (securityGroupsArr.length > 0) {
try {
if (securityGroupsArr.length == 1) {
validationContext.validationOngoingEvent(ValidationMessageType.ENTRY_VALIDATION_MESSAGE,
getFormattedMessage("validating_security_group", securityGroupsArr[0]));
} else {
validationContext.validationOngoingEvent(
ValidationMessageType.ENTRY_VALIDATION_MESSAGE,
getFormattedMessage("validating_security_groups",
org.cloudifysource.esc.util.StringUtils.arrayToString(securityGroupsArr,
", ")));
}
if (apiId.equalsIgnoreCase(EC2_API)) {
final RestContext<EC2Client, EC2AsyncClient> unwrapped = computeServiceContext.unwrap();
validateEc2SecurityGroups(unwrapped.getApi(), locationId, securityGroupsArr);
} else if (apiId.equalsIgnoreCase(OPENSTACK_API)) {
final RestContext<NovaApi, NovaAsyncApi> unwrapped = computeServiceContext.unwrap();
validateOpenstackSecurityGroups(unwrapped.getApi(), locationId,
securityGroupsArr);
} else if (apiId.equalsIgnoreCase(CLOUDSTACK)) {
/*
* RestContext<CloudStackClient, CloudStackAsyncClient> unwrapped =
* computeServiceContext.unwrap();
* validateCloudstackSecurityGroups(unwrapped.getApi().getSecurityGroupClient(),
* aggregateAllValues(securityGroupsByRegions));
*/
} else if (apiId.equalsIgnoreCase(VCLOUD)) {
// security groups not supported
} else {
// api validations not supported yet
}
validationContext.validationEventEnd(ValidationResultType.OK);
} catch (final CloudProvisioningException ex) {
validationContext.validationEventEnd(ValidationResultType.ERROR);
throw ex;
}
}
} else {
// TODO : Validation not supported
}
}
}
private void validateKeyPairForTemplate(final ComputeTemplate template, final String apiId,
final ComputeServiceContext computeServiceContext, final ValidationContext validationContext)
throws CloudProvisioningException {
String locationId = template.getLocationId();
if (StringUtils.isBlank(locationId) && apiId.equalsIgnoreCase(OPENSTACK_API)) {
locationId = getOpenstackLocationByHardwareId(template.getHardwareId());
}
if (StringUtils.isBlank(locationId)) {
throw new CloudProvisioningException("locationId is missing");
}
Object keyPairObj = template.getOptions().get("keyPairName");
if (keyPairObj == null) {
keyPairObj = template.getOptions().get("keyPair");
}
if (keyPairObj != null) {
if (!(keyPairObj instanceof String)) {
throw new CloudProvisioningException("Invalid configuration: keyPair must be of type String");
}
final String keyPairString = (String) keyPairObj;
if (StringUtils.isNotBlank(keyPairString)) {
try {
validationContext.validationOngoingEvent(ValidationMessageType.ENTRY_VALIDATION_MESSAGE,
getFormattedMessage("validating_key_pair", keyPairString));
if (apiId.equalsIgnoreCase(EC2_API)) {
validateEC2KeyPair(computeServiceContext, locationId, keyPairString);
} else if (apiId.equalsIgnoreCase(OPENSTACK_API)) {
validateOpenstackKeyPair(computeServiceContext, locationId, keyPairString);
} else if (apiId.equalsIgnoreCase(CLOUDSTACK)) {
/*
* RestContext<CloudStackClient, CloudStackAsyncClient> unwrapped =
* computeServiceContext.unwrap();
* validateCloudstackKeyPairs(unwrapped.getApi().getSSHKeyPairClient(),
* aggregateAllValues(keyPairsByRegions));
*/
} else if (apiId.equalsIgnoreCase(VCLOUD)) {
// security groups not supported
} else {
// api validations not supported yet
}
validationContext.validationEventEnd(ValidationResultType.OK);
} catch (final CloudProvisioningException ex) {
validationContext.validationEventEnd(ValidationResultType.ERROR);
throw ex;
}
}
}
}
private void validateEC2KeyPair(final ComputeServiceContext computeServiceContext, final String locationId,
final String keyPairName) throws CloudProvisioningException {
final RestContext<EC2Client, EC2AsyncClient> unwrapped = computeServiceContext.unwrap();
final EC2Client ec2Client = unwrapped.getApi();
final KeyPairClient ec2KeyPairClient = ec2Client.getKeyPairServices();
final String region = JCloudsUtils.getEC2region(ec2Client, locationId);
final Set<KeyPair> foundKeyPairs = ec2KeyPairClient.describeKeyPairsInRegion(region, keyPairName);
if (foundKeyPairs == null || foundKeyPairs.size() == 0 || foundKeyPairs.iterator().next() == null) {
throw new CloudProvisioningException(getFormattedMessage("error_key_pair_validation", keyPairName,
groovyFile, propertiesFile));
}
}
private void validateOpenstackKeyPair(final ComputeServiceContext computeServiceContext, final String locationId,
final String keyPairName) throws CloudProvisioningException {
final RestContext<NovaApi, NovaAsyncApi> unwrapped = computeServiceContext.unwrap();
final KeyPairApi keyPairApi = unwrapped.getApi().getKeyPairExtensionForZone(locationId).get();
final Predicate<org.jclouds.openstack.nova.v2_0.domain.KeyPair> keyPairNamePredicate =
org.jclouds.openstack.nova.v2_0.predicates.KeyPairPredicates.nameEquals(keyPairName);
if (!keyPairApi.list().anyMatch(keyPairNamePredicate)) {
throw new CloudProvisioningException(getFormattedMessage("error_key_pair_validation", keyPairName,
groovyFile, propertiesFile));
}
}
private boolean isKnownAPI(final String apiName) {
boolean supported = false;
if (apiName.equalsIgnoreCase(EC2_API)
|| apiName.equalsIgnoreCase(OPENSTACK_API)) {
// || apiName.equalsIgnoreCase(VCLOUD)
// || apiName.equalsIgnoreCase(CLOUDSTACK)) {
supported = true;
}
return supported;
}
private void validateEc2SecurityGroups(final EC2Client ec2Client, final String locationId,
final String[] securityGroupsInRegion) throws CloudProvisioningException {
final String region = JCloudsUtils.getEC2region(ec2Client, locationId);
final org.jclouds.ec2.services.SecurityGroupClient ec2SecurityGroupsClient =
ec2Client.getSecurityGroupServices();
final Set<String> missingSecurityGroups = new HashSet<String>();
for (final String securityGroupName : securityGroupsInRegion) {
final Set<org.jclouds.ec2.domain.SecurityGroup> foundGroups =
ec2SecurityGroupsClient.describeSecurityGroupsInRegion(region, securityGroupName);
if (foundGroups == null || foundGroups.size() == 0 || foundGroups.iterator().next() == null) {
missingSecurityGroups.add(securityGroupName);
}
}
if (missingSecurityGroups.size() == 1) {
throw new CloudProvisioningException(getFormattedMessage("error_security_group_validation",
missingSecurityGroups.iterator().next(), groovyFile, propertiesFile));
} else if (missingSecurityGroups.size() > 1) {
throw new CloudProvisioningException(getFormattedMessage("error_security_groups_validation",
Arrays.toString(missingSecurityGroups.toArray()), groovyFile, propertiesFile));
}
}
private void validateOpenstackSecurityGroups(final NovaApi novaApi, final String region,
final String[] securityGroupsInRegion) throws CloudProvisioningException {
final Set<String> missingSecurityGroups = new HashSet<String>();
final SecurityGroupApi securityGroupApi = novaApi.getSecurityGroupExtensionForZone(region).get();
for (final String securityGroupName : securityGroupsInRegion) {
final Predicate<org.jclouds.openstack.nova.v2_0.domain.SecurityGroup> securityGroupNamePredicate =
org.jclouds.openstack.nova.v2_0.predicates.SecurityGroupPredicates.nameEquals(securityGroupName);
if (!securityGroupApi.list().anyMatch(securityGroupNamePredicate)) {
missingSecurityGroups.add(securityGroupName);
}
}
if (missingSecurityGroups.size() == 1) {
throw new CloudProvisioningException(getFormattedMessage("error_security_group_validation",
missingSecurityGroups.iterator().next(), groovyFile, propertiesFile));
} else if (missingSecurityGroups.size() > 1) {
throw new CloudProvisioningException(getFormattedMessage("error_security_groups_validation",
Arrays.toString(missingSecurityGroups.toArray()), groovyFile, propertiesFile));
}
}
private String getEndpoint(final ComputeTemplate template) {
String endpoint = null;
final Map<String, Object> templateOverrides = template.getOverrides();
if (templateOverrides != null && templateOverrides.size() > 0) {
endpoint = (String) templateOverrides.get(ENDPOINT_OVERRIDE);
}
return endpoint;
}
private void closeDeployer(final JCloudsDeployer jcloudsDeployer) {
if (jcloudsDeployer != null) {
logger.fine("Attempting to close cloud deployer");
jcloudsDeployer.close();
logger.fine("Cloud deployer closed");
}
}
private String getOpenstackLocationByHardwareId(final String hardwareId) {
String region = "";
if (!hardwareId.contains("/")) {
logger.info("HardwareId is: " + hardwareId + ". It must be formatted "
+ "as region / profile id");
throw new IllegalArgumentException("HardwareId is: " + hardwareId + ". It must be formatted "
+ "as region / profile id");
}
region = StringUtils.substringBefore(hardwareId, "/");
if (StringUtils.isBlank(region)) {
logger.info("HardwareId " + hardwareId + " is missing the region name. It must be formatted "
+ "as region / profile id");
throw new IllegalArgumentException("HardwareId is: " + hardwareId + ". It must be formatted "
+ "as region / profile id");
}
logger.fine("region: " + region);
return region;
}
/**
* returns the message as it appears in the DefaultProvisioningDriver message bundle.
*
* @param msgName
* the message key as it is defined in the message bundle.
* @param arguments
* the message arguments
* @return the formatted message according to the message key.
*/
protected String getFormattedMessage(final String msgName, final Object... arguments) {
return getFormattedMessage(getDefaultProvisioningDriverMessageBundle(), msgName, arguments);
}
/**
* Returns the message bundle of this cloud driver.
*
* @return the message bundle of this cloud driver.
*/
protected static ResourceBundle getDefaultProvisioningDriverMessageBundle() {
if (defaultProvisioningDriverMessageBundle == null) {
defaultProvisioningDriverMessageBundle = ResourceBundle.getBundle("DefaultProvisioningDriverMessages",
Locale.getDefault());
}
return defaultProvisioningDriverMessageBundle;
}
private void initIPFilters(final Cloud cloud) {
final ComputeTemplate template = cloud.getCloudCompute().getTemplates().get(
cloudTemplateName);
final String privateCidr =
(String) template.getCustom().get(PRIVATE_IP_CIDR);
if (!StringUtils.isBlank(privateCidr)) {
this.privateSubnetInfo = new SubnetUtils(privateCidr).getInfo();
}
final String privateRegex =
(String) template.getCustom().get(PRIVATE_IP_REGEX);
if (!StringUtils.isBlank(privateRegex)) {
this.privateIpPattern = Pattern.compile(privateRegex);
}
final String publicCidr =
(String) template.getCustom().get(PUBLIC_IP_CIDR);
if (!StringUtils.isBlank(publicCidr)) {
this.publicSubnetInfo = new SubnetUtils(publicCidr).getInfo();
}
final String publicRegex =
(String) template.getCustom().get(PUBLIC_IP_REGEX);
if (!StringUtils.isBlank(publicRegex)) {
this.publicIpPattern = Pattern.compile(publicRegex);
}
}
private String createExistingServersDescription(final String managementMachinePrefix,
final MachineDetails[] existingManagementServers) {
logger.info("Found existing servers matching the name: " + managementMachinePrefix);
final StringBuilder sb = new StringBuilder();
boolean first = true;
for (final MachineDetails machineDetails : existingManagementServers) {
final String existingManagementServerDescription = createManagementServerDescription(machineDetails);
if (first) {
first = false;
} else {
sb.append(", ");
}
sb.append("[").append(existingManagementServerDescription).append("]");
}
return sb.toString();
}
private String createManagementServerDescription(final MachineDetails machineDetails) {
final StringBuilder sb = new StringBuilder();
sb.append("Machine ID: ").append(machineDetails.getMachineId());
if (machineDetails.getPublicAddress() != null) {
sb.append(", Public IP: ").append(machineDetails.getPublicAddress());
}
if (machineDetails.getPrivateAddress() != null) {
sb.append(", Private IP: ").append(machineDetails.getPrivateAddress());
}
return sb.toString();
}
}