/*******************************************************************************
* 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.jclouds;
import com.google.common.base.Predicate;
import com.google.inject.AbstractModule;
import com.google.inject.Module;
import org.cloudifysource.esc.driver.provisioning.CloudProvisioningException;
import org.cloudifysource.esc.driver.provisioning.MachineDetails;
import org.cloudifysource.esc.installer.InstallerException;
import org.jclouds.ContextBuilder;
import org.jclouds.aws.ec2.compute.strategy.AWSEC2ReviseParsedImage;
import org.jclouds.compute.ComputeServiceContext;
import org.jclouds.compute.RunNodesException;
import org.jclouds.compute.domain.ComputeMetadata;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.Template;
import org.jclouds.compute.domain.TemplateBuilder;
import org.jclouds.compute.options.TemplateOptions;
import org.jclouds.domain.Location;
import org.jclouds.logging.jdk.config.JDKLoggingModule;
import org.jclouds.rest.ResourceNotFoundException;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
/************
* A JClouds based deployer that creates and queries JClouds complaint servers. All of the JClouds features used in the
* cloud ESM Machine Provisioning are called from this class.
*
*
*
* @author barakme
*
*/
public class JCloudsDeployer {
private static final long SHUTDOWN_MANAGEMENT_MACHINE_DEFAULT_TIMEOUT_IN_SECONDS = 60 * 5;
private static final int SHUTDOWN_WAIT_POLLING_INTERVAL_MILLIS = 1000;
private static java.util.logging.Logger logger = java.util.logging.Logger
.getLogger(JCloudsDeployer.class.getName());
private static final int DEFAULT_MIN_RAM_MB = 0;
private static final String DEFAULT_IMAGE_ID_RACKSPACE = "51";
private static final long RETRY_SLEEP_TIMEOUT_IN_MILLIS = 5000;
private static final int NUMBER_OF_RETRY_ATTEMPTS = 2;
private int minRamMegabytes = DEFAULT_MIN_RAM_MB;
private String imageId = DEFAULT_IMAGE_ID_RACKSPACE;
private ComputeServiceContext context;
private String hardwareId;
private Map<String, Object> extraOptions;
private final String provider;
private final String account;
private final String key;
private final Properties overrides;
public void close() {
this.context.close();
}
public String getHardwareId() {
return hardwareId;
}
public void setHardwareId(final String hardwareId) {
this.hardwareId = hardwareId;
}
public Properties getOverrides() {
return overrides;
}
public Map<String, Object> getExtraOptions() {
return extraOptions;
}
public void setExtraOptions(final Map<String, Object> extraOptions) {
this.extraOptions = extraOptions;
}
public ComputeServiceContext getContext() {
return context;
}
public String getImageId() {
return imageId;
}
public int getMinRamMegabytes() {
return minRamMegabytes;
}
public JCloudsDeployer(final String provider,
final String account,
final String key) throws IOException {
this(provider, account, key, new Properties(), new HashSet<Module>());
}
public JCloudsDeployer(final String provider,
final String account,
final String key,
final Properties overrides,
final Set<Module> modules) throws IOException {
this.provider = provider;
this.account = account;
this.key = key;
this.overrides = overrides;
modules.add(new AbstractModule() {
@Override
protected void configure() {
bind(
AWSEC2ReviseParsedImage.class).to(
WindowsServerEC2ReviseParsedImage.class);
}
});
// Enable logging using gs_logging
modules.add(new JDKLoggingModule());
this.context = ContextBuilder.newBuilder(provider)
.credentials(account, key)
.modules(modules)
.overrides(overrides)
.buildView(ComputeServiceContext.class);
}
/**********
* Starts up a server based on the deployer's template, and returns its meta data. The server may not have started
* yet when this call returns.
*
* @param serverName server name.
* @param locationId the id of the location in which to create the server.
* @return the new server meta data.
* @throws CloudProvisioningException .
*/
public NodeMetadata createServer(final String serverName,
final String locationId) throws CloudProvisioningException {
Set<? extends NodeMetadata> nodes = null;
try {
logger.fine("Creating a new server with tag: " + serverName + ". This may take a few minutes");
nodes = createServersWithRetry(serverName, 1, getTemplate(locationId));
} catch (final RunNodesException e) {
// if there are nodes in the returned maps - kill them
removeLeakingServersAfterCreationException(e);
throw new CloudProvisioningException("Failed to start machine", e);
}
if (nodes.isEmpty()) {
throw new IllegalStateException("Failed to create server");
}
if (nodes.size() > 1) {
throw new IllegalStateException("Created too many servers");
}
return nodes.iterator().next();
}
/***********
* Creates the specified number of servers from the given template.
*
* @param groupName server group name.
* @param numberOfMachines number of machines.
* @param locationId the id of the location in which to create the server.
* @return the created nodes.
* @throws InstallerException if creation of one or more nodes failed.
*/
public Set<? extends NodeMetadata> createServers(final String groupName, final int numberOfMachines,
final String locationId) throws InstallerException {
Set<? extends NodeMetadata> nodes = null;
try {
logger.fine("JClouds Deployer is creating new machines with group: " + groupName
+ ". This may take a few minutes");
nodes = createServersWithRetry(groupName, numberOfMachines, getTemplate(locationId));
} catch (final RunNodesException e) {
// if there are nodes in the returned maps - kill them
removeLeakingServersAfterCreationException(e);
throw new InstallerException("Failed to start Cloud server with JClouds", e);
}
if (nodes.isEmpty()) {
throw new IllegalStateException("Failed to create machines");
}
if (nodes.size() > numberOfMachines) {
throw new IllegalStateException("Created too manys machines");
}
return nodes;
}
/********
* Creates a server with the given name and template.
*
* @param serverName the server name.
* @param template the template.
* @return the server meta data.
* @throws RunNodesException .
*/
public NodeMetadata createServer(final String serverName, final Template template) throws RunNodesException {
try {
final Set<? extends NodeMetadata> nodes = createServersWithRetry(
serverName, 1, template);
if (nodes.isEmpty()) {
return null;
}
if (nodes.size() != 1) {
throw new IllegalStateException();
}
return nodes.iterator().next();
} catch (RunNodesException e) {
// if there are nodes in the returned maps - kill them
removeLeakingServersAfterCreationException(e);
throw e;
}
}
/*********
* Cleans up the deployer's resources.
*/
@PreDestroy
public void destroy() {
context.close();
}
/********
* Useful testing method for cloud tests. In production, you should use a template.
*
* @param name the server name.
* @return the node meta data.
* @throws RunNodesException .
*/
public Set<? extends NodeMetadata> createDefaultServer(final String name) throws RunNodesException {
return this.context.getComputeService().createNodesInGroup(name, 1);
}
public Set<? extends Image> getAllImages() {
return this.context.getComputeService().listImages();
}
public Set<? extends ComputeMetadata> getAllServers() {
return this.context.getComputeService().listNodes();
}
public Set<? extends Location> getAllLocations() {
return this.context.getComputeService().listAssignableLocations();
}
/*******
* Queries the cloud for a single server that matches the given critetia. If more then one is returned, throws an
* exception.
*
* @param filter the query filter.
* @return the node meta data, or null if no match is found.
*/
public NodeMetadata getServer(final Predicate<ComputeMetadata> filter) {
final Set<? extends NodeMetadata> nodes = getServers(filter);
final Set<NodeMetadata> runningNodes = new HashSet<NodeMetadata>();
final Iterator<? extends NodeMetadata> nodesIterator = nodes.iterator();
while (nodesIterator.hasNext()) {
final NodeMetadata node = nodesIterator.next();
if (node.getStatus() != NodeMetadata.Status.TERMINATED) {
runningNodes.add(node);
}
}
if (runningNodes.isEmpty()) {
return null;
}
if (runningNodes.size() != 1) {
throw new IllegalStateException("runningNodes.size() == " + runningNodes.size() + " != 1");
}
return runningNodes.iterator().next();
}
/********
* Looks for a server with an ID that matches the given ID.
*
* @param serverID the server ID.
* @return the node meta data, or null.
*/
public NodeMetadata getServerByID(final String serverID) {
return this.context.getComputeService().getNodeMetadata(serverID);
}
/*********
* Queries for a server whose name STARTS WITH the given name. Note that underscores in the name ('_') are replaced
* with blanks.
*
* @param serverName the server name.
* @return node meta data
*/
public NodeMetadata getServerByName(final String serverName) {
final String adaptedServerName = serverName.replace("_", "") + "-";
final Predicate<ComputeMetadata> filter = new Predicate<ComputeMetadata>() {
@Override
public boolean apply(final ComputeMetadata compute) {
if (compute.getName() == null) {
return false;
}
return compute.getName().startsWith(adaptedServerName);
}
};
return getServer(filter);
}
/*******************
* Returns all nodes that match the given criteria.
*
* @param filter the filter criteria.
* @return the nodes.
*/
public Set<? extends NodeMetadata> getServers(final Predicate<ComputeMetadata> filter) {
return this.context.getComputeService().listNodesDetailsMatching(filter);
}
/*******************
* Returns all nodes that match the group provided.
*
* @param group the group.
* @return the nodes.
*/
public Set<? extends NodeMetadata> getServers(final String group) {
return getServers(new Predicate<ComputeMetadata>() {
@Override
public boolean apply(final ComputeMetadata input) {
final NodeMetadata node = (NodeMetadata) input;
return node.getGroup() != null && node.getGroup().equals(group);
}
});
}
/***********
* Returns a server whose private or public IPs contain the given IP.
*
* @param ip the IP to look for.
* @return the node meta data, or null.
*/
public NodeMetadata getServerWithIP(final String ip) {
final Predicate<ComputeMetadata> filter = new Predicate<ComputeMetadata>() {
@Override
public boolean apply(final ComputeMetadata compute) {
final NodeMetadata node = (NodeMetadata) compute;
return node.getPrivateAddresses().contains(ip) || node.getPublicAddresses().contains(ip);
}
};
return getServer(filter);
}
/************
* Returns the default template used to create new servers. Note that if the template has not been instantiated yet,
* it will be done in this call.
*
* @return the template.
*/
public Template getTemplate(String locationId) {
logger.fine("Creating Cloud Template with locationId = " + locationId + ". This may take a few seconds");
final TemplateBuilder builder = this.context.getComputeService().templateBuilder();
if (imageId != null && !imageId.isEmpty()) {
builder.imageId(imageId);
}
if (minRamMegabytes > 0) {
builder.minRam(minRamMegabytes);
}
if (hardwareId != null && !hardwareId.isEmpty()) {
builder.hardwareId(hardwareId);
}
if (locationId != null && !locationId.isEmpty()) {
builder.locationId(locationId);
}
// this is usually a remote call, and may take a while to return.
Template template = builder.build();
handleExtraOptions(template);
logger.fine("Cloud Template is ready for use. " + template);
return template;
}
private void handleExtraOptions(Template template) {
if (this.extraOptions != null) {
// use reflection to set extra options
final Set<Entry<String, Object>> optionEntries = this.extraOptions.entrySet();
final TemplateOptions templateOptions = template.getOptions();
final Method[] templateOptionsMethods = templateOptions.getClass().getMethods();
for (final Entry<String, Object> entry : optionEntries) {
final String entryKey = entry.getKey();
final Object entryValue = entry.getValue();
if (entryValue == null) {
handleNullValueTemplateOption(
optionEntries, templateOptions, entry, entryKey);
} else {
final boolean found = handleSingleParameterOption(
templateOptions, entryKey, entryValue, templateOptionsMethods);
if (!found) {
if (entryValue instanceof List<?>) {
handleListParameterOption(
templateOptions, entryKey, entryValue, templateOptionsMethods);
} else {
throw new IllegalArgumentException(
"Could not find a template option method matching name: " + entryKey
+ " and value: " + entryValue + ".");
}
}
}
}
}
}
private void handleListParameterOption(final TemplateOptions templateOptions, final String entryKey,
final Object entryValue, final Method[] templateOptionMethods) {
// no method accepts a list - try for a method that
// takes a parameter for each list entry
@SuppressWarnings("unchecked")
final List<Object> paramList = (List<Object>) entryValue;
final Object[] paramArray = paramList.toArray();
for (Method m : templateOptionMethods) {
if (m.getName().equals(entryKey)) {
if (m.getParameterTypes().length == paramList.size()) {
try {
m.invoke(templateOptions, paramArray);
return;
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Failed to set option: " + entryKey
+ " by invoking method: "
+ m + " with value: " + Arrays.toString(paramArray) + ". Error was: " + e.getMessage(), e);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException("Failed to set option: " + entryKey
+ " by invoking method: "
+ m + " with value: " + Arrays.toString(paramArray) + ". Error was: " + e.getMessage(), e);
} catch (InvocationTargetException e) {
throw new IllegalArgumentException("Failed to set option: " + entryKey
+ " by invoking method: "
+ m + " with value: " + Arrays.toString(paramArray) + ". Error was: " + e.getMessage(), e);
}
}
}
}
}
private boolean handleSingleParameterOption(final TemplateOptions templateOptions, final String entryKey,
final Object entryValue, final Method[] templateOptionsMethods) {
int numOfMethodsFound = 0;
Exception invocationException = null;
for (final Method method : templateOptionsMethods) {
if (method.getName().equals(
entryKey)) {
if (method.getParameterTypes().length == 1) {
try {
++numOfMethodsFound;
logger.fine("Invoking " + entryKey + ". Number of methods found so far: " + numOfMethodsFound);
method.invoke(
templateOptions, entryValue);
// invoked successfully
return true;
} catch (final IllegalArgumentException e) {
invocationException = e;
} catch (final IllegalAccessException e) {
invocationException = e;
} catch (final InvocationTargetException e) {
invocationException = e;
}
}
}
}
// If I am here, either no method was found, or an exception was thrown
if (invocationException == null) {
return false;
// throw new IllegalArgumentException("Failed to set template option: " + entryKey + " to value: "
// + entryValue + ". Could not find a matching method.");
} else {
throw new IllegalArgumentException("Failed to set template option: " + entryKey + " to value: "
+ entryValue + ". An error was encountered while trying to set the option: "
+ invocationException.getMessage(), invocationException);
}
}
private void handleNullValueTemplateOption(final Set<Entry<String, Object>> optionEntries,
final TemplateOptions templateOptions, final Entry<String, Object> entry, final String entryKey) {
// first look for no arg method
Method m = null;
try {
m = optionEntries.getClass().getMethod(
entryKey);
// got the method
} catch (final SecurityException e) {
throw new IllegalArgumentException("Error while looking for method to match template option: " + entryKey,
e);
} catch (final NoSuchMethodException e) {
// ignore - method was not found
}
if (m != null) {
// Found a no-arg method for this option
try {
// invoke with no args
m.invoke(templateOptions);
} catch (final Exception e) {
throw new IllegalArgumentException("Failed to set template option with name: " + entryKey, e);
}
} else {
// look for a matching method with a single argument
try {
m = optionEntries.getClass().getMethod(
entryKey, Object.class);
// got the method
} catch (final SecurityException e) {
throw new IllegalArgumentException("Error while looking for method to match template option: "
+ entryKey, e);
} catch (final NoSuchMethodException e) {
// ignore - method was not found
}
// invoke with a null parameter
if (m != null) {
try {
m.invoke(
templateOptions, (Object) null);
} catch (final Exception e) {
throw new IllegalArgumentException("Failed to set template option with name: " + entryKey
+ " to value: null", e);
}
} else {
throw new IllegalArgumentException("Could not find a method matching template option: "
+ entry.getKey());
}
}
}
public void setImageId(final String imageId) {
this.imageId = imageId;
}
public void setMinRamMegabytes(final int minRamMegabytes) {
this.minRamMegabytes = minRamMegabytes;
}
/* CHECKSTYLE:OFF */
public void setMinRamMegabytes(final String minRamMegabytes_str) {
if (minRamMegabytes_str != null && !minRamMegabytes_str.isEmpty()) {
setMinRamMegabytes(Integer.parseInt(minRamMegabytes_str));
}
}
/* CHECKSTYLE:ON */
/******
* Shuts down a server with the given ID.
*
* @param serverId the server ID.
*/
public void shutdownMachine(final String serverId) {
this.context.getComputeService().destroyNode(serverId);
}
/*********
* Shutdown the server.
*
* @param serverId the server id.
* @param unit time unit to wait.
* @param duration duration to wait.
* @throws TimeoutException if timeout expired.
* @throws InterruptedException .
*/
public void shutdownMachineAndWait(final String serverId, final TimeUnit unit, final long duration)
throws TimeoutException, InterruptedException {
// first shutdown the machine
logger.fine("Retrieving data on node with id " + serverId);
NodeMetadata nodeMetadata = this.context.getComputeService().getNodeMetadata(serverId);
logger.fine("Invoking destroy node on " + serverId);
this.context.getComputeService().destroyNode(serverId);
logger.info("Machine: " + nodeMetadata.getPrivateAddresses() + "-" + serverId + " shutdown has started. "
+ "Waiting for process to complete");
final long endTime = System.currentTimeMillis() + unit.toMillis(duration);
// now wait for the machine to stop
NodeMetadata.Status state = null;
while (System.currentTimeMillis() < endTime) {
logger.fine("Retrieving data on node with id " + serverId);
final NodeMetadata node = this.context.getComputeService().getNodeMetadata(serverId);
if (node == null) {
// machine was terminated and deleted from cloud
return;
}
state = node.getStatus();
logger.fine("Machine: " + node.getPrivateAddresses() + "-" + serverId + " state is: " + state);
switch (state) {
case TERMINATED:
// machine was successfully terminated
return;
case PENDING:
case RUNNING:
case SUSPENDED:
// machine has not shut down yet
break;
case ERROR:
case UNRECOGNIZED:
default:
logger.warning("While waiting for machine " + serverId
+ " to shut down, received unexpected node state: " + state);
break;
}
Thread.sleep(SHUTDOWN_WAIT_POLLING_INTERVAL_MILLIS);
}
throw new TimeoutException("Termination of cloud node with id " + serverId + " was requested, "
+ "but machine did not shut down in the required time. Last state was : " + state);
}
/******
* Shutdown all nodes in group.
*
* @param group group name.
*/
public void shutdownMachineGroup(final String group) {
this.context.getComputeService().destroyNodesMatching(
new Predicate<NodeMetadata>() {
@Override
public boolean apply(final NodeMetadata input) {
return input.getGroup() != null && input.getGroup().equals(group);
}
});
}
/********
* Shutdown servers by ips.
*
* @param ips list of IPs. Any node which has one of these IPs will be shut down.
*/
public void shutdownMachinesWithIPs(final Set<String> ips) {
this.context.getComputeService().destroyNodesMatching(
new Predicate<NodeMetadata>() {
@Override
public boolean apply(final NodeMetadata input) {
if (!input.getPrivateAddresses().isEmpty()) {
final String ip = input.getPrivateAddresses().iterator().next();
return ips.contains(ip);
}
return false;
}
});
}
/********
* Shutdown servers by ids.
*
* @param machines array of machines.
*/
public void shutdownMachinesByIds(final MachineDetails[] machines, final long timeoutInMinutes) throws
TimeoutException, InterruptedException {
long endTime = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(timeoutInMinutes);
List<MachineDetails> nonTerminatedNodes = new ArrayList<MachineDetails>();
for (MachineDetails md : machines) {
nonTerminatedNodes.add(md);
shutdownNodeAsync(md.getMachineId());
}
logger.info("Shutdown of machines " + toIps(nonTerminatedNodes) + " has started. Waiting for them to " +
"terminate, " +
"it may take a few minutes.");
// wait for all machines to terminate
while (System.currentTimeMillis() < endTime) {
for (MachineDetails md : new ArrayList<MachineDetails>(nonTerminatedNodes)) {
NodeMetadata.Status status = getNodeStatus(md.getMachineId());
if (NodeMetadata.Status.TERMINATED.equals(status) || status == null) {
logger.info("Machine " + md.getPublicAddress() + "/" + md.getPrivateAddress()
+ " has terminated" + ".");
nonTerminatedNodes.remove(md);
} else {
logger.fine(
"Machine: [" + md.getPublicAddress() + "/" + md.getPrivateAddress() + "]" + " state is: " +
status);
}
}
if (nonTerminatedNodes.isEmpty()) return;
Thread.sleep(SHUTDOWN_WAIT_POLLING_INTERVAL_MILLIS);
}
throw new TimeoutException("Timed out while waiting for machines " + toIps(nonTerminatedNodes) + " to terminate. Make " +
"sure these machines are terminated.");
}
public void shutdownNodeAsync(final String id) {
logger.fine("Destroying node " + id);
this.context.getComputeService().destroyNode(id);
}
public NodeMetadata.Status getNodeStatus(final String id) {
NodeMetadata nodeMetadata = this.context.getComputeService().getNodeMetadata(id);
if (nodeMetadata != null) {
return nodeMetadata.getStatus();
}
return null;
}
private Set<String> toIps(List<MachineDetails> machines) {
Set<String> ips = new HashSet<String>();
for (MachineDetails md : machines) {
ips.add(md.getPublicAddress() + "/" + md.getPrivateAddress());
}
return ips;
}
/*********
* Returns a server with a tag that equals the given tag. Note that comparison is also performed with the given tag
* with underscores removed.
*
* @param tag the tag to look for.
* @return the node meta data, or null.
*/
public NodeMetadata getServerByTag(final String tag) {
final Predicate<ComputeMetadata> filter = new Predicate<ComputeMetadata>() {
@Override
public boolean apply(final ComputeMetadata compute) {
final NodeMetadata node = (NodeMetadata) compute;
if (node.getGroup() == null) {
return false;
}
if (node.getGroup().equals(tag)) {
return true;
}
if (node.getGroup().equals(tag.replace("_", ""))) {
return true;
}
return false;
}
};
return getServer(filter);
}
private Set<? extends NodeMetadata> createServersWithRetry(final String group, final int count,
final Template template)
throws RunNodesException {
int retryAttempts = 0;
boolean retry;
Set<? extends NodeMetadata> nodes = null;
do {
retry = false;
try {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Starting machine with template : " + template);
}
nodes = this.context.getComputeService().createNodesInGroup(
group, count, template);
} catch (final ResourceNotFoundException e) {
if (retryAttempts < NUMBER_OF_RETRY_ATTEMPTS && e.getMessage() != null
&& e.getMessage().contains("The security group")
&& e.getMessage().contains("does not exist")) {
try {
Thread.sleep(RETRY_SLEEP_TIMEOUT_IN_MILLIS);
} catch (final InterruptedException e1) {
/* do nothing */
}
retryAttempts += 1;
retry = true;
} else {
throw e;
}
}
} while (retry);
return nodes;
}
/*******
* Resets the instance of the compute context.
*
* @param currentContext .
*/
public synchronized void reset(final ComputeServiceContext currentContext) {
// THIS CODE IS NOT THREAD-SAFE!!!
if (this.context != currentContext) {
// context already reset by another thread.
return;
}
logger.warning("Resetting JClouds Deployer");
this.context.close();
this.context = ContextBuilder.newBuilder(provider)
.credentials(account, key)
.modules(new HashSet<Module>())
.overrides(overrides)
.buildView(ComputeServiceContext.class);
}
private void removeLeakingServersAfterCreationException(final RunNodesException e) {
if (!e.getSuccessfulNodes().isEmpty()) {
for (NodeMetadata node : e.getSuccessfulNodes()) {
try {
logger.warning("JClouds Deployer is shutting down a server that failed to start properly: "
+ node.getId());
shutdownMachine(node.getId());
} catch (Throwable t) {
logger.warning("Failed to shut down leaking server: " + node.getId());
}
}
}
if (!e.getNodeErrors().isEmpty()) {
for (NodeMetadata node : e.getNodeErrors().keySet()) {
try {
logger.warning("JClouds Deployer is shutting down a server that failed to start properly: "
+ node.getId());
shutdownMachine(node.getId());
} catch (Throwable t) {
logger.warning("Failed to shut down leaking server: " + node.getId());
}
}
}
}
}