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/08/12 17:55:22 UTC

[04/35] incubator-brooklyn git commit: [BROOKLYN-162] package rename to org.apache.brooklyn: software/webapp

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/ControlledDynamicWebAppClusterIntegrationTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/ControlledDynamicWebAppClusterIntegrationTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/ControlledDynamicWebAppClusterIntegrationTest.java
new file mode 100644
index 0000000..b004a8c
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/ControlledDynamicWebAppClusterIntegrationTest.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.webapp;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.test.HttpTestUtils;
+import org.apache.brooklyn.test.TestResourceUnavailableException;
+import org.apache.brooklyn.test.entity.TestJavaWebAppEntity;
+import org.apache.brooklyn.entity.basic.RecordingSensorEventListener;
+import org.apache.brooklyn.entity.proxy.AbstractController;
+import org.apache.brooklyn.entity.proxy.LoadBalancer;
+import org.apache.brooklyn.entity.proxy.nginx.NginxController;
+import org.apache.brooklyn.entity.webapp.ControlledDynamicWebAppCluster;
+import org.apache.brooklyn.entity.webapp.DynamicWebAppCluster;
+import org.apache.brooklyn.entity.webapp.tomcat.TomcatServer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.BrooklynAppLiveTestSupport;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import brooklyn.test.Asserts;
+import brooklyn.util.collections.CollectionFunctionals;
+import brooklyn.util.collections.MutableMap;
+
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+public class ControlledDynamicWebAppClusterIntegrationTest extends BrooklynAppLiveTestSupport {
+    private static final Logger log = LoggerFactory.getLogger(ControlledDynamicWebAppClusterIntegrationTest.class);
+
+    private static final int TIMEOUT_MS = 10*1000;
+    
+    private LocalhostMachineProvisioningLocation loc;
+    private List<LocalhostMachineProvisioningLocation> locs;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        super.setUp();
+
+        loc = app.newLocalhostProvisioningLocation();
+        locs = ImmutableList.of(loc);
+    }
+
+    public String getTestWar() {
+        TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), "/hello-world.war");
+        return "classpath://hello-world.war";
+    }
+    
+    @Test(groups="Integration")
+    public void testPropogateHttpPorts() {
+        ControlledDynamicWebAppCluster cluster = app.createAndManageChild(EntitySpec.create(ControlledDynamicWebAppCluster.class)
+                .configure("initialSize", 1));
+        app.start(locs);
+
+        EntityTestUtils.assertAttributeEventuallyNonNull(cluster, LoadBalancer.PROXY_HTTP_PORT);
+        EntityTestUtils.assertAttributeEventuallyNonNull(cluster, LoadBalancer.PROXY_HTTPS_PORT);
+    }
+    
+    @Test(groups="Integration")
+    public void testConfiguresController() {
+        ControlledDynamicWebAppCluster cluster = app.createAndManageChild(EntitySpec.create(ControlledDynamicWebAppCluster.class)
+                .configure("initialSize", 1)
+                .configure("memberSpec", EntitySpec.create(TomcatServer.class).configure("war", getTestWar())));
+        app.start(locs);
+
+        String url = cluster.getController().getAttribute(NginxController.ROOT_URL);
+        HttpTestUtils.assertHttpStatusCodeEventuallyEquals(url, 200);
+        HttpTestUtils.assertContentEventuallyContainsText(url, "Hello");
+    }
+    
+    @Test(groups="Integration")
+    public void testSetsToplevelHostnameFromController() {
+        ControlledDynamicWebAppCluster cluster = app.createAndManageChild(EntitySpec.create(ControlledDynamicWebAppCluster.class)
+                .configure("initialSize", 1)
+                .configure("memberSpec", EntitySpec.create(TomcatServer.class).configure("war", getTestWar())));
+        app.start(locs);
+
+        String expectedHostname = cluster.getController().getAttribute(LoadBalancer.HOSTNAME);
+        String expectedRootUrl = cluster.getController().getAttribute(LoadBalancer.ROOT_URL);
+        boolean expectedServiceUp = true;
+        
+        assertNotNull(expectedHostname);
+        assertNotNull(expectedRootUrl);
+        
+        EntityTestUtils.assertAttributeEqualsEventually(MutableMap.of("timeout", TIMEOUT_MS), cluster, ControlledDynamicWebAppCluster.HOSTNAME, expectedHostname);
+        EntityTestUtils.assertAttributeEqualsEventually(MutableMap.of("timeout", TIMEOUT_MS), cluster, ControlledDynamicWebAppCluster.ROOT_URL, expectedRootUrl);
+        EntityTestUtils.assertAttributeEqualsEventually(MutableMap.of("timeout", TIMEOUT_MS), cluster, ControlledDynamicWebAppCluster.SERVICE_UP, expectedServiceUp);
+    }
+    
+    @Test(groups="Integration")
+    public void testCustomWebClusterSpecGetsMemberSpec() {
+        ControlledDynamicWebAppCluster cluster = app.createAndManageChild(EntitySpec.create(ControlledDynamicWebAppCluster.class)
+                .configure("initialSize", 1)
+                .configure(ControlledDynamicWebAppCluster.MEMBER_SPEC, EntitySpec.create(TomcatServer.class)
+                        .configure(TomcatServer.ROOT_WAR, getTestWar()))
+                .configure(ControlledDynamicWebAppCluster.WEB_CLUSTER_SPEC, EntitySpec.create(DynamicWebAppCluster.class)
+                        .displayName("mydisplayname")));
+        app.start(locs);
+
+        String url = cluster.getController().getAttribute(NginxController.ROOT_URL);
+        HttpTestUtils.assertContentEventuallyContainsText(url, "Hello");
+
+        // and make sure it really was using our custom spec
+        assertEquals(cluster.getCluster().getDisplayName(), "mydisplayname");
+    }
+    
+    // Needs to be integration test because still using nginx controller; could pass in mock controller
+    @Test(groups="Integration")
+    public void testSetsServiceLifecycle() {
+        ControlledDynamicWebAppCluster cluster = app.createAndManageChild( EntitySpec.create(ControlledDynamicWebAppCluster.class)
+                .configure("initialSize", 1)
+                .configure(ControlledDynamicWebAppCluster.MEMBER_SPEC, EntitySpec.create(TestJavaWebAppEntity.class)) );
+        
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+        
+        RecordingSensorEventListener<Lifecycle> listener = new RecordingSensorEventListener<Lifecycle>(true);
+        app.subscribe(cluster, Attributes.SERVICE_STATE_ACTUAL, listener);
+        app.start(locs);
+        
+        Asserts.eventually(Suppliers.ofInstance(listener.getEventValues()), CollectionFunctionals.sizeEquals(2));
+        assertEquals(listener.getEventValues(), ImmutableList.of(Lifecycle.STARTING, Lifecycle.RUNNING), "vals="+listener.getEventValues());
+        listener.clearEvents();
+        
+        app.stop();
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+        Asserts.eventually(Suppliers.ofInstance(listener), CollectionFunctionals.sizeEquals(2));
+        assertEquals(listener.getEventValues(), ImmutableList.of(Lifecycle.STOPPING, Lifecycle.STOPPED), "vals="+listener.getEventValues());
+    }
+    
+    @Test(groups="Integration")
+    public void testTomcatAbsoluteRedirect() {
+        final ControlledDynamicWebAppCluster cluster = app.createAndManageChild(EntitySpec.create(ControlledDynamicWebAppCluster.class)
+            .configure(ControlledDynamicWebAppCluster.MEMBER_SPEC, EntitySpec.create(TomcatServer.class)
+            .configure(TomcatServer.ROOT_WAR, getTestWar()))
+            .configure("initialSize", 1)
+            .configure(AbstractController.SERVICE_UP_URL_PATH, "hello/redirectAbsolute")
+        );
+        app.start(locs);
+
+        final NginxController nginxController = (NginxController) cluster.getController();
+        Asserts.succeedsEventually(new Callable<Boolean>() {
+            @Override
+            public Boolean call() throws Exception {
+                return nginxController.getServerPoolAddresses().size() == 1;
+            }
+        });
+        
+        Entity tomcatServer = Iterables.getOnlyElement(cluster.getCluster().getMembers());
+        EntityTestUtils.assertAttributeEqualsEventually(tomcatServer, Attributes.SERVICE_UP, true);
+        
+        EntityTestUtils.assertAttributeEqualsContinually(nginxController, Attributes.SERVICE_UP, true);
+        
+        app.stop();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/ControlledDynamicWebAppClusterTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/ControlledDynamicWebAppClusterTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/ControlledDynamicWebAppClusterTest.java
new file mode 100644
index 0000000..30b66ba
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/ControlledDynamicWebAppClusterTest.java
@@ -0,0 +1,213 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.webapp;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.List;
+
+import org.apache.brooklyn.entity.proxy.AbstractController;
+import org.apache.brooklyn.entity.proxy.LoadBalancer;
+import org.apache.brooklyn.entity.proxy.TrackingAbstractController;
+import org.apache.brooklyn.entity.webapp.ControlledDynamicWebAppCluster;
+import org.apache.brooklyn.entity.webapp.DynamicWebAppCluster;
+import org.apache.brooklyn.entity.webapp.jboss.JBoss7Server;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.test.TestResourceUnavailableException;
+import org.apache.brooklyn.test.entity.TestJavaWebAppEntity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.BrooklynAppUnitTestSupport;
+import brooklyn.entity.Group;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.BasicGroup;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.basic.SoftwareProcess;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import brooklyn.test.Asserts;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+public class ControlledDynamicWebAppClusterTest extends BrooklynAppUnitTestSupport {
+    private static final Logger log = LoggerFactory.getLogger(ControlledDynamicWebAppClusterTest.class);
+
+    private LocalhostMachineProvisioningLocation loc;
+    private List<LocalhostMachineProvisioningLocation> locs;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        super.setUp();
+
+        loc = app.newLocalhostProvisioningLocation();
+        locs = ImmutableList.of(loc);
+    }
+
+    public String getTestWar() {
+        TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), "/hello-world.war");
+        return "classpath://hello-world.war";
+    }
+
+    @Test
+    public void testUsesCustomController() {
+        AbstractController controller = app.createAndManageChild(EntitySpec.create(TrackingAbstractController.class).displayName("mycustom"));
+
+        ControlledDynamicWebAppCluster cluster = app.createAndManageChild(EntitySpec.create(ControlledDynamicWebAppCluster.class)
+                .configure("initialSize", 0)
+                .configure(ControlledDynamicWebAppCluster.CONTROLLER, controller)
+                .configure("memberSpec", EntitySpec.create(JBoss7Server.class).configure("war", getTestWar())));
+        app.start(locs);
+
+        EntityTestUtils.assertAttributeEqualsEventually(controller, AbstractController.SERVICE_UP, true);
+        assertEquals(cluster.getController(), controller);
+
+        // Stopping cluster should not stop controller (because it didn't create it)
+        cluster.stop();
+        EntityTestUtils.assertAttributeEquals(controller, AbstractController.SERVICE_UP, true);
+    }
+    
+    @Test
+    public void testUsesCustomControlledGroup() {
+        TestJavaWebAppEntity webServer = app.createAndManageChild(EntitySpec.create(TestJavaWebAppEntity.class));
+        webServer.setAttribute(Attributes.SUBNET_HOSTNAME, "myhostname");
+        webServer.setAttribute(Attributes.HTTP_PORT, 1234);
+        
+        TrackingAbstractController controller = app.createAndManageChild(EntitySpec.create(TrackingAbstractController.class));
+        Group controlledGroup = app.createAndManageChild(EntitySpec.create(BasicGroup.class));
+        controlledGroup.addMember(webServer);
+        
+        ControlledDynamicWebAppCluster cluster = app.createAndManageChild(EntitySpec.create(ControlledDynamicWebAppCluster.class)
+                .configure("initialSize", 0)
+                .configure(ControlledDynamicWebAppCluster.CONTROLLER, controller)
+                .configure(ControlledDynamicWebAppCluster.CONTROLLED_GROUP, controlledGroup)
+                .configure("memberSpec", EntitySpec.create(JBoss7Server.class).configure("war", getTestWar())));
+        app.start(locs);
+
+        assertEquals(controller.getUpdates(), ImmutableList.of(ImmutableSet.of("myhostname:1234")));
+    }
+    
+    @Test
+    public void testUsesCustomControllerSpec() {
+        EntitySpec<TrackingAbstractController> controllerSpec = EntitySpec.create(TrackingAbstractController.class).displayName("mycustom");
+        ControlledDynamicWebAppCluster cluster = app.createAndManageChild(EntitySpec.create(ControlledDynamicWebAppCluster.class)
+                .configure("initialSize", 0)
+                .configure(ControlledDynamicWebAppCluster.CONTROLLER_SPEC, controllerSpec)
+                .configure("memberSpec", EntitySpec.create(JBoss7Server.class).configure("war", getTestWar())));
+        app.start(locs);
+        LoadBalancer controller = cluster.getController();
+        
+        EntityTestUtils.assertAttributeEqualsEventually(controller, AbstractController.SERVICE_UP, true);
+        assertEquals(controller.getDisplayName(), "mycustom");
+
+        // Stopping cluster should stop the controller (because it created it)
+        cluster.stop();
+        EntityTestUtils.assertAttributeEquals(controller, AbstractController.SERVICE_UP, false);
+    }
+    
+    @Test
+    public void testTheTestJavaWebApp() {
+        SoftwareProcess n = app.createAndManageChild(EntitySpec.create(TestJavaWebAppEntity.class));
+        app.start(locs);
+
+        EntityTestUtils.assertAttributeEqualsEventually(n, AbstractController.SERVICE_UP, true);
+        
+        app.stop();
+        EntityTestUtils.assertAttributeEqualsEventually(n, AbstractController.SERVICE_UP, false);
+    }
+    
+    @Test
+    public void testSetsInitialSize() {
+        ControlledDynamicWebAppCluster cluster = app.createAndManageChild(EntitySpec.create(ControlledDynamicWebAppCluster.class)
+                .configure("initialSize", 2)
+                .configure(ControlledDynamicWebAppCluster.CONTROLLER_SPEC, EntitySpec.create(TrackingAbstractController.class))
+                .configure(ControlledDynamicWebAppCluster.MEMBER_SPEC, EntitySpec.create(TestJavaWebAppEntity.class)) );
+        app.start(locs);
+
+        Iterable<TestJavaWebAppEntity> webservers = Iterables.filter(cluster.getCluster().getMembers(), TestJavaWebAppEntity.class);
+        assertEquals(Iterables.size(webservers), 2, "webservers="+webservers);
+    }
+    
+    @Test
+    public void testUsesCustomWebClusterSpec() {
+        ControlledDynamicWebAppCluster cluster = app.createAndManageChild(EntitySpec.create(ControlledDynamicWebAppCluster.class)
+                .configure("initialSize", 0)
+                .configure(ControlledDynamicWebAppCluster.CONTROLLER_SPEC, EntitySpec.create(TrackingAbstractController.class))
+                .configure(ControlledDynamicWebAppCluster.WEB_CLUSTER_SPEC, EntitySpec.create(DynamicWebAppCluster.class)
+                        .displayName("mydisplayname")));
+        app.start(locs);
+
+        assertEquals(cluster.getCluster().getDisplayName(), "mydisplayname");
+    }
+
+    @Test
+    public void testMembersReflectChildClusterMembers() {
+        final ControlledDynamicWebAppCluster cluster = app.createAndManageChild(EntitySpec.create(ControlledDynamicWebAppCluster.class)
+                .configure("initialSize", 1)
+                .configure(ControlledDynamicWebAppCluster.CONTROLLER_SPEC, EntitySpec.create(TrackingAbstractController.class))
+                .configure(ControlledDynamicWebAppCluster.MEMBER_SPEC, EntitySpec.create(TestJavaWebAppEntity.class)) );
+        app.start(locs);
+        final DynamicWebAppCluster childCluster = cluster.getCluster();
+        
+        // Expect initial member(s) to be the same
+        assertEquals(childCluster.getMembers().size(), 1);
+        Asserts.succeedsEventually(new Runnable() {
+            @Override public void run() {
+                Asserts.assertEqualsIgnoringOrder(childCluster.getMembers(), cluster.getMembers());
+            }});
+        
+        // After resize up, same members
+        cluster.resize(2);
+        assertEquals(childCluster.getMembers().size(), 2);
+        Asserts.succeedsEventually(new Runnable() {
+            @Override public void run() {
+                Asserts.assertEqualsIgnoringOrder(childCluster.getMembers(), cluster.getMembers());
+            }});
+        
+        // After resize down, same members
+        cluster.resize(1);
+        assertEquals(childCluster.getMembers().size(), 1);
+        Asserts.succeedsEventually(new Runnable() {
+            @Override public void run() {
+                Asserts.assertEqualsIgnoringOrder(childCluster.getMembers(), cluster.getMembers());
+            }});
+    }
+    
+    @Test
+    public void testStopOnChildUnmanaged() {
+        final ControlledDynamicWebAppCluster cluster = app.createAndManageChild(EntitySpec.create(ControlledDynamicWebAppCluster.class)
+                .configure("initialSize", 1)
+                .configure(ControlledDynamicWebAppCluster.CONTROLLER_SPEC, EntitySpec.create(TrackingAbstractController.class))
+                .configure(ControlledDynamicWebAppCluster.MEMBER_SPEC, EntitySpec.create(TestJavaWebAppEntity.class)) );
+        app.start(locs);
+        final DynamicWebAppCluster childCluster = cluster.getCluster();
+        LoadBalancer controller = cluster.getController();
+        
+        Entities.unmanage(childCluster);
+        Entities.unmanage(controller);
+        
+        cluster.stop();
+        EntityTestUtils.assertAttributeEquals(cluster, ControlledDynamicWebAppCluster.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/DynamicWebAppClusterTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/DynamicWebAppClusterTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/DynamicWebAppClusterTest.java
new file mode 100644
index 0000000..ec063f4
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/DynamicWebAppClusterTest.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.webapp;
+
+import static org.testng.Assert.assertEquals;
+
+import org.apache.brooklyn.entity.webapp.ControlledDynamicWebAppCluster;
+import org.apache.brooklyn.entity.webapp.DynamicWebAppCluster;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.test.entity.TestJavaWebAppEntity;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityLocal;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.trait.Startable;
+import brooklyn.location.basic.SimulatedLocation;
+import brooklyn.test.entity.TestApplication;
+import brooklyn.util.collections.MutableMap;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
+public class DynamicWebAppClusterTest {
+    
+    private static final int SHORT_WAIT_MS = 250;
+    
+    private TestApplication app;
+    private SimulatedLocation loc;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        app = TestApplication.Factory.newManagedInstanceForTests();
+        loc = app.newSimulatedLocation();
+    }
+    
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+
+    @Test
+    public void testTestJavaWebAppEntityStarts() throws Exception {
+        Entity test = app.createAndManageChild(EntitySpec.create(TestJavaWebAppEntity.class));
+        test.invoke(Startable.START, ImmutableMap.of("locations", ImmutableList.of(loc))).get();
+        
+        EntityTestUtils.assertAttributeEqualsEventually(test, Attributes.SERVICE_UP, true);
+    }
+    
+    @Test
+    public void testRequestCountAggregation() throws Exception {
+        final DynamicWebAppCluster cluster = app.createAndManageChild(EntitySpec.create(DynamicWebAppCluster.class)
+                .configure("initialSize", 2)
+                .configure(ControlledDynamicWebAppCluster.MEMBER_SPEC, EntitySpec.create(TestJavaWebAppEntity.class)) );
+        
+        app.start(ImmutableList.of(loc));
+        
+        for (Entity member : cluster.getMembers()) {
+            ((TestJavaWebAppEntity)member).spoofRequest();
+        }
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, DynamicWebAppCluster.REQUEST_COUNT, 2);
+        
+        for (Entity member : cluster.getMembers()) {
+            for (int i = 0; i < 2; i++) {
+                ((TestJavaWebAppEntity)member).spoofRequest();
+            }
+        }
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, DynamicWebAppCluster.REQUEST_COUNT_PER_NODE, 3d);
+    }
+    
+    @Test
+    public void testSetsServiceUpIfMemberIsUp() throws Exception {
+        DynamicWebAppCluster cluster = app.createAndManageChild(EntitySpec.create(DynamicWebAppCluster.class)
+                .configure("initialSize", 1)
+                .configure(ControlledDynamicWebAppCluster.MEMBER_SPEC, EntitySpec.create(TestJavaWebAppEntity.class)) );
+    
+        app.start(ImmutableList.of(loc));
+        
+        // Should initially be true (now that TestJavaWebAppEntity sets true) 
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, DynamicWebAppCluster.SERVICE_UP, true);
+        
+        // When child is !service_up, should report false
+        ((EntityLocal)Iterables.get(cluster.getMembers(), 0)).setAttribute(Startable.SERVICE_UP, false);
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, DynamicWebAppCluster.SERVICE_UP, false);
+        EntityTestUtils.assertAttributeEqualsContinually(MutableMap.of("timeout", SHORT_WAIT_MS), cluster, DynamicWebAppCluster.SERVICE_UP, false);
+        
+        cluster.resize(2);
+        
+        // When one of the two children is service_up, should report true
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, DynamicWebAppCluster.SERVICE_UP, true);
+
+        // And if that serviceUp child goes away, should again report false
+        Entities.unmanage(Iterables.get(cluster.getMembers(), 1));
+        ((EntityLocal)Iterables.get(cluster.getMembers(), 0)).setAttribute(Startable.SERVICE_UP, false);
+        
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, DynamicWebAppCluster.SERVICE_UP, false);
+    }
+    
+    @Test
+    public void testPropertiesToChildren() throws Exception {
+        DynamicWebAppCluster cluster = app.createAndManageChild(EntitySpec.create(DynamicWebAppCluster.class)
+            .configure(ControlledDynamicWebAppCluster.MEMBER_SPEC, EntitySpec.create(TestJavaWebAppEntity.class)
+                .configure("a", 1))
+            .configure(DynamicWebAppCluster.CUSTOM_CHILD_FLAGS, ImmutableMap.of("b", 2)));
+
+        app.start(ImmutableList.of(loc));
+        
+        TestJavaWebAppEntity we = (TestJavaWebAppEntity) Iterables.getOnlyElement(cluster.getMembers());
+        assertEquals(we.getA(), 1);
+        assertEquals(we.getB(), 2);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/DynamicWebAppFabricTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/DynamicWebAppFabricTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/DynamicWebAppFabricTest.java
new file mode 100644
index 0000000..63954e8
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/DynamicWebAppFabricTest.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.webapp;
+
+import java.util.List;
+
+import org.apache.brooklyn.entity.webapp.ControlledDynamicWebAppCluster;
+import org.apache.brooklyn.entity.webapp.DynamicWebAppCluster;
+import org.apache.brooklyn.entity.webapp.DynamicWebAppFabric;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.test.entity.TestJavaWebAppEntity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.DynamicGroup;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityInternal;
+import brooklyn.entity.basic.EntityLocal;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.trait.Changeable;
+import brooklyn.location.basic.SimulatedLocation;
+import brooklyn.test.entity.TestApplication;
+import brooklyn.util.collections.MutableMap;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * TODO clarify test purpose
+ */
+public class DynamicWebAppFabricTest {
+    private static final Logger log = LoggerFactory.getLogger(DynamicWebAppFabricTest.class);
+
+    private static final long TIMEOUT_MS = 10*1000;
+    
+    private TestApplication app;
+    private SimulatedLocation loc1;
+    private SimulatedLocation loc2;
+    private List<SimulatedLocation> locs;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        app = TestApplication.Factory.newManagedInstanceForTests();
+        loc1 = app.newSimulatedLocation();
+        loc2 = app.newSimulatedLocation();
+        locs = ImmutableList.of(loc1, loc2);
+    }
+    
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+
+    @Test
+    public void testRequestCountAggregation() {
+        DynamicWebAppFabric fabric = app.createAndManageChild(EntitySpec.create(DynamicWebAppFabric.class)
+                .configure(DynamicWebAppFabric.MEMBER_SPEC, EntitySpec.create(TestJavaWebAppEntity.class)) );
+        
+        app.start(locs);
+        for (Entity member : fabric.getChildren()) {
+            ((EntityLocal)member).setAttribute(Changeable.GROUP_SIZE, 1);
+        }
+        
+        for (Entity member : fabric.getChildren()) {
+            ((EntityInternal)member).setAttribute(DynamicGroup.GROUP_SIZE, 1);
+            ((TestJavaWebAppEntity)member).spoofRequest();
+        }
+        EntityTestUtils.assertAttributeEqualsEventually(MutableMap.of("timeout", TIMEOUT_MS), fabric, DynamicWebAppFabric.REQUEST_COUNT, 2);
+        
+        // Note this is time-sensitive: need to do the next two sends before the previous one has dropped out
+        // of the time-window.
+        for (Entity member : fabric.getChildren()) {
+            for (int i = 0; i < 2; i++) {
+                ((TestJavaWebAppEntity)member).spoofRequest();
+            }
+        }
+        EntityTestUtils.assertAttributeEqualsEventually(MutableMap.of("timeout", TIMEOUT_MS), fabric, DynamicWebAppFabric.REQUEST_COUNT_PER_NODE, 3d);
+    }
+    
+    @Test
+    public void testRequestCountAggregationOverClusters() {
+        DynamicWebAppFabric fabric = app.createAndManageChild(EntitySpec.create(DynamicWebAppFabric.class)
+                .configure(DynamicWebAppFabric.MEMBER_SPEC, 
+                    EntitySpec.create(DynamicWebAppCluster.class)
+                        .configure("initialSize", 2)
+                        .configure(ControlledDynamicWebAppCluster.MEMBER_SPEC, EntitySpec.create(TestJavaWebAppEntity.class)) ));
+
+        app.start(locs);
+        
+        for (Entity cluster : fabric.getChildren()) {
+            for (Entity node : ((DynamicWebAppCluster)cluster).getMembers()) {
+                ((TestJavaWebAppEntity)node).spoofRequest();
+            }
+        }
+        EntityTestUtils.assertAttributeEqualsEventually(MutableMap.of("timeout", TIMEOUT_MS), fabric, DynamicWebAppFabric.REQUEST_COUNT, 4);
+        
+        // Note this is time-sensitive: need to do the next two sends before the previous one has dropped out
+        // of the time-window.
+        for (Entity cluster : fabric.getChildren()) {
+            for (Entity node : ((DynamicWebAppCluster)cluster).getMembers()) {
+                for (int i = 0; i < 2; i++) {
+                    ((TestJavaWebAppEntity)node).spoofRequest();
+                }
+            }
+        }
+        EntityTestUtils.assertAttributeEqualsEventually(MutableMap.of("timeout", TIMEOUT_MS), fabric, DynamicWebAppFabric.REQUEST_COUNT_PER_NODE, 3d);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/ElasticCustomLocationTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/ElasticCustomLocationTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/ElasticCustomLocationTest.java
new file mode 100644
index 0000000..7a13ac2
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/ElasticCustomLocationTest.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.webapp;
+
+import java.util.Map;
+
+import org.apache.brooklyn.entity.webapp.ElasticJavaWebAppService;
+import org.apache.brooklyn.entity.webapp.ElasticJavaWebAppService.ElasticJavaWebAppServiceAwareLocation;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.BasicConfigurableEntityFactory;
+import brooklyn.entity.basic.ConfigurableEntityFactory;
+import brooklyn.entity.basic.Entities;
+import brooklyn.location.basic.SimulatedLocation;
+import brooklyn.test.entity.TestApplication;
+import brooklyn.test.entity.TestEntityImpl;
+import brooklyn.util.collections.MutableMap;
+
+import com.google.common.collect.ImmutableList;
+
+public class ElasticCustomLocationTest {
+
+    public static class MockWebServiceLocation extends SimulatedLocation implements ElasticJavaWebAppServiceAwareLocation {
+        public MockWebServiceLocation() {
+        }
+        
+        @Override
+        public ConfigurableEntityFactory<ElasticJavaWebAppService> newWebClusterFactory() {
+            return new BasicConfigurableEntityFactory(MockWebService.class);
+        }
+    }
+    
+    public static class MockWebService extends TestEntityImpl implements ElasticJavaWebAppService {
+        public MockWebService() {
+        } 
+        // TODO Used by asicConfigurableEntityFactory.newEntity2, via MockWebServiceLocation.newWebClusterFactory
+        public MockWebService(Map flags, Entity parent) {
+            super(flags, parent);
+        } 
+    }
+
+    private TestApplication app;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        app = ApplicationBuilder.newManagedApp(TestApplication.class);
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+
+    @Test
+    public void testElasticClusterCreatesTestEntity() {
+        MockWebServiceLocation l = new MockWebServiceLocation();
+        app.setConfig(MockWebService.ROOT_WAR, "WAR0");
+        app.setConfig(MockWebService.NAMED_WARS, ImmutableList.of("ignore://WARn"));
+        
+        ElasticJavaWebAppService svc = 
+            new ElasticJavaWebAppService.Factory().newFactoryForLocation(l).newEntity(MutableMap.of("war", "WAR1"), app);
+        Entities.manage(svc);
+        
+        Assert.assertTrue(svc instanceof MockWebService, "expected MockWebService, got "+svc);
+        //check config has been set correctly, where overridden, and where inherited
+        Assert.assertEquals(svc.getConfig(MockWebService.ROOT_WAR), "WAR1");
+        Assert.assertEquals(svc.getConfig(MockWebService.NAMED_WARS), ImmutableList.of("ignore://WARn"));
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/ElasticJavaWebAppServiceIntegrationTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/ElasticJavaWebAppServiceIntegrationTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/ElasticJavaWebAppServiceIntegrationTest.java
new file mode 100644
index 0000000..93859a2
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/ElasticJavaWebAppServiceIntegrationTest.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.entity.webapp;
+
+import org.apache.brooklyn.entity.webapp.ElasticJavaWebAppService;
+import org.apache.brooklyn.test.HttpTestUtils;
+import org.apache.brooklyn.test.TestResourceUnavailableException;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.Entities;
+import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import brooklyn.test.entity.TestApplication;
+import brooklyn.util.collections.MutableMap;
+
+import com.google.common.collect.ImmutableList;
+
+public class ElasticJavaWebAppServiceIntegrationTest {
+
+    private LocalhostMachineProvisioningLocation loc;
+    private TestApplication app;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        app = TestApplication.Factory.newManagedInstanceForTests();
+        loc = app.newLocalhostProvisioningLocation();
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+
+    public String getTestWar() {
+        TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), "/hello-world.war");
+        return "classpath://hello-world.war";
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test(groups = "Integration")
+    // TODO a new approach to what ElasticJavaWebAppService.Factory does, giving a different entity depending on location!
+    public void testLegacyFactory() {
+        ElasticJavaWebAppService svc =
+            new ElasticJavaWebAppService.Factory().newEntity(MutableMap.of("war", getTestWar()), app);
+        Entities.manage(svc);
+        app.start(ImmutableList.of(loc));
+        
+        String url = svc.getAttribute(ElasticJavaWebAppService.ROOT_URL);
+        Assert.assertNotNull(url);
+        HttpTestUtils.assertContentEventuallyContainsText(url, "Hello");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/FilenameToWebContextMapperTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/FilenameToWebContextMapperTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/FilenameToWebContextMapperTest.java
new file mode 100644
index 0000000..e49cf12
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/FilenameToWebContextMapperTest.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.webapp;
+
+import org.apache.brooklyn.entity.webapp.FilenameToWebContextMapper;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+@Test
+public class FilenameToWebContextMapperTest {
+
+//    *   either ROOT.WAR or /       denotes root context
+//    * <p>
+//    *   anything of form  FOO.?AR  (ending .?AR) is copied with that name (unless copying not necessary)
+//    *                              and is expected to be served from /FOO
+//    * <p>
+//    *   anything of form  /FOO     (with leading slash) is expected to be served from /FOO
+//    *                              (and is copied as FOO.WAR)
+//    * <p>
+//    *   anything of form  FOO      (without a dot) is expected to be served from /FOO
+//    *                              (and is copied as FOO.WAR)
+//    * <p>                            
+//    *   otherwise <i>please note</i> behaviour may vary on different appservers;
+//    *   e.g. FOO.FOO would probably be ignored on appservers which expect a file copied across (usually),
+//    *   but served as /FOO.FOO on systems that take a deployment context.
+
+    FilenameToWebContextMapper m = new FilenameToWebContextMapper();
+    
+    private void assertMapping(String input, String context, String filename) {
+        Assert.assertEquals(m.convertDeploymentTargetNameToContext(input), context);
+        Assert.assertEquals(m.convertDeploymentTargetNameToFilename(input), filename);
+    }
+    
+    public void testRootNames() {
+        assertMapping("/", "/", "ROOT.war");
+        assertMapping("ROOT.war", "/", "ROOT.war");
+        
+        //bad ones -- some of these aren't invertible
+        assertMapping("/ROOT.war", "/ROOT.war", "ROOT.war.war");
+        assertMapping("/ROOT", "/ROOT", "ROOT.war");
+        
+        //and leave empty string alone (will cause subsequent error)
+        assertMapping("", "", "");
+    }
+
+    public void testOtherNames() {
+        assertMapping("/foo", "/foo", "foo.war");
+        assertMapping("/foo.foo", "/foo.foo", "foo.foo.war");
+        assertMapping("foo.war", "/foo", "foo.war");
+        assertMapping("foo.Ear", "/foo", "foo.Ear");
+        assertMapping("foo", "/foo", "foo.war");
+        
+        //bad ones -- some of these aren't invertible
+        assertMapping("foo.foo", "/foo.foo", "foo.foo");
+    }
+    
+    public void testInferFromUrl() {
+        Assert.assertEquals(m.findArchiveNameFromUrl("http//localhost/simple.war", false), "simple.war");
+        Assert.assertEquals(m.findArchiveNameFromUrl("http//localhost/simple.Ear?type=raw", false), "simple.Ear");
+        Assert.assertEquals(m.findArchiveNameFromUrl("http//localhost/simple.war?type=raw*other=sample.war", false), "simple.war");
+        Assert.assertEquals(m.findArchiveNameFromUrl("http//localhost/get?file=simple.war", false), "simple.war");
+        Assert.assertEquals(m.findArchiveNameFromUrl("http//localhost/get?file=simple.war&other=ignore", false), "simple.war");
+        //takes the first (but logs warning in verbose mode)
+        Assert.assertEquals(m.findArchiveNameFromUrl("http//localhost/get?file=simple.war&other=sample.war", false), "simple.war");
+        //allows hyphen
+        Assert.assertEquals(m.findArchiveNameFromUrl("http//localhost/get?file=simple-simon.war&other=sample", false), "simple-simon.war");
+        Assert.assertEquals(m.findArchiveNameFromUrl("http//localhost/get?file=simple\\simon.war&other=sample", false), "simon.war");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/HttpsSslConfigTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/HttpsSslConfigTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/HttpsSslConfigTest.java
new file mode 100644
index 0000000..d21c0d9
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/HttpsSslConfigTest.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.webapp;
+
+import org.apache.brooklyn.entity.webapp.HttpsSslConfig;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.flags.TypeCoercions;
+
+public class HttpsSslConfigTest {
+
+    @Test
+    public void testCoerce() {
+        HttpsSslConfig config = TypeCoercions.coerce(MutableMap.of("keystoreUrl", "http://foo", "keystorePassword", "b4r", "keyAlias", "baz"), 
+            HttpsSslConfig.class);
+        Assert.assertEquals(config.getKeystoreUrl(), "http://foo");
+        Assert.assertEquals(config.getKeystorePassword(), "b4r");
+        Assert.assertEquals(config.getKeyAlias(), "baz");
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/TomcatAutoScalerPolicyTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/TomcatAutoScalerPolicyTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/TomcatAutoScalerPolicyTest.java
new file mode 100644
index 0000000..2158d2d
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/TomcatAutoScalerPolicyTest.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.webapp;
+
+import static org.apache.brooklyn.test.HttpTestUtils.connectToUrl;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import org.apache.brooklyn.entity.webapp.DynamicWebAppCluster;
+import org.apache.brooklyn.entity.webapp.tomcat.TomcatServer;
+import org.apache.brooklyn.entity.webapp.tomcat.TomcatServerImpl;
+import org.apache.brooklyn.management.ManagementContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.location.LocationSpec;
+import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import brooklyn.policy.autoscaling.AutoScalerPolicy;
+import brooklyn.test.Asserts;
+import brooklyn.test.entity.TestApplication;
+import brooklyn.util.collections.MutableMap;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+public class TomcatAutoScalerPolicyTest {
+
+    @SuppressWarnings("unused")
+    private static final Logger LOG = LoggerFactory.getLogger(TomcatAutoScalerPolicyTest.class);
+
+    // TODO Test is time-sensitive: we send two web-requests in rapid succession, and expect the average workrate to
+    // be 2 msgs/sec; we then expect resizing to kick-in.
+    // P speculate that... if for some reason things are running slow (e.g. GC during that one second), then brooklyn 
+    // may not report the 2 msgs/sec.
+
+    private LocalhostMachineProvisioningLocation loc;
+    private TestApplication app;
+    private ManagementContext managementContext;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        app = TestApplication.Factory.newManagedInstanceForTests();
+        managementContext = app.getManagementContext();
+        loc = managementContext.getLocationManager().createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class)
+                .configure("name", "london"));
+    }
+    
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+    
+    @Test(groups="Integration")
+    public void testWithTomcatServers() throws Exception {
+        /*
+         * One DynamicWebAppClster with auto-scaler policy
+         * AutoScaler listening to DynamicWebAppCluster.TOTAL_REQS
+         * AutoScaler minSize 1
+         * AutoScaler upper metric 1
+         * AutoScaler lower metric 0
+         * .. send one request
+         * wait til auto-scaling complete
+         * assert cluster size 2
+         */
+
+        final DynamicWebAppCluster cluster = app.createAndManageChild(EntitySpec.create(DynamicWebAppCluster.class)
+                .configure(DynamicWebAppCluster.INITIAL_SIZE, 1)
+                .configure(DynamicWebAppCluster.MEMBER_SPEC, EntitySpec.create(TomcatServer.class)));
+
+        final AutoScalerPolicy policy = AutoScalerPolicy.builder()
+                .metric(DynamicWebAppCluster.REQUEST_COUNT_PER_NODE)
+                .metricRange(0, 1)
+                .minPoolSize(1)
+                .build();
+        cluster.addPolicy(policy);
+        
+        app.start(ImmutableList.of(loc));
+        
+        assertEquals(cluster.getCurrentSize(), (Integer)1);
+        
+        // Scaling based on *total requests* processed, rather than the requests per second.
+        // So just hit it with 2 requests.
+        // Alternatively could hit each tomcat server's URL twice per second; but that's less deterministic.
+        TomcatServer tc = (TomcatServer) Iterables.getOnlyElement(cluster.getMembers());
+        for (int i = 0; i < 2; i++) {
+            connectToUrl(tc.getAttribute(TomcatServerImpl.ROOT_URL));
+        }
+        
+        // We'll scale to two members as soon as the policy detects it.
+        // But the second member won't count in the requests-per-node until it has started up.
+        // Expect to see (if we polled at convenient times):
+        //  - zero requests per node (because haven't yet retrieved over JMX the metric)
+        //  - two requests per node, with one member
+        //  - two requests per node, with two members (one of whom is still starting up, so doesn't count)
+        //  - one request per node (i.e. two divided across the two active members)
+        Asserts.succeedsEventually(MutableMap.of("timeout", 5*60*1000), new Runnable() {
+            @Override public void run() {
+                String err = "policy="+policy.isRunning()+"; size="+cluster.getCurrentSize()+"; reqCountPerNode="+cluster.getAttribute(DynamicWebAppCluster.REQUEST_COUNT_PER_NODE);
+                assertTrue(policy.isRunning(), "err="+err);
+                assertEquals(cluster.getCurrentSize(), (Integer)2, "err="+err);
+                assertEquals(cluster.getAttribute(DynamicWebAppCluster.REQUEST_COUNT_PER_NODE), 1.0d, "err="+err);
+            }});
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/WebAppConcurrentDeployTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/WebAppConcurrentDeployTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/WebAppConcurrentDeployTest.java
new file mode 100644
index 0000000..5b64cb6
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/WebAppConcurrentDeployTest.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.webapp;
+
+import static org.testng.Assert.assertEquals;
+
+import java.net.URI;
+import java.util.Collection;
+
+import org.apache.http.client.HttpClient;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.BrooklynAppUnitTestSupport;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.BrooklynConfigKeys;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.location.Location;
+import brooklyn.location.LocationSpec;
+import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import brooklyn.test.Asserts;
+
+import org.apache.brooklyn.entity.webapp.JavaWebAppSoftwareProcess;
+import org.apache.brooklyn.entity.webapp.jboss.JBoss7Server;
+import org.apache.brooklyn.entity.webapp.tomcat.TomcatServer;
+import org.apache.brooklyn.management.Task;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.test.TestResourceUnavailableException;
+
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.http.HttpTool;
+import brooklyn.util.http.HttpToolResponse;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class WebAppConcurrentDeployTest extends BrooklynAppUnitTestSupport {
+    private Location loc;
+    
+    @Override
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        super.setUp();
+        app.config().set(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, false);
+//      tested on  loc = mgmt.getLocationRegistry().resolve("byon:(hosts=\"hostname\")");
+        loc = mgmt.getLocationManager().createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class));
+    }
+    
+    @DataProvider(name = "basicEntities")
+    public Object[][] basicEntities() {
+        return new Object[][]{
+            {EntitySpec.create(TomcatServer.class)},
+            // Hot Deploy not enabled?
+            // {EntitySpec.create(JBoss6Server.class)},
+            {EntitySpec.create(JBoss7Server.class)},
+        };
+    }
+
+    @Test(groups = "Live", dataProvider="basicEntities")
+    public void testConcurrentDeploys(EntitySpec<? extends JavaWebAppSoftwareProcess> webServerSpec) throws Exception {
+        JavaWebAppSoftwareProcess server = app.createAndManageChild(webServerSpec);
+        app.start(ImmutableList.of(loc));
+        EntityTestUtils.assertAttributeEqualsEventually(server, Attributes.SERVICE_UP, Boolean.TRUE);
+        Collection<Task<Void>> deploys = MutableList.of();
+        for (int i = 0; i < 5; i++) {
+            deploys.add(server.invoke(TomcatServer.DEPLOY, MutableMap.of("url", getTestWar(), "targetName", "/")));
+        }
+        for(Task<Void> t : deploys) {
+            t.getUnchecked();
+        }
+
+        final HttpClient client = HttpTool.httpClientBuilder().build();
+        final URI warUrl = URI.create(server.getAttribute(JavaWebAppSoftwareProcess.ROOT_URL));
+        Asserts.succeedsEventually(new Runnable() {
+            @Override
+            public void run() {
+                HttpToolResponse resp = HttpTool.httpGet(client, warUrl, ImmutableMap.<String,String>of());
+                assertEquals(resp.getResponseCode(), 200);
+            }
+        });
+    }
+    
+    public String getTestWar() {
+        TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), "/hello-world.war");
+        return "classpath://hello-world.war";
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/WebAppLiveIntegrationTest.groovy
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/WebAppLiveIntegrationTest.groovy b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/WebAppLiveIntegrationTest.groovy
new file mode 100644
index 0000000..db16cbd
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/WebAppLiveIntegrationTest.groovy
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.webapp
+
+import static brooklyn.entity.basic.ConfigKeys.*
+import static org.apache.brooklyn.entity.webapp.jboss.JBoss6Server.*
+import static org.apache.brooklyn.test.TestUtils.*
+import static java.util.concurrent.TimeUnit.*
+import static org.testng.Assert.*
+
+import java.util.concurrent.TimeUnit
+
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.testng.annotations.AfterMethod
+import org.testng.annotations.BeforeMethod
+import org.testng.annotations.DataProvider
+import org.testng.annotations.Test
+
+import brooklyn.config.BrooklynProperties
+import brooklyn.entity.Application
+import brooklyn.entity.basic.SoftwareProcess
+import brooklyn.entity.basic.Entities
+import brooklyn.entity.trait.Startable
+import org.apache.brooklyn.entity.webapp.jboss.JBoss6Server
+import org.apache.brooklyn.entity.webapp.jboss.JBoss6ServerImpl
+import org.apache.brooklyn.entity.webapp.jboss.JBoss7Server
+import org.apache.brooklyn.entity.webapp.jboss.JBoss7ServerImpl
+import org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
+import org.apache.brooklyn.entity.webapp.tomcat.TomcatServerImpl
+import brooklyn.location.Location
+import brooklyn.location.basic.BasicLocationRegistry
+import org.apache.brooklyn.test.TestUtils
+import brooklyn.test.entity.TestApplicationImpl
+import brooklyn.util.internal.TimeExtras
+
+/**
+ * This tests that we can run jboss entity on AWS.
+ */
+public class WebAppLiveIntegrationTest {
+    private static final Logger logger = LoggerFactory.getLogger(WebAppLiveIntegrationTest.class)
+
+    static { TimeExtras.init() }
+
+    public static final int DEFAULT_HTTP_PORT = 8080
+    public static final int DEFAULT_JMX_PORT = 32199
+
+    // Port increment for JBoss 6.
+    public static final int PORT_INCREMENT = 400
+
+    // The parent application entity for these tests
+    Application application = new TestApplicationImpl()
+
+    Location loc
+
+    /**
+     * Provides instances of {@link TomcatServer}, {@link JBoss6Server} and {@link JBoss7Server} to the tests below.
+     *
+     * TODO combine the data provider here with the integration tests
+     *
+     * @see WebAppIntegrationTest#basicEntities()
+     */
+    @DataProvider(name = "basicEntities")
+    public Object[][] basicEntities() {
+        TomcatServer tomcat = new TomcatServerImpl(parent:application, httpPort:DEFAULT_HTTP_PORT, jmxPort:DEFAULT_JMX_PORT)
+        JBoss6Server jboss6 = new JBoss6ServerImpl(parent:application, portIncrement:PORT_INCREMENT, jmxPort:DEFAULT_JMX_PORT)
+        JBoss7Server jboss7 = new JBoss7ServerImpl(parent:application, httpPort:DEFAULT_HTTP_PORT, jmxPort:DEFAULT_JMX_PORT)
+        return [ [ tomcat ], [ jboss6 ], [ jboss7 ] ]
+    }
+
+    private File getResource(String path) {
+        return TestUtils.getResource(path, getClass().getClassLoader());
+    }
+
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() {
+        Entities.manage(application)
+
+        BrooklynProperties props = BrooklynProperties.Factory.newDefault()
+        props.put("brooklyn.location.jclouds.aws-ec2.imagel-id", "us-east-1/ami-2342a94a")
+        props.put("brooklyn.location.jclouds.aws-ec2.image-owner", "411009282317")
+
+        loc = new BasicLocationRegistry(props).resolve("aws-ec2:us-east-1")
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void shutdown() {
+        if (application != null) Entities.destroyAll(application.getManagementContext());
+    }
+
+    @Test(groups = "Live", dataProvider="basicEntities")
+    public void testStartsWebAppInAws(final SoftwareProcess entity) {
+        entity.start([ loc ])
+        executeUntilSucceedsWithShutdown(entity, abortOnError:false, timeout:75*SECONDS, useGroovyTruth:true) {
+            assertTrue(entity.getAttribute(Startable.SERVICE_UP))
+            true
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/jboss/ControlledDynamicWebAppClusterRebindIntegrationTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/jboss/ControlledDynamicWebAppClusterRebindIntegrationTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/jboss/ControlledDynamicWebAppClusterRebindIntegrationTest.java
new file mode 100644
index 0000000..59232e6
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/jboss/ControlledDynamicWebAppClusterRebindIntegrationTest.java
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.webapp.jboss;
+
+import static org.apache.brooklyn.test.EntityTestUtils.assertAttributeEqualsEventually;
+import static org.apache.brooklyn.test.HttpTestUtils.assertHttpStatusCodeEquals;
+import static org.apache.brooklyn.test.HttpTestUtils.assertHttpStatusCodeEventuallyEquals;
+import static org.testng.Assert.assertEquals;
+
+import java.io.File;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.apache.brooklyn.entity.proxy.nginx.NginxController;
+import org.apache.brooklyn.entity.webapp.ControlledDynamicWebAppCluster;
+import org.apache.brooklyn.entity.webapp.jboss.JBoss7Server;
+import org.apache.brooklyn.test.TestResourceUnavailableException;
+import org.apache.brooklyn.test.WebAppMonitor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.SoftwareProcess;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.rebind.RebindTestUtils;
+import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import brooklyn.management.internal.LocalManagementContext;
+import brooklyn.test.entity.TestApplication;
+import brooklyn.util.internal.TimeExtras;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.io.Files;
+
+public class ControlledDynamicWebAppClusterRebindIntegrationTest {
+    private static final Logger LOG = LoggerFactory.getLogger(ControlledDynamicWebAppClusterRebindIntegrationTest.class);
+    
+    static { TimeExtras.init(); }
+
+    private LocalhostMachineProvisioningLocation localhostProvisioningLocation;
+    private TestApplication origApp;
+    private TestApplication newApp;
+    private List<WebAppMonitor> webAppMonitors = new CopyOnWriteArrayList<WebAppMonitor>();
+    private ExecutorService executor;
+    
+    private ClassLoader classLoader = getClass().getClassLoader();
+    private LocalManagementContext origManagementContext;
+    private File mementoDir;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() {
+        executor = Executors.newCachedThreadPool();
+
+        mementoDir = Files.createTempDir();
+        LOG.info("Test persisting to "+mementoDir);
+        origManagementContext = RebindTestUtils.newPersistingManagementContext(mementoDir, classLoader);
+
+        localhostProvisioningLocation = new LocalhostMachineProvisioningLocation();
+        origApp = ApplicationBuilder.newManagedApp(TestApplication.class, origManagementContext);
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        for (WebAppMonitor monitor : webAppMonitors) {
+            monitor.terminate();
+        }
+        if (executor != null) executor.shutdownNow();
+        if (newApp != null) Entities.destroyAll(newApp.getManagementContext());
+        if (origApp != null) Entities.destroyAll(origApp.getManagementContext());
+        if (mementoDir != null) RebindTestUtils.deleteMementoDir(mementoDir);
+    }
+
+    public String getTestWar() {
+        TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), "/hello-world.war");
+        return "classpath://hello-world.war";
+    }
+
+    private TestApplication rebind() throws Exception {
+        RebindTestUtils.waitForPersisted(origApp);
+        
+        // Stop the old management context, so original nginx won't interfere
+        origManagementContext.terminate();
+        
+        return (TestApplication) RebindTestUtils.rebind(mementoDir, getClass().getClassLoader());
+    }
+
+    private WebAppMonitor newWebAppMonitor(String url) {
+        WebAppMonitor monitor = new WebAppMonitor(url)
+//                .delayMillis(0)
+                .logFailures(LOG);
+        webAppMonitors.add(monitor);
+        executor.execute(monitor);
+        return monitor;
+    }
+    
+    @Test(groups = {"Integration"})
+    public void testRebindsToRunningCluster() throws Exception {
+        NginxController origNginx = origApp.createAndManageChild(EntitySpec.create(NginxController.class).configure("domain", "localhost"));
+
+        origApp.createAndManageChild(EntitySpec.create(ControlledDynamicWebAppCluster.class)
+                .configure("memberSpec", EntitySpec.create(JBoss7Server.class).configure("war", getTestWar()))
+                .configure("initialSize", 1)
+                .configure("controller", origNginx));
+        
+        origApp.start(ImmutableList.of(localhostProvisioningLocation));
+        String rootUrl = origNginx.getAttribute(JBoss7Server.ROOT_URL);
+        
+        assertHttpStatusCodeEventuallyEquals(rootUrl, 200);
+        WebAppMonitor monitor = newWebAppMonitor(rootUrl);
+        
+        // Rebind
+        newApp = rebind();
+        NginxController newNginx = (NginxController) Iterables.find(newApp.getChildren(), Predicates.instanceOf(NginxController.class));
+        ControlledDynamicWebAppCluster newCluster = (ControlledDynamicWebAppCluster) Iterables.find(newApp.getChildren(), Predicates.instanceOf(ControlledDynamicWebAppCluster.class));
+
+        assertAttributeEqualsEventually(newNginx, SoftwareProcess.SERVICE_UP, true);
+        assertHttpStatusCodeEquals(rootUrl, 200);
+
+        // Confirm the cluster is usable: we can scale-up
+        assertEquals(newCluster.getCurrentSize(), (Integer)1);
+        newCluster.resize(2);
+        
+        Iterable<Entity> newJbosses = Iterables.filter(newCluster.getCluster().getChildren(), Predicates.instanceOf(JBoss7Server.class));
+        assertEquals(Iterables.size(newJbosses), 2);
+        
+        Thread.sleep(1000);
+        for (int i = 0; i < 10; i++) {
+            assertHttpStatusCodeEquals(rootUrl, 200);
+        }
+        
+        // Ensure while doing all of this the original jboss server remained reachable
+        assertEquals(monitor.getFailures(), 0);
+        
+        // Ensure cluster is usable: we can scale back to stop the original jboss server
+        newCluster.resize(0);
+        
+        assertHttpStatusCodeEventuallyEquals(rootUrl, 404);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/jboss/DynamicWebAppClusterRebindIntegrationTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/jboss/DynamicWebAppClusterRebindIntegrationTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/jboss/DynamicWebAppClusterRebindIntegrationTest.java
new file mode 100644
index 0000000..30c0068
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/jboss/DynamicWebAppClusterRebindIntegrationTest.java
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.webapp.jboss;
+
+import static org.apache.brooklyn.test.HttpTestUtils.assertHttpStatusCodeEquals;
+import static org.apache.brooklyn.test.HttpTestUtils.assertHttpStatusCodeEventuallyEquals;
+import static org.apache.brooklyn.test.HttpTestUtils.assertUrlUnreachableEventually;
+import static org.testng.Assert.assertEquals;
+
+import java.io.File;
+import java.net.URL;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.apache.brooklyn.entity.webapp.DynamicWebAppCluster;
+import org.apache.brooklyn.entity.webapp.jboss.JBoss7Server;
+import org.apache.brooklyn.test.TestResourceUnavailableException;
+import org.apache.brooklyn.test.WebAppMonitor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.rebind.RebindTestUtils;
+import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import brooklyn.management.internal.LocalManagementContext;
+import brooklyn.test.entity.TestApplication;
+import brooklyn.util.collections.MutableMap;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.io.Files;
+
+public class DynamicWebAppClusterRebindIntegrationTest {
+    private static final Logger LOG = LoggerFactory.getLogger(DynamicWebAppClusterRebindIntegrationTest.class);
+    
+    private LocalhostMachineProvisioningLocation localhostProvisioningLocation;
+    private TestApplication origApp;
+    private TestApplication newApp;
+    private List<WebAppMonitor> webAppMonitors = new CopyOnWriteArrayList<WebAppMonitor>();
+    private ExecutorService executor;
+    
+    private ClassLoader classLoader = getClass().getClassLoader();
+    private LocalManagementContext origManagementContext;
+    private File mementoDir;
+    
+    @BeforeMethod(groups = "Integration")
+    public void setUp() {
+        executor = Executors.newCachedThreadPool();
+
+        mementoDir = Files.createTempDir();
+        origManagementContext = RebindTestUtils.newPersistingManagementContext(mementoDir, classLoader);
+
+        localhostProvisioningLocation = new LocalhostMachineProvisioningLocation();
+        origApp = ApplicationBuilder.newManagedApp(TestApplication.class, origManagementContext);
+    }
+
+    @AfterMethod(groups = "Integration", alwaysRun=true)
+    public void tearDown() throws Exception {
+        for (WebAppMonitor monitor : webAppMonitors) {
+            monitor.terminate();
+        }
+        if (executor != null) executor.shutdownNow();
+        if (newApp != null) Entities.destroyAll(newApp.getManagementContext());
+        if (origApp != null) Entities.destroyAll(origApp.getManagementContext());
+        if (mementoDir != null) RebindTestUtils.deleteMementoDir(mementoDir);
+    }
+
+    public String getTestWar() {
+        TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), "/hello-world.war");
+        return "classpath://hello-world.war";
+    }
+
+    private TestApplication rebind() throws Exception {
+        RebindTestUtils.waitForPersisted(origApp);
+        
+        // Stop the old management context, so original nginx won't interfere
+        origManagementContext.terminate();
+        
+        return (TestApplication) RebindTestUtils.rebind(mementoDir, getClass().getClassLoader());
+    }
+
+    private WebAppMonitor newWebAppMonitor(String url) {
+        WebAppMonitor monitor = new WebAppMonitor(url)
+//                .delayMillis(0)
+                .logFailures(LOG);
+        webAppMonitors.add(monitor);
+        executor.execute(monitor);
+        return monitor;
+    }
+    
+    @Test(groups = "Integration")
+    public void testRebindsToRunningCluster() throws Exception {
+        DynamicWebAppCluster origCluster = origApp.createAndManageChild(EntitySpec.create(DynamicWebAppCluster.class)
+                .configure("memberSpec", EntitySpec.create(JBoss7Server.class).configure("war", getTestWar()))
+                .configure("initialSize", 1));
+        
+        origApp.start(ImmutableList.of(localhostProvisioningLocation));
+        JBoss7Server origJboss = (JBoss7Server) Iterables.find(origCluster.getChildren(), Predicates.instanceOf(JBoss7Server.class));
+        String jbossUrl = origJboss.getAttribute(JBoss7Server.ROOT_URL);
+        
+        assertHttpStatusCodeEventuallyEquals(jbossUrl, 200);
+        WebAppMonitor monitor = newWebAppMonitor(jbossUrl);
+        
+        // Rebind
+        newApp = rebind();
+        DynamicWebAppCluster newCluster = (DynamicWebAppCluster) Iterables.find(newApp.getChildren(), Predicates.instanceOf(DynamicWebAppCluster.class));
+
+        assertHttpStatusCodeEquals(jbossUrl, 200);
+
+        // Confirm the cluster is usable: we can scale-up
+        assertEquals(newCluster.getCurrentSize(), (Integer)1);
+        newCluster.resize(2);
+
+        Iterable<Entity> newJbosses = Iterables.filter(newCluster.getChildren(), Predicates.instanceOf(JBoss7Server.class));
+        assertEquals(Iterables.size(newJbosses), 2);
+        for (Entity j : newJbosses) {
+            assertHttpStatusCodeEventuallyEquals(j.getAttribute(JBoss7Server.ROOT_URL), 200);
+        }
+
+        // Ensure while doing all of this the original jboss server remained reachable
+        assertEquals(monitor.getFailures(), 0);
+        
+        // Ensure cluster is usable: we can scale back to stop the original jboss server
+        newCluster.resize(0);
+        
+        assertUrlUnreachableEventually(jbossUrl);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6ServerAwsEc2LiveTest.java
----------------------------------------------------------------------
diff --git a/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6ServerAwsEc2LiveTest.java b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6ServerAwsEc2LiveTest.java
new file mode 100644
index 0000000..1262765
--- /dev/null
+++ b/software/webapp/src/test/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6ServerAwsEc2LiveTest.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.entity.webapp.jboss;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.testng.Assert.assertNotNull;
+
+import java.net.URL;
+
+import org.apache.brooklyn.entity.webapp.jboss.JBoss6Server;
+import org.apache.brooklyn.test.HttpTestUtils;
+import org.apache.brooklyn.test.TestResourceUnavailableException;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.AbstractEc2LiveTest;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.location.Location;
+import brooklyn.test.Asserts;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * A simple test of installing+running on AWS-EC2, using various OS distros and versions. 
+ */
+public class JBoss6ServerAwsEc2LiveTest extends AbstractEc2LiveTest {
+    
+    public String getTestWar() {
+        TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), "/hello-world-no-mapping.war");
+        return "classpath://hello-world-no-mapping.war";
+    }
+
+    @Override
+    protected void doTest(Location loc) throws Exception {
+        final JBoss6Server server = app.createAndManageChild(EntitySpec.create(JBoss6Server.class)
+                .configure("war", getTestWar()));
+        
+        app.start(ImmutableList.of(loc));
+        
+        String url = server.getAttribute(JBoss6Server.ROOT_URL);
+        
+        HttpTestUtils.assertHttpStatusCodeEventuallyEquals(url, 200);
+        HttpTestUtils.assertContentContainsText(url, "Hello");
+        
+        Asserts.succeedsEventually(new Runnable() {
+            @Override public void run() {
+                assertNotNull(server.getAttribute(JBoss6Server.REQUEST_COUNT));
+                assertNotNull(server.getAttribute(JBoss6Server.ERROR_COUNT));
+                assertNotNull(server.getAttribute(JBoss6Server.TOTAL_PROCESSING_TIME));
+                assertNotNull(server.getAttribute(JBoss6Server.MAX_PROCESSING_TIME));
+                assertNotNull(server.getAttribute(JBoss6Server.BYTES_RECEIVED));
+                assertNotNull(server.getAttribute(JBoss6Server.BYTES_SENT));
+            }});
+    }
+    
+    @Test(enabled=false)
+    public void testDummy() {} // Convince testng IDE integration that this really does have test methods  
+}