http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4c0e5fd/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/ShellToolAbstractTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/ShellToolAbstractTest.java b/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/ShellToolAbstractTest.java new file mode 100644 index 0000000..794a512 --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/ShellToolAbstractTest.java @@ -0,0 +1,441 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.brooklyn.core.util.internal.ssh; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotEquals; +import static org.testng.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.apache.brooklyn.core.util.config.ConfigBag; +import org.apache.brooklyn.core.util.internal.ssh.ShellTool; +import org.apache.brooklyn.core.util.internal.ssh.SshTool; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import brooklyn.util.collections.MutableMap; +import brooklyn.util.text.Identifiers; +import brooklyn.util.time.Time; + +import com.google.common.base.Stopwatch; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; + +public abstract class ShellToolAbstractTest { + + protected List<ShellTool> tools = Lists.newArrayList(); + protected List<String> filesCreated; + protected String localFilePath; + + protected ShellTool tool; + + protected ShellTool newTool() { + return newTool(MutableMap.<String,Object>of()); + } + + protected ShellTool newTool(Map<String,?> flags) { + ShellTool t = newUnregisteredTool(flags); + tools.add(t); + return t; + } + + protected abstract ShellTool newUnregisteredTool(Map<String,?> flags); + + protected ShellTool tool() { return tool; } + + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + localFilePath = "/tmp/ssh-test-local-"+Identifiers.makeRandomId(8); + filesCreated = new ArrayList<String>(); + filesCreated.add(localFilePath); + + tool = newTool(); + connect(tool); + } + + @AfterMethod(alwaysRun=true) + public void afterMethod() throws Exception { + for (ShellTool t : tools) { + if (t instanceof SshTool) ((SshTool)t).disconnect(); + } + for (String fileCreated : filesCreated) { + new File(fileCreated).delete(); + } + } + + protected static void connect(ShellTool tool) { + if (tool instanceof SshTool) + ((SshTool)tool).connect(); + } + + @Test(groups = {"Integration"}) + public void testExecConsecutiveCommands() throws Exception { + String out = execScript("echo run1"); + String out2 = execScript("echo run2"); + + assertTrue(out.contains("run1"), "out="+out); + assertTrue(out2.contains("run2"), "out="+out); + } + + @Test(groups = {"Integration"}) + public void testExecScriptChainOfCommands() throws Exception { + String out = execScript("export MYPROP=abc", "echo val is $MYPROP"); + + assertTrue(out.contains("val is abc"), "out="+out); + } + + @Test(groups = {"Integration"}) + public void testExecScriptReturningNonZeroExitCode() throws Exception { + int exitcode = tool.execScript(MutableMap.<String,Object>of(), ImmutableList.of("exit 123")); + assertEquals(exitcode, 123); + } + + @Test(groups = {"Integration"}) + public void testExecScriptReturningZeroExitCode() throws Exception { + int exitcode = tool.execScript(MutableMap.<String,Object>of(), ImmutableList.of("date")); + assertEquals(exitcode, 0); + } + + @Test(groups = {"Integration"}) + public void testExecScriptCommandWithEnvVariables() throws Exception { + String out = execScript(ImmutableList.of("echo val is $MYPROP2"), ImmutableMap.of("MYPROP2", "myval")); + + assertTrue(out.contains("val is myval"), "out="+out); + } + + @Test(groups = {"Integration"}) + public void testScriptDataNotLost() throws Exception { + String out = execScript("echo `echo foo``echo bar`"); + + assertTrue(out.contains("foobar"), "out="+out); + } + + @Test(groups = {"Integration"}) + public void testExecScriptWithSleepThenExit() throws Exception { + Stopwatch watch = Stopwatch.createStarted(); + execScript("sleep 1", "exit 0"); + assertTrue(watch.elapsed(TimeUnit.MILLISECONDS) > 900, "only slept "+Time.makeTimeStringRounded(watch)); + } + + // Really just tests that it returns; the command will be echo'ed automatically so this doesn't assert the command will have been executed + @Test(groups = {"Integration"}) + public void testExecScriptBigCommand() throws Exception { + String bigstring = Strings.repeat("a", 10000); + String out = execScript("echo "+bigstring); + + assertTrue(out.contains(bigstring), "out="+out); + } + + @Test(groups = {"Integration"}) + public void testExecScriptBigChainOfCommand() throws Exception { + String bigstring = Strings.repeat("abcdefghij", 100); // 1KB + List<String> cmds = Lists.newArrayList(); + for (int i = 0; i < 10; i++) { + cmds.add("export MYPROP"+i+"="+bigstring); + cmds.add("echo val"+i+" is $MYPROP"+i); + } + String out = execScript(cmds); + + for (int i = 0; i < 10; i++) { + assertTrue(out.contains("val"+i+" is "+bigstring), "out="+out); + } + } + + @Test(groups = {"Integration"}) + public void testExecScriptAbortsOnCommandFailure() throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int exitcode = tool.execScript(ImmutableMap.of("out", out), ImmutableList.of("export MYPROP=myval", "acmdthatdoesnotexist", "echo val is $MYPROP")); + String outstr = new String(out.toByteArray()); + + assertFalse(outstr.contains("val is myval"), "out="+out); + assertNotEquals(exitcode, 0); + } + + @Test(groups = {"Integration"}) + public void testExecScriptWithSleepThenBigCommand() throws Exception { + String bigstring = Strings.repeat("abcdefghij", 1000); // 10KB + String out = execScript("sleep 2", "export MYPROP="+bigstring, "echo val is $MYPROP"); + assertTrue(out.contains("val is "+bigstring), "out="+out); + } + + @Test(groups = {"WIP", "Integration"}) + public void testExecScriptBigConcurrentCommand() throws Exception { + ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool()); + List<ListenableFuture<?>> futures = new ArrayList<ListenableFuture<?>>(); + try { + for (int i = 0; i < 10; i++) { + final ShellTool localtool = newTool(); + connect(localtool); + + futures.add(executor.submit(new Runnable() { + public void run() { + String bigstring = Strings.repeat("abcdefghij", 1000); // 10KB + String out = execScript(localtool, ImmutableList.of("export MYPROP="+bigstring, "echo val is $MYPROP")); + assertTrue(out.contains("val is "+bigstring), "outSize="+out.length()+"; out="+out); + }})); + } + Futures.allAsList(futures).get(); + } finally { + executor.shutdownNow(); + } + } + + @Test(groups = {"WIP", "Integration"}) + public void testExecScriptBigConcurrentSleepyCommand() throws Exception { + ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool()); + List<ListenableFuture<?>> futures = new ArrayList<ListenableFuture<?>>(); + try { + long starttime = System.currentTimeMillis(); + for (int i = 0; i < 10; i++) { + final ShellTool localtool = newTool(); + connect(localtool); + + futures.add(executor.submit(new Runnable() { + public void run() { + String bigstring = Strings.repeat("abcdefghij", 1000); // 10KB + String out = execScript(localtool, ImmutableList.of("sleep 2", "export MYPROP="+bigstring, "echo val is $MYPROP")); + assertTrue(out.contains("val is "+bigstring), "out="+out); + }})); + } + Futures.allAsList(futures).get(); + long runtime = System.currentTimeMillis() - starttime; + + long OVERHEAD = 20*1000; + assertTrue(runtime < 2000+OVERHEAD, "runtime="+runtime); + + } finally { + executor.shutdownNow(); + } + } + + @Test(groups = {"Integration"}) + public void testExecChainOfCommands() throws Exception { + String out = execCommands("MYPROP=abc", "echo val is $MYPROP"); + + assertEquals(out, "val is abc\n"); + } + + @Test(groups = {"Integration"}) + public void testExecReturningNonZeroExitCode() throws Exception { + int exitcode = tool.execCommands(MutableMap.<String,Object>of(), ImmutableList.of("exit 123")); + assertEquals(exitcode, 123); + } + + @Test(groups = {"Integration"}) + public void testExecReturningZeroExitCode() throws Exception { + int exitcode = tool.execCommands(MutableMap.<String,Object>of(), ImmutableList.of("date")); + assertEquals(exitcode, 0); + } + + @Test(groups = {"Integration"}) + public void testExecCommandWithEnvVariables() throws Exception { + String out = execCommands(ImmutableList.of("echo val is $MYPROP2"), ImmutableMap.of("MYPROP2", "myval")); + + assertEquals(out, "val is myval\n"); + } + + @Test(groups = {"Integration"}) + public void testExecBigCommand() throws Exception { + String bigstring = Strings.repeat("abcdefghij", 1000); // 10KB + String out = execCommands("echo "+bigstring); + + assertEquals(out, bigstring+"\n", "actualSize="+out.length()+"; expectedSize="+bigstring.length()); + } + + @Test(groups = {"Integration"}) + public void testExecBigConcurrentCommand() throws Exception { + runExecBigConcurrentCommand(10, 0L); + } + + // TODO Fails I believe due to synchronization model in SshjTool of calling connect/disconnect. + // Even with a retry-count of 4, it still fails because some commands are calling disconnect + // while another concurrently executing command expects to be still connected. + @Test(groups = {"Integration", "WIP"}) + public void testExecBigConcurrentCommandWithStaggeredStart() throws Exception { + // This test is to vary the concurrency of concurrent actions + runExecBigConcurrentCommand(50, 100L); + } + + protected void runExecBigConcurrentCommand(int numCommands, long staggeredDelayBeforeStart) throws Exception { + ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool()); + List<ListenableFuture<?>> futures = new ArrayList<ListenableFuture<?>>(); + try { + for (int i = 0; i < numCommands; i++) { + long delay = (long) (Math.random() * staggeredDelayBeforeStart); + if (i > 0) Time.sleep(delay); + + futures.add(executor.submit(new Runnable() { + public void run() { + String bigstring = Strings.repeat("abcdefghij", 1000); // 10KB + String out = execCommands("echo "+bigstring); + assertEquals(out, bigstring+"\n", "actualSize="+out.length()+"; expectedSize="+bigstring.length()); + }})); + } + Futures.allAsList(futures).get(); + } finally { + executor.shutdownNow(); + } + } + + // fails if terminal enabled + @Test(groups = {"Integration"}) + @Deprecated // tests deprecated code + public void testExecScriptCapturesStderr() throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + String nonExistantCmd = "acmdthatdoesnotexist"; + tool.execScript(ImmutableMap.of("out", out, "err", err), ImmutableList.of(nonExistantCmd)); + assertTrue(new String(err.toByteArray()).contains(nonExistantCmd+": command not found"), "out="+out+"; err="+err); + } + + // fails if terminal enabled + @Test(groups = {"Integration"}) + @Deprecated // tests deprecated code + public void testExecCapturesStderr() throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + String nonExistantCmd = "acmdthatdoesnotexist"; + tool.execCommands(ImmutableMap.of("out", out, "err", err), ImmutableList.of(nonExistantCmd)); + String errMsg = new String(err.toByteArray()); + assertTrue(errMsg.contains(nonExistantCmd+": command not found\n"), "errMsg="+errMsg+"; out="+out+"; err="+err); + + } + + @Test(groups = {"Integration"}) + public void testScriptHeader() { + final ShellTool localtool = newTool(); + String out = execScript(MutableMap.of("scriptHeader", "#!/bin/bash -e\necho hello world\n"), + localtool, Arrays.asList("echo goodbye world"), null); + assertTrue(out.contains("goodbye world"), "no goodbye in output: "+out); + assertTrue(out.contains("hello world"), "no hello in output: "+out); + } + + @Test(groups = {"Integration"}) + public void testStdErr() { + final ShellTool localtool = newTool(); + Map<String,Object> props = new LinkedHashMap<String, Object>(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + props.put("out", out); + props.put("err", err); + int exitcode = localtool.execScript(props, Arrays.asList("echo hello err > /dev/stderr"), null); + assertFalse(out.toString().contains("hello err"), "hello found where it shouldn't have been, in stdout: "+out); + assertTrue(err.toString().contains("hello err"), "no hello in stderr: "+err); + assertEquals(0, exitcode); + } + + @Test(groups = {"Integration"}) + public void testRunAsRoot() { + final ShellTool localtool = newTool(); + Map<String,Object> props = new LinkedHashMap<String, Object>(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + props.put("out", out); + props.put("err", err); + props.put(SshTool.PROP_RUN_AS_ROOT.getName(), true); + int exitcode = localtool.execScript(props, Arrays.asList("whoami"), null); + assertTrue(out.toString().contains("root"), "not running as root; whoami is: "+out+" (err is '"+err+"')"); + assertEquals(0, exitcode); + } + + @Test(groups = {"Integration"}) + public void testExecScriptEchosExecute() throws Exception { + String out = execScript("date"); + assertTrue(out.toString().contains("Executed"), "Executed did not display: "+out); + } + + @Test(groups = {"Integration"}) + public void testExecScriptEchosDontExecuteWhenToldNoExtraOutput() throws Exception { + final ShellTool localtool = newTool(); + Map<String,Object> props = new LinkedHashMap<String, Object>(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + props.put("out", out); + props.put("err", err); + props.put(SshTool.PROP_NO_EXTRA_OUTPUT.getName(), true); + int exitcode = localtool.execScript(props, Arrays.asList("echo hello world"), null); + assertFalse(out.toString().contains("Executed"), "Executed should not have displayed: "+out); + assertEquals(out.toString().trim(), "hello world"); + assertEquals(0, exitcode); + } + + protected String execCommands(String... cmds) { + return execCommands(Arrays.asList(cmds)); + } + + protected String execCommands(List<String> cmds) { + return execCommands(cmds, ImmutableMap.<String,Object>of()); + } + + protected String execCommands(List<String> cmds, Map<String,?> env) { + return execCommands(null, cmds, env); + } + + protected String execCommands(ConfigBag config, List<String> cmds, Map<String,?> env) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + MutableMap<String,Object> flags = MutableMap.<String,Object>of("out", out); + if (config!=null) flags.add(config.getAllConfig()); + tool.execCommands(flags, cmds, env); + return new String(out.toByteArray()); + } + + protected String execScript(String... cmds) { + return execScript(tool, Arrays.asList(cmds)); + } + + protected String execScript(ShellTool t, List<String> cmds) { + return execScript(ImmutableMap.<String,Object>of(), t, cmds, ImmutableMap.<String,Object>of()); + } + + protected String execScript(List<String> cmds) { + return execScript(cmds, ImmutableMap.<String,Object>of()); + } + + protected String execScript(List<String> cmds, Map<String,?> env) { + return execScript(MutableMap.<String,Object>of(), tool, cmds, env); + } + + protected String execScript(Map<String, ?> props, ShellTool tool, List<String> cmds, Map<String,?> env) { + Map<String, Object> props2 = new LinkedHashMap<String, Object>(props); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + props2.put("out", out); + int exitcode = tool.execScript(props2, cmds, env); + String outstr = new String(out.toByteArray()); + assertEquals(exitcode, 0, outstr); + return outstr; + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4c0e5fd/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/SshToolAbstractIntegrationTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/SshToolAbstractIntegrationTest.java b/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/SshToolAbstractIntegrationTest.java new file mode 100644 index 0000000..309d4fb --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/SshToolAbstractIntegrationTest.java @@ -0,0 +1,304 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.brooklyn.core.util.internal.ssh; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.util.Arrays; +import java.util.Calendar; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.core.util.internal.ssh.ShellTool; +import org.apache.brooklyn.core.util.internal.ssh.SshException; +import org.apache.brooklyn.core.util.internal.ssh.SshTool; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import brooklyn.util.collections.MutableMap; +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.os.Os; +import brooklyn.util.text.Identifiers; +import brooklyn.util.text.Strings; + +import com.google.common.base.Charsets; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.io.Files; + +/** + * Test the operation of the {@link SshTool} utility class; to be extended to test concrete implementations. + * + * Requires keys set up, e.g. running: + * + * <pre> + * cd ~/.ssh + * ssh-keygen + * id_rsa_with_passphrase + * mypassphrase + * mypassphrase + * </pre> + * + */ +public abstract class SshToolAbstractIntegrationTest extends ShellToolAbstractTest { + + private static final Logger log = LoggerFactory.getLogger(SshToolAbstractIntegrationTest.class); + + // 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 String remoteFilePath; + + protected SshTool tool() { return (SshTool)tool; } + + protected abstract SshTool newUnregisteredTool(Map<String,?> flags); + + @Override + protected SshTool newTool() { + return newTool(ImmutableMap.of("host", "localhost", "privateKeyFile", "~/.ssh/id_rsa")); + } + + @Override + protected SshTool newTool(Map<String,?> flags) { + return (SshTool) super.newTool(flags); + } + + + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + super.setUp(); + remoteFilePath = "/tmp/ssh-test-remote-"+Identifiers.makeRandomId(8); + filesCreated.add(remoteFilePath); + } + + protected void assertRemoteFileContents(String remotePath, String expectedContents) { + String catout = execCommands("cat "+remotePath); + assertEquals(catout, expectedContents); + } + + /** + * @param remotePath + * @param expectedPermissions Of the form, for example, "-rw-r--r--" + */ + protected void assertRemoteFilePermissions(String remotePath, String expectedPermissions) { + String lsout = execCommands("ls -l "+remotePath); + assertTrue(lsout.contains(expectedPermissions), lsout); + } + + protected void assertRemoteFileLastModifiedIsNow(String remotePath) { + // Check default last-modified time is `now`. + // Be lenient in assertion, in case unlucky that clock ticked over to next hour/minute as test was running. + // TODO Code could be greatly improved, but low priority! + // Output format: + // -rw-r--r-- 1 aled wheel 18 Apr 24 15:03 /tmp/ssh-test-remote-CvFN9zQA + // [0] [1] [2] [3] [4] [5] [6] [7] [8] + + String lsout = execCommands("ls -l "+remotePath); + + String[] lsparts = lsout.split("\\s+"); + int day = Integer.parseInt(lsparts[6]); + int hour = Integer.parseInt(lsparts[7].split(":")[0]); + int minute = Integer.parseInt(lsparts[7].split(":")[1]); + + Calendar expected = Calendar.getInstance(); + int expectedDay = expected.get(Calendar.DAY_OF_MONTH); + int expectedHour = expected.get(Calendar.HOUR_OF_DAY); + int expectedMinute = expected.get(Calendar.MINUTE); + + assertEquals(day, expectedDay, "ls="+lsout+"; lsparts="+Arrays.toString(lsparts)+"; expected="+expected+"; expectedDay="+expectedDay+"; day="+day+"; zone="+expected.getTimeZone()); + assertTrue(Math.abs(hour - expectedHour) <= 1, "ls="+lsout+"; lsparts="+Arrays.toString(lsparts)+"; expected="+expected+"; expectedHour="+expectedHour+"; hour="+hour+"; zone="+expected.getTimeZone()); + assertTrue(Math.abs(minute - expectedMinute) <= 1, "ls="+lsout+"; lsparts="+Arrays.toString(lsparts)+"; expected="+expected+"; expectedMinute="+expectedMinute+"; minute="+minute+"; zone="+expected.getTimeZone()); + } + + @Test(groups = {"Integration"}) + 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); + assertRemoteFilePermissions(remoteFilePath, "-rw-r--r--"); + + // TODO would like to also assert lastModified time, but on jenkins the jvm locale + // and the OS locale are different (i.e. different timezones) so the file time-stamp + // is several hours out. + //assertRemoteFileLastModifiedIsNow(remoteFilePath); + } + + @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); + + assertRemoteFilePermissions(remoteFilePath, "-rwxr-xr--"); + } + + @Test(groups = {"Integration"}) + public void testCopyToServerWithLastModifiedDate() throws Exception { + long lastModificationTime = 1234567; + 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}'"]) + //varies depending on timezone + assertTrue(lsout.contains("Jan 15 1970") || lsout.contains("Jan 14 1970") || lsout.contains("Jan 16 1970"), lsout); + //assertLastModified(lsout, lastModifiedDate) + } + + @Test(groups = {"Integration"}) + public void testCopyFileToServerWithPermissions() throws Exception { + String contents = "echo hello world!\n"; + Files.write(contents, new File(localFilePath), Charsets.UTF_8); + tool().copyToServer(ImmutableMap.of("permissions", "0754"), new File(localFilePath), remoteFilePath); + + assertRemoteFileContents(remoteFilePath, contents); + + String lsout = execCommands("ls -l "+remoteFilePath); + assertTrue(lsout.contains("-rwxr-xr--"), lsout); + } + + @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)); + } + + // TODO No config options in sshj or scp for auto-creating the parent directories + @Test(enabled=false, groups = {"Integration"}) + 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().copyToServer(MutableMap.<String,Object>of(), contents.getBytes(), remoteFileInDirPath); + + assertRemoteFileContents(remoteFileInDirPath, contents); + } + + + @Test(groups = {"Integration"}) + public void testAllocatePty() { + final ShellTool localtool = newTool(MutableMap.of("host", "localhost", SshTool.PROP_ALLOCATE_PTY.getName(), true)); + Map<String,Object> props = new LinkedHashMap<String, Object>(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + props.put("out", out); + props.put("err", err); + int exitcode = localtool.execScript(props, Arrays.asList("echo hello err > /dev/stderr"), null); + assertTrue(out.toString().contains("hello err"), "no hello in output: "+out+" (err is '"+err+"')"); + assertFalse(err.toString().contains("hello err"), "hello found in stderr: "+err); + assertEquals(0, exitcode); + } + + // 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 = newTool(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()); + 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 = newTool(ImmutableMap.<String,Object>builder() + .put(SshTool.PROP_HOST.getName(), "localhost") + .put(SshTool.PROP_PRIVATE_KEY_FILE.getName(), SSH_KEY_WITH_PASSPHRASE) + .build()); + localtool2.connect(); + fail(); + } catch (Exception e) { + SshException se = Exceptions.getFirstThrowableOfType(e, SshException.class); + if (se == null) throw e; + } + } + + @Test(groups = {"Integration"}) + public void testConnectWithInvalidUserThrowsException() throws Exception { + final ShellTool localtool = newTool(ImmutableMap.of("user", "wronguser", "host", "localhost", "privateKeyFile", "~/.ssh/id_rsa")); + tools.add(localtool); + try { + connect(localtool); + fail(); + } catch (SshException e) { + if (!e.toString().contains("failed to connect")) throw e; + } + } + + @Test(groups = {"Integration"}) + public void testOutputAsExpected() throws Exception { + final String CONTENTS = "hello world\n" + + "bye bye\n"; + execCommands("cat > "+Os.mergePaths(Os.tmp(), "test1")+" << X\n" + + CONTENTS + + "X\n"); + String read = execCommands("echo START_FOO", "cat "+Os.mergePaths(Os.tmp(), "test1"), "echo END_FOO"); + log.debug("read back data written, as:\n"+read); + String contents = Strings.getFragmentBetween(read, "START_FOO", "END_FOO"); + Assert.assertEquals(CONTENTS.trim(), contents.trim()); + } + + @Test(groups = {"Integration"}) + public void testScriptDirPropertiesIsRespected() { + // For explanation of (some of) the magic behind this command, see http://stackoverflow.com/a/229606/68898 + final String command = "if [[ \"$0\" == \"/var/tmp/\"* ]]; then true; else false; fi"; + + SshTool sshTool = newTool(ImmutableMap.<String, Object>builder() + .put(SshTool.PROP_HOST.getName(), "localhost") + .build()); + int rc = sshTool.execScript(ImmutableMap.<String, Object>builder() + .put(SshTool.PROP_SCRIPT_DIR.getName(), "/var/tmp") + .build(), ImmutableList.of(command)); + assertEquals(rc, 0); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4c0e5fd/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/SshToolAbstractPerformanceTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/SshToolAbstractPerformanceTest.java b/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/SshToolAbstractPerformanceTest.java new file mode 100644 index 0000000..1730816 --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/SshToolAbstractPerformanceTest.java @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.brooklyn.core.util.internal.ssh; + +import java.io.ByteArrayOutputStream; +import java.lang.management.ManagementFactory; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import javax.management.MBeanServer; +import javax.management.ObjectName; + +import org.apache.brooklyn.core.util.internal.ssh.SshTool; +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.util.collections.MutableMap; +import brooklyn.util.text.Identifiers; +import brooklyn.util.time.Time; + +import com.google.common.base.Stopwatch; +import com.google.common.collect.ImmutableList; + +/** + * Test the performance of different variants of invoking the sshj tool. + * + * Intended for human-invocation and inspection, to see which parts are most expensive. + */ +public abstract class SshToolAbstractPerformanceTest { + + private static final Logger LOG = LoggerFactory.getLogger(SshToolAbstractPerformanceTest.class); + + private SshTool tool; + + protected abstract SshTool newSshTool(Map<String,?> flags); + + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + } + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { + if (tool != null) tool.disconnect(); + } + + @Test(groups = {"Integration"}) + public void testConsecutiveConnectAndDisconnect() throws Exception { + Runnable task = new Runnable() { + public void run() { + tool = newSshTool(MutableMap.of("host", "localhost")); + tool.connect(); + tool.disconnect(); + } + }; + runMany(task, "connect-disconnect", 10); + } + + @Test(groups = {"Integration"}) + public void testConsecutiveSmallCommands() throws Exception { + runExecManyCommands(ImmutableList.of("true"), false, "small-cmd", 10); + } + + @Test(groups = {"Integration"}) + public void testConsecutiveSmallCommandsWithStdouterr() throws Exception { + runExecManyCommands(ImmutableList.of("true"), true, "small-cmd-with-stdout", 10); + } + + @Test(groups = {"Integration"}) + public void testConsecutiveBigStdoutCommands() throws Exception { + runExecManyCommands(ImmutableList.of("head -c 100000 /dev/urandom"), true, "big-stdout", 10); + } + + @Test(groups = {"Integration"}) + public void testConsecutiveBigStdinCommands() throws Exception { + String bigstr = Identifiers.makeRandomId(100000); + runExecManyCommands(ImmutableList.of("echo "+bigstr+" | wc -c"), true, "big-stdin", 10); + } + + private void runExecManyCommands(final List<String> cmds, final boolean captureOutAndErr, String context, int iterations) throws Exception { + Runnable task = new Runnable() { + @Override public void run() { + execScript(cmds, captureOutAndErr); + }}; + runMany(task, context, iterations); + } + + private void runMany(Runnable task, String context, int iterations) throws Exception { + MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer(); + ObjectName osMBeanName = ObjectName.getInstance(ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME); + long preCpuTime = (Long) mbeanServer.getAttribute(osMBeanName, "ProcessCpuTime"); + Stopwatch stopwatch = Stopwatch.createStarted(); + + for (int i = 0; i < iterations; i++) { + task.run(); + + long postCpuTime = (Long) mbeanServer.getAttribute(osMBeanName, "ProcessCpuTime"); + long elapsedTime = stopwatch.elapsed(TimeUnit.MILLISECONDS); + double fractionCpu = (elapsedTime > 0) ? ((double)postCpuTime-preCpuTime) / TimeUnit.MILLISECONDS.toNanos(elapsedTime) : -1; + LOG.info("Executing {}; completed {}; took {}; fraction cpu {}", new Object[] {context, (i+1), Time.makeTimeStringRounded(elapsedTime), fractionCpu}); + } + } + + private int execScript(List<String> cmds, boolean captureOutandErr) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + MutableMap<String,?> flags = (captureOutandErr) ? MutableMap.of("out", out, "err", err) : MutableMap.<String,Object>of(); + + tool = newSshTool(MutableMap.of("host", "localhost")); + tool.connect(); + int result = tool.execScript(flags, cmds); + tool.disconnect(); + + int outlen = out.toByteArray().length; + int errlen = out.toByteArray().length; + if (LOG.isTraceEnabled()) LOG.trace("Executed: result={}; stdout={}; stderr={}", new Object[] {result, outlen, errlen}); + return result; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4c0e5fd/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/cli/SshCliToolIntegrationTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/cli/SshCliToolIntegrationTest.java b/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/cli/SshCliToolIntegrationTest.java new file mode 100644 index 0000000..f8efa87 --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/cli/SshCliToolIntegrationTest.java @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.brooklyn.core.util.internal.ssh.cli; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import java.io.ByteArrayOutputStream; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.brooklyn.core.util.internal.ssh.SshException; +import org.apache.brooklyn.core.util.internal.ssh.SshTool; +import org.apache.brooklyn.core.util.internal.ssh.SshToolAbstractIntegrationTest; +import org.apache.brooklyn.core.util.internal.ssh.cli.SshCliTool; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.Assert; +import org.testng.annotations.Test; + +import brooklyn.util.collections.MutableMap; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +/** + * Test the operation of the {@link SshJschTool} utility class. + */ +public class SshCliToolIntegrationTest extends SshToolAbstractIntegrationTest { + + private static final Logger log = LoggerFactory.getLogger(SshCliToolIntegrationTest.class); + + protected SshTool newUnregisteredTool(Map<String,?> flags) { + return new SshCliTool(flags); + } + + @Test(groups = {"Integration"}) + public void testFlags() throws Exception { + final SshTool localtool = newTool(ImmutableMap.of("sshFlags", "-vvv -tt", "host", "localhost")); + tools.add(localtool); + try { + localtool.connect(); + Map<String,Object> props = new LinkedHashMap<String, Object>(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + props.put("out", out); + props.put("err", err); + int exitcode = localtool.execScript(props, Arrays.asList("echo hello err > /dev/stderr"), null); + Assert.assertEquals(0, exitcode, "exitCode="+exitcode+", but expected 0"); + log.debug("OUT from ssh -vvv command is: "+out); + log.debug("ERR from ssh -vvv command is: "+err); + assertFalse(err.toString().contains("hello err"), "hello found where it shouldn't have been, in stderr (should have been tty merged to stdout): "+err); + assertTrue(out.toString().contains("hello err"), "no hello in stdout: "+err); + // look for word 'ssh' to confirm we got verbose output + assertTrue(err.toString().toLowerCase().contains("ssh"), "no mention of ssh in stderr: "+err); + } catch (SshException e) { + if (!e.toString().contains("failed to connect")) throw e; + } + } + + // Need to have at least one test method here (rather than just inherited) for eclipse to recognize it + @Test(enabled = false) + public void testDummy() throws Exception { + } + + // TODO When running mvn on the command line (for Aled), this test hangs when prompting for a password (but works in the IDE!) + // Doing .connect() isn't enough; need to cause ssh or scp to be invoked + @Test(enabled=false, groups = {"Integration"}) + public void testConnectWithInvalidUserThrowsException() throws Exception { + final SshTool localtool = newTool(ImmutableMap.of("user", "wronguser", "host", "localhost", "privateKeyFile", "~/.ssh/id_rsa")); + tools.add(localtool); + try { + localtool.connect(); + int result = localtool.execScript(ImmutableMap.<String,Object>of(), ImmutableList.of("date")); + fail("exitCode="+result+", but expected exception"); + } catch (SshException e) { + if (!e.toString().contains("failed to connect")) throw e; + } + } + + // 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 testCopyToServerWithLastModifiedDate() throws Exception { + super.testCopyToServerWithLastModifiedDate(); + } + + @Test(groups = {"Integration"}) + public void testExecReturningNonZeroExitCode() throws Exception { + int exitcode = tool.execCommands(MutableMap.<String,Object>of(), ImmutableList.of("exit 123")); + assertEquals(exitcode, 123); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4c0e5fd/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/cli/SshCliToolPerformanceTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/cli/SshCliToolPerformanceTest.java b/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/cli/SshCliToolPerformanceTest.java new file mode 100644 index 0000000..fd01180 --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/cli/SshCliToolPerformanceTest.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.brooklyn.core.util.internal.ssh.cli; + +import java.util.Map; + +import org.apache.brooklyn.core.util.internal.ssh.SshTool; +import org.apache.brooklyn.core.util.internal.ssh.SshToolAbstractPerformanceTest; +import org.apache.brooklyn.core.util.internal.ssh.cli.SshCliTool; +import org.testng.annotations.Test; + +/** + * Test the performance of different variants of invoking the sshj tool. + * + * Intended for human-invocation and inspection, to see which parts are most expensive. + */ +public class SshCliToolPerformanceTest extends SshToolAbstractPerformanceTest { + + @Override + protected SshTool newSshTool(Map<String,?> flags) { + return new SshCliTool(flags); + } + + // Need to have at least one test method here (rather than just inherited) for eclipse to recognize it + @Test(enabled = false) + public void testDummy() throws Exception { + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4c0e5fd/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/process/ProcessToolIntegrationTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/process/ProcessToolIntegrationTest.java b/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/process/ProcessToolIntegrationTest.java new file mode 100644 index 0000000..3dd558d --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/process/ProcessToolIntegrationTest.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.brooklyn.core.util.internal.ssh.process; + +import static org.testng.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Map; + +import org.apache.brooklyn.core.util.config.ConfigBag; +import org.apache.brooklyn.core.util.internal.ssh.ShellToolAbstractTest; +import org.apache.brooklyn.core.util.internal.ssh.process.ProcessTool; +import org.testng.Assert; +import org.testng.annotations.Test; + +/** + * Test the operation of the {@link ProcessTool} utility class. + */ +public class ProcessToolIntegrationTest extends ShellToolAbstractTest { + + @Override + protected ProcessTool newUnregisteredTool(Map<String,?> flags) { + return new ProcessTool(flags); + } + + // ones here included as *non*-integration tests. must run on windows and linux. + // (also includes integration tests from parent) + + @Test(groups="UNIX") + public void testPortableCommand() throws Exception { + String out = execScript("echo hello world"); + assertTrue(out.contains("hello world"), "out="+out); + } + + @Test(groups="Integration") + public void testLoginShell() { + // this detection scheme only works for commands; can't test whether it works for scripts without + // requiring stuff in bash_profile / profile / etc, which gets hard to make portable; + // it is nearly the same code path on the impl so this is probably enough + + final String LOGIN_SHELL_CHECK = "shopt -q login_shell && echo 'yes, login shell' || echo 'no, not login shell'"; + ConfigBag config = ConfigBag.newInstance().configure(ProcessTool.PROP_NO_EXTRA_OUTPUT, true); + String out; + + out = execCommands(config, Arrays.asList(LOGIN_SHELL_CHECK), null); + Assert.assertEquals(out.trim(), "no, not login shell", "out = "+out); + + config.configure(ProcessTool.PROP_LOGIN_SHELL, true); + out = execCommands(config, Arrays.asList(LOGIN_SHELL_CHECK), null); + Assert.assertEquals(out.trim(), "yes, login shell", "out = "+out); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4c0e5fd/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/process/ProcessToolStaticsTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/process/ProcessToolStaticsTest.java b/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/process/ProcessToolStaticsTest.java new file mode 100644 index 0000000..eacd761 --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/process/ProcessToolStaticsTest.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.brooklyn.core.util.internal.ssh.process; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.util.Arrays; +import java.util.List; + +import org.apache.brooklyn.core.util.internal.ssh.process.ProcessTool; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import brooklyn.util.collections.MutableMap; +import brooklyn.util.os.Os; + +public class ProcessToolStaticsTest { + + ByteArrayOutputStream out; + ByteArrayOutputStream err; + + @BeforeMethod(alwaysRun=true) + public void clear() { + out = new ByteArrayOutputStream(); + err = new ByteArrayOutputStream(); + } + + private List<String> getTestCommand() { + if(Os.isMicrosoftWindows()) { + return Arrays.asList("cmd", "/c", "echo", "hello", "world"); + } else { + return Arrays.asList("echo", "hello", "world"); + } + } + + @Test + public void testRunsWithStdout() throws Exception { + int code = ProcessTool.execSingleProcess(getTestCommand(), null, (File)null, out, err, this); + Assert.assertEquals(err.toString().trim(), ""); + Assert.assertEquals(out.toString().trim(), "hello world"); + Assert.assertEquals(code, 0); + } + + @Test(groups="Integration") // *nix only + public void testRunsWithBashEnvVarAndStderr() throws Exception { + int code = ProcessTool.execSingleProcess(Arrays.asList("/bin/bash", "-c", "echo hello $NAME | tee /dev/stderr"), + MutableMap.of("NAME", "BOB"), (File)null, out, err, this); + Assert.assertEquals(err.toString().trim(), "hello BOB", "err is: "+err); + Assert.assertEquals(out.toString().trim(), "hello BOB", "out is: "+out); + Assert.assertEquals(code, 0); + } + + @Test(groups="Integration") // *nix only + public void testRunsManyCommandsWithBashEnvVarAndStderr() throws Exception { + int code = ProcessTool.execProcesses(Arrays.asList("echo hello $NAME", "export NAME=JOHN", "echo goodbye $NAME | tee /dev/stderr"), + MutableMap.of("NAME", "BOB"), (File)null, out, err, " ; ", false, this); + Assert.assertEquals(err.toString().trim(), "goodbye JOHN", "err is: "+err); + Assert.assertEquals(out.toString().trim(), "hello BOB\ngoodbye JOHN", "out is: "+out); + Assert.assertEquals(code, 0); + } + + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4c0e5fd/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/sshj/SshjToolAsyncStubIntegrationTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/sshj/SshjToolAsyncStubIntegrationTest.java b/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/sshj/SshjToolAsyncStubIntegrationTest.java new file mode 100644 index 0000000..e3b4b7d --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/sshj/SshjToolAsyncStubIntegrationTest.java @@ -0,0 +1,178 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.brooklyn.core.util.internal.ssh.sshj; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; + +import org.apache.brooklyn.core.internal.BrooklynFeatureEnablement; +import org.apache.brooklyn.core.util.internal.ssh.SshAbstractTool.SshAction; +import org.apache.brooklyn.core.util.internal.ssh.sshj.SshjTool; +import org.apache.brooklyn.core.util.internal.ssh.sshj.SshjTool.ShellAction; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.time.Duration; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; + +/** + * Tests for async-exec with {@link SshjTool}, where it stubs out the actual ssh commands + * to return a controlled sequence of responses. + */ +public class SshjToolAsyncStubIntegrationTest { + + static class InjectedResult { + Predicate<SshjTool.ShellAction> expected; + Function<SshjTool.ShellAction, Integer> result; + + InjectedResult(Predicate<SshjTool.ShellAction> expected, Function<SshjTool.ShellAction, Integer> result) { + this.expected = expected; + this.result = result; + } + } + + private SshjTool tool; + private List<InjectedResult> sequence; + int counter = 0; + private boolean origFeatureEnablement; + + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + origFeatureEnablement = BrooklynFeatureEnablement.enable(BrooklynFeatureEnablement.FEATURE_SSH_ASYNC_EXEC); + sequence = Lists.newArrayList(); + counter = 0; + + tool = new SshjTool(ImmutableMap.<String,Object>of("host", "localhost")) { + @SuppressWarnings("unchecked") + protected <T, C extends SshAction<T>> T acquire(C action, int sshTries, Duration sshTriesTimeout) { + if (action instanceof SshjTool.ShellAction) { + SshjTool.ShellAction shellAction = (SshjTool.ShellAction) action; + InjectedResult injectedResult = sequence.get(counter); + assertTrue(injectedResult.expected.apply(shellAction), "counter="+counter+"; cmds="+shellAction.commands); + counter++; + return (T) injectedResult.result.apply(shellAction); + } + return super.acquire(action, sshTries, sshTriesTimeout); + } + }; + } + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { + try { + if (tool != null) tool.disconnect(); + } finally { + BrooklynFeatureEnablement.setEnablement(BrooklynFeatureEnablement.FEATURE_SSH_ASYNC_EXEC, origFeatureEnablement); + } + } + + private Predicate<SshjTool.ShellAction> containsCmd(final String cmd) { + return new Predicate<SshjTool.ShellAction>() { + @Override public boolean apply(ShellAction input) { + return input != null && input.commands.toString().contains(cmd); + } + }; + } + + private Function<SshjTool.ShellAction, Integer> returning(final int result, final String stdout, final String stderr) { + return new Function<SshjTool.ShellAction, Integer>() { + @Override public Integer apply(ShellAction input) { + try { + if (stdout != null && input.out != null) input.out.write(stdout.getBytes()); + if (stderr != null && input.err != null) input.err.write(stderr.getBytes()); + } catch (IOException e) { + throw Exceptions.propagate(e); + } + return result; + } + }; + } + + @Test(groups="Integration") + public void testPolls() throws Exception { + sequence = ImmutableList.of( + new InjectedResult(containsCmd("nohup"), returning(0, "", "")), + new InjectedResult(containsCmd("# Long poll"), returning(0, "mystringToStdout", "mystringToStderr"))); + + runTest(0, "mystringToStdout", "mystringToStderr"); + assertEquals(counter, sequence.size()); + } + + @Test(groups="Integration") + public void testPollsAndReturnsNonZeroExitCode() throws Exception { + sequence = ImmutableList.of( + new InjectedResult(containsCmd("nohup"), returning(0, "", "")), + new InjectedResult(containsCmd("# Long poll"), returning(123, "mystringToStdout", "mystringToStderr")), + new InjectedResult(containsCmd("# Retrieve status"), returning(0, "123", ""))); + + runTest(123, "mystringToStdout", "mystringToStderr"); + assertEquals(counter, sequence.size()); + } + + @Test(groups="Integration") + public void testPollsRepeatedly() throws Exception { + sequence = ImmutableList.of( + new InjectedResult(containsCmd("nohup"), returning(0, "", "")), + new InjectedResult(containsCmd("# Long poll"), returning(125, "mystringToStdout", "mystringToStderr")), + new InjectedResult(containsCmd("# Retrieve status"), returning(0, "", "")), + new InjectedResult(containsCmd("# Long poll"), returning(125, "mystringToStdout2", "mystringToStderr2")), + new InjectedResult(containsCmd("# Retrieve status"), returning(0, "", "")), + new InjectedResult(containsCmd("# Long poll"), returning(-1, "mystringToStdout3", "mystringToStderr3")), + new InjectedResult(containsCmd("# Long poll"), returning(125, "mystringToStdout4", "mystringToStderr4")), + new InjectedResult(containsCmd("# Retrieve status"), returning(0, "", "")), + new InjectedResult(containsCmd("# Long poll"), returning(0, "mystringToStdout5", "mystringToStderr5"))); + + runTest(0, + "mystringToStdout"+"mystringToStdout2"+"mystringToStdout3"+"mystringToStdout4"+"mystringToStdout5", + "mystringToStderr"+"mystringToStderr2"+"mystringToStderr3"+"mystringToStderr4"+"mystringToStderr5"); + assertEquals(counter, sequence.size()); + } + + protected void runTest(int expectedExit, String expectedStdout, String expectedStderr) throws Exception { + List<String> cmds = ImmutableList.of("abc"); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + int exitCode = tool.execScript( + ImmutableMap.of( + "out", out, + "err", err, + SshjTool.PROP_EXEC_ASYNC.getName(), true, + SshjTool.PROP_NO_EXTRA_OUTPUT.getName(), true, + SshjTool.PROP_EXEC_ASYNC_POLLING_TIMEOUT.getName(), Duration.ONE_MILLISECOND), + cmds, + ImmutableMap.<String,String>of()); + String outStr = new String(out.toByteArray()); + String errStr = new String(err.toByteArray()); + + assertEquals(exitCode, expectedExit); + assertEquals(outStr.trim(), expectedStdout); + assertEquals(errStr.trim(), expectedStderr); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4c0e5fd/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/sshj/SshjToolIntegrationTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/sshj/SshjToolIntegrationTest.java b/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/sshj/SshjToolIntegrationTest.java new file mode 100644 index 0000000..bf1aafb --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/sshj/SshjToolIntegrationTest.java @@ -0,0 +1,314 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.brooklyn.core.util.internal.ssh.sshj; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import net.schmizz.sshj.connection.channel.direct.Session; + +import org.apache.brooklyn.core.internal.BrooklynFeatureEnablement; +import org.apache.brooklyn.core.util.internal.ssh.SshException; +import org.apache.brooklyn.core.util.internal.ssh.SshTool; +import org.apache.brooklyn.core.util.internal.ssh.SshToolAbstractIntegrationTest; +import org.apache.brooklyn.core.util.internal.ssh.sshj.SshjTool; +import org.testng.annotations.Test; + +import brooklyn.test.Asserts; +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.exceptions.RuntimeTimeoutException; +import brooklyn.util.os.Os; +import brooklyn.util.time.Duration; + +import com.google.common.base.Stopwatch; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +/** + * Test the operation of the {@link SshJschTool} utility class. + */ +public class SshjToolIntegrationTest extends SshToolAbstractIntegrationTest { + + @Override + protected SshTool newUnregisteredTool(Map<String,?> flags) { + return new SshjTool(flags); + } + + // TODO requires vt100 terminal emulation to work? + @Test(enabled = false, groups = {"Integration"}) + public void testExecShellWithCommandTakingStdin() throws Exception { + // Uses `tee` to redirect stdin to the given file; cntr-d (i.e. char 4) stops tee with exit code 0 + String content = "blah blah"; + String out = execShellDirectWithTerminalEmulation("tee "+remoteFilePath, content, ""+(char)4, "echo file contents: `cat "+remoteFilePath+"`"); + + assertTrue(out.contains("file contents: blah blah"), "out="+out); + } + + @Test(groups = {"Integration"}) + public void testGivesUpAfterMaxRetries() throws Exception { + final AtomicInteger callCount = new AtomicInteger(); + + final SshTool localtool = new SshjTool(ImmutableMap.of("sshTries", 3, "host", "localhost", "privateKeyFile", "~/.ssh/id_rsa")) { + protected SshAction<Session> newSessionAction() { + callCount.incrementAndGet(); + throw new RuntimeException("Simulating ssh execution failure"); + } + }; + + tools.add(localtool); + try { + localtool.execScript(ImmutableMap.<String,Object>of(), ImmutableList.of("true")); + fail(); + } catch (SshException e) { + if (!e.toString().contains("out of retries")) throw e; + assertEquals(callCount.get(), 3); + } + } + + @Test(groups = {"Integration"}) + public void testReturnsOnSuccessWhenRetrying() throws Exception { + final AtomicInteger callCount = new AtomicInteger(); + final int successOnAttempt = 2; + final SshTool localtool = new SshjTool(ImmutableMap.of("sshTries", 3, "host", "localhost", "privateKeyFile", "~/.ssh/id_rsa")) { + protected SshAction<Session> newSessionAction() { + callCount.incrementAndGet(); + if (callCount.incrementAndGet() >= successOnAttempt) { + return super.newSessionAction(); + } else { + throw new RuntimeException("Simulating ssh execution failure"); + } + } + }; + + tools.add(localtool); + localtool.execScript(ImmutableMap.<String,Object>of(), ImmutableList.of("true")); + assertEquals(callCount.get(), successOnAttempt); + } + + @Test(groups = {"Integration"}) + public void testGivesUpAfterMaxTime() throws Exception { + final AtomicInteger callCount = new AtomicInteger(); + final SshTool localtool = new SshjTool(ImmutableMap.of("sshTriesTimeout", 1000, "host", "localhost", "privateKeyFile", "~/.ssh/id_rsa")) { + protected SshAction<Session> newSessionAction() { + callCount.incrementAndGet(); + try { + Thread.sleep(600); + } catch (InterruptedException e) { + throw Exceptions.propagate(e); + } + throw new RuntimeException("Simulating ssh execution failure"); + } + }; + + tools.add(localtool); + try { + localtool.execScript(ImmutableMap.<String,Object>of(), ImmutableList.of("true")); + fail(); + } catch (RuntimeTimeoutException e) { + if (!e.toString().contains("out of time")) throw e; + assertEquals(callCount.get(), 2); + } + } + + @Test(groups = {"Integration"}) + public void testUsesCustomLocalTempDir() throws Exception { + class SshjToolForTest extends SshjTool { + public SshjToolForTest(Map<String, ?> map) { + super(map); + } + public File getLocalTempDir() { + return localTempDir; + } + }; + + final SshjToolForTest localtool = new SshjToolForTest(ImmutableMap.<String, Object>of("host", "localhost")); + assertNotNull(localtool.getLocalTempDir()); + assertEquals(localtool.getLocalTempDir(), new File(Os.tidyPath(SshjTool.PROP_LOCAL_TEMP_DIR.getDefaultValue()))); + + String customTempDir = Os.tmp(); + final SshjToolForTest localtool2 = new SshjToolForTest(ImmutableMap.of( + "host", "localhost", + SshjTool.PROP_LOCAL_TEMP_DIR.getName(), customTempDir)); + assertEquals(localtool2.getLocalTempDir(), new File(customTempDir)); + + String customRelativeTempDir = "~/tmp"; + final SshjToolForTest localtool3 = new SshjToolForTest(ImmutableMap.of( + "host", "localhost", + SshjTool.PROP_LOCAL_TEMP_DIR.getName(), customRelativeTempDir)); + assertEquals(localtool3.getLocalTempDir(), new File(Os.tidyPath(customRelativeTempDir))); + } + + @Test(groups = {"Integration"}) + public void testAsyncExecStdoutAndStderr() throws Exception { + boolean origFeatureEnablement = BrooklynFeatureEnablement.enable(BrooklynFeatureEnablement.FEATURE_SSH_ASYNC_EXEC); + try { + // Include a sleep, to ensure that the contents retrieved in first poll and subsequent polls are appended + List<String> cmds = ImmutableList.of( + "echo mystringToStdout", + "echo mystringToStderr 1>&2", + "sleep 5", + "echo mystringPostSleepToStdout", + "echo mystringPostSleepToStderr 1>&2"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + int exitCode = tool.execScript( + ImmutableMap.of( + "out", out, + "err", err, + SshjTool.PROP_EXEC_ASYNC.getName(), true, + SshjTool.PROP_NO_EXTRA_OUTPUT.getName(), true, + SshjTool.PROP_EXEC_ASYNC_POLLING_TIMEOUT.getName(), Duration.ONE_SECOND), + cmds, + ImmutableMap.<String,String>of()); + String outStr = new String(out.toByteArray()); + String errStr = new String(err.toByteArray()); + + assertEquals(exitCode, 0); + assertEquals(outStr.trim(), "mystringToStdout\nmystringPostSleepToStdout"); + assertEquals(errStr.trim(), "mystringToStderr\nmystringPostSleepToStderr"); + } finally { + BrooklynFeatureEnablement.setEnablement(BrooklynFeatureEnablement.FEATURE_SSH_ASYNC_EXEC, origFeatureEnablement); + } + } + + @Test(groups = {"Integration"}) + public void testAsyncExecReturnsExitCode() throws Exception { + boolean origFeatureEnablement = BrooklynFeatureEnablement.enable(BrooklynFeatureEnablement.FEATURE_SSH_ASYNC_EXEC); + try { + int exitCode = tool.execScript( + ImmutableMap.of(SshjTool.PROP_EXEC_ASYNC.getName(), true), + ImmutableList.of("exit 123"), + ImmutableMap.<String,String>of()); + assertEquals(exitCode, 123); + } finally { + BrooklynFeatureEnablement.setEnablement(BrooklynFeatureEnablement.FEATURE_SSH_ASYNC_EXEC, origFeatureEnablement); + } + } + + @Test(groups = {"Integration"}) + public void testAsyncExecTimesOut() throws Exception { + Stopwatch stopwatch = Stopwatch.createStarted(); + boolean origFeatureEnablement = BrooklynFeatureEnablement.enable(BrooklynFeatureEnablement.FEATURE_SSH_ASYNC_EXEC); + try { + tool.execScript( + ImmutableMap.of(SshjTool.PROP_EXEC_ASYNC.getName(), true, SshjTool.PROP_EXEC_TIMEOUT.getName(), Duration.millis(1)), + ImmutableList.of("sleep 60"), + ImmutableMap.<String,String>of()); + fail(); + } catch (Exception e) { + TimeoutException te = Exceptions.getFirstThrowableOfType(e, TimeoutException.class); + if (te == null) throw e; + } finally { + BrooklynFeatureEnablement.setEnablement(BrooklynFeatureEnablement.FEATURE_SSH_ASYNC_EXEC, origFeatureEnablement); + } + + long seconds = stopwatch.elapsed(TimeUnit.SECONDS); + assertTrue(seconds < 30, "exec took "+seconds+" seconds"); + } + + @Test(groups = {"Integration"}) + public void testAsyncExecAbortsIfProcessFails() throws Exception { + final AtomicReference<Throwable> error = new AtomicReference<Throwable>(); + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + try { + Stopwatch stopwatch = Stopwatch.createStarted(); + int exitStatus = tool.execScript( + ImmutableMap.of(SshjTool.PROP_EXEC_ASYNC.getName(), true, SshjTool.PROP_EXEC_TIMEOUT.getName(), Duration.millis(1)), + ImmutableList.of("sleep 63"), + ImmutableMap.<String,String>of()); + + assertEquals(exitStatus, 143 /* 128 + Signal number (SIGTERM) */); + + long seconds = stopwatch.elapsed(TimeUnit.SECONDS); + assertTrue(seconds < 30, "exec took "+seconds+" seconds"); + } catch (Throwable t) { + error.set(t); + } + }}); + + boolean origFeatureEnablement = BrooklynFeatureEnablement.enable(BrooklynFeatureEnablement.FEATURE_SSH_ASYNC_EXEC); + try { + thread.start(); + + Asserts.succeedsEventually(new Runnable() { + @Override + public void run() { + int exitStatus = tool.execCommands(ImmutableMap.<String,Object>of(), ImmutableList.of("ps aux| grep \"sleep 63\" | grep -v grep")); + assertEquals(exitStatus, 0); + }}); + + tool.execCommands(ImmutableMap.<String,Object>of(), ImmutableList.of("ps aux| grep \"sleep 63\" | grep -v grep | awk '{print($2)}' | xargs kill")); + + thread.join(30*1000); + assertFalse(thread.isAlive()); + if (error.get() != null) { + throw Exceptions.propagate(error.get()); + } + } finally { + thread.interrupt(); + BrooklynFeatureEnablement.setEnablement(BrooklynFeatureEnablement.FEATURE_SSH_ASYNC_EXEC, origFeatureEnablement); + } + } + + + protected String execShellDirect(List<String> cmds) { + return execShellDirect(cmds, ImmutableMap.<String,Object>of()); + } + + protected String execShellDirect(List<String> cmds, Map<String,?> env) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int exitcode = ((SshjTool)tool).execShellDirect(ImmutableMap.of("out", out), cmds, env); + String outstr = new String(out.toByteArray()); + assertEquals(exitcode, 0, outstr); + return outstr; + } + + private String execShellDirectWithTerminalEmulation(String... cmds) { + return execShellDirectWithTerminalEmulation(Arrays.asList(cmds)); + } + + private String execShellDirectWithTerminalEmulation(List<String> cmds) { + return execShellDirectWithTerminalEmulation(cmds, ImmutableMap.<String,Object>of()); + } + + private String execShellDirectWithTerminalEmulation(List<String> cmds, Map<String,?> env) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int exitcode = ((SshjTool)tool).execShellDirect(ImmutableMap.of("allocatePTY", true, "out", out), cmds, env); + String outstr = new String(out.toByteArray()); + assertEquals(exitcode, 0, outstr); + return outstr; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4c0e5fd/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/sshj/SshjToolPerformanceTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/sshj/SshjToolPerformanceTest.java b/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/sshj/SshjToolPerformanceTest.java new file mode 100644 index 0000000..71ea4e6 --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/core/util/internal/ssh/sshj/SshjToolPerformanceTest.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.brooklyn.core.util.internal.ssh.sshj; + +import java.util.Map; + +import org.apache.brooklyn.core.util.internal.ssh.SshTool; +import org.apache.brooklyn.core.util.internal.ssh.SshToolAbstractPerformanceTest; +import org.apache.brooklyn.core.util.internal.ssh.sshj.SshjTool; +import org.testng.annotations.Test; + +/** + * Test the performance of different variants of invoking the sshj tool. + * + * Intended for human-invocation and inspection, to see which parts are most expensive. + */ +public class SshjToolPerformanceTest extends SshToolAbstractPerformanceTest { + + @Override + protected SshTool newSshTool(Map<String,?> flags) { + return new SshjTool(flags); + } + + // Need to have at least one test method here (rather than just inherited) for eclipse to recognize it + @Test(enabled = false) + public void testDummy() throws Exception { + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4c0e5fd/core/src/test/java/org/apache/brooklyn/core/util/mutex/WithMutexesTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/core/util/mutex/WithMutexesTest.java b/core/src/test/java/org/apache/brooklyn/core/util/mutex/WithMutexesTest.java new file mode 100644 index 0000000..c37ab30 --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/core/util/mutex/WithMutexesTest.java @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.brooklyn.core.util.mutex; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +import org.apache.brooklyn.core.util.mutex.MutexSupport; +import org.apache.brooklyn.core.util.mutex.SemaphoreWithOwners; +import org.apache.brooklyn.core.util.mutex.WithMutexes; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class WithMutexesTest { + + @Test + public void testOneAcquisitionAndRelease() throws InterruptedException { + MutexSupport m = new MutexSupport(); + Map<String, SemaphoreWithOwners> sems; + SemaphoreWithOwners s; + try { + m.acquireMutex("foo", "something foo"); + sems = m.getAllSemaphores(); + Assert.assertEquals(sems.size(), 1); + s = sems.get("foo"); + Assert.assertEquals(s.getDescription(), "something foo"); + Assert.assertEquals(s.getOwningThreads(), Arrays.asList(Thread.currentThread())); + Assert.assertEquals(s.getRequestingThreads(), Collections.emptyList()); + Assert.assertTrue(s.isInUse()); + Assert.assertTrue(s.isCallingThreadAnOwner()); + } finally { + m.releaseMutex("foo"); + } + Assert.assertFalse(s.isInUse()); + Assert.assertFalse(s.isCallingThreadAnOwner()); + Assert.assertEquals(s.getDescription(), "something foo"); + Assert.assertEquals(s.getOwningThreads(), Collections.emptyList()); + Assert.assertEquals(s.getRequestingThreads(), Collections.emptyList()); + + sems = m.getAllSemaphores(); + Assert.assertEquals(sems, Collections.emptyMap()); + } + + @Test(groups = "Integration") //just because it takes a wee while + public void testBlockingAcquisition() throws InterruptedException { + final MutexSupport m = new MutexSupport(); + m.acquireMutex("foo", "something foo"); + + Assert.assertFalse(m.tryAcquireMutex("foo", "something else")); + + Thread t = new Thread() { + public void run() { + try { + m.acquireMutex("foo", "thread 2 foo"); + } catch (InterruptedException e) { + e.printStackTrace(); + } + m.releaseMutex("foo"); + } + }; + t.start(); + + t.join(500); + Assert.assertTrue(t.isAlive()); + Assert.assertEquals(m.getSemaphore("foo").getRequestingThreads(), Arrays.asList(t)); + + m.releaseMutex("foo"); + + t.join(1000); + Assert.assertFalse(t.isAlive()); + + Assert.assertEquals(m.getAllSemaphores(), Collections.emptyMap()); + } + + + public static class SampleWithMutexesDelegatingMixin implements WithMutexes { + + /* other behaviour would typically go here... */ + + WithMutexes mutexSupport = 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); + } + } + + @Test + public void testDelegatingMixinPattern() throws InterruptedException { + WithMutexes m = new SampleWithMutexesDelegatingMixin(); + m.acquireMutex("foo", "sample"); + Assert.assertTrue(m.hasMutex("foo")); + Assert.assertFalse(m.hasMutex("bar")); + m.releaseMutex("foo"); + Assert.assertFalse(m.hasMutex("foo")); + } +}
