/* * ****************************************************************************** * * 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.jclouds.softlayer; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; import org.cloudifysource.domain.cloud.compute.ComputeTemplate; import org.cloudifysource.esc.driver.provisioning.CloudProvisioningException; import org.cloudifysource.esc.driver.provisioning.MachineDetails; import org.cloudifysource.esc.driver.provisioning.ProvisioningContext; import org.cloudifysource.esc.driver.provisioning.jclouds.DefaultProvisioningDriver; import org.cloudifysource.esc.util.Utils; import org.jclouds.softlayer.compute.functions.guest.VirtualGuestToNodeMetadata; import org.jclouds.softlayer.compute.functions.guest.VirtualGuestToReducedNodeMetaData; import org.jclouds.softlayer.reference.SoftLayerConstants; import com.google.inject.AbstractModule; import com.google.inject.Module; /** * This driver injects a custom module to jclouds in order to boost poor performance on softlayer. * * @author Eli Polonsky * @since 2.7.0 */ public class SoftlayerProvisioningDriver extends DefaultProvisioningDriver { private static final String CLEANUP_SCRIPT_ON_MACHINE_FAILURE = "cleanupScriptOnMachineFailure"; private static final String FAILED_MACHINE_PRIVATE_ADDRESS = "FAILED_MACHINE_PRIVATE_ADDRESS"; private static final String FAILED_MACHINE_PUBLIC_ADDRESS = "FAILED_MACHINE_PUBLIC_ADDRESS"; private static final String FAILED_MACHINE_ID = "FAILED_MACHINE_ID"; private static final String FAILED_MACHINE_LOCATION_ID = "FAILED_MACHINE_LOCATION_ID"; private static final String FAILED_MACHINE_ENV_VARS = "FAILED_MACHINE_ENV_VARS"; @Override public Set<Module> setupModules(final String templateName, final ComputeTemplate template) { Set<Module> modules = super.setupModules(templateName, template); int packageId = Utils.getInteger(template.getOverrides() .get(SoftLayerConstants.PROPERTY_SOFTLAYER_PACKAGE_ID), 46); if (packageId == 46) { // We are using virtual guests modules.add(new AbstractModule() { @Override protected void configure() { bind(VirtualGuestToNodeMetadata.class).to(VirtualGuestToReducedNodeMetaData.class); } }); } return modules; } @Override public void onMachineFailure(final ProvisioningContext context, final long duration, final TimeUnit unit) throws CloudProvisioningException, TimeoutException { logger.finest("Handling compute resources following machine failure"); // customary call to the super implementation super.onMachineFailure(context, duration, unit); runCleanupScriptOnMachineFailure(context.getPreviousMachineDetails()); } private void runCleanupScriptOnMachineFailure(final MachineDetails failedMachineDetails) throws CloudProvisioningException { String cleanupScript = getCleanupScriptValue(); File scriptFile = new File(cleanupScript); if (!scriptFile.isFile()) { String errMsg = "The cleanup script file denoted by \"" + cleanupScript + "\" does not exist or is not a file"; logger.warning(errMsg); throw new CloudProvisioningException(errMsg); } // run the script and pass the machine details of the failed machine as env vars logger.info("Executing cleanup script following failure of machine: " + failedMachineDetails.toString()); Map<String, String> commandEnvVars = machineDetailsToEnvVars(failedMachineDetails); try { runScriptInSubProcess(scriptFile.getAbsolutePath(), commandEnvVars); } catch (Exception e) { String errMsg = "An error encountered during the execution of cleanup script \"" + cleanupScript + "\", " + e.getMessage(); throw new CloudProvisioningException(errMsg, e); } } private String getCleanupScriptValue() { String cleanupScriptValue = ""; final Map<String, Object> customSettings = cloud.getCustom(); if (customSettings != null) { // get cleanup script if set if (customSettings.containsKey(CLEANUP_SCRIPT_ON_MACHINE_FAILURE)) { final Object cleanupScriptObj = customSettings.get(CLEANUP_SCRIPT_ON_MACHINE_FAILURE); if (cleanupScriptObj != null) { if (cleanupScriptObj instanceof String) { cleanupScriptValue = (String) cleanupScriptObj; } else { throw new IllegalArgumentException("Unexpected value for Softlayer cloud driver property: " + CLEANUP_SCRIPT_ON_MACHINE_FAILURE + ". A String value was expected, but got: " + cleanupScriptValue.getClass().getName()); } } } } return cleanupScriptValue; } private Map<String, String> machineDetailsToEnvVars(final MachineDetails failedMachineDetails) { Map<String, String> envVars = new HashMap<String, String>(); envVars.put(FAILED_MACHINE_PRIVATE_ADDRESS, failedMachineDetails.getPrivateAddress()); envVars.put(FAILED_MACHINE_PUBLIC_ADDRESS, failedMachineDetails.getPublicAddress()); envVars.put(FAILED_MACHINE_ID, failedMachineDetails.getMachineId()); envVars.put(FAILED_MACHINE_LOCATION_ID, failedMachineDetails.getLocationId()); envVars.put(FAILED_MACHINE_ENV_VARS, Arrays.toString(failedMachineDetails.getEnvironment().entrySet().toArray())); return envVars; } private void runScriptInSubProcess(final String command, final Map<String, String> envVars) throws IOException { String cmdLine = command; if (isWindows()) { // need to use the call command to intercept the cloudify batch file return code. cmdLine = "cmd /c call " + cmdLine; } final String[] parts = cmdLine.split(" "); final ProcessBuilder pb = new ProcessBuilder(parts); pb.redirectErrorStream(true); // apply additional environment variables if set for (Entry<String, String> envVar : envVars.entrySet()) { pb.environment().put(envVar.getKey(), envVar.getValue()); } logger.info("command " + command + " will be executed with env: \"" + pb.environment() + "\""); final Process process = pb.start(); // handle process results ProcessResult processResult = getProcessResult(process); if (processResult.getExitcode() == 0) { logger.info("script output: " + processResult.getOutput()); } else { logger.warning("Execution of cleanup script " + command + "did not end succesfully, exit code was " + processResult.getExitcode() + "output is " + processResult.getOutput()); } } private ProcessResult getProcessResult(final Process process) { // Print CLI output if exists. final BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream())); final StringBuilder consoleOutput = new StringBuilder(""); final AtomicReference<Throwable> exception = new AtomicReference<Throwable>(); int exitcode = -1; try { Thread thread = new Thread(new Runnable() { String line = null; @Override public void run() { try { while ((line = br.readLine()) != null) { consoleOutput.append(line + "\n"); } } catch (Throwable e) { exception.set(e); } } }); thread.setDaemon(true); thread.start(); exitcode = process.waitFor(); thread.join(5000); } catch (InterruptedException e) { logger.warning("Failed to get process output. output = " + consoleOutput + ", reported error: " + exception.get().getMessage()); } if (exception.get() != null) { logger.warning("Failed to get process output. output = " + consoleOutput + ", reported error: " + exception.get().getMessage()); } String stdout = consoleOutput.toString(); return new ProcessResult(stdout, exitcode); } protected class ProcessResult { private final String output; private final int exitcode; public ProcessResult(final String output, final int exitcode) { this.output = output; this.exitcode = exitcode; } @Override public String toString() { return "ProcessResult [output=" + getOutput() + ", exitcode=" + getExitcode() + "]"; } public String getOutput() { return output; } public int getExitcode() { return exitcode; } } public static boolean isWindows() { return System.getProperty("os.name").toLowerCase().startsWith("win"); } }