You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2015/11/23 19:29:44 UTC

[15/20] incubator-brooklyn git commit: Fix location handling in integration test and remove unnecessary classes.

Fix location handling in integration test and remove unnecessary classes.


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

Branch: refs/heads/master
Commit: b2713ca4d7382ff1057531a6ef92a8a038a59d39
Parents: 6017880
Author: Geoff Macartney <ge...@cloudsoftcorp.com>
Authored: Wed Nov 18 12:59:25 2015 +0000
Committer: Geoff Macartney <ge...@cloudsoftcorp.com>
Committed: Wed Nov 18 14:35:48 2015 +0000

----------------------------------------------------------------------
 .../test/framework/SimpleShellCommand.java      |  75 ------
 .../test/framework/SimpleShellCommandImpl.java  | 260 -------------------
 ...impleShellCommandLifecycleEffectorTasks.java |  57 ----
 .../test/framework/SimpleShellCommandTest.java  |  31 ++-
 .../framework/SimpleShellCommandTestImpl.java   | 199 ++++++++++++--
 .../SimpleShellCommandIntegrationTest.java      |  47 ++--
 6 files changed, 236 insertions(+), 433 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2713ca4/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommand.java
----------------------------------------------------------------------
diff --git a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommand.java b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommand.java
deleted file mode 100644
index fc182ef..0000000
--- a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommand.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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.test.framework;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.ImplementedBy;
-import org.apache.brooklyn.config.ConfigKey;
-import org.apache.brooklyn.core.config.ConfigKeys;
-import org.apache.brooklyn.core.entity.trait.Startable;
-import org.apache.brooklyn.core.sensor.AttributeSensorAndConfigKey;
-import org.apache.brooklyn.entity.software.base.SoftwareProcess;
-import org.apache.brooklyn.util.core.flags.SetFromFlag;
-
-import static org.apache.brooklyn.core.config.ConfigKeys.newConfigKey;
-
-/**
- * Entity to invoke on a node a simple command that will immediately succeed or fail.
- *
- * Invokes the command in the start operation, and declares itself RUNNING.
- */
-@ImplementedBy(SimpleShellCommandImpl.class)
-public interface SimpleShellCommand extends Entity, Startable {
-
-    String TMP_DEFAULT = "/tmp";
-
-    /**
-     * Result of a command invocation.
-     */
-    interface Result {
-        int getExitCode();
-        String getStdout();
-        String getStderr();
-
-    }
-
-    /**
-     * Supply the command to invoke directly. Cannot be used together with {@link #DOWNLOAD_URL}.
-     */
-    @SetFromFlag(nullable = false)
-    ConfigKey<String> COMMAND = ConfigKeys.newConfigKey(String.class, "command", "Command to invoke");
-
-    /**
-     * Download a script to invoke. Cannot be used together with {@link #COMMAND}.
-     */
-    @SetFromFlag("downloadUrl")
-    AttributeSensorAndConfigKey<String, String> DOWNLOAD_URL = SoftwareProcess.DOWNLOAD_URL;
-
-    /**
-     * Where the script will be downloaded on the target machine.
-     */
-    @SetFromFlag("scriptDir")
-    ConfigKey<String> SCRIPT_DIR = newConfigKey("script.dir", "directory where downloaded scripts should be put", TMP_DEFAULT);
-
-    /**
-     * The working directory that the script will be run from on the target machine.
-     */
-    @SetFromFlag("runDir")
-    ConfigKey<String> RUN_DIR = newConfigKey(String.class, "run.dir", "directory where downloaded scripts should be run from");
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2713ca4/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandImpl.java
----------------------------------------------------------------------
diff --git a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandImpl.java b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandImpl.java
deleted file mode 100644
index c63e1fc..0000000
--- a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandImpl.java
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
- * 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.test.framework;
-
-import com.google.common.base.Joiner;
-import com.google.common.base.Splitter;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import org.apache.brooklyn.api.location.Location;
-import org.apache.brooklyn.api.location.MachineLocation;
-import org.apache.brooklyn.api.mgmt.TaskFactory;
-import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks;
-import org.apache.brooklyn.core.entity.AbstractEntity;
-import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
-import org.apache.brooklyn.core.location.Locations;
-import org.apache.brooklyn.location.ssh.SshMachineLocation;
-import org.apache.brooklyn.util.collections.MutableList;
-import org.apache.brooklyn.util.core.task.DynamicTasks;
-import org.apache.brooklyn.util.core.task.ssh.SshTasks;
-import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
-import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.apache.brooklyn.util.guava.Maybe;
-import org.apache.brooklyn.util.text.Strings;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.*;
-
-import static org.apache.brooklyn.core.entity.lifecycle.Lifecycle.*;
-import static org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic.setExpectedState;
-import static org.apache.brooklyn.util.text.Strings.isBlank;
-import static org.apache.brooklyn.util.text.Strings.isNonBlank;
-
-/**
- * Implementation for {@link SimpleShellCommand}.
- */
-public class SimpleShellCommandImpl extends AbstractEntity implements SimpleShellCommand {
-
-    private static final Logger LOG = LoggerFactory.getLogger(SimpleShellCommandImpl.class);
-    private static final int A_LINE = 80;
-    public static final String DEFAULT_NAME = "download.sh";
-    private static final String CD = "cd";
-
-    @Override
-    public void init() {
-        super.init();
-        getLifecycleEffectorTasks().attachLifecycleEffectors(this);
-    }
-
-    @Override
-    public void start(Collection<? extends Location> locations) {
-        addLocations(filterLocations(locations));
-        setExpectedState(this, STARTING);
-    }
-
-
-    @Override
-    public void stop() {
-        LOG.debug("{} Stopping simple command", this);
-        setUpAndRunState(false, STOPPED);
-    }
-
-    @Override
-    public void restart() {
-        LOG.debug("{} Restarting simple command", this);
-        setUpAndRunState(true, RUNNING);
-    }
-
-    protected SimpleShellCommandLifecycleEffectorTasks getLifecycleEffectorTasks() {
-        return new SimpleShellCommandLifecycleEffectorTasks();
-    }
-
-    /**
-     * Gives the opportunity to sub-classes to do additional work based on the result of the command.
-     */
-    protected void handle(SimpleShellCommand.Result result) {
-        LOG.debug("{}, Result is {}\nwith output [\n{}\n] and error [\n{}\n]", new Object[] {
-            this, result.getExitCode(), shorten(result.getStdout()), shorten(result.getStderr())
-        });
-    }
-
-    private String shorten(String text) {
-        return Strings.maxlenWithEllipsis(text, A_LINE);
-    }
-
-    /**
-     * Does nothing in this class but gives sub-classes the opportunity to filter locations according to some criterion.
-     */
-    public Collection<? extends Location> filterLocations(Collection<? extends Location> locations) {
-        return locations;
-    }
-
-    private void setUpAndRunState(boolean up, Lifecycle status) {
-        sensors().set(SERVICE_UP, up);
-        setExpectedState(this, status);
-    }
-
-    public void execute(MachineLocation machineLocation) {
-        try {
-            executeCommand(machineLocation);
-            setUpAndRunState(true, RUNNING);
-        } catch (Exception e) {
-            setUpAndRunState(false, ON_FIRE);
-            throw Exceptions.propagate(e);
-        }
-    }
-
-    private void executeCommand(MachineLocation machineLocation) {
-
-        SimpleShellCommand.Result result = null;
-        String downloadUrl = getConfig(DOWNLOAD_URL);
-        String command = getConfig(COMMAND);
-
-        String downloadName = DOWNLOAD_URL.getName();
-        String commandName = COMMAND.getName();
-
-        if (!(isNonBlank(downloadUrl) ^ isNonBlank(command))) {
-            throw illegal("Must specify exactly one of", downloadName, "and", commandName);
-        }
-
-        if (isNonBlank(downloadUrl)) {
-            String scriptDir = getConfig(SCRIPT_DIR);
-            String scriptPath = calculateDestPath(downloadUrl, scriptDir);
-            result = executeDownloadedScript(machineLocation, downloadUrl, scriptPath);
-        }
-
-        if (isNonBlank(command)) {
-            result = executeShellCommand(machineLocation, command);
-        }
-
-        handle(result);
-    }
-
-    private SimpleShellCommand.Result executeDownloadedScript(MachineLocation machineLocation, String url, String scriptPath) {
-
-        SshMachineLocation machine = getSshMachine(ImmutableList.<Location>of(machineLocation));
-
-        TaskFactory<?> install = SshTasks.installFromUrl(ImmutableMap.<String, Object>of(), machine, url, scriptPath);
-        DynamicTasks.queue(install);
-        DynamicTasks.waitForLast();
-
-        List<String> commands = new ArrayList<>();
-        commands.add("chmod u+x " + scriptPath);
-        maybeCdToRunDir(commands);
-        commands.add(scriptPath);
-
-        return runCommands(machine, commands);
-    }
-
-    private SimpleShellCommand.Result executeShellCommand(MachineLocation machineLocation, String command) {
-
-        SshMachineLocation machine = getSshMachine(ImmutableList.of(machineLocation));
-
-        List<String> commands = new ArrayList<>();
-        maybeCdToRunDir(commands);
-        commands.add(command);
-
-        return runCommands(machine, commands);
-    }
-
-    private void maybeCdToRunDir(List<String> commands) {
-        String runDir = getConfig(RUN_DIR);
-        if (!isBlank(runDir)) {
-            commands.add(CD + " " + runDir);
-        }
-    }
-
-    private Result runCommands(SshMachineLocation machine, List<String> commands) {
-        SshEffectorTasks.SshEffectorTaskFactory<Integer> etf = SshEffectorTasks.ssh(machine, commands.toArray(new String[]{}));
-
-        ProcessTaskWrapper<Integer> job = DynamicTasks.queue(etf);
-        DynamicTasks.waitForLast();
-        return buildResult(job);
-    }
-
-    private <T> SimpleShellCommand.Result buildResult(final ProcessTaskWrapper<Integer> job) {
-        final int exitCode = job.get();
-        final String stdout = job.getStdout().trim();
-        final String stderr = job.getStderr().trim();
-        return new SimpleShellCommand.Result() {
-
-            @Override
-            public int getExitCode() {
-                return exitCode;
-            }
-
-            @Override
-            public String getStdout() {
-                return stdout;
-            }
-
-            @Override
-            public String getStderr() {
-                return stderr;
-            }
-        };
-    }
-
-    private IllegalArgumentException illegal(String message, String ...messages) {
-        return new IllegalArgumentException(Joiner.on(' ').join(this.toString() + ":", message, messages));
-    }
-
-    private SshMachineLocation getSshMachine(Collection<? extends Location> hostLocations) {
-        Maybe<SshMachineLocation> host = Locations.findUniqueSshMachineLocation(hostLocations);
-        if (host.isAbsent()) {
-            throw new IllegalArgumentException("No SSH machine found to run command");
-        }
-        return host.get();
-    }
-
-    private String calculateDestPath(String url, String directory) {
-        try {
-            URL asUrl = new URL(url);
-            Iterable<String> path = Splitter.on("/").split(asUrl.getPath());
-            String scriptName = getLastPartOfPath(path, DEFAULT_NAME);
-            return Joiner.on("/").join(directory, "test-" + randomDir(), scriptName);
-        } catch (MalformedURLException e) {
-            throw illegal("Malformed URL:", url);
-        }
-    }
-
-    private String randomDir() {
-        return Integer.valueOf(new Random(System.currentTimeMillis()).nextInt(100000)).toString();
-    }
-
-    private static String getLastPartOfPath(Iterable<String> path, String defaultName) {
-        MutableList<String> parts = MutableList.copyOf(path);
-        Collections.reverse(parts);
-        Iterator<String> it = parts.iterator();
-        String scriptName = null;
-
-        // strip any trailing "/" parts of URL
-        while (isBlank(scriptName) && it.hasNext()) {
-            scriptName = it.next();
-        }
-        if (isBlank(scriptName)) {
-            scriptName = defaultName;
-        }
-        return scriptName;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2713ca4/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandLifecycleEffectorTasks.java
----------------------------------------------------------------------
diff --git a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandLifecycleEffectorTasks.java b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandLifecycleEffectorTasks.java
deleted file mode 100644
index fdccd99..0000000
--- a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandLifecycleEffectorTasks.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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.test.framework;
-
-import com.google.common.base.Supplier;
-import org.apache.brooklyn.api.location.Location;
-import org.apache.brooklyn.api.location.MachineLocation;
-import org.apache.brooklyn.entity.software.base.lifecycle.MachineLifecycleEffectorTasks;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.annotation.Nullable;
-import java.util.Collection;
-
-public class SimpleShellCommandLifecycleEffectorTasks extends MachineLifecycleEffectorTasks {
-
-    private static final Logger LOG = LoggerFactory.getLogger(SimpleShellCommandLifecycleEffectorTasks.class);
-
-    protected Location getLocation(@Nullable Collection<? extends Location> locations) {
-        return super.getLocation(entity().filterLocations(locations));
-    }
-
-
-    @Override
-    protected String startProcessesAtMachine(Supplier<MachineLocation> machineS) {
-        LOG.debug("Performing lifecycle startProcessesAtMachine on simple command");
-        MachineLocation machineLocation = machineS.get();
-        entity().execute(machineLocation);
-        return "Started simple command on " + machineLocation;
-    }
-
-    @Override
-    protected String stopProcessesAtMachine() {
-        LOG.debug("No action needed on simple command stopped");
-        return "Stopped";
-    }
-
-    protected SimpleShellCommandImpl entity() {
-        return (SimpleShellCommandImpl) super.entity();
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2713ca4/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTest.java
----------------------------------------------------------------------
diff --git a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTest.java b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTest.java
index 17621d9..0b71f2f 100644
--- a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTest.java
+++ b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTest.java
@@ -22,15 +22,21 @@ import com.google.common.collect.Maps;
 import org.apache.brooklyn.api.entity.ImplementedBy;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.sensor.AttributeSensorAndConfigKey;
+import org.apache.brooklyn.entity.software.base.SoftwareProcess;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
 
 import java.util.Map;
 
+import static org.apache.brooklyn.core.config.ConfigKeys.newConfigKey;
+
 /**
  * Tests using a simple command execution.
  */
 @ImplementedBy(SimpleShellCommandTestImpl.class)
-public interface SimpleShellCommandTest extends SimpleShellCommand, BaseTest {
+public interface SimpleShellCommandTest extends BaseTest {
+
+    String TMP_DEFAULT = "/tmp";
 
     /**
      * Equals assertion on command result.
@@ -53,6 +59,29 @@ public interface SimpleShellCommandTest extends SimpleShellCommand, BaseTest {
     String IS_EMPTY = "isEmpty";
 
     /**
+     * Supply the command to invoke directly. Cannot be used together with {@link #DOWNLOAD_URL}.
+     */
+    @SetFromFlag(nullable = false)
+    ConfigKey<String> COMMAND = ConfigKeys.newConfigKey(String.class, "command", "Command to invoke");
+
+    /**
+     * Download a script to invoke. Cannot be used together with {@link #COMMAND}.
+     */
+    @SetFromFlag("downloadUrl")
+    AttributeSensorAndConfigKey<String, String> DOWNLOAD_URL = SoftwareProcess.DOWNLOAD_URL;
+
+    /**
+     * Where the script will be downloaded on the target machine.
+     */
+    @SetFromFlag("scriptDir")
+    ConfigKey<String> SCRIPT_DIR = newConfigKey("script.dir", "directory where downloaded scripts should be put", TMP_DEFAULT);
+
+    /**
+     * The working directory that the script will be run from on the target machine.
+     */
+    @SetFromFlag("runDir")
+    ConfigKey<String> RUN_DIR = newConfigKey(String.class, "run.dir", "directory where downloaded scripts should be run from");
+    /**
      * Assertions on the exit code of the simple command.
      *
      * If not explicitly configured, the default assertion is a non-zero exit code.

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2713ca4/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTestImpl.java
----------------------------------------------------------------------
diff --git a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTestImpl.java b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTestImpl.java
index 6a35e73..c0ce8fc 100644
--- a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTestImpl.java
+++ b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTestImpl.java
@@ -20,39 +20,92 @@ package org.apache.brooklyn.test.framework;
 
 
 import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
 import com.google.common.collect.ImmutableMap;
-import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.mgmt.TaskFactory;
+import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.location.Machines;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.core.task.DynamicTasks;
+import org.apache.brooklyn.util.core.task.ssh.SshTasks;
+import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.text.Identifiers;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.*;
 
+import static org.apache.brooklyn.core.entity.lifecycle.Lifecycle.*;
+import static org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic.setExpectedState;
 import static org.apache.brooklyn.util.groovy.GroovyJavaMethods.truth;
+import static org.apache.brooklyn.util.text.Strings.isBlank;
+import static org.apache.brooklyn.util.text.Strings.isNonBlank;
 import static org.apache.commons.collections.MapUtils.isEmpty;
 
-public class SimpleShellCommandTestImpl extends SimpleShellCommandImpl implements SimpleShellCommandTest {
+public class SimpleShellCommandTestImpl extends AbstractTest implements SimpleShellCommandTest {
 
     public static final int SUCCESS = 0;
 
+    private static final Logger LOG = LoggerFactory.getLogger(SimpleShellCommandTestImpl.class);
+    private static final int A_LINE = 80;
+    public static final String DEFAULT_NAME = "download.sh";
+    private static final String CD = "cd";
+
     @Override
-    public Entity resolveTarget() {
-        return AbstractTest.resolveTarget(getExecutionContext(), this);
+    public void start(Collection<? extends Location> locations) {
+        setExpectedState(this, STARTING);
+        execute();
     }
 
-    /**
-     * The test will choose the location of its target entity.
-     */
-    public Collection<? extends Location> filterLocations(Collection<? extends Location> locations) {
-        Entity target = resolveTarget();
-        Collection<Location> targetLocations = target.getLocations();
-        return targetLocations;
+    @Override
+    public void stop() {
+        LOG.debug("{} Stopping simple command", this);
+        setUpAndRunState(false, STOPPED);
     }
 
     @Override
-    protected void handle(SimpleShellCommand.Result result) {
+    public void restart() {
+        LOG.debug("{} Restarting simple command", this);
+        execute();
+    }
+
+    private void setUpAndRunState(boolean up, Lifecycle status) {
+        sensors().set(SERVICE_UP, up);
+        setExpectedState(this, status);
+    }
+
+    private static class Result {
+        int exitCode;
+        String stdout;
+        String stderr;
+        public Result(final ProcessTaskWrapper<Integer> job) {
+            exitCode = job.get();
+            stdout = job.getStdout().trim();
+            stderr = job.getStderr().trim();
+        }
+        public int getExitCode() {
+            return exitCode;
+        }
+        public String getStdout() {
+            return stdout;
+        }
+        public String getStderr() {
+            return stderr;
+        }
+    }
+
+    protected void handle(Result result) {
+        LOG.debug("{}, Result is {}\nwith output [\n{}\n] and error [\n{}\n]", new Object[] {
+            this, result.getExitCode(), shorten(result.getStdout()), shorten(result.getStderr())
+        });
         AssertionSupport support = new AssertionSupport();
         checkAssertions(support, exitCodeAssertions(), "exit code", result.getExitCode());
         checkAssertions(support, getConfig(ASSERT_OUT), "stdout", result.getStdout());
@@ -60,6 +113,120 @@ public class SimpleShellCommandTestImpl extends SimpleShellCommandImpl implement
         support.validate();
     }
 
+    private String shorten(String text) {
+        return Strings.maxlenWithEllipsis(text, A_LINE);
+    }
+
+    public void execute() {
+        try {
+            SshMachineLocation machineLocation =
+                Machines.findUniqueMachineLocation(resolveTarget().getLocations(), SshMachineLocation.class).get();
+            executeCommand(machineLocation);
+            setUpAndRunState(true, RUNNING);
+        } catch (Throwable t) {
+            setUpAndRunState(false, ON_FIRE);
+            throw Exceptions.propagate(t);
+        }
+    }
+
+    private void executeCommand(SshMachineLocation machineLocation) {
+
+        Result result = null;
+        String downloadUrl = getConfig(DOWNLOAD_URL);
+        String command = getConfig(COMMAND);
+
+        String downloadName = DOWNLOAD_URL.getName();
+        String commandName = COMMAND.getName();
+
+        if (!(isNonBlank(downloadUrl) ^ isNonBlank(command))) {
+            throw illegal("Must specify exactly one of", downloadName, "and", commandName);
+        }
+
+        if (isNonBlank(downloadUrl)) {
+            String scriptDir = getConfig(SCRIPT_DIR);
+            String scriptPath = calculateDestPath(downloadUrl, scriptDir);
+            result = executeDownloadedScript(machineLocation, downloadUrl, scriptPath);
+        }
+
+        if (isNonBlank(command)) {
+            result = executeShellCommand(machineLocation, command);
+        }
+
+        handle(result);
+    }
+
+    private Result executeDownloadedScript(SshMachineLocation machineLocation, String url, String scriptPath) {
+
+        TaskFactory<?> install = SshTasks.installFromUrl(ImmutableMap.<String, Object>of(), machineLocation, url, scriptPath);
+        DynamicTasks.queue(install);
+        DynamicTasks.waitForLast();
+
+        List<String> commands = new ArrayList<>();
+        commands.add("chmod u+x " + scriptPath);
+        maybeCdToRunDir(commands);
+        commands.add(scriptPath);
+
+        return runCommands(machineLocation, commands);
+    }
+
+    private Result executeShellCommand(SshMachineLocation machineLocation, String command) {
+
+        List<String> commands = new ArrayList<>();
+        maybeCdToRunDir(commands);
+        commands.add(command);
+
+        return runCommands(machineLocation, commands);
+    }
+
+    private void maybeCdToRunDir(List<String> commands) {
+        String runDir = getConfig(RUN_DIR);
+        if (!isBlank(runDir)) {
+            commands.add(CD + " " + runDir);
+        }
+    }
+
+    private Result runCommands(SshMachineLocation machine, List<String> commands) {
+        SshEffectorTasks.SshEffectorTaskFactory<Integer> etf = SshEffectorTasks.ssh(commands.toArray(new String[]{}))
+            .machine(machine);
+
+        ProcessTaskWrapper<Integer> job = DynamicTasks.queue(etf);
+        job.asTask().blockUntilEnded();
+        return new Result(job);
+    }
+
+
+
+    private IllegalArgumentException illegal(String message, String ...messages) {
+        return new IllegalArgumentException(Joiner.on(' ').join(this.toString() + ":", message, messages));
+    }
+
+    private String calculateDestPath(String url, String directory) {
+        try {
+            URL asUrl = new URL(url);
+            Iterable<String> path = Splitter.on("/").split(asUrl.getPath());
+            String scriptName = getLastPartOfPath(path, DEFAULT_NAME);
+            return Joiner.on("/").join(directory, "test-" + Identifiers.makeRandomId(8), scriptName);
+        } catch (MalformedURLException e) {
+            throw illegal("Malformed URL:", url);
+        }
+    }
+
+    private static String getLastPartOfPath(Iterable<String> path, String defaultName) {
+        MutableList<String> parts = MutableList.copyOf(path);
+        Collections.reverse(parts);
+        Iterator<String> it = parts.iterator();
+        String scriptName = null;
+
+        // strip any trailing "/" parts of URL
+        while (isBlank(scriptName) && it.hasNext()) {
+            scriptName = it.next();
+        }
+        if (isBlank(scriptName)) {
+            scriptName = defaultName;
+        }
+        return scriptName;
+    }
+    
     private <T> void checkAssertions(AssertionSupport support, Map<?, ?> assertions, String target, T actual) {
         if (null == assertions) {
             return;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b2713ca4/usage/test-framework/src/test/java/org/apache/brooklyn/test/framework/SimpleShellCommandIntegrationTest.java
----------------------------------------------------------------------
diff --git a/usage/test-framework/src/test/java/org/apache/brooklyn/test/framework/SimpleShellCommandIntegrationTest.java b/usage/test-framework/src/test/java/org/apache/brooklyn/test/framework/SimpleShellCommandIntegrationTest.java
index 7f8f078..c6b5b87 100644
--- a/usage/test-framework/src/test/java/org/apache/brooklyn/test/framework/SimpleShellCommandIntegrationTest.java
+++ b/usage/test-framework/src/test/java/org/apache/brooklyn/test/framework/SimpleShellCommandIntegrationTest.java
@@ -21,38 +21,37 @@ package org.apache.brooklyn.test.framework;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import org.apache.brooklyn.api.entity.EntitySpec;
-import org.apache.brooklyn.api.location.LocationSpec;
 import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
 import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
 import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
 import org.apache.brooklyn.core.test.entity.TestEntity;
 import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.test.Asserts;
 import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.Random;
 
 import static org.apache.brooklyn.test.framework.BaseTest.TARGET_ENTITY;
-import static org.apache.brooklyn.test.framework.SimpleShellCommand.COMMAND;
 import static org.apache.brooklyn.test.framework.SimpleShellCommandTest.*;
 import static org.assertj.core.api.Assertions.assertThat;
 
 public class SimpleShellCommandIntegrationTest extends BrooklynAppUnitTestSupport {
 
     private static final String UP = "up";
-    private LocalhostMachineProvisioningLocation localhost;
-
-    protected void setUpApp() {
-        super.setUpApp();
-        localhost = app.getManagementContext().getLocationManager()
-            .createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class));
+    private LocalhostMachineProvisioningLocation loc;
+    private SshMachineLocation machine;
+
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() throws Exception {
+        super.setUp();
+        loc = app.newLocalhostProvisioningLocation();
+        machine = loc.obtain();
     }
 
     @DataProvider(name = "shouldInsistOnJustOneOfCommandAndScript")
@@ -66,15 +65,15 @@ public class SimpleShellCommandIntegrationTest extends BrooklynAppUnitTestSuppor
         };
     }
 
-    @Test(dataProvider = "shouldInsistOnJustOneOfCommandAndScript")
-    public void shouldInsistOnJustOneOfCommandAndScript(String command, String script, boolean valid) throws  Exception{
+    @Test(groups= "Integration", dataProvider = "shouldInsistOnJustOneOfCommandAndScript")
+    public void shouldInsistOnJustOneOfCommandAndScript(String command, String script, boolean valid) throws Exception {
         Path scriptPath = null;
         String scriptUrl = null;
         if (null != script) {
             scriptPath = createTempScript("pwd", "pwd");
             scriptUrl = "file:" + scriptPath;
         }
-        TestEntity testEntity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        TestEntity testEntity = app.createAndManageChild(EntitySpec.create(TestEntity.class).location(machine));
 
         app.createAndManageChild(EntitySpec.create(SimpleShellCommandTest.class)
             .configure(TARGET_ENTITY, testEntity)
@@ -82,7 +81,7 @@ public class SimpleShellCommandIntegrationTest extends BrooklynAppUnitTestSuppor
             .configure(DOWNLOAD_URL, scriptUrl));
 
         try {
-            app.start(ImmutableList.of(localhost));
+            app.start(ImmutableList.of(loc));
             if (!valid) {
                 Asserts.shouldHaveFailedPreviously();
             }
@@ -99,7 +98,7 @@ public class SimpleShellCommandIntegrationTest extends BrooklynAppUnitTestSuppor
 
     @Test(groups = "Integration")
     public void shouldInvokeCommand() {
-        TestEntity testEntity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        TestEntity testEntity = app.createAndManageChild(EntitySpec.create(TestEntity.class).location(machine));
 
         SimpleShellCommandTest uptime = app.createAndManageChild(EntitySpec.create(SimpleShellCommandTest.class)
             .configure(TARGET_ENTITY, testEntity)
@@ -107,7 +106,7 @@ public class SimpleShellCommandIntegrationTest extends BrooklynAppUnitTestSuppor
             .configure(ASSERT_STATUS, ImmutableMap.of(EQUALS, 0))
             .configure(ASSERT_OUT, ImmutableMap.of(CONTAINS, UP)));
 
-        app.start(ImmutableList.of(localhost));
+        app.start(ImmutableList.of(loc));
 
         assertThat(uptime.sensors().get(SERVICE_UP)).isTrue()
             .withFailMessage("Service should be up");
@@ -118,7 +117,7 @@ public class SimpleShellCommandIntegrationTest extends BrooklynAppUnitTestSuppor
 
     @Test(groups = "Integration")
     public void shouldNotBeUpIfAssertionFails() {
-        TestEntity testEntity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        TestEntity testEntity = app.createAndManageChild(EntitySpec.create(TestEntity.class).location(machine));
 
         SimpleShellCommandTest uptime = app.createAndManageChild(EntitySpec.create(SimpleShellCommandTest.class)
             .configure(TARGET_ENTITY, testEntity)
@@ -126,7 +125,7 @@ public class SimpleShellCommandIntegrationTest extends BrooklynAppUnitTestSuppor
             .configure(ASSERT_STATUS, ImmutableMap.of(EQUALS, 1)));
 
         try {
-            app.start(ImmutableList.of(localhost));
+            app.start(ImmutableList.of(loc));
         } catch (Exception e) {
             assertThat(e.getCause().getMessage().contains("exit code equals 1"));
         }
@@ -138,7 +137,7 @@ public class SimpleShellCommandIntegrationTest extends BrooklynAppUnitTestSuppor
 
     @Test(groups = "Integration")
     public void shouldInvokeScript() throws Exception {
-        TestEntity testEntity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        TestEntity testEntity = app.createAndManageChild(EntitySpec.create(TestEntity.class).location(machine));
 
         String text = "hello world";
         Path testScript = createTempScript("script", "echo " + text);
@@ -150,7 +149,7 @@ public class SimpleShellCommandIntegrationTest extends BrooklynAppUnitTestSuppor
                 .configure(ASSERT_STATUS, ImmutableMap.of(EQUALS, 0))
                 .configure(ASSERT_OUT, ImmutableMap.of(CONTAINS, text)));
 
-            app.start(ImmutableList.of(localhost));
+            app.start(ImmutableList.of(loc));
 
             assertThat(uptime.sensors().get(SERVICE_UP)).isTrue()
                 .withFailMessage("Service should be up");
@@ -162,9 +161,9 @@ public class SimpleShellCommandIntegrationTest extends BrooklynAppUnitTestSuppor
         }
     }
 
-    @Test
+    @Test(groups = "Integration")
     public void shouldExecuteInTheRunDir() throws Exception {
-        TestEntity testEntity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        TestEntity testEntity = app.createAndManageChild(EntitySpec.create(TestEntity.class).location(machine));
 
         Path pwdPath = createTempScript("pwd", "pwd");
 
@@ -184,7 +183,7 @@ public class SimpleShellCommandIntegrationTest extends BrooklynAppUnitTestSuppor
                 .configure(ASSERT_STATUS, ImmutableMap.of(EQUALS, 0))
                 .configure(ASSERT_OUT, ImmutableMap.of(CONTAINS, "/tmp")));
 
-            app.start(ImmutableList.of(localhost));
+            app.start(ImmutableList.of(loc));
 
             assertThat(pwd.sensors().get(SERVICE_UP)).isTrue().withFailMessage("Service should be up");
             assertThat(ServiceStateLogic.getExpectedState(pwd)).isEqualTo(Lifecycle.RUNNING)