You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by al...@apache.org on 2016/07/19 19:05:00 UTC

[1/5] brooklyn-server git commit: Don’t call postInstallCommand if skipInstall=true

Repository: brooklyn-server
Updated Branches:
  refs/heads/master 8d8d5766d -> 284f110a8


Don\u2019t call postInstallCommand if skipInstall=true

Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/bc7e0afe
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/bc7e0afe
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/bc7e0afe

Branch: refs/heads/master
Commit: bc7e0afe7495036019f9d1897e3961f0da4d6153
Parents: a941963
Author: Aled Sage <al...@gmail.com>
Authored: Tue Jun 28 21:11:44 2016 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Tue Jun 28 21:11:44 2016 +0100

----------------------------------------------------------------------
 .../entity/software/base/AbstractSoftwareProcessDriver.java  | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/bc7e0afe/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessDriver.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessDriver.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessDriver.java
index f6449b0..aaa7cde 100644
--- a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessDriver.java
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessDriver.java
@@ -158,11 +158,11 @@ public abstract class AbstractSoftwareProcessDriver implements SoftwareProcessDr
                         waitForConfigKey(BrooklynConfigKeys.INSTALL_LATCH);
                         install();
                     }});
-                }
 
-                DynamicTasks.queue("post-install-command", new Runnable() { public void run() {
-                    runPostInstallCommand();
-                }});
+                    DynamicTasks.queue("post-install-command", new Runnable() { public void run() {
+                        runPostInstallCommand();
+                    }});
+                }
             }});
 
             DynamicTasks.queue("customize", new Runnable() { public void run() {


[4/5] brooklyn-server git commit: Adds VanillaWindowsProcessTest (non-live)

Posted by al...@apache.org.
Adds VanillaWindowsProcessTest (non-live)


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/d24e0de1
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/d24e0de1
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/d24e0de1

Branch: refs/heads/master
Commit: d24e0de157b0d666e3ad570a1ed7b1c5b13fba7c
Parents: 915ffa7
Author: Aled Sage <al...@gmail.com>
Authored: Tue Jun 28 21:40:51 2016 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Tue Jun 28 22:05:37 2016 +0100

----------------------------------------------------------------------
 software/base/pom.xml                           |   7 +
 .../base/VanillaWindowsProcessTest.java         | 161 +++++++++++++++++++
 .../core/internal/winrm/RecordingWinRmTool.java | 107 ++++++++++++
 3 files changed, 275 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/d24e0de1/software/base/pom.xml
----------------------------------------------------------------------
diff --git a/software/base/pom.xml b/software/base/pom.xml
index 04306fc..7e0d4fd 100644
--- a/software/base/pom.xml
+++ b/software/base/pom.xml
@@ -138,6 +138,13 @@
         </dependency>
         <dependency>
             <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-software-winrm</artifactId>
+            <version>${project.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
             <artifactId>brooklyn-locations-jclouds</artifactId>
             <version>${project.version}</version>
             <classifier>tests</classifier>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/d24e0de1/software/base/src/test/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcessTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcessTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcessTest.java
new file mode 100644
index 0000000..4523e11
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcessTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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;
+
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.util.List;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.location.MachineLocation;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.location.byon.FixedListMachineProvisioningLocation;
+import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
+import org.apache.brooklyn.util.core.internal.winrm.RecordingWinRmTool;
+import org.apache.brooklyn.util.core.internal.winrm.RecordingWinRmTool.ExecParams;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class VanillaWindowsProcessTest extends BrooklynAppUnitTestSupport {
+
+    @SuppressWarnings("unused")
+    private static final Logger LOG = LoggerFactory.getLogger(VanillaWindowsProcessTest.class);
+
+    private Location loc;
+    
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        loc = app.getManagementContext().getLocationManager().createLocation(LocationSpec.create(FixedListMachineProvisioningLocation.class)
+                .configure(FixedListMachineProvisioningLocation.MACHINE_SPECS, ImmutableList.<LocationSpec<? extends MachineLocation>>of(
+                        LocationSpec.create(WinRmMachineLocation.class)
+                                .configure("address", "1.2.3.4")
+                                .configure(WinRmMachineLocation.WINRM_TOOL_CLASS, RecordingWinRmTool.class.getName()))));
+        
+        RecordingWinRmTool.clear();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        RecordingWinRmTool.clear();
+    }
+    
+    @Test
+    public void testAllCmds() throws Exception {
+        app.createAndManageChild(EntitySpec.create(VanillaWindowsProcess.class)
+                .configure(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, true)
+                .configure(VanillaWindowsProcess.PRE_INSTALL_COMMAND, "preInstallCommand")
+                .configure(VanillaWindowsProcess.INSTALL_COMMAND, "installCommand")
+                .configure(VanillaWindowsProcess.POST_INSTALL_COMMAND, "postInstallCommand")
+                .configure(VanillaWindowsProcess.PRE_CUSTOMIZE_COMMAND, "preCustomizeCommand")
+                .configure(VanillaWindowsProcess.CUSTOMIZE_COMMAND, "customizeCommand")
+                .configure(VanillaWindowsProcess.POST_CUSTOMIZE_COMMAND, "postCustomizeCommand")
+                .configure(VanillaWindowsProcess.PRE_LAUNCH_COMMAND, "preLaunchCommand")
+                .configure(VanillaWindowsProcess.LAUNCH_COMMAND, "launchCommand")
+                .configure(VanillaWindowsProcess.POST_LAUNCH_COMMAND, "postLaunchCommand")
+                .configure(VanillaWindowsProcess.CHECK_RUNNING_COMMAND, "checkRunningCommand")
+                .configure(VanillaWindowsProcess.STOP_COMMAND, "stopCommand"));
+        app.start(ImmutableList.of(loc));
+
+        assertExecsContain(RecordingWinRmTool.getExecs(), ImmutableList.of(
+                "preInstallCommand", "installCommand", "postInstallCommand", 
+                "preCustomizeCommand", "customizeCommand", "postCustomizeCommand", 
+                "preLaunchCommand", "launchCommand", "postLaunchCommand", 
+                "checkRunningCommand"));
+        
+        app.stop();
+
+        assertExecContains(RecordingWinRmTool.getLastExec(), "stopCommand");
+    }
+    
+    @Test
+    public void testAllPowershell() throws Exception {
+        app.createAndManageChild(EntitySpec.create(VanillaWindowsProcess.class)
+                .configure(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, true)
+                .configure(VanillaWindowsProcess.PRE_INSTALL_POWERSHELL_COMMAND, "preInstallCommand")
+                .configure(VanillaWindowsProcess.INSTALL_POWERSHELL_COMMAND, "installCommand")
+                .configure(VanillaWindowsProcess.POST_INSTALL_POWERSHELL_COMMAND, "postInstallCommand")
+                .configure(VanillaWindowsProcess.PRE_CUSTOMIZE_POWERSHELL_COMMAND, "preCustomizeCommand")
+                .configure(VanillaWindowsProcess.CUSTOMIZE_POWERSHELL_COMMAND, "customizeCommand")
+                .configure(VanillaWindowsProcess.POST_CUSTOMIZE_POWERSHELL_COMMAND, "postCustomizeCommand")
+                .configure(VanillaWindowsProcess.PRE_LAUNCH_POWERSHELL_COMMAND, "preLaunchCommand")
+                .configure(VanillaWindowsProcess.LAUNCH_POWERSHELL_COMMAND, "launchCommand")
+                .configure(VanillaWindowsProcess.POST_LAUNCH_POWERSHELL_COMMAND, "postLaunchCommand")
+                .configure(VanillaWindowsProcess.CHECK_RUNNING_POWERSHELL_COMMAND, "checkRunningCommand")
+                .configure(VanillaWindowsProcess.STOP_POWERSHELL_COMMAND, "stopCommand"));
+        app.start(ImmutableList.of(loc));
+
+        assertExecsContain(RecordingWinRmTool.getExecs(), ImmutableList.of(
+                "preInstallCommand", "installCommand", "postInstallCommand", 
+                "preCustomizeCommand", "customizeCommand", "postCustomizeCommand", 
+                "preLaunchCommand", "launchCommand", "postLaunchCommand", 
+                "checkRunningCommand"));
+        
+        app.stop();
+
+        assertExecContains(RecordingWinRmTool.getLastExec(), "stopCommand");
+    }
+    
+    protected void assertExecsContain(List<? extends ExecParams> actuals, List<String> expectedCmds) {
+        String errMsg = "actuals="+actuals+"; expected="+expectedCmds;
+        assertTrue(actuals.size() >= expectedCmds.size(), "actualSize="+actuals.size()+"; expectedSize="+expectedCmds.size()+"; "+errMsg);
+        for (int i = 0; i < expectedCmds.size(); i++) {
+            assertExecContains(actuals.get(i), expectedCmds.get(i), errMsg);
+        }
+    }
+
+    protected void assertExecContains(ExecParams actual, String expectedCmdRegex) {
+        assertExecContains(actual, expectedCmdRegex, null);
+    }
+    
+    protected void assertExecContains(ExecParams actual, String expectedCmdRegex, String errMsg) {
+        for (String cmd : actual.commands) {
+            if (cmd.matches(expectedCmdRegex)) {
+                return;
+            }
+        }
+        fail(expectedCmdRegex + " not matched by any commands in " + actual+(errMsg != null ? "; "+errMsg : ""));
+    }
+
+    protected void assertExecsNotContains(List<? extends ExecParams> actuals, List<String> expectedNotCmdRegexs) {
+        for (ExecParams actual : actuals) {
+            assertExecContains(actual, expectedNotCmdRegexs);
+        }
+    }
+    
+    protected void assertExecContains(ExecParams actual, List<String> expectedNotCmdRegexs) {
+        for (String cmdRegex : expectedNotCmdRegexs) {
+            for (String subActual : actual.commands) {
+                if (subActual.matches(cmdRegex)) {
+                    fail("Exec should not contain " + cmdRegex + ", but matched by " + actual);
+                }
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/d24e0de1/software/winrm/src/test/java/org/apache/brooklyn/util/core/internal/winrm/RecordingWinRmTool.java
----------------------------------------------------------------------
diff --git a/software/winrm/src/test/java/org/apache/brooklyn/util/core/internal/winrm/RecordingWinRmTool.java b/software/winrm/src/test/java/org/apache/brooklyn/util/core/internal/winrm/RecordingWinRmTool.java
new file mode 100644
index 0000000..14fcd76
--- /dev/null
+++ b/software/winrm/src/test/java/org/apache/brooklyn/util/core/internal/winrm/RecordingWinRmTool.java
@@ -0,0 +1,107 @@
+/*
+ * 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.util.core.internal.winrm;
+
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.brooklyn.util.stream.Streams;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+/**
+ * For stubbing out the {@link WinRmTool}, so that no real winrm commands are executed.
+ * Records all the commands that are executed, so that assertions can subsequently be made.
+ * 
+ * By default, all commands return exit code 0, and no stdout/stderr.
+ */
+public class RecordingWinRmTool implements WinRmTool {
+
+    public enum ExecType {
+        COMMAND,
+        POWER_SHELL,
+        COPY_TO_SERVER;
+    }
+    
+    public static class ExecParams {
+        public final ExecType type;
+        public final List<String> commands;
+        
+        public ExecParams(ExecType type, List<String> commands) {
+            this.type = type;
+            this.commands = commands;
+        }
+        
+        @Override
+        public String toString() {
+            return Objects.toStringHelper(this)
+                    .add("type", type)
+                    .add("commands", commands)
+                    .toString();
+        }
+    }
+
+    
+    public static List<ExecParams> execs = Lists.newCopyOnWriteArrayList();
+    public static List<Map<?,?>> constructorProps = Lists.newCopyOnWriteArrayList();
+    
+    public static void clear() {
+        execs.clear();
+        constructorProps.clear();
+    }
+    
+    public static List<ExecParams> getExecs() {
+        return ImmutableList.copyOf(execs);
+    }
+    
+    public static ExecParams getLastExec() {
+        return execs.get(execs.size()-1);
+    }
+    
+    public RecordingWinRmTool(Map<?,?> props) {
+        constructorProps.add(props);
+    }
+    
+    @Override
+    public WinRmToolResponse executeCommand(List<String> commands) {
+        execs.add(new ExecParams(ExecType.COMMAND, commands));
+        return new WinRmToolResponse("", "", 0);
+    }
+
+    @Override
+    public WinRmToolResponse executePs(List<String> commands) {
+        execs.add(new ExecParams(ExecType.POWER_SHELL, commands));
+        return new WinRmToolResponse("", "", 0);
+    }
+
+    @Override
+    public WinRmToolResponse copyToServer(InputStream source, String destination) {
+        execs.add(new ExecParams(ExecType.COPY_TO_SERVER, ImmutableList.of(new String(Streams.readFully(source)))));
+        return new WinRmToolResponse("", "", 0);
+    }
+
+    @Override
+    @Deprecated
+    public WinRmToolResponse executeScript(List<String> commands) {
+        throw new UnsupportedOperationException();
+    }
+}


[3/5] brooklyn-server git commit: Adds EffectorUtilsTest

Posted by al...@apache.org.
Adds EffectorUtilsTest

Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/915ffa7f
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/915ffa7f
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/915ffa7f

Branch: refs/heads/master
Commit: 915ffa7fe957d80720f6f3392a7ecc49b7b68027
Parents: 13ce4cb
Author: Aled Sage <al...@gmail.com>
Authored: Tue Jun 28 21:15:04 2016 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Tue Jun 28 21:15:04 2016 +0100

----------------------------------------------------------------------
 .../core/mgmt/internal/EffectorUtilsTest.java   | 119 +++++++++++++++++++
 1 file changed, 119 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/915ffa7f/core/src/test/java/org/apache/brooklyn/core/mgmt/internal/EffectorUtilsTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/mgmt/internal/EffectorUtilsTest.java b/core/src/test/java/org/apache/brooklyn/core/mgmt/internal/EffectorUtilsTest.java
new file mode 100644
index 0000000..449e9ed
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/mgmt/internal/EffectorUtilsTest.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.mgmt.internal;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.core.effector.EffectorBody;
+import org.apache.brooklyn.core.effector.Effectors;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class EffectorUtilsTest {
+
+    private Effector<Void> effector = Effectors.effector(Void.class, "myEffector")
+            .parameter(byte.class, "byteParam")
+            .parameter(char.class, "charParam")
+            .parameter(short.class, "shortParam")
+            .parameter(int.class, "intParam")
+            .parameter(long.class, "longParam")
+            .parameter(float.class, "floatParam")
+            .parameter(double.class, "doubleParam")
+            .parameter(boolean.class, "boolParam")
+            .parameter(String.class, "stringParam")
+            .impl(new EffectorBody<Void>() {
+                    @Override public Void call(ConfigBag parameters) {
+                        return null;
+                    }})
+            .build();
+
+    private Map<Object, Object> argsMap = ImmutableMap.builder()
+            .put("byteParam", (byte)1)
+            .put("charParam", (char)'2')
+            .put("shortParam", (short)3)
+            .put("intParam", (int)4)
+            .put("longParam", (long)5)
+            .put("floatParam", (float)6.0)
+            .put("doubleParam", (double)7.0)
+            .put("boolParam", true)
+            .put("stringParam", "mystring")
+            .build();
+
+    private Map<Object, Object> argsMapRequiringCoercion = ImmutableMap.builder()
+            .put("byteParam", "1")
+            .put("charParam", "2")
+            .put("shortParam", "3")
+            .put("intParam", "4")
+            .put("longParam", "5")
+            .put("floatParam", "6.0")
+            .put("doubleParam", "7.0")
+            .put("boolParam", "true")
+            .put("stringParam", "mystring")
+            .build();
+
+    private Object[] argsArray = new Object[] {(byte)1, (char)'2', (short)3, (int)4, (long)5, (float)6.0, (double)7.0, true, "mystring"};
+
+    private Object[] expectedArgs = argsArray;
+
+    @Test
+    public void testPrepareArgsFromMap() throws Exception {
+        assertEquals(EffectorUtils.prepareArgsForEffector(effector, argsMap), expectedArgs);
+        assertEquals(EffectorUtils.prepareArgsForEffector(effector, argsMapRequiringCoercion), expectedArgs);
+    }
+    
+    @Test
+    public void testPrepareArgsFromArray() throws Exception {
+        assertEquals(EffectorUtils.prepareArgsForEffector(effector, argsArray), expectedArgs);
+    }
+    
+    @Test(expectedExceptions=IllegalArgumentException.class, expectedExceptionsMessageRegExp=".*Invalid arguments.*missing argument.*stringParam.*")
+    public void testPrepareArgsFromMapThrowsIfMissing() throws Exception {
+        MutableMap<Object, Object> mapMissingArg = MutableMap.builder()
+                .putAll(argsMap)
+                .remove("stringParam")
+                .build();
+        EffectorUtils.prepareArgsForEffector(effector, mapMissingArg);
+    }
+    
+    @Test(expectedExceptions=IllegalArgumentException.class, expectedExceptionsMessageRegExp=".*Invalid arguments.*count mismatch.*")
+    public void testPrepareArgsFromArrayThrowsIfMissing() throws Exception {
+        Object[] arrayMissingArg = Arrays.asList(argsArray).subList(0, argsArray.length - 1).toArray();
+        EffectorUtils.prepareArgsForEffector(effector, arrayMissingArg);
+    }
+
+    @Test
+    public void testFindEffector() {
+        Maybe<Effector<?>> found = EffectorUtils.findEffector(ImmutableList.of(effector), "myEffector");
+        assertEquals(found.get(), effector);
+        
+        Maybe<Effector<?>> notFound = EffectorUtils.findEffector(ImmutableList.of(effector), "wrongEffectorName");
+        assertFalse(notFound.isPresent(), notFound.toString());
+    }
+}


[5/5] brooklyn-server git commit: This closes #223

Posted by al...@apache.org.
This closes #223


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/284f110a
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/284f110a
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/284f110a

Branch: refs/heads/master
Commit: 284f110a8571a8c96ef73687d2c6baa287ea91ea
Parents: 8d8d576 d24e0de
Author: Aled Sage <al...@gmail.com>
Authored: Tue Jul 19 20:04:22 2016 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Tue Jul 19 20:04:22 2016 +0100

----------------------------------------------------------------------
 .../core/mgmt/internal/EffectorUtilsTest.java   | 119 +++++++
 .../core/internal/ssh/RecordingSshTool.java     | 136 ++++++--
 software/base/pom.xml                           |   7 +
 .../base/AbstractSoftwareProcessDriver.java     |   8 +-
 .../base/VanillaSoftwareProcessTest.java        | 322 +++++++++++++++++++
 .../base/VanillaWindowsProcessTest.java         | 161 ++++++++++
 .../core/internal/winrm/RecordingWinRmTool.java | 107 ++++++
 7 files changed, 828 insertions(+), 32 deletions(-)
----------------------------------------------------------------------



[2/5] brooklyn-server git commit: Adds VanillaSoftwareProcessTest (as non-integration)

Posted by al...@apache.org.
Adds VanillaSoftwareProcessTest (as non-integration)

Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/13ce4cb8
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/13ce4cb8
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/13ce4cb8

Branch: refs/heads/master
Commit: 13ce4cb8c5767a7a99aa77a09e1f91a9ebdee7a7
Parents: bc7e0af
Author: Aled Sage <al...@gmail.com>
Authored: Tue Jun 28 21:14:37 2016 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Tue Jun 28 21:14:37 2016 +0100

----------------------------------------------------------------------
 .../core/internal/ssh/RecordingSshTool.java     | 136 ++++++--
 .../base/VanillaSoftwareProcessTest.java        | 322 +++++++++++++++++++
 2 files changed, 430 insertions(+), 28 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/13ce4cb8/core/src/test/java/org/apache/brooklyn/util/core/internal/ssh/RecordingSshTool.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/util/core/internal/ssh/RecordingSshTool.java b/core/src/test/java/org/apache/brooklyn/util/core/internal/ssh/RecordingSshTool.java
index f7faaca..2ac8acf 100644
--- a/core/src/test/java/org/apache/brooklyn/util/core/internal/ssh/RecordingSshTool.java
+++ b/core/src/test/java/org/apache/brooklyn/util/core/internal/ssh/RecordingSshTool.java
@@ -28,16 +28,53 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 
+import javax.annotation.Nullable;
+
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.text.Strings;
 
+import com.google.common.base.Objects;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 
-/** Mock tool */
+/**
+ * For stubbing out the {@link SshTool}, so that no real ssh/scp commands are executed.
+ * Records all the commands that are executed, so that assertions can subsequently be made.
+ * 
+ * By default, all commands return exit code 0, and no stdout/stderr.
+ * 
+ * This can be customised for particular commands using {@link #setCustomResponse(String, CustomResponseGenerator)}
+ * to specify the exit code, stdout and stderr of a matching command.
+ */
 public class RecordingSshTool implements SshTool {
     
+    public static class ExecParams {
+        public final Map<String, ?> props;
+        public final List<String> commands;
+        public final Map<String, ?> env;
+        
+        public ExecParams(Map<String, ?> props, List<String> commands, Map<String, ?> env) {
+            this.props = props;
+            this.commands = commands;
+            this.env = env;
+        }
+        
+        @Override
+        public String toString() {
+            return Objects.toStringHelper(this)
+                    .add("props", props)
+                    .add("commands", commands)
+                    .add("env", env).toString();
+        }
+    }
+
+    public interface CustomResponseGenerator {
+        public CustomResponse generate(ExecParams execParams);
+    }
+
     public static class CustomResponse {
         public final int exitCode;
         public final String stdout;
@@ -53,6 +90,14 @@ public class RecordingSshTool implements SshTool {
         public String toString() {
             return "CustomResponse["+exitCode+"; "+stdout+"; "+stderr+"]";
         }
+        
+        public CustomResponseGenerator toGenerator() {
+            return new CustomResponseGenerator() {
+                @Override public CustomResponse generate(ExecParams execParams) {
+                    return CustomResponse.this;
+                }
+            };
+        }
     }
     
     public static class ExecCmd {
@@ -74,9 +119,38 @@ public class RecordingSshTool implements SshTool {
         }
     }
     
+    public static class ExecCmdPredicates {
+        public static Predicate<ExecCmd> containsEnv(final Map<String, ?> expected) {
+            return new Predicate<ExecCmd>() {
+                @Override public boolean apply(@Nullable ExecCmd input) {
+                    if (input == null) return false;
+                    if (input.env == null) return false;
+                    for (Map.Entry<?,?> entry : expected.entrySet()) {
+                        Object key = entry.getKey();
+                        if (!(input.env.containsKey(key) && Objects.equal(entry.getValue(), input.env.get(key)))) {
+                            return false;
+                        }
+                    }
+                    return true;
+                }};
+        }
+        public static Predicate<ExecCmd> containsCmd(final String expected) {
+            return new Predicate<ExecCmd>() {
+                @Override public boolean apply(@Nullable ExecCmd input) {
+                    if (input == null) return false;
+                    for (String cmd : input.commands) {
+                        if (expected.equals(cmd)) {
+                            return true;
+                        }
+                    }
+                    return false;
+                }};
+        }
+    }
+    
     public static List<ExecCmd> execScriptCmds = Lists.newCopyOnWriteArrayList();
     public static List<Map<?,?>> constructorProps = Lists.newCopyOnWriteArrayList();
-    public static Map<String, CustomResponse> customResponses = Maps.newConcurrentMap();
+    public static Map<String, CustomResponseGenerator> customResponses = Maps.newConcurrentMap();
     
     private boolean connected;
     
@@ -86,10 +160,22 @@ public class RecordingSshTool implements SshTool {
         customResponses.clear();
     }
     
-    public static void setCustomResponse(String cmdRegex, CustomResponse response) {
+    public static void clearCmdHistory() {
+        execScriptCmds.clear();
+    }
+
+    public static void setCustomResponse(String cmdRegex, CustomResponseGenerator response) {
         customResponses.put(cmdRegex, checkNotNull(response, "response"));
     }
     
+    public static void setCustomResponse(String cmdRegex, CustomResponse response) {
+        customResponses.put(cmdRegex, checkNotNull(response, "response").toGenerator());
+    }
+    
+    public static List<ExecCmd> getExecCmds() {
+        return ImmutableList.copyOf(execScriptCmds);
+    }
+    
     public static ExecCmd getLastExecCmd() {
         return execScriptCmds.get(execScriptCmds.size()-1);
     }
@@ -110,34 +196,14 @@ public class RecordingSshTool implements SshTool {
         return connected;
     }
     @Override public int execScript(Map<String, ?> props, List<String> commands, Map<String, ?> env) {
-        execScriptCmds.add(new ExecCmd(props, "", commands, env));
-        for (String cmd : commands) {
-            for (Entry<String, CustomResponse> entry : customResponses.entrySet()) {
-                if (cmd.matches(entry.getKey())) {
-                    CustomResponse response = entry.getValue();
-                    writeCustomResponseStreams(props, response);
-                    return response.exitCode;
-                }
-            }
-        }
-        return 0;
+        return execInternal(props, commands, env);
+    }
+    @Override public int execCommands(Map<String, ?> props, List<String> commands, Map<String, ?> env) {
+        return execInternal(props, commands, env);
     }
     @Override public int execScript(Map<String, ?> props, List<String> commands) {
         return execScript(props, commands, ImmutableMap.<String,Object>of());
     }
-    @Override public int execCommands(Map<String, ?> props, List<String> commands, Map<String, ?> env) {
-        execScriptCmds.add(new ExecCmd(props, "", commands, env));
-        for (String cmd : commands) {
-            for (Entry<String, CustomResponse> entry : customResponses.entrySet()) {
-                if (cmd.matches(entry.getKey())) {
-                    CustomResponse response = entry.getValue();
-                    writeCustomResponseStreams(props, response);
-                    return response.exitCode;
-                }
-            }
-        }
-        return 0;
-    }
     @Override public int execCommands(Map<String, ?> props, List<String> commands) {
         return execCommands(props, commands, ImmutableMap.<String,Object>of());
     }
@@ -153,6 +219,20 @@ public class RecordingSshTool implements SshTool {
     @Override public int copyFromServer(Map<String, ?> props, String pathAndFileOnRemoteServer, File local) {
         return 0;
     }
+    protected int execInternal(Map<String, ?> props, List<String> commands, Map<String, ?> env) {
+        execScriptCmds.add(new ExecCmd(props, "", commands, env));
+        for (String cmd : commands) {
+            for (Entry<String, CustomResponseGenerator> entry : customResponses.entrySet()) {
+                if (cmd.matches(entry.getKey())) {
+                    CustomResponseGenerator responseGenerator = entry.getValue();
+                    CustomResponse response = responseGenerator.generate(new ExecParams(props, commands, env));
+                    writeCustomResponseStreams(props, response);
+                    return response.exitCode;
+                }
+            }
+        }
+        return 0;
+    }
     protected void writeCustomResponseStreams(Map<String, ?> props, CustomResponse response) {
         try {
             if (Strings.isNonBlank(response.stdout) && props.get(SshTool.PROP_OUT_STREAM.getName()) != null) {
@@ -165,4 +245,4 @@ public class RecordingSshTool implements SshTool {
             Exceptions.propagate(e);
         }
     }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/13ce4cb8/software/base/src/test/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessTest.java
new file mode 100644
index 0000000..3b30328
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/VanillaSoftwareProcessTest.java
@@ -0,0 +1,322 @@
+/*
+ * 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;
+
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.location.MachineLocation;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.location.byon.FixedListMachineProvisioningLocation;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.core.internal.ssh.RecordingSshTool;
+import org.apache.brooklyn.util.core.internal.ssh.RecordingSshTool.CustomResponse;
+import org.apache.brooklyn.util.core.internal.ssh.RecordingSshTool.ExecCmd;
+import org.apache.brooklyn.util.core.internal.ssh.RecordingSshTool.ExecCmdPredicates;
+import org.apache.brooklyn.util.core.internal.ssh.RecordingSshTool.ExecParams;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class VanillaSoftwareProcessTest extends BrooklynAppUnitTestSupport {
+
+    private Location loc;
+    
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        loc = app.getManagementContext().getLocationManager().createLocation(LocationSpec.create(FixedListMachineProvisioningLocation.class)
+                .configure(FixedListMachineProvisioningLocation.MACHINE_SPECS, ImmutableList.<LocationSpec<? extends MachineLocation>>of(
+                        LocationSpec.create(SshMachineLocation.class)
+                                .configure("address", "1.2.3.4")
+                                .configure(SshMachineLocation.SSH_TOOL_CLASS, RecordingSshTool.class.getName()))));
+        
+        RecordingSshTool.clear();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        RecordingSshTool.clear();
+    }
+    
+    @Test
+    public void testAllCmds() throws Exception {
+        app.createAndManageChild(EntitySpec.create(VanillaSoftwareProcess.class)
+                .configure(VanillaSoftwareProcess.PRE_INSTALL_COMMAND, "preInstallCommand")
+                .configure(VanillaSoftwareProcess.INSTALL_COMMAND, "installCommand")
+                .configure(VanillaSoftwareProcess.POST_INSTALL_COMMAND, "postInstallCommand")
+                .configure(VanillaSoftwareProcess.PRE_CUSTOMIZE_COMMAND, "preCustomizeCommand")
+                .configure(VanillaSoftwareProcess.CUSTOMIZE_COMMAND, "customizeCommand")
+                .configure(VanillaSoftwareProcess.POST_CUSTOMIZE_COMMAND, "postCustomizeCommand")
+                .configure(VanillaSoftwareProcess.PRE_LAUNCH_COMMAND, "preLaunchCommand")
+                .configure(VanillaSoftwareProcess.LAUNCH_COMMAND, "launchCommand")
+                .configure(VanillaSoftwareProcess.POST_LAUNCH_COMMAND, "postLaunchCommand")
+                .configure(VanillaSoftwareProcess.CHECK_RUNNING_COMMAND, "checkRunningCommand")
+                .configure(VanillaSoftwareProcess.STOP_COMMAND, "stopCommand"));
+        app.start(ImmutableList.of(loc));
+
+        assertExecsContain(RecordingSshTool.getExecCmds(), ImmutableList.of(
+                "preInstallCommand", "installCommand", "postInstallCommand", 
+                "preCustomizeCommand", "customizeCommand", "postCustomizeCommand", 
+                "preLaunchCommand", "launchCommand", "postLaunchCommand", 
+                "checkRunningCommand"));
+        
+        app.stop();
+
+        assertExecContains(RecordingSshTool.getLastExecCmd(), "stopCommand");
+    }
+
+    // See https://issues.apache.org/jira/browse/BROOKLYN-273
+    @Test
+    public void testRestartCmds() throws Exception {
+        VanillaSoftwareProcess entity = app.createAndManageChild(EntitySpec.create(VanillaSoftwareProcess.class)
+                .configure(VanillaSoftwareProcess.PRE_INSTALL_COMMAND, "preInstallCommand")
+                .configure(VanillaSoftwareProcess.INSTALL_COMMAND, "installCommand")
+                .configure(VanillaSoftwareProcess.POST_INSTALL_COMMAND, "postInstallCommand")
+                .configure(VanillaSoftwareProcess.PRE_CUSTOMIZE_COMMAND, "customizeCommand")
+                .configure(VanillaSoftwareProcess.CUSTOMIZE_COMMAND, "postCustomizeCommand")
+                .configure(VanillaSoftwareProcess.POST_CUSTOMIZE_COMMAND, "preCustomizeCommand")
+                .configure(VanillaSoftwareProcess.PRE_LAUNCH_COMMAND, "preLaunchCommand")
+                .configure(VanillaSoftwareProcess.LAUNCH_COMMAND, "launchCommand")
+                .configure(VanillaSoftwareProcess.POST_LAUNCH_COMMAND, "postLaunchCommand")
+                .configure(VanillaSoftwareProcess.CHECK_RUNNING_COMMAND, "checkRunningCommand")
+                .configure(VanillaSoftwareProcess.STOP_COMMAND, "stopCommand"));
+        app.start(ImmutableList.of(loc));
+
+        // Stop the entity, and clear out all record of previous execs
+        Entities.invokeEffector(app, entity, VanillaSoftwareProcess.STOP, ImmutableMap.of(
+                VanillaSoftwareProcess.StopSoftwareParameters.STOP_MACHINE_MODE.getName(), VanillaSoftwareProcess.StopSoftwareParameters.StopMode.NEVER,
+                VanillaSoftwareProcess.StopSoftwareParameters.STOP_PROCESS_MODE.getName(), VanillaSoftwareProcess.StopSoftwareParameters.StopMode.ALWAYS))
+                .get();
+
+        RecordingSshTool.clearCmdHistory();
+
+        // Invoke restart(), and check if all steps were executed
+        Entities.invokeEffector(app, entity, VanillaSoftwareProcess.RESTART, ImmutableMap.of(
+                VanillaSoftwareProcess.RestartSoftwareParameters.RESTART_CHILDREN.getName(), false,
+                VanillaSoftwareProcess.RestartSoftwareParameters.RESTART_MACHINE.getName(), VanillaSoftwareProcess.RestartSoftwareParameters.RestartMachineMode.FALSE))
+                .get();
+
+        assertExecsContain(RecordingSshTool.getExecCmds(), ImmutableList.of(
+                "checkRunningCommand", "stopCommand",  
+                "preLaunchCommand", "launchCommand", "postLaunchCommand", 
+                "checkRunningCommand"));
+    }
+
+    
+    @Test
+    public void testSkipInstallation() throws Exception {
+        app.createAndManageChild(EntitySpec.create(VanillaSoftwareProcess.class)
+                .configure(VanillaSoftwareProcess.SKIP_INSTALLATION, true)
+                .configure(VanillaSoftwareProcess.PRE_INSTALL_COMMAND, "preInstallCommand")
+                .configure(VanillaSoftwareProcess.INSTALL_COMMAND, "installCommand")
+                .configure(VanillaSoftwareProcess.POST_INSTALL_COMMAND, "postInstallCommand")
+                .configure(VanillaSoftwareProcess.PRE_CUSTOMIZE_COMMAND, "preCustomizeCommand")
+                .configure(VanillaSoftwareProcess.CUSTOMIZE_COMMAND, "customizeCommand")
+                .configure(VanillaSoftwareProcess.POST_CUSTOMIZE_COMMAND, "postCustomizeCommand")
+                .configure(VanillaSoftwareProcess.PRE_LAUNCH_COMMAND, "preLaunchCommand")
+                .configure(VanillaSoftwareProcess.LAUNCH_COMMAND, "launchCommand")
+                .configure(VanillaSoftwareProcess.POST_LAUNCH_COMMAND, "postLaunchCommand")
+                .configure(VanillaSoftwareProcess.CHECK_RUNNING_COMMAND, "checkRunningCommand")
+                .configure(VanillaSoftwareProcess.STOP_COMMAND, "stopCommand"));
+        app.start(ImmutableList.of(loc));
+
+        assertExecsContain(RecordingSshTool.getExecCmds(), ImmutableList.of(
+                "preCustomizeCommand", "customizeCommand", "postCustomizeCommand", 
+                "preLaunchCommand", "launchCommand", "postLaunchCommand", 
+                "checkRunningCommand"));
+        
+        assertExecsNotContains(RecordingSshTool.getExecCmds(), ImmutableList.of(
+                "preInstallCommand", "installCommand", "postInstallCommand"));
+    }
+
+    @Test
+    public void testSkipEntityStartIfRunningWhenAlreadyRunning() throws Exception {
+        app.createAndManageChild(EntitySpec.create(VanillaSoftwareProcess.class)
+                .configure(VanillaSoftwareProcess.SKIP_ENTITY_START_IF_RUNNING, true)
+                .configure(VanillaSoftwareProcess.PRE_INSTALL_COMMAND, "preInstallCommand")
+                .configure(VanillaSoftwareProcess.INSTALL_COMMAND, "installCommand")
+                .configure(VanillaSoftwareProcess.POST_INSTALL_COMMAND, "postInstallCommand")
+                .configure(VanillaSoftwareProcess.PRE_CUSTOMIZE_COMMAND, "preCustomizeCommand")
+                .configure(VanillaSoftwareProcess.CUSTOMIZE_COMMAND, "customizeCommand")
+                .configure(VanillaSoftwareProcess.POST_CUSTOMIZE_COMMAND, "postCustomizeCommand")
+                .configure(VanillaSoftwareProcess.PRE_LAUNCH_COMMAND, "preLaunchCommand")
+                .configure(VanillaSoftwareProcess.LAUNCH_COMMAND, "launchCommand")
+                .configure(VanillaSoftwareProcess.POST_LAUNCH_COMMAND, "postLaunchCommand")
+                .configure(VanillaSoftwareProcess.CHECK_RUNNING_COMMAND, "checkRunningCommand")
+                .configure(VanillaSoftwareProcess.STOP_COMMAND, "stopCommand"));
+        app.start(ImmutableList.of(loc));
+
+        assertExecsContain(RecordingSshTool.getExecCmds(), ImmutableList.of(
+                "checkRunningCommand"));
+        
+        assertExecsNotContains(RecordingSshTool.getExecCmds(), ImmutableList.of(
+                "launchCommand"));
+    }
+
+    @Test
+    public void testSkipEntityStartIfRunningWhenNotYetRunning() throws Exception {
+        // The custom-responses are so that checkRunning returns success only after launch is done
+        final AtomicBoolean isStarted = new AtomicBoolean();
+        RecordingSshTool.setCustomResponse(".*checkRunningCommand.*", new RecordingSshTool.CustomResponseGenerator() {
+            @Override public CustomResponse generate(ExecParams execParams) {
+                int exitCode = isStarted.get() ? 0 : 1;
+                return new CustomResponse(exitCode, "", "");
+            }});
+        RecordingSshTool.setCustomResponse(".*launchCommand.*", new RecordingSshTool.CustomResponseGenerator() {
+            @Override public CustomResponse generate(ExecParams execParams) {
+                isStarted.set(true);
+                return new CustomResponse(0, "", "");
+            }});
+        RecordingSshTool.setCustomResponse(".*stopCommand.*", new RecordingSshTool.CustomResponseGenerator() {
+            @Override public CustomResponse generate(ExecParams execParams) {
+                isStarted.set(false);
+                return new CustomResponse(0, "", "");
+            }});
+
+        app.createAndManageChild(EntitySpec.create(VanillaSoftwareProcess.class)
+                .configure(VanillaSoftwareProcess.SKIP_ENTITY_START_IF_RUNNING, true)
+                .configure(VanillaSoftwareProcess.PRE_INSTALL_COMMAND, "preInstallCommand")
+                .configure(VanillaSoftwareProcess.INSTALL_COMMAND, "installCommand")
+                .configure(VanillaSoftwareProcess.POST_INSTALL_COMMAND, "postInstallCommand")
+                .configure(VanillaSoftwareProcess.PRE_CUSTOMIZE_COMMAND, "preCustomizeCommand")
+                .configure(VanillaSoftwareProcess.CUSTOMIZE_COMMAND, "customizeCommand")
+                .configure(VanillaSoftwareProcess.POST_CUSTOMIZE_COMMAND, "postCustomizeCommand")
+                .configure(VanillaSoftwareProcess.PRE_LAUNCH_COMMAND, "preLaunchCommand")
+                .configure(VanillaSoftwareProcess.LAUNCH_COMMAND, "launchCommand")
+                .configure(VanillaSoftwareProcess.POST_LAUNCH_COMMAND, "postLaunchCommand")
+                .configure(VanillaSoftwareProcess.CHECK_RUNNING_COMMAND, "checkRunningCommand")
+                .configure(VanillaSoftwareProcess.STOP_COMMAND, "stopCommand"));
+        app.start(ImmutableList.of(loc));
+
+        assertExecsContain(RecordingSshTool.getExecCmds(), ImmutableList.of(
+                "checkRunningCommand",
+                "preInstallCommand", "installCommand", "postInstallCommand", 
+                "preCustomizeCommand", "customizeCommand", "postCustomizeCommand", 
+                "preLaunchCommand", "launchCommand", "postLaunchCommand", 
+                "checkRunningCommand"));
+    }
+
+    @Test
+    public void testShellEnv() throws Exception {
+        app.createAndManageChild(EntitySpec.create(VanillaSoftwareProcess.class)
+                .configure(VanillaSoftwareProcess.SHELL_ENVIRONMENT.subKey("KEY1"), "VAL1")
+                .configure(VanillaSoftwareProcess.PRE_INSTALL_COMMAND, "preInstallCommand")
+                .configure(VanillaSoftwareProcess.INSTALL_COMMAND, "installCommand")
+                .configure(VanillaSoftwareProcess.POST_INSTALL_COMMAND, "postInstallCommand")
+                .configure(VanillaSoftwareProcess.PRE_CUSTOMIZE_COMMAND, "preCustomizeCommand")
+                .configure(VanillaSoftwareProcess.CUSTOMIZE_COMMAND, "customizeCommand")
+                .configure(VanillaSoftwareProcess.POST_CUSTOMIZE_COMMAND, "postCustomizeCommand")
+                .configure(VanillaSoftwareProcess.PRE_LAUNCH_COMMAND, "preLaunchCommand")
+                .configure(VanillaSoftwareProcess.LAUNCH_COMMAND, "launchCommand")
+                .configure(VanillaSoftwareProcess.POST_LAUNCH_COMMAND, "postLaunchCommand")
+                .configure(VanillaSoftwareProcess.CHECK_RUNNING_COMMAND, "checkRunningCommand")
+                .configure(VanillaSoftwareProcess.STOP_COMMAND, "stopCommand"));
+        app.start(ImmutableList.of(loc));
+
+        Map<String, String> expectedEnv = ImmutableMap.of("KEY1", "VAL1");
+        
+        assertExecsSatisfy(RecordingSshTool.getExecCmds(), ImmutableList.of(
+                Predicates.and(ExecCmdPredicates.containsCmd("preInstallCommand"), ExecCmdPredicates.containsEnv(expectedEnv)),
+                Predicates.and(ExecCmdPredicates.containsCmd("installCommand"), ExecCmdPredicates.containsEnv(expectedEnv)),
+                Predicates.and(ExecCmdPredicates.containsCmd("postInstallCommand"), ExecCmdPredicates.containsEnv(expectedEnv)),
+                Predicates.and(ExecCmdPredicates.containsCmd("preCustomizeCommand"), ExecCmdPredicates.containsEnv(expectedEnv)),
+                Predicates.and(ExecCmdPredicates.containsCmd("customizeCommand"), ExecCmdPredicates.containsEnv(expectedEnv)),
+                Predicates.and(ExecCmdPredicates.containsCmd("postCustomizeCommand"), ExecCmdPredicates.containsEnv(expectedEnv)),
+                Predicates.and(ExecCmdPredicates.containsCmd("preLaunchCommand"), ExecCmdPredicates.containsEnv(expectedEnv)),
+                Predicates.and(ExecCmdPredicates.containsCmd("launchCommand"), ExecCmdPredicates.containsEnv(expectedEnv)),
+                Predicates.and(ExecCmdPredicates.containsCmd("postLaunchCommand"), ExecCmdPredicates.containsEnv(expectedEnv)),
+                Predicates.and(ExecCmdPredicates.containsCmd("checkRunningCommand"), ExecCmdPredicates.containsEnv(expectedEnv))));
+        
+        app.stop();
+
+        assertExecSatisfies(
+                RecordingSshTool.getLastExecCmd(),
+                Predicates.and(ExecCmdPredicates.containsCmd("stopCommand"), ExecCmdPredicates.containsEnv(expectedEnv)));
+    }
+    
+    protected void assertExecsContain(List<ExecCmd> actuals, List<String> expectedCmds) {
+        String errMsg = "actuals="+actuals+"; expected="+expectedCmds;
+        assertTrue(actuals.size() >= expectedCmds.size(), "actualSize="+actuals.size()+"; expectedSize="+expectedCmds.size()+"; "+errMsg);
+        for (int i = 0; i < expectedCmds.size(); i++) {
+            assertExecContains(actuals.get(i), expectedCmds.get(i), errMsg);
+        }
+    }
+
+    protected void assertExecContains(ExecCmd actual, String expectedCmdRegex) {
+        assertExecContains(actual, expectedCmdRegex, null);
+    }
+    
+    protected void assertExecContains(ExecCmd actual, String expectedCmdRegex, String errMsg) {
+        for (String cmd : actual.commands) {
+            if (cmd.matches(expectedCmdRegex)) {
+                return;
+            }
+        }
+        fail(expectedCmdRegex + " not matched by any commands in " + actual+(errMsg != null ? "; "+errMsg : ""));
+    }
+
+    protected void assertExecsNotContains(List<? extends ExecCmd> actuals, List<String> expectedNotCmdRegexs) {
+        for (ExecCmd actual : actuals) {
+            assertExecContains(actual, expectedNotCmdRegexs);
+        }
+    }
+    
+    protected void assertExecContains(ExecCmd actual, List<String> expectedNotCmdRegexs) {
+        for (String cmdRegex : expectedNotCmdRegexs) {
+            for (String subActual : actual.commands) {
+                if (subActual.matches(cmdRegex)) {
+                    fail("Exec should not contain " + cmdRegex + ", but matched by " + actual);
+                }
+            }
+        }
+    }
+
+    protected void assertExecsSatisfy(List<ExecCmd> actuals, List<? extends Predicate<? super ExecCmd>> expectedCmds) {
+        String errMsg = "actuals="+actuals+"; expected="+expectedCmds;
+        assertTrue(actuals.size() >= expectedCmds.size(), "actualSize="+actuals.size()+"; expectedSize="+expectedCmds.size()+"; "+errMsg);
+        for (int i = 0; i < expectedCmds.size(); i++) {
+            assertExecSatisfies(actuals.get(i), expectedCmds.get(i), errMsg);
+        }
+    }
+
+    protected void assertExecSatisfies(ExecCmd actual, Predicate<? super ExecCmd> expected) {
+        assertExecSatisfies(actual, expected, null);
+    }
+    
+    protected void assertExecSatisfies(ExecCmd actual, Predicate<? super ExecCmd> expected, String errMsg) {
+        if (!expected.apply(actual)) {
+            fail(expected + " not matched by " + actual + (errMsg != null ? "; "+errMsg : ""));
+        }
+    }
+}