Merge remote-tracking branch 'aledsage/fix/IntegrationTests-20130124'
Fix conflicts in core/src/test/java/brooklyn/entity/basic/EffectorConcatenateTest
(converted to java in other pull request, adds destroyAll here)
diff --git a/core/src/main/java/brooklyn/location/basic/SshMachineLocation.java b/core/src/main/java/brooklyn/location/basic/SshMachineLocation.java
index 4fd3eae..65b58c9 100644
--- a/core/src/main/java/brooklyn/location/basic/SshMachineLocation.java
+++ b/core/src/main/java/brooklyn/location/basic/SshMachineLocation.java
@@ -530,47 +530,27 @@
public int copyTo(InputStream src, String destination) {
return copyTo(MutableMap.<String,Object>of(), src, destination);
}
- public int copyTo(Map<String,?> props, InputStream src, String destination) {
- return copyTo(props, src, -1, destination);
- }
public int copyTo(InputStream src, long filesize, String destination) {
return copyTo(MutableMap.<String,Object>of(), src, filesize, destination);
}
// FIXME the return code is not a reliable indicator of success or failure
- public int copyTo(final Map<String,?> props, InputStream src, long filesize, final String destination) {
- final long finalFilesize;
- final InputStream finalSrc;
- File tempFile = null;
-
- try {
- if (filesize==-1) {
- try {
- // TODO Use ConfigKeys.BROOKLYN_DATA_DIR, but how to get access to that here?
- tempFile = ResourceUtils.writeToTempFile(src, localTempDir, "sshcopy", "data");
- tempFile.setReadable(false, false);
- tempFile.setReadable(true, true);
- tempFile.setWritable(false);
- tempFile.setExecutable(false);
- finalFilesize = tempFile.length();
- finalSrc = new FileInputStream(tempFile);
- } catch (IOException e) {
- throw Throwables.propagate(e);
- } finally {
- Closeables.closeQuietly(src);
- }
- } else {
- finalFilesize = filesize;
- finalSrc = src;
- }
-
+ public int copyTo(final Map<String,?> props, final InputStream src, final long filesize, final String destination) {
+ if (filesize == -1) {
+ return copyTo(props, src, destination);
+ } else {
return execSsh(props, new Function<SshTool,Integer>() {
public Integer apply(SshTool ssh) {
- return ssh.createFile(props, destination, finalSrc, finalFilesize);
+ return ssh.createFile(props, destination, src, filesize);
}});
-
- } finally {
- if (tempFile != null) tempFile.delete();
- }
+ }
+ }
+ // FIXME the return code is not a reliable indicator of success or failure
+ // Closes input stream before returning
+ public int copyTo(final Map<String,?> props, final InputStream src, final String destination) {
+ return execSsh(props, new Function<SshTool,Integer>() {
+ public Integer apply(SshTool ssh) {
+ return ssh.copyToServer(props, src, destination);
+ }});
}
// FIXME the return code is not a reliable indicator of success or failure
@@ -580,7 +560,7 @@
public int copyFrom(final Map<String,?> props, final String remote, final String local) {
return execSsh(props, new Function<SshTool,Integer>() {
public Integer apply(SshTool ssh) {
- return ssh.transferFileFrom(props, remote, local);
+ return ssh.copyFromServer(props, remote, new File(local));
}});
}
diff --git a/core/src/main/java/brooklyn/util/internal/ssh/SshAbstractTool.java b/core/src/main/java/brooklyn/util/internal/ssh/SshAbstractTool.java
index b807543..f45cfbf 100644
--- a/core/src/main/java/brooklyn/util/internal/ssh/SshAbstractTool.java
+++ b/core/src/main/java/brooklyn/util/internal/ssh/SshAbstractTool.java
@@ -4,21 +4,17 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
-import java.io.ByteArrayOutputStream;
+import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
-import org.jclouds.io.Payload;
-import org.jclouds.io.payloads.ByteArrayPayload;
-import org.jclouds.io.payloads.InputStreamPayload;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -28,11 +24,8 @@
import brooklyn.util.text.StringEscapes.BashStringEscapes;
import com.google.common.base.Objects;
-import com.google.common.base.Throwables;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
-import com.google.common.io.ByteStreams;
-import com.google.common.io.LimitInputStream;
public abstract class SshAbstractTool implements SshTool {
@@ -93,28 +86,28 @@
@SuppressWarnings("unchecked")
public B from(Map<String,?> props) {
- host = getMandatoryVal(props, "host", String.class);
- port = getOptionalVal(props, "port", Integer.class, port);
- user = getOptionalVal(props, "user", String.class, user);
+ host = getMandatoryVal(props, PROP_HOST);
+ port = getOptionalVal(props, PROP_PORT);
+ user = getOptionalVal(props, PROP_USER);
- password = getOptionalVal(props, "password", String.class, password);
+ password = getOptionalVal(props, PROP_PASSWORD);
warnOnDeprecated(props, "privateKey", "privateKeyData");
- privateKeyData = getOptionalVal(props, "privateKey", String.class, privateKeyData);
- privateKeyData = getOptionalVal(props, "privateKeyData", String.class, privateKeyData);
- privateKeyPassphrase = getOptionalVal(props, "privateKeyPassphrase", String.class, privateKeyPassphrase);
+ privateKeyData = getOptionalVal(props, PROP_PRIVATE_KEY);
+ privateKeyData = getOptionalVal(props, PROP_PRIVATE_KEY_DATA);
+ privateKeyPassphrase = getOptionalVal(props, PROP_PRIVATE_KEY_PASSPHRASE);
// for backwards compatibility accept keyFiles and privateKey
// but sshj accepts only a single privateKeyFile; leave blank to use defaults (i.e. ~/.ssh/id_rsa and id_dsa)
warnOnDeprecated(props, "keyFiles", null);
- privateKeyFiles.addAll(getOptionalVal(props, "keyFiles", List.class, Collections.emptyList()));
- String privateKeyFile = getOptionalVal(props, "privateKeyFile", String.class, null);
+ privateKeyFiles.addAll(getOptionalVal(props, PROP_KEY_FILES));
+ String privateKeyFile = getOptionalVal(props, PROP_PRIVATE_KEY_FILE);
if (privateKeyFile != null) privateKeyFiles.add(privateKeyFile);
- strictHostKeyChecking = getOptionalVal(props, "strictHostKeyChecking", Boolean.class, strictHostKeyChecking);
- allocatePTY = getOptionalVal(props, "allocatePTY", Boolean.class, allocatePTY);
+ strictHostKeyChecking = getOptionalVal(props, PROP_STRICT_HOST_KEY_CHECKING);
+ allocatePTY = getOptionalVal(props, PROP_ALLOCATE_PTY);
- localTempDir = getOptionalVal(props, "localTempDir", File.class, localTempDir);
+ localTempDir = getOptionalVal(props, PROP_LOCAL_TEMP_DIR);
return self();
}
@@ -179,39 +172,16 @@
toString = String.format("%s@%s:%d", user, host, port);
}
- protected Payload toPayload(InputStream input, long length) {
- InputStreamPayload payload = new InputStreamPayload(new LimitInputStream(input, length));
- payload.getContentMetadata().setContentLength(length);
- return payload;
- }
-
- protected Payload toPayload(InputStream input) {
- /*
- * TODO sshj needs to know the length of the InputStream to copy the file:
- * java.lang.NullPointerException
- * at brooklyn.util.internal.ssh.SshjTool$PutFileAction$1.getLength(SshjTool.java:574)
- * at net.schmizz.sshj.sftp.SFTPFileTransfer$Uploader.upload(SFTPFileTransfer.java:174)
- * at net.schmizz.sshj.sftp.SFTPFileTransfer$Uploader.access$100(SFTPFileTransfer.java:162)
- * at net.schmizz.sshj.sftp.SFTPFileTransfer.upload(SFTPFileTransfer.java:61)
- * at net.schmizz.sshj.sftp.SFTPClient.put(SFTPClient.java:248)
- * at brooklyn.util.internal.ssh.SshjTool$PutFileAction.create(SshjTool.java:569)
- *
- * Unfortunately that requires consuming the input stream to find out! We can't just do:
- * new InputStreamPayload(input)
- *
- * This is nasty: we have to hold the entire file in-memory.
- * It's worth a look at changing sshj to not need the length, if possible.
- */
- try {
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- ByteStreams.copy(input, byteArrayOutputStream);
- return new ByteArrayPayload(byteArrayOutputStream.toByteArray());
- } catch (IOException e) {
- LOG.warn("Error consuming stream", e);
- throw Throwables.propagate(e);
- }
+ @Override
+ public String toString() {
+ return toString;
}
+ protected static Boolean hasVal(Map<String,?> map, ConfigKey<?> keyC) {
+ String key = keyC.getName();
+ return map.containsKey(key);
+ }
+
protected static <T> T getMandatoryVal(Map<String,?> map, ConfigKey<T> keyC) {
String key = keyC.getName();
checkArgument(map.containsKey(key), "must contain key '"+keyC+"'");
@@ -275,11 +245,6 @@
throw new SshException("(" + toString() + ") " + message + ":" + e.getMessage(), e);
}
- @Override
- public String toString() {
- return toString;
- }
-
protected File writeTempFile(InputStream contents) {
// TODO Use ConfigKeys.BROOKLYN_DATA_DIR, but how to get access to that here?
File tempFile = ResourceUtils.writeToTempFile(contents, localTempDir, "sshcopy", "data");
@@ -290,6 +255,14 @@
return tempFile;
}
+ protected File writeTempFile(String contents) {
+ return writeTempFile(contents.getBytes());
+ }
+
+ protected File writeTempFile(byte[] contents) {
+ return writeTempFile(new ByteArrayInputStream(contents));
+ }
+
public String getHostAddress() {
return this.host;
}
diff --git a/core/src/main/java/brooklyn/util/internal/ssh/SshTool.java b/core/src/main/java/brooklyn/util/internal/ssh/SshTool.java
index d2ac669..b716d3e 100644
--- a/core/src/main/java/brooklyn/util/internal/ssh/SshTool.java
+++ b/core/src/main/java/brooklyn/util/internal/ssh/SshTool.java
@@ -26,8 +26,8 @@
public static final ConfigKey<String> PROP_HOST = new StringConfigKey("host", "Host to connect to (required)", null);
public static final ConfigKey<Integer> PROP_PORT = new BasicConfigKey<Integer>(Integer.class, "port", "Port on host to connect to", 22);
- public static final ConfigKey<String> PROP_USER = new StringConfigKey("user", "User to connect as", null);
- public static final ConfigKey<String> PROP_PASSWORD = new StringConfigKey("user", "Password to use to connect", null);
+ public static final ConfigKey<String> PROP_USER = new StringConfigKey("user", "User to connect as", System.getProperty("user.name"));
+ public static final ConfigKey<String> PROP_PASSWORD = new StringConfigKey("password", "Password to use to connect", null);
public static final ConfigKey<OutputStream> PROP_OUT_STREAM = new BasicConfigKey<OutputStream>(OutputStream.class, "out", "Stream to which to capture stdout");
public static final ConfigKey<OutputStream> PROP_ERR_STREAM = new BasicConfigKey<OutputStream>(OutputStream.class, "err", "Stream to which to capture stderr");
@@ -50,11 +50,14 @@
public static final ConfigKey<Integer> PROP_SSH_TRIES_TIMEOUT = new BasicConfigKey<Integer>(Integer.class, "sshTriesTimeout", "Timeout when attempting to connect for ssh operations; so if too slow trying sshTries times, will abort anyway", 2*60*1000);
public static final ConfigKey<Long> PROP_SSH_RETRY_DELAY = new BasicConfigKey<Long>(Long.class, "sshRetryDelay", "Time (in milliseconds) before first ssh-retry, after which it will do exponential backoff", 50L);
+ public static final ConfigKey<File> PROP_LOCAL_TEMP_DIR = new BasicConfigKey<File>(File.class, "localTempDir", "The directory on the local machine (i.e. running brooklyn) for writing temp files",
+ new File(System.getProperty("java.io.tmpdir"), "tmpssh"));
+
public static final ConfigKey<String> PROP_PERMISSIONS = new StringConfigKey("permissions", "Default permissions for files copied/created on remote machine; must be four-digit octal string, default '0644'", "0644");
public static final ConfigKey<Long> PROP_LAST_MODIFICATION_DATE = new BasicConfigKey<Long>(Long.class, "lastModificationDate", "Last-modification-date to be set on files copied/created (should be UTC/1000, ie seconds since 1970; defaults to current)", 0L);
public static final ConfigKey<Long> PROP_LAST_ACCESS_DATE = new BasicConfigKey<Long>(Long.class, "lastAccessDate", "Last-access-date to be set on files copied/created (should be UTC/1000, ie seconds since 1970; defaults to lastModificationDate)", 0L);
- // FIXME Defined/used only in SshMachineLocation?
+ // TODO Could define the following in SshMachineLocation, or some such?
//public static ConfigKey<String> PROP_LOG_PREFIX = new StringConfigKey("logPrefix", "???", ???);
//public static ConfigKey<Boolean> PROP_NO_STDOUT_LOGGING = new StringConfigKey("noStdoutLogging", "???", ???);
//public static ConfigKey<Boolean> PROP_NO_STDOUT_LOGGING = new StringConfigKey("noStdoutLogging", "???", ???);
@@ -86,14 +89,18 @@
public boolean isConnected();
/**
- * Executes the set of commands in a shell script; optional property 'out'
- * should be an output stream. Blocks until completion (unless property
- * 'block' set as false).
+ * Executes the set of commands in a shell script. Blocks until completion.
* <p>
- * values in environment parameters are wrapped in double quotes, with double quotes escaped
+ *
+ * Optional properties are:
+ * <ul>
+ * <li>'out' {@link OutputStream} - see {@link PROP_OUT_STREAM}
+ * <li>'err' {@link OutputStream} - see {@link PROP_ERR_STREAM}
+ * </ul>
*
* @return exit status of script
- * @throws SshException
+ *
+ * @throws SshException If failed to connect
*/
public int execScript(Map<String,?> props, List<String> commands, Map<String,?> env);
@@ -102,22 +109,27 @@
*/
public int execScript(Map<String,?> props, List<String> commands);
- /** @deprecated @see execScript(Map, List, Map) */
+ /** @deprecated since 0.4; use execScript(...) */
public int execShell(Map<String,?> props, List<String> commands);
- /** @deprecated @see execScript(Map, List, Map) */
+
+ /** @deprecated since 0.4; execScript(...) */
public int execShell(Map<String,?> props, List<String> commands, Map<String,?> env);
/**
- * Executes the set of commands using ssh exec, ";" separated (overridable
- * with property 'separator'.
- *
- * Optional properties 'out' and 'err' should be streams.
- * <p>
- * This is generally simpler/preferable to shell, but is not suitable if you need
- * env values whare are only set on a fully-fledged shell.
+ * Executes the set of commands using ssh exec.
*
- * @return exit status
- * @throws SshException
+ * This is generally more efficient than shell, but is not suitable if you need
+ * env values which are only set on a fully-fledged shell.
+ *
+ * Optional properties are:
+ * <ul>
+ * <li>'out' {@link OutputStream} - see {@link PROP_OUT_STREAM}
+ * <li>'err' {@link OutputStream} - see {@link PROP_ERR_STREAM}
+ * <li>'separator', defaulting to ";" - see {@link PROP_SEPARATOR}
+ * </ul>
+ *
+ * @return exit status of commands
+ * @throws SshException If failed to connect
*/
public int execCommands(Map<String,?> properties, List<String> commands, Map<String,?> env);
@@ -127,53 +139,69 @@
public int execCommands(Map<String,?> properties, List<String> commands);
/**
- * @see #createFile(Map, String, InputStream, long)
+ * Copies the file to the server at the given path.
+ * If path is null, empty, '.', '..', or ends with '/' then file name is used.
+ * <p>
+ * The file will not preserve the permission of last _access_ date.
+ *
+ * Optional properties are:
+ * <ul>
+ * <li>'permissions' (e.g. "0644") - see {@link PROP_PERMISSIONS}
+ * <li>'lastModificationDate' see {@link PROP_LAST_MODIFICATION_DATE}; not supported by all SshTool implementations
+ * <li>'lastAccessDate' see {@link PROP_LAST_ACCESS_DATE}; not supported by all SshTool implementations
+ * </ul>
+ *
+ * @return exit code (not supported by all SshTool implementations, sometimes just returning 0)
+ */
+ public int copyToServer(Map<String,?> props, File localFile, String pathAndFileOnRemoteServer);
+
+ /**
+ * Closes the given input stream before returning.
+ *
+ * @see copyToServer(Map, File, String)
+ */
+ public int copyToServer(Map<String,?> props, InputStream contents, String pathAndFileOnRemoteServer);
+
+ /**
+ * @see copyToServer(Map, File, String)
+ */
+ public int copyToServer(Map<String,?> props, byte[] contents, String pathAndFileOnRemoteServer);
+
+ /**
+ * Copies the file to the server at the given path.
+ * If path is null, empty, '.', '..', or ends with '/' then file name is used.
+ * <p>
+ * Optional properties are:
+ * <ul>
+ * <li>'permissions' (e.g. "0644") - see {@link PROP_PERMISSIONS}
+ * </ul>
+ *
+ * @return exit code (not supported by all SshTool implementations, sometimes just returning 0)
+ */
+ public int copyFromServer(Map<String,?> props, String pathAndFileOnRemoteServer, File local);
+
+ /**
+ * @deprecated since 0.5; See copyToServer(Map, InputStream, String)
*/
public int transferFileTo(Map<String,?> props, InputStream input, String pathAndFileOnRemoteServer);
/**
- * @see #createFile(Map, String, InputStream, long)
+ * @deprecated since 0.5; See copyFromServer(Map, InputStream, String)
*/
public int transferFileFrom(Map<String,?> props, String pathAndFileOnRemoteServer, String pathAndFileOnLocalServer);
/**
- * Creates the given file with the given contents.
- *
- * Properties can be:
- * <ul>
- * <li>permissions (must be four-digit octal string, default '0644');
- * <li>lastModificationDate (should be UTC/1000, ie seconds since 1970; defaults to current);
- * <li>lastAccessDate (again UTC/1000; defaults to lastModificationDate);
- * </ul>
- * If neither lastXxxDate set it does not send that line (unless property ptimestamp set true)
- *
- * Closes the input stream before returning.
- *
- * @param props
- * @param pathAndFileOnRemoteServer
- * @param input
- * @param size
- * @throws SshException
+ * @deprecated since 0.5; See copyToServer(Map, InputStream, String)
*/
public int createFile(Map<String,?> props, String pathAndFileOnRemoteServer, InputStream input, long size);
/**
- * @see #createFile(Map, String, InputStream, long)
+ * @deprecated since 0.5; See copyToServer(Map, byte[], String)
*/
public int createFile(Map<String,?> props, String pathAndFileOnRemoteServer, String contents);
/**
- * @see #createFile(Map, String, InputStream, long)
+ * @deprecated since 0.5; See copyToServer(Map, byte[], String)
*/
public int createFile(Map<String,?> props, String pathAndFileOnRemoteServer, byte[] contents);
-
- /**
- * Copies file, but won't preserve permission of last _access_ date.
- * If path is null, empty, '.', '..', or ends with '/' then file name is used.
- * <p>
- * To set permissions (or override mod date) use for example 'permissions:"0644"',
- *
- * @see #createFile(Map, String, InputStream, long)
- */
- public int copyToServer(Map<String,?> props, File f, String pathAndFileOnRemoteServer);
}
diff --git a/core/src/main/java/brooklyn/util/internal/ssh/cli/SshCliTool.java b/core/src/main/java/brooklyn/util/internal/ssh/cli/SshCliTool.java
index 7206ce7..4633337 100644
--- a/core/src/main/java/brooklyn/util/internal/ssh/cli/SshCliTool.java
+++ b/core/src/main/java/brooklyn/util/internal/ssh/cli/SshCliTool.java
@@ -2,7 +2,6 @@
import static com.google.common.base.Preconditions.checkNotNull;
-import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -105,44 +104,42 @@
return true;
}
- private File writeTempFile(String contents) {
- return writeTempFile(contents.getBytes());
+ @Override
+ public int copyToServer(java.util.Map<String,?> props, byte[] contents, String pathAndFileOnRemoteServer) {
+ return copyTempFileToServer(props, writeTempFile(contents), pathAndFileOnRemoteServer);
}
-
- private File writeTempFile(byte[] contents) {
- return writeTempFile(new ByteArrayInputStream(contents));
+
+ @Override
+ public int copyToServer(java.util.Map<String,?> props, InputStream contents, String pathAndFileOnRemoteServer) {
+ return copyTempFileToServer(props, writeTempFile(contents), pathAndFileOnRemoteServer);
}
-
+
@Override
public int transferFileTo(Map<String,?> props, InputStream input, String pathAndFileOnRemoteServer) {
- return copyTempFileToServer(props, writeTempFile(input), pathAndFileOnRemoteServer);
+ return copyToServer(props, input, pathAndFileOnRemoteServer);
}
@Override
public int createFile(Map<String,?> props, String pathAndFileOnRemoteServer, InputStream input, long size) {
- return copyTempFileToServer(props, writeTempFile(input), pathAndFileOnRemoteServer);
+ return copyToServer(props, input, pathAndFileOnRemoteServer);
}
@Override
public int createFile(Map<String,?> props, String pathAndFileOnRemoteServer, String contents) {
- return copyTempFileToServer(props, writeTempFile(contents), pathAndFileOnRemoteServer);
+ return copyToServer(props, contents.getBytes(), pathAndFileOnRemoteServer);
}
- /** Creates the given file with the given contents.
- *
- * Permissions specified using 'permissions:0755'.
- */
@Override
public int createFile(Map<String,?> props, String pathAndFileOnRemoteServer, byte[] contents) {
- return copyTempFileToServer(props, writeTempFile(contents), pathAndFileOnRemoteServer);
+ return copyToServer(props, contents, pathAndFileOnRemoteServer);
}
@Override
public int copyToServer(Map<String,?> props, File f, String pathAndFileOnRemoteServer) {
- if (props.containsKey("lastModificationDate")) {
+ if (hasVal(props, PROP_LAST_MODIFICATION_DATE)) {
LOG.warn("Unsupported ssh feature, setting lastModificationDate for {}:{}", this, pathAndFileOnRemoteServer);
}
- if (props.containsKey("lastAccessDate")) {
+ if (hasVal(props, PROP_LAST_ACCESS_DATE)) {
LOG.warn("Unsupported ssh feature, setting lastAccessDate for {}:{}", this, pathAndFileOnRemoteServer);
}
String permissions = getOptionalVal(props, PROP_PERMISSIONS);
@@ -169,7 +166,12 @@
@Override
public int transferFileFrom(Map<String,?> props, String pathAndFileOnRemoteServer, String pathAndFileOnLocalServer) {
- return scpFromServer(props, pathAndFileOnRemoteServer, new File(pathAndFileOnLocalServer));
+ return copyFromServer(props, pathAndFileOnRemoteServer, new File(pathAndFileOnLocalServer));
+ }
+
+ @Override
+ public int copyFromServer(Map<String,?> props, String pathAndFileOnRemoteServer, File localFile) {
+ return scpFromServer(props, pathAndFileOnRemoteServer, localFile);
}
@Override
@@ -207,7 +209,7 @@
"rm -f "+scriptPath+" < /dev/null"+separator+
"exit $RESULT";
- Integer result = ssh(props, cmd);
+ Integer result = sshExec(props, cmd);
return result != null ? result : -1;
}
@@ -222,41 +224,20 @@
}
private int scpToServer(Map<String,?> props, File local, String remote) {
- File tempFile = null;
- try {
- List<String> cmd = Lists.newArrayList();
- cmd.add(getOptionalVal(props, PROP_SCP_EXECUTABLE, scpExecutable));
- if (privateKeyFile != null) {
- cmd.add("-i");
- cmd.add(privateKeyFile.getAbsolutePath());
- } else if (privateKeyData != null) {
- tempFile = writeTempFile(privateKeyData);
- cmd.add("-i");
- cmd.add(tempFile.getAbsolutePath());
- }
- if (!strictHostKeyChecking) {
- cmd.add("-o");
- cmd.add("StrictHostKeyChecking=no");
- }
- if (port != 22) {
- cmd.add("-P");
- cmd.add(""+port);
- }
- cmd.add(local.getAbsolutePath());
- cmd.add((Strings.isEmpty(getUsername()) ? "" : getUsername()+"@")+getHostAddress()+":"+remote);
-
- if (LOG.isTraceEnabled()) LOG.trace("Executing with command: {}", cmd);
- int result = execProcess(props, cmd);
-
- if (LOG.isTraceEnabled()) LOG.trace("Executed command: {}; exit code {}", cmd, result);
- return result;
-
- } finally {
- if (tempFile != null) tempFile.delete();
- }
+ String to = (Strings.isEmpty(getUsername()) ? "" : getUsername()+"@")+getHostAddress()+":"+remote;
+ return scpExec(props, local.getAbsolutePath(), to);
}
private int scpFromServer(Map<String,?> props, String remote, File local) {
+ String from = (Strings.isEmpty(getUsername()) ? "" : getUsername()+"@")+getHostAddress()+":"+remote;
+ return scpExec(props, from, local.getAbsolutePath());
+ }
+
+ private int chmodOnServer(Map<String,?> props, String permissions, String remote) {
+ return sshExec(props, "chmod "+permissions+" "+remote);
+ }
+
+ private int scpExec(Map<String,?> props, String from, String to) {
File tempFile = null;
try {
List<String> cmd = Lists.newArrayList();
@@ -277,8 +258,8 @@
cmd.add("-P");
cmd.add(""+port);
}
- cmd.add((Strings.isEmpty(getUsername()) ? "" : getUsername()+"@")+getHostAddress()+":"+remote);
- cmd.add(local.getAbsolutePath());
+ cmd.add(from);
+ cmd.add(to);
if (LOG.isTraceEnabled()) LOG.trace("Executing with command: {}", cmd);
int result = execProcess(props, cmd);
@@ -291,11 +272,7 @@
}
}
- private int chmodOnServer(Map<String,?> props, String permissions, String remote) {
- return ssh(props, "chmod "+permissions+" "+remote);
- }
-
- private int ssh(Map<String,?> props, String command) {
+ private int sshExec(Map<String,?> props, String command) {
File tempCmdFile = writeTempFile(command);
File tempKeyFile = null;
try {
@@ -344,23 +321,21 @@
try {
Process p = pb.start();
- if (true) {// FIXME
-// if (out != null) {
+ if (out != null) {
InputStream outstream = p.getInputStream();
- outgobbler = new StreamGobbler(outstream, out, LOG).setLogPrefix("[stdout] ");// FIXME (Logger) null);
+ outgobbler = new StreamGobbler(outstream, out, (Logger) null);
outgobbler.start();
}
- if (true) {// FIXME
-// if (err != null) {
+ if (err != null) {
InputStream errstream = p.getErrorStream();
- errgobbler = new StreamGobbler(errstream, err, LOG).setLogPrefix("[stdout] ");// FIXME (Logger) null);
+ errgobbler = new StreamGobbler(errstream, err, (Logger) null);
errgobbler.start();
}
int result = p.waitFor();
- outgobbler.blockUntilFinished();
- errgobbler.blockUntilFinished();
+ if (outgobbler != null) outgobbler.blockUntilFinished();
+ if (errgobbler != null) errgobbler.blockUntilFinished();
if (result==255)
// this is not definitive, but tests (and code?) expects throw exception if can't connect;
diff --git a/core/src/main/java/brooklyn/util/internal/ssh/sshj/SshjTool.java b/core/src/main/java/brooklyn/util/internal/ssh/sshj/SshjTool.java
index f72e149..6f87f45 100644
--- a/core/src/main/java/brooklyn/util/internal/ssh/sshj/SshjTool.java
+++ b/core/src/main/java/brooklyn/util/internal/ssh/sshj/SshjTool.java
@@ -31,7 +31,7 @@
import org.jclouds.io.Payloads;
import org.jclouds.io.payloads.ByteArrayPayload;
import org.jclouds.io.payloads.FilePayload;
-import org.jclouds.io.payloads.StringPayload;
+import org.jclouds.io.payloads.InputStreamPayload;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -50,6 +50,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Files;
+import com.google.common.io.LimitInputStream;
import com.google.common.net.HostAndPort;
/**
@@ -188,58 +189,89 @@
}
@Override
+ public int copyToServer(java.util.Map<String,?> props, byte[] contents, String pathAndFileOnRemoteServer) {
+ return copyToServer(props, toPayload(contents), pathAndFileOnRemoteServer);
+ }
+
+ @Override
+ public int copyToServer(java.util.Map<String,?> props, InputStream contents, String pathAndFileOnRemoteServer) {
+ /*
+ * TODO sshj needs to know the length of the InputStream to copy the file:
+ * java.lang.NullPointerException
+ * at brooklyn.util.internal.ssh.SshjTool$PutFileAction$1.getLength(SshjTool.java:574)
+ * at net.schmizz.sshj.sftp.SFTPFileTransfer$Uploader.upload(SFTPFileTransfer.java:174)
+ * at net.schmizz.sshj.sftp.SFTPFileTransfer$Uploader.access$100(SFTPFileTransfer.java:162)
+ * at net.schmizz.sshj.sftp.SFTPFileTransfer.upload(SFTPFileTransfer.java:61)
+ * at net.schmizz.sshj.sftp.SFTPClient.put(SFTPClient.java:248)
+ * at brooklyn.util.internal.ssh.SshjTool$PutFileAction.create(SshjTool.java:569)
+ *
+ * Unfortunately that requires consuming the input stream to find out! We can't just do:
+ * new InputStreamPayload(input)
+ *
+ * This is nasty: we have to either write it to a temp file, or hold the entire contents in-memory.
+ * It's worth a look at changing sshj to not need the length, if possible.
+ *
+ * TODO Could have a switch where we hold it in memory if less than some max size, but write it to the file if
+ * too big.
+ */
+ File tempFile = writeTempFile(contents);
+ try {
+ return copyToServer(props, tempFile, pathAndFileOnRemoteServer);
+ } finally {
+ tempFile.delete();
+ }
+ }
+
+ @Override
+ public int copyToServer(java.util.Map<String,?> props, File localFile, String pathAndFileOnRemoteServer) {
+ return copyToServer(props, toPayload(localFile), pathAndFileOnRemoteServer);
+ }
+
+ @Override
public int transferFileTo(Map<String,?> props, InputStream input, String pathAndFileOnRemoteServer) {
- return createFile(props, pathAndFileOnRemoteServer, toPayload(input));
+ return copyToServer(props, input, pathAndFileOnRemoteServer);
}
@Override
public int createFile(Map<String,?> props, String pathAndFileOnRemoteServer, InputStream input, long size) {
- return createFile(props, pathAndFileOnRemoteServer, toPayload(input, size));
+ return copyToServer(props, toPayload(input, size), pathAndFileOnRemoteServer);
}
- /**
- * Creates the given file with the given contents.
- *
- * Permissions specified using 'permissions:0755'.
- */
@Override
public int createFile(Map<String,?> props, String pathAndFileOnRemoteServer, String contents) {
- return createFile(props, pathAndFileOnRemoteServer, new StringPayload(contents));
+ return copyToServer(props, contents.getBytes(), pathAndFileOnRemoteServer);
}
- /** Creates the given file with the given contents.
- *
- * Permissions specified using 'permissions:0755'.
- */
@Override
public int createFile(Map<String,?> props, String pathAndFileOnRemoteServer, byte[] contents) {
- return createFile(props, pathAndFileOnRemoteServer, new ByteArrayPayload(contents));
+ return copyToServer(props, contents, pathAndFileOnRemoteServer);
}
- @Override
- public int copyToServer(Map<String,?> props, File f, String pathAndFileOnRemoteServer) {
- return createFile(props, pathAndFileOnRemoteServer, new FilePayload(f));
+ private int copyToServer(Map<String,?> props, Payload contents, String pathAndFileOnRemoteServer) {
+ acquire(new PutFileAction(props, pathAndFileOnRemoteServer, contents));
+ return 0; // TODO Can we assume put will have thrown exception if failed? Rather than exit code != 0?
}
@Override
public int transferFileFrom(Map<String,?> props, String pathAndFileOnRemoteServer, String pathAndFileOnLocalServer) {
+ return copyFromServer(props, pathAndFileOnRemoteServer, new File(pathAndFileOnLocalServer));
+ }
+
+ @Override
+ public int copyFromServer(Map<String,?> props, String pathAndFileOnRemoteServer, File localFile) {
Payload payload = acquire(new GetFileAction(pathAndFileOnRemoteServer));
try {
- Files.copy(InputSuppliers.of(payload.getInput()), new File(pathAndFileOnLocalServer));
+ Files.copy(InputSuppliers.of(payload.getInput()), localFile);
return 0; // TODO Can we assume put will have thrown exception if failed? Rather than exit code != 0?
} catch (IOException e) {
throw Throwables.propagate(e);
}
}
- private int createFile(Map<String,?> props, String pathAndFileOnRemoteServer, Payload payload) {
- acquire(new PutFileAction(props, pathAndFileOnRemoteServer, payload));
- return 0; // TODO Can we assume put will have thrown exception if failed? Rather than exit code != 0?
- }
-
public int execShell(Map<String,?> props, List<String> commands) {
return execScript(props, commands, Collections.<String,Object>emptyMap());
}
+
public int execShell(Map<String,?> props, List<String> commands, Map<String,?> env) {
return execScript(props, commands, env);
}
@@ -286,7 +318,7 @@
if (LOG.isTraceEnabled()) LOG.trace("Running shell command at {} as script: {}", host, scriptContents);
- createFile(ImmutableMap.of("permissions", "0700"), scriptPath, scriptContents);
+ copyToServer(ImmutableMap.of("permissions", "0700"), scriptContents.getBytes(), scriptPath);
// use "-f" because some systems have "rm" aliased to "rm -i"; use "< /dev/null" to guarantee doesn't hang
List<String> cmds = ImmutableList.of(
@@ -453,10 +485,10 @@
private long lastAccessDate;
PutFileAction(Map<String,?> props, String path, Payload contents) {
- String permissions = getOptionalVal(props, "permissions", String.class, "0644");
+ String permissions = getOptionalVal(props, PROP_PERMISSIONS, "0644");
permissionsMask = Integer.parseInt(permissions, 8);
- lastModificationDate = getOptionalVal(props, "lastModificationDate", Long.class, 0L);
- lastAccessDate = getOptionalVal(props, "lastAccessDate", Long.class, 0L);
+ lastModificationDate = getOptionalVal(props, PROP_LAST_MODIFICATION_DATE, 0L);
+ lastAccessDate = getOptionalVal(props, PROP_LAST_ACCESS_DATE, 0L);
if (lastAccessDate <= 0 ^ lastModificationDate <= 0) {
lastAccessDate = Math.max(lastAccessDate, lastModificationDate);
lastModificationDate = Math.max(lastAccessDate, lastModificationDate);
@@ -743,4 +775,17 @@
}
}
+ protected Payload toPayload(byte[] input) {
+ return new ByteArrayPayload(input);
+ }
+
+ protected Payload toPayload(File input) {
+ return new FilePayload(input);
+ }
+
+ protected Payload toPayload(InputStream input, long length) {
+ InputStreamPayload payload = new InputStreamPayload(new LimitInputStream(input, length));
+ payload.getContentMetadata().setContentLength(length);
+ return payload;
+ }
}
diff --git a/core/src/test/java/brooklyn/entity/basic/EffectorConcatenateTest.groovy b/core/src/test/java/brooklyn/entity/basic/EffectorConcatenateTest.groovy
deleted file mode 100644
index 06e8098..0000000
--- a/core/src/test/java/brooklyn/entity/basic/EffectorConcatenateTest.groovy
+++ /dev/null
@@ -1,178 +0,0 @@
-package brooklyn.entity.basic;
-
-import static org.testng.Assert.*
-import groovy.transform.InheritConstructors
-
-import java.util.concurrent.TimeUnit
-import java.util.concurrent.atomic.AtomicReference
-
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-import org.testng.annotations.AfterMethod
-import org.testng.annotations.BeforeMethod
-import org.testng.annotations.Test
-
-import brooklyn.entity.Application
-import brooklyn.entity.Effector
-import brooklyn.entity.Entity
-import brooklyn.management.ExecutionManager
-import brooklyn.management.Task
-import brooklyn.test.entity.TestApplication
-import brooklyn.util.task.BasicExecutionContext
-import brooklyn.util.task.Tasks
-
-public class EffectorConcatenateTest {
-
-
- private static final Logger log = LoggerFactory.getLogger(EffectorConcatenateTest.class);
- private static final long TIMEOUT = 10*1000
-
- public class MyEntity extends AbstractEntity {
-
- public static Effector<String> CONCATENATE = new MethodEffector<Void>(MyEntity.class, "concatenate");
-
- public MyEntity(Map flags) {
- super(flags)
- }
- public MyEntity(Entity parent) {
- super(parent)
- }
- public MyEntity(Map flags, Entity parent) {
- super(flags, parent)
- }
-
- AtomicReference concatTask = new AtomicReference();
- // FIXME instead of waiting on this we should use semaphores -- seems we very occasionally get spurious wakes
- AtomicReference response = new AtomicReference();
-
- @Description("sample effector concatenating strings and sometimes waiting")
- String concatenate(@NamedParameter("first") @Description("first argument") String first,
- @NamedParameter("second") @Description("2nd arg") String second) {
- if ("wait".equals(first)) {
- // if first arg is wait, spawn a child, then wait
- BasicExecutionContext.getCurrentExecutionContext().submit(
- displayName: "SarcyResponse",
- {
- log.info("beginning scary response "+Tasks.current()+", with tags "+Tasks.current().tags)
- synchronized (response) {
- Tasks.setBlockingDetails("looks like the backstroke to me");
- response.notifyAll();
- response.wait(TIMEOUT);
- }
- });
-
- Tasks.setExtraStatusDetails("What's the soup du jour? That's the soup of the day!");
-
- // wait, setting task info from the second arg
- // (test will assert that status details are reported correctly)
- long startTime = System.currentTimeMillis();
- synchronized (concatTask) {
- concatTask.set(Tasks.current());
- concatTask.notifyAll();
- Tasks.withBlockingDetails(second) {
- concatTask.wait(TIMEOUT);
- }
- concatTask.set(null);
- }
- if (System.currentTimeMillis()-startTime >= TIMEOUT)
- fail("took too long, probably wasn't notified");
- }
- return first+second
- }
- }
-
- private Application app;
- private MyEntity e;
-
- @BeforeMethod(alwaysRun=true)
- public void setUp() {
- app = new TestApplication();
- e = new MyEntity(app);
- Entities.startManagement(app);
- }
-
- @AfterMethod(alwaysRun=true)
- public void tearDown() {
- if (app != null) Entities.destroyAll(app);
- }
-
- @Test
- public void testCanInvokeEffector() {
- // invocation map syntax
- Task<String> task = e.invoke(MyEntity.CONCATENATE, [first:"a",second:"b"])
- assertEquals(task.get(TIMEOUT, TimeUnit.MILLISECONDS), "ab")
-
- // method syntax
- assertEquals("xy", e.concatenate("x", "y"));
- }
-
- @Test
- public void testTaskReporting() {
- final AtomicReference<String> result = new AtomicReference<String>();
-
- Thread bg = new Thread({
- try {
- long startTime = System.currentTimeMillis();
- synchronized (e.concatTask) {
- try {
- while (e.concatTask.get()==null) {
- e.concatTask.wait(1000);
- if (System.currentTimeMillis()-startTime >= TIMEOUT) {
- result.set("took too long, probably wasn't notified");
- return;
- }
- }
-
- Task t = e.concatTask.get();
- String status = t.getStatusDetail(true);
- log.info("concat task says:\n"+status);
- if (!status.startsWith("waiter, what's this fly doing")) {
- result.set("Status not in expected format: doesn't start with blocking details 'waiter...'\n"+status);
- return;
- }
- if (!status.contains("du jour")) {
- result.set("Status not in expected format: doesn't contain extra status details phrase 'du jour'\n"+status);
- return;
- }
- // looks healthy
- } finally {
- e.concatTask.notifyAll();
- }
- }
-
- ExecutionManager em = e.getExecutionContext().getExecutionManager();
- synchronized (e.response) {
- Task reply=null;
- while (reply==null) {
- Collection<Task> entityTasks = em.getTasksWithTag(e);
- log.info("entity "+e+" running: "+entityTasks);
- reply = entityTasks.find { Task t -> t.displayName=="SarcyResponse" }
- if (reply!=null) break;
- if (System.currentTimeMillis()-startTime >= TIMEOUT) {
- result.set("response took too long, probably wasn't notified");
- return;
- }
- e.response.wait(TIMEOUT);
- }
- String status = reply.getStatusDetail(true);
- log.info("reply task says:\n"+status);
- if (!status.contains("backstroke")) {
- result.set("Status not in expected format: doesn't contain blocking details phrase 'backstroke'\n"+status);
- return;
- }
- e.response.notifyAll();
- }
- } catch (Throwable t) {
- log.warn("Failure: "+t, t);
- result.set("Failure: "+t);
- }
- });
- bg.start();
-
- e.concatenate("wait", "waiter, what's this fly doing in my soup?");
-
- bg.join();
- String problem = result.get();
- if (problem!=null) fail(problem);
- }
-}
diff --git a/core/src/test/java/brooklyn/entity/basic/EffectorConcatenateTest.java b/core/src/test/java/brooklyn/entity/basic/EffectorConcatenateTest.java
new file mode 100644
index 0000000..3d8732f
--- /dev/null
+++ b/core/src/test/java/brooklyn/entity/basic/EffectorConcatenateTest.java
@@ -0,0 +1,219 @@
+package brooklyn.entity.basic;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.fail;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.Application;
+import brooklyn.entity.Effector;
+import brooklyn.entity.Entity;
+import brooklyn.management.ExecutionManager;
+import brooklyn.management.Task;
+import brooklyn.test.entity.TestApplication;
+import brooklyn.util.MutableMap;
+import brooklyn.util.task.BasicExecutionContext;
+import brooklyn.util.task.Tasks;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
+public class EffectorConcatenateTest {
+
+
+ private static final Logger log = LoggerFactory.getLogger(EffectorConcatenateTest.class);
+ private static final long TIMEOUT = 10*1000;
+
+ public static class MyEntity extends AbstractEntity {
+
+ public static Effector<String> CONCATENATE = new MethodEffector<String>(MyEntity.class, "concatenate");
+ public static Effector<Void> WAIT_A_BIT = new MethodEffector<Void>(MyEntity.class, "waitabit");
+ public static Effector<Void> SPAWN_CHILD = new MethodEffector<Void>(MyEntity.class, "spawnchild");
+
+ public MyEntity() {
+ super();
+ }
+ public MyEntity(Entity parent) {
+ super(parent);
+ }
+
+ /** The "current task" representing the effector currently executing */
+ AtomicReference<Task<?>> waitingTask = new AtomicReference<Task<?>>();
+
+ /** latch is .countDown'ed by the effector at the beginning of the "waiting" point */
+ CountDownLatch nowWaitingLatch = new CountDownLatch(1);
+
+ /** latch is await'ed on by the effector when it is in the "waiting" point */
+ CountDownLatch continueFromWaitingLatch = new CountDownLatch(1);
+
+ @Description("sample effector concatenating strings")
+ public String concatenate(@NamedParameter("first") @Description("first argument") String first,
+ @NamedParameter("second") @Description("2nd arg") String second) throws Exception {
+ return first+second;
+ }
+
+ @Description("sample effector doing some waiting")
+ public void waitabit() throws Exception {
+ waitingTask.set(Tasks.current());
+
+ Tasks.setExtraStatusDetails("waitabit extra status details");
+
+ Tasks.withBlockingDetails("waitabit.blocking", new Callable<Void>() {
+ public Void call() throws Exception {
+ nowWaitingLatch.countDown();
+ if (!continueFromWaitingLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) {
+ fail("took too long to be told to continue");
+ }
+ return null;
+ }});
+ }
+
+ @Description("sample effector that spawns a child task that waits a bit")
+ public void spawnchild() throws Exception {
+ // spawn a child, then wait
+ BasicExecutionContext.getCurrentExecutionContext().submit(
+ MutableMap.of("displayName", "SpawnedChildName"),
+ new Callable<Void>() {
+ public Void call() throws Exception {
+ log.info("beginning spawned child response "+Tasks.current()+", with tags "+Tasks.current().getTags());
+ Tasks.setBlockingDetails("spawned child blocking details");
+ nowWaitingLatch.countDown();
+ if (!continueFromWaitingLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) {
+ fail("took too long to be told to continue");
+ }
+ return null;
+ }});
+ }
+ }
+
+ private Application app;
+ private MyEntity e;
+
+ @BeforeMethod(alwaysRun=true)
+ public void setUp() {
+ app = new TestApplication();
+ e = new MyEntity(app);
+ Entities.startManagement(app);
+ }
+
+ @AfterMethod(alwaysRun=true)
+ public void tearDown() {
+ if (app != null) Entities.destroyAll(app);
+ }
+
+ @Test
+ public void testCanInvokeEffector() throws Exception {
+ // invocation map syntax
+ Task<String> task = e.invoke(MyEntity.CONCATENATE, ImmutableMap.of("first", "a", "second", "b"));
+ assertEquals(task.get(TIMEOUT, TimeUnit.MILLISECONDS), "ab");
+
+ // method syntax
+ assertEquals("xy", e.concatenate("x", "y"));
+ }
+
+ @Test
+ public void testReportsTaskDetails() throws Exception {
+ final AtomicReference<String> result = new AtomicReference<String>();
+
+ Thread bg = new Thread(new Runnable() {
+ public void run() {
+ try {
+ // Expect "wait a bit" to tell us it's blocking
+ if (!e.nowWaitingLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) {
+ result.set("took too long for waitabit to be waiting");
+ return;
+ }
+
+ // Expect "wait a bit" to have retrieved and set its task
+ try {
+ Task<?> t = e.waitingTask.get();
+ String status = t.getStatusDetail(true);
+ log.info("waitabit task says:\n"+status);
+ if (!status.contains("waitabit extra status details")) {
+ result.set("Status not in expected format: doesn't contain extra status details phrase 'My extra status details'\n"+status);
+ return;
+ }
+ if (!status.startsWith("waitabit.blocking")) {
+ result.set("Status not in expected format: doesn't start with blocking details 'waitabit.blocking'\n"+status);
+ return;
+ }
+ } finally {
+ e.continueFromWaitingLatch.countDown();
+ }
+ } catch (Throwable t) {
+ log.warn("Failure: "+t, t);
+ result.set("Failure: "+t);
+ }
+ }});
+ bg.start();
+
+ e.invoke(MyEntity.WAIT_A_BIT, ImmutableMap.<String,Object>of())
+ .get(TIMEOUT, TimeUnit.MILLISECONDS);
+
+ bg.join(TIMEOUT*2);
+ assertFalse(bg.isAlive());
+
+ String problem = result.get();
+ if (problem!=null) fail(problem);
+ }
+
+ @Test
+ public void testReportsSpawnedTaskDetails() throws Exception {
+ final AtomicReference<String> result = new AtomicReference<String>();
+
+ Thread bg = new Thread(new Runnable() {
+ public void run() {
+ try {
+ // Expect "spawned child" to tell us it's blocking
+ if (!e.nowWaitingLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) {
+ result.set("took too long for spawnchild's sub-task to be waiting");
+ return;
+ }
+
+ // Expect spawned task to be have been tagged with entity
+ ExecutionManager em = e.getManagementContext().getExecutionManager();
+ Task<?> subtask = Iterables.find(em.getTasksWithTag(e), new Predicate<Task<?>>() {
+ public boolean apply(Task<?> input) {
+ return "SpawnedChildName".equals(input.getDisplayName());
+ }
+ });
+
+ // Expect spawned task to haev correct "blocking details"
+ try {
+ String status = subtask.getStatusDetail(true);
+ log.info("subtask task says:\n"+status);
+ if (!status.contains("spawned child blocking details")) {
+ result.set("Status not in expected format: doesn't contain blocking details phrase 'spawned child blocking details'\n"+status);
+ return;
+ }
+ } finally {
+ e.continueFromWaitingLatch.countDown();
+ }
+ } catch (Throwable t) {
+ log.warn("Failure: "+t, t);
+ result.set("Failure: "+t);
+ }
+ }});
+ bg.start();
+
+ e.invoke(MyEntity.SPAWN_CHILD, ImmutableMap.<String,Object>of())
+ .get(TIMEOUT, TimeUnit.MILLISECONDS);
+
+ bg.join(TIMEOUT*2);
+ assertFalse(bg.isAlive());
+
+ String problem = result.get();
+ if (problem!=null) fail(problem);
+ }
+}
diff --git a/core/src/test/java/brooklyn/util/internal/ssh/SshToolIntegrationTest.java b/core/src/test/java/brooklyn/util/internal/ssh/SshToolIntegrationTest.java
index fc87a55..87c84a0 100644
--- a/core/src/test/java/brooklyn/util/internal/ssh/SshToolIntegrationTest.java
+++ b/core/src/test/java/brooklyn/util/internal/ssh/SshToolIntegrationTest.java
@@ -17,12 +17,12 @@
import java.util.Map;
import java.util.concurrent.Executors;
+import org.jclouds.util.Throwables2;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import brooklyn.util.MutableMap;
-import brooklyn.util.internal.ssh.sshj.SshjTool;
import brooklyn.util.text.Identifiers;
import com.beust.jcommander.internal.Lists;
@@ -37,11 +37,17 @@
import com.google.common.util.concurrent.MoreExecutors;
/**
- * Test the operation of the {@link SshJschTool} utility class.
+ * Test the operation of the {@link SshTool} utility class; to be extended to test concrete implementations.
*/
public abstract class SshToolIntegrationTest {
+ // FIXME need tests which take properties set in entities and brooklyn.properties;
+ // but not in this class because it is lower level than entities, Aled would argue.
+
// TODO No tests for retry logic and exception handing yet
+
+ public static final String SSH_KEY_WITH_PASSPHRASE = System.getProperty("sshPrivateKeyWithPassphrase", "~/.ssh/id_rsa_with_passphrase");
+ public static final String SSH_PASSPHRASE = System.getProperty("sshPrivateKeyPassphrase", "mypassphrase");
protected List<SshTool> tools = Lists.newArrayList();
protected SshTool tool;
@@ -51,7 +57,7 @@
protected abstract SshTool newSshTool(Map<String,?> flags);
- @BeforeMethod(alwaysRun=true)//(groups = {"Integration"})
+ @BeforeMethod(alwaysRun=true)
public void setUp() throws Exception {
localFilePath = "/tmp/ssh-test-local-"+Identifiers.makeRandomId(8);
remoteFilePath = "/tmp/ssh-test-remote-"+Identifiers.makeRandomId(8);
@@ -283,18 +289,36 @@
}
@Test(groups = {"Integration"})
- public void testCreateFileWithPermissions() throws Exception {
- tool.createFile(ImmutableMap.of("permissions","0754"), remoteFilePath, "echo hello world!\n");
+ public void testCopyToServerFromBytes() throws Exception {
+ String contents = "echo hello world!\n";
+ byte[] contentBytes = contents.getBytes();
+ tool.copyToServer(MutableMap.<String,Object>of(), contentBytes, remoteFilePath);
+
+ assertRemoteFileContents(remoteFilePath, contents);
+ }
+
+ @Test(groups = {"Integration"})
+ public void testCopyToServerFromInputStream() throws Exception {
+ String contents = "echo hello world!\n";
+ ByteArrayInputStream contentsStream = new ByteArrayInputStream(contents.getBytes());
+ tool.copyToServer(MutableMap.<String,Object>of(), contentsStream, remoteFilePath);
+
+ assertRemoteFileContents(remoteFilePath, contents);
+ }
+
+ @Test(groups = {"Integration"})
+ public void testCopyToServerWithPermissions() throws Exception {
+ tool.copyToServer(ImmutableMap.of("permissions","0754"), "echo hello world!\n".getBytes(), remoteFilePath);
String out = execCommands("ls -l "+remoteFilePath);
assertTrue(out.contains("-rwxr-xr--"), out);
}
@Test(groups = {"Integration"})
- public void testCreateFileWithLastModifiedDate() throws Exception {
+ public void testCopyToServerWithLastModifiedDate() throws Exception {
long lastModificationTime = 1234567;
Date lastModifiedDate = new Date(lastModificationTime);
- tool.createFile(ImmutableMap.of("lastModificationDate", lastModificationTime), remoteFilePath, "echo hello world!\n");
+ tool.copyToServer(ImmutableMap.of("lastModificationDate", lastModificationTime), "echo hello world!\n".getBytes(), remoteFilePath);
String lsout = execCommands("ls -l "+remoteFilePath);//+" | awk '{print \$6 \" \" \$7 \" \" \$8}'"])
//execCommands([ "ls -l "+remoteFilePath+" | awk '{print \$6 \" \" \$7 \" \" \$8}'"])
@@ -316,6 +340,19 @@
}
@Test(groups = {"Integration"})
+ public void testCopyFromServer() throws Exception {
+ String contentsWithoutLineBreak = "echo hello world!";
+ String contents = contentsWithoutLineBreak+"\n";
+ tool.copyToServer(MutableMap.<String,Object>of(), contents.getBytes(), remoteFilePath);
+
+ tool.copyFromServer(MutableMap.<String,Object>of(), remoteFilePath, new File(localFilePath));
+
+ List<String> actual = Files.readLines(new File(localFilePath), Charsets.UTF_8);
+ assertEquals(actual, ImmutableList.of(contentsWithoutLineBreak));
+ }
+
+ @Test(groups = {"Integration"})
+ @Deprecated // tests deprecated code
public void testTransferFileToServer() throws Exception {
String contents = "echo hello world!\n";
ByteArrayInputStream contentsStream = new ByteArrayInputStream(contents.getBytes());
@@ -325,6 +362,7 @@
}
@Test(groups = {"Integration"})
+ @Deprecated // tests deprecated code
public void testCreateFileFromBytes() throws Exception {
String contents = "echo hello world!\n";
byte[] contentBytes = contents.getBytes();
@@ -334,6 +372,7 @@
}
@Test(groups = {"Integration"})
+ @Deprecated // tests deprecated code
public void testCreateFileFromInputStream() throws Exception {
String contents = "echo hello world!\n";
ByteArrayInputStream contentsStream = new ByteArrayInputStream(contents.getBytes());
@@ -343,10 +382,11 @@
}
@Test(groups = {"Integration"})
+ @Deprecated // tests deprecated code
public void testTransferFileFromServer() throws Exception {
String contentsWithoutLineBreak = "echo hello world!";
String contents = contentsWithoutLineBreak+"\n";
- tool.createFile(MutableMap.<String,Object>of(), remoteFilePath, contents);
+ tool.copyToServer(MutableMap.<String,Object>of(), contents.getBytes(), remoteFilePath);
tool.transferFileFrom(MutableMap.<String,Object>of(), remoteFilePath, localFilePath);
@@ -356,20 +396,21 @@
// TODO No config options in sshj or scp for auto-creating the parent directories
@Test(enabled=false, groups = {"Integration"})
- public void testCreateFileInNonExistantDir() throws Exception {
+ public void testCopyFileToNonExistantDir() throws Exception {
String contents = "echo hello world!\n";
String remoteFileDirPath = "/tmp/ssh-test-remote-dir-"+Identifiers.makeRandomId(8);
String remoteFileInDirPath = remoteFileDirPath + File.separator + "ssh-test-remote-"+Identifiers.makeRandomId(8);
filesCreated.add(remoteFileInDirPath);
filesCreated.add(remoteFileDirPath);
- tool.createFile(MutableMap.<String,Object>of(), remoteFileInDirPath, contents);
+ tool.copyToServer(MutableMap.<String,Object>of(), contents.getBytes(), remoteFileInDirPath);
assertRemoteFileContents(remoteFileInDirPath, contents);
}
// fails if terminal enabled
@Test(groups = {"Integration"})
+ @Deprecated // tests deprecated code
public void testExecShellCapturesStderr() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream err = new ByteArrayOutputStream();
@@ -380,6 +421,7 @@
// fails if terminal enabled
@Test(groups = {"Integration"})
+ @Deprecated // tests deprecated code
public void testExecCapturesStderr() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream err = new ByteArrayOutputStream();
@@ -412,7 +454,33 @@
assertTrue(out.contains("hello world"), "no hello in output: "+out);
}
-// XXX; // new tests which take properties set in entities and brooklyn.properties
+ // Requires setting up an extra ssh key, with a passphrase, and adding it to ~/.ssh/authorized_keys
+ @Test(groups = {"Integration"})
+ public void testSshKeyWithPassphrase() throws Exception {
+ final SshTool localtool = newSshTool(ImmutableMap.<String,Object>builder()
+ .put(SshTool.PROP_HOST.getName(), "localhost")
+ .put(SshTool.PROP_PRIVATE_KEY_FILE.getName(), SSH_KEY_WITH_PASSPHRASE)
+ .put(SshTool.PROP_PRIVATE_KEY_PASSPHRASE.getName(), SSH_PASSPHRASE)
+ .build());
+ tools.add(localtool);
+ localtool.connect();
+
+ assertEquals(tool.execScript(MutableMap.<String,Object>of(), ImmutableList.of("date")), 0);
+
+ // Also needs the negative test to prove that we're really using an ssh-key with a passphrase
+ try {
+ final SshTool localtool2 = newSshTool(ImmutableMap.<String,Object>builder()
+ .put(SshTool.PROP_HOST.getName(), "localhost")
+ .put(SshTool.PROP_PRIVATE_KEY_FILE.getName(), SSH_KEY_WITH_PASSPHRASE)
+ .build());
+ tools.add(localtool2);
+ localtool2.connect();
+ fail();
+ } catch (Exception e) {
+ SshException se = Throwables2.getFirstThrowableOfType(e, SshException.class);
+ if (se == null) throw e;
+ }
+ }
private void assertRemoteFileContents(String remotePath, String expectedContents) {
String catout = execCommands("cat "+remotePath);
diff --git a/core/src/test/java/brooklyn/util/internal/ssh/cli/SshCliToolIntegrationTest.java b/core/src/test/java/brooklyn/util/internal/ssh/cli/SshCliToolIntegrationTest.java
index bd69203..a6c8a2f 100644
--- a/core/src/test/java/brooklyn/util/internal/ssh/cli/SshCliToolIntegrationTest.java
+++ b/core/src/test/java/brooklyn/util/internal/ssh/cli/SshCliToolIntegrationTest.java
@@ -1,13 +1,11 @@
package brooklyn.util.internal.ssh.cli;
-import static org.testng.Assert.assertEquals;
import static org.testng.Assert.fail;
import java.util.Map;
import org.testng.annotations.Test;
-import brooklyn.util.MutableMap;
import brooklyn.util.internal.ssh.SshException;
import brooklyn.util.internal.ssh.SshTool;
import brooklyn.util.internal.ssh.SshToolIntegrationTest;
@@ -44,20 +42,16 @@
}
}
+ // TODO ssh-cli doesn't support pass-phrases yet
+ @Test(enabled=false, groups = {"Integration"})
+ public void testSshKeyWithPassphrase() throws Exception {
+ super.testSshKeyWithPassphrase();
+ }
+
// Setting last modified date not yet supported for cli-based ssh
@Override
@Test(enabled=false, groups = {"Integration"})
- public void testCreateFileWithLastModifiedDate() throws Exception {
- super.testCreateFileWithLastModifiedDate();
- }
-
- @Test(groups = {"Integration"})
- public void testCreateFileFromBytes() throws Exception {
- super.testCreateFileFromBytes();
- }
-
- @Test(groups = {"Integration"})
- public void testExecShellReturningZeroExitCode() throws Exception {
- super.testExecShellReturningZeroExitCode();
+ public void testCopyToServerWithLastModifiedDate() throws Exception {
+ super.testCopyToServerWithLastModifiedDate();
}
}
diff --git a/examples/pom.xml b/examples/pom.xml
index 6c29dd4..95ff73a 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -19,6 +19,17 @@
<relativePath>../pom.xml</relativePath>
</parent>
+ <repositories>
+ <!-- enable sonatype snapshots repo (only for snapshots) -->
+ <repository>
+ <id>sonatype-nexus-snapshots</id>
+ <name>Sonatype Nexus Snapshots</name>
+ <url>https://oss.sonatype.org/content/repositories/snapshots</url>
+ <releases> <enabled>false</enabled> </releases>
+ <snapshots> <enabled>true</enabled> </snapshots>
+ </repository>
+ </repositories>
+
<modules>
<module>webapps</module>
<module>simple-web-cluster</module>