org.cloudifysource.esc.driver.provisioning.BaseProvisioningDriver.java Source code

Java tutorial

Introduction

Here is the source code for org.cloudifysource.esc.driver.provisioning.BaseProvisioningDriver.java

Source

/*******************************************************************************
 * Copyright (c) 2012 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;

import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.cloudifysource.dsl.cloud.Cloud;
import org.cloudifysource.dsl.cloud.compute.ComputeTemplate;
import org.cloudifysource.dsl.internal.CloudifyConstants;
import org.cloudifysource.esc.driver.provisioning.context.ProvisioningDriverClassContext;
import org.cloudifysource.esc.driver.provisioning.context.ProvisioningDriverClassContextAware;
import org.jclouds.util.CredentialUtils;
import org.openspaces.admin.Admin;

/**
 * @author noak
 * @since 2.0.1
 */
public abstract class BaseProvisioningDriver
        implements ProvisioningDriver, ProvisioningDriverClassContextAware, ProvisioningDriverBootstrapValidation {

    protected static final int MULTIPLE_SHUTDOWN_REQUEST_IGNORE_TIMEOUT = 120000;
    protected static final int WAIT_THREAD_SLEEP_MILLIS = 10000;
    protected static final int WAIT_TIMEOUT_MILLIS = 360000;
    // TODO - make this a configuration option
    protected static final int MAX_SERVERS_LIMIT = 200;

    protected static final String EVENT_ATTEMPT_CONNECTION_TO_CLOUD_API = "try_to_connect_to_cloud_api";
    protected static final String EVENT_ACCOMPLISHED_CONNECTION_TO_CLOUD_API = "connection_to_cloud_api_succeeded";
    protected static final String EVENT_ATTEMPT_START_MGMT_VMS = "attempting_to_create_management_vms";
    protected static final String EVENT_MGMT_VMS_STARTED = "management_started_successfully";
    protected static final String AGENT_MACHINE_PREFIX = "cloudify-agent-";
    protected static final String MANAGMENT_MACHINE_PREFIX = "cloudify-managememnt-";

    protected boolean management;
    protected static AtomicInteger counter = new AtomicInteger();
    protected String serverNamePrefix;
    protected String cloudName;
    protected String cloudTemplateName;
    protected Admin admin;
    protected Cloud cloud;
    protected ProvisioningDriverClassContext context;

    protected final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(this.getClass().getName());

    protected final List<ProvisioningDriverListener> eventsListenersList = new LinkedList<ProvisioningDriverListener>();
    protected Boolean cleanRemoteDirectoryOnStart = false;

    /**
     * Initializing the cloud deployer according to the given cloud
     * configuration.
     *
     * @param cloud
     *            Cloud object to use
     */
    protected abstract void initDeployer(final Cloud cloud);

    @Override
    public void setProvisioningDriverClassContext(final ProvisioningDriverClassContext context) {
        this.context = context;
    }

    @Override
    public String getCloudName() {
        return this.cloudName;
    }

    @Override
    public void setAdmin(final Admin admin) {
        this.admin = admin;

    }

    @Override
    public void addListener(final ProvisioningDriverListener pdl) {
        this.eventsListenersList.add(pdl);
    }

    @Override
    public void setConfig(final Cloud cloud, final String cloudTemplateName, final boolean management,
            final String serviceName) throws CloudProvisioningException {

        this.cloud = cloud;
        this.cloudTemplateName = cloudTemplateName;
        this.management = management;
        this.cloudName = cloud.getName();

        publishEvent(EVENT_ATTEMPT_CONNECTION_TO_CLOUD_API, cloud.getProvider().getProvider());
        initDeployer(cloud);
        publishEvent(EVENT_ACCOMPLISHED_CONNECTION_TO_CLOUD_API, cloud.getProvider().getProvider());

        logger.fine("Initializing Cloud Provisioning - management mode: " + management + ". Using template: "
                + cloudTemplateName + " with cloud: " + cloudName);

        String prefix = management ? cloud.getProvider().getManagementGroup()
                : cloud.getProvider().getMachineNamePrefix();

        if (StringUtils.isBlank(prefix)) {
            if (management) {
                prefix = MANAGMENT_MACHINE_PREFIX;
            } else {
                prefix = AGENT_MACHINE_PREFIX;
            }

            logger.warning("Prefix for machine name was not set. Using: " + prefix);
        }

        this.serverNamePrefix = prefix;

        initCleanRemoteOnStart(cloud);
    }

    private void initCleanRemoteOnStart(final Cloud cloud) {
        // set custom settings
        final Map<String, Object> customSettings = cloud.getCustom();
        if (customSettings != null) {
            // clean GS files on shutdown
            if (customSettings.containsKey(CloudifyConstants.CUSTOM_PROPERTY_CLEAN_REMOTE_DIR_ON_START)) {
                final Object cleanRemoteDirValue = customSettings
                        .get(CloudifyConstants.CUSTOM_PROPERTY_CLEAN_REMOTE_DIR_ON_START);
                if (cleanRemoteDirValue instanceof Boolean) {
                    this.cleanRemoteDirectoryOnStart = (Boolean) cleanRemoteDirValue;
                } else if (cleanRemoteDirValue instanceof String) {
                    this.cleanRemoteDirectoryOnStart = Boolean.parseBoolean((String) cleanRemoteDirValue);
                } else {
                    throw new IllegalArgumentException("Unexpected value for BYON property: "
                            + CloudifyConstants.CUSTOM_PROPERTY_CLEAN_REMOTE_DIR_ON_START
                            + ". Was expecting a boolean or String, got: "
                            + cleanRemoteDirValue.getClass().getName());
                }
            }
        }
    }

    private void logServerDetails(final MachineDetails machineDetails, final File tempFile) {
        if (logger.isLoggable(Level.FINE)) {
            final String nodePrefix = "[" + machineDetails.getMachineId() + "] ";
            logger.fine(nodePrefix + "Cloud Server is allocated.");
            if (tempFile == null) {
                logger.fine(nodePrefix + "Password: ***");
            } else {
                logger.fine(nodePrefix + "Key File: " + tempFile.getAbsolutePath());
            }

            if (logger.isLoggable(Level.FINE)) {
                logger.fine("Private IP: " + machineDetails.getPrivateAddress());
                logger.fine("Public IP: " + machineDetails.getPublicAddress());
            }
        }
    }

    /**
     * Handles credentials for accessing the server - in this order: 1. pem file
     * (set as a key file on the user block in the groovy file) 2. machine's
     * remote password (set previously by the cloud driver)
     *
     * @param machineDetails
     *            The MachineDetails object that represents this server
     * @param template
     *            the cloud template.
     * @throws CloudProvisioningException
     *             Indicates missing credentials or IOException (when a key file
     *             is used)
     */
    protected void handleServerCredentials(final MachineDetails machineDetails, final ComputeTemplate template)
            throws CloudProvisioningException {

        File keyFile = null;
        // using a key (pem) file
        if (machineDetails.getKeyFile() != null) {
            keyFile = machineDetails.getKeyFile();
            if (!keyFile.isFile()) {
                throw new CloudProvisioningException(
                        "The specified key file could not be found: " + keyFile.getAbsolutePath());
            }
        } else if (StringUtils.isNotBlank(template.getKeyFile())) {
            final String keyFileStr = template.getKeyFile();
            // fixConfigRelativePaths(cloud, template);
            keyFile = new File(keyFileStr);
            if (!keyFile.isAbsolute()) {
                keyFile = new File(template.getAbsoluteUploadDir(), keyFileStr);
            }
            if (keyFile != null && !keyFile.exists()) {
                throw new CloudProvisioningException(
                        "The specified key file could not be found: " + keyFile.getAbsolutePath());
            }
        } else {
            // using a password
            final String remotePassword = machineDetails.getRemotePassword();
            if (StringUtils.isNotBlank(remotePassword)) {
                // is this actually a pem file?
                if (CredentialUtils.isPrivateKeyCredential(remotePassword)) {
                    logger.fine("Cloud has provided a key file for connections to new machines");
                    try {
                        keyFile = File.createTempFile("gs-esm-key", ".pem");
                        keyFile.deleteOnExit();
                        FileUtils.write(keyFile, remotePassword);

                        // template.setKeyFile(keyFile.getAbsolutePath());
                        machineDetails.setKeyFile(keyFile);
                    } catch (final IOException e) {
                        throw new CloudProvisioningException(
                                "Failed to create a temporary " + "file for cloud server's key file", e);
                    }
                } else {
                    // this is a password
                    logger.fine("Cloud has provided a password for remote connections to new machines");
                }
            } else {
                // if we got here - there is no key file or password on the
                // cloud or node.
                logger.severe("No Password or key file specified in the cloud configuration file - connection to"
                        + " the new machine is not possible.");
                throw new CloudProvisioningException(
                        "No credentials (password or key file) supplied with the cloud configuration file");
            }
        }

        logServerDetails(machineDetails, keyFile);
    }

    /**
     * Publish a provisioning event occurred for the listeners registered on
     * this class.
     *
     * @param eventName
     *            The name of the event (must be in the message bundle)
     * @param args
     *            Arguments that complement the event message
     */
    protected void publishEvent(final String eventName, final Object... args) {
        for (final ProvisioningDriverListener listener : this.eventsListenersList) {
            listener.onProvisioningEvent(eventName, args);
        }
    }

    /*********
     * Created a machine details with basic settings from the given cloud
     * template.
     *
     * @param template
     *            the cloud template.
     * @return the newly created machine details.
     */
    protected MachineDetails createMachineDetailsForTemplate(final ComputeTemplate template) {

        final MachineDetails md = new MachineDetails();
        md.setAgentRunning(false);
        md.setCloudifyInstalled(false);
        md.setInstallationDirectory(null);

        md.setRemoteUsername(template.getUsername());
        md.setRemotePassword(template.getPassword());

        md.setRemoteExecutionMode(template.getRemoteExecution());
        md.setFileTransferMode(template.getFileTransfer());
        md.setScriptLangeuage(template.getScriptLanguage());
        md.setCleanRemoteDirectoryOnStart(this.cleanRemoteDirectoryOnStart);
        return md;

    }

    /*********
     * .
     * @param endTime .
     * @param numberOfManagementMachines .
     * @return .
     * @throws TimeoutException .
     * @throws CloudProvisioningException .
     */
    protected MachineDetails[] doStartManagementMachines(final long endTime, final int numberOfManagementMachines)
            throws TimeoutException, CloudProvisioningException {
        final ExecutorService executors = Executors.newFixedThreadPool(numberOfManagementMachines);

        @SuppressWarnings("unchecked")
        final Future<MachineDetails>[] futures = (Future<MachineDetails>[]) new Future<?>[numberOfManagementMachines];

        final ComputeTemplate managementTemplate = this.cloud.getCloudCompute().getTemplates()
                .get(this.cloud.getConfiguration().getManagementMachineTemplate());
        try {
            // Call startMachine asynchronously once for each management machine
            for (int i = 0; i < numberOfManagementMachines; i++) {
                final int index = i + 1;
                futures[i] = executors.submit(new Callable<MachineDetails>() {

                    @Override
                    public MachineDetails call() throws Exception {
                        return createServer(serverNamePrefix + index, endTime, managementTemplate);
                    }
                });

            }

            // Wait for each of the async calls to terminate.
            int numberOfErrors = 0;
            Exception firstCreationException = null;
            final MachineDetails[] createdManagementMachines = new MachineDetails[numberOfManagementMachines];
            for (int i = 0; i < createdManagementMachines.length; i++) {
                try {
                    createdManagementMachines[i] = futures[i].get(endTime - System.currentTimeMillis(),
                            TimeUnit.MILLISECONDS);
                } catch (final InterruptedException e) {
                    ++numberOfErrors;
                    publishEvent("failed_to_create_management_vm", e.getMessage());
                    logger.log(Level.SEVERE, "Failed to start a management machine", e);
                    if (firstCreationException == null) {
                        firstCreationException = e;
                    }

                } catch (final ExecutionException e) {
                    ++numberOfErrors;
                    publishEvent("failed_to_create_management_vm", e.getMessage());
                    logger.log(Level.SEVERE, "Failed to start a management machine", e);
                    if (firstCreationException == null) {
                        firstCreationException = e;
                    }
                }
            }

            // In case of a partial error, shutdown all servers that did start up
            if (numberOfErrors > 0) {
                handleProvisioningFailure(numberOfManagementMachines, numberOfErrors, firstCreationException,
                        createdManagementMachines);
            }

            return createdManagementMachines;
        } finally {
            if (executors != null) {
                executors.shutdownNow();
            }
        }
    }

    /**
     * returns the message as it appears in the DefaultProvisioningDriver message bundle.
     *
     * @param messageBundle
     *            The message bundle containing the specified message
     * @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 ResourceBundle messageBundle, final String msgName,
            final Object... arguments) {
        final String message = messageBundle.getString(msgName);
        if (message == null) {
            logger.warning("Missing resource in messages resource bundle: " + msgName);
            return msgName;
        }
        try {
            return MessageFormat.format(message, arguments);
        } catch (final IllegalArgumentException e) {
            logger.fine("Failed to format message: " + msgName + " with format: " + message + " and arguments: "
                    + Arrays.toString(arguments));
            return msgName;
        }
    }

    protected abstract MachineDetails createServer(String serverName, long endTime, ComputeTemplate template)
            throws CloudProvisioningException, TimeoutException;

    protected abstract void handleProvisioningFailure(int numberOfManagementMachines, int numberOfErrors,
            Exception firstCreationException, MachineDetails[] createdManagementMachines)
            throws CloudProvisioningException;

    @Override
    public void onServiceUninstalled(long duration, TimeUnit unit)
            throws InterruptedException, TimeoutException, CloudProvisioningException {

    }
}