All Downloads are FREE. Search and download functionalities are using the official Maven repository.

brooklyn.location.basic.SshMachineLocation Maven / Gradle / Ivy

package brooklyn.location.basic;

import static brooklyn.util.GroovyJavaMethods.truth;
import groovy.lang.Closure;

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.Reader;
import java.net.InetAddress;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import brooklyn.config.BrooklynLogging;
import brooklyn.config.ConfigKey;
import brooklyn.config.ConfigKey.HasConfigKey;
import brooklyn.config.ConfigUtils;
import brooklyn.entity.basic.ConfigKeys;
import brooklyn.event.basic.BasicConfigKey;
import brooklyn.event.basic.MapConfigKey;
import brooklyn.location.Location;
import brooklyn.location.MachineLocation;
import brooklyn.location.OsDetails;
import brooklyn.location.PortRange;
import brooklyn.location.PortSupplier;
import brooklyn.location.basic.PortRanges.BasicPortRange;
import brooklyn.location.geo.HasHostGeoInfo;
import brooklyn.location.geo.HostGeoInfo;
import brooklyn.util.ResourceUtils;
import brooklyn.util.collections.MutableMap;
import brooklyn.util.config.ConfigBag;
import brooklyn.util.exceptions.Exceptions;
import brooklyn.util.flags.SetFromFlag;
import brooklyn.util.flags.TypeCoercions;
import brooklyn.util.internal.ssh.SshException;
import brooklyn.util.internal.ssh.SshTool;
import brooklyn.util.internal.ssh.sshj.SshjTool;
import brooklyn.util.mutex.MutexSupport;
import brooklyn.util.mutex.WithMutexes;
import brooklyn.util.pool.BasicPool;
import brooklyn.util.pool.Pool;
import brooklyn.util.stream.KnownSizeInputStream;
import brooklyn.util.stream.ReaderInputStream;
import brooklyn.util.stream.StreamGobbler;
import brooklyn.util.task.Tasks;
import brooklyn.util.text.Strings;

import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.io.Closeables;

/**
 * Operations on a machine that is accessible via ssh.
 * 

* We expose two ways of running scripts. * One (execCommands) passes lines to bash, that is lightweight but fragile. * Another (execScript) creates a script on the remote machine, more portable but heavier. *

* Additionally there are routines to copyTo, copyFrom; and installTo (which tries a curl, and falls back to copyTo * in event the source is accessible by the caller only). */ public class SshMachineLocation extends AbstractLocation implements MachineLocation, PortSupplier, WithMutexes, Closeable { public static final Logger LOG = LoggerFactory.getLogger(SshMachineLocation.class); public static final Logger logSsh = LoggerFactory.getLogger(BrooklynLogging.SSH_IO); protected interface ExecRunner { public int exec(SshTool ssh, Map flags, List cmds, Map env); } @SetFromFlag String user; @SetFromFlag String privateKeyData; @SetFromFlag(nullable = false) InetAddress address; @SetFromFlag transient WithMutexes mutexSupport; @SetFromFlag private Set usedPorts; /** any property that should be passed as ssh config (connection-time) * can be prefixed with this and . and will be passed through (with the prefix removed), * e.g. (SSHCONFIG_PREFIX+"."+"StrictHostKeyChecking"):"yes" * @deprecated use {@link SshTool#BROOKLYN_CONFIG_KEY_PREFIX} */ @Deprecated public static final String SSHCONFIG_PREFIX = "sshconfig"; public static final ConfigKey SSH_HOST = ConfigKeys.SSH_CONFIG_HOST; public static final ConfigKey SSH_PORT = ConfigKeys.SSH_CONFIG_PORT; public static final ConfigKey SSH_EXECUTABLE = ConfigKeys.newStringConfigKey("sshExecutable", "Allows an `ssh` executable file to be specified, to be used in place of the default (programmatic) java ssh client", null); public static final ConfigKey SCP_EXECUTABLE = ConfigKeys.newStringConfigKey("scpExecutable", "Allows an `scp` executable file to be specified, to be used in place of the default (programmatic) java ssh client", null); // TODO remove public static final ConfigKey PASSWORD = SshTool.PROP_PASSWORD; public static final ConfigKey PRIVATE_KEY_FILE = SshTool.PROP_PRIVATE_KEY_FILE; public static final ConfigKey PRIVATE_KEY_DATA = SshTool.PROP_PRIVATE_KEY_DATA; public static final ConfigKey PRIVATE_KEY_PASSPHRASE = SshTool.PROP_PRIVATE_KEY_PASSPHRASE; public static final ConfigKey SCRIPT_DIR = ConfigKeys.newStringConfigKey("scriptDir", "directory where scripts should be placed and executed on the SSH target machine", null); public static final ConfigKey> SSH_ENV_MAP = new MapConfigKey(Object.class, "env", "environment variables to pass to the remote SSH shell session", null); public static final ConfigKey ALLOCATE_PTY = SshTool.PROP_ALLOCATE_PTY; // TODO remove // new BasicConfigKey(Boolean.class, "allocatePTY", "whether pseudo-terminal emulation should be turned on; " + // "this causes stderr to be redirected to stdout, but it may be required for some commands (such as `sudo` when requiretty is set)", false); public static final ConfigKey STDOUT = new BasicConfigKey(OutputStream.class, "out"); public static final ConfigKey STDERR = new BasicConfigKey(OutputStream.class, "err"); public static final ConfigKey NO_STDOUT_LOGGING = new BasicConfigKey(Boolean.class, "noStdoutLogging", "whether to disable logging of stdout from SSH commands (e.g. for verbose commands)", false); public static final ConfigKey NO_STDERR_LOGGING = new BasicConfigKey(Boolean.class, "noStderrLogging", "whether to disable logging of stderr from SSH commands (e.g. for verbose commands)", false); public static final ConfigKey LOG_PREFIX = ConfigKeys.newStringConfigKey("logPrefix"); public static final ConfigKey LOCAL_TEMP_DIR = SshTool.PROP_LOCAL_TEMP_DIR; /** specifies config keys where a change in the value does not require a new SshTool instance, * i.e. these can be specified per command on the tool */ public static final Set> REUSABLE_SSH_PROPS = ImmutableSet.of(STDOUT, STDERR, SCRIPT_DIR); public static final Set> ALL_SSH_CONFIG_KEYS = ImmutableSet.>builder(). addAll(ConfigUtils.getStaticKeysOnClass(SshMachineLocation.class)). addAll(ConfigUtils.getStaticKeysOnClass(SshTool.class)). build(); public static final Set ALL_SSH_CONFIG_KEY_NAMES = ImmutableSet.copyOf(Iterables.transform(ALL_SSH_CONFIG_KEYS, new Function,String>() { @Override public String apply(HasConfigKey input) { return input.getConfigKey().getName(); } })); private transient Pool vanillaSshToolPool; public SshMachineLocation() { this(MutableMap.of()); } public SshMachineLocation(Map properties) { super(properties); usedPorts = (usedPorts != null) ? Sets.newLinkedHashSet(usedPorts) : Sets.newLinkedHashSet(); vanillaSshToolPool = buildVanillaPool(); } private BasicPool buildVanillaPool() { return BasicPool.builder() .name(name+"@"+address+ (hasConfig(SSH_HOST) ? "("+getConfig(SSH_HOST)+":"+getConfig(SSH_PORT)+")" : "")+ ":"+ System.identityHashCode(this)) .supplier(new Supplier() { @Override public SshTool get() { return connectSsh(Collections.emptyMap()); }}) .viabilityChecker(new Predicate() { @Override public boolean apply(SshTool input) { return input != null && input.isConnected(); }}) .closer(new Function() { @Override public Void apply(SshTool input) { try { input.disconnect(); } catch (Exception e) { if (logSsh.isDebugEnabled()) logSsh.debug("On machine "+SshMachineLocation.this+", ssh-disconnect failed", e); } return null; }}) .build(); } public void configure(Map properties) { super.configure(properties); // TODO Note that check for addresss!=null is done automatically in super-constructor, in FlagUtils.checkRequiredFields // Yikes, dangerous code for accessing fields of sub-class in super-class' constructor! But getting away with it so far! if (mutexSupport == null) { mutexSupport = new MutexSupport(); } boolean deferConstructionChecks = (properties.containsKey("deferConstructionChecks") && TypeCoercions.coerce(properties.get("deferConstructionChecks"), Boolean.class)); if (!deferConstructionChecks) { if (properties.containsKey("username")) { LOG.warn("Using deprecated ssh machine property 'username': use 'user' instead", new Throwable("source of deprecated ssh 'username' invocation")); user = ""+properties.get("username"); } if (name == null) { name = (truth(user) ? user+"@" : "") + address.getHostName(); } if (getHostGeoInfo() == null) { Location parentLocation = getParent(); if ((parentLocation instanceof HasHostGeoInfo) && ((HasHostGeoInfo)parentLocation).getHostGeoInfo()!=null) setHostGeoInfo( ((HasHostGeoInfo)parentLocation).getHostGeoInfo() ); else setHostGeoInfo(HostGeoInfo.fromLocation(this)); } } } /** @deprecated temporary Beta method introduced in 0.5.0 */ public void addConfig(Map vals) { // configure(vals); getConfigBag().putAll(vals); } @Override public void close() throws IOException { vanillaSshToolPool.close(); } @Override protected void finalize() throws Throwable { close(); } public InetAddress getAddress() { return address; } public String getUser() { return user; } public int run(String command) { return run(MutableMap.of(), command, MutableMap.of()); } public int run(Map props, String command) { return run(props, command, MutableMap.of()); } public int run(String command, Map env) { return run(MutableMap.of(), command, env); } public int run(Map props, String command, Map env) { return run(props, ImmutableList.of(command), env); } /** * @deprecated in 1.4.1, @see execCommand and execScript */ public int run(List commands) { return run(MutableMap.of(), commands, MutableMap.of()); } public int run(Map props, List commands) { return run(props, commands, MutableMap.of()); } public int run(List commands, Map env) { return run(MutableMap.of(), commands, env); } public int run(final Map props, final List commands, final Map env) { if (commands == null || commands.isEmpty()) return 0; return execSsh(props, new Function() { public Integer apply(SshTool ssh) { return ssh.execScript(props, commands, env); }}); } protected T execSsh(Map props, Function task) { if (props.isEmpty() || Sets.difference(props.keySet(), REUSABLE_SSH_PROPS).isEmpty()) { return vanillaSshToolPool.exec(task); } else { SshTool ssh = connectSsh(props); try { return task.apply(ssh); } finally { ssh.disconnect(); } } } protected SshTool connectSsh() { return connectSsh(ImmutableMap.of()); } protected boolean previouslyConnected = false; protected SshTool connectSsh(Map props) { try { if (!truth(user)) user = System.getProperty("user.name"); ConfigBag args = new ConfigBag(). configure(SshTool.PROP_USER, user). // default value of host, overridden if SSH_HOST is supplied configure(SshTool.PROP_HOST, address.getHostName()). putAll(props); for (Map.Entry entry: getAllConfig(true).entrySet()) { String key = entry.getKey(); if (key.startsWith(SshTool.BROOKLYN_CONFIG_KEY_PREFIX)) { key = Strings.removeFromStart(key, SshTool.BROOKLYN_CONFIG_KEY_PREFIX); } else if (key.startsWith(SSHCONFIG_PREFIX)) { key = Strings.removeFromStart(key, SSHCONFIG_PREFIX); } else if (ALL_SSH_CONFIG_KEY_NAMES.contains(entry.getKey())) { // key should be included, and does not need to be changed // TODO make this config-setting mechanism more universal // currently e.g. it will not admit a tool-specific property. // thinking either we know about the tool here, // or we don't allow unadorned keys to be set // (require use of BROOKLYN_CONFOG_KEY_PREFIX) } else { // this key is not applicatble here; ignore it continue; } args.putStringKey(key, entry.getValue()); } if (LOG.isTraceEnabled()) LOG.trace("creating ssh session for "+args); // look up tool class String sshToolClass = args.get(SshTool.PROP_TOOL_CLASS); if (sshToolClass==null) sshToolClass = SshjTool.class.getName(); SshTool ssh = (SshTool) Class.forName(sshToolClass).getConstructor(Map.class).newInstance(args.getAllConfig()); if (LOG.isTraceEnabled()) LOG.trace("using ssh-tool {} (of type {}); props ", ssh, sshToolClass); Tasks.setBlockingDetails("Opening ssh connection"); try { ssh.connect(); } finally { Tasks.setBlockingDetails(null); } previouslyConnected = true; return ssh; } catch (Exception e) { if (previouslyConnected) throw Throwables.propagate(e); // subsequence connection (above) most likely network failure, our remarks below won't help // on first connection include additional information if we can't connect, to help with debugging String rootCause = Throwables.getRootCause(e).getMessage(); throw new IllegalStateException("Cannot establish ssh connection to "+user+" @ "+this+ (rootCause!=null && !rootCause.isEmpty() ? " ("+rootCause+")" : "")+". \n"+ "Ensure that passwordless and passphraseless ssh access is enabled using standard keys from ~/.ssh or " + "as configured in brooklyn.properties. " + "Check that the target host is accessible, " + "that credentials are correct (location and permissions if using a key), " + "that the SFTP subsystem is available on the remote side, " + "and that there is sufficient random noise in /dev/random on both ends. " + "To debug less common causes, see the original error in the trace or log, and/or enable 'net.schmizz' (sshj) logging." , e); } } /** * Convenience for running commands using ssh {@literal exec} mode. * @deprecated in 1.4.1, @see execCommand and execScript */ public int exec(List commands) { return exec(MutableMap.of(), commands, MutableMap.of()); } public int exec(Map props, List commands) { return exec(props, commands, MutableMap.of()); } public int exec(List commands, Map env) { return exec(MutableMap.of(), commands, env); } public int exec(final Map props, final List commands, final Map env) { Preconditions.checkNotNull(address, "host address must be specified for ssh"); if (commands == null || commands.isEmpty()) return 0; return execSsh(props, new Function() { public Integer apply(SshTool ssh) { return ssh.execCommands(props, commands, env); }}); } // TODO submitCommands and submitScript which submit objects we can subsequently poll (cf JcloudsSshMachineLocation.submitRunScript) /** executes a set of commands, directly on the target machine (no wrapping in script). * joined using ' ; ' by default. *

* Stdout and stderr will be logged automatically to brooklyn.SSH logger, unless the * flags 'noStdoutLogging' and 'noStderrLogging' are set. To set a logging prefix, use * the flag 'logPrefix'. *

* Currently runs the commands in an interactive/login shell * by passing each as a line to bash. To terminate early, use: *

     * foo || exit 1
     * 
* It may be desirable instead, in some situations, to wrap as: *
     * { line1 ; } && { line2 ; } ... 
     * 
* and run as a single command (possibly not as an interacitve/login * shell) causing the script to exit on the first command which fails. *

* Currently this has to be done by the caller. * (If desired we can add a flag {@code exitIfAnyNonZero} to support this mode, * and/or {@code commandPrepend} and {@code commandAppend} similar to * (currently supported in SshjTool) {@code separator}.) */ public int execCommands(String summaryForLogging, List commands) { return execCommands(MutableMap.of(), summaryForLogging, commands, MutableMap.of()); } public int execCommands(Map props, String summaryForLogging, List commands) { return execCommands(props, summaryForLogging, commands, MutableMap.of()); } public int execCommands(String summaryForLogging, List commands, Map env) { return execCommands(MutableMap.of(), summaryForLogging, commands, env); } public int execCommands(Map props, String summaryForLogging, List commands, Map env) { return execWithLogging(props, summaryForLogging, commands, env, new ExecRunner() { @Override public int exec(SshTool ssh, Map flags, List cmds, Map env) { return ssh.execCommands(flags, cmds, env); }}); } /** executes a set of commands, wrapped as a script sent to the remote machine. *

* Stdout and stderr will be logged automatically to brooklyn.SSH logger, unless the * flags 'noStdoutLogging' and 'noStderrLogging' are set. To set a logging prefix, use * the flag 'logPrefix'. */ public int execScript(String summaryForLogging, List commands) { return execScript(MutableMap.of(), summaryForLogging, commands, MutableMap.of()); } public int execScript(Map props, String summaryForLogging, List commands) { return execScript(props, summaryForLogging, commands, MutableMap.of()); } public int execScript(String summaryForLogging, List commands, Map env) { return execScript(MutableMap.of(), summaryForLogging, commands, env); } public int execScript(Map props, String summaryForLogging, List commands, Map env) { return execWithLogging(props, summaryForLogging, commands, env, new ExecRunner() { @Override public int exec(SshTool ssh, Map flags, List cmds, Map env) { return ssh.execScript(flags, cmds, env); }}); } protected int execWithLogging(Map props, String summaryForLogging, List commands, Map env, final Closure execCommand) { return execWithLogging(props, summaryForLogging, commands, env, new ExecRunner() { @Override public int exec(SshTool ssh, Map flags, List cmds, Map env) { return execCommand.call(ssh, flags, cmds, env); }}); } @SuppressWarnings("resource") protected int execWithLogging(Map props, final String summaryForLogging, final List commands, final Map env, final ExecRunner execCommand) { if (logSsh.isDebugEnabled()) logSsh.debug("{} on machine {}: {}", new Object[] {summaryForLogging, this, commands}); Preconditions.checkNotNull(address, "host address must be specified for ssh"); if (commands.isEmpty()) { logSsh.debug("{} on machine {} ending: no commands to run", summaryForLogging, this); return 0; } final ConfigBag execFlags = new ConfigBag().putAll(props); // some props get overridden in execFlags, so remove them from the ssh flags final ConfigBag sshFlags = new ConfigBag().putAll(props).removeAll(LOG_PREFIX, STDOUT, STDERR); PipedOutputStream outO = null; PipedOutputStream outE = null; StreamGobbler gO=null, gE=null; try { String logPrefix = execFlags.get(LOG_PREFIX); if (logPrefix == null) { String hostname = getAddress().getHostName(); Integer port = execFlags.peek(SshTool.PROP_PORT); if (port == null) port = getConfig(ConfigUtils.prefixedKey(SshTool.BROOKLYN_CONFIG_KEY_PREFIX, SshTool.PROP_PORT)); if (port == null) port = getConfig(ConfigUtils.prefixedKey(SSHCONFIG_PREFIX, SshTool.PROP_PORT)); logPrefix = (user != null ? user+"@" : "") + hostname + (port != null ? ":"+port : ""); } if (!execFlags.get(NO_STDOUT_LOGGING)) { PipedInputStream insO = new PipedInputStream(); outO = new PipedOutputStream(insO); String stdoutLogPrefix = "["+(logPrefix != null ? logPrefix+":stdout" : "stdout")+"] "; gO = new StreamGobbler(insO, execFlags.get(STDOUT), logSsh).setLogPrefix(stdoutLogPrefix); gO.start(); execFlags.put(STDOUT, outO); } if (!execFlags.get(NO_STDERR_LOGGING)) { PipedInputStream insE = new PipedInputStream(); outE = new PipedOutputStream(insE); String stderrLogPrefix = "["+(logPrefix != null ? logPrefix+":stderr" : "stderr")+"] "; gE = new StreamGobbler(insE, execFlags.get(STDERR), logSsh).setLogPrefix(stderrLogPrefix); gE.start(); execFlags.put(STDERR, outE); } Tasks.setBlockingDetails("SSH executing, "+summaryForLogging); try { return execSsh(MutableMap.copyOf(sshFlags.getAllConfig()), new Function() { public Integer apply(SshTool ssh) { int result = execCommand.exec(ssh, MutableMap.copyOf(execFlags.getAllConfig()), commands, env); if (logSsh.isDebugEnabled()) logSsh.debug("{} on machine {} completed: return status {}", new Object[] {summaryForLogging, this, result}); return result; }}); } finally { Tasks.setBlockingDetails(null); } } catch (IOException e) { if (logSsh.isDebugEnabled()) logSsh.debug("{} on machine {} failed: {}", new Object[] {summaryForLogging, this, e}); throw Throwables.propagate(e); } finally { // Must close the pipedOutStreams, otherwise input will never read -1 so StreamGobbler thread would never die if (outO!=null) try { outO.flush(); } catch (IOException e) {} if (outE!=null) try { outE.flush(); } catch (IOException e) {} Closeables.closeQuietly(outO); Closeables.closeQuietly(outE); try { if (gE!=null) { gE.join(); } if (gO!=null) { gO.join(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); Throwables.propagate(e); } } } public int copyTo(File src, File destination) { return copyTo(MutableMap.of(), src, destination); } public int copyTo(Map props, File src, File destination) { return copyTo(props, src, destination.getPath()); } public int copyTo(File src, String destination) { return copyTo(MutableMap.of(), src, destination); } public int copyTo(Map props, File src, String destination) { Preconditions.checkNotNull(address, "Host address must be specified for scp"); Preconditions.checkArgument(src.exists(), "File %s must exist for scp", src.getPath()); try { return copyTo(props, new FileInputStream(src), src.length(), destination); } catch (FileNotFoundException e) { throw Throwables.propagate(e); } } public int copyTo(Reader src, String destination) { return copyTo(MutableMap.of(), src, destination); } public int copyTo(Map props, Reader src, String destination) { return copyTo(props, new ReaderInputStream(src), destination); } public int copyTo(InputStream src, String destination) { return copyTo(MutableMap.of(), src, destination); } public int copyTo(InputStream src, long filesize, String destination) { return copyTo(MutableMap.of(), src, filesize, destination); } // FIXME the return code is not a reliable indicator of success or failure public int copyTo(final Map props, final InputStream src, final long filesize, final String destination) { if (filesize == -1) { return copyTo(props, src, destination); } else { return execSsh(props, new Function() { public Integer apply(SshTool ssh) { return ssh.copyToServer(props, new KnownSizeInputStream(src, filesize), destination); // return ssh.createFile(props, destination, src, filesize); }}); } } // FIXME the return code is not a reliable indicator of success or failure // Closes input stream before returning public int copyTo(final Map props, final InputStream src, final String destination) { return execSsh(props, new Function() { public Integer apply(SshTool ssh) { return ssh.copyToServer(props, src, destination); }}); } // FIXME the return code is not a reliable indicator of success or failure public int copyFrom(String remote, String local) { return copyFrom(MutableMap.of(), remote, local); } public int copyFrom(final Map props, final String remote, final String local) { return execSsh(props, new Function() { public Integer apply(SshTool ssh) { return ssh.copyFromServer(props, remote, new File(local)); }}); } /** installs the given URL at the indicated destination. * attempts to curl the sourceUrl on the remote machine, * then if that fails, loads locally (from classpath or file) and transfers. *

* accepts either a path (terminated with /) or filename for the destination. **/ public int installTo(ResourceUtils loader, String url, String destination) { if (destination.endsWith("/")) { String destName = url; destName = destName.contains("?") ? destName.substring(0, destName.indexOf("?")) : destName; destName = destName.substring(destName.lastIndexOf('/')+1); destination = destination + destName; } LOG.debug("installing {} to {} on {}, attempting remote curl", new Object[] {url, destination, this}); try { PipedInputStream insO = new PipedInputStream(); OutputStream outO = new PipedOutputStream(insO); PipedInputStream insE = new PipedInputStream(); OutputStream outE = new PipedOutputStream(insE); new StreamGobbler(insO, null, LOG).setLogPrefix("[curl @ "+address+":stdout] ").start(); new StreamGobbler(insE, null, LOG).setLogPrefix("[curl @ "+address+":stdout] ").start(); int result = exec(MutableMap.of("out", outO, "err", outE), ImmutableList.of("curl "+url+" -L --silent --insecure --show-error --fail --connect-timeout 60 --max-time 600 --retry 5 -o "+destination)); if (result!=0 && loader!=null) { LOG.debug("installing {} to {} on {}, curl failed, attempting local fetch and copy", new Object[] {url, destination, this}); result = copyTo(loader.getResourceFromUrl(url), destination); } if (result==0) LOG.debug("installing {} complete; {} on {}", new Object[] {url, destination, this}); else LOG.warn("installing {} failed; {} on {}: {}", new Object[] {url, destination, this, result}); return result; } catch (IOException e) { throw Throwables.propagate(e); } } @Override public String toString() { return "SshMachineLocation["+name+":"+address+"]"; } @Override public String toVerboseString() { return Objects.toStringHelper(this).omitNullValues() .add("id", getId()).add("name", getDisplayName()) .add("user", getUser()).add("address", getAddress()).add("port", getConfig(SSH_PORT)) .add("parentLocation", getParent()) .toString(); } /** * @see #obtainPort(PortRange) * @see BasicPortRange#ANY_HIGH_PORT */ public boolean obtainSpecificPort(int portNumber) { // TODO Does not yet check if the port really is free on this machine if (usedPorts.contains(portNumber)) { return false; } else { usedPorts.add(portNumber); return true; } } public int obtainPort(PortRange range) { for (int p: range) if (obtainSpecificPort(p)) return p; if (LOG.isDebugEnabled()) LOG.debug("unable to find port in {} on {}; returning -1", range, this); return -1; } public void releasePort(int portNumber) { usedPorts.remove((Object) portNumber); } public boolean isSshable() { String cmd = "date"; try { int result = run(cmd); if (result == 0) { return true; } else { if (LOG.isDebugEnabled()) LOG.debug("Not reachable: {}, executing `{}`, exit code {}", new Object[] {this, cmd, result}); return false; } } catch (SshException e) { if (LOG.isDebugEnabled()) LOG.debug("Exception checking if "+this+" is reachable; assuming not", e); return false; } catch (IllegalStateException e) { if (LOG.isDebugEnabled()) LOG.debug("Exception checking if "+this+" is reachable; assuming not", e); return false; } catch (RuntimeException e) { if (Exceptions.getFirstThrowableOfType(e, IOException.class) != null) { if (LOG.isDebugEnabled()) LOG.debug("Exception checking if "+this+" is reachable; assuming not", e); return false; } else { throw e; } } } @Override public OsDetails getOsDetails() { // TODO ssh and find out what we need to know, or use jclouds... return BasicOsDetails.Factory.ANONYMOUS_LINUX; } protected WithMutexes newMutexSupport() { return new MutexSupport(); } @Override public void acquireMutex(String mutexId, String description) throws InterruptedException { mutexSupport.acquireMutex(mutexId, description); } @Override public boolean tryAcquireMutex(String mutexId, String description) { return mutexSupport.tryAcquireMutex(mutexId, description); } @Override public void releaseMutex(String mutexId) { mutexSupport.releaseMutex(mutexId); } @Override public boolean hasMutex(String mutexId) { return mutexSupport.hasMutex(mutexId); } //We want want the SshMachineLocation to be serializable and therefor the pool needs to be dealt with correctly. //In this case we are not serializing the pool (we made the field transient) and create a new pool when deserialized. //This fix is currently needed for experiments, but isn't used in normal Brooklyn usage. private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); vanillaSshToolPool = buildVanillaPool(); } }