This closes #1123
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-dist/downstream-parent/pom.xml b/brooklyn-dist/downstream-parent/pom.xml
index 6a7d694..f97dba3 100644
--- a/brooklyn-dist/downstream-parent/pom.xml
+++ b/brooklyn-dist/downstream-parent/pom.xml
@@ -57,7 +57,7 @@
     <jclouds.groupId>org.apache.jclouds</jclouds.groupId> <!-- JCLOUDS_GROUPID_VERSION -->
 
     <!-- versions should match those used by Brooklyn, to avoid conflicts -->
-    <jclouds.version>1.9.1</jclouds.version> <!-- JCLOUDS_VERSION -->
+    <jclouds.version>1.9.2</jclouds.version> <!-- JCLOUDS_VERSION -->
     <logback.version>1.0.7</logback.version>
     <slf4j.version>1.6.6</slf4j.version>  <!-- used for java.util.logging jul-to-slf4j interception -->
     <guava.version>17.0</guava.version>
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/ops/cli/cli-ref-guide.md b/brooklyn-docs/guide/ops/cli/cli-ref-guide.md
index f9c752c..9a7ebfd 100644
--- a/brooklyn-docs/guide/ops/cli/cli-ref-guide.md
+++ b/brooklyn-docs/guide/ops/cli/cli-ref-guide.md
@@ -228,7 +228,10 @@
   Invoke the effector without any parameters.
 
 - `br <entity-scope> effector <EffectorID> invoke [<param>=<value> ...]`  
-  Invoke the effector with one of more parameters.
+  Invoke the effector with one of more parameters.  
+  If the parameter value is
+  complex or multi-lined it may be provided in a file and referenced as:  
+  `<param>=@<FILE>`
 
 **NOTE** Shortcut commands have been provided for the standard start, restart and stop effectors.  For example:  
 
diff --git a/brooklyn-docs/guide/ops/cli/cli-usage-guide.md b/brooklyn-docs/guide/ops/cli/cli-usage-guide.md
index bf2f310..c8edf0d 100644
--- a/brooklyn-docs/guide/ops/cli/cli-usage-guide.md
+++ b/brooklyn-docs/guide/ops/cli/cli-usage-guide.md
@@ -261,6 +261,12 @@
 $ br app WebCluster entity NginxController:CZ8Q effector restart invoke restartChildren=true
 {% endhighlight %}
 
+If a parameter value is complex or spans multiple lines, it may be provided in a file and used like this:
+
+{% highlight text %}
+$ br app WebCluster effector start invoke locations=@data.txt
+{% endhighlight %}
+
 Shortcut commands are available for the 3 standard effectors of `start`, `restart` and `stop`.
 These commands can be used directly with an app-scope or entity-scope:
 
diff --git a/brooklyn-docs/guide/ops/persistence/index.md b/brooklyn-docs/guide/ops/persistence/index.md
index 4788db9..02c05ff 100644
--- a/brooklyn-docs/guide/ops/persistence/index.md
+++ b/brooklyn-docs/guide/ops/persistence/index.md
@@ -104,7 +104,7 @@
 To configure the Object Store, add the credentials to `~/.brooklyn/brooklyn.properties` such as:
 
 {% highlight properties %}
-brooklyn.location.named.aws-s3-eu-west-1:aws-s3:eu-west-1
+brooklyn.location.named.aws-s3-eu-west-1=aws-s3:eu-west-1
 brooklyn.location.named.aws-s3-eu-west-1.identity=ABCDEFGHIJKLMNOPQRSTU
 brooklyn.location.named.aws-s3-eu-west-1.credential=abcdefghijklmnopqrstuvwxyz1234567890ab/c
 {% endhighlight %} 
diff --git a/brooklyn-docs/guide/start/blueprints.md b/brooklyn-docs/guide/start/blueprints.md
index 0ccf664..492ac73 100644
--- a/brooklyn-docs/guide/start/blueprints.md
+++ b/brooklyn-docs/guide/start/blueprints.md
@@ -4,34 +4,36 @@
 menu_parent: index.md
 children:
 - { section: Launching from a Blueprint, title: Blueprint } 
-- { section: Launching from the Catalog, title: Catalog } 
 ---
 
-{% include fields.md %}
-
+<div style="width: 100%; display: block; background-color: #CC9966; margin-bottom: 2px;  padding: 50px 30px 50px 80px;" >
+  <h3>NOTE</h3>
+  <div>
+  The structure of Brooklyn's repositories is changing at present (Jan 2016). Until this is complete 
+  please obtain the "br" command line tool from <a href="https://github.com/brooklyncentral/brooklyn-cli">Brooklyn Central</a>
+  </div>
+</div>
 
 ## Launching from a Blueprint
 
-We'll start by deploying an application via YAML blueprint consisting of the following layers.
+We'll start by deploying an application with a simple YAML blueprint containing a Tomcat server.
 
-- Nginx load balancer
-- Tomcat app server cluster
-- MySQL DB
-
-When you first access the web console on [127.0.0.1:8081](http://127.0.0.1:8081) you will be requested to create your first application.
-
-
-[![Brooklyn web console, showing the YAML tab of the Add Application dialog.](images/add-application-modal-yaml.png)](images/add-application-modal-yaml-large.png)
-
-Switch to the YAML tab and copy the blueprint below into the large text box. 
-
-But *before* you submit it, modify the YAML to specify the location where the application will be deployed.
+Copy the blueprint below into a text file, "myapp.yaml", in your workspace, but *before* you create an application with 
+it, modify the YAML to specify the location where the application will be deployed.  (Note, to copy the file you can
+hover your mouse over the right side of the text box below to get a Javascript "copy" button.)
 
 {% highlight yaml %}
-{% readj _my-web-cluster.yaml %}
+name: Tomcat
+location:
+  jclouds:aws-ec2:
+    identity: ABCDEFGHIJKLMNOPQRST
+    credential: s3cr3tsq1rr3ls3cr3tsq1rr3ls3cr3tsq1rr3l
+services:
+- serviceType: brooklyn.entity.webapp.tomcat.TomcatServer
 {% endhighlight %}
 
-Replace the `location:` element with values for your chosen target environment, for example to use SoftLayer rather than AWS (updating with your own credentials): 
+Replace the ```location:``` element with values for your chosen target environment, for example to use SoftLayer rather 
+than AWS (updating with your own credentials): 
 
 {% highlight yaml %}
 location:
@@ -40,26 +42,45 @@
     credential: s3cr3tsq1rr3ls3cr3tsq1rr3ls3cr3tsq1rr3l
 {% endhighlight %}
 
-**Note**: See __[Locations](../ops/locations)__ in the Operations section of the User Guide for instructions on setting up alternate cloud providers, bring-your-own-nodes, or localhost targets, and storing credentials/locations in a file on disk rather than in the blueprint.
+Or, if you already have machines provisioned, you can use the "bring your own nodes" (byon) approach. 
+Of course, replace the identity and address values below with your own values.
+{% highlight yaml %}
+location:
+  byon:
+    user: myuser
+    password: mypassword
+    # or...
+    #privateKeyFile: ~/.ssh/my.pem
+    hosts:
+    - 192.168.0.18
+    - 192.168.0.19
+{% endhighlight %}
 
-With the modified YAML in the dialog, click "Finish". The dialog will close and Brooklyn will begin deploying your
-application. Your application will be shown as "Starting" on the web console's front page.
+**Note**: See __[Locations](../ops/locations)__ in the Operations section of the User Guide for instructions on setting
+up alternate cloud providers, bring-your-own-nodes, or localhost targets, and storing credentials/locations in a file 
+on disk rather than in the blueprint.
+(For the application above, if you are using a "Bring your own Nodes" location, you will need at least three nodes.)
 
-Depending on your choice of location it may take some time for the application nodes to start, the next page describes how you can monitor the progress of the application deployment and verify its successful deployment.
+First you will have to log in to brooklyn:
+{% highlight bash %}
+$ br login http://localhost:8081/
+{% endhighlight %}
 
-### Launching from the Catalog
+To secure the server you can add a username and password in Brooklyn's properties file, as described in the User Guide. 
+Then the login command will require the additional parameters of the userid and password.
 
-Instead of pasting the YAML blueprint each time, it can be added to Brooklyns Catalog where it will be accessible from the Catalog tab of the Create Application dialog.
+Now you can create the application with the command below:
 
-[![Viewing Catalog entries in Add Application dialog.](images/add-application-catalog-web-cluster-with-db.png)](images/add-application-catalog-web-cluster-with-db-large.png)
+{% highlight bash %}
+$ br deploy myapp.yaml
+Id:       hTPAF19s   
+Name:     Tomcat   
+Status:   In progress   
+{% endhighlight %}
 
-<!-- TODO: more detail for adding to catalog? but wait for persistence to be the default, 
-     rather than extensively document default.catalog.bom.
-     also need to include instructions on stopping (currently in help, including stopping apps) -->
+Depending on your choice of location it may take some time for the application to start, the next page describes how 
+you can monitor the progress of the application deployment and verify its successful deployment.
 
-See __[Catalog](../ops/catalog/)__ in the Operations section of the User Guide for instructions on creating a new Catalog entry from your Blueprint YAML.
+## Next
 
-
-## Next 
-
-So far we have touched on Brooklyn's ability to *deploy* an application blueprint to a cloud provider, but this just the beginning, proceed to  **[Monitoring and Managing Applications](managing.html)**.
+Having deployed an application, the next step is **[monitoring and managing](managing.html)** it.
\ No newline at end of file
diff --git a/brooklyn-docs/guide/start/index.md b/brooklyn-docs/guide/start/index.md
index ccd6f6f..ea8c2a3 100644
--- a/brooklyn-docs/guide/start/index.md
+++ b/brooklyn-docs/guide/start/index.md
@@ -10,3 +10,5 @@
 ---
 
 {% include list-children.html %}
+
+
diff --git a/brooklyn-docs/guide/start/managing.md b/brooklyn-docs/guide/start/managing.md
index 7139f32..da027a7 100644
--- a/brooklyn-docs/guide/start/managing.md
+++ b/brooklyn-docs/guide/start/managing.md
@@ -3,68 +3,357 @@
 title_in_menu: Monitoring and Managing Applications
 layout: website-normal
 menu_parent: index.md
+children:
+- { section: Applications } 
+- { section: Entities } 
+- { section: Sensors  } 
+- { section: Effectors  } 
+- { section: Activities } 
 ---
 
-Click on the application name, or open the Applications tab.
-
-We can explore the management hierarchy of the application, which will show us the entities it is composed of.
-
- * My Web Cluster (A `BasicApplication`)
-     * My DB (A `MySqlNode`)
-     * My Web (A `ControlledDynamicWebAppCluster`)
-        * Cluster of TomcatServer (A `DynamicWebAppCluster`)
-        * NginxController (An `NginxController`)
 
 
+So far we have touched on Brooklyn's ability to *deploy* an application blueprint to a cloud provider, but this just 
+the beginning. The sections below outline how to manage the application that has been deployed.
 
-Clicking on the "My Web Cluster" entity will show the "Summary" tab,
-giving a very high level of what that component is doing. 
-Click on each of the child components in turn for more detail on that component. 
-Note that the cluster of web servers includes a "quarantine group", to which members of the 
-cluster that fail will be added. These are excluded from the load-balancer's targets.
 
-[![Exploring My Web.](images/my-web.png)](images/my-web-large.png)
+## Scopes in CLI commands
+Many commands require a "scope" expression to indicate the target on which they operate. The scope expressions are
+as follows (values in brackets are aliases for the scope):
+
+- ```application``` APP-ID   (app, a)  
+ Selects an application, e.g. "br app myapp"  
+- ```entity```      ENT-ID   (ent, e)  
+ Selects an entity within an application scope, e.g. ```br app myapp ent myserver```  
+- ```effector```    EFF-ID   (eff, f)  
+ Selects an effector of an entity or application, e.g. ```br a myapp e myserver eff xyz```  
+- ```config```      CONF-KEY (conf, con, c)  
+ Selects a configuration key of an entity e.g. ```br a myapp e myserver config jmx.agent.mode```  
+- ```activity```    ACT-ID   (act, v)  
+ Selects an activity of an entity e.g. ```br a myapp e myserver act iHG7sq1```  
+
+For example
+{% highlight bash %}
+$ br app Tomcat ent TomcatServer:Wx7r config
+{% endhighlight %}
+runs the ```config``` command with application scope of ```Tomcat``` and entity scope of ```TomcatServer:Wx7r```.
+
+## Applications
+
+Having created the application we can query its status.  We can find a summary of all deployed apps:
+{% highlight bash %}
+$ br application
+ Id         Name     Status    Location   
+ hTPAF19s   Tomcat   RUNNING   ajVVAhER  
+{% endhighlight %}
+
+```application``` can be shortened to one of the aliases ```app``` or just ```a```, for example:
+{% highlight bash %}
+$ br app
+ Id         Name     Status    Location   
+ hTPAF19s   Tomcat   RUNNING   ajVVAhER  
+{% endhighlight %}
+
+You can find the details of a given application, using its name or ID.
+{% highlight bash %}
+$ br app Tomcat
+  Id:              hTPAF19s   
+  Name:            Tomcat   
+  Status:          RUNNING   
+  ServiceUp:       true   
+  Type:            org.apache.brooklyn.entity.stock.BasicApplication   
+  CatalogItemId:   null   
+  LocationId:      ajVVAhER   
+  LocationName:    FixedListMachineProvisioningLocation:ajVV   
+  LocationSpec:    vagrantbyon   
+  LocationType:    org.apache.brooklyn.location.byon.FixedListMachineProvisioningLocation  
+{% endhighlight %}
+
+To ease management of multiple applications, or even to reduce the amount of typing required, it is convenient
+to create an alias for the commonly used application scope:
+{% highlight bash %}
+alias tom="br app Tomcat"
+{% endhighlight %}
+
+To illustrate this we will assume the above alias for the rest of this section, but to avoid confusion 
+the examples in other sections will show the full command in all cases.
+
+We can explore the management hierarchy of all applications, which will show us the entities they are composed of.
+{% highlight bash %}
+$ br tree
+|- Tomcat
++- org.apache.brooklyn.entity.stock.BasicApplication
+  |- TomcatServer:Wx7r
+  +- org.apache.brooklyn.entity.webapp.tomcat.TomcatServer
+{% endhighlight %}
+
+You can view the blueprint for the application again:
+{% highlight bash %}
+$ tom spec
+"name: Tomcat\nlocation:\n  mylocation\nservices:\n- serviceType: brooklyn.entity.webapp.tomcat.TomcatServer\n"
+{% endhighlight %}
+
+You can view the config of the application:
+{% highlight bash %}
+$ tom config
+Key                    Value   
+camp.template.id       l67i25CM   
+brooklyn.wrapper_app   true   
+{% endhighlight %}
+
+## Entities
+To explore the entities of the application you can use the ```entity``` command. This will show the 
+immediate child entities of a given application or one of its child entities.
+
+{% highlight bash %}
+$ br app Tomcat entity
+Id         Name                Type   
+Wx7r1C4e   TomcatServer:Wx7r   org.apache.brooklyn.entity.webapp.tomcat.TomcatServer      
+{% endhighlight %}
+
+```entity``` has aliases ```ent``` or ```e```.
+
+You can get summary information for an entity by providing its name (or ID).
+
+{% highlight bash %}
+$ br app Tomcat ent TomcatServer:Wx7r
+Id:              Wx7r1C4e   
+Name:            TomcatServer:Wx7r   
+Status:          RUNNING   
+ServiceUp:       true   
+Type:            org.apache.brooklyn.entity.webapp.tomcat.TomcatServer   
+CatalogItemId:   null   
+{% endhighlight %}
+
+Also you can see the config of the entity with the ```config``` command.
+
+{% highlight bash %}
+$ br app Tomcat ent TomcatServer:Wx7r config
+Key                       Value   
+jmx.agent.mode            JMXMP_AND_RMI   
+brooklyn.wrapper_app      true   
+camp.template.id          yBcQuFZe   
+onbox.base.dir            /home/vagrant/brooklyn-managed-processes   
+onbox.base.dir.resolved   true   
+install.unique_label      TomcatServer_7.0.65   
+{% endhighlight %}
+
+If an entity name is annoyingly long to type, the entity can be renamed:
+
+{% highlight bash %}
+$ br app Tomcat ent TomcatServer:Wx7r rename server
+{% endhighlight %}
+
+## Sensors
+
+"Sensors" on entities provide a real-time picture of the status and operation of an entity of the application.
+
+To view the sensors on the application itself, use the command below:
+
+{% highlight bash %}
+$ br app Tomcat sensor
+Name                       Description                                                                             Value   
+service.isUp               Whether the service is active and availability (confirmed and monitored)                true   
+service.notUp.indicators   A map of namespaced indicators that the service is not up                               {}   
+service.problems           A map of namespaced indicators of problems with a service                               {}   
+service.state              Actual lifecycle state of the service                                                   "RUNNING"   
+service.state.expected     Last controlled change to service state, indicating what the expected state should be   "running @ 1450356994928 / Thu Dec 17 12:56:34 GMT 2015"
+{% endhighlight %}
+
+To explore all sensors available on an entity use the sensor command with an entity scope.
+Note, again, the name of the application or entity can be used or the ID:
+
+{% highlight bash %}
+br app Tomcat ent TomcatServer:Wx7r sensor
+Name                                            Description                                                                                                      Value   
+download.addon.urls                             URL patterns for downloading named add-ons (will substitute things like ${version} automatically)                   
+download.url                                    URL pattern for downloading the installer (will substitute things like ${version} automatically)                 "http://download.nextag.com/apache/tomcat/tomcat-7/v${version}/bin/apache-tomcat-${version}.tar.gz"   
+expandedinstall.dir                             Directory for installed artifacts (e.g. expanded dir after unpacking .tgz)                                       "/home/vagrant/brooklyn-managed-processes/installs/TomcatServer_7.0.65/apache-tomcat-7.0.65"   
+host.address                                    Host IP address                                                                                                  "10.10.10.101"   
+host.name                                       Host name                                                                                                        "10.10.10.101"   
+host.sshAddress                                 user@host:port for ssh'ing (or null if inappropriate)                                                            "vagrant@10.10.10.101:22"   
+host.subnet.address                             Host address as known internally in the subnet where it is running (if different to host.name)                   "10.10.10.101"   
+host.subnet.hostname                            Host name as known internally in the subnet where it is running (if different to host.name)                      "10.10.10.101"   
+# etc. etc.
+{% endhighlight %}
+
+
+To study selected sensors, give the command the sensor name as an argument
+
+{% highlight bash %}
+$ br app Tomcat ent TomcatServer:Wx7r sensor webapp.url   
+"http://10.10.10.101:8080/"
+{% endhighlight %}
+
+
+## Effectors
+
+Effectors are the means by which you can manipulate the entities in an application.  For an application you can list them 
+with 
+
+{% highlight bash %}
+$ br app Tomcat effector
+Name            Description                                                                                                                                                                            Parameters   
+restart         Restart the process/service represented by an entity                                                                                                                                      
+start           Start the process/service represented by an entity                                                                                                                                     locations   
+stop            Stop the process/service represented by an entity                                                                                                                                         
+{% endhighlight %}
+
+For an entity supply the entity scope:
+
+{% highlight bash %}
+$ br app Tomcat ent TomcatServer:Wx7r effector
+Name                              Description                                                                               Parameters   
+deploy                            Deploys the given artifact, from a source URL, to a given deployment filename/context     url,targetName   
+populateServiceNotUpDiagnostics   Populates the attribute service.notUp.diagnostics, with any available health indicators      
+restart                           Restart the process/service represented by an entity                                      restartChildren,restartMachine   
+start                             Start the process/service represented by an entity                                        locations   
+stop                              Stop the process/service represented by an entity                                         stopProcessMode,stopMachineMode   
+undeploy                          Undeploys the given context/artifact                                                      targetName   
+{% endhighlight %}
+
+To view just one effector's documentation, supply its name to the show command:
+
+{% highlight bash %}
+$ br app Tomcat ent TomcatServer:Wx7r effector deploy
+Name            Description                                                                                                                                                                            Parameters   
+deploy          Deploys the given artifact, from a source URL, to a given deployment filename/context                                                                                                  url,targetName   
+{% endhighlight %}
+
+These effectors can be invoked using the command ```invoke```, supplying the application and entity id of the entity to 
+invoke the effector on.   
+
+For example, to stop an application, use the ```stop``` effector. This will cleanly shutdown all components in the 
+application and return any cloud machines that were being used. Do the invocation by supplying the effector name in 
+the scope, and using the command ```invoke```. 
+
+{% highlight bash %}
+$ br app Tomcat ent TomcatServer:Wx7r eff stop invoke
+{% endhighlight %}
+
+Note that the three "lifecycle" related effectors, ```start```, ```stop```, and ```restart```, are common to all software process 
+entities in Brooklyn. They are so commonly used that they have their own aliases. The above could also have been done
+by:
+
+{% highlight bash %}
+$ br app Tomcat ent TomcatServer:Wx7r stop
+{% endhighlight %}
+
+Some effectors require parameters for their invocation, as in the example of ```deploy``` above.  
+
+{% highlight bash %}
+br app Tomcat ent TomcatServer:Wx7r effector deploy
+Name     Description                                                                             Parameters   
+deploy   Deploys the given artifact, from a source URL, to a given deployment filename/context   url,targetName   
+{% endhighlight %}
+
+Now the effector can be invoked by supplying the parameters using ```--param parm=value``` or just ```-P parm=value```.
+
+In the example below, a sample Tomcat war file is deployed, a variable is created for the root URL using the appropriate
+sensor, and the index page is fetched. Note that at present a ```tr``` command is required in the second line below to strip
+quotation characters from the returned sensor value. 
+
+{% highlight bash %}
+$ br app Tomcat ent TomcatServer:Wx7r effector deploy invoke -P url=https://tomcat.apache.org/tomcat-6.0-doc/appdev/sample/sample.war -P targetName=sample
+$ webapp=$(br app Tomcat ent TomcatServer:Wx7r sensor webapp.url | tr -d '"')
+$ curl $webapp/sample/
+<html>
+<head>
+<title>Sample "Hello, World" Application</title>
+</head>
+# etc. etc.
+{% endhighlight %}
 
 
 ## Activities
 
-The Activity tab allows us to drill down into the tasks each entity is currently executing or has recently completed. It is possible to drill down through all child tasks, and view the commands issued, along with any errors or warnings that occurred.
+The ```activity``` command allows us to investigate the activities of an entity. 
 
-For example clicking on the NginxController in the left hand tree and opening its Activity tab you can observe the 'start' task is 'In progress'.
+To view a list of all activities associated with an entity simply use
 
-**Note**: You may observe different tasks depending on how far your deployment has progressed).
+{% highlight bash %}
+$ br app Tomcat ent TomcatServer:Wx7r activity
+Id         Task                                       Submitted                      Status      Streams   
+LtD5P1cb   start                                      Thu Dec 17 15:04:43 GMT 2015   Completed   
+l2qo4vTl   provisioning (FixedListMachineProvisi...   Thu Dec 17 15:04:43 GMT 2015   Completed   
+wLD764HE   pre-start                                  Thu Dec 17 15:04:43 GMT 2015   Completed    
+KLTxDkoa   ssh: initializing on-box base dir ./b...   Thu Dec 17 15:04:43 GMT 2015   Completed   env,stderr,stdin,stdout   
+jwwcJWmF   start (processes)                          Thu Dec 17 15:04:43 GMT 2015   Completed        
+# etc. etc.
+{% endhighlight %}
 
-[![My DB Activities Step 1.](images/my-db-activities-step1.png)](images/my-db-activities-step1-large.png)
+To view the details of an individual activity provide its ID:
 
-Clicking on the 'start' task you can discover more details on the actions being carried out by that task (a task may consist of additional subtasks).
+{% highlight bash %}
+$ br app Tomcat ent TomcatServer:Wx7r activity jwwcJWmF
+Id:                  jwwcJWmF   
+DisplayName:         start (processes)   
+Description:            
+EntityId:            efUvVWAw   
+EntityDisplayName:   TomcatServer:efUv   
+Submitted:           Thu Dec 17 15:04:43 GMT 2015   
+Started:             Thu Dec 17 15:04:43 GMT 2015   
+Ended:               Thu Dec 17 15:08:59 GMT 2015   
+CurrentStatus:       Completed   
+IsError:             false   
+IsCancelled:         false   
+SubmittedByTask:     LtD5P1cb   
+Streams:                
+DetailedStatus:      "Completed after 4m 16s
 
-[![My DB Activities Step 2.](images/my-db-activities-step2.png)](images/my-db-activities-step2-large.png)
+No return value (null)"   
+{% endhighlight %}
 
-Continuing to drill down into the 'In progress' tasks you will eventually reach the currently active task where you can investigate the ssh command executed on the target node including the current stdin, stdout and stderr output.
+If an activity has failed, the "DetailedStatus" value will show information about the failure, as an aid to diagnosis.
 
-[![My DB Activities Step 3.](images/my-db-activities-step3.png)](images/my-db-activities-step3-large.png)
+Adding the ```--children``` or ```-c``` parameter will show the activity's child activities, to allow the hierarchical structure 
+of the activities to be investigated:
+
+{% highlight bash %}
+$ br app Tomcat ent TomcatServer:Wx7r activity -c jwwcJWmF
+Id         Task                         Submitted                      Status   
+UpYRc3fw   copy-pre-install-resources   Thu Dec 17 15:04:43 GMT 2015   Completed   
+ig8sBHQr   pre-install                  Thu Dec 17 15:04:43 GMT 2015   Completed   
+Elp4HaVj   pre-install-command          Thu Dec 17 15:04:43 GMT 2015   Completed   
+YOvNobJk   setup                        Thu Dec 17 15:04:43 GMT 2015   Completed   
+VN3cDKki   copy-install-resources       Thu Dec 17 15:08:43 GMT 2015   Completed   
+xDJXQC0J   install                      Thu Dec 17 15:08:43 GMT 2015   Completed   
+zxMDXUxz   post-install-command         Thu Dec 17 15:08:58 GMT 2015   Completed   
+qnQnw7Oc   customize                    Thu Dec 17 15:08:58 GMT 2015   Completed   
+ug044ArS   copy-runtime-resources       Thu Dec 17 15:08:58 GMT 2015   Completed   
+STavcRc8   pre-launch-command           Thu Dec 17 15:08:58 GMT 2015   Completed   
+HKrYfH6h   launch                       Thu Dec 17 15:08:58 GMT 2015   Completed   
+T1m8VXbq   post-launch-command          Thu Dec 17 15:08:59 GMT 2015   Completed   
+n8eK5USE   post-launch                  Thu Dec 17 15:08:59 GMT 2015   Completed   
+{% endhighlight %}
+
+If an activity has associated input and output streams, these may be viewed by providing the activity scope and
+using the commands, ```env```, ```stdin```, ```stdout```, and ```stderr```.  For example, for the "initializing on-box base dir"
+activity from the result of the earlier example,
+
+{% highlight bash %}
+$ br app Tomcat ent TomcatServer:Wx7r act KLTxDkoa stdout
+BASE_DIR_RESULT:/home/vagrant/brooklyn-managed-processes:BASE_DIR_RESULT
+
+{% endhighlight %}
 
 
-## Sensors
+To monitor progress on an application as it deploys, for example, one could use a shell loop:
 
-Now click on the "Sensors" tab:
-these data feeds drive the real-time picture of the application.
-As you navigate in the tree at the left, you can see more targeted statistics coming in in real-time.
+{% highlight bash %}
+$ while br app Tomcat ent TomcatServer:Wx7r activity | grep 'In progress' ; do 
+  sleep 1; echo ; date; 
+done
+{% endhighlight %}
+This loop will exit when the application has deployed successfully or has failed.  If it fails then the 'stderr' 
+command may provide information about what happened in any activities that have associated streams:
 
-Explore the sensors and the tree to find the URL where the _NginxController_ for the webapp we just deployed is running. This can be found in '**My Web Cluster** -> **My Web** -> **NginxController** -> **_main.uri_**'.
-
-Quickly return to the **‘Brooklyn JS REST client’** web browser
-tab showing the "Sensors" and observe the '**My Web Cluster** -> **My Web** -> **Cluster of TomcatServer** -> **_webapp.reqs.perSec.last_**' sensor value increase.  
+{% highlight bash %}
+$ br app Tomcat ent TomcatServer:Wx7r act KLTxDkoa stderr
+{% endhighlight %}
 
 
+## Next
 
-## Stopping the Application
-
-To stop an application, select the application in the tree view (the top/root entity), click on the Effectors tab, and invoke the "Stop" effector. This will cleanly shutdown all components in the application and return any cloud machines that were being used.
-
-[![My DB Activities.](images/my-web-cluster-stop-confirm.png)](images/my-web-cluster-stop-confirm-large.png)
-
-
-### Next
-
-Brooklyn's real power is in using **[Policies](policies.html)**  to automatically *manage* applications. 
+We will look next at a slightly more complex example, which will illustrate the capabilities of Brooklyn's
+**[policies](policies.html)** mechanism, and how to configure dependencies between application entities.
\ No newline at end of file
diff --git a/brooklyn-docs/guide/start/policies.md b/brooklyn-docs/guide/start/policies.md
index b1029b2..2de509b 100644
--- a/brooklyn-docs/guide/start/policies.md
+++ b/brooklyn-docs/guide/start/policies.md
@@ -2,50 +2,176 @@
 title: Getting Started - Policies
 title_in_menu: Policies
 layout: website-normal
+menu_parent: index.md
+children:
+- { section: "A Clustered Example", title_in_menu: "Clustering" } 
+- { section: "Configuring Dependencies", title_in_menu: "Dependencies" } 
+- { section: "Managing with Policies", title_in_menu: "Policies"  } 
 ---
 
-### Exploring and Testing Policies
 
-To see an example of policy based management, please deploy the following blueprint:
+
+## A Clustered Example
+
+We'll now look at a more complex example that better shows the capabilities of Brooklyn. 
+
+We'll start by deploying an application via YAML blueprint consisting of the following layers.
+
+- A dynamically scalable Web App Cluster
+- A MySQL DB
+
+
+Copy the blueprint below into a text file, "mycluster.yaml", in your workspace, but *before* you create an application 
+with it, again modify the YAML to specify the location where the application will be deployed.  
+You will need at least five machines for this example, one for the DB, one for the Nginx controller, and three for the tomcats 
+(but you can reduce this by changing the "maxPoolSize" below.
 
 {% highlight yaml %}
-{% readj _my-web-cluster2.yaml %}
+name: cluster
+
+location:
+  jclouds:aws-ec2:
+    identity: ABCDEFGHIJKLMNOPQRST
+    credential: s3cr3tsq1rr3ls3cr3tsq1rr3ls3cr3tsq1rr3l
+
+
+services:
+- serviceType: brooklyn.entity.webapp.ControlledDynamicWebAppCluster
+  name: webcluster
+  brooklyn.config:
+    wars.root: http://search.maven.org/remotecontent?filepath=io/brooklyn/example/brooklyn-example-hello-world-sql-webapp/0.6.0-M2/brooklyn-example-hello-world-sql-webapp-0.6.0-M2.war
+    http.port: 9280+
+    proxy.http.port: 9210+
+    java.sysprops: 
+      brooklyn.example.db.url: $brooklyn:formatString("jdbc:%s%s?user=%s\\&password=%s",
+         component("db").attributeWhenReady("datastore.url"), "visitors", "brooklyn", "br00k11n")
+  brooklyn.policies:
+  - policyType: brooklyn.policy.autoscaling.AutoScalerPolicy
+    brooklyn.config:
+      metric: $brooklyn:sensor("brooklyn.entity.webapp.DynamicWebAppCluster", "webapp.reqs.perSec.windowed.perNode")
+      metricLowerBound: 10
+      metricUpperBound: 100
+      minPoolSize: 1
+      maxPoolSize: 3
+      
+- serviceType: brooklyn.entity.database.mysql.MySqlNode
+  id: db
+  name: mysql
+  brooklyn.config:
+    creationScriptUrl: https://bit.ly/brooklyn-visitors-creation-script
 {% endhighlight %}
 
-The app server cluster has an `AutoScalerPolicy`, and the loadbalancer has a `targets` policy.
+Explore this app using the ```application``` and other commands from the previous section.
 
-Use the Applications tab in the web console to drill down into the Policies section of the ControlledDynamicWebAppCluster. You will see that the `AutoScalerPolicy` is running.
+## Configuring Dependencies
+The App above illustrates how one component in a blueprint can be configured with information relating to one of the other 
+components in the blueprint.  In this example the web cluster is configured with a URL for JDBC connections to the database.
+{% highlight yaml %}
+java.sysprops: 
+      brooklyn.example.db.url: $brooklyn:formatString("jdbc:%s%s?user=%s\\&password=%s",
+         component("db").attributeWhenReady("datastore.url"), "visitors", "brooklyn", "br00k11n")
+{% endhighlight %}
+
+the syntax ```$brooklyn:formatString(...)``` is an example of the Brooklyn DSL (Domain Specific Language) which 
+allows expressions referring to Brooklyn's management information to be embedded in blueprints.  The line above also illustrates the use of Brooklyn's ```component(...)``` and ```attributeWhenReady(...)``` to get an identified component from a deployment, and to wait until the component is fully deployed before reading one of its sensors ("datastore.url" in this case). 
+
+## Managing with Policies
 
 
-This policy automatically scales the cluster up or down to be the right size for the cluster's current load. One server is the minimum size allowed by the policy.
+The app server cluster has an `AutoScalerPolicy`and the loadbalancer has a `Controller targets tracker` policy.
 
-The loadbalancer's `targets` policy ensures that the loadbalancer is updated as the cluster size changes.
+For example
+{% highlight yaml %}
+$ br app cluster ent webcluster policy
+Id         Name                                                      State   
+mMZngBnb   org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy   RUNNING   
+{% endhighlight %}
 
-Sitting idle, this cluster will only contain one server, but you can use a tool like [jmeter](http://jmeter.apache.org/) pointed at the nginx endpoint to create load on the cluster. Download a jmeter test plan [here](https://github.com/apache/incubator-brooklyn/blob/master/examples/simple-web-cluster/resources/jmeter-test-plan.jmx).
+You can investigate the status of the `AutoScalerPolicy` with its name or, more succinctly, its ID:
 
-As load is added, Apache Brooklyn requests a new cloud machine, creates a new app server, and adds it to the cluster. As load is removed, servers are removed from the cluster, and the infrastructure is handed back to the cloud.
+{% highlight yaml %}
+$ br app cluster ent webcluster policy mMZngBnb
+"RUNNING"
+{% endhighlight %}
+
+A more detailed description of the parameters of the policy can be obtained with
+{% highlight yaml %}
+$ br app cluster ent webcluster policy org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy
+Name                                      Value                                                                Description   
+autoscaler.currentSizeOperator            org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy$4@9393100       
+autoscaler.entityWithMetric                                                                                    The Entity with the metric that will be monitored   
+autoscaler.maxPoolSize                    4                                                                       
+autoscaler.maxReachedNotificationDelay    0ms                                                                  Time that we consistently wanted to go above the maxPoolSize for, after which the maxSizeReachedSensor (if any) will be emitted   
+autoscaler.maxSizeReachedSensor                                                                                Sensor for which a notification will be emitted (on the associated entity) when we consistently wanted to resize the pool above the max allowed size, for maxReachedNotificationDelay milliseconds   
+autoscaler.metric                         Sensor: webapp.reqs.perSec.windowed.perNode (java.lang.Double)          
+autoscaler.metricLowerBound               10                                                                   The lower bound of the monitored metric. Below this the policy will resize down   
+autoscaler.metricUpperBound               100                                                                  The upper bound of the monitored metric. Above this the policy will resize up   
+autoscaler.minPeriodBetweenExecs          100ms                                                                   
+autoscaler.minPoolSize                    1                                                                       
+autoscaler.poolColdSensor                 Sensor: resizablepool.cold (java.util.Map)                              
+autoscaler.poolHotSensor                  Sensor: resizablepool.hot (java.util.Map)                               
+autoscaler.poolOkSensor                   Sensor: resizablepool.cold (java.util.Map)                              
+autoscaler.resizeDownIterationIncrement   1                                                                    Batch size for resizing down; the size will be decreased by a multiple of this value   
+autoscaler.resizeDownIterationMax         2147483647                                                           Maximum change to the size on a single iteration when scaling down   
+autoscaler.resizeDownStabilizationDelay   0ms                                                                     
+autoscaler.resizeOperator                 org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy$3@387a7e10      
+autoscaler.resizeUpIterationIncrement     1                                                                    Batch size for resizing up; the size will be increased by a multiple of this value   
+autoscaler.resizeUpIterationMax           2147483647                                                           Maximum change to the size on a single iteration when scaling up   
+autoscaler.resizeUpStabilizationDelay     0ms                                               
+{% endhighlight %}
+
+
+The loadbalancer's `Controller targets tracker` policy ensures that the loadbalancer is updated as the cluster size changes.
+
+This policy automatically scales the cluster up or down to be the right size for the cluster's current load. One server 
+is the minimum size allowed by the policy.
+
+Sitting idle, this cluster will only contain one server, but you can use a tool like [jmeter](http://jmeter.apache.org/) 
+pointed at the nginx endpoint to create load on the cluster. Download a jmeter test 
+plan [here](https://github.com/apache/incubator-brooklyn/blob/master/examples/simple-web-cluster/resources/jmeter-test-plan.jmx).
+
+As load is added, Apache Brooklyn requests a new cloud machine, creates a new app server, and adds it to the cluster. 
+As load is removed, servers are removed from the cluster, and the infrastructure is handed back to the cloud.
 
 
 ### Under the Covers
 
 The `AutoScalerPolicy` here is configured to respond to the sensor
-reporting requests per second per node, invoking the default `resize` effector.
-By clicking on the policy, you can configure it to respond to a much lower threshhold
+reporting requests per second per node, invoking the default ```resize``` effector.
+By updating on the policy, you can configure it to respond to a much lower threshhold
 or set long stabilization delays (the period before it scales out or back).
 
-An even simpler test is to manually suspend the policy, by clicking "Suspend" in the policies list.
-You can then switch to the "Effectors" tab and manually trigger a `resize`.
+At present the CLI does not support a command to update a policy.
+
+However it is possible to manually suspend the policy, by 
+
+{% highlight bash %}
+$ br app cluster ent webcluster stop-policy org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy
+
+{% endhighlight %}
+
+You can then invoke a ```resize``` using the appropriate effector:
+{% highlight bash %}
+$ br app cluster ent webcluster effector resize invoke -P desiredSize=3
+{% endhighlight %}
+
 On resize, new nodes are created and configured, 
 and in this case a policy on the nginx node reconfigures nginx whenever the set of active
 targets changes.
 
+The policy can then be re-enabled with start-policy:
 
-### Next
+{% highlight bash %}
+$ br app cluster ent webcluster start-policy org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy
+
+{% endhighlight %}
+
+
+## Next
 
 This guide has given a quick overview to writing blueprints for applications, deploying applications, and
 managing them. Next, learn more about any of:
 
 * [Writing Blueprints with YAML](../yaml/) 
 * [Writing Blueprints with Java](../java/) 
-* [Operating Brooklyn](../ops/) 
-
+* [Operating Brooklyn](../ops/) 
\ No newline at end of file
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-docs/guide/yaml/winrm/index.md b/brooklyn-docs/guide/yaml/winrm/index.md
index 959f88c..a787aa0 100644
--- a/brooklyn-docs/guide/yaml/winrm/index.md
+++ b/brooklyn-docs/guide/yaml/winrm/index.md
@@ -250,6 +250,16 @@
 
     install.command: $brooklyn:formatString("c:\\myscript.bat %s", component("db").attributeWhenReady("datastore.url"))
 
+### Powershell - Using Start-Process
+
+When you are invoking a command from a powershell script with `Start-Process` cmdlet,
+please use the `-Wait` and the `-PassThru` arguments.
+Example `Start-Process C:\mycommand -Wait -PassThru`
+
+Using `-Wait` guarantees that the script process and its children and thus the winrm session won't be terminated until it is finished.
+`-PassThru` Returns a process object for each process that the cmdlet started. By default, this cmdlet does not generate any output.
+See https://technet.microsoft.com/en-us/library/hh849848.aspx
+
 ### Rebooting
 
 Where a reboot is required as part of the entity setup, this can be configured using
@@ -378,6 +388,12 @@
 This could cause the WinRM connection attempts to timeout. The location configuration option 
 `waitForWinRmAvailable` defaults to `30m` (i.e. 30 minutes). This can be increased if required.
 
+Incorrectly prepared Windows template can cause the deployment to time-out expecting an interaction by the user.
+You can verify if this is the case by RDP to the deployment which is taking to much time to complete. 
+It is recommended to manually deploy a single VM for every newly created Windows template to verify that it can be
+used for unattended installations and it doesn't wait and/or require an input by the user.
+See [Windows template settings for an Unattended Installation](#windows-template-settings-for-an-unattended-installation) under Known Limitations below. 
+
 ### Windows log files
 
 Details of the commands executed, and their results, can be found in the Brooklyn log and in the Brooklyn 
@@ -499,3 +515,12 @@
 
 Blueprint authors are strongly encourages to explicitly specific directories for file
 uploads and in their Powershell scripts.
+
+### Windows template settings for an Unattended Installation
+
+Windows template needs certain configuration to be applied to prevent windows setup UI from being displayed.
+The default behavior is to display it if there are incorrect or empty settings. Showing Setup UI will prevent the proper
+deployment, because it will expect interaction by the user such as agreeing on the license agreement or some of the setup dialogs.
+
+Detailed instruction how to prepare an Unattended installation are provided at [https://technet.microsoft.com/en-us/library/cc722411%28v=ws.10%29.aspx](https://technet.microsoft.com/en-us/library/cc722411%28v=ws.10%29.aspx).
+
diff --git a/brooklyn-docs/website/index.md b/brooklyn-docs/website/index.md
index 1312fdc..e419488 100644
--- a/brooklyn-docs/website/index.md
+++ b/brooklyn-docs/website/index.md
@@ -5,7 +5,7 @@
 children:
 - learnmore/
 - { path: download/, menu: null }
-- { path: /guide/start/index.md, title_in_menu: Get Started, href_path: /guide/start/running.md}
+- { path: /guide/start/index.md, title_in_menu: Get Started, href_path: /guide/start/index.md}
 - path: documentation/
   menu:
   - { path: /guide/index.md, title_in_menu: "User Guide", 
diff --git a/brooklyn-library/software/database/src/main/java/org/apache/brooklyn/entity/database/mysql/MySqlNode.java b/brooklyn-library/software/database/src/main/java/org/apache/brooklyn/entity/database/mysql/MySqlNode.java
index 6306956..5003d4d 100644
--- a/brooklyn-library/software/database/src/main/java/org/apache/brooklyn/entity/database/mysql/MySqlNode.java
+++ b/brooklyn-library/software/database/src/main/java/org/apache/brooklyn/entity/database/mysql/MySqlNode.java
@@ -75,6 +75,9 @@
     @SetFromFlag("socketUid")
     StringAttributeSensorAndConfigKey SOCKET_UID = new StringAttributeSensorAndConfigKey(
             "mysql.socketUid", "Socket uid, for use in file /tmp/mysql.sock.<uid>.3306 (or randomly generated if not set)", null);
+
+    @SetFromFlag("generalLog")
+    ConfigKey GENERAL_LOG = ConfigKeys.newBooleanConfigKey("mysql.general_log", "Enable general log", false);
     
     /** @deprecated since 0.7.0 use DATASTORE_URL */ @Deprecated
     AttributeSensor<String> MYSQL_URL = DATASTORE_URL;
diff --git a/brooklyn-library/software/database/src/main/resources/org/apache/brooklyn/entity/database/mysql/mysql.conf b/brooklyn-library/software/database/src/main/resources/org/apache/brooklyn/entity/database/mysql/mysql.conf
index 85f55ab..40b4086 100644
--- a/brooklyn-library/software/database/src/main/resources/org/apache/brooklyn/entity/database/mysql/mysql.conf
+++ b/brooklyn-library/software/database/src/main/resources/org/apache/brooklyn/entity/database/mysql/mysql.conf
@@ -13,7 +13,15 @@
 basedir         = ${driver.baseDir}
 datadir         = ${driver.dataDir}
 bind-address    = 0.0.0.0
+log_error       = ${driver.runDir}/mysql_error_${entity.getId()}.log
+log_warnings    = 2
+general_log      = ${config["mysql.general_log"]?string('on','off')}
+general_log_file = ${driver.runDir}/mysql_general_${entity.getId()}.log
 # skip-networking
 
+#Prevent the GRANT statement from automatically creating new user accounts if it would otherwise do so,
+#unless authentication information is specified
+sql_mode = NO_AUTO_CREATE_USER
+
 # Custom configuration options
 ${driver.mySqlServerOptionsString}
\ No newline at end of file
diff --git a/brooklyn-library/software/database/src/main/resources/org/apache/brooklyn/entity/database/mysql/mysql_master.conf b/brooklyn-library/software/database/src/main/resources/org/apache/brooklyn/entity/database/mysql/mysql_master.conf
index 791f2da..54a773b 100644
--- a/brooklyn-library/software/database/src/main/resources/org/apache/brooklyn/entity/database/mysql/mysql_master.conf
+++ b/brooklyn-library/software/database/src/main/resources/org/apache/brooklyn/entity/database/mysql/mysql_master.conf
@@ -15,6 +15,10 @@
 bind-address    = 0.0.0.0
 # skip-networking
 
+#Prevent the GRANT statement from automatically creating new user accounts if it would otherwise do so,
+#unless authentication information is specified
+sql_mode = NO_AUTO_CREATE_USER
+
 # Replication config
 server-id       = 1
 binlog-format   = mixed
diff --git a/brooklyn-library/software/database/src/main/resources/org/apache/brooklyn/entity/database/mysql/mysql_slave.conf b/brooklyn-library/software/database/src/main/resources/org/apache/brooklyn/entity/database/mysql/mysql_slave.conf
index 1c69423..b4af02a 100644
--- a/brooklyn-library/software/database/src/main/resources/org/apache/brooklyn/entity/database/mysql/mysql_slave.conf
+++ b/brooklyn-library/software/database/src/main/resources/org/apache/brooklyn/entity/database/mysql/mysql_slave.conf
@@ -16,6 +16,10 @@
 bind-address    = 0.0.0.0
 # skip-networking
 
+#Prevent the GRANT statement from automatically creating new user accounts if it would otherwise do so,
+#unless authentication information is specified
+sql_mode = NO_AUTO_CREATE_USER
+
 # Replication config
 server-id       = ${config["mysql.server_id"]}
 relay-log       = mysql-slave-${config["mysql.server_id"]}-relay
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/DslAndRebindYamlTest.java b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/DslAndRebindYamlTest.java
index 31a7951..354e0a0 100644
--- a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/DslAndRebindYamlTest.java
+++ b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/DslAndRebindYamlTest.java
@@ -19,58 +19,82 @@
 package org.apache.brooklyn.camp.brooklyn;
 
 import java.io.File;
+import java.util.List;
 import java.util.Set;
 import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import org.apache.brooklyn.api.entity.Application;
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.mgmt.ha.MementoCopyMode;
+import org.apache.brooklyn.api.mgmt.rebind.mementos.BrooklynMementoRawData;
 import org.apache.brooklyn.api.sensor.AttributeSensor;
 import org.apache.brooklyn.api.sensor.Sensor;
+import org.apache.brooklyn.camp.brooklyn.spi.dsl.BrooklynDslDeferredSupplier;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.entity.Attributes;
 import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.EntityAsserts;
 import org.apache.brooklyn.core.entity.EntityInternal;
 import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.mgmt.persist.BrooklynPersistenceUtils;
 import org.apache.brooklyn.core.mgmt.rebind.RebindTestUtils;
 import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.core.test.entity.TestEntity;
-import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.entity.group.DynamicCluster;
 import org.apache.brooklyn.util.collections.MutableSet;
 import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.guava.Maybe;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
 import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
 import com.google.common.io.Files;
 
 @Test
 public class DslAndRebindYamlTest extends AbstractYamlTest {
-    
+
     private static final Logger log = LoggerFactory.getLogger(DslAndRebindYamlTest.class);
-    
+
     protected ClassLoader classLoader = getClass().getClassLoader();
     protected File mementoDir;
     protected Set<ManagementContext> mgmtContexts = MutableSet.of();
+    protected ExecutorService executor;
 
     @Override
     protected LocalManagementContext newTestManagementContext() {
-        if (mementoDir!=null) throw new IllegalStateException("already created mgmt context");
+        if (mementoDir != null) throw new IllegalStateException("already created mgmt context");
         mementoDir = Files.createTempDir();
         mementoDir.deleteOnExit();
         LocalManagementContext mgmt = RebindTestUtils.newPersistingManagementContext(mementoDir, classLoader, 1);
         mgmtContexts.add(mgmt);
         return mgmt;
     }
+
+    @BeforeMethod(alwaysRun = true)
+    @Override
+    public void setUp() {
+    	super.setUp();
+        executor = Executors.newSingleThreadExecutor();
+    }
     
     @AfterMethod(alwaysRun = true)
     @Override
     public void tearDown() {
-        for (ManagementContext mgmt: mgmtContexts) Entities.destroyAll(mgmt);
+    	if (executor != null) executor.shutdownNow();
+        for (ManagementContext mgmt : mgmtContexts) Entities.destroyAll(mgmt);
         super.tearDown();
         mementoDir = null;
         mgmtContexts.clear();
@@ -83,15 +107,13 @@
 
     public Application rebind(Application app) throws Exception {
         RebindTestUtils.waitForPersisted(app);
-        // not strictly needed, but for good measure:
-        RebindTestUtils.checkCurrentMementoSerializable(app);
         Application result = RebindTestUtils.rebind(mementoDir, getClass().getClassLoader());
         mgmtContexts.add(result.getManagementContext());
         return result;
     }
 
 
-    protected Entity setupAndCheckTestEntityInBasicYamlWith(String ...extras) throws Exception {
+    protected Entity setupAndCheckTestEntityInBasicYamlWith(String... extras) throws Exception {
         Entity app = createAndStartApplication(loadYaml("test-entity-basic-template.yaml", extras));
         waitForApplicationTasks(app);
 
@@ -99,11 +121,11 @@
 
         log.info("App started:");
         Entities.dumpInfo(app);
-        
+
         Assert.assertTrue(app.getChildren().iterator().hasNext(), "Expected app to have child entity");
         Entity entity = app.getChildren().iterator().next();
         Assert.assertTrue(entity instanceof TestEntity, "Expected TestEntity, found " + entity.getClass());
-        
+
         return entity;
     }
 
@@ -115,34 +137,210 @@
             }
         }).build()).getUnchecked();
     }
-    
+
+    protected <T> Future<T> getConfigInTaskAsync(final Entity entity, final ConfigKey<T> key) {
+	    // Wait for the attribute to be ready in a new Task
+	    Callable<T> configGetter = new Callable<T>() {
+	        @Override
+	        public T call() throws Exception {
+	            T s = getConfigInTask(entity, key);
+	            getLogger().info("getConfig {}={}", key, s);
+	            return s;
+	        }
+	    };
+	    return executor.submit(configGetter);
+    }
+
     @Test
     public void testDslAttributeWhenReady() throws Exception {
         Entity testEntity = entityWithAttributeWhenReady();
-        ((EntityInternal)testEntity).sensors().set(Sensors.newStringSensor("foo"), "bar");
+        ((EntityInternal) testEntity).sensors().set(Sensors.newStringSensor("foo"), "bar");
         Assert.assertEquals(getConfigInTask(testEntity, TestEntity.CONF_NAME), "bar");
     }
 
     @Test
-    public void testDslAttributeWhenReadyRebind() throws Exception {
+    public void testDslAttributeWhenReadyRebindWhenResolved() throws Exception {
         Entity testEntity = entityWithAttributeWhenReady();
-        ((EntityInternal)testEntity).sensors().set(Sensors.newStringSensor("foo"), "bar");
-        Application app2 = rebind(testEntity.getApplication());
-        Entity e2 = Iterables.getOnlyElement( app2.getChildren() );
+        ((EntityInternal) testEntity).sensors().set(Sensors.newStringSensor("foo"), "bar");
         
+        Application app2 = rebind(testEntity.getApplication());
+        Entity e2 = Iterables.getOnlyElement(app2.getChildren());
+
         Assert.assertEquals(getConfigInTask(e2, TestEntity.CONF_NAME), "bar");
     }
 
+    @Test
+    public void testDslAttributeWhenReadyWhenNotYetResolved() throws Exception {
+        Entity testEntity = entityWithAttributeWhenReady();
+        
+        Application app2 = rebind(testEntity.getApplication());
+        Entity e2 = Iterables.getOnlyElement(app2.getChildren());
+
+        // Wait for the attribute to be ready in a new Task
+        Future<String> stringFuture = getConfigInTaskAsync(e2, TestEntity.CONF_NAME);
+
+        // Check that the Task is still waiting for attribute to be ready
+        Assert.assertFalse(stringFuture.isDone());
+
+        // Set the sensor; expect that to complete
+        e2.sensors().set(Sensors.newStringSensor("foo"), "bar");
+        String s = stringFuture.get(10, TimeUnit.SECONDS); // Timeout just for sanity
+        Assert.assertEquals(s, "bar");
+    }
+
+    @Test
+    public void testDslAttributeWhenReadyPersistedAsDeferredSupplier() throws Exception {
+    	doDslAttributeWhenReadyPersistedAsDeferredSupplier(false);
+    }
+    
+    @Test
+    public void testDslAttributeWhenReadyPersistedWithoutLeakingResolvedValue() throws Exception {
+    	doDslAttributeWhenReadyPersistedAsDeferredSupplier(true);
+    }
+    
+    protected void doDslAttributeWhenReadyPersistedAsDeferredSupplier(boolean resolvedBeforeRebind) throws Exception {
+        Entity testEntity = entityWithAttributeWhenReady();
+        
+        if (resolvedBeforeRebind) {
+        	testEntity.sensors().set(Sensors.newStringSensor("foo"), "bar");
+        	Assert.assertEquals(getConfigInTask(testEntity, TestEntity.CONF_NAME), "bar");
+        }
+        
+        // Persist and rebind
+        Application app2 = rebind(testEntity.getApplication());
+        Entity e2 = Iterables.getOnlyElement(app2.getChildren());
+
+        Maybe<Object> maybe = ((EntityInternal) e2).config().getLocalRaw(TestEntity.CONF_NAME);
+        Assert.assertTrue(maybe.isPresentAndNonNull());
+        Assert.assertTrue(BrooklynDslDeferredSupplier.class.isInstance(maybe.get()));
+        BrooklynDslDeferredSupplier<?> deferredSupplier = (BrooklynDslDeferredSupplier<?>) maybe.get();
+        Assert.assertEquals(deferredSupplier.toString(), "$brooklyn:entity(\"x\").attributeWhenReady(\"foo\")");
+
+        // Assert the persisted state itself is as expected, and not too big
+        BrooklynMementoRawData raw = BrooklynPersistenceUtils.newStateMemento(app2.getManagementContext(), MementoCopyMode.LOCAL);
+        String persistedStateForE2 = raw.getEntities().get(e2.getId());
+        Matcher matcher = Pattern.compile(".*\\<test.confName\\>(.*)\\<\\/test.confName\\>.*", Pattern.DOTALL)
+                .matcher(persistedStateForE2);
+        Assert.assertTrue(matcher.find());
+        String testConfNamePersistedState = matcher.group(1);
+
+        Assert.assertNotNull(testConfNamePersistedState);
+        // should be about 200 chars long, something like:
+        //
+        //      <test.confName>
+        //        <org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent_-AttributeWhenReady>
+        //          <component>
+        //            <componentId>x</componentId>
+        //            <scope>GLOBAL</scope>
+        //          </component>
+        //          <sensorName>foo</sensorName>
+        //        </org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent_-AttributeWhenReady>
+        //      </test.confName>
+
+        Assert.assertTrue(testConfNamePersistedState.length() < 400, "persisted state too long: " + testConfNamePersistedState);
+        
+        Assert.assertFalse(testConfNamePersistedState.contains("bar"), "value 'bar' leaked in persisted state");
+    }
+
+    @Test
+    public void testDslAttributeWhenReadyInEntitySpecWhenNotYetResolved() throws Exception {
+    	doDslAttributeWhenReadyInEntitySpec(false);
+    }
+    
+    @Test
+    public void testDslAttributeWhenReadyInEntitySpecWhenAlreadyResolved() throws Exception {
+    	doDslAttributeWhenReadyInEntitySpec(true);
+    }
+    
+    protected void doDslAttributeWhenReadyInEntitySpec(boolean resolvedBeforeRebind) throws Exception {
+        String yaml = "location: localhost\n" +
+                "name: Test Cluster\n" +
+                "services:\n" +
+                "- type: org.apache.brooklyn.entity.group.DynamicCluster\n" +
+                "  id: test-cluster\n" +
+                "  initialSize: 0\n" +
+                "  memberSpec:\n" +
+                "    $brooklyn:entitySpec:\n" +
+                "      type: org.apache.brooklyn.core.test.entity.TestEntity\n" +
+                "      brooklyn.config:\n" +
+                "        test.confName: $brooklyn:component(\"test-cluster\").attributeWhenReady(\"sensor\")";
+
+        final Entity testEntity = createAndStartApplication(yaml);
+        DynamicCluster cluster = (DynamicCluster) Iterables.getOnlyElement(testEntity.getApplication().getChildren());
+        cluster.resize(1);
+        Assert.assertEquals(cluster.getMembers().size(), 1);
+
+        if (resolvedBeforeRebind) {
+            cluster.sensors().set(Sensors.newStringSensor("sensor"), "bar");
+        }
+
+        // Persist and rebind
+        Application app2 = rebind(cluster.getApplication());
+        DynamicCluster cluster2 = (DynamicCluster) Iterables.getOnlyElement(app2.getApplication().getChildren());
+
+        // Assert the persisted state itself is as expected, and not too big
+        BrooklynMementoRawData raw = BrooklynPersistenceUtils.newStateMemento(app2.getManagementContext(), MementoCopyMode.LOCAL);
+        String persistedStateForE2 = raw.getEntities().get(cluster2.getId());
+        String expectedTag = "org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent_-AttributeWhenReady";
+        Matcher matcher = Pattern.compile(".*\\<"+expectedTag+"\\>(.*)\\<\\/"+expectedTag+"\\>.*", Pattern.DOTALL)
+                .matcher(persistedStateForE2);
+        Assert.assertTrue(matcher.find(), persistedStateForE2);
+        String testConfNamePersistedState = matcher.group(1);
+        Assert.assertNotNull(testConfNamePersistedState);
+
+        // Can re-size to create a new member entity
+        cluster2.resize(2);
+        Assert.assertEquals(cluster2.getMembers().size(), 2);
+        
+        // Both the existing and the new member should have the DeferredSupplier config
+        for (Entity member : Iterables.filter(cluster2.getChildren(), TestEntity.class)) {
+	        Maybe<Object> maybe = ((EntityInternal)member).config().getLocalRaw(TestEntity.CONF_NAME);
+	        Assert.assertTrue(maybe.isPresentAndNonNull());
+	        BrooklynDslDeferredSupplier<?> deferredSupplier = (BrooklynDslDeferredSupplier<?>) maybe.get();
+	        Assert.assertEquals(deferredSupplier.toString(), "$brooklyn:entity(\"test-cluster\").attributeWhenReady(\"sensor\")");
+        }
+        
+        if (resolvedBeforeRebind) {
+            // All members should resolve their config
+            for (Entity member : Iterables.filter(cluster2.getChildren(), TestEntity.class)) {
+		        String val = getConfigInTask(member, TestEntity.CONF_NAME);
+		        Assert.assertEquals(val, "bar");
+            }
+        } else {
+        	List<Future<String>> futures = Lists.newArrayList();
+        	
+            // All members should have unresolved values
+            for (Entity member : Iterables.filter(cluster2.getChildren(), TestEntity.class)) {
+		        // Wait for the attribute to be ready in a new Task
+		        Future<String> stringFuture = getConfigInTaskAsync(member, TestEntity.CONF_NAME);
+		        futures.add(stringFuture);
+		        
+		        // Check that the Task is still waiting for attribute to be ready
+		        Thread.sleep(100);
+		        Assert.assertFalse(stringFuture.isDone());
+            }
+            
+            // After setting the sensor, all those values should now resolve
+	        cluster2.sensors().set(Sensors.newStringSensor("sensor"), "bar");
+	        
+	        for (Future<String> future : futures) {
+		        String s = future.get(10, TimeUnit.SECONDS); // Timeout just for sanity
+		        Assert.assertEquals(s, "bar");
+            }
+        }
+    }
+
     private Entity entityWithAttributeWhenReady() throws Exception {
-        return setupAndCheckTestEntityInBasicYamlWith( 
-            "  id: x",
-            "  brooklyn.config:",
-            "    test.confName: $brooklyn:component(\"x\").attributeWhenReady(\"foo\")");
+        return setupAndCheckTestEntityInBasicYamlWith(
+                "  id: x",
+                "  brooklyn.config:",
+                "    test.confName: $brooklyn:component(\"x\").attributeWhenReady(\"foo\")");
     }
 
     private void doTestOnEntityWithSensor(Entity testEntity, Sensor<?> expectedSensor) throws Exception {
         doTestOnEntityWithSensor(testEntity, expectedSensor, true);
     }
+
     private void doTestOnEntityWithSensor(Entity testEntity, Sensor<?> expectedSensor, boolean inTask) throws Exception {
         @SuppressWarnings("rawtypes")
         ConfigKey<Sensor> configKey = ConfigKeys.newConfigKey(Sensor.class, "test.sensor");
@@ -150,50 +348,52 @@
         s = inTask ? getConfigInTask(testEntity, configKey) : testEntity.getConfig(configKey);
         Assert.assertEquals(s, expectedSensor);
         Application app2 = rebind(testEntity.getApplication());
-        Entity te2 = Iterables.getOnlyElement( app2.getChildren() );
+        Entity te2 = Iterables.getOnlyElement(app2.getChildren());
         s = inTask ? getConfigInTask(te2, configKey) : te2.getConfig(configKey);
         Assert.assertEquals(s, expectedSensor);
     }
-    
+
     @Test
     public void testDslSensorFromClass() throws Exception {
         doTestOnEntityWithSensor(entityWithSensorFromClass(), Attributes.SERVICE_UP);
         // without context it can still find it
         doTestOnEntityWithSensor(entityWithSensorFromClass(), Attributes.SERVICE_UP, false);
     }
+
     @Test
     public void testDslSensorLocal() throws Exception {
         doTestOnEntityWithSensor(entityWithSensorLocal(), TestEntity.SEQUENCE);
-        // here without context it makes one up, so type info (and description etc) not present; 
+        // here without context it makes one up, so type info (and description etc) not present;
         // but context is needed to submit the DslDeferredSupplier object, so this would fail
 //        doTestOnEntityWithSensor(entityWithSensorAdHoc(), Sensors.newSensor(Object.class, TestEntity.SEQUENCE.getName()), false);
     }
+
     @Test
     public void testDslSensorAdHoc() throws Exception {
         doTestOnEntityWithSensor(entityWithSensorAdHoc(), Sensors.newSensor(Object.class, "sensor.foo"));
         // here context has no impact, but it is needed to submit the DslDeferredSupplier object so this would fail
 //        doTestOnEntityWithSensor(entityWithSensorAdHoc(), Sensors.newSensor(Object.class, "sensor.foo"), false);
     }
-    
+
     private Entity entityWithSensorFromClass() throws Exception {
-        return setupAndCheckTestEntityInBasicYamlWith( 
-            "  id: x",
-            "  brooklyn.config:",
-            "    test.sensor: $brooklyn:sensor(\""+Attributes.class.getName()+"\", \""+Attributes.SERVICE_UP.getName()+"\")");
+        return setupAndCheckTestEntityInBasicYamlWith(
+                "  id: x",
+                "  brooklyn.config:",
+                "    test.sensor: $brooklyn:sensor(\"" + Attributes.class.getName() + "\", \"" + Attributes.SERVICE_UP.getName() + "\")");
     }
 
     private Entity entityWithSensorLocal() throws Exception {
-        return setupAndCheckTestEntityInBasicYamlWith( 
-            "  id: x",
-            "  brooklyn.config:",
-            "    test.sensor: $brooklyn:sensor(\""+TestEntity.SEQUENCE.getName()+"\")");
+        return setupAndCheckTestEntityInBasicYamlWith(
+                "  id: x",
+                "  brooklyn.config:",
+                "    test.sensor: $brooklyn:sensor(\"" + TestEntity.SEQUENCE.getName() + "\")");
     }
 
     private Entity entityWithSensorAdHoc() throws Exception {
-        return setupAndCheckTestEntityInBasicYamlWith( 
-            "  id: x",
-            "  brooklyn.config:",
-            "    test.sensor: $brooklyn:sensor(\"sensor.foo\")");
+        return setupAndCheckTestEntityInBasicYamlWith(
+                "  id: x",
+                "  brooklyn.config:",
+                "    test.sensor: $brooklyn:sensor(\"sensor.foo\")");
     }
 
 
@@ -207,18 +407,18 @@
     public void testDslConfigFromRootRebind() throws Exception {
         Entity testEntity = entityWithConfigFromRoot();
         Application app2 = rebind(testEntity.getApplication());
-        Entity e2 = Iterables.getOnlyElement( app2.getChildren() );
-        
+        Entity e2 = Iterables.getOnlyElement(app2.getChildren());
+
         Assert.assertEquals(getConfigInTask(e2, TestEntity.CONF_NAME), "bar");
     }
 
     private Entity entityWithConfigFromRoot() throws Exception {
-        return setupAndCheckTestEntityInBasicYamlWith( 
-            "  id: x",
-            "  brooklyn.config:",
-            "    test.confName: $brooklyn:component(\"x\").config(\"foo\")",
-            "brooklyn.config:",
-            "  foo: bar");
+        return setupAndCheckTestEntityInBasicYamlWith(
+                "  id: x",
+                "  brooklyn.config:",
+                "    test.confName: $brooklyn:component(\"x\").config(\"foo\")",
+                "brooklyn.config:",
+                "  foo: bar");
     }
 
 
@@ -232,16 +432,16 @@
     public void testDslFormatStringRebind() throws Exception {
         Entity testEntity = entityWithFormatString();
         Application app2 = rebind(testEntity.getApplication());
-        Entity e2 = Iterables.getOnlyElement( app2.getChildren() );
-        
+        Entity e2 = Iterables.getOnlyElement(app2.getChildren());
+
         Assert.assertEquals(getConfigInTask(e2, TestEntity.CONF_NAME), "hello world");
     }
 
     private Entity entityWithFormatString() throws Exception {
-        return setupAndCheckTestEntityInBasicYamlWith( 
-            "  id: x",
-            "  brooklyn.config:",
-            "    test.confName: $brooklyn:formatString(\"hello %s\", \"world\")");
+        return setupAndCheckTestEntityInBasicYamlWith(
+                "  id: x",
+                "  brooklyn.config:",
+                "    test.confName: $brooklyn:formatString(\"hello %s\", \"world\")");
     }
 
 
@@ -292,7 +492,7 @@
         );
         testEntity.sensors().set(TestEntity.NAME, "somefooname");
         AttributeSensor<String> transformedSensor = Sensors.newStringSensor("test.name.transformed");
-        EntityTestUtils.assertAttributeEqualsEventually(testEntity, transformedSensor, "somebarname");
+        EntityAsserts.assertAttributeEqualsEventually(testEntity, transformedSensor, "somebarname");
     }
 
     @Test
@@ -309,7 +509,7 @@
         testEntity.sensors().set(Sensors.newStringSensor("test.replacement"), "bar");
         testEntity.sensors().set(TestEntity.NAME, "somefooname");
         AttributeSensor<String> transformedSensor = Sensors.newStringSensor("test.name.transformed");
-        EntityTestUtils.assertAttributeEqualsEventually(testEntity, transformedSensor, "somebarname");
+        EntityAsserts.assertAttributeEqualsEventually(testEntity, transformedSensor, "somebarname");
     }
 
 }
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..337c302 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
@@ -53,6 +53,7 @@
 import org.apache.brooklyn.core.test.entity.TestEntity;
 import org.apache.brooklyn.core.test.entity.TestEntityImpl;
 import org.apache.brooklyn.entity.group.DynamicCluster;
+import org.apache.brooklyn.entity.group.DynamicFabric;
 import org.apache.brooklyn.entity.software.base.SameServerEntity;
 import org.apache.brooklyn.entity.stock.BasicEntity;
 import org.apache.brooklyn.util.collections.MutableMap;
@@ -60,6 +61,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;
@@ -67,6 +70,7 @@
 import org.testng.annotations.Test;
 import org.testng.collections.Lists;
 
+import com.google.common.base.Joiner;
 import com.google.common.base.Suppliers;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -100,7 +104,6 @@
         setupAndCheckTestEntityInBasicYamlWith();
     }
 
-    @SuppressWarnings("unchecked")
     @Test
     public void testBrooklynConfig() throws Exception {
         Entity testEntity = setupAndCheckTestEntityInBasicYamlWith( 
@@ -113,6 +116,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 +134,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 +605,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 +644,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 +680,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");
@@ -694,6 +728,48 @@
     }
 
     @Test
+    public void testCreateFabricWithLocationsAtTopLevel() throws Exception {
+        String yaml = Joiner.on("\n").join(
+                "services:",
+                "- type: org.apache.brooklyn.entity.group.DynamicFabric",
+                "  memberSpec:",
+                "    $brooklyn:entitySpec:",
+                "      type: org.apache.brooklyn.core.test.entity.TestEntity",
+                "locations:",
+                "- byon(hosts=\"1.1.1.1\")",
+                "- byon(hosts=\"1.1.1.2\")"
+                );
+
+        Entity app = createAndStartApplication(yaml);
+        waitForApplicationTasks(app);
+        DynamicFabric fabric = Iterables.getOnlyElement(Entities.descendants(app, DynamicFabric.class));
+        Iterable<TestEntity> members = Entities.descendants(fabric, TestEntity.class);
+        
+        assertEquals(Iterables.size(members), 2);
+    }
+
+    @Test
+    public void testCreateFabricWithLocationsInline() throws Exception {
+        String yaml = Joiner.on("\n").join(
+                "services:",
+                "- type: org.apache.brooklyn.entity.group.DynamicFabric",
+                "  memberSpec:",
+                "    $brooklyn:entitySpec:",
+                "      type: org.apache.brooklyn.core.test.entity.TestEntity",
+                "  locations:",
+                "  - byon(hosts=\"1.1.1.1\")",
+                "  - byon(hosts=\"1.1.1.2\")"
+                );
+
+        Entity app = createAndStartApplication(yaml);
+        waitForApplicationTasks(app);
+        DynamicFabric fabric = Iterables.getOnlyElement(Entities.descendants(app, DynamicFabric.class));
+        Iterable<TestEntity> members = Entities.descendants(fabric, TestEntity.class);
+        
+        assertEquals(Iterables.size(members), 2);
+    }
+
+    @Test
     public void testEntitySpecConfig() throws Exception {
         String yaml =
                 "services:\n"+
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/BrooklynFeatureEnablement.java b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/BrooklynFeatureEnablement.java
index e2fe918..1fb4767 100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/BrooklynFeatureEnablement.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/BrooklynFeatureEnablement.java
@@ -186,7 +186,7 @@
         }
     }
     
-    static void setDefault(String property, boolean val) {
+    public static void setDefault(String property, boolean val) {
         synchronized (MUTEX) {
             if (!FEATURE_ENABLEMENTS.containsKey(property)) {
                 String rawVal = System.getProperty(property);
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/config/external/vault/VaultAppIdExternalConfigSupplier.java b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/config/external/vault/VaultAppIdExternalConfigSupplier.java
index 84d333d..c71d57a 100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/config/external/vault/VaultAppIdExternalConfigSupplier.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/config/external/vault/VaultAppIdExternalConfigSupplier.java
@@ -38,7 +38,7 @@
 
     private static final Logger LOG = LoggerFactory.getLogger(VaultAppIdExternalConfigSupplier.class);
 
-    protected VaultAppIdExternalConfigSupplier(ManagementContext managementContext, String name, Map<String, String> config) {
+    public VaultAppIdExternalConfigSupplier(ManagementContext managementContext, String name, Map<String, String> config) {
         super(managementContext, name, config);
     }
 
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/DynamicFabricImpl.java b/brooklyn-server/core/src/main/java/org/apache/brooklyn/entity/group/DynamicFabricImpl.java
index 67c4c79..92bf5d2 100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/entity/group/DynamicFabricImpl.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/entity/group/DynamicFabricImpl.java
@@ -18,11 +18,13 @@
  */
 package org.apache.brooklyn.entity.group;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static org.apache.brooklyn.util.groovy.GroovyJavaMethods.elvis;
 import static org.apache.brooklyn.util.groovy.GroovyJavaMethods.truth;
 
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutionException;
@@ -41,6 +43,7 @@
 import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
 import org.apache.brooklyn.core.entity.trait.Changeable;
 import org.apache.brooklyn.core.entity.trait.Startable;
+import org.apache.brooklyn.core.location.Locations;
 import org.apache.brooklyn.enricher.stock.Enrichers;
 import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.exceptions.Exceptions;
@@ -112,9 +115,10 @@
         if (locsO!=null) {
             addLocations(locsO);
         }
-        
+        Collection<Location> locs = Collections.unmodifiableCollection(Locations.getLocationsCheckingAncestors(getLocations(), this));
+
         List<Location> newLocations = MutableList.copyOf(locsO);
-        if (newLocations.isEmpty()) newLocations.addAll(getLocations());
+        if (newLocations.isEmpty()) newLocations.addAll(locs);
         
         Preconditions.checkNotNull(newLocations, "locations must be supplied");
         Preconditions.checkArgument(newLocations.size() >= 1, "One or more locations must be supplied");
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..c497ee4 100644
--- a/brooklyn-server/pom.xml
+++ b/brooklyn-server/pom.xml
@@ -90,7 +90,7 @@
         <coverage.target>${working.dir}</coverage.target>
 
 		<!-- Dependency Versions -->
-        <jclouds.version>1.9.1</jclouds.version> <!-- JCLOUDS_VERSION -->
+        <jclouds.version>1.9.2</jclouds.version> <!-- JCLOUDS_VERSION -->
         <logback.version>1.0.7</logback.version>
         <slf4j.version>1.6.6</slf4j.version>  <!-- used for java.util.logging jul-to-slf4j interception -->
         <guava.version>17.0</guava.version>
@@ -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/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessDriver.java b/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessDriver.java
index 59dac4f..d243833 100644
--- a/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessDriver.java
+++ b/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessDriver.java
@@ -107,23 +107,22 @@
             skipStart = entityStarted.or(false);
         }
         if (!skipStart) {
-            DynamicTasks.queue("copy-pre-install-resources", new Runnable() { public void run() {
-                waitForConfigKey(BrooklynConfigKeys.PRE_INSTALL_RESOURCES_LATCH);
-                copyPreInstallResources();
-            }});
-
-            DynamicTasks.queue("pre-install", new Runnable() { public void run() {
-                preInstall();
-            }});
-
-            DynamicTasks.queue("pre-install-command", new Runnable() { public void run() {
-                runPreInstallCommand();
-            }});
-
             Optional<Boolean> locationInstalled = Optional.fromNullable(getLocation().getConfig(BrooklynConfigKeys.SKIP_ENTITY_INSTALLATION));
             Optional<Boolean> entityInstalled = Optional.fromNullable(entity.getConfig(BrooklynConfigKeys.SKIP_ENTITY_INSTALLATION));
             boolean skipInstall = locationInstalled.or(entityInstalled).or(false);
             if (!skipInstall) {
+                DynamicTasks.queue("copy-pre-install-resources", new Runnable() { public void run() {
+                    waitForConfigKey(BrooklynConfigKeys.PRE_INSTALL_RESOURCES_LATCH);
+                    copyPreInstallResources();
+                }});
+
+                DynamicTasks.queue("pre-install", new Runnable() { public void run() {
+                    preInstall();
+                }});
+
+                DynamicTasks.queue("pre-install-command", new Runnable() { public void run() {
+                    runPreInstallCommand();
+                }});
                 DynamicTasks.queue("setup", new Runnable() { public void run() {
                     waitForConfigKey(BrooklynConfigKeys.SETUP_LATCH);
                     setup();
diff --git a/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SameServerDriverLifecycleEffectorTasks.java b/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SameServerDriverLifecycleEffectorTasks.java
index 4d50fad..9cd6149 100644
--- a/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SameServerDriverLifecycleEffectorTasks.java
+++ b/brooklyn-server/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SameServerDriverLifecycleEffectorTasks.java
@@ -73,23 +73,17 @@
            firewall will open the initial port instead. Mostly a problem for SameServerEntity, localhost location.
         */
         // TODO: Remove duplication between this and SoftwareProcessImpl.getRequiredOpenPorts
-        for (ConfigKey<?> k: entity.getEntityType().getConfigKeys()) {
-            Object value;
+        final Set<ConfigKey<?>> configKeys = entity.getEntityType().getConfigKeys();
+        for (ConfigKey<?> k: configKeys) {
             if (PortRange.class.isAssignableFrom(k.getType()) || k.getName().matches(".*\\.port")) {
-                value = entity.config().get(k);
-            } else {
-                // config().get() will cause this to block until all config has been resolved
-                // using config().getRaw(k) means that we won't be able to use e.g. 'http.port: $brooklyn:component("x").attributeWhenReady("foo")'
-                // but that's unlikely to be used
-                Maybe<Object> maybeValue = ((AbstractEntity.BasicConfigurationSupport)entity.config()).getRaw(k);
-                value = maybeValue.isPresent() ? maybeValue.get() : null;
-            }
-
-            Maybe<PortRange> maybePortRange = TypeCoercions.tryCoerce(value, TypeToken.of(PortRange.class));
-
-            if (maybePortRange.isPresentAndNonNull()) {
-                PortRange p = maybePortRange.get();
-                if (p != null && !p.isEmpty()) ports.add(p.iterator().next());
+                Object value = entity.config().get(k);
+                Maybe<PortRange> maybePortRange = TypeCoercions.tryCoerce(value, new TypeToken<PortRange>() {});
+                if (maybePortRange.isPresentAndNonNull()) {
+                    PortRange p = maybePortRange.get();
+                    if (p != null && !p.isEmpty()) {
+                        ports.add(p.iterator().next());
+                    }
+                }
             }
         }
         LOG.debug("getRequiredOpenPorts detected default {} for {}", ports, entity);
diff --git a/brooklyn-server/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SameServerDriverLifecycleEffectorTasksTest.java b/brooklyn-server/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SameServerDriverLifecycleEffectorTasksTest.java
new file mode 100644
index 0000000..6aa8122
--- /dev/null
+++ b/brooklyn-server/software/base/src/test/java/org/apache/brooklyn/entity/software/base/SameServerDriverLifecycleEffectorTasksTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Collection;
+
+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.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.entity.AbstractEntity;
+import org.apache.brooklyn.core.location.PortRanges;
+import org.apache.brooklyn.core.sensor.PortAttributeSensorAndConfigKey;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+public class SameServerDriverLifecycleEffectorTasksTest extends BrooklynAppUnitTestSupport {
+
+    @ImplementedBy(EntityWithConfigImpl.class)
+    public interface EntityWithConfig extends Entity {
+        PortAttributeSensorAndConfigKey PORT = new PortAttributeSensorAndConfigKey(
+                "port", "port", PortRanges.fromString("1234"));
+        ConfigKey<Integer> INTEGER = ConfigKeys.newIntegerConfigKey(
+                "test.integer", "int", 1);
+        ConfigKey<Double> DOUBLE = ConfigKeys.newDoubleConfigKey(
+                "test.double", "double", 2.0);
+        ConfigKey<String> STRING = ConfigKeys.newStringConfigKey(
+                "test.string", "string", "3");
+    }
+
+    public static class EntityWithConfigImpl extends AbstractEntity implements EntityWithConfig {
+    }
+
+    @Test
+    public void testGetRequiredOpenPorts() {
+        SameServerEntity entity = app.createAndManageChild(EntitySpec.create(SameServerEntity.class).child(
+                EntitySpec.create(EntityWithConfig.class)
+                        // Previously SSDLET coerced everything TypeCoercions could handle to a port!
+                        .configure(EntityWithConfig.INTEGER, 1)
+                        .configure(EntityWithConfig.DOUBLE, 2.0)
+                        .configure(EntityWithConfig.STRING, "3")));
+        SameServerDriverLifecycleEffectorTasks effectorTasks = new SameServerDriverLifecycleEffectorTasks();
+        Collection<Integer> requiredPorts = effectorTasks.getRequiredOpenPorts(entity);
+        final ImmutableSet<Integer> expected = ImmutableSet.of(22, 1234);
+        assertEquals(requiredPorts, expected,
+                "expected=" + Iterables.toString(expected) + ", actual=" + Iterables.toString(requiredPorts));
+    }
+
+}
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)
 
 ---------------------------------------------------