This closes #1153

WinRM docs for correct preparation of windows templates
diff --git a/LICENSE b/LICENSE
index ca60394..ef288dc 100644
--- a/LICENSE
+++ b/LICENSE
@@ -282,6 +282,13 @@
   Used under the following license: The MIT License (http://opensource.org/licenses/MIT)
   Copyright (c) Vitaly Puzrin (2011-2015)
 
+This project includes the software: marked.js
+  Available at: https://github.com/chjj/marked
+  Developed by: Christopher Jeffrey (https://github.com/chjj)
+  Version used: 0.3.1
+  Used under the following license: The MIT License (http://opensource.org/licenses/MIT)
+  Copyright (c) Christopher Jeffrey (2011-2014)
+
 This project includes the software: moment.js
   Available at: http://momentjs.com
   Developed by: Tim Wood (http://momentjs.com)
@@ -323,19 +330,11 @@
       Arpad Borsos (2012)
     Used under the BSD 2-Clause license.
 
-This project includes the software: Swagger JS
-  Available at: https://github.com/wordnik/swagger-js
-  Inclusive of: swagger.js
-  Version used: 1.0.1
-  Used under the following license: Apache License, version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
-  Copyright (c) SmartBear Software (2011-2015)
+This project includes the software: swagger
+  Used under the following license: <no license info>
 
-This project includes the software: Swagger UI
-  Available at: https://github.com/wordnik/swagger-ui
-  Inclusive of: swagger-ui.js
-  Version used: 1.0.1
-  Used under the following license: Apache License, version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
-  Copyright (c) SmartBear Software (2011-2015)
+This project includes the software: swagger-ui
+  Used under the following license: <no license info>
 
 This project includes the software: typeahead.js
   Available at: https://github.com/twitter/typeahead.js
diff --git a/brooklyn-dist/dist/licensing/extras-files b/brooklyn-dist/dist/licensing/extras-files
index bb4a3b3..cbba9c5 100644
--- a/brooklyn-dist/dist/licensing/extras-files
+++ b/brooklyn-dist/dist/licensing/extras-files
@@ -1 +1 @@
-../jsgui/src/main/license/source-inclusions.yaml:../cli/src/main/license/source-inclusions.yaml
+../../brooklyn-ui/src/main/license/source-inclusions.yaml:../../brooklyn-server/server-cli/src/main/license/source-inclusions.yaml
diff --git a/brooklyn-dist/dist/licensing/licenses/jsgui/BSD-2-Clause b/brooklyn-dist/dist/licensing/licenses/brooklyn-ui/BSD-2-Clause
similarity index 100%
rename from brooklyn-dist/dist/licensing/licenses/jsgui/BSD-2-Clause
rename to brooklyn-dist/dist/licensing/licenses/brooklyn-ui/BSD-2-Clause
diff --git a/brooklyn-dist/dist/licensing/licenses/jsgui/BSD-3-Clause b/brooklyn-dist/dist/licensing/licenses/brooklyn-ui/BSD-3-Clause
similarity index 100%
rename from brooklyn-dist/dist/licensing/licenses/jsgui/BSD-3-Clause
rename to brooklyn-dist/dist/licensing/licenses/brooklyn-ui/BSD-3-Clause
diff --git a/brooklyn-dist/dist/licensing/licenses/cli/MIT b/brooklyn-dist/dist/licensing/licenses/brooklyn-ui/MIT
similarity index 100%
copy from brooklyn-dist/dist/licensing/licenses/cli/MIT
copy to brooklyn-dist/dist/licensing/licenses/brooklyn-ui/MIT
diff --git a/brooklyn-dist/dist/licensing/licenses/jsgui/MIT b/brooklyn-dist/dist/licensing/licenses/jsgui/MIT
deleted file mode 100644
index 71dfb45..0000000
--- a/brooklyn-dist/dist/licensing/licenses/jsgui/MIT
+++ /dev/null
@@ -1,20 +0,0 @@
-The MIT License ("MIT")
-
-  Permission is hereby granted, free of charge, to any person obtaining a copy
-  of this software and associated documentation files (the "Software"), to deal
-  in the Software without restriction, including without limitation the rights
-  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-  copies of the Software, and to permit persons to whom the Software is
-  furnished to do so, subject to the following conditions:
-  
-  The above copyright notice and this permission notice shall be included in
-  all copies or substantial portions of the Software.
-  
-  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-  THE SOFTWARE.
-  
diff --git a/brooklyn-dist/dist/licensing/licenses/cli/MIT b/brooklyn-dist/dist/licensing/licenses/server-cli/MIT
similarity index 100%
rename from brooklyn-dist/dist/licensing/licenses/cli/MIT
rename to brooklyn-dist/dist/licensing/licenses/server-cli/MIT
diff --git a/brooklyn-dist/dist/licensing/projects-with-custom-licenses b/brooklyn-dist/dist/licensing/projects-with-custom-licenses
index ebf210c..97839fc 100644
--- a/brooklyn-dist/dist/licensing/projects-with-custom-licenses
+++ b/brooklyn-dist/dist/licensing/projects-with-custom-licenses
@@ -1,2 +1,2 @@
-../jsgui
-../cli
+../../brooklyn-ui
+../../brooklyn-server/server-cli
diff --git a/brooklyn-dist/dist/src/main/license/files/LICENSE b/brooklyn-dist/dist/src/main/license/files/LICENSE
index b280a7d..39db9db 100644
--- a/brooklyn-dist/dist/src/main/license/files/LICENSE
+++ b/brooklyn-dist/dist/src/main/license/files/LICENSE
@@ -232,21 +232,33 @@
   Used under the following license: Eclipse Public License, version 1.0 (http://www.eclipse.org/legal/epl-v10.html)
 
 This project includes the software: com.fasterxml.jackson.core
-  Available at: http://wiki.fasterxml.com/JacksonHome
+  Available at: http://github.com/FasterXML/jackson https://github.com/FasterXML/jackson
   Developed by: FasterXML (http://fasterxml.com/)
-  Version used: 2.4.2; 2.4.0
+  Version used: 2.4.5
+  Used under the following license: Apache License, version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.txt)
+
+This project includes the software: com.fasterxml.jackson.dataformat
+  Available at: https://github.com/FasterXML/jackson http://wiki.fasterxml.com/JacksonExtensionXmlDataBinding
+  Developed by: FasterXML (http://fasterxml.com/)
+  Version used: 2.4.5
+  Used under the following license: Apache License, version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.txt)
+
+This project includes the software: com.fasterxml.jackson.datatype
+  Available at: http://wiki.fasterxml.com/JacksonModuleJoda
+  Developed by: FasterXML (http://fasterxml.com/)
+  Version used: 2.4.5
   Used under the following license: Apache License, version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.txt)
 
 This project includes the software: com.fasterxml.jackson.jaxrs
   Available at: http://wiki.fasterxml.com/JacksonHome
   Developed by: FasterXML (http://fasterxml.com/)
-  Version used: 2.4.2
+  Version used: 2.4.5
   Used under the following license: Apache License, version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.txt)
 
 This project includes the software: com.fasterxml.jackson.module
   Available at: http://wiki.fasterxml.com/JacksonJAXBAnnotations
   Developed by: FasterXML (http://fasterxml.com/)
-  Version used: 2.4.2
+  Version used: 2.4.5
   Used under the following license: Apache License, version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.txt)
 
 This project includes the software: com.google.code.findbugs
@@ -329,13 +341,13 @@
 This project includes the software: com.sun.jersey
   Available at: https://jersey.java.net/
   Developed by: Oracle Corporation (http://www.oracle.com/)
-  Version used: 1.18.1
+  Version used: 1.19
   Used under the following license: Common Development and Distribution License, version 1.1 (http://glassfish.java.net/public/CDDL+GPL_1_1.html)
 
 This project includes the software: com.sun.jersey.contribs
   Available at: https://jersey.java.net/
   Developed by: Oracle Corporation (http://www.oracle.com/)
-  Version used: 1.18.1
+  Version used: 1.19
   Used under the following license: Common Development and Distribution License, version 1.1 (http://glassfish.java.net/public/CDDL+GPL_1_1.html)
 
 This project includes the software: com.thoughtworks.xstream
@@ -344,11 +356,6 @@
   Version used: 1.4.7
   Used under the following license: BSD License (http://xstream.codehaus.org/license.html)
 
-This project includes the software: com.wordnik
-  Available at: https://github.com/wordnik/swagger-core
-  Version used: 1.0.1
-  Used under the following license: Apache License, version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.html)
-
 This project includes the software: commons-beanutils
   Available at: http://commons.apache.org/proper/commons-beanutils/
   Developed by: The Apache Software Foundation (http://www.apache.org/)
@@ -409,6 +416,11 @@
   Version used: 0.1.0
   Used under the following license: Apache License, version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.txt)
 
+This project includes the software: io.swagger
+  Available at: https://github.com/swagger-api/swagger-core
+  Version used: 1.5.3
+  Used under the following license: Apache License, version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.html)
+
 This project includes the software: javax.annotation
   Available at: https://jcp.org/en/jsr/detail?id=250
   Version used: 1.0
@@ -426,8 +438,9 @@
   Used under the following license: CDDL + GPLv2 with classpath exception (https://glassfish.dev.java.net/nonav/public/CDDL+GPL.html)
 
 This project includes the software: javax.validation
-  Version used: 1.0.0.GA
-  Used under the following license: Apache License, version 2.0 (in-project reference: license.txt)
+  Available at: http://beanvalidation.org
+  Version used: 1.1.0.Final
+  Used under the following license: Apache License, version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.txt)
 
 This project includes the software: javax.ws.rs
   Available at: https://jsr311.java.net/
@@ -435,6 +448,12 @@
   Version used: 1.1.1
   Used under the following license: CDDL License (http://www.opensource.org/licenses/cddl1.php)
 
+This project includes the software: joda-time
+  Available at: http://joda-time.sourceforge.net
+  Developed by: Joda.org (http://www.joda.org)
+  Version used: 2.2
+  Used under the following license: Apache License, version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.txt)
+
 This project includes the software: jQuery JavaScript Library
   Available at: http://jquery.com/
   Developed by: The jQuery Foundation (http://jquery.org/)
@@ -495,6 +514,13 @@
   Used under the following license: The MIT License (http://opensource.org/licenses/MIT)
   Copyright (c) Vitaly Puzrin (2011-2015)
 
+This project includes the software: marked.js
+  Available at: https://github.com/chjj/marked
+  Developed by: Christopher Jeffrey (https://github.com/chjj)
+  Version used: 0.3.1
+  Used under the following license: The MIT License (http://opensource.org/licenses/MIT)
+  Copyright (c) Christopher Jeffrey (2011-2014)
+
 This project includes the software: moment.js
   Available at: http://momentjs.com
   Developed by: Tim Wood (http://momentjs.com)
@@ -532,7 +558,7 @@
 This project includes the software: org.apache.commons
   Available at: http://commons.apache.org/
   Developed by: The Apache Software Foundation (http://www.apache.org/)
-  Version used: 3.1; 1.4
+  Version used: 3.3.2; 1.4
   Used under the following license: Apache License, version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.txt)
 
 This project includes the software: org.apache.felix
@@ -604,12 +630,24 @@
   Version used: 1.1
   Used under the following license: Apache License, version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
 
+This project includes the software: org.codehaus.woodstox
+  Available at: http://wiki.fasterxml.com/WoodstoxStax2
+  Developed by: fasterxml.com (http://fasterxml.com)
+  Version used: 3.1.4
+  Used under the following license: BSD License (http://www.opensource.org/licenses/bsd-license.php)
+
 This project includes the software: org.eclipse.jetty
   Available at: http://www.eclipse.org/jetty
   Developed by: Webtide (http://webtide.com)
   Version used: 9.2.13.v20150730
   Used under the following license: Apache License, version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
 
+This project includes the software: org.eclipse.jetty.toolchain
+  Available at: http://www.eclipse.org/jetty
+  Developed by: Mort Bay Consulting (http://www.mortbay.com)
+  Version used: 3.1.M0
+  Used under the following license: Apache License, version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+
 This project includes the software: org.freemarker
   Available at: http://freemarker.org/
   Version used: 2.3.22
@@ -650,12 +688,6 @@
   Version used: 0.9.9-RC1
   Used under the following license: WTFPL (http://www.wtfpl.net/)
 
-This project includes the software: org.scala-lang
-  Available at: http://www.scala-lang.org/
-  Developed by: LAMP/EPFL (http://lamp.epfl.ch/)
-  Version used: 2.9.1-1
-  Used under the following license: BSD License (http://www.scala-lang.org/downloads/license.html)
-
 This project includes the software: org.slf4j
   Available at: http://www.slf4j.org
   Developed by: QOS.ch (http://www.qos.ch)
@@ -706,19 +738,11 @@
       Arpad Borsos (2012)
     Used under the BSD 2-Clause license.
 
-This project includes the software: Swagger JS
-  Available at: https://github.com/wordnik/swagger-js
-  Inclusive of: swagger.js
-  Version used: 1.0.1
-  Used under the following license: Apache License, version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
-  Copyright (c) SmartBear Software (2011-2015)
+This project includes the software: swagger
+  Used under the following license: <no license info>
 
-This project includes the software: Swagger UI
-  Available at: https://github.com/wordnik/swagger-ui
-  Inclusive of: swagger-ui.js
-  Version used: 1.0.1
-  Used under the following license: Apache License, version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
-  Copyright (c) SmartBear Software (2011-2015)
+This project includes the software: swagger-ui
+  Used under the following license: <no license info>
 
 This project includes the software: typeahead.js
   Available at: https://github.com/twitter/typeahead.js
diff --git a/brooklyn-docs/guide/misc/release-notes.md b/brooklyn-docs/guide/misc/release-notes.md
index e67048e..03ba4a3 100644
--- a/brooklyn-docs/guide/misc/release-notes.md
+++ b/brooklyn-docs/guide/misc/release-notes.md
@@ -36,6 +36,16 @@
 Classes such as HttpFeed that previously returned org.apache.brooklyn.util.core.http.HttpToolResponse in some methods now 
 return org.apache.brooklyn.util.HttpToolResponse.
 
+2. **Major:** Locations set in YAML or on a spec are no longer passed to `child.start(...)` by `AbstractApplication`;
+this has no effect in most cases as `SoftwareProcess.start` looks at local and inherited locations, but in ambiguous cases
+it means that locally defined locations are now preferred. Other classes of entities may need to do similar behaviour,
+and it means that calls to `Entity.getLocations()` in some cases will not show parent locations,
+unless discovered and set locally e.g. `start()`. The new method `Entities.getAllInheritedLocations(Entity)`
+can be used to traverse the hierarchy.  It also means that when a type in the registry (catalog) includes a location,
+and a caller references it, that location will now take priority over a location defined in a parent.
+Additionally, any locations specified in YAML extending the registered type will now *replace* locations on the referenced type;
+this means in many cases an explicit `locations: []` when extending a type will cause locations to be taken from the
+parent or application root in YAML.      
+
 For changes in prior versions, please refer to the release notes for 
 [0.8.0](/v/0.8.0-incubating/misc/release-notes.html).
-
diff --git a/brooklyn-docs/guide/yaml/example_yaml/entities/infrastructuredeploymenttestcase-entity.yaml b/brooklyn-docs/guide/yaml/example_yaml/entities/infrastructuredeploymenttestcase-entity.yaml
new file mode 100644
index 0000000..6b344da
--- /dev/null
+++ b/brooklyn-docs/guide/yaml/example_yaml/entities/infrastructuredeploymenttestcase-entity.yaml
@@ -0,0 +1,11 @@
+- type: org.apache.brooklyn.test.framework.InfrastructureDeploymentTestCase
+  brooklyn.config:
+    infrastructure.deployment.location.sensor: entity.dynamicLocation
+    infrastructure.deployment.spec:
+      $brooklyn:entitySpec:
+        - type: docker-cloud-calico
+          ...
+    infrastructure.deployment.entity.specs:
+      - $brooklyn:entitySpec:
+          type: org.apache.brooklyn.entity.software.base.VanillaSoftwareProcess
+          ...
\ No newline at end of file
diff --git a/brooklyn-docs/guide/yaml/example_yaml/entities/loopovergroupmembers-entity.yaml b/brooklyn-docs/guide/yaml/example_yaml/entities/loopovergroupmembers-entity.yaml
new file mode 100644
index 0000000..e97ab4c
--- /dev/null
+++ b/brooklyn-docs/guide/yaml/example_yaml/entities/loopovergroupmembers-entity.yaml
@@ -0,0 +1,6 @@
+- type: org.apache.brooklyn.test.framework.LoopOverGroupMembersTestCase
+  target: $brooklyn:component("infrastructure").component("child", "DockerHosts")
+  testSpec:
+    $brooklyn:entitySpec:
+      type: org.apache.brooklyn.test.framework.TestSensor
+      ...
\ No newline at end of file
diff --git a/brooklyn-docs/guide/yaml/test/index.md b/brooklyn-docs/guide/yaml/test/index.md
index ae7f818..02482b7 100644
--- a/brooklyn-docs/guide/yaml/test/index.md
+++ b/brooklyn-docs/guide/yaml/test/index.md
@@ -6,20 +6,24 @@
 - usage-examples.md
 ---
 
-Brooklyn provides a selection of basic test entities which can be used to validate Blueprints via YAML. These are divided into two groups; structural, which effect the order in which child entities are started; and validation, which are used to confirm the application is deployed as intended.
+Brooklyn provides a selection of test entities which can be used to validate Blueprints via YAML. The basic building block is a TargetableTestComponent, which is used to resolve a target. There are two different groups of entities that inherit from TargetableTestComponent. The first is structural, which effects how the tests are run, for example by affecting the order they are run in. The second group is validation, which is used to confirm the application is deployed as intended, for example by checking some sensor value.
 
 Structural test entities include:
 
 - `TestCase`  - starts child entities sequentially.
 - `ParallelTestCase` - starts child entities in parallel.
+- `LoopOverGroupMembersTestCase` - creates a TargetableTestComponent for each member of a group.
+- `InfrastructureDeploymentTestCase` - will create the specified Infrastructure and then deploy the target entity specifications there.
 
 Validation test entities include:
 
 - `TestSensor` - perform assertion on a specified sensor.
-- `TestEffector` - invoke effector on specified target entity.
+- `TestEffector` - perform assertion on response to effector call.
 - `TestHttpCall` - perform assertion on response to specified HTTP GET Request.
 - `SimpleShellCommandTest` - test assertions on the result of a shell command on the same node as the target entity.
 
+TargetableTestComponents can be chained together, with the target being inherited by the components children. For example, a ParallelTestCase could be created that has a TestHttpCall as a child. As long as the TestHttpCall itself does not have a target, it will use the target of it's parent, ParallelTestCase. Using this technique, we can build up complex test scenarios.
+
 The following sections provide details on each test entity along with examples of their use.
 
 {% include list-children.html %}
diff --git a/brooklyn-docs/guide/yaml/test/test-entities.md b/brooklyn-docs/guide/yaml/test/test-entities.md
index f8eb087..a81a3d6 100644
--- a/brooklyn-docs/guide/yaml/test/test-entities.md
+++ b/brooklyn-docs/guide/yaml/test/test-entities.md
@@ -33,6 +33,32 @@
 
 Timeouts on child entities should be set relative to the start of the `ParallelTestCase`.
 
+### LoopOverGroupMembersTestCase
+The `LoopOverGroupMembersTestCase` entity is configured with a target group and a test specification. For each member of the targeted group, the test case will create a TargetableTestComponent entity from the supplied test specification and set the components target to be the group member.
+
+{% highlight yaml %}
+{% readj example_yaml/entities/loopovergroupmembers-entity.yaml %}
+{% endhighlight %}
+
+#### Parameters
+- `target` - group who's members are to be tested, specified via DSL. For example, `$brooklyn:component("tomcat")`. See also the `targetId` parameter.
+- `targetId` - alternative to the `target` parameter which wraps the DSL component lookup requiring only the `id` be supplied. For example, `tomcat`. Please note, this must point to a group.
+- `test.spec` - The TargetableTestComponent to create for each child.
+
+
+### InfrastructureDeploymentTestCase
+The `InfrastructureDeploymentTestCase` will first create and deploy an infrastructure from the `infrastructure.deployment.spec` config. It will then retrieve a deployment location by getting the value of the infrastructures `infrastructure.deployment.location.sensor` sensor. It will then create and deploy all entities from the `infrastructure.deployment.spec` config to the deployment location.
+
+{% highlight yaml %}
+{% readj example_yaml/entities/infrastructuredeploymenttestcase-entity.yaml %}
+{% endhighlight %}
+
+#### Parameters
+
+- `infrastructure.deployment.spec` - the infrastructure to be deployed.
+- `infrastructure.deployment.entity.specs` - the entities to be deployed to the infrastructure
+- `infrastructure.deployment.location.sensor` - the name of the sensor on the infrastructure to retrieve the deployment location
+
 ## Validation Test Entities
 
 ### TestSensor
@@ -50,7 +76,7 @@
 - `assert` - assertion to perform on the specified sensor value. See section on assertions below.
 
 ### TestEffector
-The `TestEffector` entity invokes the specified effector on a target entity.
+The `TestEffector` entity invokes the specified effector on a target entity. If the result of the effector is a String, it will then perform assertions on the result.
 {% highlight yaml %}
 {% readj example_yaml/entities/testeffector-entity.yaml %}
 {% endhighlight %}
@@ -61,6 +87,7 @@
 - `timeout` - duration to wait on the effector task to complete. For example `10s`, `10m`, etc
 - `effector` - effector to invoke, for example `deploy`.
 - `params` - parameters to pass to the effector, these will depend on the entity and effector being tested. The example above shows the `url` and `targetName` parameters being passed to Tomcats `deploy` effector.
+- `assert` - assertion to perform on the returned result. See section on assertions below.
 
 ### TestHttpCall
 The `TestHttpCall` entity performs a HTTP GET on the specified URL and performs an assertion on the response.
@@ -77,7 +104,7 @@
 ### SimpleShellCommandTest
 
 The SimpleShellCommandTest runs a command on the host of the target entity.
-The script is expected not to run indefinitely, but to return a result (process exit code), along with its 
+The script is expected not to run indefinitely, but to return a result (process exit code), along with its
 standard out and error streams, which can then be tested using assertions.
 If no assertions are explicitly configured, the default is to assert a non-zero exit code.
 
@@ -119,7 +146,7 @@
        matches: .*[\d]* days.*
 ```
 
-If there is the need to make multiple assertions with the same key, the assertions can be specified 
+If there is the need to make multiple assertions with the same key, the assertions can be specified
 as a list of such maps:
 
 ```yaml
diff --git a/brooklyn-server/api/src/main/java/org/apache/brooklyn/api/entity/EntitySpec.java b/brooklyn-server/api/src/main/java/org/apache/brooklyn/api/entity/EntitySpec.java
index 7440221..58cf946 100644
--- a/brooklyn-server/api/src/main/java/org/apache/brooklyn/api/entity/EntitySpec.java
+++ b/brooklyn-server/api/src/main/java/org/apache/brooklyn/api/entity/EntitySpec.java
@@ -374,6 +374,13 @@
         return this;
     }
     
+    /** clears locations defined in the spec */
+    public <V> EntitySpec<T> clearLocations() {
+        checkMutable();
+        locations.clear();
+        return this;        
+    }
+    
     /** adds the supplied locations to the spec */
     public <V> EntitySpec<T> locations(Iterable<? extends Location> val) {
         checkMutable();
diff --git a/brooklyn-server/api/src/main/java/org/apache/brooklyn/api/internal/AbstractBrooklynObjectSpec.java b/brooklyn-server/api/src/main/java/org/apache/brooklyn/api/internal/AbstractBrooklynObjectSpec.java
index ab046d5..560c499 100644
--- a/brooklyn-server/api/src/main/java/org/apache/brooklyn/api/internal/AbstractBrooklynObjectSpec.java
+++ b/brooklyn-server/api/src/main/java/org/apache/brooklyn/api/internal/AbstractBrooklynObjectSpec.java
@@ -38,8 +38,10 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.annotations.Beta;
 import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
@@ -107,8 +109,35 @@
         return self();
     }
     
+    // TODO which semantics are correct? replace has been the behaviour;
+    // add breaks tests and adds unwanted parameters,
+    // but replacing will cause some desired parameters to be lost.
+    // i (AH) think ideally the caller should remove any parameters which
+    // have been defined as config keys, and then add the others;
+    // or actually we should always add, since this is really defining the config keys,
+    // and maybe extend the SpecParameter object to be able to advertise whether
+    // it is a CatalogConfig or merely a config key, maybe introducing displayable, or even priority 
+    // (but note part of the reason for CatalogConfig.priority is that java reflection doesn't preserve field order) .
+    // see also comments on the camp SpecParameterResolver.
+    @Beta
     public SpecT parameters(List<? extends SpecParameter<?>> parameters) {
-        this.parameters = ImmutableList.copyOf(checkNotNull(parameters, "parameters"));
+        return parametersReplace(parameters);
+    }
+    /** adds the given parameters */
+    @Beta
+    public SpecT parametersAdd(List<? extends SpecParameter<?>> parameters) {
+        // parameters follows immutable pattern, unlike the other fields
+        Builder<SpecParameter<?>> result = ImmutableList.<SpecParameter<?>>builder();
+        if (this.parameters!=null)
+            result.addAll(this.parameters);
+        result.addAll( checkNotNull(parameters, "parameters") );
+        this.parameters = result.build();
+        return self();
+    }
+    /** replaces parameters with the given */
+    @Beta
+    public SpecT parametersReplace(List<? extends SpecParameter<?>> parameters) {
+        this.parameters = ImmutableList.copyOf( checkNotNull(parameters, "parameters") );
         return self();
     }
 
diff --git a/brooklyn-server/camp/camp-base/src/main/java/org/apache/brooklyn/camp/spi/AbstractResource.java b/brooklyn-server/camp/camp-base/src/main/java/org/apache/brooklyn/camp/spi/AbstractResource.java
index 56a51f4..c24eab1 100644
--- a/brooklyn-server/camp/camp-base/src/main/java/org/apache/brooklyn/camp/spi/AbstractResource.java
+++ b/brooklyn-server/camp/camp-base/src/main/java/org/apache/brooklyn/camp/spi/AbstractResource.java
@@ -30,7 +30,6 @@
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 
 /** Superclass of CAMP resource implementation objects.
  * Typically used to hold common state of implementation objects
@@ -91,7 +90,7 @@
         return representationSkew;
     }
     public Map<String, Object> getCustomAttributes() {
-        return ImmutableMap.copyOf(customAttributes);
+        return MutableMap.copyOf(customAttributes).asUnmodifiable();
     }
     
     // setters
diff --git a/brooklyn-server/camp/camp-base/src/main/java/org/apache/brooklyn/camp/spi/pdp/DeploymentPlan.java b/brooklyn-server/camp/camp-base/src/main/java/org/apache/brooklyn/camp/spi/pdp/DeploymentPlan.java
index 792392e..7ad3164 100644
--- a/brooklyn-server/camp/camp-base/src/main/java/org/apache/brooklyn/camp/spi/pdp/DeploymentPlan.java
+++ b/brooklyn-server/camp/camp-base/src/main/java/org/apache/brooklyn/camp/spi/pdp/DeploymentPlan.java
@@ -22,13 +22,11 @@
 import java.util.List;
 import java.util.Map;
 
+import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-
 public class DeploymentPlan {
 
     String name;
@@ -107,15 +105,15 @@
     }
     
     public List<Artifact> getArtifacts() {
-        return ImmutableList.copyOf(artifacts);
+        return MutableList.copyOf(artifacts).asUnmodifiable();
     }
 
     public List<Service> getServices() {
-        return ImmutableList.copyOf(services);
+        return MutableList.copyOf(services).asUnmodifiable();
     }
 
     public Map<String, Object> getCustomAttributes() {
-        return ImmutableMap.copyOf(customAttributes);
+        return MutableMap.copyOf(customAttributes).asUnmodifiable();
     }
 
     /**
diff --git a/brooklyn-server/camp/camp-base/src/main/java/org/apache/brooklyn/camp/spi/resolve/interpret/PlanInterpretationContext.java b/brooklyn-server/camp/camp-base/src/main/java/org/apache/brooklyn/camp/spi/resolve/interpret/PlanInterpretationContext.java
index 26822aa..f45d7c6 100644
--- a/brooklyn-server/camp/camp-base/src/main/java/org/apache/brooklyn/camp/spi/resolve/interpret/PlanInterpretationContext.java
+++ b/brooklyn-server/camp/camp-base/src/main/java/org/apache/brooklyn/camp/spi/resolve/interpret/PlanInterpretationContext.java
@@ -22,9 +22,9 @@
 import java.util.Map;
 
 import org.apache.brooklyn.camp.spi.resolve.PlanInterpreter;
+import org.apache.brooklyn.util.collections.MutableMap;
 
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 
 public class PlanInterpretationContext {
 
@@ -34,7 +34,7 @@
 
     public PlanInterpretationContext(Map<String,?> originalDeploymentPlan, List<PlanInterpreter> interpreters) {
         super();
-        this.originalDeploymentPlan = ImmutableMap.copyOf(originalDeploymentPlan);
+        this.originalDeploymentPlan = MutableMap.copyOf(originalDeploymentPlan).asUnmodifiable();
         this.interpreters = ImmutableList.copyOf(interpreters);
         this.allInterpreter = new PlanInterpreter() {
             @Override
diff --git a/brooklyn-server/camp/camp-base/src/main/java/org/apache/brooklyn/camp/spi/resolve/interpret/PlanInterpretationNode.java b/brooklyn-server/camp/camp-base/src/main/java/org/apache/brooklyn/camp/spi/resolve/interpret/PlanInterpretationNode.java
index 6542b8b..2cf3d5d 100644
--- a/brooklyn-server/camp/camp-base/src/main/java/org/apache/brooklyn/camp/spi/resolve/interpret/PlanInterpretationNode.java
+++ b/brooklyn-server/camp/camp-base/src/main/java/org/apache/brooklyn/camp/spi/resolve/interpret/PlanInterpretationNode.java
@@ -27,8 +27,6 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 
 /** Helper class for {@link PlanInterpreter} instances, doing the recursive work */
@@ -228,7 +226,7 @@
 
     public void immutable() {
         if (!isChanged()) {
-            if (!testCollectionImmutable(getNewValue())) {
+            if (!checkCollectionImmutable(getNewValue())) {
                 // results of Yaml parse are not typically immutable,
                 // so force them to be changed so result of interpretation is immutable
                 changed = true;
@@ -242,19 +240,19 @@
     }
     
     private void checkImmutable(Object in) {
-        if (!testCollectionImmutable(in))
+        if (!checkCollectionImmutable(in))
             log.warn("Node original value "+in+" at "+this+" should be immutable");
     }
     
-    private static boolean testCollectionImmutable(Object in) {
-        if (in instanceof Map) return (in instanceof ImmutableMap);
-        if (in instanceof Iterable) return (in instanceof ImmutableList);
+    private static boolean checkCollectionImmutable(Object in) {
+        // not used -- input might now be UnmodifiableMap, as some args might be null, 
+        // and UnmodifiableMap is private :(  ... (and same for list)
         return true;
     }
 
     private static Object immutable(Object in) {
-        if (in instanceof Map) return ImmutableMap.copyOf((Map<?,?>)in);
-        if (in instanceof Iterable) return ImmutableList.copyOf((Iterable<?>)in);
+        if (in instanceof Map) return MutableMap.copyOf((Map<?,?>)in).asUnmodifiable();
+        if (in instanceof Iterable) return MutableList.copyOf((Iterable<?>)in).asUnmodifiable();
         return in;
     }
 
diff --git a/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java b/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
index 5840440..931c2e7 100644
--- a/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
+++ b/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
@@ -89,20 +89,20 @@
         // first build the children into an empty shell app
         List<EntitySpec<?>> childSpecs = createServiceSpecs(template, platform, loader, encounteredTypeSymbolicNames);
         for (EntitySpec<?> childSpec : childSpecs) {
-            app.child(childSpec);
+            // children get parsed and unwrapped irrespective of the NEVER_UNWRAP_APPS setting;
+            // we could support a NEVER_UNWRAP_NESTED_ENTITIES item but i don't know if there's a use case
+            app.child(EntityManagementUtils.unwrapEntity(childSpec));
         }
 
-        if (shouldUnwrap(template, app)) {
+        if (allowedToUnwrap(template, app)) {
             app = EntityManagementUtils.unwrapApplication(app);
         }
 
         return app;
     }
 
-    private boolean shouldUnwrap(AssemblyTemplate template, EntitySpec<? extends Application> app) {
-        if (Boolean.TRUE.equals(TypeCoercions.coerce(template.getCustomAttributes().get(NEVER_UNWRAP_APPS_PROPERTY), Boolean.class)))
-            return false;
-        return EntityManagementUtils.canPromoteWrappedApplication(app);
+    private boolean allowedToUnwrap(AssemblyTemplate template, EntitySpec<? extends Application> app) {
+        return !(Boolean.TRUE.equals(TypeCoercions.coerce(template.getCustomAttributes().get(NEVER_UNWRAP_APPS_PROPERTY), Boolean.class)));
     }
 
     private List<EntitySpec<?>> buildTemplateServicesAsSpecs(BrooklynClassLoadingContext loader, AssemblyTemplate template, CampPlatform platform, Set<String> encounteredRegisteredTypeIds) {
diff --git a/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java b/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java
index 8f4db88..fdc2559 100644
--- a/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java
+++ b/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java
@@ -49,6 +49,7 @@
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.mgmt.BrooklynTags;
 import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
+import org.apache.brooklyn.core.mgmt.EntityManagementUtils;
 import org.apache.brooklyn.core.mgmt.ManagementContextInjectable;
 import org.apache.brooklyn.core.mgmt.classloading.JavaBrooklynClassLoadingContext;
 import org.apache.brooklyn.core.resolve.entity.EntitySpecResolver;
@@ -168,6 +169,7 @@
             }
             throw new IllegalStateException("Unable to create spec for type " + type + ". " + msgDetails);
         }
+        spec = EntityManagementUtils.unwrapEntity(spec);
 
         populateSpec(spec, encounteredRegisteredTypeSymbolicNames);
 
@@ -207,12 +209,13 @@
                 // encounteredRegisteredTypeIds must contain the items currently being loaded (the dependency chain),
                 // but not parent items in this type already resolved.
                 EntitySpec<? extends Entity> childSpec = entityResolver.resolveSpec(encounteredRegisteredTypeIds);
-                spec.child(childSpec);
+                spec.child(EntityManagementUtils.unwrapEntity(childSpec));
             }
         }
 
-        if (source!=null)
+        if (source!=null) {
             spec.tag(BrooklynTags.newYamlSpecTag(source));
+        }
 
         if (!Strings.isBlank(name))
             spec.displayName(name);
@@ -221,9 +224,13 @@
         if (planId != null)
             spec.configure(BrooklynCampConstants.PLAN_ID, planId);
 
-        List<Location> childLocations = new BrooklynYamlLocationResolver(mgmt).resolveLocations(attrs.getAllConfig(), true);
-        if (childLocations != null)
-            spec.locations(childLocations);
+        List<Location> locations = new BrooklynYamlLocationResolver(mgmt).resolveLocations(attrs.getAllConfig(), true);
+        if (locations != null) {
+            // override locations defined in the type if locations are specified here
+            // empty list can be used by caller to clear, so they are inherited
+            spec.clearLocations();
+            spec.locations(locations);
+        }
 
         decorateSpec(spec, encounteredRegisteredTypeIds);
     }
@@ -326,7 +333,7 @@
             if (input instanceof Map)
                 return transformSpecialFlags((Map<?, ?>)input);
             else if (input instanceof Set<?>)
-                return MutableSet.of(transformSpecialFlags((Iterable<?>)input));
+                return MutableSet.copyOf(transformSpecialFlags((Iterable<?>)input));
             else if (input instanceof List<?>)
                 return MutableList.copyOf(transformSpecialFlags((Iterable<?>)input));
             else if (input instanceof Iterable<?>)
@@ -365,7 +372,9 @@
                 @SuppressWarnings("unchecked")
                 Map<String, Object> resolvedConfig = (Map<String, Object>)transformSpecialFlags(specConfig.getSpecConfiguration());
                 specConfig.setSpecConfiguration(resolvedConfig);
-                return Factory.newInstance(getLoader(), specConfig.getSpecConfiguration()).resolveSpec(encounteredRegisteredTypeIds);
+                EntitySpec<?> entitySpec = Factory.newInstance(getLoader(), specConfig.getSpecConfiguration()).resolveSpec(encounteredRegisteredTypeIds);
+                
+                return EntityManagementUtils.unwrapEntity(entitySpec);
             }
             if (flag instanceof ManagementContextInjectable) {
                 log.debug("Injecting Brooklyn management context info object: {}", flag);
diff --git a/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynEntityDecorationResolver.java b/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynEntityDecorationResolver.java
index 6734875..4913cb1 100644
--- a/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynEntityDecorationResolver.java
+++ b/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynEntityDecorationResolver.java
@@ -186,6 +186,9 @@
         @Override
         public void decorate(EntitySpec<?> entitySpec, ConfigBag attrs) {
             List<? extends SpecParameter<?>> explicitParams = buildListOfTheseDecorationsFromEntityAttributes(attrs);
+            // TODO see discussion at EntitySpec.parameters; 
+            // maybe we should instead inherit always, or 
+            // inherit except where it is set as config and then add the new explicit ones
             if (!explicitParams.isEmpty()) {
                 entitySpec.parameters(explicitParams);
             }
diff --git a/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampResolver.java b/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampResolver.java
index 602baa3..7523343 100644
--- a/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampResolver.java
+++ b/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampResolver.java
@@ -20,7 +20,6 @@
 
 import java.util.Set;
 
-import com.google.common.collect.Iterables;
 import org.apache.brooklyn.api.entity.Application;
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.entity.EntitySpec;
@@ -131,15 +130,16 @@
         if (instantiator instanceof AssemblyTemplateSpecInstantiator) {
             EntitySpec<? extends Application> appSpec = ((AssemblyTemplateSpecInstantiator)instantiator).createApplicationSpec(at, camp, loader, encounteredTypes);
 
-            if (!isApplication && EntityManagementUtils.canPromoteChildrenInWrappedApplication(appSpec)) {
-                EntitySpec<?> childSpec = Iterables.getOnlyElement(appSpec.getChildren());
-                EntityManagementUtils.mergeWrapperParentSpecToChildEntity(appSpec, childSpec);
-                return childSpec;
-            }
+            // above will unwrap but only if it's an Application (and it's permitted); 
+            // but it doesn't know whether we need an App or if an Entity is okay  
+            if (!isApplication) return EntityManagementUtils.unwrapEntity(appSpec);
+            // if we need an App then definitely *don't* unwrap here because
+            // the instantiator will have done that, and it knows if the plan
+            // specified a wrapped app explicitly (whereas we don't easily know that here!)
             return appSpec;
             
         } else {
-            throw new IllegalStateException("Unable to instantiate YAML; incompatible instantiator "+instantiator+" for "+at);
+            throw new IllegalStateException("Unable to instantiate YAML; invalid type or parameters in plan:\n"+plan);
         }
 
     }
diff --git a/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslDeferredSupplier.java b/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslDeferredSupplier.java
index 65bf561..a417e32 100644
--- a/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslDeferredSupplier.java
+++ b/brooklyn-server/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslDeferredSupplier.java
@@ -18,7 +18,10 @@
  */
 package org.apache.brooklyn.camp.brooklyn.spi.dsl;
 
+import java.io.IOException;
+import java.io.ObjectInputStream;
 import java.io.Serializable;
+import java.util.concurrent.locks.ReentrantLock;
 
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.mgmt.ExecutionContext;
@@ -55,6 +58,11 @@
  * and should not accessed until after the components / entities are created 
  * and are being started.
  * (TODO the precise semantics of this are under development.)
+ * 
+ * The threading model is that only one thread can call {@link #get()} at a time. An interruptible
+ * lock is obtained using {@link #lock} for the duration of that method. It is important to not
+ * use {@code synchronized} because that is not interruptible - if someone tries to get the value
+ * and interrupts after a short wait, then we must release the lock immediately and return.
  * <p>
  **/
 public abstract class BrooklynDslDeferredSupplier<T> implements DeferredSupplier<T>, TaskFactory<Task<T>>, Serializable {
@@ -63,6 +71,15 @@
 
     private static final Logger log = LoggerFactory.getLogger(BrooklynDslDeferredSupplier.class);
 
+    /**
+     * Lock to be used, rather than {@code synchronized} blocks, for anything long-running.
+     * Use {@link #getLock()} rather than this field directly, to ensure it is reinitialised 
+     * after rebinding.
+     * 
+     * @see https://issues.apache.org/jira/browse/BROOKLYN-214
+     */
+    private transient ReentrantLock lock;
+    
     // TODO json of this object should *be* this, not wrapped this ($brooklyn:literal is a bit of a hack, though it might work!)
     @JsonInclude
     @JsonProperty(value="$brooklyn:literal")
@@ -72,8 +89,9 @@
     public BrooklynDslDeferredSupplier() {
         PlanInterpretationNode sourceNode = BrooklynDslInterpreter.currentNode();
         dsl = sourceNode!=null ? sourceNode.getOriginalValue() : null;
+        lock = new ReentrantLock();
     }
-
+    
     /** returns the current entity; for use in implementations of {@link #get()} */
     protected final static EntityInternal entity() {
         return (EntityInternal) BrooklynTaskTags.getTargetOrContextEntity(Tasks.current());
@@ -88,7 +106,13 @@
     }
 
     @Override
-    public final synchronized T get() {
+    public final T get() {
+        try {
+            getLock().lockInterruptibly();
+        } catch (InterruptedException e) {
+            throw Exceptions.propagate(e);
+        }
+        
         try {
             if (log.isDebugEnabled())
                 log.debug("Queuing task to resolve "+dsl);
@@ -110,9 +134,21 @@
 
         } catch (Exception e) {
             throw Exceptions.propagate(e);
+        } finally {
+            getLock().unlock();
         }
     }
 
+    // Use this method, rather than the direct field, to ensure it is initialised after rebinding.
+    protected ReentrantLock getLock() {
+        synchronized (this) {
+            if (lock == null) {
+                lock = new ReentrantLock();
+            }
+        }
+        return lock;
+    }
+
     @Override
     public abstract Task<T> newTask();
 
diff --git a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlTest.java b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlTest.java
index 909564c..4478f2b 100644
--- a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlTest.java
+++ b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/AbstractYamlTest.java
@@ -20,6 +20,7 @@
 
 import java.io.Reader;
 import java.io.StringReader;
+import java.util.Map;
 import java.util.Set;
 
 import org.apache.brooklyn.api.catalog.BrooklynCatalog;
@@ -115,11 +116,14 @@
     }
 
     protected Entity createAndStartApplication(String input) throws Exception {
+        return createAndStartApplication(input, MutableMap.<String,String>of());
+    }
+    protected Entity createAndStartApplication(String input, Map<String,String> startParameters) throws Exception {
         EntitySpec<?> spec = 
             mgmt().getTypeRegistry().createSpecFromPlan(CampTypePlanTransformer.FORMAT, input, RegisteredTypeLoadingContexts.spec(Application.class), EntitySpec.class);
         final Entity app = brooklynMgmt.getEntityManager().createEntity(spec);
         // start the app (happens automatically if we use camp to instantiate, but not if we use crate spec approach)
-        app.invoke(Startable.START, MutableMap.<String,String>of()).get();
+        app.invoke(Startable.START, startParameters).get();
         return app;
     }
 
diff --git a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/DependentConfigPollingYamlTest.java b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/DependentConfigPollingYamlTest.java
new file mode 100644
index 0000000..10df5f0
--- /dev/null
+++ b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/DependentConfigPollingYamlTest.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.camp.brooklyn;
+
+import static org.testng.Assert.assertTrue;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.time.Duration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Iterables;
+
+@Test
+public class DependentConfigPollingYamlTest extends AbstractYamlTest {
+    private static final Logger log = LoggerFactory.getLogger(DependentConfigPollingYamlTest.class);
+    
+    private ExecutorService executor;
+
+    @BeforeMethod(alwaysRun = true)
+    @Override
+    public void setUp() {
+        super.setUp();
+        executor = Executors.newCachedThreadPool();
+    }
+            
+    @AfterMethod(alwaysRun = true)
+    @Override
+    public void tearDown() {
+        if (executor != null) executor.shutdownNow();
+        super.tearDown();
+    }
+            
+    // Test for BROOKLYN-214. Previously, the brief Tasks.resolving would cause a thread to be
+    // leaked. This was because it would call into BrooklynDslDeferredSupplier.get, which would
+    // wait on a synchronized block and thus not be interruptible - the thread would be consumed
+    // forever, until the attributeWhenReady returned true!
+    //
+    // Integration test, because takes several seconds.
+    @Test(groups="Integration")
+    public void testResolveAttributeWhenReadyWithTimeoutDoesNotLeaveThreadRunning() throws Exception {
+        String yaml = Joiner.on("\n").join(
+                "services:",
+                "- type: org.apache.brooklyn.core.test.entity.TestEntity",
+                "  id: myentity",
+                "  brooklyn.config:",
+                "    test.confName: $brooklyn:entity(\"myentity\").attributeWhenReady(\"mysensor\")");
+        
+        final Entity app = createAndStartApplication(yaml);
+        final TestEntity entity = (TestEntity) Iterables.getOnlyElement(app.getChildren());
+
+        // Cause a thread to block, getting the config - previousy (before fixing 214) this would be in
+        // the synchronized block if BrooklynDslDeferredSupplier.get().
+        // The sleep is to ensure we really did get into the locking code.
+        executor.submit(new Callable<Object>() {
+            public Object call() {
+                return entity.config().get(TestEntity.CONF_NAME);
+            }});
+        Thread.sleep(100);
+        
+        // Try to resolve the value many times, each in its own task, but with a short timeout for each.
+        final int numIterations = 20;
+        final int preNumThreads = Thread.activeCount();
+        
+        for (int i = 0; i < numIterations; i++) {
+            // Same as RestValueResolver.getImmediateValue
+            Tasks.resolving(entity.config().getRaw(TestEntity.CONF_NAME).get())
+                    .as(Object.class)
+                    .defaultValue("UNRESOLVED")
+                    .timeout(Duration.millis(100))
+                    .context(entity)
+                    .swallowExceptions()
+                    .get();
+        }
+
+        // Confirm we haven't left threads behind.
+        Asserts.succeedsEventually(new Runnable() {
+            public void run() {
+                int postNumThreads = Thread.activeCount();
+                String msg = "pre="+preNumThreads+"; post="+postNumThreads+"; iterations="+numIterations;
+                log.info(msg);
+                assertTrue(postNumThreads < preNumThreads + (numIterations / 2), msg);
+            }});
+    }
+
+    @Override
+    protected Logger getLogger() {
+        return log;
+    }
+}
diff --git a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EntitiesYamlTest.java b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EntitiesYamlTest.java
index 8131208..af2faf9 100644
--- a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EntitiesYamlTest.java
+++ b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/EntitiesYamlTest.java
@@ -60,6 +60,8 @@
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.guava.Functionals;
 import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.stream.Streams;
+import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes;
 import org.apache.brooklyn.util.time.Duration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -100,7 +102,6 @@
         setupAndCheckTestEntityInBasicYamlWith();
     }
 
-    @SuppressWarnings("unchecked")
     @Test
     public void testBrooklynConfig() throws Exception {
         Entity testEntity = setupAndCheckTestEntityInBasicYamlWith( 
@@ -113,6 +114,17 @@
             "      - dogs",
             "      - cats",
             "      - badgers",
+            "    test.confSetPlain: !!set",
+            "      ? square",
+            "      ? circle",
+            "      ? triangle",
+            "    test.confMapThing:",
+            "      foo: bar",
+            "      baz: qux",
+            "    test.confListThing:",
+            "      - dogs",
+            "      - cats",
+            "      - badgers",
             "    test.confSetThing: !!set",
             "      ? square",
             "      ? circle",
@@ -120,14 +132,13 @@
             "    test.confObject: 5");
         
         Assert.assertEquals(testEntity.getConfig(TestEntity.CONF_NAME), "Test Entity Name");
-        List<String> list = testEntity.getConfig(TestEntity.CONF_LIST_PLAIN);
-        Assert.assertEquals(list, ImmutableList.of("dogs", "cats", "badgers"));
-        Map<String, String> map = testEntity.getConfig(TestEntity.CONF_MAP_PLAIN);
-        Assert.assertEquals(map, ImmutableMap.of("foo", "bar", "baz", "qux"));
-        Set<String> set = (Set<String>)testEntity.getConfig(TestEntity.CONF_SET_THING);
-        Assert.assertEquals(set, ImmutableSet.of("square", "circle", "triangle"));
-        Object object = testEntity.getConfig(TestEntity.CONF_OBJECT);
-        Assert.assertEquals(object, 5);
+        Assert.assertEquals(testEntity.getConfig(TestEntity.CONF_OBJECT), 5);
+        Assert.assertEquals(testEntity.getConfig(TestEntity.CONF_LIST_PLAIN), ImmutableList.of("dogs", "cats", "badgers"));
+        Assert.assertEquals(testEntity.getConfig(TestEntity.CONF_MAP_PLAIN), ImmutableMap.of("foo", "bar", "baz", "qux"));
+        Assert.assertEquals(testEntity.getConfig(TestEntity.CONF_SET_PLAIN), ImmutableSet.of("square", "circle", "triangle"));
+        Assert.assertEquals(testEntity.getConfig(TestEntity.CONF_LIST_THING), ImmutableList.of("dogs", "cats", "badgers"));
+        Assert.assertEquals(testEntity.getConfig(TestEntity.CONF_MAP_THING), ImmutableMap.of("foo", "bar", "baz", "qux"));
+        Assert.assertEquals(testEntity.getConfig(TestEntity.CONF_SET_THING), ImmutableSet.of("square", "circle", "triangle"));
     }
 
     @Test
@@ -592,7 +603,7 @@
         Assert.assertEquals(app.getChildren().size(), 1);
         Entity entity = app.getChildren().iterator().next();
         Assert.assertNotNull(entity);
-        Assert.assertEquals(entity.getLocations().size(), 1);
+        Assert.assertEquals(entity.getLocations().size(), 0);
     }
 
     @Test
@@ -631,7 +642,8 @@
         Assert.assertEquals(app.getChildren().size(), 1);
         Entity entity = app.getChildren().iterator().next();
         Assert.assertNotNull(entity);
-        Assert.assertEquals(entity.getLocations().size(), 2);
+        // 2016-01 locations now not set on entity unless explicitly passed to "start" 
+        Assert.assertEquals(entity.getLocations().size(), 0);
     }
 
     @Test
@@ -666,6 +678,26 @@
         Assert.assertEquals(app.getChildren().size(), 1);
         Entity entity = app.getChildren().iterator().next();
         
+        Assert.assertEquals(entity.getLocations().size(), 1);
+        Iterator<Location> entityLocationIterator = entity.getLocations().iterator();
+        Assert.assertEquals(entityLocationIterator.next().getDisplayName(), "localhost name");
+        
+        Location appLocation = app.getLocations().iterator().next();
+        Assert.assertEquals(appLocation.getDisplayName(), "byon name");
+    }
+
+    @Test
+    public void testWithEntityLocationsAndStartInLocation() throws Exception {
+        Entity app = createAndStartApplication(Streams.readFully(loadYaml("test-entity-basic-template.yaml",  
+            "  location: localhost:(name=localhost name)")),
+            // must pass as JSON list because otherwise the comma confuses the list parser
+            MutableMap.of("locations", "[ "+JavaStringEscapes.wrapJavaString(
+                "byon:(hosts=\"1.1.1.1\", name=\"byon name\")")+" ]") );
+        waitForApplicationTasks(app);
+        Assert.assertEquals(app.getLocations().size(), 1);
+        Assert.assertEquals(app.getChildren().size(), 1);
+        Entity entity = app.getChildren().iterator().next();
+        
         Assert.assertEquals(entity.getLocations().size(), 2);
         Iterator<Location> entityLocationIterator = entity.getLocations().iterator();
         Assert.assertEquals(entityLocationIterator.next().getDisplayName(), "localhost name");
diff --git a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ExternalConfigYamlTest.java b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ExternalConfigYamlTest.java
index 210f158..66d3cfe 100644
--- a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ExternalConfigYamlTest.java
+++ b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ExternalConfigYamlTest.java
@@ -24,7 +24,9 @@
 import java.io.StringReader;
 import java.util.Map;
 
-import com.google.common.collect.Iterables;
+import org.apache.brooklyn.api.catalog.CatalogItem;
+import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle;
+import org.apache.brooklyn.api.catalog.CatalogItem.CatalogItemType;
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.mgmt.ExecutionContext;
 import org.apache.brooklyn.api.mgmt.ManagementContext;
@@ -33,7 +35,6 @@
 import org.apache.brooklyn.core.config.external.AbstractExternalConfigSupplier;
 import org.apache.brooklyn.core.config.external.ExternalConfigSupplier;
 import org.apache.brooklyn.core.internal.BrooklynProperties;
-import org.apache.brooklyn.core.location.AbstractLocation;
 import org.apache.brooklyn.core.location.cloud.CloudLocationConfig;
 import org.apache.brooklyn.core.mgmt.internal.CampYamlParser;
 import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
@@ -48,10 +49,17 @@
 import org.testng.annotations.Test;
 
 import com.google.common.base.Joiner;
+import com.google.common.collect.Iterables;
 
 @Test
 public class ExternalConfigYamlTest extends AbstractYamlTest {
     private static final Logger log = LoggerFactory.getLogger(ExternalConfigYamlTest.class);
+    
+    // Choose a small jar; it is downloaded in some tests.
+    // Pick an OSGi bundle that is not part of core brooklyn.
+    private static final String LIBRARY_URL = "https://repository.apache.org/content/groups/public/org/apache/logging/log4j/log4j-api/2.5/log4j-api-2.5.jar";
+    private static final String LIBRARY_SYMBOLIC_NAME = "org.apache.logging.log4j.api";
+    private static final String LIBRARY_VERSION = "2.5.0";
 
     @Override
     protected LocalManagementContext newTestManagementContext() {
@@ -60,8 +68,19 @@
         props.put("brooklyn.external.myprovider.mykey", "myval");
         props.put("brooklyn.external.myproviderWithoutMapArg", MyExternalConfigSupplierWithoutMapArg.class.getName());
 
+        props.put("brooklyn.external.myprovider.myCatalogId", "myId");
+        props.put("brooklyn.external.myprovider.myCatalogItemType", "template");
+        props.put("brooklyn.external.myprovider.myCatalogVersion", "1.2");
+        props.put("brooklyn.external.myprovider.myCatalogDescription", "myDescription");
+        props.put("brooklyn.external.myprovider.myCatalogDisplayName", "myDisplayName");
+        props.put("brooklyn.external.myprovider.myCatalogIconUrl", "classpath:///myIconUrl.png");
+        props.put("brooklyn.external.myprovider.myCatalogLibraryUrl", LIBRARY_URL);
+        props.put("brooklyn.external.myprovider.myCatalogLibraryName", LIBRARY_SYMBOLIC_NAME);
+        props.put("brooklyn.external.myprovider.myCatalogLibraryVersion", LIBRARY_VERSION);
+
         return LocalManagementContextForTests.builder(true)
                 .useProperties(props)
+                .disableOsgi(false)
                 .build();
     }
 
@@ -108,6 +127,97 @@
         assertEquals(Iterables.getOnlyElement( app.getLocations() ).config().get(MY_CONFIG_KEY), "myval");
     }
 
+    // Will download the given catalog library jar
+    @Test(groups="Integration")
+    public void testExternalisedCatalogConfigReferencedFromYaml() throws Exception {
+        String yaml = Joiner.on("\n").join(
+                "brooklyn.catalog:",
+                "    id: $brooklyn:external(\"myprovider\", \"myCatalogId\")",
+                "    itemType: $brooklyn:external(\"myprovider\", \"myCatalogItemType\")",
+                "    version: $brooklyn:external(\"myprovider\", \"myCatalogVersion\")",
+                "    description: $brooklyn:external(\"myprovider\", \"myCatalogDescription\")",
+                "    displayName: $brooklyn:external(\"myprovider\", \"myCatalogDisplayName\")",
+                "    iconUrl: $brooklyn:external(\"myprovider\", \"myCatalogIconUrl\")",
+                "    brooklyn.libraries:",
+                "    - $brooklyn:external(\"myprovider\", \"myCatalogLibraryUrl\")",
+                "",
+                "    item:",
+                "      services:",
+                "      - type: brooklyn.entity.database.mysql.MySqlNode");
+
+        catalog.addItems(yaml);
+
+        CatalogItem<Object, Object> item = Iterables.getOnlyElement(catalog.getCatalogItems());
+        CatalogBundle bundle = Iterables.getOnlyElement(item.getLibraries());
+        assertEquals(item.getId(), "myId:1.2");
+        assertEquals(item.getCatalogItemType(), CatalogItemType.TEMPLATE);
+        assertEquals(item.getVersion(), "1.2");
+        assertEquals(item.getDescription(), "myDescription");
+        assertEquals(item.getDisplayName(), "myDisplayName");
+        assertEquals(item.getIconUrl(), "classpath:///myIconUrl.png");
+        assertEquals(bundle.getUrl(), LIBRARY_URL);
+    }
+
+    // Will download the given catalog library jar
+    @Test(groups="Integration")
+    public void testExternalisedCatalogConfigReferencedFromYamlWithLibraryMap() throws Exception {
+        String yaml = Joiner.on("\n").join(
+                "brooklyn.catalog:",
+                "    id: myid",
+                "    itemType: template",
+                "    version: 1.2",
+                "    description: myDescription",
+                "    displayName: myDisplayName",
+                "    iconUrl: classpath:///myIconUrl.png",
+                "    brooklyn.libraries:",
+                "    - name: $brooklyn:external(\"myprovider\", \"myCatalogLibraryName\")",
+                "      version: $brooklyn:external(\"myprovider\", \"myCatalogLibraryVersion\")",
+                "      url: $brooklyn:external(\"myprovider\", \"myCatalogLibraryUrl\")",
+                "",
+                "    item:",
+                "      services:",
+                "      - type: brooklyn.entity.database.mysql.MySqlNode");
+
+        catalog.addItems(yaml);
+
+        CatalogItem<Object, Object> item = Iterables.getOnlyElement(catalog.getCatalogItems());
+        CatalogBundle bundle = Iterables.getOnlyElement(item.getLibraries());
+        assertEquals(bundle.getUrl(), LIBRARY_URL);
+        assertEquals(bundle.getSymbolicName(), LIBRARY_SYMBOLIC_NAME);
+        assertEquals(bundle.getVersion(), LIBRARY_VERSION);
+    }
+
+    // Will download the given catalog library jar
+    // Confirms "normal" behaviour, when all values in the catalog are hard-coded rather than using external config.
+    @Test(groups="Integration")
+    public void testNonExternalisedCatalogConfigReferencedFromYaml() throws Exception {
+        String yaml = Joiner.on("\n").join(
+                "brooklyn.catalog:",
+                "  id: osgi.test",
+                "  itemType: template",
+                "  version: 1.3",
+                "  description: CentOS 6.6 With GUI - 1.3",
+                "  displayName: CentOS 6.6",
+                "  iconUrl: classpath:///centos.png",
+                "  brooklyn.libraries:",
+                "  - " + LIBRARY_URL,
+                "",
+                "  item:",
+                "    services:",
+                "    - type: brooklyn.entity.database.mysql.MySqlNode");
+
+        catalog.addItems(yaml);
+
+        CatalogItem<Object, Object> item = Iterables.getOnlyElement(catalog.getCatalogItems());
+        assertEquals(item.getId(), "osgi.test:1.3");
+        assertEquals(item.getCatalogItemType(), CatalogItemType.TEMPLATE);
+        assertEquals(item.getVersion(), "1.3");
+        assertEquals(item.getDescription(), "CentOS 6.6 With GUI - 1.3");
+        assertEquals(item.getDisplayName(), "CentOS 6.6");
+        assertEquals(item.getIconUrl(), "classpath:///centos.png");
+        assertEquals(Iterables.getOnlyElement(item.getLibraries()).getUrl(), LIBRARY_URL);
+    }
+
     @Test(groups="Integration")
     public void testExternalisedLocationConfigSetViaProvisioningPropertiesReferencedFromYaml() throws Exception {
         String yaml = Joiner.on("\n").join(
diff --git a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/LocationsYamlTest.java b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/LocationsYamlTest.java
index 269ff16..371a477 100644
--- a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/LocationsYamlTest.java
+++ b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/LocationsYamlTest.java
@@ -28,15 +28,16 @@
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.location.Location;
 import org.apache.brooklyn.api.location.MachineLocation;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.Assert;
-import org.testng.annotations.Test;
+import org.apache.brooklyn.core.entity.Entities;
 import org.apache.brooklyn.location.byon.FixedListMachineProvisioningLocation;
 import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
 import org.apache.brooklyn.location.multi.MultiLocation;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
@@ -49,7 +50,7 @@
         String yaml = 
                 "location: localhost\n"+
                 "services:\n"+
-                "- serviceType: org.apache.brooklyn.core.test.entity.TestEntity\n";
+                "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
         
         Entity app = createStartWaitAndLogApplication(new StringReader(yaml));
         LocalhostMachineProvisioningLocation loc = (LocalhostMachineProvisioningLocation) Iterables.getOnlyElement(app.getLocations());
@@ -61,7 +62,7 @@
         String yaml = 
                 "location: localhost:(name=myname)\n"+
                 "services:\n"+
-                "- serviceType: org.apache.brooklyn.core.test.entity.TestEntity\n";
+                "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
         
         Entity app = createStartWaitAndLogApplication(new StringReader(yaml));
         LocalhostMachineProvisioningLocation loc = (LocalhostMachineProvisioningLocation) Iterables.getOnlyElement(app.getLocations());
@@ -74,7 +75,7 @@
                 "location:\n"+
                 "  localhost\n"+
                 "services:\n"+
-                "- serviceType: org.apache.brooklyn.core.test.entity.TestEntity\n";
+                "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
         
         Entity app = createStartWaitAndLogApplication(new StringReader(yaml));
         LocalhostMachineProvisioningLocation loc = (LocalhostMachineProvisioningLocation) Iterables.getOnlyElement(app.getLocations());
@@ -88,7 +89,7 @@
                 "- localhost:(name=loc1)\n"+
                 "- localhost:(name=loc2)\n"+
                 "services:\n"+
-                "- serviceType: org.apache.brooklyn.core.test.entity.TestEntity\n";
+                "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
         
         Entity app = createStartWaitAndLogApplication(new StringReader(yaml));
         List<Location> locs = ImmutableList.copyOf(app.getLocations());
@@ -107,7 +108,7 @@
                 "    displayName: myname\n"+
                 "    myconfkey: myconfval\n"+
                 "services:\n"+
-                "- serviceType: org.apache.brooklyn.core.test.entity.TestEntity\n";
+                "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
         
         Entity app = createStartWaitAndLogApplication(new StringReader(yaml));
         LocalhostMachineProvisioningLocation loc = (LocalhostMachineProvisioningLocation) Iterables.getOnlyElement(app.getLocations());
@@ -126,7 +127,7 @@
                 "    displayName: myname2\n"+
                 "    myconfkey: myconfval2\n"+
                 "services:\n"+
-                "- serviceType: org.apache.brooklyn.core.test.entity.TestEntity\n";
+                "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
         
         Entity app = createStartWaitAndLogApplication(new StringReader(yaml));
         List<Location> locs = ImmutableList.copyOf(app.getLocations());
@@ -145,7 +146,7 @@
         String yaml = 
                 "location: \n"+
                 "services:\n"+
-                "- serviceType: org.apache.brooklyn.core.test.entity.TestEntity\n";
+                "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
         
         Entity app = createStartWaitAndLogApplication(new StringReader(yaml));
         assertTrue(app.getLocations().isEmpty(), "locs="+app.getLocations());
@@ -158,7 +159,7 @@
                 "locations:\n"+
                 "- localhost\n"+
                 "services:\n"+
-                "- serviceType: org.apache.brooklyn.core.test.entity.TestEntity\n";
+                "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
         
         try {
             createStartWaitAndLogApplication(new StringReader(yaml));
@@ -174,7 +175,7 @@
                 "location:\n"+
                 "- localhost\n"+
                 "services:\n"+
-                "- serviceType: org.apache.brooklyn.core.test.entity.TestEntity\n";
+                "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
         
         try {
             createStartWaitAndLogApplication(new StringReader(yaml));
@@ -189,11 +190,11 @@
                 "locations:\n"+
                 "- localhost:(name=loc1)\n"+
                 "services:\n"+
-                "- serviceType: org.apache.brooklyn.core.test.entity.TestEntity\n";
+                "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
         
         Entity app = createStartWaitAndLogApplication(new StringReader(yaml));
         Entity child = Iterables.getOnlyElement(app.getChildren());
-        LocalhostMachineProvisioningLocation loc = (LocalhostMachineProvisioningLocation) Iterables.getOnlyElement(child.getLocations());
+        LocalhostMachineProvisioningLocation loc = (LocalhostMachineProvisioningLocation) Iterables.getOnlyElement(Entities.getAllInheritedLocations(child));
         assertEquals(loc.getDisplayName(), "loc1");
     }
 
@@ -208,11 +209,11 @@
                 "    - 127.0.0.1\n"+
                 "    - brooklyn@127.0.0.2\n"+
                 "services:\n"+
-                "- serviceType: org.apache.brooklyn.core.test.entity.TestEntity\n";
+                "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
         
         Entity app = createStartWaitAndLogApplication(new StringReader(yaml));
         Entity child = Iterables.getOnlyElement(app.getChildren());
-        FixedListMachineProvisioningLocation<?> loc = (FixedListMachineProvisioningLocation<?>) Iterables.getOnlyElement(child.getLocations());
+        FixedListMachineProvisioningLocation<?> loc = (FixedListMachineProvisioningLocation<?>) Iterables.getOnlyElement(Entities.getAllInheritedLocations(child));
         Assert.assertEquals(loc.getChildren().size(), 2);
         
         SshMachineLocation l1 = (SshMachineLocation)loc.obtain();
@@ -229,11 +230,11 @@
                 "    user: root\n"+
                 "    hosts: \"{127.0.{0,127}.{1-2},brooklyn@127.0.0.127}\"\n"+
                 "services:\n"+
-                "- serviceType: org.apache.brooklyn.core.test.entity.TestEntity\n";
+                "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
         
         Entity app = createStartWaitAndLogApplication(new StringReader(yaml));
         Entity child = Iterables.getOnlyElement(app.getChildren());
-        FixedListMachineProvisioningLocation<?> loc = (FixedListMachineProvisioningLocation<?>) Iterables.getOnlyElement(child.getLocations());
+        FixedListMachineProvisioningLocation<?> loc = (FixedListMachineProvisioningLocation<?>) Iterables.getOnlyElement(Entities.getAllInheritedLocations(child));
         Assert.assertEquals(loc.getChildren().size(), 5);
         
         assertUserAddress((SshMachineLocation)loc.obtain(), "root", "127.0.0.1");
@@ -257,11 +258,11 @@
                 "      hosts:\n"+
                 "      - 127.0.0.127\n"+
                 "services:\n"+
-                "- serviceType: org.apache.brooklyn.core.test.entity.TestEntity\n";
+                "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
         
         Entity app = createStartWaitAndLogApplication(new StringReader(yaml));
         Entity child = Iterables.getOnlyElement(app.getChildren());
-        MultiLocation<?> loc = (MultiLocation<?>) Iterables.getOnlyElement(child.getLocations());
+        MultiLocation<?> loc = (MultiLocation<?>) Iterables.getOnlyElement(Entities.getAllInheritedLocations(child));
         Assert.assertEquals(loc.getSubLocations().size(), 2);
         
         assertUserAddress((SshMachineLocation)loc.obtain(), "root", "127.0.0.1");
diff --git a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java
index 47925e5..f792d65 100644
--- a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java
+++ b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java
@@ -32,6 +32,7 @@
 import org.apache.brooklyn.api.typereg.RegisteredType;
 import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest;
 import org.apache.brooklyn.core.config.BasicConfigKey;
+import org.apache.brooklyn.core.entity.Entities;
 import org.apache.brooklyn.core.mgmt.osgi.OsgiStandaloneTest;
 import org.apache.brooklyn.core.typereg.RegisteredTypePredicates;
 import org.apache.brooklyn.core.typereg.RegisteredTypes;
@@ -187,7 +188,7 @@
             "  - type: org.apache.brooklyn.entity.stock.BasicStartable");
 
         Entity simpleEntity = Iterables.getOnlyElement(app.getChildren());
-        Location location = Iterables.getOnlyElement(simpleEntity.getLocations());
+        Location location = Iterables.getOnlyElement(Entities.getAllInheritedLocations(simpleEntity));
         assertEquals(location.getClass().getName(), locType);
         assertEquals(location.getConfig(new BasicConfigKey<String>(String.class, "config1")), "config1");
         assertEquals(location.getConfig(new BasicConfigKey<String>(String.class, "config2")), "config2 override");
diff --git a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlTemplateTest.java b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlTemplateTest.java
index c3dbc48..165fd49 100644
--- a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlTemplateTest.java
+++ b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlTemplateTest.java
@@ -18,13 +18,30 @@
  */
 package org.apache.brooklyn.camp.brooklyn.catalog;
 
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.List;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
 import org.apache.brooklyn.api.typereg.RegisteredType;
 import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.mgmt.BrooklynTags;
+import org.apache.brooklyn.core.mgmt.BrooklynTags.NamedStringTag;
+import org.apache.brooklyn.core.mgmt.EntityManagementUtils;
 import org.apache.brooklyn.core.mgmt.osgi.OsgiStandaloneTest;
+import org.apache.brooklyn.core.test.entity.TestEntity;
 import org.apache.brooklyn.core.typereg.RegisteredTypePredicates;
 import org.apache.brooklyn.core.typereg.RegisteredTypes;
+import org.apache.brooklyn.entity.group.DynamicCluster;
+import org.apache.brooklyn.entity.stock.BasicApplication;
+import org.apache.brooklyn.test.Asserts;
 import org.apache.brooklyn.test.support.TestResourceUnavailableException;
 import org.apache.brooklyn.util.osgi.OsgiTestResources;
+import org.python.google.common.collect.Iterables;
 import org.testng.Assert;
 import org.testng.TestListenerAdapter;
 import org.testng.TestNG;
@@ -63,6 +80,135 @@
         deleteCatalogEntity("t1");
     }
 
+    public void testServiceTypeEntityOfTypeCatalogTemplateNotWrapped() throws Exception {
+        addCatalogItems(
+                "brooklyn.catalog:",
+                "  id: t1",
+                "  item_type: template",
+                "  name: myT1",
+                "  item:",
+                "    services:",
+                "    - type: " + TestEntity.class.getName());
+        addCatalogItems(
+                "brooklyn.catalog:",
+                "  id: t2",
+                "  item_type: template",
+                "  name: myT2",
+                "  item:",
+                "    services:",
+                "    - type: t1",
+                "    - type: t1");
+
+        Entity app = createAndStartApplication(
+                "services:",
+                "- type: t2");
+        waitForApplicationTasks(app);
+        
+        Entities.dumpInfo(app);
+        Entity t1a = Iterables.get(app.getChildren(), 0);
+        Entity t1b = Iterables.get(app.getChildren(), 1);
+        assertEquals(app.getChildren().size(), 2);
+        assertEquals(t1a.getChildren().size(), 0);
+        assertEquals(t1b.getChildren().size(), 0);
+        
+        assertTrue(app instanceof BasicApplication);
+        assertTrue(t1a instanceof TestEntity);
+        assertTrue(t1b instanceof TestEntity);
+    }
+
+    @Test
+    public void testChildEntityOfTypeCatalogTemplateNotWrapped() throws Exception {
+        addCatalogItems(
+                "brooklyn.catalog:",
+                "  id: t1",
+                "  item_type: template",
+                "  name: myT1",
+                "  item:",
+                "    services:",
+                "    - type: " + TestEntity.class.getName());
+        addCatalogItems(
+                "brooklyn.catalog:",
+                "  id: t2",
+                "  item_type: template",
+                "  name: myT2",
+                "  item:",
+                "    services:",
+                "    - type: " + TestEntity.class.getName(),
+                "      brooklyn.children:",
+                "      - type: t1");
+
+        Entity app = createAndStartApplication(
+                "services:",
+                "- type: t2");
+        waitForApplicationTasks(app);
+        
+        Entities.dumpInfo(app);
+        Entity t2 = Iterables.getOnlyElement(app.getChildren());
+        Entity t1 = Iterables.getOnlyElement(t2.getChildren());
+        assertEquals(t1.getChildren().size(), 0);
+        
+        assertTrue(app instanceof BasicApplication);
+        assertTrue(t1 instanceof TestEntity);
+        assertTrue(t2 instanceof TestEntity);
+    }
+
+    @Test
+    public void testMemberSpecEntityOfTypeCatalogTemplateNotWrapped() throws Exception {
+        addCatalogItems(
+                "brooklyn.catalog:",
+                "  id: t1",
+                "  item_type: template",
+                "  name: myT1",
+                "  item:",
+                "    services:",
+                "    - type: " + TestEntity.class.getName());
+        addCatalogItems(
+                "brooklyn.catalog:",
+                "  id: t2",
+                "  item_type: template",
+                "  name: myT2",
+                "  item:",
+                "    services:",
+                "    - type: " + DynamicCluster.class.getName(),
+                "      brooklyn.config:",
+                "        memberSpec:",
+                "          $brooklyn:entitySpec:",
+                "            type: t1",
+                "        cluster.initial.size: 1");
+
+        Entity app = createAndStartApplication(
+                "location: localhost",
+                "services:",
+                "- type: t2");
+        waitForApplicationTasks(app);
+        
+        Entities.dumpInfo(app);
+        DynamicCluster t2 = (DynamicCluster) Iterables.getOnlyElement(app.getChildren());
+        Entity t1 = Iterables.getOnlyElement(t2.getMembers());
+        assertEquals(t1.getChildren().size(), 0);
+        
+        assertTrue(app instanceof BasicApplication);
+        assertTrue(t2 instanceof DynamicCluster);
+        assertTrue(t1 instanceof TestEntity);
+    }
+
+    @Test
+    public void testMetadataOnSpecCreatedFromItem() throws Exception {
+        makeItem();
+        EntitySpec<? extends Application> spec = EntityManagementUtils.createEntitySpecForApplication(mgmt(), 
+            "services: [ { type: t1 } ]\n" +
+            "location: localhost");
+        
+        List<NamedStringTag> yamls = BrooklynTags.findAll(BrooklynTags.YAML_SPEC_KIND, spec.getTags());
+        Assert.assertEquals(yamls.size(), 1, "Expected 1 yaml tag; instead had: "+yamls);
+        String yaml = Iterables.getOnlyElement(yamls).getContents();
+        Asserts.assertStringContains(yaml, "services:", "t1", "localhost");
+        
+        EntitySpec<?> child = Iterables.getOnlyElement( spec.getChildren() );
+        Assert.assertEquals(child.getType().getName(), SIMPLE_ENTITY_TYPE);
+        Assert.assertEquals(child.getCatalogItemId(), "t1:"+TEST_VERSION);
+    }
+    
     private RegisteredType makeItem() {
         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
         
diff --git a/brooklyn-server/camp/camp-brooklyn/src/test/resources/test-entity-basic-template.yaml b/brooklyn-server/camp/camp-brooklyn/src/test/resources/test-entity-basic-template.yaml
index b690309..7ace993 100644
--- a/brooklyn-server/camp/camp-brooklyn/src/test/resources/test-entity-basic-template.yaml
+++ b/brooklyn-server/camp/camp-brooklyn/src/test/resources/test-entity-basic-template.yaml
@@ -19,6 +19,6 @@
 description: TestEntity with templated brooklyn.config and additional config (such as services)
 origin: https://github.com/apache/incubator-brooklyn
 services:
-- serviceType: org.apache.brooklyn.core.test.entity.TestEntity
+- type: org.apache.brooklyn.core.test.entity.TestEntity
   name: testentity
 # should have nothing below here as the test appends things underneath
diff --git a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
index 96cf452..41a0a9c 100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
@@ -41,12 +41,14 @@
 import org.apache.brooklyn.core.catalog.CatalogPredicates;
 import org.apache.brooklyn.core.catalog.internal.CatalogClasspathDo.CatalogScanningModes;
 import org.apache.brooklyn.core.location.BasicLocationRegistry;
+import org.apache.brooklyn.core.mgmt.internal.CampYamlParser;
 import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
 import org.apache.brooklyn.core.typereg.BrooklynTypePlanTransformer;
 import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.collections.MutableSet;
 import org.apache.brooklyn.util.core.flags.TypeCoercions;
+import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.javalang.AggregateClassLoader;
@@ -395,14 +397,39 @@
 
         if (sourceYaml==null) sourceYaml = new Yaml().dump(itemMetadata);
 
-        Map<Object,Object> catalogMetadata = MutableMap.builder().putAll(parentMetadata).putAll(itemMetadata).build();
+        Map<?, ?> itemMetadataWithoutItemDef = MutableMap.builder()
+                .putAll(itemMetadata)
+                .remove("item")
+                .remove("items")
+                .build();
         
+        // Parse CAMP-YAML DSL in item metadata (but not in item or items - those will be parsed only when used). 
+        CampYamlParser parser = mgmt.getConfig().getConfig(CampYamlParser.YAML_PARSER_KEY);
+        if (parser != null) {
+            itemMetadataWithoutItemDef = parser.parse((Map<String, Object>) itemMetadataWithoutItemDef);
+            try {
+                itemMetadataWithoutItemDef = (Map<String, Object>) Tasks.resolveDeepValue(itemMetadataWithoutItemDef, Object.class, mgmt.getServerExecutionContext());
+            } catch (Exception e) {
+                throw Exceptions.propagate(e);
+            }
+            
+        } else {
+            log.info("No Camp-YAML parser regsitered for parsing catalog item DSL; skipping DSL-parsing");
+        }
+
+        Map<Object,Object> catalogMetadata = MutableMap.<Object, Object>builder()
+                .putAll(parentMetadata)
+                .putAll(itemMetadataWithoutItemDef)
+                .putIfNotNull("item", itemMetadata.get("item"))
+                .putIfNotNull("items", itemMetadata.get("items"))
+                .build();
+
         // brooklyn.libraries we treat specially, to append the list, with the child's list preferred in classloading order
         // `libraries` is supported in some places as a legacy syntax; it should always be `brooklyn.libraries` for new apps
         // TODO in 0.8.0 require brooklyn.libraries, don't allow "libraries" on its own
-        List<?> librariesNew = MutableList.copyOf(getFirstAs(itemMetadata, List.class, "brooklyn.libraries", "libraries").orNull());
+        List<?> librariesNew = MutableList.copyOf(getFirstAs(itemMetadataWithoutItemDef, List.class, "brooklyn.libraries", "libraries").orNull());
         Collection<CatalogBundle> libraryBundlesNew = CatalogItemDtoAbstract.parseLibraries(librariesNew);
-        
+
         List<?> librariesCombined = MutableList.copyOf(librariesNew)
             .appendAll(getFirstAs(parentMetadata, List.class, "brooklyn.libraries", "libraries").orNull());
         if (!librariesCombined.isEmpty())
@@ -413,7 +440,7 @@
         // (this load is required for the scan below and I think also for yaml resolution)
         CatalogUtils.installLibraries(mgmt, libraryBundlesNew);
 
-        Boolean scanJavaAnnotations = getFirstAs(itemMetadata, Boolean.class, "scanJavaAnnotations", "scan_java_annotations").orNull();
+        Boolean scanJavaAnnotations = getFirstAs(itemMetadataWithoutItemDef, Boolean.class, "scanJavaAnnotations", "scan_java_annotations").orNull();
         if (scanJavaAnnotations==null || !scanJavaAnnotations) {
             // don't scan
         } else {
@@ -433,8 +460,8 @@
         if (items!=null) {
             int count = 0;
             for (Map<?,?> i: ((List<Map<?,?>>)items)) {
-                collectCatalogItems(Yamls.getTextOfYamlAtPath(sourceYaml, "items", count).getMatchedYamlTextOrWarn(), 
-                    i, result, catalogMetadata);
+                collectCatalogItems(Yamls.getTextOfYamlAtPath(sourceYaml, "items", count).getMatchedYamlTextOrWarn(),
+                        i, result, catalogMetadata);
                 count++;
             }
         }
@@ -463,8 +490,10 @@
         PlanInterpreterGuessingType planInterpreter = new PlanInterpreterGuessingType(null, item, sourceYaml, itemType, libraryBundles, result).reconstruct();
         if (!planInterpreter.isResolved()) {
             throw Exceptions.create("Could not resolve item"
-                + (Strings.isNonBlank(id) ? " "+id : Strings.isNonBlank(symbolicName) ? " "+symbolicName : Strings.isNonBlank(name) ? name : "")
-                // better not to show yaml, takes up lots of space, and with multiple plan transformers there might be multiple errors 
+                + (Strings.isNonBlank(id) ? " '"+id+"'" : Strings.isNonBlank(symbolicName) ? " '"+symbolicName+"'" : Strings.isNonBlank(name) ? " '"+name+"'" : "")
+                // better not to show yaml, takes up lots of space, and with multiple plan transformers there might be multiple errors; 
+                // some of the errors themselves may reproduce it
+                // (ideally in future we'll be able to return typed errors with caret position of error)
 //                + ":\n"+sourceYaml
                 , planInterpreter.getErrors());
         }
@@ -582,12 +611,12 @@
         return oldValue;
     }
 
-    private Collection<CatalogItemDtoAbstract<?, ?>> scanAnnotationsFromLocal(ManagementContext mgmt, Map<Object, Object> catalogMetadata) {
+    private Collection<CatalogItemDtoAbstract<?, ?>> scanAnnotationsFromLocal(ManagementContext mgmt, Map<?, ?> catalogMetadata) {
         CatalogDto dto = CatalogDto.newNamedInstance("Local Scanned Catalog", "All annotated Brooklyn entities detected in the classpath", "scanning-local-classpath");
         return scanAnnotationsInternal(mgmt, new CatalogDo(dto), catalogMetadata);
     }
     
-    private Collection<CatalogItemDtoAbstract<?, ?>> scanAnnotationsFromBundles(ManagementContext mgmt, Collection<CatalogBundle> libraries, Map<Object, Object> catalogMetadata) {
+    private Collection<CatalogItemDtoAbstract<?, ?>> scanAnnotationsFromBundles(ManagementContext mgmt, Collection<CatalogBundle> libraries, Map<?, ?> catalogMetadata) {
         CatalogDto dto = CatalogDto.newNamedInstance("Bundles Scanned Catalog", "All annotated Brooklyn entities detected in bundles", "scanning-bundles-classpath-"+libraries.hashCode());
         List<String> urls = MutableList.of();
         for (CatalogBundle b: libraries) {
@@ -608,7 +637,7 @@
         return scanAnnotationsInternal(mgmt, subCatalog, catalogMetadata);
     }
     
-    private Collection<CatalogItemDtoAbstract<?, ?>> scanAnnotationsInternal(ManagementContext mgmt, CatalogDo subCatalog, Map<Object, Object> catalogMetadata) {
+    private Collection<CatalogItemDtoAbstract<?, ?>> scanAnnotationsInternal(ManagementContext mgmt, CatalogDo subCatalog, Map<?, ?> catalogMetadata) {
         // TODO this does java-scanning only;
         // the call when scanning bundles should use the CatalogItem instead and use OSGi when loading for scanning
         // (or another scanning mechanism).  see comments on CatalogClasspathDo.load
@@ -989,7 +1018,7 @@
         };
     }
     
-    private static <T,SpecT> Function<CatalogItemDo<T, SpecT>, CatalogItem<T,SpecT>> itemDoToDtoAddingSelectedMetadataDuringScan(final Map<Object, Object> catalogMetadata) {
+    private static <T,SpecT> Function<CatalogItemDo<T, SpecT>, CatalogItem<T,SpecT>> itemDoToDtoAddingSelectedMetadataDuringScan(final Map<?, ?> catalogMetadata) {
         return new Function<CatalogItemDo<T,SpecT>, CatalogItem<T,SpecT>>() {
             @Override
             public CatalogItem<T,SpecT> apply(@Nullable CatalogItemDo<T,SpecT> item) {
diff --git a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogXmlSerializer.java b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogXmlSerializer.java
index 855a753..3cf686e 100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogXmlSerializer.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogXmlSerializer.java
@@ -36,7 +36,8 @@
         super(DeserializingClassRenamesProvider.loadDeserializingClassRenames());
         
         xstream.addDefaultImplementation(ArrayList.class, Collection.class);
-        
+       
+        //Doesn't work well for non-standard lists, like Lists.transform results
         xstream.aliasType("list", List.class);
         xstream.aliasType("map", Map.class);
 
diff --git a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/AbstractApplication.java b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/AbstractApplication.java
index e000997..3fd4b05 100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/AbstractApplication.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/AbstractApplication.java
@@ -38,6 +38,8 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.collect.ImmutableSet;
+
 /**
  * Users can extend this to define the entities in their application, and the relationships between
  * those entities. Users should override the {@link #init()} method, and in there should create 
@@ -143,7 +145,8 @@
     @Override
     public void start(Collection<? extends Location> locations) {
         this.addLocations(locations);
-        Collection<? extends Location> locationsToUse = getLocations();
+        // 2016-01: only pass locations passed to us, as per ML discussion
+        Collection<? extends Location> locationsToUse = locations==null ? ImmutableSet.<Location>of() : locations;
         ServiceProblemsLogic.clearProblemsIndicator(this, START);
         ServiceStateLogic.ServiceNotUpLogic.updateNotUpIndicator(this, Attributes.SERVICE_STATE_ACTUAL, "Application starting");
         setExpectedStateAndRecordLifecycleEvent(Lifecycle.STARTING);
diff --git a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/Entities.java b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/Entities.java
index 59e3ec1..9297388 100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/Entities.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/Entities.java
@@ -80,6 +80,7 @@
 import org.apache.brooklyn.core.objs.proxy.EntityProxyImpl;
 import org.apache.brooklyn.core.sensor.DependentConfiguration;
 import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.collections.MutableSet;
 import org.apache.brooklyn.util.core.ResourceUtils;
 import org.apache.brooklyn.util.core.config.ConfigBag;
 import org.apache.brooklyn.util.core.flags.FlagUtils;
@@ -1183,4 +1184,13 @@
         return root;
     }
 
+    public static Set<Location> getAllInheritedLocations(Entity entity) {
+        Set<Location> result = MutableSet.of();
+        while (entity!=null) {
+            result.addAll(entity.getLocations());
+            entity = entity.getParent();
+        }
+        return result;
+    }
+    
 }
diff --git a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/trait/Resizable.java b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/trait/Resizable.java
index 68c9398..36e6ba8 100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/trait/Resizable.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/trait/Resizable.java
@@ -31,6 +31,21 @@
  */
 public interface Resizable {
 
+    /**
+     * Indicates that resizing up to the desired size is not possible - only resized to the 
+     * {@link Resizable#getCurrentSize()}, because there is insufficient capacity.
+     */
+    public static class InsufficientCapacityException extends RuntimeException {
+        private static final long serialVersionUID = 953230498564942446L;
+        
+        public InsufficientCapacityException(String msg) {
+            super(msg);
+        }
+        public InsufficientCapacityException(String msg, Throwable cause) {
+            super(msg, cause);
+        }
+    }
+    
     MethodEffector<Integer> RESIZE = new MethodEffector<Integer>(Resizable.class, "resize");
 
     /**
@@ -38,6 +53,9 @@
      *
      * @param desiredSize the new size of the entity group.
      * @return the new size of the group.
+     * 
+     * @throws InsufficientCapacityException If the request was to grow, but there is no capacity to grow to
+     *         the desired size.
      */
     @Effector(description="Changes the size of the entity (e.g. the number of nodes in a cluster)")
     Integer resize(@EffectorParam(name="desiredSize", description="The new size of the cluster") Integer desiredSize);
diff --git a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/location/geo/LocalhostExternalIpLoader.java b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/location/geo/LocalhostExternalIpLoader.java
index fd95585..f6623fc 100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/location/geo/LocalhostExternalIpLoader.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/location/geo/LocalhostExternalIpLoader.java
@@ -44,8 +44,21 @@
 
     public static final Logger LOG = LoggerFactory.getLogger(LocalhostExternalIpLoader.class);
 
-    private static final AtomicBoolean retrievingLocalExternalIp = new AtomicBoolean(false);
-    private static final CountDownLatch triedLocalExternalIp = new CountDownLatch(1);
+    /**
+     * Mutex to guard access to retrievingLocalExternalIp.
+     */
+    private static final Object mutex = new Object();
+    /**
+     * When null there is no ongoing attempt to load the external IP address. Either no attempt has been made or the
+     * last attempt has been completed.
+     * When set there is an ongoing attempt to load the external IP address. New attempts to lookup the external IP
+     * address should wait on this latch instead of making another attempt to load the IP address.
+     */
+    private static CountDownLatch retrievingLocalExternalIp;
+    /**
+     * Cached external IP address of localhost. Null if either no attempt has been made to resolve the address or the
+     * last attempt failed.
+     */
     private static volatile String localExternalIp;
 
     private static class IpLoader implements Callable<String> {
@@ -120,57 +133,75 @@
     }
 
     /**
-     * Requests URLs returned by {@link #getIpAddressWebsites()} until one returns an IP address.
+     * Requests URLs returned by {@link #getIpAddressWebsites()} until one returns an IP address or all URLs have been tried.
      * The address is assumed to be the external IP address of localhost.
      * @param blockFor The maximum duration to wait for the IP address to be resolved.
      *                 An indefinite way if null.
      * @return A string in IPv4 format, or null if no such address could be ascertained.
      */
     private static String doLoad(Duration blockFor) {
-        if (localExternalIp != null) {
-            return localExternalIp;
+        // Check for a cached external IP address
+        final String resolvedIp = localExternalIp;
+        if (resolvedIp != null) {
+            return resolvedIp;
         }
 
-        final List<String> candidateUrls = getIpAddressWebsites();
-        if (candidateUrls.isEmpty()) {
-            LOG.debug("No candidate URLs to use to determine external IP of localhost");
-            return null;
+        // Check for an ongoing attempt to load an external IP address
+        final boolean startAttemptToLoadIp;
+        final CountDownLatch attemptToRetrieveLocalExternalIp;
+        synchronized (mutex) {
+            if (retrievingLocalExternalIp == null) {
+                retrievingLocalExternalIp = new CountDownLatch(1);
+                startAttemptToLoadIp = true;
+            }
+            else {
+                startAttemptToLoadIp = false;
+            }
+            attemptToRetrieveLocalExternalIp = retrievingLocalExternalIp;
         }
 
-        // do in private thread, otherwise blocks for 30s+ on dodgy network!
+        // Attempt to load the external IP address in private thread, otherwise blocks for 30s+ on dodgy network!
         // (we can skip it if someone else is doing it, we have synch lock so we'll get notified)
-        if (retrievingLocalExternalIp.compareAndSet(false, true)) {
+        if (startAttemptToLoadIp) {
+            final List<String> candidateUrls = getIpAddressWebsites();
+            if (candidateUrls.isEmpty()) {
+                LOG.debug("No candidate URLs to use to determine external IP of localhost");
+                return null;
+            }
+
             new Thread() {
                 public void run() {
                     for (String url : candidateUrls) {
                         try {
                             LOG.debug("Looking up external IP of this host from {} in private thread {}", url, Thread.currentThread());
-                            localExternalIp = new IpLoader(url).call();
-                            LOG.debug("Finished looking up external IP of this host from {} in private thread, result {}", url, localExternalIp);
+                            final String loadedIp = new IpLoader(url).call();
+                            localExternalIp = loadedIp;
+                            LOG.debug("Finished looking up external IP of this host from {} in private thread, result {}", url, loadedIp);
                             break;
                         } catch (Throwable t) {
                             LOG.debug("Unable to look up external IP of this host from {}, probably offline {})", url, t);
-                        } finally {
-                            retrievingLocalExternalIp.set(false);
-                            triedLocalExternalIp.countDown();
                         }
                     }
+
+                    attemptToRetrieveLocalExternalIp.countDown();
+
+                    synchronized (mutex) {
+                        retrievingLocalExternalIp = null;
+                    }
                 }
             }.start();
         }
 
         try {
             if (blockFor!=null) {
-                Durations.await(triedLocalExternalIp, blockFor);
+                Durations.await(attemptToRetrieveLocalExternalIp, blockFor);
             } else {
-                triedLocalExternalIp.await();
+                attemptToRetrieveLocalExternalIp.await();
             }
         } catch (InterruptedException e) {
             throw Exceptions.propagate(e);
         }
-        if (localExternalIp == null) {
-            return null;
-        }
+
         return localExternalIp;
     }
 
diff --git a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/BrooklynTags.java b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/BrooklynTags.java
index e8d7915..0ddc79a 100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/BrooklynTags.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/BrooklynTags.java
@@ -19,8 +19,10 @@
 package org.apache.brooklyn.core.mgmt;
 
 import java.io.Serializable;
+import java.util.ArrayList;
 import java.util.List;
 
+import org.apache.brooklyn.util.collections.MutableList;
 import org.codehaus.jackson.annotate.JsonIgnore;
 import org.codehaus.jackson.annotate.JsonProperty;
 
@@ -73,7 +75,7 @@
         public boolean equals(Object o) {
             if (this == o) return true;
             if (o == null || getClass() != o.getClass()) return false;
-            ListTag that = (ListTag) o;
+            ListTag<?> that = (ListTag<?>) o;
             return list == null ? that.list == null : list.equals(that.list);
         }
 
@@ -85,11 +87,17 @@
 
     public static class TraitsTag extends ListTag<String> {
         public TraitsTag(List<Class<?>> interfaces) {
-            super(Lists.transform(interfaces, new Function<Class<?>, String>() {
-                @Override public String apply(Class<?> input) {
-                    return input.getName();
-                }
-            }));
+            // The transformed list is a view, meaning that it references
+            // the instances list. This means that it will serialize 
+            // the list of classes along with the anonymous function which
+            // is not what we want. Force eager evaluation instead, using
+            // a simple list, supported by {@link CatalogXmlSerializer}.
+            super(new ArrayList<String>(
+                Lists.transform(interfaces, new Function<Class<?>, String>() {
+                    @Override public String apply(Class<?> input) {
+                        return input.getName();
+                    }
+                })));
         }
 
         @JsonProperty("traits")
@@ -117,5 +125,14 @@
         }
         return null;
     }
-    
+
+    public static List<NamedStringTag> findAll(String kind, Iterable<Object> tags) {
+        List<NamedStringTag> result = MutableList.of();
+        for (Object object: tags) {
+            if (object instanceof NamedStringTag && kind.equals(((NamedStringTag)object).kind))
+                result.add( (NamedStringTag) object );
+        }
+        return result;
+    }
+
 }
diff --git a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java
index ef461db..e75e2de 100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java
@@ -50,7 +50,6 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.annotations.Beta;
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -67,11 +66,12 @@
     /**
      * A marker config value which indicates that an {@link Application} entity was created automatically,
      * needed because a plan might give multiple top-level entities or a non-Application top-level entity,
-     * but brooklyn requires an {@link Application} at the root.
+     * in a context where Brooklyn requires an {@link Application} at the root.
      * <p>
      * Typically when such a wrapper app wraps another {@link Application}
-     * (or when we are adding to an existing entity and it wraps multiple {@link Entity} instances)
-     * it will be unwrapped. See {@link #newWrapperApp()} and {@link #unwrapApplication(EntitySpec)}.
+     * (or where we are looking for a single {@link Entity}, or a list to add, and they are so wrapped)
+     * it will be unwrapped. 
+     * See {@link #newWrapperApp()} and {@link #unwrapApplication(EntitySpec)}.
      */
     public static final ConfigKey<Boolean> WRAPPER_APP_MARKER = ConfigKeys.newBooleanConfigKey("brooklyn.wrapper_app");
 
@@ -159,21 +159,16 @@
 
         // see whether we can promote children
         List<EntitySpec<?>> specs = MutableList.of();
-        if (canPromoteChildrenInWrappedApplication(specA)) {
-            // we can promote
-            for (EntitySpec<?> specC: specA.getChildren()) {
-                mergeWrapperParentSpecToChildEntity(specA, specC);
-                specs.add(specC);
-            }
-        } else {
+        if (!canUnwrapEntity(specA)) {
             // if not promoting, set a nice name if needed
             if (Strings.isEmpty(specA.getDisplayName())) {
                 int size = specA.getChildren().size();
                 String childrenCountString = size+" "+(size!=1 ? "children" : "child");
                 specA.displayName("Dynamically added "+childrenCountString);
             }
-            specs.add(specA);
         }
+        
+        specs.add(unwrapEntity(specA));
 
         final List<Entity> children = MutableList.of();
         for (EntitySpec<?> spec: specs) {
@@ -219,43 +214,62 @@
 
         return CreationResult.of(children, task);
     }
-
-    /** if an application should be unwrapped, it does so, returning the child; otherwise returns the argument passed in.
-     * use {@link #canPromoteWrappedApplication(EntitySpec)} to test whether it will unwrap. */
-    public static EntitySpec<? extends Application> unwrapApplication(EntitySpec<? extends Application> wrapperApplication) {
-        if (canPromoteWrappedApplication(wrapperApplication)) {
-            @SuppressWarnings("unchecked")
-            EntitySpec<? extends Application> wrappedApplication = (EntitySpec<? extends Application>) Iterables.getOnlyElement( wrapperApplication.getChildren() );
-
-            // if promoted, apply the transformations done to the app
-            // (transformations will be done by the resolveSpec call above, but we are collapsing oldApp so transfer to app=newApp)
-            EntityManagementUtils.mergeWrapperParentSpecToChildEntity(wrapperApplication, wrappedApplication);
-            return wrappedApplication;
+    
+    /** Unwraps a single {@link Entity} if appropriate. See {@link #WRAPPER_APP_MARKER}.
+     * Also see {@link #canUnwrapEntity(EntitySpec)} to test whether it will unwrap. */
+    public static EntitySpec<? extends Entity> unwrapEntity(EntitySpec<? extends Entity> wrapperApplication) {
+        if (!canUnwrapEntity(wrapperApplication)) {
+            return wrapperApplication;
         }
-        return wrapperApplication;
+        EntitySpec<?> wrappedEntity = Iterables.getOnlyElement(wrapperApplication.getChildren());
+        @SuppressWarnings("unchecked")
+        EntitySpec<? extends Application> wrapperApplicationTyped = (EntitySpec<? extends Application>) wrapperApplication;
+        EntityManagementUtils.mergeWrapperParentSpecToChildEntity(wrapperApplicationTyped, wrappedEntity);
+        return wrappedEntity;
+    }
+    
+    /** Unwraps a wrapped {@link Application} if appropriate.
+     * This is like {@link #canUnwrapEntity(EntitySpec)} with an additional check that the wrapped child is an {@link Application}. 
+     * See {@link #WRAPPER_APP_MARKER} for an overview. 
+     * Also see {@link #canUnwrapApplication(EntitySpec)} to test whether it will unwrap. */
+    public static EntitySpec<? extends Application> unwrapApplication(EntitySpec<? extends Application> wrapperApplication) {
+        if (!canUnwrapApplication(wrapperApplication)) {
+            return wrapperApplication;
+        }
+        @SuppressWarnings("unchecked")
+        EntitySpec<? extends Application> wrappedApplication = (EntitySpec<? extends Application>) unwrapEntity(wrapperApplication);
+        return wrappedApplication;
     }
     
     /** Modifies the child so it includes the inessential setup of its parent,
      * for use when unwrapping specific children, but a name or other item may have been set on the parent.
      * See {@link #WRAPPER_APP_MARKER}. */
-    @Beta //where should this live long-term?
-    public static void mergeWrapperParentSpecToChildEntity(EntitySpec<? extends Application> wrapperParent, EntitySpec<?> wrappedChild) {
-        if (Strings.isNonEmpty(wrapperParent.getDisplayName()))
+    private static void mergeWrapperParentSpecToChildEntity(EntitySpec<? extends Application> wrapperParent, EntitySpec<?> wrappedChild) {
+        if (Strings.isNonEmpty(wrapperParent.getDisplayName())) {
             wrappedChild.displayName(wrapperParent.getDisplayName());
-        if (!wrapperParent.getLocations().isEmpty())
-            wrappedChild.locations(wrapperParent.getLocations());
-        if (!wrapperParent.getParameters().isEmpty()) {
-            wrappedChild.parameters(wrapperParent.getParameters());
+        }
+        
+        wrappedChild.locations(wrapperParent.getLocations());
+        
+        if (!wrapperParent.getParameters().isEmpty())
+            wrappedChild.parametersReplace(wrapperParent.getParameters());
+        
+        if (wrappedChild.getCatalogItemId()==null) {
+            wrappedChild.catalogItemId(wrapperParent.getCatalogItemId());
         }
 
-        // NB: this clobbers child config; might prefer to deeply merge maps etc
-        // (but this should not be surprising, as unwrapping is often parameterising the nested blueprint, so outer config should dominate) 
+        // NB: this clobber's child config wherever they conflict; might prefer to deeply merge maps etc
+        // (or maybe even prevent the merge in these cases; 
+        // not sure there is a compelling reason to have config on a pure-wrapper parent)
         Map<ConfigKey<?>, Object> configWithoutWrapperMarker = Maps.filterKeys(wrapperParent.getConfig(), Predicates.not(Predicates.<ConfigKey<?>>equalTo(EntityManagementUtils.WRAPPER_APP_MARKER)));
         wrappedChild.configure(configWithoutWrapperMarker);
         wrappedChild.configure(wrapperParent.getFlags());
         
-        // TODO copying tags to all entities is not ideal;
-        // in particular the BrooklynTags.YAML_SPEC tag will show all entities if the root has multiple
+        // copying tags to all entities may be something the caller wants to control,
+        // e.g. if we're creating a list of entities which will be added,
+        // ignoring the parent Application holder; 
+        // in that case each child's BrooklynTags.YAML_SPEC tag will show all entities;
+        // but in the normal case where we're unwrapping one, it's probably right.
         wrappedChild.tags(wrapperParent.getTags());
     }
 
@@ -263,31 +277,48 @@
         return EntitySpec.create(BasicApplication.class).configure(WRAPPER_APP_MARKER, true);
     }
     
-    /** returns true if the spec is for an empty-ish wrapper app contianing an application, 
-     * for use when adding from a plan specifying an application which was wrapped because it had to be.
+    /** As {@link #canUnwrapEntity(EntitySpec)}
+     * but additionally requiring that the wrapped item is an {@link Application},
+     * for use when the context requires an {@link Application} ie a root of a spec.
      * @see #WRAPPER_APP_MARKER */
+    public static boolean canUnwrapApplication(EntitySpec<? extends Application> wrapperApplication) {
+        if (!canUnwrapEntity(wrapperApplication)) return false;
+
+        EntitySpec<?> childSpec = Iterables.getOnlyElement(wrapperApplication.getChildren());
+        return (childSpec.getType()!=null && Application.class.isAssignableFrom(childSpec.getType()));
+    }
+    /** @deprecated since 0.9.0 use {@link #canUnwrapApplication(EntitySpec)} */ @Deprecated
     public static boolean canPromoteWrappedApplication(EntitySpec<? extends Application> app) {
-        if (!hasSingleChild(app))
-            return false;
-
-        EntitySpec<?> childSpec = Iterables.getOnlyElement(app.getChildren());
-        if (childSpec.getType()==null || !Application.class.isAssignableFrom(childSpec.getType()))
-            return false;
-
-        return canPromoteChildrenInWrappedApplication(app);
+        return canUnwrapApplication(app);
     }
     
-    /** returns true if the spec is for a wrapper app with no important settings, wrapping a single child. 
-     * for use when adding from a plan specifying multiple entities but nothing significant at the application level.
-     * @see #WRAPPER_APP_MARKER */
-    public static boolean canPromoteChildrenInWrappedApplication(EntitySpec<? extends Application> spec) {
+    /** Returns true if the spec is for a wrapper app with no important settings, wrapping a single child entity. 
+     * for use when adding from a plan specifying multiple entities but there is nothing significant at the application level,
+     * and the context would like to flatten it to remove the wrapper yielding just a single entity.
+     * (but note the result is not necessarily an {@link Application}; 
+     * see {@link #canUnwrapApplication(EntitySpec)} if that is required).
+     * <p>
+     * Note callers will normally use one of {@link #unwrapEntity(EntitySpec)} or {@link #unwrapApplication(EntitySpec)}.
+     * 
+     * @see #WRAPPER_APP_MARKER for an overview */
+    public static boolean canUnwrapEntity(EntitySpec<? extends Entity> spec) {
         return isWrapperApp(spec) && hasSingleChild(spec) &&
-                //equivalent to no keys starting with "brooklyn."
-                spec.getEnrichers().isEmpty() &&
-                spec.getEnricherSpecs().isEmpty() &&
-                spec.getInitializers().isEmpty() &&
-                spec.getPolicies().isEmpty() &&
-                spec.getPolicySpecs().isEmpty();
+            // these "brooklyn.*" items on the app rather than the child absolutely prevent unwrapping
+            // as their semantics could well be different whether they are on the parent or the child
+            spec.getEnrichers().isEmpty() &&
+            spec.getEnricherSpecs().isEmpty() &&
+            spec.getInitializers().isEmpty() &&
+            spec.getPolicies().isEmpty() &&
+            spec.getPolicySpecs().isEmpty() &&
+            // these items prevent merge only if they are defined at both levels
+            (spec.getLocations().isEmpty() || Iterables.getOnlyElement(spec.getChildren()).getLocations().isEmpty())
+            // TODO what should we do with parameters? currently clobbers due to EntitySpec.parameters(...) behaviour.
+//            && (spec.getParameters().isEmpty() || Iterables.getOnlyElement(spec.getChildren()).getParameters().isEmpty())
+            ;
+    }
+    /** @deprecated since 0.9.0 use {@link #canUnwrapEntity(EntitySpec)} */ @Deprecated
+    public static boolean canPromoteChildrenInWrappedApplication(EntitySpec<? extends Application> spec) {
+        return canUnwrapEntity(spec);
     }
 
     public static boolean isWrapperApp(EntitySpec<?> spec) {
diff --git a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/EffectorUtils.java b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/EffectorUtils.java
index c6ec3ac..f8bb7cb 100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/EffectorUtils.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/EffectorUtils.java
@@ -149,6 +149,9 @@
         }
         if (newArgsNeeded>0)
             throw new IllegalArgumentException("Invalid arguments (missing "+newArgsNeeded+") for effector "+eff+": "+m);
+        if (!m.isEmpty()) {
+            log.warn("Unsupported parameter to "+eff+" (ignoring): "+m);
+        }
         return newArgs.toArray(new Object[newArgs.size()]);
     }
 
diff --git a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalEntityFactory.java b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalEntityFactory.java
index 3b0795b..8914ca4 100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalEntityFactory.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalEntityFactory.java
@@ -55,6 +55,7 @@
 import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.javalang.AggregateClassLoader;
+import org.apache.brooklyn.util.javalang.Reflections;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -99,6 +100,7 @@
             interfaces.add(spec.getType());
         } else {
             log.warn("EntitySpec declared in terms of concrete type "+spec.getType()+"; should be supplied in terms of interface");
+            interfaces.addAll(Reflections.getAllInterfaces(spec.getType()));
         }
         interfaces.addAll(spec.getAdditionalInterfaces());
         
diff --git a/brooklyn-server/core/src/main/java/org/apache/brooklyn/entity/group/AbstractGroup.java b/brooklyn-server/core/src/main/java/org/apache/brooklyn/entity/group/AbstractGroup.java
index aa9ca90..625d981 100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/entity/group/AbstractGroup.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/entity/group/AbstractGroup.java
@@ -58,8 +58,12 @@
     AttributeSensor<Entity> FIRST = Sensors.newSensor(Entity.class,
             "cluster.first.entity", "The first member of the cluster");
 
+    /**
+     * @deprecated since 0.9.0, the UI no longer relies on the use of delegates to represent group membership (see #929)
+     */
+    @Deprecated
     ConfigKey<Boolean> MEMBER_DELEGATE_CHILDREN = ConfigKeys.newBooleanConfigKey(
-            "group.members.delegate", "Add delegate child entities for members of the group", Boolean.FALSE);
+            "group.members.delegate", "Deprecated: Add delegate child entities for members of the group", Boolean.FALSE);
 
     ConfigKey<String> MEMBER_DELEGATE_NAME_FORMAT = ConfigKeys.newStringConfigKey(
             "group.members.delegate.nameFormat", "Delegate members name format string (Use %s for the original entity display name)", "%s");
diff --git a/brooklyn-server/core/src/main/java/org/apache/brooklyn/entity/group/AbstractGroupImpl.java b/brooklyn-server/core/src/main/java/org/apache/brooklyn/entity/group/AbstractGroupImpl.java
index d8814bd..bfd366f 100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/entity/group/AbstractGroupImpl.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/entity/group/AbstractGroupImpl.java
@@ -146,6 +146,7 @@
                 sensors().emit(MEMBER_ADDED, member);
 
                 if (Boolean.TRUE.equals(getConfig(MEMBER_DELEGATE_CHILDREN))) {
+                    log.warn("Use of deprecated ConfigKey {} in {} (as of 0.9.0)", MEMBER_DELEGATE_CHILDREN.getName(), this);
                     Optional<Entity> result = Iterables.tryFind(getChildren(), Predicates.equalTo(member));
                     if (!result.isPresent()) {
                         String nameFormat = Optional.fromNullable(getConfig(MEMBER_DELEGATE_NAME_FORMAT)).or("%s");
diff --git a/brooklyn-server/core/src/main/java/org/apache/brooklyn/entity/group/DynamicCluster.java b/brooklyn-server/core/src/main/java/org/apache/brooklyn/entity/group/DynamicCluster.java
index f528db7..781cb0c 100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/entity/group/DynamicCluster.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/entity/group/DynamicCluster.java
@@ -48,6 +48,7 @@
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.Function;
+import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Multimap;
 import com.google.common.reflect.TypeToken;
@@ -101,6 +102,15 @@
     ConfigKey<Boolean> QUARANTINE_FAILED_ENTITIES = ConfigKeys.newBooleanConfigKey(
             "dynamiccluster.quarantineFailedEntities", "If true, will quarantine entities that fail to start; if false, will get rid of them (i.e. delete them)", true);
 
+    @SetFromFlag("quarantineFilter")
+    ConfigKey<Predicate<? super Throwable>> QUARANTINE_FILTER = ConfigKeys.newConfigKey(
+            new TypeToken<Predicate<? super Throwable>>() {},
+            "dynamiccluster.quarantineFilter", 
+            "Quarantine the failed nodes that pass this filter (given the exception thrown by the node). "
+                    + "Default is those that did not fail with NoMachinesAvailableException "
+                    + "(Config ignored if quarantineFailedEntities is false)", 
+            null);
+
     AttributeSensor<Lifecycle> SERVICE_STATE_ACTUAL = Attributes.SERVICE_STATE_ACTUAL;
 
     BasicNotificationSensor<Entity> ENTITY_QUARANTINED = new BasicNotificationSensor<Entity>(Entity.class, "dynamiccluster.entityQuarantined", "Entity failed to start, and has been quarantined");
diff --git a/brooklyn-server/core/src/main/java/org/apache/brooklyn/entity/group/DynamicClusterImpl.java b/brooklyn-server/core/src/main/java/org/apache/brooklyn/entity/group/DynamicClusterImpl.java
index b8e5c63..16a82d4 100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/entity/group/DynamicClusterImpl.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/entity/group/DynamicClusterImpl.java
@@ -38,6 +38,7 @@
 import org.apache.brooklyn.api.entity.Group;
 import org.apache.brooklyn.api.location.Location;
 import org.apache.brooklyn.api.location.MachineProvisioningLocation;
+import org.apache.brooklyn.api.location.NoMachinesAvailableException;
 import org.apache.brooklyn.api.mgmt.Task;
 import org.apache.brooklyn.api.policy.Policy;
 import org.apache.brooklyn.api.sensor.AttributeSensor;
@@ -50,6 +51,7 @@
 import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
 import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
 import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic.ServiceProblemsLogic;
+import org.apache.brooklyn.core.entity.trait.Resizable;
 import org.apache.brooklyn.core.entity.trait.Startable;
 import org.apache.brooklyn.core.entity.trait.StartableMethods;
 import org.apache.brooklyn.core.location.Locations;
@@ -80,6 +82,7 @@
 import com.google.common.base.Functions;
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
 import com.google.common.base.Predicates;
 import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableList;
@@ -331,6 +334,19 @@
         return getAttribute(QUARANTINE_GROUP);
     }
 
+    protected Predicate<? super Throwable> getQuarantineFilter() {
+        Predicate<? super Throwable> result = getConfig(QUARANTINE_FILTER);
+        if (result != null) {
+            return result;
+        } else {
+            return new Predicate<Throwable>() {
+                @Override public boolean apply(Throwable input) {
+                    return Exceptions.getFirstThrowableOfType(input, NoMachinesAvailableException.class) == null;
+                }
+            };
+        }
+    }
+
     protected int getInitialQuorumSize() {
         int initialSize = getConfig(INITIAL_SIZE).intValue();
         int initialQuorumSize = getConfig(INITIAL_QUORUM_SIZE).intValue();
@@ -518,7 +534,20 @@
             } else {
                 if (LOG.isDebugEnabled()) LOG.debug("Resize no-op {} from {} to {}", new Object[] {this, originalSize, desiredSize});
             }
-            resizeByDelta(delta);
+            // If we managed to grow at all, then expect no exception.
+            // Otherwise, if failed because NoMachinesAvailable, then propagate as InsufficientCapacityException.
+            // This tells things like the AutoScalerPolicy to not keep retrying.
+            try {
+                resizeByDelta(delta);
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                NoMachinesAvailableException nmae = Exceptions.getFirstThrowableOfType(e, NoMachinesAvailableException.class);
+                if (nmae != null) {
+                    throw new Resizable.InsufficientCapacityException("Failed to resize", e);
+                } else {
+                    throw Exceptions.propagate(e);
+                }
+            }
         }
         return getCurrentSize();
     }
@@ -669,7 +698,7 @@
         }
     }
 
-    /** <strong>Note</strong> for sub-clases; this method can be called while synchronized on {@link #mutex}. */
+    /** <strong>Note</strong> for sub-classes; this method can be called while synchronized on {@link #mutex}. */
     protected Collection<Entity> grow(int delta) {
         Preconditions.checkArgument(delta > 0, "Must call grow with positive delta.");
 
@@ -696,8 +725,10 @@
             chosenLocations = Collections.nCopies(delta, getLocation());
         }
 
-        // create and start the entities
-        return addInEachLocation(chosenLocations, ImmutableMap.of()).getWithError();
+        // create and start the entities.
+        // if any fail, then propagate the error.
+        ReferenceWithError<Collection<Entity>> result = addInEachLocation(chosenLocations, ImmutableMap.of());
+        return result.getWithError();
     }
 
     /** <strong>Note</strong> for sub-clases; this method can be called while synchronized on {@link #mutex}. */
@@ -786,7 +817,7 @@
         // quarantine/cleanup as necessary
         if (!errors.isEmpty()) {
             if (isQuarantineEnabled()) {
-                quarantineFailedNodes(errors.keySet());
+                quarantineFailedNodes(errors);
             } else {
                 cleanupFailedNodes(errors.keySet());
             }
@@ -796,11 +827,18 @@
         return ReferenceWithError.newInstanceWithoutError(result);
     }
 
-    protected void quarantineFailedNodes(Collection<Entity> failedEntities) {
-        for (Entity entity : failedEntities) {
-            sensors().emit(ENTITY_QUARANTINED, entity);
-            getQuarantineGroup().addMember(entity);
-            removeMember(entity);
+    protected void quarantineFailedNodes(Map<Entity, Throwable> failedEntities) {
+        for (Map.Entry<Entity, Throwable> entry : failedEntities.entrySet()) {
+            Entity entity = entry.getKey();
+            Throwable cause = entry.getValue();
+            if (cause == null || getQuarantineFilter().apply(cause)) {
+                sensors().emit(ENTITY_QUARANTINED, entity);
+                getQuarantineGroup().addMember(entity);
+                removeMember(entity);
+            } else {
+                LOG.info("Cluster {} discarding failed node {}, rather than quarantining", this, entity);
+                discardNode(entity);
+            }
         }
     }
 
diff --git a/brooklyn-server/core/src/main/java/org/apache/brooklyn/entity/group/QuarantineGroup.java b/brooklyn-server/core/src/main/java/org/apache/brooklyn/entity/group/QuarantineGroup.java
index e6f2aab..c13d914 100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/entity/group/QuarantineGroup.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/entity/group/QuarantineGroup.java
@@ -27,8 +27,6 @@
 @ImplementedBy(QuarantineGroupImpl.class)
 public interface QuarantineGroup extends AbstractGroup {
 
-    ConfigKey<Boolean> MEMBER_DELEGATE_CHILDREN = ConfigKeys.newConfigKeyWithDefault(AbstractGroup.MEMBER_DELEGATE_CHILDREN, Boolean.TRUE);
-
     @Effector(description="Removes all members of the quarantined group, unmanaging them")
     void expungeMembers(
             @EffectorParam(name="firstStop", description="Whether to first call stop() on those members that are stoppable") boolean stopFirst);
diff --git a/brooklyn-server/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java b/brooklyn-server/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java
index 5e74e7c..b059858 100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/location/ssh/SshMachineLocation.java
@@ -89,6 +89,7 @@
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.exceptions.RuntimeInterruptedException;
 import org.apache.brooklyn.util.guava.KeyTransformingLoadingCache.KeyTransformingSameTypeLoadingCache;
+import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.pool.BasicPool;
 import org.apache.brooklyn.util.pool.Pool;
 import org.apache.brooklyn.util.ssh.BashCommands;
@@ -389,7 +390,7 @@
     private BasicPool<SshTool> buildPool(final Map<String, ?> properties) {
         return BasicPool.<SshTool>builder()
                 .name(getDisplayName()+"@"+address+":"+getPort()+
-                        (config().getRaw(SSH_HOST).isPresent() ? "("+getConfig(SSH_HOST)+":"+getConfig(SSH_PORT)+")" : "")+
+                        (config().getRaw(SSH_HOST).isPresent() ? "("+getConfig(SSH_HOST)+":"+getPort()+")" : "")+
                         ":hash"+System.identityHashCode(this))
                 .supplier(new Supplier<SshTool>() {
                         @Override public SshTool get() {
@@ -550,7 +551,21 @@
 
     /** port for SSHing */
     public int getPort() {
-        return getConfig(SshTool.PROP_PORT);
+        // Prefer PROP_PORT (i.e. "port"). However if that is explicitly null or is not set, then see if
+        // SSH_PORT (i.e. "brooklyn.ssh.config.port") has been set and use that.
+        // If neither is set (or is explicitly set to null), then use the default PROP_PORT value.
+        //
+        // Note we don't just rely on config().get(PROP_PORT) returning the default, because we hit a rebind
+        // error where the location's port configuration had been explicitly set to null.
+        // See https://issues.apache.org/jira/browse/BROOKLYN-215
+        
+        Maybe<Object> raw = config().getRaw(SshTool.PROP_PORT);
+        if (raw.orNull() == null && config().getRaw(SSH_PORT).orNull() != null) {
+            return config().get(SSH_PORT);
+        } else {
+            Integer result = config().get(SshTool.PROP_PORT);
+            return (result != null) ? result : SshTool.PROP_PORT.getDefaultValue();
+        }
     }
 
     protected <T> T execSsh(final Map<String, ?> props, final Function<ShellTool, T> task) {
diff --git a/brooklyn-server/core/src/main/resources/org/apache/brooklyn/location/geo/external-ip-address-resolvers.txt b/brooklyn-server/core/src/main/resources/org/apache/brooklyn/location/geo/external-ip-address-resolvers.txt
index bc3c26e..693114a 100644
--- a/brooklyn-server/core/src/main/resources/org/apache/brooklyn/location/geo/external-ip-address-resolvers.txt
+++ b/brooklyn-server/core/src/main/resources/org/apache/brooklyn/location/geo/external-ip-address-resolvers.txt
@@ -18,7 +18,6 @@
 http://jsonip.com/
 http://myip.dnsomatic.com/
 http://checkip.dyndns.org/
-http://www.telize.com/ip
 http://wtfismyip.com/text
 http://whatismyip.akamai.com/
 http://myip.wampdeveloper.com/
diff --git a/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/catalog/internal/CatalogDtoTest.java b/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/catalog/internal/CatalogDtoTest.java
index dc3662c..228c922 100644
--- a/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/catalog/internal/CatalogDtoTest.java
+++ b/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/catalog/internal/CatalogDtoTest.java
@@ -39,12 +39,14 @@
 import org.apache.brooklyn.core.catalog.internal.CatalogXmlSerializer;
 import org.apache.brooklyn.core.catalog.internal.CatalogClasspathDo.CatalogScanningModes;
 import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.mgmt.BrooklynTags;
 import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
 import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
 import org.apache.brooklyn.core.test.entity.TestApplication;
 import org.apache.brooklyn.core.test.entity.TestEntity;
 import org.apache.brooklyn.util.core.BrooklynMavenArtifacts;
 import org.apache.brooklyn.util.maven.MavenRetriever;
+import org.apache.commons.lang3.ClassUtils;
 
 import com.google.common.collect.ImmutableList;
 
@@ -111,6 +113,9 @@
         testEntitiesJavaCatalog.addEntry(CatalogItemBuilder.newTemplate(TestApplication.class.getCanonicalName(), BasicBrooklynCatalog.NO_VERSION)
                 .displayName("Test App from JAR")
                 .javaType(TestApplication.class.getCanonicalName())
+                .tag(BrooklynTags.newNotesTag("Some notes for catalog testing"))
+                .tag(BrooklynTags.newYamlSpecTag("This is the spec for a test catalog item"))
+                .tag(BrooklynTags.newTraitsTag(ClassUtils.getAllInterfaces(TestApplication.class)))
                 .build());
         testEntitiesJavaCatalog.addEntry(CatalogItemBuilder.newEntity(TestEntity.class.getCanonicalName(), BasicBrooklynCatalog.NO_VERSION)
                 .displayName("Test Entity from JAR")
diff --git a/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/entity/trait/FailingEntity.java b/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/entity/trait/FailingEntity.java
index 532eb4d..7845326 100644
--- a/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/entity/trait/FailingEntity.java
+++ b/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/entity/trait/FailingEntity.java
@@ -60,7 +60,7 @@
     ConfigKey<Predicate<? super FailingEntity>> FAIL_ON_RESTART_CONDITION = (ConfigKey) ConfigKeys.newConfigKey(Predicate.class, "failOnRestartCondition", "Whether to throw exception on call to restart", null);
     
     @SetFromFlag("exceptionClazz")
-    ConfigKey<Class<? extends RuntimeException>> EXCEPTION_CLAZZ = (ConfigKey) ConfigKeys.newConfigKey(Class.class, "exceptionClazz", "Type of exception to throw", IllegalStateException.class);
+    ConfigKey<Class<? extends Exception>> EXCEPTION_CLAZZ = (ConfigKey) ConfigKeys.newConfigKey(Class.class, "exceptionClazz", "Type of exception to throw", IllegalStateException.class);
     
     @SetFromFlag("execOnFailure")
     ConfigKey<Function<? super FailingEntity,?>> EXEC_ON_FAILURE = (ConfigKey) ConfigKeys.newConfigKey(Function.class, "execOnFailure", "Callback to execute before throwing an exception, on any failure", Functions.identity());
diff --git a/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/entity/trait/FailingEntityImpl.java b/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/entity/trait/FailingEntityImpl.java
index a675c78..423bdd3 100644
--- a/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/entity/trait/FailingEntityImpl.java
+++ b/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/entity/trait/FailingEntityImpl.java
@@ -79,7 +79,12 @@
     
     private RuntimeException newException(String msg) {
         try {
-            return getConfig(EXCEPTION_CLAZZ).getConstructor(String.class).newInstance("Simulating entity stop failure for test");
+            Exception result = getConfig(EXCEPTION_CLAZZ).getConstructor(String.class).newInstance("Simulating entity stop failure for test");
+            if (!(result instanceof RuntimeException)) {
+                return new RuntimeException("wrapping", result);
+            } else {
+                return (RuntimeException)result;
+            }
         } catch (Exception e) {
             throw Exceptions.propagate(e);
         }
diff --git a/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindSshMachineLocationTest.java b/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindSshMachineLocationTest.java
index 92fdb55..ec8cacb 100644
--- a/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindSshMachineLocationTest.java
+++ b/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindSshMachineLocationTest.java
@@ -80,5 +80,23 @@
         
         assertEquals(newChildLoc.execScript(Collections.<String,Object>emptyMap(), "mysummary", ImmutableList.of("true")), 0);
     }
-    
+
+    // See https://issues.apache.org/jira/browse/BROOKLYN-215
+    @Test(groups="Integration")
+    public void testRebindWhenPortNull() throws Exception {
+        SshMachineLocation machine = origManagementContext.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+                .configure("address", "localhost")
+                .configure("port", null));
+        FixedListMachineProvisioningLocation<?> byon = origManagementContext.getLocationManager().createLocation(LocationSpec.create(FixedListMachineProvisioningLocation.class)
+                .configure("machines", ImmutableList.of(machine)));
+        origApp.start(ImmutableList.of(byon));
+        LOG.info("Before rebind, machine="+machine.toString());
+
+        newApp = (TestApplication) rebind();
+        
+        FixedListMachineProvisioningLocation<?> newByon = (FixedListMachineProvisioningLocation<?>) Iterables.getOnlyElement(newApp.getLocations(), 0);
+        SshMachineLocation newMachine = (SshMachineLocation) Iterables.get(newByon.getChildren(), 0);
+        
+        LOG.info("After rebind, machine="+newMachine.toString());
+    }
 }
diff --git a/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/test/entity/TestCluster.java b/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/test/entity/TestCluster.java
index 3ff61f0..9593203 100644
--- a/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/test/entity/TestCluster.java
+++ b/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/test/entity/TestCluster.java
@@ -18,8 +18,12 @@
  */
 package org.apache.brooklyn.core.test.entity;
 
+import java.util.List;
+
 import org.apache.brooklyn.api.entity.EntityLocal;
 import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.entity.group.DynamicCluster;
 
 /**
@@ -27,4 +31,10 @@
 */
 @ImplementedBy(TestClusterImpl.class)
 public interface TestCluster extends DynamicCluster, EntityLocal {
+    
+    ConfigKey<Integer> MAX_SIZE = ConfigKeys.newIntegerConfigKey("testCluster.maxSize", "Size after which it will throw InsufficientCapacityException", Integer.MAX_VALUE);
+    
+    List<Integer> getSizeHistory();
+    
+    List<Integer> getDesiredSizeHistory();
 }
diff --git a/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/test/entity/TestClusterImpl.java b/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/test/entity/TestClusterImpl.java
index bf8d0cb..0edea8f 100644
--- a/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/test/entity/TestClusterImpl.java
+++ b/brooklyn-server/core/src/test/java/org/apache/brooklyn/core/test/entity/TestClusterImpl.java
@@ -18,22 +18,32 @@
  */
 package org.apache.brooklyn.core.test.entity;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
 import org.apache.brooklyn.core.entity.trait.Startable;
 import org.apache.brooklyn.entity.group.DynamicClusterImpl;
 import org.apache.brooklyn.util.collections.QuorumCheck.QuorumChecks;
 
+import com.google.common.collect.ImmutableList;
+
 /**
 * Mock cluster entity for testing.
 */
 public class TestClusterImpl extends DynamicClusterImpl implements TestCluster {
     private volatile int size;
 
+    private final List<Integer> desiredSizeHistory = Collections.synchronizedList(new ArrayList<Integer>());
+    private final List<Integer> sizeHistory = Collections.synchronizedList(new ArrayList<Integer>());
+    
     public TestClusterImpl() {
     }
     
     @Override
     public void init() {
         super.init();
+        sizeHistory.add(size);
         size = getConfig(INITIAL_SIZE);
         sensors().set(Startable.SERVICE_UP, true);
     }
@@ -48,11 +58,36 @@
     
     @Override
     public Integer resize(Integer desiredSize) {
-        this.size = desiredSize;
+        desiredSizeHistory.add(desiredSize);
+        int achievableSize = Math.min(desiredSize, getConfig(MAX_SIZE));
+        
+        if (achievableSize != size) {
+            this.sizeHistory.add(achievableSize);
+            this.size = achievableSize;
+        }
+        
+        if (desiredSize > achievableSize) {
+            throw new InsufficientCapacityException("Simulating insufficient capacity (desiredSize="+desiredSize+"; maxSize="+getConfig(MAX_SIZE)+"; newSize="+size+")");
+        }
+
         return size;
     }
     
     @Override
+    public List<Integer> getSizeHistory() {
+        synchronized (sizeHistory) {
+            return ImmutableList.copyOf(sizeHistory);
+        }
+    }
+    
+    @Override
+    public List<Integer> getDesiredSizeHistory() {
+        synchronized (desiredSizeHistory) {
+            return ImmutableList.copyOf(desiredSizeHistory);
+        }
+    }
+    
+    @Override
     public void stop() {
         size = 0;
         super.stop();
diff --git a/brooklyn-server/core/src/test/java/org/apache/brooklyn/entity/group/DynamicClusterTest.java b/brooklyn-server/core/src/test/java/org/apache/brooklyn/entity/group/DynamicClusterTest.java
index dd37e58..f58ac90 100644
--- a/brooklyn-server/core/src/test/java/org/apache/brooklyn/entity/group/DynamicClusterTest.java
+++ b/brooklyn-server/core/src/test/java/org/apache/brooklyn/entity/group/DynamicClusterTest.java
@@ -44,6 +44,7 @@
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.entity.EntitySpec;
 import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.NoMachinesAvailableException;
 import org.apache.brooklyn.api.mgmt.Task;
 import org.apache.brooklyn.api.sensor.SensorEvent;
 import org.apache.brooklyn.core.entity.Attributes;
@@ -54,6 +55,7 @@
 import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
 import org.apache.brooklyn.core.entity.trait.Changeable;
 import org.apache.brooklyn.core.entity.trait.FailingEntity;
+import org.apache.brooklyn.core.entity.trait.Resizable;
 import org.apache.brooklyn.core.location.SimulatedLocation;
 import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
 import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
@@ -72,13 +74,13 @@
 import org.testng.annotations.Test;
 
 import com.google.common.base.Function;
+import com.google.common.base.Predicate;
 import com.google.common.base.Predicates;
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.Atomics;
 
 
@@ -202,6 +204,65 @@
         assertEquals(entity.getApplication(), app);
     }
 
+    @Test
+    public void testResizeWhereChildThrowsNoMachineAvailableExceptionIsPropagatedAsInsufficientCapacityException() throws Exception {
+        final DynamicCluster cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+            .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(FailingEntity.class)
+                    .configure(FailingEntity.FAIL_ON_START, true)
+                    .configure(FailingEntity.EXCEPTION_CLAZZ, NoMachinesAvailableException.class))
+            .configure(DynamicCluster.INITIAL_SIZE, 0));
+        cluster.start(ImmutableList.of(loc));
+        
+        try {
+            cluster.resize(1);
+            Asserts.shouldHaveFailedPreviously();
+        } catch (Exception e) {
+            Asserts.expectedFailureOfType(e, Resizable.InsufficientCapacityException.class);
+        }
+    }
+
+    @Test
+    public void testResizeWhereSubsetOfChildrenThrowsNoMachineAvailableExceptionIsPropagatedAsInsuffientCapacityException() throws Exception {
+        final DynamicCluster cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+            .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(FailingEntity.class)
+                    .configure(FailingEntity.FAIL_ON_START_CONDITION, new Predicate<FailingEntity>() {
+                        final AtomicInteger counter = new AtomicInteger();
+                        @Override public boolean apply(FailingEntity input) {
+                            // Only second and subsequent entities fail
+                            int index = counter.getAndIncrement();
+                            return (index >= 1);
+                        }})
+                    .configure(FailingEntity.EXCEPTION_CLAZZ, NoMachinesAvailableException.class))
+            .configure(DynamicCluster.INITIAL_SIZE, 0));
+        cluster.start(ImmutableList.of(loc));
+
+        // Managed to partially resize, but will still throw exception.
+        // The getCurrentSize will report how big we managed to get.
+        // The children that failed due to NoMachinesAvailableException will have been unmanaged automatically.
+        try {
+            cluster.resize(2);
+            Asserts.shouldHaveFailedPreviously();
+        } catch (Exception e) {
+            Asserts.expectedFailureOfType(e, Resizable.InsufficientCapacityException.class);
+        }
+        assertEquals(cluster.getCurrentSize(), (Integer)1);
+        Iterable<FailingEntity> children1 = Iterables.filter(cluster.getChildren(), FailingEntity.class);
+        assertEquals(Iterables.size(children1), 1);
+        assertEquals(Iterables.getOnlyElement(children1).sensors().get(TestEntity.SERVICE_UP), Boolean.TRUE);
+        
+        // This attempt will also fail, because all new children will fail
+        try {
+            cluster.resize(2);
+            Asserts.shouldHaveFailedPreviously();
+        } catch (Exception e) {
+            Asserts.expectedFailureOfType(e, Resizable.InsufficientCapacityException.class);
+        }
+        assertEquals(cluster.getCurrentSize(), (Integer)1);
+        Iterable<FailingEntity> children2 = Iterables.filter(cluster.getChildren(), FailingEntity.class);
+        assertEquals(Iterables.size(children2), 1);
+        assertEquals(Iterables.getOnlyElement(children2), Iterables.getOnlyElement(children1));
+    }
+
     /** This can be sensitive to order, e.g. if TestEntity set expected RUNNING before setting SERVICE_UP, 
      * there would be a point when TestEntity is ON_FIRE.
      * <p>
@@ -249,6 +310,7 @@
         assertEquals(Iterables.size(Entities.descendants(cluster, TestEntity.class)), 0);
     }
 
+    
     @Test
     public void currentSizePropertyReflectsActualClusterSize() throws Exception {
         DynamicCluster cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
@@ -583,6 +645,62 @@
     }
 
     @Test
+    public void testQuarantineFailedEntitiesRespectsCustomFilter() throws Exception {
+        Predicate<Throwable> filter = new Predicate<Throwable>() {
+            @Override public boolean apply(Throwable input) {
+                return Exceptions.getFirstThrowableOfType(input, AllowedException.class) != null;
+            }
+        };
+        runQuarantineFailedEntitiesRespectsFilter(AllowedException.class, DisallowedException.class, filter);
+    }
+    @SuppressWarnings("serial")
+    public static class AllowedException extends RuntimeException {
+        public AllowedException(String message) {
+            super(message);
+        }
+    }
+    @SuppressWarnings("serial")
+    public static class DisallowedException extends RuntimeException {
+        public DisallowedException(String message) {
+            super(message);
+        }
+    }
+
+    @Test
+    public void testQuarantineFailedEntitiesRespectsDefaultFilter() throws Exception {
+        Predicate<Throwable> filter = null;
+        runQuarantineFailedEntitiesRespectsFilter(AllowedException.class, NoMachinesAvailableException.class, filter);
+    }
+    
+    protected void runQuarantineFailedEntitiesRespectsFilter(Class<? extends Exception> allowedException, 
+            Class<? extends Exception> disallowedException, Predicate<Throwable> quarantineFilter) throws Exception {
+        final List<Class<? extends Exception>> failureCauses = ImmutableList.<Class<? extends Exception>>of(allowedException, disallowedException);
+        final AtomicInteger counter = new AtomicInteger(0);
+        DynamicCluster cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+                .configure("quarantineFailedEntities", true)
+                .configure("initialSize", 0)
+                .configure("quarantineFilter", quarantineFilter)
+                .configure("factory", new EntityFactory() {
+                    @Override public Entity newEntity(Map flags, Entity parent) {
+                        int num = counter.getAndIncrement();
+                        return app.getManagementContext().getEntityManager().createEntity(EntitySpec.create(FailingEntity.class)
+                                .configure(flags)
+                                .configure(FailingEntity.FAIL_ON_START, true)
+                                .configure(FailingEntity.EXCEPTION_CLAZZ, failureCauses.get(num))
+                                .parent(parent));
+                    }}));
+
+        cluster.start(ImmutableList.of(loc));
+        resizeExpectingError(cluster, 2);
+        Iterable<FailingEntity> children = Iterables.filter(cluster.getChildren(), FailingEntity.class);
+        Collection<Entity> quarantineMembers = cluster.sensors().get(DynamicCluster.QUARANTINE_GROUP).getMembers();
+        
+        assertEquals(cluster.getCurrentSize(), (Integer)0);
+        assertEquals(Iterables.getOnlyElement(children).config().get(FailingEntity.EXCEPTION_CLAZZ), allowedException);
+        assertEquals(Iterables.getOnlyElement(quarantineMembers), Iterables.getOnlyElement(children));
+    }
+
+    @Test
     public void defaultRemovalStrategyShutsDownNewestFirstWhenResizing() throws Exception {
         final List<Entity> creationOrder = Lists.newArrayList();
         DynamicCluster cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
diff --git a/brooklyn-server/karaf/feature.xml b/brooklyn-server/karaf/feature.xml
deleted file mode 100644
index fdb959d..0000000
--- a/brooklyn-server/karaf/feature.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
-    Licensed to the Apache Software Foundation (ASF) under one or more
-    contributor license agreements.  See the NOTICE file distributed with
-    this work for additional information regarding copyright ownership.
-    The ASF licenses this file to You under the Apache License, Version 2.0
-    (the "License"); you may not use this file except in compliance with
-    the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="org.apache.brooklyn-${project.version}">
-
-    <repository>mvn:org.apache.karaf.features/standard/${karaf.version}/xml/features</repository>
-    <repository>mvn:org.apache.karaf.features/enterprise/${karaf.version}/xml/features</repository>
-    <repository>mvn:org.apache.karaf.features/spring/${karaf.version}/xml/features</repository>
-  
-    <!-- TODO: complete the features set -->
-    <feature name="brooklyn-core" version="${project.version}" description="Brooklyn Core">
-        <bundle>mvn:org.apache.brooklyn/brooklyn-core/${project.version}</bundle>
-        <bundle>mvn:org.apache.brooklyn/brooklyn-api/${project.version}</bundle>
-        <bundle>mvn:org.apache.brooklyn/brooklyn-utils-common/${project.version}</bundle>
-        <bundle>mvn:org.apache.brooklyn/brooklyn-utils-groovy/${project.version}</bundle>
-        <bundle>mvn:org.apache.brooklyn/brooklyn-logback-includes/${project.version}</bundle>
-    
-        <!-- TODO: via geoip -->
-        <bundle dependency="true">wrap:mvn:com.google.http-client/google-http-client/1.18.0-rc</bundle>
-        <!-- TODO: why it does not come automagically? -->
-        <bundle dependency="true">mvn:com.google.guava/guava/${guava.version}</bundle>
-        <bundle dependency="true">mvn:com.google.code.gson/gson/${gson.version}</bundle>
-        <bundle dependency="true">mvn:com.jayway.jsonpath/json-path/${jsonPath.version}</bundle>
-        <bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-core/${fasterxml.jackson.version}</bundle>
-        <bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-databind/${fasterxml.jackson.version}</bundle>
-        <bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-annotations/${fasterxml.jackson.version}</bundle>
-        <bundle dependency="true">mvn:net.minidev/json-smart/${jsonSmart.version}</bundle>
-        <bundle dependency="true">mvn:net.minidev/asm/${minidev.asm.version}</bundle>
-    </feature>
-  
-    <feature name="brooklyn-commands"  version="${project.version}"  description="Brooklyn Shell Commands">
-        <bundle>mvn:org.apache.brooklyn/brooklyn-commands/${project.version}</bundle>
-        <!--<feature version="${project.version}">brooklyn-core</feature>-->
-    </feature>
-  
-</features>
diff --git a/brooklyn-server/karaf/features/pom.xml b/brooklyn-server/karaf/features/pom.xml
index 03cd932..c7bb6b4 100755
--- a/brooklyn-server/karaf/features/pom.xml
+++ b/brooklyn-server/karaf/features/pom.xml
@@ -51,10 +51,6 @@
                     <startLevel>50</startLevel>
                     <aggregateFeatures>true</aggregateFeatures>
                     <resolver>(obr)</resolver>
-                    <checkDependencyChange>true</checkDependencyChange>
-                    <failOnDependencyChange>false</failOnDependencyChange>
-                    <logDependencyChanges>true</logDependencyChanges>
-                    <overwriteChangedDependencies>true</overwriteChangedDependencies>
                 </configuration>
             </plugin>
         </plugins>
diff --git a/brooklyn-server/karaf/features/src/main/feature/feature.xml b/brooklyn-server/karaf/features/src/main/feature/feature.xml
index 384bc7d..49a90e6 100644
--- a/brooklyn-server/karaf/features/src/main/feature/feature.xml
+++ b/brooklyn-server/karaf/features/src/main/feature/feature.xml
@@ -197,4 +197,22 @@
         <!--<feature version="${project.version}">brooklyn-core</feature>-->
     </feature>
   
+    <feature name="brooklyn-software-winrm" version="${project.version}" description="Brooklyn WinRM Software Entities">
+        <bundle>mvn:org.apache.brooklyn/brooklyn-software-winrm/${project.version}</bundle>
+        <feature>brooklyn-core</feature>
+        <bundle dependency="true">wrap:mvn:io.cloudsoft.windows/winrm4j/${winrm4j.version}</bundle>
+    </feature>
+
+    <feature name="brooklyn-policy"  version="${project.version}" description="Brooklyn Policies">
+        <bundle>mvn:org.apache.brooklyn/brooklyn-policy/${project.version}</bundle>
+        <feature>brooklyn-core</feature>
+    </feature>
+
+    <feature name="brooklyn-software-base"  version="${project.version}"  description="Brooklyn Software Base">
+        <bundle>mvn:org.apache.brooklyn/brooklyn-software-base/${project.version}</bundle>
+        <feature>brooklyn-software-winrm</feature>
+        <feature>brooklyn-policy</feature>
+        <!-- python dependency required but not supported since karaf cannot handle its runtime bindings -->
+    </feature>
+
 </features>
diff --git a/brooklyn-server/karaf/features/src/main/history/dependencies.xml b/brooklyn-server/karaf/features/src/main/history/dependencies.xml
deleted file mode 100644
index 2bcbdca..0000000
--- a/brooklyn-server/karaf/features/src/main/history/dependencies.xml
+++ /dev/null
@@ -1,103 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.3.0" name="org.apache.brooklyn-0.9.0-SNAPSHOT">  <!-- BROOKLYN_VERSION -->
-    <feature version="0.0.0">
-        <feature prerequisite="false" dependency="false">brooklyn-api</feature>
-        <feature prerequisite="false" dependency="false">brooklyn-api</feature>
-        <feature prerequisite="false" dependency="false">brooklyn-camp-base</feature>
-        <feature prerequisite="false" dependency="false">brooklyn-camp-base</feature>
-        <feature prerequisite="false" dependency="false">brooklyn-camp-base</feature>
-        <feature prerequisite="false" dependency="false">brooklyn-camp-brooklyn</feature>
-        <feature prerequisite="false" dependency="false">brooklyn-core</feature>
-        <feature prerequisite="false" dependency="false">brooklyn-core</feature>
-        <feature prerequisite="false" dependency="false">brooklyn-rest-api</feature>
-        <feature prerequisite="false" dependency="false">brooklyn-utils-common</feature>
-        <feature prerequisite="false" dependency="false">brooklyn-utils-common</feature>
-        <feature prerequisite="false" dependency="false">brooklyn-utils-common</feature>
-        <feature prerequisite="false" dependency="false">brooklyn-utils-common</feature>
-        <feature prerequisite="false" dependency="false">brooklyn-utils-rest-swagger</feature>
-        <feature prerequisite="false" dependency="false">brooklyn-utils-rest-swagger</feature>
-        <feature prerequisite="false" dependency="false">jetty</feature>
-        <feature prerequisite="false" dependency="false">swagger-crippled</feature>
-        <feature prerequisite="false" dependency="false">war</feature>
-        <feature prerequisite="false" dependency="false">war</feature>
-        <bundle>mvn:ch.qos.logback/logback-classic/1.0.7</bundle>
-        <bundle>mvn:ch.qos.logback/logback-core/1.0.7</bundle>
-        <bundle>mvn:com.fasterxml.jackson.core/jackson-annotations/2.4.5</bundle>
-        <bundle>mvn:com.fasterxml.jackson.core/jackson-annotations/2.4.5</bundle>
-        <bundle>mvn:com.fasterxml.jackson.core/jackson-core/2.4.5</bundle>
-        <bundle>mvn:com.fasterxml.jackson.core/jackson-core/2.4.5</bundle>
-        <bundle>mvn:com.fasterxml.jackson.core/jackson-databind/2.4.5</bundle>
-        <bundle>mvn:com.fasterxml.jackson.core/jackson-databind/2.4.5</bundle>
-        <bundle>mvn:com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.4.5</bundle>
-        <bundle>mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-base/2.4.5</bundle>
-        <bundle>mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-json-provider/2.4.5</bundle>
-        <bundle>mvn:com.google.code.gson/gson/2.3</bundle>
-        <bundle>mvn:com.google.guava/guava/17.0</bundle>
-        <bundle>mvn:com.jayway.jsonpath/json-path/2.0.0</bundle>
-        <bundle>mvn:com.sun.jersey.contribs/jersey-multipart/1.19</bundle>
-        <bundle>mvn:com.sun.jersey/jersey-core/1.19</bundle>
-        <bundle>mvn:com.sun.jersey/jersey-core/1.19</bundle>
-        <bundle>mvn:com.sun.jersey/jersey-server/1.19</bundle>
-        <bundle>mvn:com.sun.jersey/jersey-server/1.19</bundle>
-        <bundle>mvn:com.sun.jersey/jersey-servlet/1.19</bundle>
-        <bundle>mvn:com.sun.jersey/jersey-servlet/1.19</bundle>
-        <bundle>mvn:com.sun.jersey/jersey-servlet/1.19</bundle>
-        <bundle>mvn:com.thoughtworks.xstream/xstream/1.4.7</bundle>
-        <bundle>mvn:commons-beanutils/commons-beanutils/1.9.1</bundle>
-        <bundle>mvn:commons-codec/commons-codec/1.9</bundle>
-        <bundle>mvn:commons-codec/commons-codec/1.9</bundle>
-        <bundle>mvn:commons-collections/commons-collections/3.2.1</bundle>
-        <bundle>mvn:commons-io/commons-io/2.4</bundle>
-        <bundle>mvn:commons-lang/commons-lang/2.4</bundle>
-        <bundle>mvn:io.swagger/swagger-annotations/1.5.3</bundle>
-        <bundle>mvn:io.swagger/swagger-models/1.5.3</bundle>
-        <bundle>mvn:javax.servlet/javax.servlet-api/3.1.0</bundle>
-        <bundle>mvn:javax.servlet/javax.servlet-api/3.1.0</bundle>
-        <bundle>mvn:javax.ws.rs/jsr311-api/1.1.1</bundle>
-        <bundle>mvn:net.minidev/asm/1.0.2</bundle>
-        <bundle>mvn:net.minidev/json-smart/2.1.1</bundle>
-        <bundle>mvn:net.schmizz/sshj/0.8.1</bundle>
-        <bundle>mvn:org.apache.brooklyn.camp/camp-base/0.9.0-SNAPSHOT</bundle>  <!-- BROOKLYN_VERSION -->
-        <bundle>mvn:org.apache.brooklyn.camp/camp-server/0.9.0-SNAPSHOT</bundle>  <!-- BROOKLYN_VERSION -->
-        <bundle>mvn:org.apache.brooklyn/brooklyn-api/0.9.0-SNAPSHOT</bundle>  <!-- BROOKLYN_VERSION -->
-        <bundle>mvn:org.apache.brooklyn/brooklyn-camp/0.9.0-SNAPSHOT</bundle>  <!-- BROOKLYN_VERSION -->
-        <bundle>mvn:org.apache.brooklyn/brooklyn-commands/0.9.0-SNAPSHOT</bundle>  <!-- BROOKLYN_VERSION -->
-        <bundle>mvn:org.apache.brooklyn/brooklyn-core/0.9.0-SNAPSHOT</bundle>  <!-- BROOKLYN_VERSION -->
-        <bundle>mvn:org.apache.brooklyn/brooklyn-jsgui/0.9.0-SNAPSHOT/war</bundle>  <!-- BROOKLYN_VERSION -->
-        <bundle>mvn:org.apache.brooklyn/brooklyn-logback-includes/0.9.0-SNAPSHOT</bundle>  <!-- BROOKLYN_VERSION -->
-        <bundle>mvn:org.apache.brooklyn/brooklyn-rest-api/0.9.0-SNAPSHOT</bundle>  <!-- BROOKLYN_VERSION -->
-        <bundle>mvn:org.apache.brooklyn/brooklyn-rest-server/0.9.0-SNAPSHOT</bundle>  <!-- BROOKLYN_VERSION -->
-        <bundle>mvn:org.apache.brooklyn/brooklyn-rt-osgi/0.9.0-SNAPSHOT</bundle>  <!-- BROOKLYN_VERSION -->
-        <bundle>mvn:org.apache.brooklyn/brooklyn-utils-common/0.9.0-SNAPSHOT</bundle>  <!-- BROOKLYN_VERSION -->
-        <bundle>mvn:org.apache.brooklyn/brooklyn-utils-common/0.9.0-SNAPSHOT</bundle>  <!-- BROOKLYN_VERSION -->
-        <bundle>mvn:org.apache.brooklyn/brooklyn-utils-groovy/0.9.0-SNAPSHOT</bundle>  <!-- BROOKLYN_VERSION -->
-        <bundle>mvn:org.apache.brooklyn/brooklyn-utils-rest-swagger/0.9.0-SNAPSHOT</bundle>  <!-- BROOKLYN_VERSION -->
-        <bundle>mvn:org.apache.commons/commons-compress/1.4</bundle>
-        <bundle>mvn:org.apache.commons/commons-lang3/3.1</bundle>
-        <bundle>mvn:org.apache.commons/commons-lang3/3.1</bundle>
-        <bundle>mvn:org.apache.commons/commons-lang3/3.1</bundle>
-        <bundle>mvn:org.apache.commons/commons-lang3/3.1</bundle>
-        <bundle>mvn:org.apache.httpcomponents/httpclient-osgi/4.4.1</bundle>
-        <bundle>mvn:org.apache.httpcomponents/httpcore-osgi/4.4.1</bundle>
-        <bundle>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.jzlib/1.1.3_2</bundle>
-        <bundle>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.reflections/0.9.9_1</bundle>
-        <bundle>mvn:org.bouncycastle/bcpkix-jdk15on/1.49</bundle>
-        <bundle>mvn:org.bouncycastle/bcprov-ext-jdk15on/1.49</bundle>
-        <bundle>mvn:org.codehaus.groovy/groovy-all/2.3.7</bundle>
-        <bundle>mvn:org.codehaus.jackson/jackson-core-asl/1.9.13</bundle>
-        <bundle>mvn:org.codehaus.jackson/jackson-core-asl/1.9.13</bundle>
-        <bundle>mvn:org.codehaus.jackson/jackson-core-asl/1.9.13</bundle>
-        <bundle>mvn:org.codehaus.jackson/jackson-jaxrs/1.9.13</bundle>
-        <bundle>mvn:org.codehaus.jackson/jackson-mapper-asl/1.9.13</bundle>
-        <bundle>mvn:org.codehaus.jackson/jackson-mapper-asl/1.9.13</bundle>
-        <bundle>mvn:org.freemarker/freemarker/2.3.22</bundle>
-        <bundle>mvn:org.jvnet.mimepull/mimepull/1.9.3</bundle>
-        <bundle>mvn:org.slf4j/jul-to-slf4j/1.6.6</bundle>
-        <bundle>mvn:org.yaml/snakeyaml/1.11</bundle>
-        <bundle>wrap:mvn:com.google.http-client/google-http-client/1.18.0-rc</bundle>
-        <bundle>wrap:mvn:com.maxmind.geoip2/geoip2/0.8.1</bundle>
-        <bundle>wrap:mvn:javax.validation/validation-api/1.1.0.Final</bundle>
-        <bundle>wrap:mvn:org.tukaani/xz/1.4</bundle>
-        <bundle>wrap:mvn:xpp3/xpp3_min/1.1.4c</bundle>
-    </feature>
-</features>
diff --git a/brooklyn-server/policy/src/main/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicy.java b/brooklyn-server/policy/src/main/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicy.java
index d315260..b484359 100644
--- a/brooklyn-server/policy/src/main/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicy.java
+++ b/brooklyn-server/policy/src/main/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicy.java
@@ -20,7 +20,6 @@
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.apache.brooklyn.util.JavaGroovyEquivalents.groovyTruth;
-import groovy.lang.Closure;
 
 import java.util.Map;
 import java.util.concurrent.Callable;
@@ -30,8 +29,6 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.apache.brooklyn.api.catalog.Catalog;
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.entity.EntityLocal;
@@ -55,6 +52,8 @@
 import org.apache.brooklyn.util.core.flags.TypeCoercions;
 import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.time.Duration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Function;
 import com.google.common.base.Preconditions;
@@ -62,6 +61,8 @@
 import com.google.common.reflect.TypeToken;
 import com.google.common.util.concurrent.ThreadFactoryBuilder;
 
+import groovy.lang.Closure;
+
 
 /**
  * Policy that is attached to a {@link Resizable} entity and dynamically adjusts its size in response to
@@ -290,6 +291,7 @@
             .defaultValue(1)
             .reconfigurable(true)
             .build();
+    
     @SetFromFlag("resizeUpIterationMax")
     public static final ConfigKey<Integer> RESIZE_UP_ITERATION_MAX = BasicConfigKey.builder(Integer.class)
             .name("autoscaler.resizeUpIterationMax")
@@ -297,6 +299,7 @@
             .description("Maximum change to the size on a single iteration when scaling up")
             .reconfigurable(true)
             .build();
+    
     @SetFromFlag("resizeDownIterationIncrement")
     public static final ConfigKey<Integer> RESIZE_DOWN_ITERATION_INCREMENT = BasicConfigKey.builder(Integer.class)
             .name("autoscaler.resizeDownIterationIncrement")
@@ -304,6 +307,7 @@
             .defaultValue(1)
             .reconfigurable(true)
             .build();
+    
     @SetFromFlag("resizeDownIterationMax")
     public static final ConfigKey<Integer> RESIZE_DOWN_ITERATION_MAX = BasicConfigKey.builder(Integer.class)
             .name("autoscaler.resizeDownIterationMax")
@@ -346,6 +350,12 @@
             .reconfigurable(true)
             .build();
 
+    public static final ConfigKey<Integer> INSUFFICIENT_CAPACITY_HIGH_WATER_MARK = BasicConfigKey.builder(Integer.class)
+            .name("autoscaler.insufficientCapacityHighWaterMark")
+            .defaultValue(null)
+            .reconfigurable(true)
+            .build();
+
     @SetFromFlag("resizeOperator")
     public static final ConfigKey<ResizeOperator> RESIZE_OPERATOR = BasicConfigKey.builder(ResizeOperator.class)
             .name("autoscaler.resizeOperator")
@@ -564,6 +574,10 @@
         return getConfig(MAX_POOL_SIZE);
     }
     
+    private Integer getInsufficientCapacityHighWaterMark() {
+        return getConfig(INSUFFICIENT_CAPACITY_HIGH_WATER_MARK);
+    }
+    
     private ResizeOperator getResizeOperator() {
         return getConfig(RESIZE_OPERATOR);
     }
@@ -620,6 +634,14 @@
                 throw new IllegalArgumentException("Min pool size "+val+" must not be greater than max pool size "+getConfig(MAX_POOL_SIZE));
             }
             onPoolSizeLimitsChanged(getConfig(MIN_POOL_SIZE), newMax);
+        } else if (key.equals(INSUFFICIENT_CAPACITY_HIGH_WATER_MARK)) {
+            Integer newVal = (Integer) val;
+            Integer oldVal = config().get(INSUFFICIENT_CAPACITY_HIGH_WATER_MARK);
+            if (oldVal != null && (newVal == null || newVal > oldVal)) {
+                LOG.info("{} resetting {} to {}, which will enable resizing above previous level of {}",
+                        new Object[] {AutoScalerPolicy.this, INSUFFICIENT_CAPACITY_HIGH_WATER_MARK.getName(), newVal, oldVal});
+                // TODO see above about changing metricLowerBound; not triggering resize now
+            }
         } else {
             throw new UnsupportedOperationException("reconfiguring "+key+" unsupported for "+this);
         }
@@ -848,9 +870,17 @@
         onNewUnboundedPoolSize(desiredSizeUnconstrained);
     }
 
+    private int applyMinMaxConstraints(long desiredSize) {
+        return applyMinMaxConstraints(desiredSize > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)desiredSize);
+    }
+    
     private int applyMinMaxConstraints(int desiredSize) {
-        desiredSize = Math.max(getMinPoolSize(), desiredSize);
-        desiredSize = Math.min(getMaxPoolSize(), desiredSize);
+        int minSize = getMinPoolSize();
+        int maxSize = getMaxPoolSize();
+        Integer insufficientCapacityHighWaterMark = getInsufficientCapacityHighWaterMark();
+        desiredSize = Math.max(minSize, desiredSize);
+        desiredSize = Math.min(maxSize, desiredSize);
+        if (insufficientCapacityHighWaterMark != null) desiredSize = Math.min(insufficientCapacityHighWaterMark, desiredSize);
         return desiredSize;
     }
 
@@ -989,36 +1019,47 @@
     }
 
     private void resizeNow() {
-        long currentPoolSize = getCurrentSizeOperator().apply(poolEntity);
+        final int currentPoolSize = getCurrentSizeOperator().apply(poolEntity);
         CalculatedDesiredPoolSize calculatedDesiredPoolSize = calculateDesiredPoolSize(currentPoolSize);
-        final long desiredPoolSize = calculatedDesiredPoolSize.size;
+        long desiredPoolSize = calculatedDesiredPoolSize.size;
         boolean stable = calculatedDesiredPoolSize.stable;
         
+        final int targetPoolSize = applyMinMaxConstraints(desiredPoolSize);
+        
         if (!stable) {
             // the desired size fluctuations are not stable; ensure we check again later (due to time-window)
             // even if no additional events have been received
             // (note we continue now with as "good" a resize as we can given the instability)
             if (LOG.isTraceEnabled()) LOG.trace("{} re-scheduling resize check for {}, as desired size not stable (current {}, desired {}); continuing with resize...", 
-                    new Object[] {this, poolEntity, currentPoolSize, desiredPoolSize});
+                    new Object[] {this, poolEntity, currentPoolSize, targetPoolSize});
             scheduleResize();
         }
-        if (currentPoolSize == desiredPoolSize) {
+        if (currentPoolSize == targetPoolSize) {
             if (LOG.isTraceEnabled()) LOG.trace("{} not resizing pool {} from {} to {}", 
-                    new Object[] {this, poolEntity, currentPoolSize, desiredPoolSize});
+                    new Object[] {this, poolEntity, currentPoolSize, targetPoolSize});
             return;
         }
         
         if (LOG.isDebugEnabled()) LOG.debug("{} requesting resize to {}; current {}, min {}, max {}", 
-                new Object[] {this, desiredPoolSize, currentPoolSize, getMinPoolSize(), getMaxPoolSize()});
+                new Object[] {this, targetPoolSize, currentPoolSize, getMinPoolSize(), getMaxPoolSize()});
         
         Entities.submit(entity, Tasks.<Void>builder().displayName("Auto-scaler")
-            .description("Auto-scaler recommending resize from "+currentPoolSize+" to "+desiredPoolSize)
+            .description("Auto-scaler recommending resize from "+currentPoolSize+" to "+targetPoolSize)
             .tag(BrooklynTaskTags.NON_TRANSIENT_TASK_TAG)
             .body(new Callable<Void>() {
                 @Override
                 public Void call() throws Exception {
                     // TODO Should we use int throughout, rather than casting here?
-                    getResizeOperator().resize(poolEntity, (int) desiredPoolSize);
+                    try {
+                        getResizeOperator().resize(poolEntity, (int) targetPoolSize);
+                    } catch (Resizable.InsufficientCapacityException e) {
+                        // cannot resize beyond this; set the high-water mark
+                        int insufficientCapacityHighWaterMark = getCurrentSizeOperator().apply(poolEntity);
+                        LOG.warn("{} failed to resize {} due to insufficient capacity; setting high-water mark to {}, "
+                                + "and will not attempt to resize above that level again", 
+                                new Object[] {AutoScalerPolicy.this, poolEntity, insufficientCapacityHighWaterMark});
+                        config().set(INSUFFICIENT_CAPACITY_HIGH_WATER_MARK, insufficientCapacityHighWaterMark);
+                    }
                     return null;
                 }
             }).build())
diff --git a/brooklyn-server/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicyMetricTest.java b/brooklyn-server/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicyMetricTest.java
index e04f714..ad67b75 100644
--- a/brooklyn-server/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicyMetricTest.java
+++ b/brooklyn-server/policy/src/test/java/org/apache/brooklyn/policy/autoscaling/AutoScalerPolicyMetricTest.java
@@ -36,17 +36,18 @@
 import org.apache.brooklyn.core.test.entity.TestCluster;
 import org.apache.brooklyn.core.test.entity.TestEntity;
 import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.time.Duration;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 
 public class AutoScalerPolicyMetricTest {
     
-    private static long TIMEOUT_MS = 10000;
-    private static long SHORT_WAIT_MS = 50;
+    private static long SHORT_WAIT_MS = 250;
     
     private static final AttributeSensor<Integer> MY_ATTRIBUTE = Sensors.newIntegerSensor("autoscaler.test.intAttrib");
     TestApplication app;
@@ -75,7 +76,7 @@
         Asserts.succeedsContinually(ImmutableMap.of("timeout", SHORT_WAIT_MS), currentSizeAsserter(tc, 1));
 
         tc.sensors().set(MY_ATTRIBUTE, 101);
-        Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 2));
+        Asserts.succeedsEventually(currentSizeAsserter(tc, 2));
     }
     
     @Test
@@ -89,7 +90,7 @@
         Asserts.succeedsContinually(ImmutableMap.of("timeout", SHORT_WAIT_MS), currentSizeAsserter(tc, 2));
 
         tc.sensors().set(MY_ATTRIBUTE, 49);
-        Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 1));
+        Asserts.succeedsEventually(currentSizeAsserter(tc, 1));
     }
     
     @Test(groups="Integration")
@@ -101,11 +102,11 @@
         
         // workload 200 so requires doubling size to 10 to handle: (200*5)/100 = 10
         tc.sensors().set(MY_ATTRIBUTE, 200);
-        Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 10));
+        Asserts.succeedsEventually(currentSizeAsserter(tc, 10));
         
         // workload 5, requires 1 entity: (10*110)/100 = 11
         tc.sensors().set(MY_ATTRIBUTE, 110);
-        Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 11));
+        Asserts.succeedsEventually(currentSizeAsserter(tc, 11));
     }
     
     @Test(groups="Integration")
@@ -117,14 +118,14 @@
         
         // workload can be handled by 4 servers, within its valid range: (49*5)/50 = 4.9
         tc.sensors().set(MY_ATTRIBUTE, 49);
-        Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 4));
+        Asserts.succeedsEventually(currentSizeAsserter(tc, 4));
         
         // workload can be handled by 4 servers, within its valid range: (25*4)/50 = 2
         tc.sensors().set(MY_ATTRIBUTE, 25);
-        Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 2));
+        Asserts.succeedsEventually(currentSizeAsserter(tc, 2));
         
         tc.sensors().set(MY_ATTRIBUTE, 0);
-        Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 1));
+        Asserts.succeedsEventually(currentSizeAsserter(tc, 1));
     }
     
     @Test(groups="Integration")
@@ -139,11 +140,11 @@
 
         // Decreases to min-size only
         tc.sensors().set(MY_ATTRIBUTE, 0);
-        Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 2));
+        Asserts.succeedsEventually(currentSizeAsserter(tc, 2));
         
         // Increases to max-size only
         tc.sensors().set(MY_ATTRIBUTE, 100000);
-        Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 6));
+        Asserts.succeedsEventually(currentSizeAsserter(tc, 6));
     }
     
     @Test(groups="Integration",invocationCount=20)
@@ -167,14 +168,14 @@
 
         // workload can be handled by 6 servers, so no need to notify: 6 <= (100*6)/50
         tc.sensors().set(MY_ATTRIBUTE, 600);
-        Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 6));
+        Asserts.succeedsEventually(currentSizeAsserter(tc, 6));
         assertTrue(maxReachedEvents.isEmpty());
         
         // Increases to above max capacity: would require (100000*6)/100 = 6000
         tc.sensors().set(MY_ATTRIBUTE, 100000);
         
         // Assert our listener gets notified (once)
-        Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
+        Asserts.succeedsEventually(new Runnable() {
             public void run() {
                 assertEquals(maxReachedEvents.size(), 1);
                 assertEquals(maxReachedEvents.get(0).getMaxAllowed(), 6);
@@ -245,7 +246,7 @@
         policy.suspend();
         policy.resume();
         tc.sensors().set(MY_ATTRIBUTE, 101);
-        Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 2));
+        Asserts.succeedsEventually(currentSizeAsserter(tc, 2));
     }
     
     @Test
@@ -268,6 +269,84 @@
 
         // Then confirm we listen to the correct "entityWithMetric"
         entityWithMetric.sensors().set(TestEntity.SEQUENCE, 101);
-        Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), currentSizeAsserter(tc, 2));
+        Asserts.succeedsEventually(currentSizeAsserter(tc, 2));
+    }
+    
+    @Test(groups="Integration")
+    public void testOnFailedGrowWillSetHighwaterMarkAndNotResizeAboveThatAgain() {
+        tc = app.createAndManageChild(EntitySpec.create(TestCluster.class)
+                .configure("initialSize", 0)
+                .configure(TestCluster.MAX_SIZE, 2));
+
+        tc.resize(1);
+        
+        tc.policies().add(AutoScalerPolicy.builder()
+                .metric(MY_ATTRIBUTE)
+                .metricLowerBound(50)
+                .metricUpperBound(100)
+                .buildSpec());
+        
+        // workload 200 so requires doubling size to 2 to handle: (200*1)/100 = 2
+        tc.sensors().set(MY_ATTRIBUTE, 200);
+        Asserts.succeedsEventually(currentSizeAsserter(tc, 2));
+        assertEquals(tc.getDesiredSizeHistory(), ImmutableList.of(1, 2), "desired="+tc.getDesiredSizeHistory());
+        assertEquals(tc.getSizeHistory(), ImmutableList.of(0, 1, 2), "sizes="+tc.getSizeHistory());
+        tc.sensors().set(MY_ATTRIBUTE, 100);
+        
+        // workload 110, requires 1 more entity: (2*110)/100 = 2.1
+        // But max size is 2, so resize will fail.
+        tc.sensors().set(MY_ATTRIBUTE, 110);
+        Asserts.succeedsContinually(ImmutableMap.of("timeout", SHORT_WAIT_MS), currentSizeAsserter(tc, 2));
+        assertEquals(tc.getDesiredSizeHistory(), ImmutableList.of(1, 2, 3), "desired="+tc.getDesiredSizeHistory()); // TODO succeeds eventually?
+        assertEquals(tc.getSizeHistory(), ImmutableList.of(0, 1, 2), "sizes="+tc.getSizeHistory());
+        
+        // another attempt to resize will not cause it to ask
+        tc.sensors().set(MY_ATTRIBUTE, 110);
+        Asserts.succeedsContinually(ImmutableMap.of("timeout", SHORT_WAIT_MS), currentSizeAsserter(tc, 2));
+        assertEquals(tc.getDesiredSizeHistory(), ImmutableList.of(1, 2, 3), "desired="+tc.getDesiredSizeHistory());
+        assertEquals(tc.getSizeHistory(), ImmutableList.of(0, 1, 2), "sizes="+tc.getSizeHistory());
+        
+        // but if we shrink down again, then we'll still be able to go up to the previous level.
+        // We'll only try to go as high as the previous high-water mark though.
+        tc.sensors().set(MY_ATTRIBUTE, 1);
+        Asserts.succeedsEventually(ImmutableMap.of("timeout", SHORT_WAIT_MS), currentSizeAsserter(tc, 1));
+        assertEquals(tc.getDesiredSizeHistory(), ImmutableList.of(1, 2, 3, 1), "desired="+tc.getDesiredSizeHistory());
+        assertEquals(tc.getSizeHistory(), ImmutableList.of(0, 1, 2, 1), "sizes="+tc.getSizeHistory());
+        
+        tc.sensors().set(MY_ATTRIBUTE, 10000);
+        Asserts.succeedsEventually(ImmutableMap.of("timeout", SHORT_WAIT_MS), currentSizeAsserter(tc, 2));
+        assertEquals(tc.getDesiredSizeHistory(), ImmutableList.of(1, 2, 3, 1, 2), "desired="+tc.getDesiredSizeHistory());
+        assertEquals(tc.getSizeHistory(), ImmutableList.of(0, 1, 2, 1, 2), "sizes="+tc.getSizeHistory());
+    }
+    
+    // When there is a resizeUpStabilizationDelay, it remembers all the previously requested sizes (in the recent history)
+    // and then looks at those in the stabilization-delay to determine the sustained desired. This test checks that
+    // we apply the highwater-mark even when the desired size had been recorded prior to the highwater mark being
+    // discovered.
+    @Test(groups="Integration")
+    public void testOnFailedGrowWithStabilizationDelayWillSetHighwaterMarkAndNotResizeAboveThatAgain() throws Exception {
+        tc = app.createAndManageChild(EntitySpec.create(TestCluster.class)
+                .configure("initialSize", 0)
+                .configure(TestCluster.MAX_SIZE, 2));
+
+        tc.resize(1);
+        
+        tc.policies().add(AutoScalerPolicy.builder()
+                .metric(MY_ATTRIBUTE)
+                .metricLowerBound(50)
+                .metricUpperBound(100)
+                .resizeUpStabilizationDelay(Duration.ONE_SECOND)
+                .buildSpec());
+        
+        // workload 200 so requires doubling size to 2 to handle: (200*1)/100 = 2
+        for (int i = 0; i < 10; i++) {
+            tc.sensors().set(MY_ATTRIBUTE, 200 + (i*100));
+            Thread.sleep(100);
+        }
+        
+        Asserts.succeedsEventually(currentSizeAsserter(tc, 2));
+        Asserts.succeedsContinually(currentSizeAsserter(tc, 2));
+        assertEquals(tc.getDesiredSizeHistory(), ImmutableList.of(1, 2, 3), "desired="+tc.getDesiredSizeHistory());
+        assertEquals(tc.getSizeHistory(), ImmutableList.of(0, 1, 2), "sizes="+tc.getSizeHistory());
     }
 }
diff --git a/brooklyn-server/pom.xml b/brooklyn-server/pom.xml
index d5ae3a8..59bf6b2 100644
--- a/brooklyn-server/pom.xml
+++ b/brooklyn-server/pom.xml
@@ -99,7 +99,7 @@
         <fasterxml.jackson.version>2.4.5</fasterxml.jackson.version>  <!-- more recent jackson, but not compatible with old annotations! -->
         <jersey.version>1.19</jersey.version>
         <httpclient.version>4.4.1</httpclient.version>
-        <commons-lang3.version>3.1</commons-lang3.version>
+        <commons-lang3.version>3.3.2</commons-lang3.version>
         <groovy.version>2.3.7</groovy.version> <!-- Version supported by https://github.com/groovy/groovy-eclipse/wiki/Groovy-Eclipse-2.9.1-Release-Notes -->
         <jsr305.version>2.0.1</jsr305.version>
         <snakeyaml.version>1.11</snakeyaml.version>
diff --git a/brooklyn-server/rest/rest-client/src/main/java/org/apache/brooklyn/rest/client/util/http/BuiltResponsePreservingError.java b/brooklyn-server/rest/rest-client/src/main/java/org/apache/brooklyn/rest/client/util/http/BuiltResponsePreservingError.java
index fb43c4c..d011172 100644
--- a/brooklyn-server/rest/rest-client/src/main/java/org/apache/brooklyn/rest/client/util/http/BuiltResponsePreservingError.java
+++ b/brooklyn-server/rest/rest-client/src/main/java/org/apache/brooklyn/rest/client/util/http/BuiltResponsePreservingError.java
@@ -70,7 +70,9 @@
     
     @Override
     public Object getEntity() {
-        if (error!=null) Exceptions.propagate(error);
+        if (error!=null) {
+            throw new IllegalStateException("getEntity called on BuiltResponsePreservingError, where an Error had been preserved", error);
+        }
         return super.getEntity();
     }
 
diff --git a/brooklyn-server/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java b/brooklyn-server/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java
index 01bf992..82bac22 100644
--- a/brooklyn-server/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java
+++ b/brooklyn-server/rest/rest-server/src/main/java/org/apache/brooklyn/rest/resources/CatalogResource.java
@@ -124,7 +124,12 @@
         Map<String,Object> result = MutableMap.of();
         
         for (CatalogItem<?,?> item: items) {
-            result.put(item.getId(), CatalogTransformer.catalogItemSummary(brooklyn(), item));
+            try {
+                result.put(item.getId(), CatalogTransformer.catalogItemSummary(brooklyn(), item));
+            } catch (Throwable t) {
+                log.warn("Error loading catalog item '"+item+"' (rethrowing): "+t);
+                throw Exceptions.propagate(t);
+            }
         }
         return Response.status(Status.CREATED).entity(result).build();
     }
diff --git a/brooklyn-server/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/DefaultExceptionMapper.java b/brooklyn-server/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/DefaultExceptionMapper.java
index 38a38eb..978755b 100644
--- a/brooklyn-server/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/DefaultExceptionMapper.java
+++ b/brooklyn-server/rest/rest-server/src/main/java/org/apache/brooklyn/rest/util/DefaultExceptionMapper.java
@@ -93,9 +93,12 @@
             }
         }
         
-        Builder rb = ApiError.builderFromThrowable(Exceptions.collapse(throwable2));
+        Throwable throwable3 = Exceptions.collapse(throwable2);
+        Builder rb = ApiError.builderFromThrowable(throwable3);
         if (Strings.isBlank(rb.getMessage()))
             rb.message("Internal error. Contact server administrator to consult logs for more details.");
+        if (Exceptions.isPrefixImportant(throwable3))
+            rb.message(Exceptions.getPrefixText(throwable3)+": "+rb.getMessage());
         return rb.build().asResponse(Status.INTERNAL_SERVER_ERROR, MediaType.APPLICATION_JSON_TYPE);
     }
 }
diff --git a/brooklyn-server/server-cli/src/main/resources/brooklyn/default.catalog.bom b/brooklyn-server/server-cli/src/main/resources/brooklyn/default.catalog.bom
index 483f04d..97dc8a7 100644
--- a/brooklyn-server/server-cli/src/main/resources/brooklyn/default.catalog.bom
+++ b/brooklyn-server/server-cli/src/main/resources/brooklyn/default.catalog.bom
@@ -181,6 +181,8 @@
         brooklyn.config:
           my.message:   $brooklyn:formatString("connected to Riak at %s",
                             $brooklyn:entity("riak-cluster").attributeWhenReady("main.uri"))
+        # and clear the location defined there so it is taken from this template
+        locations:      []
                             
       # use the off-the-shelf Riak cluster
       - type:           org.apache.brooklyn.entity.nosql.riak.RiakCluster
@@ -240,6 +242,8 @@
               # and a lot of sensors defined
               type:           2-bash-web-server-template
               name:           My Bash Web Server VM with Sensors
+              # and clear the location defined there so it is taken from this template
+              locations:      []
               
               brooklyn.config:
                 my.message:   "part of the cluster"
@@ -332,6 +336,8 @@
           # point this load balancer at the cluster, specifying port to forward to
           loadbalancer.serverpool:  $brooklyn:entity("my-web-cluster")
           member.sensor.portNumber: app.port
+          # disable sticky sessions to allow easy validation of balancing via browser refresh
+          nginx.sticky: false
       
       brooklyn.enrichers:
       # publish a few useful info sensors and KPI's to the root of the app
diff --git a/brooklyn-server/software/base/pom.xml b/brooklyn-server/software/base/pom.xml
index 556e982..51ae966 100644
--- a/brooklyn-server/software/base/pom.xml
+++ b/brooklyn-server/software/base/pom.xml
@@ -163,6 +163,10 @@
                 <configuration>
                     <instructions>
                         <Include-Resource>{maven-resources}, target/classes/brooklyn-jmxmp-agent-shaded-${project.version}.jar, target/classes/brooklyn-jmxrmi-agent-${project.version}.jar</Include-Resource>
+                        <Import-Package>
+                            !org.python.core,
+                            *
+                        </Import-Package>
                     </instructions>
                 </configuration>
             </plugin>
diff --git a/brooklyn-server/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/autoscaling/AutoScalerPolicyNoMoreMachinesTest.java b/brooklyn-server/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/autoscaling/AutoScalerPolicyNoMoreMachinesTest.java
new file mode 100644
index 0000000..77175d2
--- /dev/null
+++ b/brooklyn-server/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/autoscaling/AutoScalerPolicyNoMoreMachinesTest.java
@@ -0,0 +1,211 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.software.base.test.autoscaling;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.policy.PolicySpec;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
+import org.apache.brooklyn.core.entity.trait.Resizable;
+import org.apache.brooklyn.core.mgmt.internal.CollectionChangeListener;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.core.test.entity.TestCluster;
+import org.apache.brooklyn.entity.group.DynamicCluster;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess;
+import org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.time.Duration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
+
+public class AutoScalerPolicyNoMoreMachinesTest extends BrooklynAppUnitTestSupport {
+
+    @SuppressWarnings("unused")
+    private static final Logger log = LoggerFactory.getLogger(AutoScalerPolicyNoMoreMachinesTest.class);
+    
+    private static long SHORT_WAIT_MS = 250;
+    
+    DynamicCluster cluster;
+    Location loc;
+    AutoScalerPolicy policy;
+    Set<Entity> entitiesAdded;
+    Set<Entity> entitiesRemoved;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        super.setUp();
+        cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+                .configure(TestCluster.INITIAL_SIZE, 0)
+                .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(EmptySoftwareProcess.class)
+                        .configure(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, true)));
+        loc = mgmt.getLocationRegistry().resolve("byon(hosts='1.1.1.1,1.1.1.2')");
+        app.start(ImmutableList.of(loc));
+        
+        entitiesAdded = Sets.newLinkedHashSet();
+        entitiesRemoved = Sets.newLinkedHashSet();
+        mgmt.addEntitySetListener(new CollectionChangeListener<Entity>() {
+            @Override public void onItemAdded(Entity item) {
+                entitiesAdded.add(item);
+            }
+            @Override public void onItemRemoved(Entity item) {
+                entitiesRemoved.add(item);
+            }});
+    }
+
+    @Test
+    public void testResizeDirectly() throws Exception {
+        assertSize(0);
+
+        cluster.resize(2);
+        assertSize(2);
+        
+        // Won't get a location to successfully resize (byon location only has 2 machines); 
+        // so still left with 2 members (failed node not quarantined, because exception well understood)
+        try {
+            cluster.resize(3);
+            Asserts.shouldHaveFailedPreviously();
+        } catch (Exception e) {
+            Asserts.expectedFailureOfType(e, Resizable.InsufficientCapacityException.class);
+        }
+        assertSize(2, 0, 1);
+
+        // Resize down; will delete one of our nodes
+        cluster.resize(1);
+        assertSize(1, 0, 2);
+        
+        // Resize back up to 2 should be allowed
+        cluster.resize(2);
+        assertSize(2, 0, 2);
+    }
+    
+    @Test
+    public void testPoolHotSensorResizingBeyondMaxMachines() throws Exception {
+        cluster.resize(1);
+        policy = cluster.policies().add(PolicySpec.create(AutoScalerPolicy.class)
+                .configure(AutoScalerPolicy.MIN_PERIOD_BETWEEN_EXECS, Duration.millis(10)));
+
+        // Single node trying to handle a load of 21; too high, so will add one more node
+        cluster.sensors().emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(21L, 10L, 20L));
+        assertSizeEventually(2);
+
+        // Two nodes handing an aggregated load of 41; too high for 2 nodes so tries to scale to 3.
+        // But byon location only has 2 nodes so will fail.
+        cluster.sensors().emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(21L, 10L, 20L));
+        assertSizeEventually(2, 0, 1);
+
+        // Should not repeatedly retry
+        assertSizeContinually(2, 0, 1);
+        
+        // If there is another indication of too much load, should not retry yet again.
+        cluster.sensors().emit(AutoScalerPolicy.DEFAULT_POOL_HOT_SENSOR, message(42L, 10L, 20L));
+        assertSizeContinually(2, 0, 1);
+    }
+    
+    @Test
+    public void testMetricResizingBeyondMaxMachines() throws Exception {
+        AttributeSensor<Integer> metric = Sensors.newIntegerSensor("test.aggregatedLoad");
+        
+        cluster.resize(1);
+        policy = cluster.policies().add(PolicySpec.create(AutoScalerPolicy.class)
+                .configure(AutoScalerPolicy.METRIC, metric)
+                .configure(AutoScalerPolicy.METRIC_LOWER_BOUND, 10)
+                .configure(AutoScalerPolicy.METRIC_UPPER_BOUND, 20)
+                .configure(AutoScalerPolicy.MIN_PERIOD_BETWEEN_EXECS, Duration.millis(10)));
+
+        // Single node trying to handle a load of 21; too high, so will add one more node.
+        // That takes the load back to within acceptable limits
+        cluster.sensors().set(metric, 21);
+        assertSizeEventually(2);
+        cluster.sensors().set(metric, 19);
+
+        // With two nodes, load is now too high, so will try (and fail) to add one more node.
+        // Trigger another attempt to resize.
+        // Any nodes that fail with NoMachinesAvailableException will be immediately deleted.
+        cluster.sensors().set(metric, 22);
+        assertSizeEventually(2, 0, 1);
+        assertSizeContinually(2, 0, 1);
+        
+        // Metric is re-published; should not keep retrying
+        cluster.sensors().set(metric, 21);
+        assertSizeContinually(2, 0, 1);
+    }
+
+    protected Map<String, Object> message(double currentWorkrate, double lowThreshold, double highThreshold) {
+        return message(cluster.getCurrentSize(), currentWorkrate, lowThreshold, highThreshold);
+    }
+    
+    protected Map<String, Object> message(int currentSize, double currentWorkrate, double lowThreshold, double highThreshold) {
+        return ImmutableMap.<String,Object>of(
+            AutoScalerPolicy.POOL_CURRENT_SIZE_KEY, currentSize,
+            AutoScalerPolicy.POOL_CURRENT_WORKRATE_KEY, currentWorkrate,
+            AutoScalerPolicy.POOL_LOW_THRESHOLD_KEY, lowThreshold,
+            AutoScalerPolicy.POOL_HIGH_THRESHOLD_KEY, highThreshold);
+    }
+    
+    protected void assertSize(Integer targetSize) {
+        assertSize(targetSize, 0);
+    }
+
+    protected void assertSize(int targetSize, int quarantineSize, final int deletedSize) {
+        assertSize(targetSize, quarantineSize);
+        assertEquals(entitiesRemoved.size(), deletedSize, "removed="+entitiesRemoved);
+    }
+    
+    protected void assertSize(int targetSize, int quarantineSize) {
+        assertEquals(cluster.getCurrentSize(), (Integer) targetSize, "cluster.currentSize");
+        assertEquals(cluster.getMembers().size(), targetSize, "cluster.members.size");
+        assertEquals(cluster.sensors().get(DynamicCluster.QUARANTINE_GROUP).getMembers().size(), quarantineSize, "cluster.quarantine.size");
+        assertEquals(mgmt.getEntityManager().findEntities(Predicates.instanceOf(EmptySoftwareProcess.class)).size(), targetSize + quarantineSize, "instanceCount(EmptySoftwareProcess)");
+    }
+    
+    protected void assertSizeEventually(int targetSize) {
+        assertSizeEventually(targetSize, 0, 0);
+    }
+    
+    protected void assertSizeEventually(final int targetSize, final int quarantineSize, final int deletedSize) {
+        Asserts.succeedsEventually(new Runnable() {
+            public void run() {
+                assertSize(targetSize, quarantineSize);
+                assertEquals(entitiesRemoved.size(), deletedSize, "removed="+entitiesRemoved);
+            }});
+    }
+    
+    protected void assertSizeContinually(final int targetSize, final int quarantineSize, final int deletedSize) {
+        Asserts.succeedsContinually(ImmutableMap.of("timeout", SHORT_WAIT_MS), new Runnable() {
+            public void run() {
+                assertSize(targetSize, quarantineSize);
+                assertEquals(entitiesRemoved.size(), deletedSize, "removed="+entitiesRemoved);
+            }});
+    }
+}
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/BaseTest.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/BaseTest.java
index a97b9db..f5eb30b 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/BaseTest.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/BaseTest.java
@@ -18,34 +18,20 @@
  */
 package org.apache.brooklyn.test.framework;
 
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
 import com.google.common.collect.ImmutableList;
-import com.google.common.reflect.TypeToken;
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.mgmt.ExecutionContext;
+
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.entity.trait.Startable;
 import org.apache.brooklyn.util.time.Duration;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
 /**
  * A base interface for all tests.
  */
-public interface BaseTest extends Entity, Startable {
-
-    /**
-     * The target entity to test (optional, use either this or targetId).
-     */
-    ConfigKey<Entity> TARGET_ENTITY = ConfigKeys.newConfigKey(Entity.class, "target", "Entity under test");
-
-    /**
-     * Id of the target entity to test (optional, use either this or target).
-     */
-    ConfigKey<String> TARGET_ID = ConfigKeys.newStringConfigKey("targetId", "Id of the entity under test");
+public interface BaseTest extends TargetableTestComponent, Startable {
 
     /**
      * The assertions to be made.
@@ -59,12 +45,5 @@
     ConfigKey<Duration> TIMEOUT = ConfigKeys.newConfigKey(Duration.class, "timeout", "Time to wait on result",
         new Duration(1L, TimeUnit.SECONDS));
 
-    /**
-     * Get the target of the test.
-     *
-     * @return The target.
-     * @throws IllegalArgumentException if the target cannot be found.
-     */
-    Entity resolveTarget();
 
 }
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/InfrastructureDeploymentTestCase.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/InfrastructureDeploymentTestCase.java
index 5f368df..a3ffa53 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/InfrastructureDeploymentTestCase.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/InfrastructureDeploymentTestCase.java
@@ -18,7 +18,10 @@
  */
 package org.apache.brooklyn.test.framework;
 
+import java.util.List;
+
 import com.google.common.reflect.TypeToken;
+
 import org.apache.brooklyn.api.entity.EntitySpec;
 import org.apache.brooklyn.api.entity.ImplementedBy;
 import org.apache.brooklyn.config.ConfigKey;
@@ -30,19 +33,19 @@
  * Created by graememiller on 04/12/2015.
  */
 @ImplementedBy(value = InfrastructureDeploymentTestCaseImpl.class)
-public interface InfrastructureDeploymentTestCase extends TestCase {
+public interface InfrastructureDeploymentTestCase extends TargetableTestComponent {
 
     /**
-     * Entity spec to deploy. This will be deployed second, after the INFRASTRUCTURE_SPEC has been deployed. This will be deployed to the DEPLOYMENT_LOCATION.
+     * Entity specs to deploy. These will be deployed second, after the INFRASTRUCTURE_SPEC has been deployed. These specs will be deployed to the DEPLOYMENT_LOCATION.
      * All children will be deployed after this
      */
-    ConfigKey<EntitySpec<SoftwareProcess>> ENTITY_SPEC_TO_DEPLOY = ConfigKeys.newConfigKey(new TypeToken<EntitySpec<SoftwareProcess>>(){}, "infrastructure.deployment.entity.spec", "Entity spec to deploy to infrastructure");
+    ConfigKey<List<EntitySpec<? extends SoftwareProcess>>> ENTITY_SPEC_TO_DEPLOY = ConfigKeys.newConfigKey(new TypeToken<List<EntitySpec<? extends SoftwareProcess>>>(){}, "infrastructure.deployment.entity.specs", "Entity specs to deploy to infrastructure");
 
 
     /**
      * Infrastructure to deploy. This will be deployed first, then the ENTITY_SPEC_TO_DEPLOY will be deployed, then any children
      */
-    ConfigKey<EntitySpec<StartableApplication>> INFRASTRUCTURE_SPEC = ConfigKeys.newConfigKey(new TypeToken<EntitySpec<StartableApplication>>(){}, "infrastructure.deployment.spec", "Infrastructure to deploy");
+    ConfigKey<EntitySpec<? extends StartableApplication>> INFRASTRUCTURE_SPEC = ConfigKeys.newConfigKey(new TypeToken<EntitySpec<? extends StartableApplication>>(){}, "infrastructure.deployment.spec", "Infrastructure to deploy");
 
 
     /**
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/InfrastructureDeploymentTestCaseImpl.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/InfrastructureDeploymentTestCaseImpl.java
index 900c0a0..55f3e63 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/InfrastructureDeploymentTestCaseImpl.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/InfrastructureDeploymentTestCaseImpl.java
@@ -18,16 +18,20 @@
  */
 package org.apache.brooklyn.test.framework;
 
+import java.util.Collection;
+import java.util.List;
+
 import com.google.common.collect.ImmutableList;
+
 import org.apache.brooklyn.api.entity.EntitySpec;
 import org.apache.brooklyn.api.location.Location;
 import org.apache.brooklyn.core.annotation.EffectorParam;
+import org.apache.brooklyn.core.entity.Attributes;
 import org.apache.brooklyn.core.entity.StartableApplication;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
 import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.entity.software.base.SoftwareProcess;
 
-import java.util.Collection;
-
 /**
  * Created by graememiller on 04/12/2015.
  */
@@ -35,23 +39,55 @@
 
     @Override
     public void start(@EffectorParam(name = "locations") Collection<? extends Location> locations) {
+        setServiceState(false, Lifecycle.STARTING);
+
         //Create the infrastructure
-        EntitySpec<StartableApplication> infrastructureSpec = config().get(INFRASTRUCTURE_SPEC);
+        EntitySpec<? extends StartableApplication> infrastructureSpec = config().get(INFRASTRUCTURE_SPEC);
+        if (infrastructureSpec == null) {
+            setServiceState(false, Lifecycle.ON_FIRE);
+            throw new IllegalArgumentException(INFRASTRUCTURE_SPEC + " not configured");
+        }
+
         StartableApplication infrastructure = this.addChild(infrastructureSpec);
         infrastructure.start(locations);
 
         //Get the location
         String deploymentLocationSensorName = config().get(DEPLOYMENT_LOCATION_SENSOR_NAME);
-        Location locationToDeployTo = infrastructure.sensors().get(Sensors.newSensor(Location.class, deploymentLocationSensorName));
+        if (deploymentLocationSensorName == null) {
+            setServiceState(false, Lifecycle.ON_FIRE);
+            throw new IllegalArgumentException(DEPLOYMENT_LOCATION_SENSOR_NAME + " not configured");
+        }
 
+        Location locationToDeployTo = infrastructure.sensors().get(Sensors.newSensor(Location.class, deploymentLocationSensorName));
+        if (locationToDeployTo == null) {
+            setServiceState(false, Lifecycle.ON_FIRE);
+            throw new IllegalArgumentException("Infrastructure does not have a location configured on sensor "+deploymentLocationSensorName);
+        }
 
         //Start the child entity
-        EntitySpec<SoftwareProcess> entityToDeploySpec = config().get(ENTITY_SPEC_TO_DEPLOY);
-        SoftwareProcess entityToDeploy = this.addChild(entityToDeploySpec);
-        entityToDeploy.start(ImmutableList.of(locationToDeployTo));
-
+        List<EntitySpec<? extends SoftwareProcess>> entitySpecsToDeploy = config().get(ENTITY_SPEC_TO_DEPLOY);
+        if (entitySpecsToDeploy == null || entitySpecsToDeploy.isEmpty()) {
+            setServiceState(false, Lifecycle.ON_FIRE);
+            throw new IllegalArgumentException(ENTITY_SPEC_TO_DEPLOY + " not configured");
+        }
+        for (EntitySpec<? extends SoftwareProcess> softwareProcessEntitySpec : entitySpecsToDeploy) {
+            SoftwareProcess entityToDeploy = this.addChild(softwareProcessEntitySpec);
+            entityToDeploy.start(ImmutableList.of(locationToDeployTo));
+        }
 
         //Defer to super class to start children
         super.start(locations);
+        setServiceState(true, Lifecycle.RUNNING);
+    }
+
+    /**
+     * Sets the state of the Entity. Useful so that the GUI shows the correct icon.
+     *
+     * @param serviceUpState     Whether or not the entity is up.
+     * @param serviceStateActual The actual state of the entity.
+     */
+    private void setServiceState(final boolean serviceUpState, final Lifecycle serviceStateActual) {
+        sensors().set(Attributes.SERVICE_UP, serviceUpState);
+        sensors().set(Attributes.SERVICE_STATE_ACTUAL, serviceStateActual);
     }
 }
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/LoopOverGroupMembersTestCase.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/LoopOverGroupMembersTestCase.java
new file mode 100644
index 0000000..4e76604
--- /dev/null
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/LoopOverGroupMembersTestCase.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.test.framework;
+
+import com.google.common.reflect.TypeToken;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.entity.trait.Startable;
+import org.apache.brooklyn.util.core.flags.SetFromFlag;
+
+/**
+ * Created by graememiller on 11/12/2015.
+ */
+@ImplementedBy(value = LoopOverGroupMembersTestCaseImpl.class)
+public interface LoopOverGroupMembersTestCase extends TargetableTestComponent {
+
+    /**
+     * The test spec that will be run against each member of the group
+     */
+    @SetFromFlag("testSpec")
+    ConfigKey<EntitySpec<? extends TargetableTestComponent>> TEST_SPEC = ConfigKeys.newConfigKey(
+            new TypeToken<EntitySpec<? extends TargetableTestComponent>>(){},
+            "test.spec",
+            "Test spec. The test case will create one of these per child");
+
+}
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/LoopOverGroupMembersTestCaseImpl.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/LoopOverGroupMembersTestCaseImpl.java
new file mode 100644
index 0000000..f4bb06d
--- /dev/null
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/LoopOverGroupMembersTestCaseImpl.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.test.framework;
+
+import java.util.Collection;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Lists;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.entity.Group;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.annotation.EffectorParam;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.entity.trait.Startable;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+
+/**
+ * Created by graememiller on 11/12/2015.
+ */
+public class LoopOverGroupMembersTestCaseImpl extends TargetableTestComponentImpl implements LoopOverGroupMembersTestCase {
+
+    private static final Logger logger = LoggerFactory.getLogger(LoopOverGroupMembersTestCaseImpl.class);
+
+    @Override
+    public void start(@EffectorParam(name = "locations") Collection<? extends Location> locations) {
+        // Let everyone know we're starting up (so that the GUI shows the correct icon).
+        sensors().set(Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STARTING);
+
+        Entity target = resolveTarget();
+        if (target == null) {
+            logger.debug("Tasks NOT successfully run. LoopOverGroupMembersTestCaseImpl group not set");
+            setServiceState(false, Lifecycle.ON_FIRE);
+            return;
+        }
+
+        if (!(target instanceof Group)) {
+            logger.debug("Tasks NOT successfully run. LoopOverGroupMembersTestCaseImpl target is not a group");
+            setServiceState(false, Lifecycle.ON_FIRE);
+            return;
+        }
+
+        EntitySpec<? extends TargetableTestComponent> testSpec = config().get(TEST_SPEC);
+        if (testSpec == null) {
+            logger.debug("Tasks NOT successfully run. LoopOverGroupMembersTestCaseImpl test spec not set");
+            setServiceState(false, Lifecycle.ON_FIRE);
+            return;
+        }
+
+        Group group = (Group) target;
+
+        Collection<Entity> children = group.getMembers();
+        boolean allSuccesful = true;
+        for (Entity child : children) {
+            testSpec.configure(TestCase.TARGET_ENTITY, child);
+
+            try {
+                TargetableTestComponent targetableTestComponent = this.addChild(testSpec);
+                targetableTestComponent.start(locations);
+            } catch (Throwable t) {
+                allSuccesful = false;
+            }
+        }
+
+        if (allSuccesful) {
+            // Let everyone know we've started up successfully (changes the icon in the GUI).
+            logger.debug("Tasks successfully run. Update state of {} to RUNNING.", this);
+            setServiceState(true, Lifecycle.RUNNING);
+        } else {
+            // Let everyone know we've npt started up successfully (changes the icon in the GUI).
+            logger.debug("Tasks NOT successfully run. Update state of {} to ON_FIRE.", this);
+            setServiceState(false, Lifecycle.ON_FIRE);
+        }
+
+    }
+
+    @Override
+    public void stop() {
+        // Let everyone know we're stopping (so that the GUI shows the correct icon).
+        sensors().set(Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPING);
+
+        try {
+            for (Entity child : this.getChildren()) {
+                if (child instanceof Startable) ((Startable) child).stop();
+            }
+
+            // Let everyone know we've stopped successfully (changes the icon in the GUI).
+            logger.debug("Tasks successfully run. Update state of {} to STOPPED.", this);
+            setServiceState(false, Lifecycle.STOPPED);
+        } catch (Throwable t) {
+            logger.debug("Tasks NOT successfully run. Update state of {} to ON_FIRE.", this);
+            setServiceState(false, Lifecycle.ON_FIRE);
+            throw Exceptions.propagate(t);
+        }
+    }
+
+    @Override
+    public void restart() {
+        final Collection<Location> locations = Lists.newArrayList(getLocations());
+        stop();
+        start(locations);
+    }
+
+    /**
+     * Sets the state of the Entity. Useful so that the GUI shows the correct icon.
+     *
+     * @param serviceUpState     Whether or not the entity is up.
+     * @param serviceStateActual The actual state of the entity.
+     */
+    private void setServiceState(final boolean serviceUpState, final Lifecycle serviceStateActual) {
+        sensors().set(SERVICE_UP, serviceUpState);
+        sensors().set(Attributes.SERVICE_STATE_ACTUAL, serviceStateActual);
+    }
+}
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/ParallelTestCase.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/ParallelTestCase.java
index 63fe60f..96f54b8 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/ParallelTestCase.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/ParallelTestCase.java
@@ -18,9 +18,7 @@
  */
 package org.apache.brooklyn.test.framework;
 
-import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.entity.ImplementedBy;
-import org.apache.brooklyn.core.entity.trait.Startable;
 
 /**
  * This implementation will start all child entities in parallel.
@@ -28,5 +26,5 @@
  * @author Chris Burke
  */
 @ImplementedBy(value = ParallelTestCaseImpl.class)
-public interface ParallelTestCase extends Entity, Startable {
+public interface ParallelTestCase extends TargetableTestComponent {
 }
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/ParallelTestCaseImpl.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/ParallelTestCaseImpl.java
index 2fcd83c..469bc3d 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/ParallelTestCaseImpl.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/ParallelTestCaseImpl.java
@@ -20,23 +20,23 @@
 
 import java.util.Collection;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import org.apache.brooklyn.api.location.Location;
 import org.apache.brooklyn.api.mgmt.TaskAdaptable;
-import org.apache.brooklyn.core.entity.AbstractEntity;
 import org.apache.brooklyn.core.entity.Attributes;
 import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
 import org.apache.brooklyn.core.entity.trait.StartableMethods;
 import org.apache.brooklyn.util.core.task.DynamicTasks;
 import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * This implementation will start all child entities in parallel.
  * 
  * @author Chris Burke
  */
-public class ParallelTestCaseImpl extends AbstractEntity implements ParallelTestCase {
+public class ParallelTestCaseImpl extends TargetableTestComponentImpl implements ParallelTestCase {
 
     private static final Logger logger = LoggerFactory.getLogger(ParallelTestCaseImpl.class);
 
@@ -136,7 +136,7 @@
      * @param serviceStateActual The actual state of the entity.
      */
     private void setServiceState(final boolean serviceUpState, final Lifecycle serviceStateActual) {
-        sensors().set(SERVICE_UP, serviceUpState);
+        sensors().set(Attributes.SERVICE_UP, serviceUpState);
         sensors().set(Attributes.SERVICE_STATE_ACTUAL, serviceStateActual);
     }
 }
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTest.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTest.java
index f9ffda6..abd0aea 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTest.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTest.java
@@ -18,9 +18,13 @@
  */
 package org.apache.brooklyn.test.framework;
 
+import static org.apache.brooklyn.core.config.ConfigKeys.newConfigKey;
+
+import java.util.Map;
+
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.reflect.TypeToken;
+
 import org.apache.brooklyn.api.entity.ImplementedBy;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
@@ -28,12 +32,6 @@
 import org.apache.brooklyn.entity.software.base.SoftwareProcess;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import static org.apache.brooklyn.core.config.ConfigKeys.newConfigKey;
-
 /**
  * Tests using a simple command execution.
  */
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTestImpl.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTestImpl.java
index 6f2ef12..f523893 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTestImpl.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/SimpleShellCommandTestImpl.java
@@ -18,21 +18,41 @@
  */
 package org.apache.brooklyn.test.framework;
 
+import static org.apache.brooklyn.core.entity.lifecycle.Lifecycle.ON_FIRE;
+import static org.apache.brooklyn.core.entity.lifecycle.Lifecycle.RUNNING;
+import static org.apache.brooklyn.core.entity.lifecycle.Lifecycle.STARTING;
+import static org.apache.brooklyn.core.entity.lifecycle.Lifecycle.STOPPED;
+import static org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic.setExpectedState;
+import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.checkAssertions;
+import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.getAssertions;
+import static org.apache.brooklyn.util.text.Strings.isBlank;
+import static org.apache.brooklyn.util.text.Strings.isNonBlank;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Joiner;
 import com.google.common.base.Splitter;
 import com.google.common.base.Suppliers;
 import com.google.common.collect.ImmutableMap;
+
 import org.apache.brooklyn.api.location.Location;
 import org.apache.brooklyn.api.mgmt.TaskFactory;
-import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks;
 import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
 import org.apache.brooklyn.core.location.Machines;
 import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.test.framework.TestFrameworkAssertions.AssertionSupport;
 import org.apache.brooklyn.util.collections.MutableList;
-import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.task.DynamicTasks;
 import org.apache.brooklyn.util.core.task.ssh.SshTasks;
 import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
@@ -40,22 +60,9 @@
 import org.apache.brooklyn.util.text.Identifiers;
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.time.Duration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.*;
-
-import static org.apache.brooklyn.core.entity.lifecycle.Lifecycle.*;
-import static org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic.setExpectedState;
-import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.checkAssertions;
-import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.getAssertions;
-import static org.apache.brooklyn.util.text.Strings.isBlank;
-import static org.apache.brooklyn.util.text.Strings.isNonBlank;
 
 // TODO assertions below should use TestFrameworkAssertions but that class needs to be improved to give better error messages
-public class SimpleShellCommandTestImpl extends AbstractTest implements SimpleShellCommandTest {
+public class SimpleShellCommandTestImpl extends TargetableTestComponentImpl implements SimpleShellCommandTest {
 
     private static final Logger LOG = LoggerFactory.getLogger(SimpleShellCommandTestImpl.class);
     private static final int A_LINE = 80;
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponent.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponent.java
new file mode 100644
index 0000000..8ae44ad
--- /dev/null
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponent.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.test.framework;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.entity.trait.Startable;
+import org.apache.brooklyn.core.sensor.AttributeSensorAndConfigKey;
+
+/**
+ * Entity that can target another entity for the purpouse of testing
+ *
+ * @author m4rkmckenna
+ */
+@ImplementedBy(value = TargetableTestComponentImpl.class)
+public interface TargetableTestComponent extends Entity, Startable {
+
+    /**
+     * The target entity to test (optional, use either this or targetId).
+     */
+    AttributeSensorAndConfigKey<Entity, Entity> TARGET_ENTITY = ConfigKeys.newSensorAndConfigKey(Entity.class, "target", "Entity under test");
+
+    /**
+     * Id of the target entity to test (optional, use either this or target).
+     */
+    AttributeSensorAndConfigKey<String, String> TARGET_ID = ConfigKeys.newStringSensorAndConfigKey("targetId", "Id of the entity under test");
+
+    /**
+     * Get the target of the test.
+     *
+     * @return The target.
+     * @throws IllegalArgumentException if the target cannot be found.
+     */
+    Entity resolveTarget();
+
+}
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/AbstractTest.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponentImpl.java
similarity index 89%
rename from brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/AbstractTest.java
rename to brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponentImpl.java
index 434be8e..5b133bd 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/AbstractTest.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TargetableTestComponentImpl.java
@@ -18,6 +18,11 @@
  */
 package org.apache.brooklyn.test.framework;
 
+import java.util.concurrent.ExecutionException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.mgmt.ExecutionContext;
 import org.apache.brooklyn.api.mgmt.Task;
@@ -25,17 +30,13 @@
 import org.apache.brooklyn.core.entity.AbstractEntity;
 import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.concurrent.ExecutionException;
 
 /**
- * Abstract base class for tests, providing common target lookup.
+ * Class that can resolve the target for a test component
  */
-public abstract class AbstractTest extends AbstractEntity implements BaseTest {
+public abstract class TargetableTestComponentImpl extends AbstractEntity implements TargetableTestComponent {
 
-    private static final Logger LOG = LoggerFactory.getLogger(AbstractTest.class);
+    private static final Logger LOG = LoggerFactory.getLogger(TargetableTestComponentImpl.class);
 
     /**
      * Find the target entity using "target" config key, if entity provided directly in config, or by doing an implicit
@@ -63,6 +64,11 @@
 
     private static Entity getTargetById(ExecutionContext executionContext, Entity entity) {
         String targetId = entity.getConfig(TARGET_ID);
+
+        if(targetId == null){
+            return null;
+        }
+
         final Task<Entity> targetLookup = new DslComponent(targetId).newTask();
         Entity target = null;
         try {
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestCase.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestCase.java
index 34a2e34..b6d6f61 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestCase.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestCase.java
@@ -18,9 +18,7 @@
  */
 package org.apache.brooklyn.test.framework;
 
-import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.entity.ImplementedBy;
-import org.apache.brooklyn.core.entity.trait.Startable;
 
 /**
  * Entity that logically groups other test entities
@@ -28,5 +26,5 @@
  * @author m4rkmckenna
  */
 @ImplementedBy(value = TestCaseImpl.class)
-public interface TestCase extends Entity, Startable {
+public interface TestCase extends TargetableTestComponent {
 }
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestCaseImpl.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestCaseImpl.java
index 1508778..eb3a04e 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestCaseImpl.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestCaseImpl.java
@@ -18,24 +18,25 @@
  */
 package org.apache.brooklyn.test.framework;
 
+import java.util.Collection;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import com.google.common.collect.Lists;
+
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.location.Location;
-import org.apache.brooklyn.core.entity.AbstractEntity;
 import org.apache.brooklyn.core.entity.Attributes;
 import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
 import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
 import org.apache.brooklyn.core.entity.trait.Startable;
 import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Collection;
 
 /**
  * {@inheritDoc}
  */
-public class TestCaseImpl extends AbstractEntity implements TestCase {
+public class TestCaseImpl extends TargetableTestComponentImpl implements TestCase {
 
     private static final Logger LOG = LoggerFactory.getLogger(TestCaseImpl.class);
 
@@ -46,7 +47,7 @@
         ServiceStateLogic.setExpectedState(this, Lifecycle.STARTING);
         try {
             for (final Entity childEntity : getChildren()) {
-                Boolean serviceUp = childEntity.sensors().get(SERVICE_UP);
+                Boolean serviceUp = childEntity.sensors().get(Attributes.SERVICE_UP);
                 if (childEntity instanceof Startable && !Boolean.TRUE.equals(serviceUp)){
                     ((Startable) childEntity).start(locations);
                 }
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEffectorImpl.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEffectorImpl.java
index 817ef6a..c00bef2 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEffectorImpl.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestEffectorImpl.java
@@ -18,7 +18,19 @@
  */
 package org.apache.brooklyn.test.framework;
 
+import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.getAssertions;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
+
 import org.apache.brooklyn.api.effector.Effector;
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.location.Location;
@@ -30,16 +42,11 @@
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.time.Duration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Collection;
-import java.util.Map;
 
 /**
  *
  */
-public class TestEffectorImpl extends AbstractTest implements TestEffector {
+public class TestEffectorImpl extends TargetableTestComponentImpl implements TestEffector {
     private static final Logger LOG = LoggerFactory.getLogger(TestEffectorImpl.class);
 
 
@@ -60,14 +67,27 @@
             if (effector.isAbsentOrNull()) {
                 throw new AssertionError(String.format("No effector with name [%s]", effectorName));
             }
-            final Task<?> effectorResult;
+            final Task<?> effectorTask;
             if (effectorParams == null || effectorParams.isEmpty()) {
-                effectorResult = Entities.invokeEffector(this, targetEntity, effector.get());
+                effectorTask = Entities.invokeEffector(this, targetEntity, effector.get());
             } else {
-                effectorResult = Entities.invokeEffector(this, targetEntity, effector.get(), effectorParams);
+                effectorTask = Entities.invokeEffector(this, targetEntity, effector.get(), effectorParams);
             }
+
+            final Object effectorResult = effectorTask.get(timeout);
+
+            final List<Map<String, Object>> assertions = getAssertions(this, ASSERTIONS);
+            if(assertions != null && !assertions.isEmpty()){
+                TestFrameworkAssertions.checkAssertions(ImmutableMap.of("timeout", timeout), assertions, effectorName, new Supplier<String>() {
+                    @Override
+                    public String get() {
+                        return (String)effectorResult;
+                    }
+                });
+            }
+
             //Add result of effector to sensor
-            sensors().set(EFFECTOR_RESULT, effectorResult.get(timeout));
+            sensors().set(EFFECTOR_RESULT, effectorResult);
             sensors().set(SERVICE_UP, true);
             ServiceStateLogic.setExpectedState(this, Lifecycle.RUNNING);
         } catch (Throwable t) {
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestFrameworkAssertions.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestFrameworkAssertions.java
index 0e61419..3c00020 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestFrameworkAssertions.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestFrameworkAssertions.java
@@ -18,9 +18,15 @@
  */
 package org.apache.brooklyn.test.framework;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
 import com.google.common.base.Joiner;
 import com.google.common.base.Supplier;
 import com.google.common.reflect.TypeToken;
+
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.test.Asserts;
@@ -30,11 +36,6 @@
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.text.Strings;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
 
 /**
  * Utility class to evaluate test-framework assertions
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCallImpl.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCallImpl.java
index 67451ed..e3e4456 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCallImpl.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCallImpl.java
@@ -18,28 +18,31 @@
  */
 package org.apache.brooklyn.test.framework;
 
-import com.google.common.base.Supplier;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
-import org.apache.brooklyn.api.location.Location;
-import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
-import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
-import org.apache.brooklyn.util.exceptions.Exceptions;
-import org.apache.brooklyn.util.http.HttpTool;
-import org.apache.brooklyn.util.time.Duration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.getAssertions;
 
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
-import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.getAssertions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.http.HttpTool;
+import org.apache.brooklyn.util.time.Duration;
 
 /**
  * {@inheritDoc}
  */
-public class TestHttpCallImpl extends AbstractTest implements TestHttpCall {
+public class TestHttpCallImpl extends TargetableTestComponentImpl implements TestHttpCall {
 
     private static final Logger LOG = LoggerFactory.getLogger(TestHttpCallImpl.class);
 
@@ -58,12 +61,12 @@
 
         try {
             doRequestAndCheckAssertions(ImmutableMap.of("timeout", timeout), assertions, target, url);
-            sensors().set(SERVICE_UP, true);
+            sensors().set(Attributes.SERVICE_UP, true);
             ServiceStateLogic.setExpectedState(this, Lifecycle.RUNNING);
 
         } catch (Throwable t) {
             LOG.info("{} Url [{}] test failed", this, url);
-            sensors().set(SERVICE_UP, false);
+            sensors().set(Attributes.SERVICE_UP, false);
             ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE);
             throw Exceptions.propagate(t);
         }
@@ -105,7 +108,7 @@
      */
     public void stop() {
         ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPING);
-        sensors().set(SERVICE_UP, false);
+        sensors().set(Attributes.SERVICE_UP, false);
     }
 
     /**
diff --git a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensorImpl.java b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensorImpl.java
index d042991..633e982 100644
--- a/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensorImpl.java
+++ b/brooklyn-server/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestSensorImpl.java
@@ -18,11 +18,21 @@
  */
 package org.apache.brooklyn.test.framework;
 
+import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.getAssertions;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import com.google.common.base.Objects;
 import com.google.common.base.Predicate;
 import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
+
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.location.Location;
 import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
@@ -31,19 +41,11 @@
 import org.apache.brooklyn.util.core.flags.TypeCoercions;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.time.Duration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.getAssertions;
 
 /**
  * {@inheritDoc}
  */
-public class TestSensorImpl extends AbstractTest implements TestSensor {
+public class TestSensorImpl extends TargetableTestComponentImpl implements TestSensor {
 
     private static final Logger LOG = LoggerFactory.getLogger(TestSensorImpl.class);
 
diff --git a/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/InfrastructureDeploymentTestCaseTest.java b/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/InfrastructureDeploymentTestCaseTest.java
new file mode 100644
index 0000000..fa0e864
--- /dev/null
+++ b/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/InfrastructureDeploymentTestCaseTest.java
@@ -0,0 +1,267 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.brooklyn.test.framework;
+
+import static org.apache.brooklyn.core.entity.trait.Startable.SERVICE_UP;
+import static org.apache.brooklyn.test.Asserts.fail;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.reflect.TypeToken;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.sensor.AttributeSensorAndConfigKey;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess;
+import org.apache.brooklyn.entity.software.base.SoftwareProcess;
+import org.apache.brooklyn.entity.stock.BasicApplication;
+import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
+import org.apache.brooklyn.test.framework.entity.TestInfrastructure;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.text.Identifiers;
+
+/**
+ * @author Graeme Miller on 27/10/2015.
+ */
+public class InfrastructureDeploymentTestCaseTest {
+
+    private TestApplication app;
+    private ManagementContext managementContext;
+    private LocalhostMachineProvisioningLocation loc;
+    private LocalhostMachineProvisioningLocation infrastructureLoc;
+    private String LOC_NAME = "location";
+    private String INFRASTRUCTURE_LOC_NAME = "Infrastructure location";
+
+    private static final AttributeSensorAndConfigKey<Location, Location> DEPLOYMENT_LOCATION_SENSOR =
+            ConfigKeys.newSensorAndConfigKey(
+                    new TypeToken<Location>() {
+                    },
+                    "deploymentLocationSensor", "The location to deploy to");
+
+    @BeforeMethod
+    public void setup() {
+        app = TestApplication.Factory.newManagedInstanceForTests();
+        managementContext = app.getManagementContext();
+
+        loc = managementContext.getLocationManager()
+                .createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class)
+                        .configure("name", LOC_NAME));
+
+        infrastructureLoc = managementContext.getLocationManager()
+                .createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class)
+                        .configure("name", INFRASTRUCTURE_LOC_NAME));
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+
+    @Test
+    public void testVanilla() {
+        EntitySpec<TestInfrastructure> infrastructureSpec = EntitySpec.create(TestInfrastructure.class);
+        infrastructureSpec.configure(DEPLOYMENT_LOCATION_SENSOR, infrastructureLoc);
+
+        List<EntitySpec<? extends SoftwareProcess>> testSpecs = ImmutableList.<EntitySpec<? extends SoftwareProcess>>of(EntitySpec.create(EmptySoftwareProcess.class));
+
+        InfrastructureDeploymentTestCase infrastructureDeploymentTestCase = app.createAndManageChild(EntitySpec.create(InfrastructureDeploymentTestCase.class));
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.INFRASTRUCTURE_SPEC, infrastructureSpec);
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.ENTITY_SPEC_TO_DEPLOY, testSpecs);
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.DEPLOYMENT_LOCATION_SENSOR_NAME, DEPLOYMENT_LOCATION_SENSOR.getName());
+
+        app.start(ImmutableList.of(loc));
+
+        assertThat(infrastructureDeploymentTestCase.sensors().get(SERVICE_UP)).isTrue();
+        assertThat(infrastructureDeploymentTestCase.getChildren().size()).isEqualTo(2);
+
+        boolean seenInfrastructure = false;
+        boolean seenEntity = false;
+
+        for (Entity entity : infrastructureDeploymentTestCase.getChildren()) {
+            if (entity instanceof BasicApplication) {
+                assertThat(entity.getLocations().size()).isEqualTo(1);
+                assertThat(entity.getLocations().iterator().next().getDisplayName()).isEqualTo(LOC_NAME);
+                assertThat(entity.sensors().get(SERVICE_UP)).isTrue();
+
+                seenInfrastructure = true;
+            } else if (entity instanceof EmptySoftwareProcess) {
+                assertThat(entity.getLocations().size()).isEqualTo(1);
+                assertThat(entity.getLocations().iterator().next().getDisplayName()).isEqualTo(INFRASTRUCTURE_LOC_NAME);
+                assertThat(entity.sensors().get(SERVICE_UP)).isTrue();
+
+                seenEntity = true;
+            } else {
+                fail("Unknown child of InfrastructureDeploymentTestCase");
+            }
+        }
+
+        assertThat(seenInfrastructure).isTrue();
+        assertThat(seenEntity).isTrue();
+    }
+
+    @Test
+    public void testMultipleSpec() {
+        EntitySpec<TestInfrastructure> infrastructureSpec = EntitySpec.create(TestInfrastructure.class);
+        infrastructureSpec.configure(DEPLOYMENT_LOCATION_SENSOR, infrastructureLoc);
+
+        List<EntitySpec<? extends SoftwareProcess>> testSpecs = ImmutableList.<EntitySpec<? extends SoftwareProcess>>of
+                (EntitySpec.create(EmptySoftwareProcess.class),
+                        (EntitySpec.create(EmptySoftwareProcess.class)));
+
+        InfrastructureDeploymentTestCase infrastructureDeploymentTestCase = app.createAndManageChild(EntitySpec.create(InfrastructureDeploymentTestCase.class));
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.INFRASTRUCTURE_SPEC, infrastructureSpec);
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.ENTITY_SPEC_TO_DEPLOY, testSpecs);
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.DEPLOYMENT_LOCATION_SENSOR_NAME, DEPLOYMENT_LOCATION_SENSOR.getName());
+
+        app.start(ImmutableList.of(loc));
+
+        assertThat(infrastructureDeploymentTestCase.sensors().get(SERVICE_UP)).isTrue();
+        assertThat(infrastructureDeploymentTestCase.getChildren().size()).isEqualTo(3);
+
+        boolean seenInfrastructure = false;
+        int entitiesSeen = 0;
+
+        for (Entity entity : infrastructureDeploymentTestCase.getChildren()) {
+            if (entity instanceof BasicApplication) {
+                assertThat(entity.getLocations().size()).isEqualTo(1);
+                assertThat(entity.getLocations().iterator().next().getDisplayName()).isEqualTo(LOC_NAME);
+                assertThat(entity.sensors().get(SERVICE_UP)).isTrue();
+
+                seenInfrastructure = true;
+            } else if (entity instanceof EmptySoftwareProcess) {
+                assertThat(entity.getLocations().size()).isEqualTo(1);
+                assertThat(entity.getLocations().iterator().next().getDisplayName()).isEqualTo(INFRASTRUCTURE_LOC_NAME);
+                assertThat(entity.sensors().get(SERVICE_UP)).isTrue();
+
+                entitiesSeen++;
+            } else {
+                fail("Unknown child of InfrastructureDeploymentTestCase");
+            }
+        }
+
+        assertThat(seenInfrastructure).isTrue();
+        assertThat(entitiesSeen).isEqualTo(2);
+    }
+
+    @Test
+    public void testNoInfrastructureSpec() {
+        List<EntitySpec<? extends SoftwareProcess>> testSpecs = ImmutableList.<EntitySpec<? extends SoftwareProcess>>of(EntitySpec.create(EmptySoftwareProcess.class));
+
+        InfrastructureDeploymentTestCase infrastructureDeploymentTestCase = app.createAndManageChild(EntitySpec.create(InfrastructureDeploymentTestCase.class));
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.ENTITY_SPEC_TO_DEPLOY, testSpecs);
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.DEPLOYMENT_LOCATION_SENSOR_NAME, DEPLOYMENT_LOCATION_SENSOR.getName());
+
+        try {
+            app.start(ImmutableList.of(app.newSimulatedLocation()));
+            fail("Should have thrown execption");
+        } catch (Throwable throwable) {
+            Throwable firstInteresting = Exceptions.getFirstInteresting(throwable);
+            assertThat(firstInteresting).isNotNull();
+            assertThat(throwable).isNotNull();
+            assertThat(firstInteresting).isInstanceOf(IllegalArgumentException.class);
+        }
+
+        assertThat(infrastructureDeploymentTestCase.sensors().get(SERVICE_UP)).isFalse();
+    }
+
+    @Test
+    public void testNoEntitySpec() {
+        EntitySpec<TestInfrastructure> infrastructureSpec = EntitySpec.create(TestInfrastructure.class);
+        infrastructureSpec.configure(DEPLOYMENT_LOCATION_SENSOR, infrastructureLoc);
+
+        InfrastructureDeploymentTestCase infrastructureDeploymentTestCase = app.createAndManageChild(EntitySpec.create(InfrastructureDeploymentTestCase.class));
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.INFRASTRUCTURE_SPEC, infrastructureSpec);
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.DEPLOYMENT_LOCATION_SENSOR_NAME, DEPLOYMENT_LOCATION_SENSOR.getName());
+
+        try {
+            app.start(ImmutableList.of(app.newSimulatedLocation()));
+            fail("Should have thrown execption");
+        } catch (Throwable throwable) {
+            Throwable firstInteresting = Exceptions.getFirstInteresting(throwable);
+            assertThat(firstInteresting).isNotNull();
+            assertThat(throwable).isNotNull();
+            assertThat(firstInteresting).isInstanceOf(IllegalArgumentException.class);
+        }
+
+        assertThat(infrastructureDeploymentTestCase.sensors().get(SERVICE_UP)).isFalse();
+    }
+
+    @Test
+    public void testNoDeploymentLocation() {
+        EntitySpec<TestInfrastructure> infrastructureSpec = EntitySpec.create(TestInfrastructure.class);
+        infrastructureSpec.configure(DEPLOYMENT_LOCATION_SENSOR, infrastructureLoc);
+
+        List<EntitySpec<? extends SoftwareProcess>> testSpecs = ImmutableList.<EntitySpec<? extends SoftwareProcess>>of(EntitySpec.create(EmptySoftwareProcess.class));
+
+        InfrastructureDeploymentTestCase infrastructureDeploymentTestCase = app.createAndManageChild(EntitySpec.create(InfrastructureDeploymentTestCase.class));
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.INFRASTRUCTURE_SPEC, infrastructureSpec);
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.ENTITY_SPEC_TO_DEPLOY, testSpecs);
+
+        try {
+            app.start(ImmutableList.of(app.newSimulatedLocation()));
+            fail("Should have thrown execption");
+        } catch (Throwable throwable) {
+            Throwable firstInteresting = Exceptions.getFirstInteresting(throwable);
+            assertThat(firstInteresting).isNotNull();
+            assertThat(throwable).isNotNull();
+            assertThat(firstInteresting).isInstanceOf(IllegalArgumentException.class);
+        }
+
+        assertThat(infrastructureDeploymentTestCase.sensors().get(SERVICE_UP)).isFalse();
+    }
+
+    @Test
+    public void testInfrastrucutreHasNoLocation() {
+        EntitySpec<TestInfrastructure> infrastructureSpec = EntitySpec.create(TestInfrastructure.class);
+
+        List<EntitySpec<? extends SoftwareProcess>> testSpecs = ImmutableList.<EntitySpec<? extends SoftwareProcess>>of(EntitySpec.create(EmptySoftwareProcess.class));
+
+        InfrastructureDeploymentTestCase infrastructureDeploymentTestCase = app.createAndManageChild(EntitySpec.create(InfrastructureDeploymentTestCase.class));
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.INFRASTRUCTURE_SPEC, infrastructureSpec);
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.ENTITY_SPEC_TO_DEPLOY, testSpecs);
+        infrastructureDeploymentTestCase.config().set(InfrastructureDeploymentTestCase.DEPLOYMENT_LOCATION_SENSOR_NAME, DEPLOYMENT_LOCATION_SENSOR.getName());
+
+        try {
+            app.start(ImmutableList.of(app.newSimulatedLocation()));
+            fail("Should have thrown execption");
+        } catch (Throwable throwable) {
+            Throwable firstInteresting = Exceptions.getFirstInteresting(throwable);
+            assertThat(firstInteresting).isNotNull();
+            assertThat(throwable).isNotNull();
+            assertThat(firstInteresting).isInstanceOf(IllegalArgumentException.class);
+        }
+
+        assertThat(infrastructureDeploymentTestCase.sensors().get(SERVICE_UP)).isFalse();
+    }
+}
diff --git a/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/LoopOverGroupMembersTestCaseTest.java b/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/LoopOverGroupMembersTestCaseTest.java
new file mode 100644
index 0000000..39c85c5
--- /dev/null
+++ b/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/LoopOverGroupMembersTestCaseTest.java
@@ -0,0 +1,286 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.brooklyn.test.framework;
+
+import static org.apache.brooklyn.core.entity.trait.Startable.SERVICE_UP;
+import static org.apache.brooklyn.test.Asserts.fail;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.entity.Group;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.sensor.AttributeSensorAndConfigKey;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.entity.group.DynamicGroup;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess;
+import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
+import org.apache.brooklyn.util.collections.MutableSet;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.text.Identifiers;
+
+/**
+ * @author Graeme Miller on 27/10/2015.
+ */
+public class LoopOverGroupMembersTestCaseTest {
+
+    private TestApplication app;
+    private Group testGroup;
+    private ManagementContext managementContext;
+    private LocalhostMachineProvisioningLocation loc;
+    private String testId;
+    private final String SENSOR_VAL = "Hello World!";
+
+    private static final AttributeSensorAndConfigKey<String, String> STRING_SENSOR = ConfigKeys.newSensorAndConfigKey(String.class, "string-sensor", "String Sensor");
+
+    @BeforeMethod
+    public void setup() {
+        testId = Identifiers.makeRandomId(8);
+        app = TestApplication.Factory.newManagedInstanceForTests();
+        managementContext = app.getManagementContext();
+
+        loc = managementContext.getLocationManager()
+                .createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class)
+                        .configure("name", testId));
+
+        testGroup = app.createAndManageChild(EntitySpec.create(DynamicGroup.class));
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+
+    @Test
+    public void testOneChildWhichPasses() {
+        EmptySoftwareProcess emptySoftwareProcess = addEmptySoftwareProcessToGroup();
+        EntitySpec<TestSensor> testSpec = createPassingTestSensorSpec();
+
+        LoopOverGroupMembersTestCase loopOverGroupMembersTestCase = app.createAndManageChild(EntitySpec.create(LoopOverGroupMembersTestCase.class));
+        loopOverGroupMembersTestCase.config().set(LoopOverGroupMembersTestCase.TEST_SPEC, testSpec);
+        loopOverGroupMembersTestCase.config().set(LoopOverGroupMembersTestCase.TARGET_ENTITY, testGroup);
+
+        app.start(ImmutableList.of(app.newSimulatedLocation()));
+
+        assertThat(loopOverGroupMembersTestCase.getChildren().size()).isEqualTo(1);
+        assertThat(loopOverGroupMembersTestCase.sensors().get(SERVICE_UP)).isTrue();
+
+        Entity loopChildEntity = loopOverGroupMembersTestCase.getChildren().iterator().next();
+        assertThat(loopChildEntity).isInstanceOf(TestSensor.class);
+        assertThat(loopChildEntity.sensors().get(SERVICE_UP)).isTrue();
+        assertThat(loopChildEntity.config().get(LoopOverGroupMembersTestCase.TARGET_ENTITY)).isEqualTo(emptySoftwareProcess);
+    }
+
+    @Test
+    public void testMultipleChildrenWhichPass() {
+        Set<EmptySoftwareProcess> emptySoftwareProcesses = addMultipleEmptySoftwareProcessesToGroup(4);
+        EntitySpec<TestSensor> testSpec = createPassingTestSensorSpec();
+
+        LoopOverGroupMembersTestCase loopOverGroupMembersTestCase = app.createAndManageChild(EntitySpec.create(LoopOverGroupMembersTestCase.class));
+        loopOverGroupMembersTestCase.config().set(LoopOverGroupMembersTestCase.TEST_SPEC, testSpec);
+        loopOverGroupMembersTestCase.config().set(LoopOverGroupMembersTestCase.TARGET_ENTITY, testGroup);
+
+        app.start(ImmutableList.of(app.newSimulatedLocation()));
+
+        assertThat(loopOverGroupMembersTestCase.getChildren().size()).isEqualTo(4);
+        assertThat(loopOverGroupMembersTestCase.sensors().get(SERVICE_UP)).isTrue();
+
+        for (Entity loopChildEntity : loopOverGroupMembersTestCase.getChildren()) {
+            assertThat(loopChildEntity).isInstanceOf(TestSensor.class);
+            assertThat(loopChildEntity.sensors().get(SERVICE_UP)).isTrue();
+            assertThat(emptySoftwareProcesses.contains(loopChildEntity.config().get(LoopOverGroupMembersTestCase.TARGET_ENTITY))).isTrue();
+            emptySoftwareProcesses.remove(loopChildEntity.config().get(LoopOverGroupMembersTestCase.TARGET_ENTITY));
+        }
+    }
+
+    @Test
+    public void testMultipleChildrenWhichAllFail() {
+        Set<EmptySoftwareProcess> emptySoftwareProcesses = addMultipleEmptySoftwareProcessesToGroup(4);
+        EntitySpec<TestSensor> testSpec = createFailingTestSensorSpec();
+
+        LoopOverGroupMembersTestCase loopOverGroupMembersTestCase = app.createAndManageChild(EntitySpec.create(LoopOverGroupMembersTestCase.class));
+        loopOverGroupMembersTestCase.config().set(LoopOverGroupMembersTestCase.TEST_SPEC, testSpec);
+        loopOverGroupMembersTestCase.config().set(LoopOverGroupMembersTestCase.TARGET_ENTITY, testGroup);
+
+        app.start(ImmutableList.of(app.newSimulatedLocation()));
+
+        assertThat(loopOverGroupMembersTestCase.getChildren().size()).isEqualTo(4);
+        assertThat(loopOverGroupMembersTestCase.sensors().get(SERVICE_UP)).isFalse();
+
+        for (Entity loopChildEntity : loopOverGroupMembersTestCase.getChildren()) {
+            assertThat(loopChildEntity).isInstanceOf(TestSensor.class);
+            assertThat(loopChildEntity.sensors().get(SERVICE_UP)).isFalse();
+            assertThat(emptySoftwareProcesses.contains(loopChildEntity.config().get(LoopOverGroupMembersTestCase.TARGET_ENTITY))).isTrue();
+            emptySoftwareProcesses.remove(loopChildEntity.config().get(LoopOverGroupMembersTestCase.TARGET_ENTITY));
+        }
+    }
+
+    @Test
+    public void testMultipleChildrenOneOfWhichFails() {
+        Set<EmptySoftwareProcess> emptySoftwareProcesses = addMultipleEmptySoftwareProcessesToGroup(3);
+        EntitySpec<TestSensor> testSpec = createPassingTestSensorSpec();
+
+        EmptySoftwareProcess failingProcess = testGroup.addMemberChild(EntitySpec.create(EmptySoftwareProcess.class));
+        failingProcess.sensors().set(STRING_SENSOR, "THIS STRING WILL CAUSE SENSOR TEST TO FAIL");
+
+        LoopOverGroupMembersTestCase loopOverGroupMembersTestCase = app.createAndManageChild(EntitySpec.create(LoopOverGroupMembersTestCase.class));
+        loopOverGroupMembersTestCase.config().set(LoopOverGroupMembersTestCase.TEST_SPEC, testSpec);
+        loopOverGroupMembersTestCase.config().set(LoopOverGroupMembersTestCase.TARGET_ENTITY, testGroup);
+
+        app.start(ImmutableList.of(app.newSimulatedLocation()));
+
+        assertThat(loopOverGroupMembersTestCase.getChildren().size()).isEqualTo(4);
+        assertThat(loopOverGroupMembersTestCase.sensors().get(SERVICE_UP)).isFalse();
+
+        for (Entity loopChildEntity : loopOverGroupMembersTestCase.getChildren()) {
+            assertThat(loopChildEntity).isInstanceOf(TestSensor.class);
+
+            Entity targetedEntity = loopChildEntity.config().get(LoopOverGroupMembersTestCase.TARGET_ENTITY);
+
+            if (targetedEntity.equals(failingProcess)) {
+                assertThat(loopChildEntity.sensors().get(SERVICE_UP)).isFalse();
+            } else if (emptySoftwareProcesses.contains(targetedEntity)) {
+                assertThat(loopChildEntity.sensors().get(SERVICE_UP)).isTrue();
+                emptySoftwareProcesses.remove(targetedEntity);
+            } else {
+                fail("Targeted entity not recognized");
+            }
+        }
+    }
+
+    @Test
+    public void testOneChildWhichFails() {
+        EmptySoftwareProcess emptySoftwareProcess = addEmptySoftwareProcessToGroup();
+        EntitySpec<TestSensor> testSpec = createFailingTestSensorSpec();
+
+        LoopOverGroupMembersTestCase loopOverGroupMembersTestCase = app.createAndManageChild(EntitySpec.create(LoopOverGroupMembersTestCase.class));
+        loopOverGroupMembersTestCase.config().set(LoopOverGroupMembersTestCase.TEST_SPEC, testSpec);
+        loopOverGroupMembersTestCase.config().set(LoopOverGroupMembersTestCase.TARGET_ENTITY, testGroup);
+
+        app.start(ImmutableList.of(app.newSimulatedLocation()));
+
+        assertThat(loopOverGroupMembersTestCase.getChildren().size()).isEqualTo(1);
+        assertThat(loopOverGroupMembersTestCase.sensors().get(SERVICE_UP)).isFalse();
+
+        Entity loopChildEntity = loopOverGroupMembersTestCase.getChildren().iterator().next();
+        assertThat(loopChildEntity).isInstanceOf(TestSensor.class);
+        assertThat(loopChildEntity.sensors().get(SERVICE_UP)).isFalse();
+        assertThat(loopChildEntity.config().get(LoopOverGroupMembersTestCase.TARGET_ENTITY)).isEqualTo(emptySoftwareProcess);
+    }
+
+    //negative
+    // without test spec
+    // without target + taget id
+    // not a group
+
+    @Test
+    public void testNoTarget() {
+        EmptySoftwareProcess emptySoftwareProcess = addEmptySoftwareProcessToGroup();
+        EntitySpec<TestSensor> testSpec = createFailingTestSensorSpec();
+
+        LoopOverGroupMembersTestCase loopOverGroupMembersTestCase = app.createAndManageChild(EntitySpec.create(LoopOverGroupMembersTestCase.class));
+        loopOverGroupMembersTestCase.config().set(LoopOverGroupMembersTestCase.TEST_SPEC, testSpec);
+
+        app.start(ImmutableList.of(app.newSimulatedLocation()));
+
+        assertThat(loopOverGroupMembersTestCase.getChildren().size()).isEqualTo(0);
+        assertThat(loopOverGroupMembersTestCase.sensors().get(SERVICE_UP)).isFalse();
+    }
+
+    @Test
+    public void testNotTargetingGroup() {
+        EmptySoftwareProcess emptySoftwareProcess = addEmptySoftwareProcessToGroup();
+        EntitySpec<TestSensor> testSpec = createFailingTestSensorSpec();
+
+        LoopOverGroupMembersTestCase loopOverGroupMembersTestCase = app.createAndManageChild(EntitySpec.create(LoopOverGroupMembersTestCase.class));
+        loopOverGroupMembersTestCase.config().set(LoopOverGroupMembersTestCase.TEST_SPEC, testSpec);
+        loopOverGroupMembersTestCase.config().set(LoopOverGroupMembersTestCase.TARGET_ENTITY, app);
+
+        app.start(ImmutableList.of(app.newSimulatedLocation()));
+
+        assertThat(loopOverGroupMembersTestCase.getChildren().size()).isEqualTo(0);
+        assertThat(loopOverGroupMembersTestCase.sensors().get(SERVICE_UP)).isFalse();
+    }
+
+    @Test
+    public void testNoSpec() {
+        EmptySoftwareProcess emptySoftwareProcess = addEmptySoftwareProcessToGroup();
+        EntitySpec<TestSensor> testSpec = createFailingTestSensorSpec();
+
+        LoopOverGroupMembersTestCase loopOverGroupMembersTestCase = app.createAndManageChild(EntitySpec.create(LoopOverGroupMembersTestCase.class));
+        loopOverGroupMembersTestCase.config().set(LoopOverGroupMembersTestCase.TARGET_ENTITY, testGroup);
+
+        app.start(ImmutableList.of(app.newSimulatedLocation()));
+
+        assertThat(loopOverGroupMembersTestCase.getChildren().size()).isEqualTo(0);
+        assertThat(loopOverGroupMembersTestCase.sensors().get(SERVICE_UP)).isFalse();
+    }
+
+    //UTILITY METHODS
+    private EntitySpec<TestSensor> createFailingTestSensorSpec() {
+        List<Map<String, Object>> assertions = ImmutableList.<Map<String, Object>>of(
+                ImmutableMap.<String, Object>of(TestFrameworkAssertions.EQUAL_TO, "THIS IS THE WRONG STRING")
+        );
+
+        return EntitySpec.create(TestSensor.class)
+                .configure(TestSensor.SENSOR_NAME, STRING_SENSOR.getName())
+                .configure(TestSensor.ASSERTIONS, assertions);
+    }
+
+    private EntitySpec<TestSensor> createPassingTestSensorSpec() {
+        List<Map<String, Object>> assertions = ImmutableList.<Map<String, Object>>of(
+                ImmutableMap.<String, Object>of(TestFrameworkAssertions.EQUAL_TO, SENSOR_VAL)
+        );
+
+        return EntitySpec.create(TestSensor.class)
+                .configure(TestSensor.SENSOR_NAME, STRING_SENSOR.getName())
+                .configure(TestSensor.ASSERTIONS, assertions);
+    }
+
+    private Set<EmptySoftwareProcess> addMultipleEmptySoftwareProcessesToGroup(int number) {
+        MutableSet<EmptySoftwareProcess> softwareProcesses = MutableSet.<EmptySoftwareProcess>of();
+        for (int i = 0; i < number; i++) {
+            softwareProcesses.add(addEmptySoftwareProcessToGroup());
+        }
+
+        return softwareProcesses;
+    }
+
+    private EmptySoftwareProcess addEmptySoftwareProcessToGroup() {
+        EmptySoftwareProcess emptySoftwareProcess = testGroup.addMemberChild(EntitySpec.create(EmptySoftwareProcess.class));
+        emptySoftwareProcess.sensors().set(STRING_SENSOR, SENSOR_VAL);
+        return emptySoftwareProcess;
+    }
+
+}
diff --git a/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestEffectorTest.java b/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestEffectorTest.java
index b6f3c4a..d0c6b8c 100644
--- a/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestEffectorTest.java
+++ b/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestEffectorTest.java
@@ -28,13 +28,19 @@
 import org.apache.brooklyn.core.test.entity.TestApplication;
 import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
 import org.apache.brooklyn.test.framework.entity.TestEntity;
+import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.text.Identifiers;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import static org.apache.brooklyn.core.entity.trait.Startable.SERVICE_UP;
+import static org.apache.brooklyn.test.Asserts.fail;
 import static org.assertj.core.api.Assertions.assertThat;
 
+import java.util.List;
+import java.util.Map;
+
 /**
  * @author m4rkmckenna on 27/10/2015.
  */
@@ -83,6 +89,65 @@
     }
 
     @Test
+    public void testEffectorPositiveAssertions() {
+        final TestCase testCase = app.createAndManageChild(EntitySpec.create(TestCase.class));
+        final TestEntity testEntity = testCase.addChild(EntitySpec.create(TestEntity.class));
+
+        String stringToReturn = "Hello World!";
+
+        Map<String, String> effectorParams = ImmutableMap.of("stringToReturn", stringToReturn);
+
+        List<Map<String, Object>> assertions = ImmutableList.<Map<String, Object>>of(
+                ImmutableMap.<String, Object>of(TestFrameworkAssertions.EQUAL_TO, stringToReturn),
+                ImmutableMap.<String, Object>of(TestFrameworkAssertions.CONTAINS, "Hello")
+        );
+
+        final TestEffector testEffector = testCase.addChild(EntitySpec.create(TestEffector.class)
+                .configure(TestEffector.TARGET_ENTITY, testEntity)
+                .configure(TestEffector.EFFECTOR_NAME, "effectorReturnsString")
+                .configure(TestEffector.EFFECTOR_PARAMS, effectorParams)
+                .configure(TestEffector.ASSERTIONS, assertions));
+
+        app.start(ImmutableList.of(app.newSimulatedLocation()));
+
+        assertThat(testEffector.sensors().get(TestEffector.EFFECTOR_RESULT)).isEqualTo(stringToReturn);
+        assertThat(testEffector.sensors().get(SERVICE_UP)).isTrue().withFailMessage("Service should be up");
+    }
+
+    @Test
+    public void testEffectorNegativeAssertions() {
+        final TestCase testCase = app.createAndManageChild(EntitySpec.create(TestCase.class));
+        final TestEntity testEntity = testCase.addChild(EntitySpec.create(TestEntity.class));
+
+        String stringToReturn = "Goodbye World!";
+
+        Map<String, String> effectorParams = ImmutableMap.of("stringToReturn", stringToReturn);
+
+        List<Map<String, Object>> assertions = ImmutableList.<Map<String, Object>>of(
+                ImmutableMap.<String, Object>of(TestFrameworkAssertions.EQUAL_TO, "Not the string I expected"),
+                ImmutableMap.<String, Object>of(TestFrameworkAssertions.CONTAINS, "Hello")
+        );
+
+        final TestEffector testEffector = testCase.addChild(EntitySpec.create(TestEffector.class)
+                .configure(TestEffector.TARGET_ENTITY, testEntity)
+                .configure(TestEffector.EFFECTOR_NAME, "effectorReturnsString")
+                .configure(TestEffector.EFFECTOR_PARAMS, effectorParams)
+                .configure(TestEffector.ASSERTIONS, assertions));
+
+        try {
+            app.start(ImmutableList.of(app.newSimulatedLocation()));
+            fail("Should have thrown execption");
+        } catch (Throwable throwable) {
+            Throwable firstInteresting = Exceptions.getFirstInteresting(throwable);
+            assertThat(firstInteresting).isNotNull();
+            assertThat(throwable).isNotNull();
+            assertThat(firstInteresting).isInstanceOf(AssertionError.class);
+        }
+
+        assertThat(testEffector.sensors().get(SERVICE_UP)).isFalse().withFailMessage("Service should not be up");
+    }
+
+    @Test
     public void testComplexffector() {
         final TestCase testCase = app.createAndManageChild(EntitySpec.create(TestCase.class));
         final TestEntity testEntity = testCase.addChild(EntitySpec.create(TestEntity.class));
diff --git a/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/entity/TestEntity.java b/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/entity/TestEntity.java
index a0abcf1..4de9587 100644
--- a/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/entity/TestEntity.java
+++ b/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/entity/TestEntity.java
@@ -48,6 +48,9 @@
                              @EffectorParam(name = "booleanValue") final Boolean booleanValue,
                              @EffectorParam(name = "longValue") final Long longValue);
 
+    @Effector
+    String effectorReturnsString(@EffectorParam(name = "stringToReturn") final String stringToReturn);
+
     class TestPojo {
         private final String stringValue;
         private final Boolean booleanValue;
diff --git a/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/entity/TestEntityImpl.java b/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/entity/TestEntityImpl.java
index 50ef967..7f066a7 100644
--- a/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/entity/TestEntityImpl.java
+++ b/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/entity/TestEntityImpl.java
@@ -56,4 +56,9 @@
         sensors().set(COMPLEX_EFFECTOR_LONG, longValue);
         return new TestPojo(stringValue, booleanValue, longValue);
     }
+
+    @Override
+    public String effectorReturnsString(@EffectorParam(name = "stringToReturn") String stringToReturn) {
+        return stringToReturn;
+    }
 }
diff --git a/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/entity/TestInfrastructure.java b/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/entity/TestInfrastructure.java
new file mode 100644
index 0000000..abcd679
--- /dev/null
+++ b/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/entity/TestInfrastructure.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.brooklyn.test.framework.entity;
+
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.entity.stock.BasicApplication;
+
+/**
+ * Created by graememiller on 17/12/2015.
+ */
+
+@ImplementedBy(TestInfrastructureImpl.class)
+public interface TestInfrastructure extends BasicApplication {
+}
\ No newline at end of file
diff --git a/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/entity/TestInfrastructureImpl.java b/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/entity/TestInfrastructureImpl.java
new file mode 100644
index 0000000..6ec0638
--- /dev/null
+++ b/brooklyn-server/test-framework/src/test/java/org/apache/brooklyn/test/framework/entity/TestInfrastructureImpl.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.brooklyn.test.framework.entity;
+
+import java.util.Collection;
+
+import com.google.common.reflect.TypeToken;
+
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.sensor.AttributeSensorAndConfigKey;
+import org.apache.brooklyn.entity.stock.BasicApplicationImpl;
+
+/**
+ * Created by graememiller on 17/12/2015.
+ */
+public class TestInfrastructureImpl extends BasicApplicationImpl implements TestInfrastructure {
+
+    private final AttributeSensorAndConfigKey<Location, Location> DEPLOYMENT_LOCATION = ConfigKeys.newSensorAndConfigKey(
+            new TypeToken<Location>() {
+            },
+            "deploymentLocationSensor", "The location to deploy to");
+
+    @Override
+    public void postStart(Collection<? extends Location> locations) {
+        super.postStart(locations);
+        sensors().set(DEPLOYMENT_LOCATION, config().get(DEPLOYMENT_LOCATION));
+    }
+}
diff --git a/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java b/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java
index 2bd7b5c..15aa76e 100644
--- a/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java
+++ b/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java
@@ -1115,22 +1115,29 @@
         if (e instanceof ShouldHaveFailedPreviouslyAssertionError) throw (Error)e;
     }
     
-    /** Tests that an exception is not {@link ShouldHaveFailedPreviouslyAssertionError}
-     * and is one of the given types. 
+    /**
+     * Tests that an exception is not {@link ShouldHaveFailedPreviouslyAssertionError}
+     * and is either one of the given types, or has a caused-by of one of the given types.
+     * 
+     * If you want *just* the instanceof (without checking the caused-by), then just catch
+     * those exception types, treat that as success, and let any other exception be propagated.
      * 
      * @return If the test is satisfied, this method returns normally. 
      * The caller can decide whether anything more should be done with the exception.
      * If the test fails, then either it is propagated, 
      * if the {@link Throwable} is a fatal ({@link Exceptions#propagateIfFatal(Throwable)}) other than an {@link AssertionError}, 
      * or more usually the test failure of this method is thrown, 
-     * with detail of the original {@link Throwable} logged. */
+     * with detail of the original {@link Throwable} logged and included in the caused-by.
+     */
     public static void expectedFailureOfType(Throwable e, Class<?> ...permittedSupertypes) {
         if (e instanceof ShouldHaveFailedPreviouslyAssertionError) throw (Error)e;
-        for (Class<?> t: permittedSupertypes) {
-            if (t.isInstance(e)) return;
+        for (Class<?> clazz: permittedSupertypes) {
+            @SuppressWarnings("unchecked")
+            Throwable match = Exceptions.getFirstThrowableOfType(e, (Class<? extends Throwable>)clazz);
+            if (match != null) return;
         }
         rethrowPreferredException(e, 
-            new AssertionError("Error "+JavaClassNames.simpleClassName(e)+" is not any of the expected types: " + Arrays.asList(permittedSupertypes)));
+            new AssertionError("Error "+JavaClassNames.simpleClassName(e)+" is not any of the expected types: " + Arrays.asList(permittedSupertypes), e));
     }
     
     /** Tests {@link #expectedFailure(Throwable)} and that the <code>toString</code>
diff --git a/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java b/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java
index 9643018..314961a 100644
--- a/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java
+++ b/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java
@@ -33,6 +33,7 @@
 import javax.annotation.Nullable;
 
 import org.apache.brooklyn.util.collections.MutableSet;
+import org.apache.brooklyn.util.javalang.JavaClassNames;
 import org.apache.brooklyn.util.text.Strings;
 
 import com.google.common.base.Predicate;
@@ -324,7 +325,23 @@
             return new CompoundRuntimeException(prefix, exceptions);
         }
         if (Strings.isBlank(prefix)) return new CompoundRuntimeException(exceptions.size()+" errors, including: " + Exceptions.collapseText(exceptions.iterator().next()), exceptions);
-        return new CompoundRuntimeException(prefix+", "+exceptions.size()+" errors including: " + Exceptions.collapseText(exceptions.iterator().next()), exceptions);
+        return new CompoundRuntimeException(prefix+"; "+exceptions.size()+" errors including: " + Exceptions.collapseText(exceptions.iterator().next()), exceptions);
+    }
+
+    /** Some throwables require a prefix for the message to make sense,
+     * for instance NoClassDefFoundError's message is often just the type.
+     */
+    public static boolean isPrefixImportant(Throwable t) {
+        if (t instanceof NoClassDefFoundError) return true;
+        return false;
+    }
+
+    /** For {@link Throwable} instances where know {@link #isPrefixImportant(Throwable)},
+     * this returns a nice message for use as a prefix; otherwise this returns the class name.
+     * Callers should typically suppress the prefix if {@link #isPrefixBoring(Throwable)} is true. */
+    public static String getPrefixText(Throwable t) {
+        if (t instanceof NoClassDefFoundError) return "Type not found";
+        return JavaClassNames.cleanSimpleClassName(t);
     }
 
 }
diff --git a/brooklyn-server/utils/rest-swagger/pom.xml b/brooklyn-server/utils/rest-swagger/pom.xml
index 6f9a86f..83849f9 100644
--- a/brooklyn-server/utils/rest-swagger/pom.xml
+++ b/brooklyn-server/utils/rest-swagger/pom.xml
@@ -72,6 +72,10 @@
                     <groupId>org.slf4j</groupId>
                     <artifactId>slf4j-log4j12</artifactId>
                 </exclusion>
+                <exclusion>
+                    <groupId>org.apache.commons</groupId>
+                    <artifactId>commons-lang3</artifactId>
+                </exclusion>
             </exclusions>
         </dependency>
         <dependency>
diff --git a/brooklyn-server/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApiListingResource.java b/brooklyn-server/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApiListingResource.java
index 3edffe9..84c52f8 100644
--- a/brooklyn-server/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApiListingResource.java
+++ b/brooklyn-server/utils/rest-swagger/src/main/java/org/apache/brooklyn/rest/apidoc/ApiListingResource.java
@@ -48,7 +48,8 @@
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
-import org.apache.commons.lang3.StringUtils;
+
+import org.apache.brooklyn.util.text.Strings;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -171,7 +172,7 @@
             @Context HttpHeaders headers,
             @Context UriInfo uriInfo,
             @PathParam("type") String type) {
-        if (StringUtils.isNotBlank(type) && type.trim().equalsIgnoreCase("yaml")) {
+        if (Strings.isNonBlank(type) && type.trim().equalsIgnoreCase("yaml")) {
             return getListingYaml(app, sc, headers, uriInfo);
         } else {
             return getListingJson(app, sc, headers, uriInfo);
diff --git a/brooklyn-ui/src/main/license/files/LICENSE b/brooklyn-ui/src/main/license/files/LICENSE
index 4a1f38b..3b3b73d 100644
--- a/brooklyn-ui/src/main/license/files/LICENSE
+++ b/brooklyn-ui/src/main/license/files/LICENSE
@@ -208,13 +208,6 @@
   Used under the following license: The MIT License (http://opensource.org/licenses/MIT)
   Copyright (c) Jeremy Ashkenas, DocumentCloud Inc. (2010-2013)
 
-This project includes the software: backbone.js
-  Available at: http://backbonejs.org
-  Developed by: DocumentCloud Inc. (http://www.documentcloud.org/)
-  Version used: 1.1.2
-  Used under the following license: The MIT License (http://opensource.org/licenses/MIT)
-  Copyright (c) Jeremy Ashkenas, DocumentCloud (2010-2014)
-
 This project includes the software: bootstrap.js
   Available at: http://twitter.github.com/bootstrap/javascript.html#transitions
   Version used: 2.0.4
@@ -229,14 +222,6 @@
   Used under the following license: The MIT License (http://opensource.org/licenses/MIT)
   Copyright (c) Yehuda Katz (2012)
 
-This project includes the software: handlebars.js
-  Available at: https://github.com/wycats/handlebars.js
-  Developed by: Yehuda Katz (https://github.com/wycats/)
-  Inclusive of: handlebars*.js
-  Version used: 2.0.0
-  Used under the following license: The MIT License (http://opensource.org/licenses/MIT)
-  Copyright (C) by Yehuda Katz (2011-2014)
-
 This project includes the software: jQuery JavaScript Library
   Available at: http://jquery.com/
   Developed by: The jQuery Foundation (http://jquery.org/)
@@ -249,18 +234,6 @@
     Available at http://sizzlejs.com
     Used under the MIT license
 
-This project includes the software: jQuery JavaScript Library
-  Available at: http://jquery.com/
-  Developed by: The jQuery Foundation (http://jquery.org/)
-  Inclusive of: jquery.js
-  Version used: 1.8.0
-  Used under the following license: The MIT License (http://opensource.org/licenses/MIT)
-  Copyright (c) jQuery Foundation and other contributors (2012)
-  Includes code fragments from sizzle.js:
-    Copyright (c) The Dojo Foundation
-    Available at http://sizzlejs.com
-    Used under the MIT license
-
 This project includes the software: jQuery BBQ: Back Button & Query Library
   Available at: http://benalman.com/projects/jquery-bbq-plugin/
   Developed by: "Cowboy" Ben Alman (http://benalman.com/)
@@ -309,6 +282,13 @@
   Used under the following license: The MIT License (http://opensource.org/licenses/MIT)
   Copyright (c) Vitaly Puzrin (2011-2015)
 
+This project includes the software: marked.js
+  Available at: https://github.com/chjj/marked
+  Developed by: Christopher Jeffrey (https://github.com/chjj)
+  Version used: 0.3.1
+  Used under the following license: The MIT License (http://opensource.org/licenses/MIT)
+  Copyright (c) Christopher Jeffrey (2011-2014)
+
 This project includes the software: moment.js
   Available at: http://momentjs.com
   Developed by: Tim Wood (http://momentjs.com)
@@ -350,19 +330,11 @@
       Arpad Borsos (2012)
     Used under the BSD 2-Clause license.
 
-This project includes the software: Swagger JS
-  Available at: https://github.com/swagger-api/swagger-js
-  Inclusive of: swagger.js
-  Version used: 2.1.6
-  Used under the following license: Apache License, version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
-  Copyright (c) SmartBear Software (2015)
+This project includes the software: swagger
+  Used under the following license: <no license info>
 
-This project includes the software: Swagger UI
-  Available at: https://github.com/swagger-api/swagger-ui
-  Inclusive of: swagger-ui.js
-  Version used: 2.1.3
-  Used under the following license: Apache License, version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
-  Copyright (c) SmartBear Software (2015)
+This project includes the software: swagger-ui
+  Used under the following license: <no license info>
 
 This project includes the software: underscore.js
   Available at: http://underscorejs.org
@@ -372,14 +344,6 @@
   Used under the following license: The MIT License (http://opensource.org/licenses/MIT)
   Copyright (c) Jeremy Ashkenas, DocumentCloud Inc. (2009-2013)
 
-This project includes the software: underscore.js
-  Available at: http://underscorejs.org
-  Developed by: DocumentCloud Inc. (http://www.documentcloud.org/)
-  Inclusive of: underscore*.{js,map}
-  Version used: 1.7.0
-  Used under the following license: The MIT License (http://opensource.org/licenses/MIT)
-  Copyright (c) Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors (2009-2014)
-
 This project includes the software: ZeroClipboard
   Available at: http://zeroclipboard.org/
   Developed by: ZeroClipboard contributors (https://github.com/zeroclipboard)
@@ -388,13 +352,6 @@
   Used under the following license: The MIT License (http://opensource.org/licenses/MIT)
   Copyright (c) Jon Rohan, James M. Greene (2014)
 
-This project includes the software: marked.js
-  Available at: https://github.com/chjj/marked
-  Developed by: Christopher Jeffrey (https://github.com/chjj)
-  Inclusive of: marked.js
-  Version used: 0.3.1
-  Used under the following license: The MIT License (http://opensource.org/licenses/MIT)
-  Copyright (c) Christopher Jeffrey (2011-2014)
 
 ---------------------------------------------------