Repository: incubator-brooklyn
Updated Branches:
  refs/heads/master faf08c116 -> bbacc2b94


Initial draft of SimpleCommand.


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

Branch: refs/heads/master
Commit: b4e3ac7b68ed26f3efa74be7285de013d1f8c02a
Parents: ca89ed4
Author: Geoff Macartney <[email protected]>
Authored: Thu Nov 5 15:06:45 2015 +0000
Committer: Geoff Macartney <[email protected]>
Committed: Fri Nov 13 12:01:49 2015 +0000

----------------------------------------------------------------------
 .../brooklyn/util/core/task/ssh/SshTasks.java   |   5 +
 usage/test-framework/pom.xml                    |   6 +
 .../brooklyn/test/framework/AbstractTest.java   |  28 ++-
 .../brooklyn/test/framework/BaseTest.java       |   2 +
 .../brooklyn/test/framework/SimpleCommand.java  |  50 +++++
 .../test/framework/SimpleCommandDriver.java     |  70 +++++++
 .../test/framework/SimpleCommandImpl.java       | 177 +++++++++++++++++
 .../SimpleCommandLifecycleEffectorTasks.java    |  73 +++++++
 .../test/framework/SimpleCommandSshDriver.java  | 192 +++++++++++++++++++
 .../test/framework/SimpleCommandTest.java       |  78 ++++++++
 .../test/framework/SimpleCommandTestImpl.java   | 149 ++++++++++++++
 .../brooklyn/test/framework/TestCase.java       |   1 +
 .../brooklyn/test/framework/TestEffector.java   |   1 +
 .../test/framework/TestHttpCallImpl.java        |   4 +-
 .../brooklyn/test/framework/TestSensor.java     |   1 +
 .../brooklyn/test/framework/TestSensorImpl.java |   2 +-
 .../test/framework/SimpleCommandImplTest.java   | 117 +++++++++++
 .../SimpleCommandScriptIntegrationTest.java     | 134 +++++++++++++
 .../test/framework/TestEffectorTest.java        |   3 +-
 .../brooklyn/logback-appender-stdout.xml        |  37 ++++
 20 files changed, 1117 insertions(+), 13 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b4e3ac7b/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/SshTasks.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/SshTasks.java 
b/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/SshTasks.java
index e939ca8..c9fd66b 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/SshTasks.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/SshTasks.java
@@ -215,6 +215,11 @@ public class SshTasks {
         return installFromUrl(ResourceUtils.create(SshTasks.class), 
ImmutableMap.<String,Object>of(), location, url, destPath);
     }
     /** task to install a file given a url, where the url is resolved remotely 
first then locally */
+    public static TaskFactory<?> installFromUrl(final Map<String, ?> props, 
final SshMachineLocation location, final String url, final String destPath) {
+        return installFromUrl(ResourceUtils.create(SshTasks.class), props, 
location, url, destPath);
+    }
+
+    /** task to install a file given a url, where the url is resolved remotely 
first then locally */
     public static TaskFactory<?> installFromUrl(final ResourceUtils utils, 
final Map<String, ?> props, final SshMachineLocation location, final String 
url, final String destPath) {
         return new TaskFactory<TaskAdaptable<?>>() {
             @Override

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b4e3ac7b/usage/test-framework/pom.xml
----------------------------------------------------------------------
diff --git a/usage/test-framework/pom.xml b/usage/test-framework/pom.xml
index 006afdc..b4297a9 100644
--- a/usage/test-framework/pom.xml
+++ b/usage/test-framework/pom.xml
@@ -47,6 +47,12 @@
 
         <!--TEST SCOPE :: START-->
         <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-logback-xml</artifactId>
+            <version>${brooklyn.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
             <groupId>org.testng</groupId>
             <artifactId>testng</artifactId>
             <version>${testng.version}</version>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b4e3ac7b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/AbstractTest.java
----------------------------------------------------------------------
diff --git 
a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/AbstractTest.java
 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/AbstractTest.java
index d97d09b..434be8e 100644
--- 
a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/AbstractTest.java
+++ 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/AbstractTest.java
@@ -19,6 +19,7 @@
 package org.apache.brooklyn.test.framework;
 
 import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.mgmt.ExecutionContext;
 import org.apache.brooklyn.api.mgmt.Task;
 import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent;
 import org.apache.brooklyn.core.entity.AbstractEntity;
@@ -44,24 +45,33 @@ public abstract class AbstractTest extends AbstractEntity 
implements BaseTest {
      * @throws @RuntimeException if no target can be determined.
      */
     public Entity resolveTarget() {
-        Entity entity = getConfig(TARGET_ENTITY);
-        if (null == entity) {
-            entity = getTargetById();
+        return resolveTarget(getExecutionContext(), this);
+    }
+
+    /**
+     * Find the target entity in the given execution context.
+     *
+     * @see {@link #resolveTarget()}.
+     */
+    public static Entity resolveTarget(ExecutionContext executionContext, 
Entity entity) {
+        Entity target = entity.getConfig(TARGET_ENTITY);
+        if (null == target) {
+            target = getTargetById(executionContext, entity);
         }
-        return entity;
+        return target;
     }
 
-    private Entity getTargetById() {
-        String targetId = getConfig(TARGET_ID);
+    private static Entity getTargetById(ExecutionContext executionContext, 
Entity entity) {
+        String targetId = entity.getConfig(TARGET_ID);
         final Task<Entity> targetLookup = new DslComponent(targetId).newTask();
-        Entity entity = null;
+        Entity target = null;
         try {
-            entity = Tasks.resolveValue(targetLookup, Entity.class, 
getExecutionContext(), "Finding entity " + targetId);
+            target = Tasks.resolveValue(targetLookup, Entity.class, 
executionContext, "Finding entity " + targetId);
             LOG.debug("Found target by id {}", targetId);
         } catch (final ExecutionException | InterruptedException e) {
             LOG.error("Error finding target {}", targetId);
             Exceptions.propagate(e);
         }
-        return entity;
+        return target;
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b4e3ac7b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/BaseTest.java
----------------------------------------------------------------------
diff --git 
a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/BaseTest.java
 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/BaseTest.java
index 7fd70ef..4043bcc 100644
--- 
a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/BaseTest.java
+++ 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/BaseTest.java
@@ -20,6 +20,7 @@ package org.apache.brooklyn.test.framework;
 
 import com.google.common.collect.Maps;
 import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.mgmt.ExecutionContext;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.entity.trait.Startable;
@@ -60,4 +61,5 @@ public interface BaseTest extends Entity, Startable {
      * @throws IllegalArgumentException if the target cannot be found.
      */
     Entity resolveTarget();
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b4e3ac7b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommand.java
----------------------------------------------------------------------
diff --git 
a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommand.java
 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommand.java
new file mode 100644
index 0000000..3332a04
--- /dev/null
+++ 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommand.java
@@ -0,0 +1,50 @@
+/*
+ * 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 org.apache.brooklyn.util.os.Os;
+
+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(SimpleCommandImpl.class)
+public interface SimpleCommand extends Entity, Startable {
+
+    @SetFromFlag(nullable = false)
+    ConfigKey<String> DEFAULT_COMMAND = ConfigKeys.newConfigKey(String.class, 
"defaultCommand",
+            "Command to invoke if no script is provided via a downloadUrl");
+
+    @SetFromFlag("downloadUrl")
+    AttributeSensorAndConfigKey<String, String> DOWNLOAD_URL = 
SoftwareProcess.DOWNLOAD_URL;
+
+    @SetFromFlag("scriptDir")
+    ConfigKey<String> SCRIPT_DIR = newConfigKey("scriptDir", "directory where 
downloaded scripts should be put", Os.tmp());
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b4e3ac7b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandDriver.java
----------------------------------------------------------------------
diff --git 
a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandDriver.java
 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandDriver.java
new file mode 100644
index 0000000..d95d509
--- /dev/null
+++ 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandDriver.java
@@ -0,0 +1,70 @@
+/*
+ * 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.EntityLocal;
+import org.apache.brooklyn.api.entity.drivers.EntityDriver;
+import org.apache.brooklyn.api.location.Location;
+
+import java.util.Collection;
+
+/**
+ * Driver to invoke a command on a node.
+ */
+public interface SimpleCommandDriver extends EntityDriver {
+
+    /**
+     * Result of the command invocation.
+     */
+    interface Result {
+        int getExitCode();
+        String getStdout();
+        String getStderr();
+    }
+
+    /**
+     * The entity whose components we are controlling.
+     */
+    EntityLocal getEntity();
+
+    /**
+     * Execute the simple command during the start operation.
+     */
+    void start();
+
+    /**
+     * Execute the simple command during the restart.
+     */
+    void restart();
+
+    /**
+     * Does nothing.
+     */
+    void stop();
+
+    /**
+     * Execute the given command on the supplied host.
+     */
+    Result execute(Collection<? extends Location> hostLocations, String 
command);
+
+    /**
+     * Download the script at the given URL to the given directory on the host 
and execute it.
+     */
+    Result executeDownloadedScript(Collection<? extends Location> 
hostLocations, String url, String directory);
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b4e3ac7b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandImpl.java
----------------------------------------------------------------------
diff --git 
a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandImpl.java
 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandImpl.java
new file mode 100644
index 0000000..8b3d27f
--- /dev/null
+++ 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandImpl.java
@@ -0,0 +1,177 @@
+/*
+ * 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.drivers.DriverDependentEntity;
+import org.apache.brooklyn.api.entity.drivers.EntityDriverManager;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.MachineLocation;
+import org.apache.brooklyn.core.annotation.EffectorParam;
+import org.apache.brooklyn.core.entity.AbstractEntity;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+
+import static org.apache.brooklyn.core.entity.lifecycle.Lifecycle.*;
+import static 
org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic.setExpectedState;
+
+/**
+ * Implementation for {@link SimpleCommand}.
+ */
+public class SimpleCommandImpl extends AbstractEntity
+        implements SimpleCommand, DriverDependentEntity<SimpleCommandDriver> {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(SimpleCommandImpl.class);
+    private static final int A_LINE = 80;
+    private transient SimpleCommandDriver driver;
+
+    private Collection<? extends Location> locations;
+
+    @Override
+    public SimpleCommandDriver getDriver() {
+        return driver;
+    }
+
+    @Override
+    public Class<SimpleCommandDriver> getDriverInterface() {
+        return SimpleCommandDriver.class;
+    }
+
+    /**
+     * Gives the opportunity to sub-classes to do additional work based on the 
result of the command.
+     */
+    protected void handle(SimpleCommandDriver.Result result) {
+        LOG.debug("Result is {}\nwith output [\n{}\n] and error [\n{}\n]", new 
Object[] {
+                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;
+    }
+
+
+    @Override
+    public void init() {
+        super.init();
+        getLifecycleEffectorTasks().attachLifecycleEffectors(this);
+    }
+
+
+    protected void initDriver(MachineLocation machine) {
+        LOG.debug("Initializing simple command driver");
+        SimpleCommandDriver newDriver = doInitDriver(machine);
+        if (newDriver == null) {
+            throw new UnsupportedOperationException("cannot start "+this+" on 
"+machine+": no driver available");
+        }
+        driver = newDriver;
+    }
+
+    protected SimpleCommandDriver doInitDriver(MachineLocation machine) {
+        if (driver!=null) {
+            if (machine.equals(driver.getLocation())) {
+                return driver; //just reuse
+            } else {
+                LOG.warn("driver/location change is untested for {} at {}; 
changing driver and continuing", this, machine);
+                return newDriver(machine);
+            }
+        } else {
+            return newDriver(machine);
+        }
+    }
+
+    protected SimpleCommandDriver newDriver(MachineLocation machine) {
+        LOG.debug("Creating new simple command driver for {} from management 
context", machine);
+        EntityDriverManager entityDriverManager = 
getManagementContext().getEntityDriverManager();
+        return entityDriverManager.build(this, machine);
+    }
+
+    @Override
+    public void start(@EffectorParam(name = "locations") Collection<? extends 
Location> locations) {
+        this.locations = locations;
+        startOnLocations();
+    }
+
+    protected void startOnLocations() {
+        setExpectedState(this, STARTING);
+        int size = locations.size();
+        LOG.debug("Starting simple command at {} locations{}", size,
+                size > 0 ? " beginning " + locations.iterator().next() : "");
+        try {
+            execute(locations);
+            setUpAndRunState(true, RUNNING);
+
+        } catch (final Exception e) {
+            setUpAndRunState(false, ON_FIRE);
+            throw Exceptions.propagate(e);
+        }
+    }
+
+    private void execute(Collection<? extends Location> locations) {
+        SimpleCommandDriver.Result result = null;
+        String downloadUrl = getConfig(DOWNLOAD_URL);
+        if (Strings.isNonBlank(downloadUrl)) {
+            String scriptDir = getConfig(SCRIPT_DIR);
+            result = getDriver().executeDownloadedScript(locations, 
downloadUrl, scriptDir);
+
+        } else {
+            String command = getConfig(DEFAULT_COMMAND);
+            if (Strings.isBlank(command)) {
+                throw new IllegalArgumentException("No default command and no 
downloadUrl provided");
+            }
+
+            result = getDriver().execute(locations, command);
+        }
+        handle(result);
+    }
+
+
+    @Override
+    public void stop() {
+        LOG.debug("Stopping simple command");
+        setUpAndRunState(false, STOPPED);
+    }
+
+    @Override
+    public void restart() {
+        LOG.debug("Restarting simple command");
+        setUpAndRunState(true, RUNNING);
+    }
+
+    private void setUpAndRunState(boolean up, Lifecycle status) {
+        sensors().set(SERVICE_UP, up);
+        setExpectedState(this, status);
+    }
+
+    protected SimpleCommandLifecycleEffectorTasks getLifecycleEffectorTasks () 
{
+        return new SimpleCommandLifecycleEffectorTasks();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b4e3ac7b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandLifecycleEffectorTasks.java
----------------------------------------------------------------------
diff --git 
a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandLifecycleEffectorTasks.java
 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandLifecycleEffectorTasks.java
new file mode 100644
index 0000000..d044212
--- /dev/null
+++ 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandLifecycleEffectorTasks.java
@@ -0,0 +1,73 @@
+/*
+ * 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.apache.http.util.Asserts;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nullable;
+import java.util.Collection;
+
+public class SimpleCommandLifecycleEffectorTasks extends 
MachineLifecycleEffectorTasks {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(SimpleCommandLifecycleEffectorTasks.class);
+    private MachineLocation location;
+
+    protected Location getLocation(@Nullable Collection<? extends Location> 
locations) {
+        return super.getLocation(entity().filterLocations(locations));
+    }
+
+    @Override
+    protected void preStartCustom(MachineLocation machine) {
+        location = machine;
+        super.preStartCustom(location);
+        LOG.debug("Performing lifecycle preStartCustom on simple command");
+        entity().initDriver(location);
+    }
+
+    @Override
+    protected void preRestartCustom() {
+        LOG.debug("Performing lifecycle preStartCustom on simple command");
+        Asserts.notNull(location, "Cannot restart with no location");
+        entity().initDriver(location);
+    }
+
+    @Override
+    protected String startProcessesAtMachine(Supplier<MachineLocation> 
machineS) {
+        LOG.debug("Performing lifecycle startProcessesAtMachine on simple 
command");
+        entity().getDriver().start();
+        return "Started with driver " + entity().getDriver();
+    }
+
+    @Override
+    protected String stopProcessesAtMachine() {
+        LOG.debug("Performing lifecycle stopProcessesAtMachine on simple 
command");
+        entity().getDriver().stop();
+        return "Stopped";
+    }
+
+    protected SimpleCommandImpl entity() {
+        return (SimpleCommandImpl) super.entity();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b4e3ac7b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandSshDriver.java
----------------------------------------------------------------------
diff --git 
a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandSshDriver.java
 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandSshDriver.java
new file mode 100644
index 0000000..f386b1e
--- /dev/null
+++ 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandSshDriver.java
@@ -0,0 +1,192 @@
+/*
+ * 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.entity.EntityLocal;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.mgmt.TaskAdaptable;
+import org.apache.brooklyn.api.mgmt.TaskFactory;
+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.ResourceUtils;
+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.ProcessTaskFactory;
+import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.exceptions.FatalRuntimeException;
+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.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Random;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.apache.brooklyn.util.text.Strings.isBlank;
+
+/**
+ * Driver for {@link SimpleCommand}.
+ */
+public class SimpleCommandSshDriver implements SimpleCommandDriver {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(SimpleCommandSshDriver.class);
+    public static final String DEFAULT_NAME = "download.sh";
+
+    protected final EntityLocal entity;
+    protected final ResourceUtils resource;
+    protected final Location location;
+
+    public SimpleCommandSshDriver(EntityLocal entity, SshMachineLocation 
location) {
+        LOG.debug("Constructing SSH driver for simple command for {} at {}", 
entity, location);
+        this.entity = checkNotNull(entity, "entity");
+        this.location = checkNotNull(location, "location");
+        this.resource = ResourceUtils.create(entity);
+    }
+
+    @Override
+    public EntityLocal getEntity() {
+        return entity;
+    }
+
+    @Override
+    public void start() {
+        LOG.debug("Performing start in SSH driver for simple command");
+        invoke();
+    }
+
+    private void invoke() {
+        SimpleCommand simpleCommand = (SimpleCommand) getEntity();
+        simpleCommand.start(ImmutableList.of(location));
+    }
+
+    @Override
+    public void restart() {
+        LOG.debug("Performing restart in SSH driver for simple command");
+        invoke();
+    }
+
+    @Override
+    public void stop() {
+        LOG.debug("Performing stop in SSH driver for simple command");
+    }
+
+    @Override
+    public Result execute(Collection<? extends Location> hostLocations, String 
command) {
+
+        SshMachineLocation machine = getSshMachine(hostLocations);
+        ProcessTaskFactory<Integer> taskFactory = 
SshTasks.newSshExecTaskFactory(machine, command);
+
+        LOG.debug("Creating task to execute '{}' on location {}", command, 
machine);
+        final ProcessTaskWrapper<Integer> job = 
DynamicTasks.queue(taskFactory);
+        DynamicTasks.waitForLast();
+        return buildResult(job);
+    }
+
+    private <T> Result buildResult(final ProcessTaskWrapper<Integer> job) {
+        return new Result() {
+
+            @Override
+            public int getExitCode() {
+                return job.get();
+            }
+
+            @Override
+            public String getStdout() {
+                return job.getStdout().trim();
+            }
+
+            @Override
+            public String getStderr() {
+                return job.getStderr().trim();
+            }
+        };
+    }
+
+    @Override
+    public Result executeDownloadedScript(Collection<? extends Location> 
hostLocations,
+                                          String url, String directory) {
+
+        SshMachineLocation machine = getSshMachine(hostLocations);
+        String destPath = calculateDestPath(url, directory);
+
+        TaskFactory<?> install = SshTasks.installFromUrl(ImmutableMap.<String, 
Object>of(), machine, url, destPath);
+        DynamicTasks.queue(install);
+        DynamicTasks.waitForLast();
+
+        machine.execCommands("make the script executable", 
ImmutableList.<String>of("chmod u+x " + destPath));
+
+        return execute(hostLocations, destPath);
+    }
+
+    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 Exceptions.propagate(new FatalRuntimeException("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;
+    }
+
+    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();
+    }
+
+    @Override
+    public Location getLocation() {
+        return location;
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b4e3ac7b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandTest.java
----------------------------------------------------------------------
diff --git 
a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandTest.java
 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandTest.java
new file mode 100644
index 0000000..c962403
--- /dev/null
+++ 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.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.util.core.flags.SetFromFlag;
+
+import java.util.Map;
+
+/**
+ * Tests using a simple command execution.
+ */
+@ImplementedBy(SimpleCommandTestImpl.class)
+public interface SimpleCommandTest extends SimpleCommand, BaseTest {
+
+    /**
+     * Equals assertion on command result.
+     */
+    String EQUALS = "equals";
+
+    /**
+     * String contains assertion on command result.
+     */
+    String CONTAINS = "contains";
+
+    /**
+     * Regex match assertion on command result.
+     */
+    String MATCHES = "matches";
+
+    /**
+     * Is-empty match assertion on command result.
+     */
+    String IS_EMPTY = "isEmpty";
+
+    /**
+     * Assertions on the exit code of the simple command.
+     *
+     * If not explicitly configured, the default assertion is a non-zero exit 
code.
+     */
+    @SetFromFlag("assertStatus")
+    ConfigKey<Map> ASSERT_STATUS = ConfigKeys.newConfigKey(Map.class, 
"assert.status",
+            "Assertions on command exit code", Maps.newLinkedHashMap());
+
+    /**
+     * Assertions on the standard output of the command as a String.
+     */
+    @SetFromFlag("assertOut")
+    ConfigKey<Map> ASSERT_OUT = ConfigKeys.newConfigKey(Map.class, 
"assert.out",
+            "Assertions on command standard output", Maps.newLinkedHashMap());
+
+    /**
+     * Assertions on the standard error of the command as a String.
+     */
+    @SetFromFlag("assertErr")
+    ConfigKey<Map> ASSERT_ERR = ConfigKeys.newConfigKey(Map.class, 
"assert.err",
+            "Assertions on command standard error", Maps.newLinkedHashMap());
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b4e3ac7b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandTestImpl.java
----------------------------------------------------------------------
diff --git 
a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandTestImpl.java
 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandTestImpl.java
new file mode 100644
index 0000000..261afe7
--- /dev/null
+++ 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleCommandTestImpl.java
@@ -0,0 +1,149 @@
+/*
+ * 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.collect.ImmutableMap;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.location.Locations;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.groovy.GroovyJavaMethods;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.brooklyn.util.groovy.GroovyJavaMethods.truth;
+import static org.apache.commons.collections.MapUtils.isEmpty;
+
+public class SimpleCommandTestImpl extends SimpleCommandImpl implements 
SimpleCommandTest {
+
+    public static final int SUCCESS = 0;
+
+    @Override
+    public Entity resolveTarget() {
+        return AbstractTest.resolveTarget(getExecutionContext(), this);
+    }
+
+    /**
+     * The test will choose the location of its target entity.
+     */
+    public Collection<? extends Location> filterLocations(Collection<? extends 
Location> locations) {
+        Entity target = resolveTarget();
+        return target.getLocations();
+    }
+
+    @Override
+    protected void handle(SimpleCommandDriver.Result result) {
+        AssertionSupport support = new AssertionSupport();
+        checkAssertions(support, exitCodeAssertions(), "exit code", 
result.getExitCode());
+        checkAssertions(support, getConfig(ASSERT_OUT), "stdout", 
result.getStdout());
+        checkAssertions(support, getConfig(ASSERT_ERR), "stderr", 
result.getStderr());
+        support.validate();
+    }
+
+    private <T> void checkAssertions(AssertionSupport support, Map<?, ?> 
assertions, String target, T actual) {
+        if (null == assertions) {
+            return;
+        }
+        if (null == actual) {
+            support.fail(target, "no actual value", "");
+            return;
+        }
+        for (Map.Entry<?, ?> assertion : assertions.entrySet()) {
+            String condition = assertion.getKey().toString();
+            Object expected = assertion.getValue();
+            switch (condition) {
+                case EQUALS :
+                    if (!actual.equals(expected)) {
+                        support.fail(target, EQUALS, expected);
+                    }
+                    break;
+                case CONTAINS :
+                    if (!actual.toString().contains(expected.toString())) {
+                        support.fail(target, CONTAINS, expected);
+                    }
+                    break;
+                case IS_EMPTY:
+                    if (!actual.toString().isEmpty() && truth(expected)) {
+                        support.fail(target, IS_EMPTY, expected);
+                    }
+                    break;
+                case MATCHES :
+                    if (!actual.toString().matches(expected.toString())) {
+                        support.fail(target, MATCHES, expected);
+                    }
+                    break;
+                default:
+                    support.fail(target, "unknown condition", condition);
+            }
+        }
+    }
+
+    private Map<?, ?> exitCodeAssertions() {
+        Map<?, ?> assertStatus = getConfig(ASSERT_STATUS);
+        if (isEmpty(assertStatus)) {
+            assertStatus = ImmutableMap.of(EQUALS, SUCCESS);
+        }
+        return assertStatus;
+    }
+
+    public static class FailedAssertion {
+        String target;
+        String assertion;
+        String expected;
+
+        public FailedAssertion(String target, String assertion, String 
expected) {
+            this.target = target;
+            this.assertion = assertion;
+            this.expected = expected;
+        }
+        public String description() {
+            return Joiner.on(' ').join(target, assertion, expected);
+        }
+    }
+
+    /**
+     * A convenience to collect and validate any assertion failures.
+     */
+    public static class AssertionSupport {
+        private List<FailedAssertion> failures = new ArrayList<>();
+
+        public void fail(String target, String assertion, Object expected) {
+            failures.add(new FailedAssertion(target, assertion, 
expected.toString()));
+        }
+
+        /**
+         * @throws AssertionError if any failures were collected.
+         */
+        public void validate() {
+            if (0 < failures.size()) {
+                StringBuilder summary = new StringBuilder();
+                summary.append("Assertion Failures: \n");
+                for (FailedAssertion fail : failures) {
+                    summary.append(fail.description()).append("\n");
+                }
+                Asserts.fail(summary.toString());
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b4e3ac7b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestCase.java
----------------------------------------------------------------------
diff --git 
a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestCase.java
 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestCase.java
index 78fd8d6..d05889d 100644
--- 
a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestCase.java
+++ 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestCase.java
@@ -19,6 +19,7 @@
 package org.apache.brooklyn.test.framework;
 
 import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.core.entity.trait.Startable;
 
 /**
  * Entity that logically groups other test entities

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b4e3ac7b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEffector.java
----------------------------------------------------------------------
diff --git 
a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEffector.java
 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEffector.java
index 14c45f5..6639845 100644
--- 
a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEffector.java
+++ 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEffector.java
@@ -23,6 +23,7 @@ import com.google.common.reflect.TypeToken;
 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.util.core.flags.SetFromFlag;
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b4e3ac7b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCallImpl.java
----------------------------------------------------------------------
diff --git 
a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCallImpl.java
 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCallImpl.java
index 66e24da..a1d84bf 100644
--- 
a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCallImpl.java
+++ 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCallImpl.java
@@ -58,7 +58,7 @@ public class TestHttpCallImpl extends AbstractTest implements 
TestHttpCall {
             sensors().set(SERVICE_UP, true);
             ServiceStateLogic.setExpectedState(this, Lifecycle.RUNNING);
         } catch (Throwable t) {
-            LOG.info("Url [{}] test failed", url);
+            LOG.debug("Url [{}] test failed", url);
             sensors().set(SERVICE_UP, false);
             ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE);
             throw Exceptions.propagate(t);
@@ -89,7 +89,7 @@ public class TestHttpCallImpl extends AbstractTest implements 
TestHttpCall {
 
         for (final Map.Entry<?, ?> entry : assertions.entrySet()) {
             if (Objects.equal(entry.getKey(), "regex")) {
-                LOG.info("Testing if url [{}] matches regex [{}]",
+                LOG.debug("Testing if url [{}] matches regex [{}]",
                         new Object[]{url, entry.getValue()});
                 assertContentEventuallyMatches(flags, url, 
TypeCoercions.coerce(entry.getValue(), String.class));
             } else if (Objects.equal(entry.getKey(), "bodyContains")) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b4e3ac7b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensor.java
----------------------------------------------------------------------
diff --git 
a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensor.java
 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensor.java
index ec070d1..3655501 100644
--- 
a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensor.java
+++ 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensor.java
@@ -21,6 +21,7 @@ package org.apache.brooklyn.test.framework;
 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.util.core.flags.SetFromFlag;
 
 /**

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b4e3ac7b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensorImpl.java
----------------------------------------------------------------------
diff --git 
a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensorImpl.java
 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensorImpl.java
index c2d6169..f8c96a0 100644
--- 
a/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensorImpl.java
+++ 
b/usage/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensorImpl.java
@@ -65,7 +65,7 @@ public class TestSensorImpl extends AbstractTest implements 
TestSensor {
             sensors().set(SERVICE_UP, true);
             ServiceStateLogic.setExpectedState(this, Lifecycle.RUNNING);
         } catch (Throwable t) {
-            LOG.info("Sensor [{}] test failed", sensor);
+            LOG.debug("Sensor [{}] test failed", sensor);
             sensors().set(SERVICE_UP, false);
             ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE);
             throw Exceptions.propagate(t);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b4e3ac7b/usage/test-framework/src/test/java/org/apache/brooklyn/test/framework/SimpleCommandImplTest.java
----------------------------------------------------------------------
diff --git 
a/usage/test-framework/src/test/java/org/apache/brooklyn/test/framework/SimpleCommandImplTest.java
 
b/usage/test-framework/src/test/java/org/apache/brooklyn/test/framework/SimpleCommandImplTest.java
new file mode 100644
index 0000000..fac54aa
--- /dev/null
+++ 
b/usage/test-framework/src/test/java/org/apache/brooklyn/test/framework/SimpleCommandImplTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.Predicate;
+import com.google.common.base.Strings;
+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.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import 
org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import javax.annotation.Nullable;
+import java.util.UUID;
+
+import static org.apache.brooklyn.test.framework.BaseTest.TARGET_ENTITY;
+import static org.apache.brooklyn.test.framework.SimpleCommand.DEFAULT_COMMAND;
+import static org.apache.brooklyn.test.framework.SimpleCommandTest.*;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SimpleCommandImplTest {
+    private static final Logger LOG = 
LoggerFactory.getLogger(SimpleCommandImplTest.class);
+
+    private static final String UP = "up";
+    private TestApplication app;
+    private ManagementContext managementContext;
+    private LocalhostMachineProvisioningLocation localhost;
+    private String testId;
+
+
+    @BeforeMethod
+    public void setUp() {
+
+        testId = UUID.randomUUID().toString();
+
+        app = TestApplication.Factory.newManagedInstanceForTests();
+        managementContext = app.getManagementContext();
+
+        localhost = managementContext.getLocationManager()
+            
.createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class)
+                .configure("name", testId));
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+
+    @Test
+    public void shouldInvokeCommand() {
+        TestEntity testEntity = 
app.createAndManageChild(EntitySpec.create(TestEntity.class));
+
+        SimpleCommandTest uptime = 
app.createAndManageChild(EntitySpec.create(SimpleCommandTest.class)
+            .configure(TARGET_ENTITY, testEntity)
+            .configure(DEFAULT_COMMAND, "uptime")
+            .configure(ASSERT_STATUS, ImmutableMap.of(EQUALS, 0))
+            .configure(ASSERT_OUT, ImmutableMap.of(CONTAINS, UP)));
+
+        app.start(ImmutableList.of(localhost));
+
+        assertThat(uptime.sensors().get(SERVICE_UP)).isTrue()
+            .withFailMessage("Service should be up");
+        
assertThat(ServiceStateLogic.getExpectedState(uptime)).isEqualTo(Lifecycle.RUNNING)
+            .withFailMessage("Service should be marked running");
+
+    }
+
+    @Test
+    public void shouldNotBeUpIfAssertionFails() {
+        TestEntity testEntity = 
app.createAndManageChild(EntitySpec.create(TestEntity.class));
+
+        SimpleCommandTest uptime = 
app.createAndManageChild(EntitySpec.create(SimpleCommandTest.class)
+            .configure(TARGET_ENTITY, testEntity)
+            .configure(DEFAULT_COMMAND, "uptime")
+            .configure(ASSERT_STATUS, ImmutableMap.of(EQUALS, 1)));
+
+        try {
+            app.start(ImmutableList.of(localhost));
+        } catch (Exception e) {
+            assertThat(e.getCause().getMessage().contains("exit code equals 
1"));
+        }
+
+        
assertThat(ServiceStateLogic.getExpectedState(uptime)).isEqualTo(Lifecycle.ON_FIRE)
+            .withFailMessage("Service should be marked on fire");
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b4e3ac7b/usage/test-framework/src/test/java/org/apache/brooklyn/test/framework/SimpleCommandScriptIntegrationTest.java
----------------------------------------------------------------------
diff --git 
a/usage/test-framework/src/test/java/org/apache/brooklyn/test/framework/SimpleCommandScriptIntegrationTest.java
 
b/usage/test-framework/src/test/java/org/apache/brooklyn/test/framework/SimpleCommandScriptIntegrationTest.java
new file mode 100644
index 0000000..e081cef
--- /dev/null
+++ 
b/usage/test-framework/src/test/java/org/apache/brooklyn/test/framework/SimpleCommandScriptIntegrationTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import 
org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
+import org.apache.brooklyn.test.http.TestHttpRequestHandler;
+import org.apache.brooklyn.test.http.TestHttpServer;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.exceptions.FatalRuntimeException;
+import org.apache.brooklyn.util.http.HttpAsserts;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.*;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Iterator;
+import java.util.UUID;
+
+import static org.apache.brooklyn.test.framework.BaseTest.TARGET_ENTITY;
+import static org.apache.brooklyn.test.framework.SimpleCommand.DEFAULT_COMMAND;
+import static org.apache.brooklyn.test.framework.SimpleCommandTest.*;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Test(groups = "Integration")
+public class SimpleCommandScriptIntegrationTest {
+    private static final Logger LOG = 
LoggerFactory.getLogger(SimpleCommandScriptIntegrationTest.class);
+
+    private static final String UP = "up";
+    private static final String SCRIPT_NAME = "script.sh";
+    private static final String TEXT = "hello world";
+    private TestApplication app;
+    private ManagementContext managementContext;
+    private LocalhostMachineProvisioningLocation localhost;
+    private TestHttpServer server;
+    private String testId;
+
+
+    @BeforeClass
+    public void setUpTests() {
+        server = initializeServer();
+    }
+
+    @AfterClass
+    public void tearDownTests() {
+        if (null != server) {
+            server.stop();
+        }
+    }
+
+    @BeforeMethod
+    public void setUp() {
+
+        testId = UUID.randomUUID().toString();
+
+        app = TestApplication.Factory.newManagedInstanceForTests();
+        managementContext = app.getManagementContext();
+
+        localhost = managementContext.getLocationManager()
+            
.createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class)
+                .configure("name", testId));
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+
+
+    private TestHttpServer initializeServerUnstarted() {
+        return new TestHttpServer()
+            .handler("/" + SCRIPT_NAME,
+                new TestHttpRequestHandler().response("#!/bin/sh\necho " + 
TEXT + "\n"));
+    }
+    private TestHttpServer initializeServer() {
+        return initializeServerUnstarted().start();
+    }
+
+
+
+    @Test
+    public void shouldInvokeScript() {
+        TestEntity testEntity = 
app.createAndManageChild(EntitySpec.create(TestEntity.class));
+
+        String testUrl = server.getUrl() + "/" + SCRIPT_NAME;
+        HttpAsserts.assertContentContainsText(testUrl, TEXT);
+
+        SimpleCommandTest uptime = 
app.createAndManageChild(EntitySpec.create(SimpleCommandTest.class)
+            .configure(TARGET_ENTITY, testEntity)
+            .configure(DOWNLOAD_URL, testUrl)
+            .configure(ASSERT_STATUS, ImmutableMap.of(EQUALS, 0))
+            .configure(ASSERT_OUT, ImmutableMap.of(CONTAINS, TEXT)));
+
+        app.start(ImmutableList.of(localhost));
+
+        assertThat(uptime.sensors().get(SERVICE_UP)).isTrue()
+            .withFailMessage("Service should be up");
+        
assertThat(ServiceStateLogic.getExpectedState(uptime)).isEqualTo(Lifecycle.RUNNING)
+            .withFailMessage("Service should be marked running");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b4e3ac7b/usage/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestEffectorTest.java
----------------------------------------------------------------------
diff --git 
a/usage/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestEffectorTest.java
 
b/usage/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestEffectorTest.java
index ab01eea..b6f3c4a 100644
--- 
a/usage/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestEffectorTest.java
+++ 
b/usage/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestEffectorTest.java
@@ -51,7 +51,8 @@ public class TestEffectorTest {
         app = TestApplication.Factory.newManagedInstanceForTests();
         managementContext = app.getManagementContext();
 
-        loc = 
managementContext.getLocationManager().createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class)
+        loc = managementContext.getLocationManager()
+                
.createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class)
                 .configure("name", testId));
 
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b4e3ac7b/usage/test-framework/src/test/resources/brooklyn/logback-appender-stdout.xml
----------------------------------------------------------------------
diff --git 
a/usage/test-framework/src/test/resources/brooklyn/logback-appender-stdout.xml 
b/usage/test-framework/src/test/resources/brooklyn/logback-appender-stdout.xml
new file mode 100644
index 0000000..f2515ad
--- /dev/null
+++ 
b/usage/test-framework/src/test/resources/brooklyn/logback-appender-stdout.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<included>
+
+    <!-- change settings in this file for (temporary!) debug purposes, e.g. 
change filter level to DEBUG below -->
+
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d %-5level %msg%n%xEx{0}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <level>INFO</level>
+        </filter>
+    </appender>
+
+    <root>
+        <appender-ref ref="STDOUT" />
+    </root>
+
+</included>
\ No newline at end of file

Reply via email to