/******************************************************************************* * 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(); } }