/* * 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.entity.software.base.lifecycle; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import org.apache.brooklyn.api.location.NoMachinesAvailableException; import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; import org.apache.brooklyn.core.mgmt.BrooklynTaskTags.WrappedStream; import org.apache.brooklyn.entity.software.base.lifecycle.NaiveScriptRunner; import org.apache.brooklyn.entity.software.base.lifecycle.ScriptHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.core.task.BasicExecutionContext; import org.apache.brooklyn.util.core.task.BasicExecutionManager; import org.apache.brooklyn.util.core.task.Tasks; import org.apache.brooklyn.util.repeat.Repeater; import org.apache.brooklyn.util.time.Duration; import com.google.common.base.Throwables; @Test public class NaiveScriptRunnerTest { private static final Logger log = LoggerFactory.getLogger(NaiveScriptRunnerTest.class); List<String> commands = new ArrayList<String>(); @BeforeMethod private void setup() { commands.clear(); } @SuppressWarnings("rawtypes") private NaiveScriptRunner newMockRunner(final int result) { return new NaiveScriptRunner() { @Override public int execute(List<String> script, String summaryForLogging) { return execute(new MutableMap(), script, summaryForLogging); } @Override public int execute(Map flags, List<String> script, String summaryForLogging) { commands.addAll(script); return result; } }; } @SuppressWarnings("rawtypes") public static NaiveScriptRunner newLocalhostRunner() { return new NaiveScriptRunner() { LocalhostMachineProvisioningLocation location = new LocalhostMachineProvisioningLocation(); @Override public int execute(List<String> script, String summaryForLogging) { return execute(new MutableMap(), script, summaryForLogging); } @SuppressWarnings("unchecked") @Override public int execute(Map flags, List<String> script, String summaryForLogging) { try { Map flags2 = MutableMap.of("logPrefix", "test"); flags2.putAll(flags); return location.obtain().execScript(flags2, summaryForLogging, script); } catch (NoMachinesAvailableException e) { throw Throwables.propagate(e); } } }; }; public void testHeadBodyFootAndResult() { ScriptHelper h = new ScriptHelper(newMockRunner(101), "mock"); int result = h.header. append("h1", "h2").body.append("b1", "b2").footer.append("f1", "f2"). execute(); Assert.assertEquals(result, 101); Assert.assertEquals(commands, Arrays.asList("h1", "h2", "b1", "b2", "f1", "f2"), "List wrong: "+commands); } public void testFailOnNonZero() { ScriptHelper h = new ScriptHelper(newMockRunner(106), "mock"); boolean succeededWhenShouldntHave = false; try { h.body.append("ignored"). failOnNonZeroResultCode(). // will happen execute(); succeededWhenShouldntHave = true; } catch (Exception e) { log.info("ScriptHelper non-zero causes return code: "+e); } if (succeededWhenShouldntHave) Assert.fail("succeeded when shouldn't have"); } public void testFailOnNonZeroDontFailIfZero() { int result = new ScriptHelper(newMockRunner(0), "mock").body.append("ignored"). failOnNonZeroResultCode(). // will happen execute(); Assert.assertEquals(result, 0); } @Test(groups = "Integration") public void testFailingCommandFailsEarly() { ScriptHelper script = new ScriptHelper(newLocalhostRunner(), "mock"). body.append("curl road://to/nowhere", "exit 11"). gatherOutput(); int result = script.execute(); // should get _1_ from curl failing, not 11 from us // TODO not sure why though! Assert.assertEquals(1, result); } // TODO a good way to indicate when downloads fail, as that is quite a common case // but i think we need quite a bit of scaffolding to detect that problem (without inspecting logs) ... @Test(groups = "Integration") public void testGatherOutputStdout() { ScriptHelper script = new ScriptHelper(newLocalhostRunner(), "mock"). body.append("echo `echo foo``echo bar`", "exit 8"). gatherOutput(); int result = script.execute(); Assert.assertEquals(8, result); if (!script.getResultStdout().contains("foobar")) Assert.fail("STDOUT does not contain expected text 'foobar'.\n"+script.getResultStdout()+ "\nSTDERR:\n"+script.getResultStderr()); } @Test(groups = "Integration") public void testGatherOutputStderr() { ScriptHelper script = new ScriptHelper(newLocalhostRunner(), "mock"). body.append("set -x", "curl road://to/nowhere || exit 11"). gatherOutput(); int result = script.execute(); Assert.assertEquals(11, result); if (!script.getResultStderr().contains("road")) Assert.fail("STDERR does not contain expected text 'road'.\n"+script.getResultStderr()+ "\nSTDOUT:\n"+script.getResultStdout()); } @Test(groups = "Integration") public void testGatherOutuputNotEnabled() { ScriptHelper script = new ScriptHelper(newLocalhostRunner(), "mock"). body.append("echo foo", "exit 11"); int result = script.execute(); Assert.assertEquals(11, result); boolean succeededWhenShouldNotHave = false; try { script.getResultStdout(); succeededWhenShouldNotHave = true; } catch (Exception e) { /* expected */ } if (succeededWhenShouldNotHave) Assert.fail("Should have failed"); } @Test(groups = "Integration") public void testStreamsInTask() { final ScriptHelper script = new ScriptHelper(newLocalhostRunner(), "mock"). body.append("echo `echo foo``echo bar`", "grep absent-text badfile_which_does_not_exist_blaahblahasdewq"). gatherOutput(); Assert.assertNull(script.peekTask()); Task<Integer> task = script.newTask(); Assert.assertTrue(BrooklynTaskTags.streams(task).size() >= 3, "Expected at least 3 streams: "+BrooklynTaskTags.streams(task)); Assert.assertFalse(Tasks.isQueuedOrSubmitted(task)); WrappedStream in = BrooklynTaskTags.stream(task, BrooklynTaskTags.STREAM_STDIN); Assert.assertNotNull(in); Assert.assertTrue(in.streamContents.get().contains("echo foo"), "Expected 'echo foo' but had: "+in.streamContents.get()); Assert.assertTrue(in.streamSize.get() > 0); Assert.assertNotNull(script.peekTask()); } @Test(groups = "Integration") public void testAutoQueueAndRuntimeStreamsInTask() { final ScriptHelper script = new ScriptHelper(newLocalhostRunner(), "mock"). body.append("echo `echo foo``echo bar`", "grep absent-text badfile_which_does_not_exist_blaahblahasdewq"). gatherOutput(); Task<Integer> submitter = Tasks.<Integer>builder().body(new Callable<Integer>() { public Integer call() { int result = script.execute(); return result; } }).build(); BasicExecutionManager em = new BasicExecutionManager("tests"); BasicExecutionContext ec = new BasicExecutionContext(em); try { Assert.assertNull(script.peekTask()); ec.submit(submitter); // soon there should be a task which is submitted Assert.assertTrue(Repeater.create("get script").every(Duration.millis(10)).limitTimeTo(Duration.FIVE_SECONDS).until(new Callable<Boolean>() { public Boolean call() { return (script.peekTask() != null) && Tasks.isQueuedOrSubmitted(script.peekTask()); } }).run()); Task<Integer> task = script.peekTask(); Assert.assertTrue(BrooklynTaskTags.streams(task).size() >= 3, "Expected at least 3 streams: "+BrooklynTaskTags.streams(task)); // stdin should be populated WrappedStream in = BrooklynTaskTags.stream(task, BrooklynTaskTags.STREAM_STDIN); Assert.assertNotNull(in); Assert.assertTrue(in.streamContents.get().contains("echo foo"), "Expected 'echo foo' but had: "+in.streamContents.get()); Assert.assertTrue(in.streamSize.get() > 0); // out and err should exist WrappedStream out = BrooklynTaskTags.stream(task, BrooklynTaskTags.STREAM_STDOUT); WrappedStream err = BrooklynTaskTags.stream(task, BrooklynTaskTags.STREAM_STDERR); Assert.assertNotNull(out); Assert.assertNotNull(err); // it should soon finish, with exit code Integer result = task.getUnchecked(Duration.TEN_SECONDS); Assert.assertNotNull(result); Assert.assertTrue(result > 0, "Expected non-zero exit code: "+result); // and should contain foobar in stdout if (!script.getResultStdout().contains("foobar")) Assert.fail("Script STDOUT does not contain expected text 'foobar'.\n"+script.getResultStdout()+ "\nSTDERR:\n"+script.getResultStderr()); if (!out.streamContents.get().contains("foobar")) Assert.fail("Task STDOUT does not contain expected text 'foobar'.\n"+out.streamContents.get()+ "\nSTDERR:\n"+script.getResultStderr()); // and "No such file or directory" in stderr if (!script.getResultStderr().contains("No such file or directory")) Assert.fail("Script STDERR does not contain expected text 'No such ...'.\n"+script.getResultStdout()+ "\nSTDERR:\n"+script.getResultStderr()); if (!err.streamContents.get().contains("No such file or directory")) Assert.fail("Task STDERR does not contain expected text 'No such...'.\n"+out.streamContents.get()+ "\nSTDERR:\n"+script.getResultStderr()); } finally { em.shutdownNow(); } } }