You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by al...@apache.org on 2015/07/16 01:09:47 UTC

[03/12] incubator-brooklyn git commit: brooklyn-qa: add org.apache package prefix

brooklyn-qa: add org.apache package prefix


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

Branch: refs/heads/master
Commit: bd44bb8f18799d603308d19e98348c949ffa00a1
Parents: 0d7c48c
Author: Ciprian Ciubotariu <ch...@gmx.net>
Authored: Tue Jul 14 18:33:30 2015 +0300
Committer: Ciprian Ciubotariu <ch...@gmx.net>
Committed: Wed Jul 15 16:56:03 2015 +0300

----------------------------------------------------------------------
 .../qa/load/SimulatedJBoss7ServerImpl.java      | 240 --------------
 .../qa/load/SimulatedMySqlNodeImpl.java         | 183 -----------
 .../qa/load/SimulatedNginxControllerImpl.java   | 196 -----------
 .../brooklyn/qa/load/SimulatedTheeTierApp.java  | 140 --------
 .../java/brooklyn/qa/longevity/Monitor.java     | 261 ---------------
 .../brooklyn/qa/longevity/MonitorListener.java  |  35 --
 .../brooklyn/qa/longevity/MonitorPrefs.java     |  54 ---
 .../brooklyn/qa/longevity/MonitorUtils.java     | 329 -------------------
 .../brooklyn/qa/longevity/StatusRecorder.java   | 130 --------
 .../qa/load/SimulatedJBoss7ServerImpl.java      | 240 ++++++++++++++
 .../qa/load/SimulatedMySqlNodeImpl.java         | 183 +++++++++++
 .../qa/load/SimulatedNginxControllerImpl.java   | 196 +++++++++++
 .../brooklyn/qa/load/SimulatedTheeTierApp.java  | 140 ++++++++
 .../apache/brooklyn/qa/longevity/Monitor.java   | 261 +++++++++++++++
 .../brooklyn/qa/longevity/MonitorListener.java  |  35 ++
 .../brooklyn/qa/longevity/MonitorPrefs.java     |  54 +++
 .../brooklyn/qa/longevity/MonitorUtils.java     | 329 +++++++++++++++++++
 .../brooklyn/qa/longevity/StatusRecorder.java   | 130 ++++++++
 .../test/java/brooklyn/qa/load/LoadTest.java    | 243 --------------
 .../brooklyn/qa/longevity/MonitorUtilsTest.java | 165 ----------
 .../webcluster/SinusoidalLoadGenerator.java     |  90 -----
 .../qa/longevity/webcluster/WebClusterApp.java  | 101 ------
 .../org/apache/brooklyn/qa/load/LoadTest.java   | 244 ++++++++++++++
 .../brooklyn/qa/longevity/MonitorUtilsTest.java | 166 ++++++++++
 .../webcluster/SinusoidalLoadGenerator.java     |  90 +++++
 .../qa/longevity/webcluster/WebClusterApp.java  | 101 ++++++
 26 files changed, 2169 insertions(+), 2167 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/main/java/brooklyn/qa/load/SimulatedJBoss7ServerImpl.java
----------------------------------------------------------------------
diff --git a/usage/qa/src/main/java/brooklyn/qa/load/SimulatedJBoss7ServerImpl.java b/usage/qa/src/main/java/brooklyn/qa/load/SimulatedJBoss7ServerImpl.java
deleted file mode 100644
index 64f7aa5..0000000
--- a/usage/qa/src/main/java/brooklyn/qa/load/SimulatedJBoss7ServerImpl.java
+++ /dev/null
@@ -1,240 +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 brooklyn.qa.load;
-
-import static java.lang.String.format;
-
-import java.util.concurrent.Callable;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.enricher.Enrichers;
-import brooklyn.entity.basic.Attributes;
-import brooklyn.entity.webapp.jboss.JBoss7ServerImpl;
-import brooklyn.entity.webapp.jboss.JBoss7SshDriver;
-import brooklyn.event.feed.function.FunctionFeed;
-import brooklyn.event.feed.function.FunctionPollConfig;
-import brooklyn.event.feed.http.HttpFeed;
-import brooklyn.event.feed.http.HttpPollConfig;
-import brooklyn.event.feed.http.HttpValueFunctions;
-import brooklyn.location.access.BrooklynAccessUtils;
-import brooklyn.location.basic.SshMachineLocation;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.guava.Functionals;
-import brooklyn.util.os.Os;
-
-import com.google.common.net.HostAndPort;
-
-/**
- * For simulating various aspects of the JBoss 7 app-server entity.
- *  
- * The use-case for this is that the desired configuration is not always available for testing. 
- * For example, there may be insufficient resources to run 100s of JBoss app-servers, or one 
- * may be experimenting with possible configurations such as use of an external monitoring tool 
- * that is not yet available.
- * 
- * It is then possible to simulate aspects of the behaviour, for performance and load testing purposes. 
- * 
- * There is configuration for:
- * <ul>
- *   <li>{@code simulateEntity}
- *     <ul>
- *       <li>if true, no underlying entity will be started. Instead a sleep 100000 job will be run and monitored.
- *       <li>if false, the underlying entity (i.e. a JBoss app-server) will be started as normal.
- *     </ul>
- *   <li>{@code simulateExternalMonitoring}
- *     <ul>
- *       <li>if true, disables the default monitoring mechanism. Instead, a function will periodically execute 
- *           to set the entity's sensors (as though the values had been obtained from the external monitoring tool).
- *       <li>if false, then:
- *         <ul>
- *           <li>If {@code simulateEntity==true} it will execute comparable commands (e.g. execute a command of the same 
- *               size over ssh or do a comparable number of http GET requests).
- *           <li>If {@code simulateEntity==false} then normal monitoring will be done.
- *         </ul>
- *     </ul>
- *   <li>{@code skipSshOnStart}
- *     <ul>
- *       <li>If true (and if {@code simulateEntity==true}), then no ssh commands will be executed at deploy-time. 
- *           This is useful for speeding up load testing, to get to the desired number of entities.
- *           Should not be set to {@code true} if {@code simulateEntity==false}.
- *       <li>If false, the ssh commands will be executed (based on the value of {@code simulateEntity}.
- *     </ul>
- * </ul>
- */
-public class SimulatedJBoss7ServerImpl extends JBoss7ServerImpl {
-
-    public static final ConfigKey<Boolean> SIMULATE_ENTITY = SimulatedTheeTierApp.SIMULATE_ENTITY;
-    public static final ConfigKey<Boolean> SIMULATE_EXTERNAL_MONITORING = SimulatedTheeTierApp.SIMULATE_EXTERNAL_MONITORING;
-    public static final ConfigKey<Boolean> SKIP_SSH_ON_START = SimulatedTheeTierApp.SKIP_SSH_ON_START;
-    
-    private HttpFeed httpFeed;
-    private FunctionFeed functionFeed;
-    
-    @Override
-    public Class<?> getDriverInterface() {
-        return SimulatedJBoss7SshDriver.class;
-    }
-
-    @Override
-    protected void connectSensors() {
-        boolean simulateEntity = getConfig(SIMULATE_ENTITY);
-        boolean simulateExternalMonitoring = getConfig(SIMULATE_EXTERNAL_MONITORING);
-
-        if (!simulateEntity && !simulateExternalMonitoring) {
-            super.connectSensors();
-            return;
-        }
-        
-        HostAndPort hp = BrooklynAccessUtils.getBrooklynAccessibleAddress(this,
-                getAttribute(MANAGEMENT_HTTP_PORT) + getConfig(PORT_INCREMENT));
-
-        String managementUri = String.format("http://%s:%s/management/subsystem/web/connector/http/read-resource",
-                hp.getHostText(), hp.getPort());
-        setAttribute(MANAGEMENT_URL, managementUri);
-
-        if (simulateExternalMonitoring) {
-            // TODO What would set this normally, if not doing connectServiceUpIsRunning?
-            setAttribute(SERVICE_PROCESS_IS_RUNNING, true);
-        } else {
-            // if simulating entity, then simulate work of periodic HTTP request; TODO but not parsing JSON response
-            String uriToPoll = (simulateEntity) ? "http://localhost:8081" : managementUri;
-            
-            httpFeed = HttpFeed.builder()
-                    .entity(this)
-                    .period(200)
-                    .baseUri(uriToPoll)
-                    .credentials(getConfig(MANAGEMENT_USER), getConfig(MANAGEMENT_PASSWORD))
-                    .poll(new HttpPollConfig<Integer>(MANAGEMENT_STATUS)
-                            .onSuccess(HttpValueFunctions.responseCode()))
-                    .build();
-            
-            // Polls over ssh for whether process is running
-            connectServiceUpIsRunning();
-        }
-        
-        functionFeed = FunctionFeed.builder()
-                .entity(this)
-                .period(5000)
-                .poll(new FunctionPollConfig<Boolean,Boolean>(MANAGEMENT_URL_UP)
-                        .callable(new Callable<Boolean>() {
-                            private int counter = 0;
-                            public Boolean call() {
-                                setAttribute(REQUEST_COUNT, (counter++ % 100));
-                                setAttribute(ERROR_COUNT, (counter++ % 100));
-                                setAttribute(TOTAL_PROCESSING_TIME, (counter++ % 100));
-                                setAttribute(MAX_PROCESSING_TIME, (counter++ % 100));
-                                setAttribute(BYTES_RECEIVED, (long) (counter++ % 100));
-                                setAttribute(BYTES_SENT, (long) (counter++ % 100));
-                                return true;
-                            }}))
-                .build();
-        
-        addEnricher(Enrichers.builder().updatingMap(Attributes.SERVICE_NOT_UP_INDICATORS)
-                .from(MANAGEMENT_URL_UP)
-                .computing(Functionals.ifNotEquals(true).value("Management URL not reachable") )
-                .build());
-    }
-
-    @Override
-    protected void disconnectSensors() {
-        super.disconnectSensors();
-        if (httpFeed != null) httpFeed.stop();
-        if (functionFeed != null) functionFeed.stop();
-    }
-    
-    public static class SimulatedJBoss7SshDriver extends JBoss7SshDriver {
-        public SimulatedJBoss7SshDriver(SimulatedJBoss7ServerImpl entity, SshMachineLocation machine) {
-            super(entity, machine);
-        }
-        
-        @Override
-        public boolean isRunning() {
-            if (entity.getConfig(SKIP_SSH_ON_START)) {
-                return true;
-            } else {
-                return super.isRunning();
-            }
-        }
-
-        @Override
-        public void install() {
-            if (entity.getConfig(SKIP_SSH_ON_START)) {
-                // no-op
-            } else {
-                super.install();
-            }
-        }
-        
-        @Override
-        public void customize() {
-            if (entity.getConfig(SKIP_SSH_ON_START)) {
-                // no-op
-            } else {
-                super.customize();
-            }
-        }
-        
-        @Override
-        public void launch() {
-            if (!entity.getConfig(SIMULATE_ENTITY)) {
-                super.launch();
-                return;
-            }
-            
-            // We wait for evidence of JBoss running because, using
-            // brooklyn.ssh.config.tool.class=brooklyn.util.internal.ssh.cli.SshCliTool,
-            // we saw the ssh session return before the JBoss process was fully running
-            // so the process failed to start.
-            String pidFile = Os.mergePathsUnix(getRunDir(), PID_FILENAME);
-
-            if (entity.getConfig(SKIP_SSH_ON_START)) {
-                // minimal ssh, so that isRunning will subsequently work
-                newScript(MutableMap.of("usePidFile", pidFile), LAUNCHING)
-                        .body.append(
-                                format("nohup sleep 100000 > %s/console 2>&1 < /dev/null &", getRunDir()))
-                        .execute();
-            } else {
-                newScript(MutableMap.of(USE_PID_FILE, false), LAUNCHING)
-                        .body.append(
-                                "export LAUNCH_JBOSS_IN_BACKGROUND=true",
-                                format("export JBOSS_HOME=%s", getExpandedInstallDir()),
-                                format("export JBOSS_PIDFILE=%s/%s", getRunDir(), PID_FILENAME),
-                                format("echo skipping exec of %s/bin/%s.sh ", getExpandedInstallDir(), SERVER_TYPE) +
-                                        format("--server-config %s ", CONFIG_FILE) +
-                                        format("-Djboss.server.base.dir=%s/%s ", getRunDir(), SERVER_TYPE) +
-                                        format("\"-Djboss.server.base.url=file://%s/%s\" ", getRunDir(), SERVER_TYPE) +
-                                        "-Djava.net.preferIPv4Stack=true " +
-                                        "-Djava.net.preferIPv6Addresses=false " +
-                                        format(" >> %s/console 2>&1 </dev/null &", getRunDir()),
-                                format("nohup sleep 100000 > %s/console 2>&1 < /dev/null &", getRunDir()),
-                                format("echo $! > "+pidFile),
-                                format("echo starting > %s/console", getRunDir()),
-                                "for i in {1..10}\n" +
-                                        "do\n" +
-                                        "    grep -i 'starting' "+getRunDir()+"/console && exit\n" +
-                                        "    sleep 1\n" +
-                                        "done\n" +
-                                        "echo \"Couldn't determine if process is running (console output does not contain 'starting'); continuing but may subsequently fail\""
-    
-                            )
-                        .execute();
-            }
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/main/java/brooklyn/qa/load/SimulatedMySqlNodeImpl.java
----------------------------------------------------------------------
diff --git a/usage/qa/src/main/java/brooklyn/qa/load/SimulatedMySqlNodeImpl.java b/usage/qa/src/main/java/brooklyn/qa/load/SimulatedMySqlNodeImpl.java
deleted file mode 100644
index bad437a..0000000
--- a/usage/qa/src/main/java/brooklyn/qa/load/SimulatedMySqlNodeImpl.java
+++ /dev/null
@@ -1,183 +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 brooklyn.qa.load;
-
-import static java.lang.String.format;
-
-import java.util.concurrent.Callable;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.basic.AbstractSoftwareProcessSshDriver;
-import brooklyn.entity.database.mysql.MySqlNode;
-import brooklyn.entity.database.mysql.MySqlNodeImpl;
-import brooklyn.entity.database.mysql.MySqlSshDriver;
-import brooklyn.entity.software.SshEffectorTasks;
-import brooklyn.event.feed.function.FunctionFeed;
-import brooklyn.event.feed.function.FunctionPollConfig;
-import brooklyn.location.basic.SshMachineLocation;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.task.DynamicTasks;
-import brooklyn.util.task.system.ProcessTaskWrapper;
-import brooklyn.util.time.CountdownTimer;
-import brooklyn.util.time.Duration;
-
-/**
- * @see SimulatedJBoss7ServerImpl for description of purpose and configuration options.
- */
-public class SimulatedMySqlNodeImpl extends MySqlNodeImpl {
-
-    public static final ConfigKey<Boolean> SIMULATE_ENTITY = SimulatedTheeTierApp.SIMULATE_ENTITY;
-    public static final ConfigKey<Boolean> SIMULATE_EXTERNAL_MONITORING = SimulatedTheeTierApp.SIMULATE_EXTERNAL_MONITORING;
-    public static final ConfigKey<Boolean> SKIP_SSH_ON_START = SimulatedTheeTierApp.SKIP_SSH_ON_START;
-    
-    private FunctionFeed feed;
-    
-    @Override
-    public Class<?> getDriverInterface() {
-        return SimulatedMySqlSshDriver.class;
-    }
-
-    @Override
-    protected void connectSensors() {
-        boolean simulateExternalMonitoring = getConfig(SIMULATE_EXTERNAL_MONITORING);
-        if (simulateExternalMonitoring) {
-            setAttribute(DATASTORE_URL, String.format("mysql://%s:%s/", getAttribute(HOSTNAME), getAttribute(MYSQL_PORT)));
-            
-            feed = FunctionFeed.builder()
-                    .entity(this)
-                    .period(Duration.FIVE_SECONDS)
-                    .poll(new FunctionPollConfig<Boolean, Boolean>(SERVICE_UP)
-                            .callable(new Callable<Boolean>() {
-                                private int counter = 0;
-                                public Boolean call() {
-                                    setAttribute(QUERIES_PER_SECOND_FROM_MYSQL, (double)(counter++ % 100));
-                                    return true;
-                                }})
-                            .setOnFailureOrException(false))
-                    .build();
-        } else {
-            super.connectSensors();
-        }
-    }
-
-    public static class SimulatedMySqlSshDriver extends MySqlSshDriver {
-
-        private int counter = 0;
-        
-        public SimulatedMySqlSshDriver(SimulatedMySqlNodeImpl entity, SshMachineLocation machine) {
-            super(entity, machine);
-        }
-        
-        // simulate metrics, for if using ssh polling
-        @Override
-        public String getStatusCmd() {
-            if (entity.getConfig(SIMULATE_ENTITY)) {
-                return "echo Uptime: 2427  Threads: 1  Questions: 581  Slow queries: 0  Opens: 53  Flush tables: 1  Open tables: 35  Queries per second avg: "+(counter++ % 100);
-            } else {
-                return super.getStatusCmd();
-            }
-        }
-
-        @Override
-        public void install() {
-            if (entity.getConfig(SKIP_SSH_ON_START)) {
-                // no-op
-            } else {
-                super.install();
-            }
-        }
-        
-        // Not applying creation-script etc, as that requires launching msyqld (so would not scale for single-machine testing)
-        // This is a copy of super.customize, but with the mysqladmin-exec disabled
-        @Override
-        public void customize() {
-            if (!entity.getConfig(SIMULATE_ENTITY)) {
-                super.customize();
-                return;
-            } else if (entity.getConfig(SKIP_SSH_ON_START)) {
-                // no-op
-            } else {
-                copyDatabaseConfigScript();
-    
-                newScript(CUSTOMIZING)
-                    .updateTaskAndFailOnNonZeroResultCode()
-                    .body.append(
-                        "chmod 600 "+getConfigFile(),
-                        getBaseDir()+"/scripts/mysql_install_db "+
-                            "--basedir="+getBaseDir()+" --datadir="+getDataDir()+" "+
-                            "--defaults-file="+getConfigFile())
-                    .execute();
-    
-                // launch, then we will configure it
-                launch();
-    
-                CountdownTimer timer = Duration.seconds(20).countdownTimer();
-                boolean hasCreationScript = copyDatabaseCreationScript();
-                timer.waitForExpiryUnchecked();
-    
-                // DELIBERATELY SKIPPED FOR SCALABILITY TESTING ON SINGLE MACHINE
-                DynamicTasks.queue(
-                    SshEffectorTasks.ssh(
-                        "cd "+getRunDir(),
-                        "echo skipping exec of "+getBaseDir()+"/bin/mysqladmin --defaults-file="+getConfigFile()+" --password= password "+getPassword()
-                    ).summary("setting password"));
-    
-                if (hasCreationScript)
-                    executeScriptFromInstalledFileAsync("creation-script.sql");
-    
-                // not sure necessary to stop then subsequently launch, but seems safest
-                // (if skipping, use a flag in launch to indicate we've just launched it)
-                stop();
-            }
-        }
-
-        @Override
-        public void launch() {
-            if (!entity.getConfig(SIMULATE_ENTITY)) {
-                super.launch();
-                return;
-            }
-            
-            entity.setAttribute(MySqlNode.PID_FILE, getRunDir() + "/" + AbstractSoftwareProcessSshDriver.PID_FILENAME);
-            
-            if (entity.getConfig(SKIP_SSH_ON_START)) {
-                // minimal ssh, so that isRunning will subsequently work
-                newScript(MutableMap.of("usePidFile", true), LAUNCHING)
-                        .body.append(
-                                format("nohup sleep 100000 > %s 2>&1 < /dev/null &", getLogFile()))
-                        .execute();
-            } else {
-                newScript(MutableMap.of("usePidFile", true), LAUNCHING)
-                    .updateTaskAndFailOnNonZeroResultCode()
-                    .body.append(format("echo skipping normal exec of nohup %s/bin/mysqld --defaults-file=%s --user=`whoami` > %s 2>&1 < /dev/null &", getBaseDir(), getConfigFile(), getLogFile()))
-                    .body.append(format("nohup sleep 100000 > %s 2>&1 < /dev/null &", getLogFile()))
-                    .execute();
-            }
-        }
-
-        @Override
-        public ProcessTaskWrapper<Integer> executeScriptFromInstalledFileAsync(String filenameAlreadyInstalledAtServer) {
-            return DynamicTasks.queue(
-                    SshEffectorTasks.ssh(
-                                    "cd "+getRunDir(),
-                                    "echo skipping exec of "+getBaseDir()+"/bin/mysql --defaults-file="+getConfigFile()+" < "+filenameAlreadyInstalledAtServer)
-                            .summary("executing datastore script "+filenameAlreadyInstalledAtServer));
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/main/java/brooklyn/qa/load/SimulatedNginxControllerImpl.java
----------------------------------------------------------------------
diff --git a/usage/qa/src/main/java/brooklyn/qa/load/SimulatedNginxControllerImpl.java b/usage/qa/src/main/java/brooklyn/qa/load/SimulatedNginxControllerImpl.java
deleted file mode 100644
index e5c40c2..0000000
--- a/usage/qa/src/main/java/brooklyn/qa/load/SimulatedNginxControllerImpl.java
+++ /dev/null
@@ -1,196 +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 brooklyn.qa.load;
-
-import static java.lang.String.format;
-
-import java.net.URI;
-import java.util.Collection;
-import java.util.concurrent.Callable;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.Group;
-import brooklyn.entity.proxy.nginx.NginxControllerImpl;
-import brooklyn.entity.proxy.nginx.NginxSshDriver;
-import brooklyn.entity.proxy.nginx.UrlMapping;
-import brooklyn.event.SensorEvent;
-import brooklyn.event.SensorEventListener;
-import brooklyn.event.feed.ConfigToAttributes;
-import brooklyn.event.feed.function.FunctionFeed;
-import brooklyn.event.feed.function.FunctionPollConfig;
-import brooklyn.event.feed.http.HttpFeed;
-import brooklyn.event.feed.http.HttpPollConfig;
-import brooklyn.location.basic.SshMachineLocation;
-import brooklyn.policy.PolicySpec;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.net.Networking;
-
-import com.google.common.base.Functions;
-
-/**
- * @see SimulatedJBoss7ServerImpl for description of purpose and configuration options.
- */
-public class SimulatedNginxControllerImpl extends NginxControllerImpl {
-
-    public static final ConfigKey<Boolean> SIMULATE_ENTITY = SimulatedTheeTierApp.SIMULATE_ENTITY;
-    public static final ConfigKey<Boolean> SIMULATE_EXTERNAL_MONITORING = SimulatedTheeTierApp.SIMULATE_EXTERNAL_MONITORING;
-    public static final ConfigKey<Boolean> SKIP_SSH_ON_START = SimulatedTheeTierApp.SKIP_SSH_ON_START;
-    
-    private HttpFeed httpFeed;
-    private FunctionFeed functionFeed;
-    
-    @Override
-    public Class<?> getDriverInterface() {
-        return SimulatedNginxSshDriver.class;
-    }
-
-    @Override
-    public void connectSensors() {
-        boolean simulateEntity = getConfig(SIMULATE_ENTITY);
-        boolean simulateExternalMonitoring = getConfig(SIMULATE_EXTERNAL_MONITORING);
-
-        if (!simulateEntity && !simulateExternalMonitoring) {
-            super.connectSensors();
-            return;
-        }
-
-        // From AbstractController.connectSensors
-        if (getUrl()==null) {
-            setAttribute(MAIN_URI, URI.create(inferUrl()));
-            setAttribute(ROOT_URL, inferUrl());
-        }
-        addServerPoolMemberTrackingPolicy();
-
-        // From NginxController.connectSensors
-        ConfigToAttributes.apply(this);
-
-        if (!simulateExternalMonitoring) {
-            // if simulating entity, then simulate work of periodic HTTP request; TODO but not parsing JSON response
-            String uriToPoll = (simulateEntity) ? "http://localhost:8081" : getAttribute(MAIN_URI).toString();
-            
-            httpFeed = HttpFeed.builder()
-                    .entity(this)
-                    .period(getConfig(HTTP_POLL_PERIOD))
-                    .baseUri(uriToPoll)
-                    .poll(new HttpPollConfig<Boolean>(SERVICE_UP)
-                            .onSuccess(Functions.constant(true))
-                            .onFailureOrException(Functions.constant(true)))
-                    .build();
-        }
-        
-        functionFeed = FunctionFeed.builder()
-                .entity(this)
-                .period(getConfig(HTTP_POLL_PERIOD))
-                .poll(new FunctionPollConfig<Boolean,Boolean>(SERVICE_UP)
-                        .callable(new Callable<Boolean>() {
-                            public Boolean call() {
-                                return true;
-                            }}))
-                .build();
-
-        // Can guarantee that parent/managementContext has been set
-        Group urlMappings = getConfig(URL_MAPPINGS);
-        if (urlMappings != null) {
-            // Listen to the targets of each url-mapping changing
-            subscribeToMembers(urlMappings, UrlMapping.TARGET_ADDRESSES, new SensorEventListener<Collection<String>>() {
-                    @Override public void onEvent(SensorEvent<Collection<String>> event) {
-                        updateNeeded();
-                    }
-                });
-
-            // Listen to url-mappings being added and removed
-            urlMappingsMemberTrackerPolicy = addPolicy(PolicySpec.create(UrlMappingsMemberTrackerPolicy.class)
-                    .configure("group", urlMappings));
-        }
-    }
-
-    @Override
-    protected void disconnectSensors() {
-        super.disconnectSensors();
-        if (httpFeed != null) httpFeed.stop();
-        if (functionFeed != null) functionFeed.stop();
-    }
-
-    public static class SimulatedNginxSshDriver extends NginxSshDriver {
-        public SimulatedNginxSshDriver(SimulatedNginxControllerImpl entity, SshMachineLocation machine) {
-            super(entity, machine);
-        }
-
-        @Override
-        public void install() {
-            if (entity.getConfig(SKIP_SSH_ON_START)) {
-                // no-op
-            } else {
-                super.install();
-            }
-        }
-        
-        @Override
-        public void customize() {
-            if (entity.getConfig(SKIP_SSH_ON_START)) {
-                // no-op
-            } else {
-                super.customize();
-            }
-        }
-        
-        @Override
-        public void launch() {
-            if (!entity.getConfig(SIMULATE_ENTITY)) {
-                super.launch();
-                return;
-            }
-            
-            Networking.checkPortsValid(MutableMap.of("httpPort", getPort()));
-
-            if (entity.getConfig(SKIP_SSH_ON_START)) {
-                // minimal ssh, so that isRunning will subsequently work
-                newScript(MutableMap.of("usePidFile", getPidFile()), LAUNCHING)
-                        .body.append(
-                                format("mkdir -p %s/logs", getRunDir()),
-                                format("nohup sleep 100000 > %s 2>&1 < /dev/null &", getLogFileLocation()))
-                        .execute();
-            } else {
-                newScript(MutableMap.of("usePidFile", false), LAUNCHING)
-                        .body.append(
-                                format("cd %s", getRunDir()),
-                                "echo skipping exec of requireExecutable ./sbin/nginx",
-                                sudoBashCIfPrivilegedPort(getPort(), format(
-                                        "echo skipping exec of nohup ./sbin/nginx -p %s/ -c conf/server.conf > %s 2>&1 &", getRunDir(), getLogFileLocation())),
-                                format("nohup sleep 100000 > %s 2>&1 < /dev/null &", getLogFileLocation()),
-                                format("echo $! > "+getPidFile()),
-                                format("for i in {1..10}\n" +
-                                        "do\n" +
-                                        "    test -f %1$s && ps -p `cat %1$s` && exit\n" +
-                                        "    sleep 1\n" +
-                                        "done\n" +
-                                        "echo \"No explicit error launching nginx but couldn't find process by pid; continuing but may subsequently fail\"\n" +
-                                        "cat %2$s | tee /dev/stderr",
-                                        getPidFile(), getLogFileLocation()))
-                        .execute();
-            }
-        }
-
-        // Use pid file, because just simulating the run of nginx
-        @Override
-        public void stop() {
-            newScript(MutableMap.of("usePidFile", getPidFile()), STOPPING).execute();
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/main/java/brooklyn/qa/load/SimulatedTheeTierApp.java
----------------------------------------------------------------------
diff --git a/usage/qa/src/main/java/brooklyn/qa/load/SimulatedTheeTierApp.java b/usage/qa/src/main/java/brooklyn/qa/load/SimulatedTheeTierApp.java
deleted file mode 100644
index 6f95209..0000000
--- a/usage/qa/src/main/java/brooklyn/qa/load/SimulatedTheeTierApp.java
+++ /dev/null
@@ -1,140 +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 brooklyn.qa.load;
-
-import static brooklyn.event.basic.DependentConfiguration.attributeWhenReady;
-import static brooklyn.event.basic.DependentConfiguration.formatString;
-
-import java.util.Collection;
-import java.util.List;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.enricher.Enrichers;
-import brooklyn.enricher.HttpLatencyDetector;
-import brooklyn.entity.basic.AbstractApplication;
-import brooklyn.entity.basic.Attributes;
-import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.entity.basic.Entities;
-import brooklyn.entity.basic.StartableApplication;
-import brooklyn.entity.database.mysql.MySqlNode;
-import brooklyn.entity.group.DynamicCluster;
-import brooklyn.entity.java.JavaEntityMethods;
-import brooklyn.entity.proxy.nginx.NginxController;
-import brooklyn.entity.proxying.EntitySpec;
-import brooklyn.entity.trait.Startable;
-import brooklyn.entity.webapp.ControlledDynamicWebAppCluster;
-import brooklyn.entity.webapp.DynamicWebAppCluster;
-import brooklyn.entity.webapp.JavaWebAppService;
-import brooklyn.entity.webapp.WebAppService;
-import brooklyn.entity.webapp.WebAppServiceConstants;
-import brooklyn.entity.webapp.jboss.JBoss7Server;
-import brooklyn.launcher.BrooklynLauncher;
-import brooklyn.location.basic.PortRanges;
-import brooklyn.policy.autoscaling.AutoScalerPolicy;
-import brooklyn.util.CommandLineUtil;
-import brooklyn.util.collections.MutableSet;
-
-import com.google.common.base.Function;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
-
-/**
- * A 3-tier app where all components are just "simulated" - they don't actually run 
- * real app-servers or databases, instead just executing a "sleep" command to simulate 
- * the running process.
- * 
- * This is useful for load testing, where we want to test the performance of Brooklyn
- * rather than the ability to host many running app-servers.
- * 
- * The app is based on WebClusterDatabaseExampleApp
- * 
- * @see SimulatedJBoss7ServerImpl for description of purpose and configuration options.
- */
-public class SimulatedTheeTierApp extends AbstractApplication {
-
-    public static final ConfigKey<Boolean> SIMULATE_ENTITY = ConfigKeys.newBooleanConfigKey("simulateEntity", "", true);
-    
-    public static final ConfigKey<Boolean> SIMULATE_EXTERNAL_MONITORING = ConfigKeys.newBooleanConfigKey("simulateExternalMonitoring", "", true);
-
-    public static final ConfigKey<Boolean> SKIP_SSH_ON_START = ConfigKeys.newBooleanConfigKey("skipSshOnStart", "", false);
-
-    public static final String WAR_PATH = "classpath://hello-world.war";
-    public static final String DB_TABLE = "visitors";
-    public static final String DB_USERNAME = "brooklyn";
-    public static final String DB_PASSWORD = "br00k11n";
-    public static final boolean USE_HTTPS = false;
-    
-    @Override
-    public void init() {
-        MySqlNode mysql = addChild(
-                EntitySpec.create(MySqlNode.class)
-                .impl(SimulatedMySqlNodeImpl.class));
-
-        ControlledDynamicWebAppCluster web = addChild(
-                EntitySpec.create(ControlledDynamicWebAppCluster.class)
-                .configure(ControlledDynamicWebAppCluster.MEMBER_SPEC, EntitySpec.create(JBoss7Server.class).impl(SimulatedJBoss7ServerImpl.class))
-                .configure(ControlledDynamicWebAppCluster.CONTROLLER_SPEC, EntitySpec.create(NginxController.class).impl(SimulatedNginxControllerImpl.class))
-                        .configure(WebAppService.HTTP_PORT, PortRanges.fromString("8080+"))
-                        .configure(JavaWebAppService.ROOT_WAR, WAR_PATH)
-                        .configure(JavaEntityMethods.javaSysProp("brooklyn.example.db.url"), 
-                                formatString("jdbc:%s%s?user=%s\\&password=%s", 
-                                        attributeWhenReady(mysql, MySqlNode.DATASTORE_URL), DB_TABLE, DB_USERNAME, DB_PASSWORD))
-                        .configure(DynamicCluster.INITIAL_SIZE, 2)
-                        .configure(WebAppService.ENABLED_PROTOCOLS, ImmutableSet.of(USE_HTTPS ? "https" : "http")) );
-
-        web.getCluster().addPolicy(AutoScalerPolicy.builder().
-                metric(DynamicWebAppCluster.REQUESTS_PER_SECOND_IN_WINDOW_PER_NODE).
-                metricRange(10, 100).
-                sizeRange(2, 5).
-                build());
-
-        addEnricher(Enrichers.builder()
-                .propagating(Attributes.MAIN_URI, WebAppServiceConstants.ROOT_URL,
-                        DynamicWebAppCluster.REQUESTS_PER_SECOND_IN_WINDOW,
-                        HttpLatencyDetector.REQUEST_LATENCY_IN_SECONDS_IN_WINDOW)
-                .from(web)
-                .build());
-        
-        addEnricher(Enrichers.builder()
-                .aggregating(Startable.SERVICE_UP)
-                .publishing(Startable.SERVICE_UP)
-                .fromHardcodedProducers(ImmutableList.of(web, mysql))
-                .computing(new Function<Collection<Boolean>, Boolean>() {
-                    @Override public Boolean apply(Collection<Boolean> input) {
-                        return input != null && input.size() == 2 && MutableSet.copyOf(input).equals(ImmutableSet.of(true));
-                    }})
-                .build());
-    }
-    
-    public static void main(String[] argv) {
-        List<String> args = Lists.newArrayList(argv);
-        String port =  CommandLineUtil.getCommandLineOption(args, "--port", "8081+");
-        String location = CommandLineUtil.getCommandLineOption(args, "--location", "localhost");
-
-        BrooklynLauncher launcher = BrooklynLauncher.newInstance()
-                 .application(EntitySpec.create(StartableApplication.class, SimulatedTheeTierApp.class)
-                         .displayName("Brooklyn WebApp Cluster with Database example"))
-                 .webconsolePort(port)
-                 .location(location)
-                 .start();
-             
-        Entities.dumpInfo(launcher.getApplications());
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/main/java/brooklyn/qa/longevity/Monitor.java
----------------------------------------------------------------------
diff --git a/usage/qa/src/main/java/brooklyn/qa/longevity/Monitor.java b/usage/qa/src/main/java/brooklyn/qa/longevity/Monitor.java
deleted file mode 100644
index 36e9f55..0000000
--- a/usage/qa/src/main/java/brooklyn/qa/longevity/Monitor.java
+++ /dev/null
@@ -1,261 +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 brooklyn.qa.longevity;
-
-import static brooklyn.qa.longevity.StatusRecorder.Factory.chain;
-import static brooklyn.qa.longevity.StatusRecorder.Factory.noop;
-import static brooklyn.qa.longevity.StatusRecorder.Factory.toFile;
-import static brooklyn.qa.longevity.StatusRecorder.Factory.toLog;
-import static java.util.concurrent.TimeUnit.SECONDS;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.URL;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-import joptsimple.OptionParser;
-import joptsimple.OptionSet;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.util.collections.TimeWindowedList;
-import brooklyn.util.collections.TimestampedValue;
-import brooklyn.util.time.Duration;
-
-import com.google.common.base.Charsets;
-import com.google.common.base.Throwables;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Range;
-import com.google.common.io.Files;
-
-public class Monitor {
-
-    private static final Logger LOG = LoggerFactory.getLogger(Monitor.class);
-    
-    private static final int checkPeriodMs = 1000;
-
-    private static final OptionParser parser = new OptionParser() {
-        {
-            acceptsAll(ImmutableList.of("help", "?", "h"), "show help");
-            accepts("webUrl", "Web-app url")
-                    .withRequiredArg().ofType(URL.class);
-            accepts("brooklynPid", "Brooklyn pid")
-                    .withRequiredArg().ofType(Integer.class);
-            accepts("logFile", "Brooklyn log file")
-                    .withRequiredArg().ofType(File.class);
-            accepts("logGrep", "Grep in log file (defaults to 'SEVERE|ERROR|WARN|Exception|Error'")
-                    .withRequiredArg().ofType(String.class);
-            accepts("logGrepExclusionsFile", "File of expressions to be ignored in log file")
-                    .withRequiredArg().ofType(File.class);
-            accepts("webProcesses", "Name (for `ps ax | grep` of web-processes")
-                    .withRequiredArg().ofType(String.class);
-            accepts("numWebProcesses", "Number of web-processes expected (e.g. 1 or 1-3)")
-                    .withRequiredArg().ofType(String.class);
-            accepts("webProcessesCyclingPeriod", "The period (in seconds) for cycling through the range of numWebProcesses")
-                    .withRequiredArg().ofType(Integer.class);
-            accepts("outFile", "File to write monitor status info")
-                    .withRequiredArg().ofType(File.class);
-            accepts("abortOnError", "Exit the JVM on error, with exit code 1")
-                    .withRequiredArg().ofType(Boolean.class);
-        }
-    };
-
-    public static void main(String[] argv) throws InterruptedException, IOException {
-        OptionSet options = parse(argv);
-
-        if (options == null || options.has("help")) {
-            parser.printHelpOn(System.out);
-            System.exit(0);
-        }
-
-        MonitorPrefs prefs = new MonitorPrefs();
-        prefs.webUrl = options.hasArgument("webUrl") ? (URL) options.valueOf("webUrl") : null;
-        prefs.brooklynPid = options.hasArgument("brooklynPid") ? (Integer) options.valueOf("brooklynPid") : -1;
-        prefs.logFile = options.hasArgument("logFile") ? (File) options.valueOf("logFile") : null;
-        prefs.logGrep = options.hasArgument("logGrep") ? (String) options.valueOf("logGrep") : "SEVERE|ERROR|WARN|Exception|Error";
-        prefs.logGrepExclusionsFile = options.hasArgument("logGrepExclusionsFile") ? (File) options.valueOf("logGrepExclusionsFile") : null;
-        prefs.webProcessesRegex = options.hasArgument("webProcesses") ? (String) options.valueOf("webProcesses") : null;
-        prefs.numWebProcesses = options.hasArgument("numWebProcesses") ? parseRange((String) options.valueOf("numWebProcesses")) : null;
-        prefs.webProcessesCyclingPeriod = options.hasArgument("webProcessesCyclingPeriod") ? (Integer) options.valueOf("webProcessesCyclingPeriod") : -1;
-        prefs.outFile = options.hasArgument("outFile") ? (File) options.valueOf("outFile") : null;
-        prefs.abortOnError = options.hasArgument("abortOnError") ? (Boolean) options.valueOf("abortOnError") : false;
-        Monitor main = new Monitor(prefs, MonitorListener.NOOP);
-        main.start();
-    }
-
-    private static Range<Integer> parseRange(String range) {
-        if (range.contains("-")) {
-            String[] parts = range.split("-");
-            return Range.closed(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]));
-        } else {
-            return Range.singleton(Integer.parseInt(range));
-        }
-    }
-    
-    private static OptionSet parse(String...argv) {
-        try {
-            return parser.parse(argv);
-        } catch (Exception e) {
-            System.out.println("Error in parsing options: " + e.getMessage());
-            return null;
-        }
-    }
-
-    private final MonitorPrefs prefs;
-    private final StatusRecorder recorder;
-    private final MonitorListener listener;
-    
-    public Monitor(MonitorPrefs prefs, MonitorListener listener) {
-        this.prefs = prefs;
-        this.listener = listener;
-        this.recorder = chain(toLog(LOG), (prefs.outFile != null ? toFile(prefs.outFile) : noop()));
-    }
-
-    private void start() throws IOException {
-        LOG.info("Monitoring: "+prefs);
-        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
-
-        final AtomicReference<List<String>> previousLogLines = new AtomicReference<List<String>>(Collections.<String>emptyList());
-        final TimeWindowedList<Integer> numWebProcessesHistory = new TimeWindowedList<Integer>(
-                ImmutableMap.of("timePeriod", Duration.seconds(prefs.webProcessesCyclingPeriod), "minExpiredVals", 1));
-        final Set<String> logGrepExclusions = ImmutableSet.copyOf(Files.readLines(prefs.logGrepExclusionsFile, Charsets.UTF_8));
-        
-        executor.scheduleAtFixedRate(new Runnable() {
-                @Override public void run() {
-                    StatusRecorder.Record record = new StatusRecorder.Record();
-                    StringBuilder failureMsg = new StringBuilder();
-                    try {
-                        if (prefs.brooklynPid > 0) {
-                            boolean pidRunning = MonitorUtils.isPidRunning(prefs.brooklynPid, "java");
-                            MonitorUtils.MemoryUsage memoryUsage = MonitorUtils.getMemoryUsage(prefs.brooklynPid, ".*brooklyn.*", 1000);
-                            record.put("pidRunning", pidRunning);
-                            record.put("totalMemoryBytes", memoryUsage.getTotalMemoryBytes());
-                            record.put("totalMemoryInstances", memoryUsage.getTotalInstances());
-                            record.put("instanceCounts", memoryUsage.getInstanceCounts());
-                            
-                            if (!pidRunning) {
-                                failureMsg.append("pid "+prefs.brooklynPid+" is not running"+"\n");
-                            }
-                        }
-                        if (prefs.webUrl != null) {
-                            boolean webUrlUp = MonitorUtils.isUrlUp(prefs.webUrl);
-                            record.put("webUrlUp", webUrlUp);
-                            
-                            if (!webUrlUp) {
-                                failureMsg.append("web URL "+prefs.webUrl+" is not available"+"\n");
-                            }
-                        }
-                        if (prefs.logFile != null) {
-                            List<String> logLines = MonitorUtils.searchLog(prefs.logFile, prefs.logGrep, logGrepExclusions);
-                            List<String> newLogLines = getAdditions(previousLogLines.get(), logLines);
-                            previousLogLines.set(logLines);
-                            record.put("logLines", newLogLines);
-                            
-                            if (newLogLines.size() > 0) {
-                                failureMsg.append("Log contains warnings/errors: "+newLogLines+"\n");
-                            }
-                        }
-                        if (prefs.webProcessesRegex != null) {
-                            List<Integer> pids = MonitorUtils.getRunningPids(prefs.webProcessesRegex, "--webProcesses");
-                            pids.remove((Object)MonitorUtils.findOwnPid());
-                            
-                            record.put("webPids", pids);
-                            record.put("numWebPids", pids.size());
-                            numWebProcessesHistory.add(pids.size());
-                            
-                            if (prefs.numWebProcesses != null) {
-                                boolean numWebPidsInRange = prefs.numWebProcesses.apply(pids.size());
-                                record.put("numWebPidsInRange", numWebPidsInRange);
-                                
-                                if (!numWebPidsInRange) {
-                                    failureMsg.append("num web processes out-of-range: pids="+pids+"; size="+pids.size()+"; expected="+prefs.numWebProcesses);
-                                }
-                            
-                                if (prefs.webProcessesCyclingPeriod > 0) {
-                                    List<TimestampedValue<Integer>> values = numWebProcessesHistory.getValues();
-                                    long valuesTimeRange = (values.get(values.size()-1).getTimestamp() - values.get(0).getTimestamp());
-                                    if (values.size() > 0 && valuesTimeRange > SECONDS.toMillis(prefs.webProcessesCyclingPeriod)) {
-                                        int min = -1;
-                                        int max = -1;
-                                        for (TimestampedValue<Integer> val : values) {
-                                            min = (min < 0) ? val.getValue() : Math.min(val.getValue(), min);
-                                            max = Math.max(val.getValue(), max);
-                                        }
-                                        record.put("minWebSizeInPeriod", min);
-                                        record.put("maxWebSizeInPeriod", max);
-                                        
-                                        if (min > prefs.numWebProcesses.lowerEndpoint() || max < prefs.numWebProcesses.upperEndpoint()) {
-                                            failureMsg.append("num web processes not increasing/decreasing correctly: " +
-                                                    "pids="+pids+"; size="+pids.size()+"; cyclePeriod="+prefs.webProcessesCyclingPeriod+
-                                                    "; expectedRange="+prefs.numWebProcesses+"; min="+min+"; max="+max+"; history="+values);
-                                        }
-                                    } else {
-                                        int numVals = values.size();
-                                        long startTime = (numVals > 0) ? values.get(0).getTimestamp() : 0;
-                                        long endTime = (numVals > 0) ? values.get(values.size()-1).getTimestamp() : 0;
-                                        LOG.info("Insufficient vals in time-window to determine cycling behaviour over period ("+prefs.webProcessesCyclingPeriod+"secs): "+
-                                                "numVals="+numVals+"; startTime="+startTime+"; endTime="+endTime+"; periodCovered="+(endTime-startTime)/1000);
-                                    }
-                                }
-                            }
-                        }
-                        
-                    } catch (Throwable t) {
-                        LOG.error("Error during periodic checks", t);
-                        throw Throwables.propagate(t);
-                    }
-                    
-                    try {
-                        recorder.record(record);
-                        listener.onRecord(record);
-                        
-                        if (failureMsg.length() > 0) {
-                            listener.onFailure(record, failureMsg.toString());
-                            
-                            if (prefs.abortOnError) {
-                                LOG.error("Aborting on error: "+failureMsg);
-                                System.exit(1);
-                            }
-                        }
-                        
-                    } catch (Throwable t) {
-                        LOG.warn("Error recording monitor info ("+record+")", t);
-                        throw Throwables.propagate(t);
-                    }
-                }
-            }, 0, checkPeriodMs, TimeUnit.MILLISECONDS);
-    }
-    
-    // TODO What is the guava equivalent? Don't want Set.difference, because duplicates/ordered.
-    private static List<String> getAdditions(List<String> prev, List<String> next) {
-        List<String> result = Lists.newArrayList(next);
-        result.removeAll(prev);
-        return result;
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/main/java/brooklyn/qa/longevity/MonitorListener.java
----------------------------------------------------------------------
diff --git a/usage/qa/src/main/java/brooklyn/qa/longevity/MonitorListener.java b/usage/qa/src/main/java/brooklyn/qa/longevity/MonitorListener.java
deleted file mode 100644
index b0a0678..0000000
--- a/usage/qa/src/main/java/brooklyn/qa/longevity/MonitorListener.java
+++ /dev/null
@@ -1,35 +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 brooklyn.qa.longevity;
-
-import brooklyn.qa.longevity.StatusRecorder.Record;
-
-public interface MonitorListener {
-
-    public static final MonitorListener NOOP = new MonitorListener() {
-        @Override public void onRecord(Record record) {
-        }
-        @Override public void onFailure(Record record, String msg) {
-        }
-    };
-    
-    public void onRecord(Record record);
-    
-    public void onFailure(Record record, String msg);
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/main/java/brooklyn/qa/longevity/MonitorPrefs.java
----------------------------------------------------------------------
diff --git a/usage/qa/src/main/java/brooklyn/qa/longevity/MonitorPrefs.java b/usage/qa/src/main/java/brooklyn/qa/longevity/MonitorPrefs.java
deleted file mode 100644
index 3883e93..0000000
--- a/usage/qa/src/main/java/brooklyn/qa/longevity/MonitorPrefs.java
+++ /dev/null
@@ -1,54 +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 brooklyn.qa.longevity;
-
-import java.io.File;
-import java.net.URL;
-
-import com.google.common.base.Objects;
-import com.google.common.collect.Range;
-
-public class MonitorPrefs {
-
-    public URL webUrl;
-    public int brooklynPid;
-    public File logFile;
-    public String logGrep;
-    public File logGrepExclusionsFile;
-    public String webProcessesRegex;
-    public Range<Integer> numWebProcesses;
-    public int webProcessesCyclingPeriod;
-    public File outFile;
-    public boolean abortOnError;
-    
-    @Override
-    public String toString() {
-        return Objects.toStringHelper(this)
-                .add("webUrl", webUrl)
-                .add("brooklynPid", brooklynPid)
-                .add("logFile", logFile)
-                .add("logGrep", logGrep)
-                .add("logGrepExclusionsFile", logGrepExclusionsFile)
-                .add("outFile", outFile)
-                .add("webProcessesRegex", webProcessesRegex)
-                .add("numWebProcesses", numWebProcesses)
-                .add("webProcessesCyclingPeriod", webProcessesCyclingPeriod)
-                .toString();
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/main/java/brooklyn/qa/longevity/MonitorUtils.java
----------------------------------------------------------------------
diff --git a/usage/qa/src/main/java/brooklyn/qa/longevity/MonitorUtils.java b/usage/qa/src/main/java/brooklyn/qa/longevity/MonitorUtils.java
deleted file mode 100644
index 290a555..0000000
--- a/usage/qa/src/main/java/brooklyn/qa/longevity/MonitorUtils.java
+++ /dev/null
@@ -1,329 +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 brooklyn.qa.longevity;
-
-import static com.google.common.base.Strings.isNullOrEmpty;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Pattern;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.util.http.HttpTool;
-import brooklyn.util.http.HttpToolResponse;
-import brooklyn.util.stream.StreamGobbler;
-
-import com.google.common.base.Objects;
-import com.google.common.base.Throwables;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Maps;
-import com.google.common.io.ByteStreams;
-
-public class MonitorUtils {
-
-    private static final Logger LOG = LoggerFactory.getLogger(MonitorUtils.class);
-
-    private static volatile int ownPid = -1;
-
-    /**
-     * Confirm can read from URL.
-     *
-     * @param url
-     */
-    public static boolean isUrlUp(URL url) {
-        try {
-            HttpToolResponse result = HttpTool.httpGet(
-                    HttpTool.httpClientBuilder().trustAll().build(), 
-                    URI.create(url.toString()), 
-                    ImmutableMap.<String,String>of());
-            int statuscode = result.getResponseCode();
-
-            if (statuscode != 200) {
-                LOG.info("Error reading URL {}: {}, {}", new Object[]{url, statuscode, result.getReasonPhrase()});
-                return false;
-            } else {
-                return true;
-            }
-        } catch (Exception e) {
-            LOG.info("Error reading URL {}: {}", url, e);
-            return false;
-        }
-    }
-
-    public static boolean isPidRunning(int pid) {
-        return isPidRunning(pid, null);
-    }
-
-    /**
-     * Confirm the given pid is running, and that the the process matches the given regex.
-     *
-     * @param pid
-     * @param regex
-     */
-    public static boolean isPidRunning(int pid, String regex) {
-        Process process = exec("ps -p " + pid);
-        String out = waitFor(process);
-         if (process.exitValue() > 0) {
-            String err = toString(process.getErrorStream());
-            LOG.info(String.format("pid %s not running: %s", pid, err));
-            return false;
-        }
-
-        if (regex != null) {
-            String regex2 = "^\\s*" + pid + ".*" + regex;
-            boolean found = false;
-            for (String line : out.split("\n")) {
-                if (hasAtLeastOneMatch(line, regex2)) {
-                    found = true;
-                    break;
-                }
-            }
-
-            if (!found) {
-                String txt = toString(process.getInputStream());
-                LOG.info("process did not match regular expression: "+txt);
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    private static boolean hasAtLeastOneMatch(String line, String regex) {
-        return Pattern.matches(".*"+regex+".*", line);
-    }
-
-    private static String toString(InputStream in){
-        try {
-            byte[] bytes = ByteStreams.toByteArray(in);
-            return new String(bytes);
-        } catch (IOException e) {
-            throw Throwables.propagate(e);
-        }
-
-    }
-
-    public static List<Integer> getRunningPids(String regex) {
-        return getRunningPids(regex, null);
-    }
-
-    /**
-     * Confirm the given pid is running, and that the the process matches the given regex.
-     *
-     * @param regex
-     * @param excludingRegex
-     */
-    public static List<Integer> getRunningPids(String regex, String excludingRegex) {
-        Process process = exec("ps ax");
-        String out = waitFor(process);
-
-        List<Integer> result = new LinkedList<Integer>();
-        for (String line : out.split("\n")) {
-            if (excludingRegex != null && hasAtLeastOneMatch(line, excludingRegex)) {
-                continue;
-            }
-            if (hasAtLeastOneMatch(line, regex)) {
-                String[] linesplit = line.trim().split("\\s+");
-                result.add(Integer.parseInt(linesplit[0]));
-            }
-        }
-        return result;
-    }
-
-    public static MemoryUsage getMemoryUsage(int pid){
-          return getMemoryUsage(pid, null,0);
-    }
-
-    /**
-     * @param pid
-     */
-    public static MemoryUsage getMemoryUsage(int pid, String clazzRegexOfInterest, int minInstancesOfInterest) {
-        Process process = exec(String.format("jmap -histo %s", pid));
-        String out = waitFor(process);
-
-        Map<String, Integer> instanceCounts = Maps.newLinkedHashMap();
-        long totalInstances=0;
-        long totalMemoryBytes=0;
-
-        for (String line : out.split("\n")) {
-            if (clazzRegexOfInterest!=null && hasAtLeastOneMatch(line, clazzRegexOfInterest)) {
-                // Format is:
-                //   num     #instances         #bytes  class name
-                //   1:           43506        8047096  example.MyClazz
-
-                String[] parts = line.trim().split("\\s+");
-                String clazz = parts[3];
-                int instanceCount = Integer.parseInt(parts[1]);
-                if (instanceCount >= minInstancesOfInterest) {
-                    instanceCounts.put(clazz, instanceCount);
-                }
-            }
-            if (hasAtLeastOneMatch(line, "^Total.*")) {
-                String[] parts = line.split("\\s+");
-                totalInstances = Long.parseLong(parts[1]);
-                totalMemoryBytes = Long.parseLong(parts[2]);
-            }
-        }
-
-        return new MemoryUsage(totalInstances, totalMemoryBytes, instanceCounts);
-    }
-
-    public static class MemoryUsage {
-        final long totalInstances;
-        final long totalMemoryBytes;
-        final Map<String, Integer> instanceCounts;
-
-        MemoryUsage(long totalInstances, long totalMemoryBytes, Map<String, Integer> instanceCounts) {
-            this.totalInstances = totalInstances;
-            this.totalMemoryBytes = totalMemoryBytes;
-            this.instanceCounts = instanceCounts;
-        }
-
-        public String toString() {
-            return Objects.toStringHelper(this)
-                    .add("totalInstances", totalInstances)
-                    .add("totalMemoryBytes", totalMemoryBytes)
-                    .add("instanceCounts", instanceCounts)
-                    .toString();
-        }
-
-        public long getTotalInstances() {
-            return totalInstances;
-        }
-
-        public long getTotalMemoryBytes() {
-            return totalMemoryBytes;
-        }
-
-        public Map<String, Integer> getInstanceCounts() {
-            return instanceCounts;
-        }
-    }
-
-    public static List<String> searchLog(File file, String grepOfInterest) {
-        return searchLog(file, grepOfInterest, new LinkedHashSet<String>());
-    }
-
-    /**
-     * Find lines in the given file that match given given regex.
-     *
-     * @param file
-     * @param grepOfInterest
-     */
-    public static List<String> searchLog(File file, String grepOfInterest, Set<String> grepExclusions) {
-        Process process = exec(String.format("grep -E %s %s", grepOfInterest, file.getAbsoluteFile()));
-        String out = waitFor(process);
-
-        // TODO Annoying that String.split() returns size 1 when empty string; lookup javadoc when back online...
-        if (out.length() == 0) return Collections.<String>emptyList();
-
-        List<String> result = new ArrayList<String>();
-        for (String line : out.trim().split("\n")) {
-            boolean excluded = false;
-            for (String exclusion : grepExclusions) {
-                if (!isNullOrEmpty(exclusion) && hasAtLeastOneMatch(line, exclusion)) {
-                    excluded = true;
-                }
-            }
-            if (!excluded) {
-                result.add(line);
-            }
-        }
-        return result;
-    }
-
-    public static Process exec(String cmd) {
-        LOG.info("executing cmd: " + cmd);
-
-        try {
-            return Runtime.getRuntime().exec(cmd);
-        } catch (IOException e) {
-            throw Throwables.propagate(e);
-        }
-    }
-
-    public static class ProcessHasStderr extends IllegalStateException {
-        private static final long serialVersionUID = -937871002993888405L;
-        
-        byte[] stderrBytes;
-        public ProcessHasStderr(byte[] stderrBytes) {
-            this("Process printed to stderr: " + new String(stderrBytes), stderrBytes);
-        }
-        public ProcessHasStderr(String message, byte[] stderrBytes) {
-            super(message);
-            this.stderrBytes = stderrBytes;
-        }
-    }
-    
-    /**
-     * Waits for the given process to complete, consuming its stdout and returning it as a string.
-     * If there is any output on stderr an exception will be thrown.
-     */
-    public static String waitFor(Process process) {
-        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
-        @SuppressWarnings("resource") //Closeable doesn't seem appropriate for StreamGobbler since it isn't expected to be called every time
-        StreamGobbler gobblerOut = new StreamGobbler(process.getInputStream(), bytesOut, null);
-        gobblerOut.start();
-
-        ByteArrayOutputStream bytesErr = new ByteArrayOutputStream();
-        @SuppressWarnings("resource")
-        StreamGobbler gobblerErr = new StreamGobbler(process.getErrorStream(), bytesErr, null);
-        gobblerErr.start();
-
-        try {
-            process.waitFor();
-            gobblerOut.blockUntilFinished();
-            gobblerErr.blockUntilFinished();
-
-            if (bytesErr.size() > 0) {
-                throw new ProcessHasStderr(bytesErr.toByteArray());
-            }
-
-            return new String(bytesOut.toByteArray());
-        } catch (Exception e) {
-            throw Throwables.propagate(e);
-        } finally {
-            if (gobblerOut.isAlive()) gobblerOut.interrupt();
-            if (gobblerErr.isAlive()) gobblerErr.interrupt();
-        }
-    }
-
-    public static int findOwnPid() throws IOException {
-        if (ownPid >= 0) return ownPid;
-
-        String[] cmd = new String[]{"bash", "-c", "echo $PPID"};
-        Process process = Runtime.getRuntime().exec(cmd);
-        String out = MonitorUtils.waitFor(process);
-        ownPid = Integer.parseInt(out.trim());
-        return ownPid;
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/main/java/brooklyn/qa/longevity/StatusRecorder.java
----------------------------------------------------------------------
diff --git a/usage/qa/src/main/java/brooklyn/qa/longevity/StatusRecorder.java b/usage/qa/src/main/java/brooklyn/qa/longevity/StatusRecorder.java
deleted file mode 100644
index 5664a2d..0000000
--- a/usage/qa/src/main/java/brooklyn/qa/longevity/StatusRecorder.java
+++ /dev/null
@@ -1,130 +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 brooklyn.qa.longevity;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Map;
-
-import org.slf4j.Logger;
-
-import com.google.common.base.Charsets;
-import com.google.common.collect.Maps;
-import com.google.common.io.Files;
-
-public interface StatusRecorder {
-
-    public void record(Record record) throws IOException;
-    
-    public static class Factory {
-        public static final StatusRecorder NOOP = new StatusRecorder() {
-            @Override public void record(Record record) {}
-        };
-        
-        public static StatusRecorder noop() {
-            return NOOP;
-        }
-        public static StatusRecorder toFile(File outFile) {
-            return new FileBasedStatusRecorder(outFile);
-        }
-        public static StatusRecorder toSysout() {
-            return new SysoutBasedStatusRecorder();
-        }
-        public static StatusRecorder toLog(Logger log) {
-            return new LogBasedStatusRecorder(log);
-        }
-        public static StatusRecorder chain(StatusRecorder...recorders) {
-            return new ChainingStatusRecorder(recorders);
-        }
-    }
-    
-    public static class Record {
-        private final Map<String,Object> fields = Maps.newLinkedHashMap();
-        
-        public void putAll(Map<String,?> entries) {
-            fields.putAll(entries);
-        }
-        
-        public void putAll(String keyPrefix, Map<String,?> entries) {
-            for (Map.Entry<String,?> entry : entries.entrySet()) {
-                fields.put(keyPrefix+entry.getKey(), entry.getValue());
-            }
-        }
-        
-        public void put(String key, Object val) {
-            fields.put(key, val);
-        }
-        
-        @Override
-        public String toString() {
-            return fields.toString();
-        }
-    }
-    
-    public static class FileBasedStatusRecorder implements StatusRecorder {
-        private final File outFile;
-    
-        public FileBasedStatusRecorder(File outFile) {
-            this.outFile = outFile;
-        }
-        
-        @Override
-        public void record(Record record) throws IOException {
-            Files.append(record.fields.toString()+"\n", outFile, Charsets.UTF_8);
-        }
-    }
-    
-    public static class SysoutBasedStatusRecorder implements StatusRecorder {
-        public SysoutBasedStatusRecorder() {
-        }
-        
-        @Override
-        public void record(Record record) {
-            System.out.println(record.fields);
-        }
-    }
-    
-    public static class LogBasedStatusRecorder implements StatusRecorder {
-        private final Logger log;
-
-        public LogBasedStatusRecorder(Logger log) {
-            this.log = log;
-        }
-        
-        @Override
-        public void record(Record record) {
-            log.info("{}", record.fields);
-        }
-    }
-    
-    public static class ChainingStatusRecorder implements StatusRecorder {
-        private final StatusRecorder[] recorders;
-
-        public ChainingStatusRecorder(StatusRecorder... recorders) {
-            this.recorders = recorders;
-        }
-        
-        @Override
-        public void record(Record record) throws IOException {
-            for (StatusRecorder recorder : recorders) {
-                recorder.record(record);
-            }
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedJBoss7ServerImpl.java
----------------------------------------------------------------------
diff --git a/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedJBoss7ServerImpl.java b/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedJBoss7ServerImpl.java
new file mode 100644
index 0000000..762c17b
--- /dev/null
+++ b/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedJBoss7ServerImpl.java
@@ -0,0 +1,240 @@
+/*
+ * 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.qa.load;
+
+import static java.lang.String.format;
+
+import java.util.concurrent.Callable;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.enricher.Enrichers;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.webapp.jboss.JBoss7ServerImpl;
+import brooklyn.entity.webapp.jboss.JBoss7SshDriver;
+import brooklyn.event.feed.function.FunctionFeed;
+import brooklyn.event.feed.function.FunctionPollConfig;
+import brooklyn.event.feed.http.HttpFeed;
+import brooklyn.event.feed.http.HttpPollConfig;
+import brooklyn.event.feed.http.HttpValueFunctions;
+import brooklyn.location.access.BrooklynAccessUtils;
+import brooklyn.location.basic.SshMachineLocation;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.guava.Functionals;
+import brooklyn.util.os.Os;
+
+import com.google.common.net.HostAndPort;
+
+/**
+ * For simulating various aspects of the JBoss 7 app-server entity.
+ *  
+ * The use-case for this is that the desired configuration is not always available for testing. 
+ * For example, there may be insufficient resources to run 100s of JBoss app-servers, or one 
+ * may be experimenting with possible configurations such as use of an external monitoring tool 
+ * that is not yet available.
+ * 
+ * It is then possible to simulate aspects of the behaviour, for performance and load testing purposes. 
+ * 
+ * There is configuration for:
+ * <ul>
+ *   <li>{@code simulateEntity}
+ *     <ul>
+ *       <li>if true, no underlying entity will be started. Instead a sleep 100000 job will be run and monitored.
+ *       <li>if false, the underlying entity (i.e. a JBoss app-server) will be started as normal.
+ *     </ul>
+ *   <li>{@code simulateExternalMonitoring}
+ *     <ul>
+ *       <li>if true, disables the default monitoring mechanism. Instead, a function will periodically execute 
+ *           to set the entity's sensors (as though the values had been obtained from the external monitoring tool).
+ *       <li>if false, then:
+ *         <ul>
+ *           <li>If {@code simulateEntity==true} it will execute comparable commands (e.g. execute a command of the same 
+ *               size over ssh or do a comparable number of http GET requests).
+ *           <li>If {@code simulateEntity==false} then normal monitoring will be done.
+ *         </ul>
+ *     </ul>
+ *   <li>{@code skipSshOnStart}
+ *     <ul>
+ *       <li>If true (and if {@code simulateEntity==true}), then no ssh commands will be executed at deploy-time. 
+ *           This is useful for speeding up load testing, to get to the desired number of entities.
+ *           Should not be set to {@code true} if {@code simulateEntity==false}.
+ *       <li>If false, the ssh commands will be executed (based on the value of {@code simulateEntity}.
+ *     </ul>
+ * </ul>
+ */
+public class SimulatedJBoss7ServerImpl extends JBoss7ServerImpl {
+
+    public static final ConfigKey<Boolean> SIMULATE_ENTITY = SimulatedTheeTierApp.SIMULATE_ENTITY;
+    public static final ConfigKey<Boolean> SIMULATE_EXTERNAL_MONITORING = SimulatedTheeTierApp.SIMULATE_EXTERNAL_MONITORING;
+    public static final ConfigKey<Boolean> SKIP_SSH_ON_START = SimulatedTheeTierApp.SKIP_SSH_ON_START;
+    
+    private HttpFeed httpFeed;
+    private FunctionFeed functionFeed;
+    
+    @Override
+    public Class<?> getDriverInterface() {
+        return SimulatedJBoss7SshDriver.class;
+    }
+
+    @Override
+    protected void connectSensors() {
+        boolean simulateEntity = getConfig(SIMULATE_ENTITY);
+        boolean simulateExternalMonitoring = getConfig(SIMULATE_EXTERNAL_MONITORING);
+
+        if (!simulateEntity && !simulateExternalMonitoring) {
+            super.connectSensors();
+            return;
+        }
+        
+        HostAndPort hp = BrooklynAccessUtils.getBrooklynAccessibleAddress(this,
+                getAttribute(MANAGEMENT_HTTP_PORT) + getConfig(PORT_INCREMENT));
+
+        String managementUri = String.format("http://%s:%s/management/subsystem/web/connector/http/read-resource",
+                hp.getHostText(), hp.getPort());
+        setAttribute(MANAGEMENT_URL, managementUri);
+
+        if (simulateExternalMonitoring) {
+            // TODO What would set this normally, if not doing connectServiceUpIsRunning?
+            setAttribute(SERVICE_PROCESS_IS_RUNNING, true);
+        } else {
+            // if simulating entity, then simulate work of periodic HTTP request; TODO but not parsing JSON response
+            String uriToPoll = (simulateEntity) ? "http://localhost:8081" : managementUri;
+            
+            httpFeed = HttpFeed.builder()
+                    .entity(this)
+                    .period(200)
+                    .baseUri(uriToPoll)
+                    .credentials(getConfig(MANAGEMENT_USER), getConfig(MANAGEMENT_PASSWORD))
+                    .poll(new HttpPollConfig<Integer>(MANAGEMENT_STATUS)
+                            .onSuccess(HttpValueFunctions.responseCode()))
+                    .build();
+            
+            // Polls over ssh for whether process is running
+            connectServiceUpIsRunning();
+        }
+        
+        functionFeed = FunctionFeed.builder()
+                .entity(this)
+                .period(5000)
+                .poll(new FunctionPollConfig<Boolean,Boolean>(MANAGEMENT_URL_UP)
+                        .callable(new Callable<Boolean>() {
+                            private int counter = 0;
+                            public Boolean call() {
+                                setAttribute(REQUEST_COUNT, (counter++ % 100));
+                                setAttribute(ERROR_COUNT, (counter++ % 100));
+                                setAttribute(TOTAL_PROCESSING_TIME, (counter++ % 100));
+                                setAttribute(MAX_PROCESSING_TIME, (counter++ % 100));
+                                setAttribute(BYTES_RECEIVED, (long) (counter++ % 100));
+                                setAttribute(BYTES_SENT, (long) (counter++ % 100));
+                                return true;
+                            }}))
+                .build();
+        
+        addEnricher(Enrichers.builder().updatingMap(Attributes.SERVICE_NOT_UP_INDICATORS)
+                .from(MANAGEMENT_URL_UP)
+                .computing(Functionals.ifNotEquals(true).value("Management URL not reachable") )
+                .build());
+    }
+
+    @Override
+    protected void disconnectSensors() {
+        super.disconnectSensors();
+        if (httpFeed != null) httpFeed.stop();
+        if (functionFeed != null) functionFeed.stop();
+    }
+    
+    public static class SimulatedJBoss7SshDriver extends JBoss7SshDriver {
+        public SimulatedJBoss7SshDriver(SimulatedJBoss7ServerImpl entity, SshMachineLocation machine) {
+            super(entity, machine);
+        }
+        
+        @Override
+        public boolean isRunning() {
+            if (entity.getConfig(SKIP_SSH_ON_START)) {
+                return true;
+            } else {
+                return super.isRunning();
+            }
+        }
+
+        @Override
+        public void install() {
+            if (entity.getConfig(SKIP_SSH_ON_START)) {
+                // no-op
+            } else {
+                super.install();
+            }
+        }
+        
+        @Override
+        public void customize() {
+            if (entity.getConfig(SKIP_SSH_ON_START)) {
+                // no-op
+            } else {
+                super.customize();
+            }
+        }
+        
+        @Override
+        public void launch() {
+            if (!entity.getConfig(SIMULATE_ENTITY)) {
+                super.launch();
+                return;
+            }
+            
+            // We wait for evidence of JBoss running because, using
+            // brooklyn.ssh.config.tool.class=brooklyn.util.internal.ssh.cli.SshCliTool,
+            // we saw the ssh session return before the JBoss process was fully running
+            // so the process failed to start.
+            String pidFile = Os.mergePathsUnix(getRunDir(), PID_FILENAME);
+
+            if (entity.getConfig(SKIP_SSH_ON_START)) {
+                // minimal ssh, so that isRunning will subsequently work
+                newScript(MutableMap.of("usePidFile", pidFile), LAUNCHING)
+                        .body.append(
+                                format("nohup sleep 100000 > %s/console 2>&1 < /dev/null &", getRunDir()))
+                        .execute();
+            } else {
+                newScript(MutableMap.of(USE_PID_FILE, false), LAUNCHING)
+                        .body.append(
+                                "export LAUNCH_JBOSS_IN_BACKGROUND=true",
+                                format("export JBOSS_HOME=%s", getExpandedInstallDir()),
+                                format("export JBOSS_PIDFILE=%s/%s", getRunDir(), PID_FILENAME),
+                                format("echo skipping exec of %s/bin/%s.sh ", getExpandedInstallDir(), SERVER_TYPE) +
+                                        format("--server-config %s ", CONFIG_FILE) +
+                                        format("-Djboss.server.base.dir=%s/%s ", getRunDir(), SERVER_TYPE) +
+                                        format("\"-Djboss.server.base.url=file://%s/%s\" ", getRunDir(), SERVER_TYPE) +
+                                        "-Djava.net.preferIPv4Stack=true " +
+                                        "-Djava.net.preferIPv6Addresses=false " +
+                                        format(" >> %s/console 2>&1 </dev/null &", getRunDir()),
+                                format("nohup sleep 100000 > %s/console 2>&1 < /dev/null &", getRunDir()),
+                                format("echo $! > "+pidFile),
+                                format("echo starting > %s/console", getRunDir()),
+                                "for i in {1..10}\n" +
+                                        "do\n" +
+                                        "    grep -i 'starting' "+getRunDir()+"/console && exit\n" +
+                                        "    sleep 1\n" +
+                                        "done\n" +
+                                        "echo \"Couldn't determine if process is running (console output does not contain 'starting'); continuing but may subsequently fail\""
+    
+                            )
+                        .execute();
+            }
+        }
+    }
+}